npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cocoatouch

v1.2.0

Published

Apple CocoaTouch for the web: UIViewController, UIView, IBOutlet, IBAction, NSNotificationCenter and .xib nibs on top of jQuery

Downloads

549

Readme

CocoaTouch for Web

Apple's CocoaTouch, UIKit and Foundation, for the browser. Write a page as a UIViewController with @IBOutlet and @IBAction bindings and a .xib holding its html, the way you would in Xcode, and run it on jQuery.

src/uipages/home/
    index.js                 imports the other three files, exports the controller
    homeViewController.js    export class HomeViewController extends UIViewController
    homeViewController.css
    homeViewController.xib   plain html, attached to HomeViewController by name at build time
import "UIKit"


export class HomeViewController extends UIViewController {

    @IBOutlet("#title", UILabel) titleLabel
    @IBOutlet("#open", UIButton) openButton

    viewDidLoad() {
        this.titleLabel.text = "Hello"
    }

    @IBAction("#open", UIButton) openButtonTapped(sender) {
        window.location.href = "/api"
    }

    @IBAction(UIKeyModifierFlags.command + UIKeyCommand.input("k")) commandKPressed() {
        window.location.href = "/search"
    }
}

Install

npm install cocoatouch

import "UIKit" works like Swift's import UIKit: the framework's classes become ambient in the page, so files use UIViewController, UILabel or @IBOutlet unqualified. Point webpack at the framework names once:

// webpack.config.js
resolve: {
    alias: require("cocoatouch/webpack/aliases"),
}

The same goes for import "Foundation", import "CoreAnimation" and import "AVKit". Named imports work too, from "cocoatouch" or from a framework entry such as "cocoatouch/UIKit". The lowercase entries cocoatouch/uikit and friends export the same classes without touching globals.

Requirements:

  • jQuery 3 available as the global $ before the bundle runs.
  • Babel with @babel/plugin-proposal-decorators in legacy mode and @babel/plugin-proposal-class-properties, for @IBOutlet and @IBAction in your own classes. The framework itself is plain ES2022.
  • A <cocoatouch></cocoatouch> element in the page. Controllers present into it.

Nibs

A .xib is the html of one view. The webpack loader attaches it to the class of the same name exported by the sibling .js, so codeView.xib becomes CodeView.nib. The link is an import emitted at build time, which survives minification.

// webpack.config.js
{
    test: /\.xib$/,
    use: ["babel-loader", require.resolve("cocoatouch/webpack/xibLoader")],
}

A .xib without a sibling .js, or whose sibling does not export a class of that name, fails the build with a message naming both files.

Lifecycle

present(controller)   viewDidLoad -> viewWillAppear -> viewDidAppear
                      the previous root controller first gets viewWillDisappear -> viewDidDisappear
restore(controller)   rebinds outlets and actions on pre-rendered html: viewWillAppear -> viewDidAppear

Views get awakeFromNib after their outlets are bound, layoutSubviews before their nib is inserted and didMoveToWindow when a pre-rendered page is restored. addSubview links the child into the responder chain, so view.next, view.superview, view.subviews and view.parentViewController() work.

Child view controllers

A controller composes others the way UIKit's containment API does: add the child, then put its view in one of your container views. The container element becomes the child's root, its nib fills it, its outlets and actions bind inside it and viewDidLoad -> viewWillAppear -> viewDidAppear run. Removing the child empties the container, runs viewWillDisappear -> viewDidDisappear and releases the observers it registered.

class OnboardViewController extends UIViewController {

    @IBOutlet("#content", UIView) contentView

    show(step) {
        if (this.current) { this.current.removeFromParent() }
        this.current = new step()
        this.addChild(this.current)
        this.contentView.addSubview(this.current.view)
    }
}

children, parent, willMove({toParent}) and didMove({toParent}) follow UIKit. A plain view's removeFromSuperview() takes its element out of the page.

Animations

UIView.animate runs property changes over a duration: alpha and isHidden fade instead of switching. UIView.transition swaps two views, sliding the new one in from the side named by a flip option or dissolving it.

const card = UIView.loadFromNib(html)
this.listView.insertSubview(card)
card.alpha = 0
UIView.animate({withDuration: 0.5, animations: () => { card.alpha = 1 }})

UIView.transition({from: this.searchView, to: this.passwordView, duration: 0.28, options: [UIView.AnimationOptions.transitionFlipFromRight]})

insertSubview(view, {at}) places a view's nib inside another view without restyling it; tag keeps an integer on a view; accessibilityIdentifier reads or sets a view's element id; an outlet matched by class is given <owner id>-<outlet name> and a subview added in code <superview id>-<n>, so nib-drawn views stay addressable without the app naming them; UIControl.sendActions({for}) fires a control event; key commands reach the deepest bound responder that contains the focused element first and climb to the enclosing ones only when the handler returns false; and a view class with a .xib fills the empty element it is created on, so new SecureTextField("#password") renders like the outlet would.

Table views

A table view works the way it does on iOS: register a cell class for a reuse identifier, dequeue it in the data source, configure its outlets. The cell's row html lives in the .xib of the same name as the cell class.

