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

@plastic-js/tsumiki

v0.1.23

Published

A UI component library for Plastic JS.

Readme

@plastic-js/tsumiki

A UI component library for Plastic JS.

Installation

npm install @plastic-js/tsumiki

Usage

import { Dialog, Combobox, Tooltip, SelectMobile, FilterableSelectMobile } from '@plastic-js/tsumiki'

Components

SelectMobile

A mobile-optimized bottom-sheet select built with a compound parts pattern. Renders a trigger button and a draggable sheet overlay — ideal for touch interfaces.

import { createSignal } from '@plastic-js/plastic'
import { SelectMobile } from '@plastic-js/tsumiki'

function Example(){
  const value = createSignal(null)
  const items = [
    { value: 'tpe', label: 'Taipei' },
    { value: 'kxg', label: 'Kaohsiung' },
  ]

  return (
    <SelectMobile.Root
      value={value}
      onValueChange={v => value(v)}
      items={items}
    >
      <SelectMobile.Trigger placeholder='Choose a city' />
      <SelectMobile.Content />
    </SelectMobile.Root>
  )
}

SelectMobile.Root props:

| Prop | Type | Default | Description | |---|---|---|---| | value | string \| (() => string) | '' | Current selected value | | onValueChange | (value: string) => void | — | Called when an item is selected | | items | array \| (() => array) | [] | Data array | | itemToValue | (item) => string | item.value | Extract value from an item | | itemToLabel | (item) => node | item.label | Extract label from an item | | open | (() => boolean) | — | Controlled open state (getter) | | defaultOpen | boolean | false | Initial open state (uncontrolled) | | onOpenChange | (isOpen: boolean) => void | — | Called when open state changes |

SelectMobile.Trigger props:

| Prop | Type | Default | Description | |---|---|---|---| | placeholder | string | 'Select' | Placeholder text when no item is selected | | className | string | — | CSS class for the trigger button | | children | node | — | Custom trigger content (replaces default label + chevron) |

Note on Trigger element: The trigger renders <div role="button" tabIndex={0}> instead of a native <button> as a defense-in-depth measure against a Chrome iOS (WebKit) focus-lock bug.

The primary fix is in SelectMobile.Content: the sheet is rendered inline (no <Portal>) so it stays inside the parent Dialog's focus-trap boundary. However, the <div role="button"> is kept as an additional safeguard — in WebKit, tapping a <button> inside a scrollable container with -webkit-overflow-scrolling: touch can pin activeElement to the button, causing subsequent focus() calls on the filter <input> to be silently ignored. A <div role="button"> is semantically equivalent for accessibility but does not trigger this WebKit focus-lock behaviour.

Typically a native <button> would be preferred for keyboard tab navigation (tabIndex works natively). On mobile, however, keyboard tab navigation is irrelevant — the sheet is operated via touch, and the Escape key for closing is handled by the Dialog-like overlay, not by tab order. The tabIndex={0} on the <div> preserves keyboard discoverability for assistive technology while working around the iOS focus trap.

SelectMobile.Content props:

| Prop | Type | Default | Description | |---|---|---|---| | clearable | boolean | false | Show a "clear selection" row at the top | | clearLabel | string | 'None' | Label for the clear row | | className | string | — | CSS class for the sheet panel | | backdropClassName | string | — | CSS class for the backdrop overlay | | backdropStyle | object | — | Inline style for the backdrop overlay |

SelectMobile.Item props:

| Prop | Type | Default | Description | |---|---|---|---| | item | any | — | Item from the items array (resolves value/label automatically) | | value | string | — | Explicit value (use when children are custom) | | className | string | — | CSS class for the item button | | children | node | — | Custom item content |


FilterableSelectMobile

A mobile bottom-sheet select with a filter input at the top of the list. Wraps SelectMobile internally and exposes a flat single-component API.

import { createSignal } from '@plastic-js/plastic'
import { FilterableSelectMobile } from '@plastic-js/tsumiki'

function Example(){
  const value = createSignal(null)
  const cities = [
    { value: 'tpe', label: 'Taipei' },
    { value: 'kxg', label: 'Kaohsiung' },
  ]

  return (
    <FilterableSelectMobile
      items={cities}
      itemToValue={item => item.value}
      itemToLabel={item => item.label}
      value={value}
      onValueChange={v => value(v)}
      placeholder='Search city…'
    />
  )
}

Props:

| Prop | Type | Default | Description | |---|---|---|---| | items | array \| (() => array) | [] | Data array | | value | string \| (() => string) | '' | Current selected value | | onValueChange | (value: string) => void | — | Called when an item is selected | | itemToValue | (item) => string | item.value ?? item | Extract value from an item | | itemToLabel | (item) => string | item.label ?? String(item) | Extract label from an item | | placeholder | string | 'Select' | Placeholder text on the trigger | | filter | (item, query, itemToLabel) => boolean | substring match | Custom filter function | | clearable | boolean | false | Show a "clear" row at the top | | clearLabel | string | 'None' | Label for the clear row | | backdropClassName | string | — | CSS class for the backdrop overlay | | backdropStyle | object | — | Inline style for the backdrop overlay |

The filter input is auto-focused when the sheet opens and cleared when it closes.

License

MIT