src/uicomponents/transfers/
    transferCell.js      export class TransferCell extends UITableViewCell { @IBOutlet("#title", UILabel) titleLabel }
    transferCell.xib     <tr><td id="title"></td></tr>
this.tableView.register(TransferCell, {forCellReuseIdentifier: "transfer"})
this.tableView.dataSource = this

tableViewNumberOfRowsInSection(tableView, section) {
    return this.transfers.length
}

tableViewCellForRowAtIndexPath(tableView, indexPath) {
    var cell = tableView.dequeueReusableCell({withIdentifier: "transfer", for: indexPath})
    cell.titleLabel.text = this.transfers[indexPath.row].name
    return cell
}

selectRow({at}), deselectRow({at}), indexPathForSelectedRow, indexPathsForSelectedRows, allowsMultipleSelection, setEditing(true) and cellForRow({at}) behave as on iOS. The delegate receives tableViewDidSelectRowAtIndexPath, tableViewDidDeselectRowAtIndexPath and, in editing mode, tableViewCommitEditingStyleForRowAt(tableView, "delete", indexPath). UICollectionView follows the same shape with IndexPath sections and items.

Controls

  • UIButton: showsActivityIndicator = true swaps the title for a spinner and disables the button until set back; icon = {position, icon, text} places an icon beside the title.
  • UITextField: the delegate gets textFieldDidBeginEditing, textFieldDidEndEditing and textFieldShouldReturn; isFirstResponder, becomeFirstResponder(), resignFirstResponder().
  • UISearchTextField: tokens with insertToken, removeToken, removeAllTokens, validation, paste handling, keyboard selection; the delegate gets tokensUpdated, textFieldWillInsertText and textFieldDidPaste. Selected tokens use the view's tintColor, which defaults to the page's --action-or-selection-color token.
  • UIDatePicker: date, minimumDate, maximumDate, locale, datePickerMode = "yearAndMonth"; wraps the jQuery UI datepicker, so jquery-ui must be on the page where it is used.
  • UIDevice.current (its userInterfaceIdiom is a UIUserInterfaceIdiom: phone, pad or web for a desktop browser): model, platform, userInterfaceIdiom.
  • UITapGestureRecognizer({target, action}) with view.addGestureRecognizer(recognizer).
  • Foundation: DispatchGroup (enter, leave, notify), IndexPath({row, section}), Locale(identifier) with date formats and datepicker regional strings.

Views get an init() hook that runs when the object is constructed, before any nib is attached. UILabel.text and friends sanitize through DOMPurify when the page loads it, and strip scripts otherwise.

cocoatouch/uikit.css carries the few styles the controls need; import it once.

Notifications

NotificationCenter.default is observer keyed. Pass a DOM event target as object to observe that event; leave it out to post and observe in-app notifications.

NotificationCenter.default.addObserver(this, {name: "scroll", object: window, selector: () => this.updateMenu()})
NotificationCenter.default.addObserver(this, {name: "cartDidChange", selector: "cartDidChange"})
NotificationCenter.default.post({name: "cartDidChange", userInfo: {count: 3}})
NotificationCenter.default.removeObserver(this)

Everything a view or controller observes is released when its root controller is dismissed, so window and document listeners never pile up across navigations. Keyboard @IBActions register the same way.

Editor and linter support

The ambient names are declared, so an editor can still jump to UIViewController, complete its members and underline a typo before the build does. The package ships generated .d.ts files for every entry plus types/globals.d.ts for the names import "UIKit" makes ambient. In a JavaScript project, point jsconfig.json at them once:

{
    "compilerOptions": {
        "checkJs": true,
        "experimentalDecorators": true,
        "paths": {
            "UIKit": ["./node_modules/cocoatouch/types/UIKit.d.ts"],
            "Foundation": ["./node_modules/cocoatouch/types/Foundation.d.ts"]
        }
    },
    "files": ["node_modules/cocoatouch/types/globals.d.ts"],
    "include": ["src"]
}

For ESLint, cocoatouch/eslint/globals exports the same names as a globals object, so no-undef accepts them and still flags misspellings.

Server side rendering

Capture the <cocoatouch> inner html after present, serve it with window.__PRERENDERED = true, and call controller.restore(controller) instead of present. Outlets and actions rebind to the existing DOM, and views added at runtime through addSubview are found again by their @IBAction selectors.

Classes

| Foundation | UIKit | Other | |---|---|---| | NSObject | UIResponder, UIView, UIViewController | CALayer | | NotificationCenter | UIControl, UIButton, UILabel, UITextField, UISearchTextField | AVPlayer | | | UIImageView, UIImage, UIColor, UIControlEvent | | | | UIScrollView, UITableView, UITableViewCell | | | | UIPickerView, UISegmentedControl, UISwitch | | | | UIProgressView, UIActivityIndicatorView | | | DispatchGroup, IndexPath, Locale | UIDevice, UIDatePicker, UICollectionView | | | | IBOutlet, IBAction, UIKeyCommand, UIKeyModifierFlags | |

Sample

sample/ is a small app with a menu of pages, one per part of UIKit: buttons, labels, text fields, a custom view with its own nib, and a table view with a custom cell. It depends on this package through file:.., so it runs against the working tree.

cd sample
npm install
npm start

Then open http://localhost:8080.

Tests

npm test