{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-list",
  "type": "registry:component",
  "title": "List",
  "description": "Rows with one tab stop, type-ahead and a row action",
  "author": "sudo-kw <https://github.com/sudo-kw>",
  "dependencies": [
    "@gpuix/native@0.11.0",
    "@gpuix/react@0.11.0",
    "effect@4.1.2",
    "react@^19.2.4"
  ],
  "registryDependencies": [
    "https://gpuixui.sudokw.com/gpuix-primitives.json",
    "https://gpuixui.sudokw.com/gpuix-tokens.json",
    "https://gpuixui.sudokw.com/gpuix-tw.json"
  ],
  "files": [
    {
      "path": "packages/components/src/internal/clock.ts",
      "target": "@ui/gpuixui/components/internal/clock.ts",
      "type": "registry:lib",
      "content": "import { Effect } from \"effect\";\nimport * as Clock from \"effect/Clock\";\n\nexport const monotonicNanos = (): bigint =>\n  Effect.runSync(Clock.monotonicTimeNanos);\n\nexport const hoverDelay = (value: number, fallback: number): number =>\n  Number.isFinite(value) ? Math.max(0, value) : fallback;\n"
    },
    {
      "path": "packages/components/src/internal/collection.ts",
      "target": "@ui/gpuixui/components/internal/collection.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport { pipe } from \"effect\";\n\nexport interface CollectionItem {\n  readonly id: number;\n  readonly disabled: boolean;\n}\n\nexport interface Collection {\n  readonly register: (item: CollectionItem) => () => void;\n\n  readonly items: () => ReadonlyArray<CollectionItem>;\n}\n\nexport interface ValueItem extends CollectionItem {\n  readonly value: string;\n}\n\nexport const upsertValue = <Item extends ValueItem>(\n  items: ReadonlyArray<Item>,\n  item: Item,\n): ReadonlyArray<Item> =>\n  Arr.some(items, (existing) => existing.value === item.value)\n    ? Arr.map(items, (existing) =>\n        existing.value === item.value ? item : existing,\n      )\n    : Arr.append(items, item);\n\nexport const insert = (\n  items: ReadonlyArray<CollectionItem>,\n  item: CollectionItem,\n): ReadonlyArray<CollectionItem> =>\n  Arr.some(items, (existing) => existing.id === item.id)\n    ? Arr.map(items, (existing) => (existing.id === item.id ? item : existing))\n    : Arr.append(items, item);\n\nexport const remove = (\n  items: ReadonlyArray<CollectionItem>,\n  id: number,\n): ReadonlyArray<CollectionItem> =>\n  Arr.filter(items, (existing) => existing.id !== id);\n\nexport const enabledIndexes = (\n  items: ReadonlyArray<CollectionItem>,\n): ReadonlyArray<number> =>\n  Arr.map(\n    Arr.filter(\n      Arr.map(items, (item, index) => ({ index, disabled: item.disabled })),\n      (entry) => !entry.disabled,\n    ),\n    (entry) => entry.index,\n  );\n\nconst nth = (\n  indexes: ReadonlyArray<number>,\n  position: number,\n): Option.Option<number> => Option.fromNullishOr(indexes[position]);\n\nexport const firstEnabled = (\n  items: ReadonlyArray<CollectionItem>,\n): Option.Option<number> => nth(enabledIndexes(items), 0);\n\nexport const lastEnabled = (\n  items: ReadonlyArray<CollectionItem>,\n): Option.Option<number> =>\n  pipe(nth(enabledIndexes(items), Arr.length(enabledIndexes(items)) - 1));\n\nexport const indexOfId = (\n  items: ReadonlyArray<CollectionItem>,\n  id: number,\n): Option.Option<number> => Arr.findFirstIndex(items, (item) => item.id === id);\n\nexport const nextEnabled = (\n  items: ReadonlyArray<CollectionItem>,\n  current: Option.Option<number>,\n  delta: 1 | -1,\n): Option.Option<number> => {\n  const pool = enabledIndexes(items);\n  const size = Arr.length(pool);\n  if (size === 0) return Option.none();\n\n  const step = (position: number) =>\n    nth(pool, (position + delta + size) % size);\n\n  return pipe(\n    current,\n    Option.match({\n      onNone: () => (delta > 0 ? nth(pool, 0) : nth(pool, size - 1)),\n      onSome: (index) =>\n        pipe(\n          Arr.findFirstIndex(pool, (candidate) => candidate === index),\n          Option.match({\n            onSome: step,\n            onNone: () =>\n              delta > 0\n                ? pipe(\n                    Arr.findFirst(pool, (candidate) => candidate > index),\n                    Option.orElse(() => nth(pool, 0)),\n                  )\n                : pipe(\n                    Arr.findLast(pool, (candidate) => candidate < index),\n                    Option.orElse(() => nth(pool, size - 1)),\n                  ),\n          }),\n        ),\n    }),\n  );\n};\n\nexport const createCollection = (): Collection => {\n  let entries: ReadonlyArray<CollectionItem> = [];\n  return {\n    register: (item) => {\n      entries = insert(entries, item);\n      return () => {\n        entries = remove(entries, item.id);\n      };\n    },\n    items: () => entries,\n  };\n};\n"
    },
    {
      "path": "packages/components/src/internal/key.ts",
      "target": "@ui/gpuixui/components/internal/key.ts",
      "type": "registry:lib",
      "content": "import type { EventPayload, KeyEvent, Props } from \"@gpuix/react\";\nimport * as Option from \"effect/Option\";\n\nexport type KeyPhase = \"down\" | \"up\";\n\nexport const isActivation = (event: EventPayload, phase: KeyPhase): boolean => {\n  if (event.isHeld === true) return false;\n  if (event.key === \"enter\") return phase === \"down\";\n  return event.key === \"space\" && phase === \"up\";\n};\n\nexport const swallowsDefault = (\n  event: EventPayload,\n  phase: KeyPhase,\n): boolean => event.key === \"space\" && phase === \"down\";\n\nexport const activationHandlers = (\n  activate: (event: KeyEvent) => void,\n  observe: Pick<Props, \"onKeyDown\" | \"onKeyUp\"> = {},\n): Pick<Props, \"onKeyDown\" | \"onKeyUp\"> => ({\n  onKeyDown: (event: KeyEvent) => {\n    if (swallowsDefault(event, \"down\")) event.preventDefault();\n    if (isActivation(event, \"down\")) activate(event);\n    observe.onKeyDown?.(event);\n  },\n  onKeyUp: (event: KeyEvent) => {\n    if (isActivation(event, \"up\")) activate(event);\n    observe.onKeyUp?.(event);\n  },\n});\n\nexport const isEnter = (event: EventPayload): boolean => event.key === \"enter\";\n\nexport const isDown = (event: EventPayload): boolean =>\n  event.key === \"down\" || (event.key === \"n\" && event.modifiers?.ctrl === true);\n\nexport const isUp = (event: EventPayload): boolean =>\n  event.key === \"up\" || (event.key === \"p\" && event.modifiers?.ctrl === true);\n\nexport const isRight = (event: EventPayload): boolean => event.key === \"right\";\n\nexport const isLeft = (event: EventPayload): boolean => event.key === \"left\";\n\nexport const isFirst = (event: EventPayload): boolean => event.key === \"home\";\n\nexport const isLast = (event: EventPayload): boolean => event.key === \"end\";\n\nexport const isPageUp = (event: EventPayload): boolean =>\n  event.key === \"pageup\";\n\nexport const isPageDown = (event: EventPayload): boolean =>\n  event.key === \"pagedown\";\n\nexport const isDismiss = (event: EventPayload): boolean =>\n  event.key === \"escape\";\n\nexport const isTab = (event: EventPayload): boolean => event.key === \"tab\";\n\nexport const isSelectAll = (event: EventPayload): boolean =>\n  event.key === \"a\" &&\n  (event.modifiers?.cmd === true || event.modifiers?.ctrl === true);\n\nexport const isMenuKey = (event: EventPayload): boolean =>\n  (event.key === \"f10\" && event.modifiers?.shift === true) ||\n  event.key === \"contextmenu\";\n\nexport const axisDirection = (\n  event: EventPayload,\n  orientation: \"horizontal\" | \"vertical\",\n): Option.Option<1 | -1> => {\n  if (event.key === \"right\") return Option.some(1);\n  if (event.key === \"left\") return Option.some(-1);\n  if (orientation === \"vertical\") {\n    if (isDown(event)) return Option.some(1);\n    if (isUp(event)) return Option.some(-1);\n  }\n  return Option.none();\n};\n"
    },
    {
      "path": "packages/components/src/internal/parts.ts",
      "target": "@ui/gpuixui/components/internal/parts.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as P from \"effect/Predicate\";\nimport {\n  Children,\n  Fragment,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n} from \"react\";\n\nexport type PartComponent = (props: never) => ReactNode;\n\nexport type PartChildren =\n  | ReactElement<unknown>\n  | boolean\n  | null\n  | undefined\n  | ReadonlyArray<PartChildren>;\n\nexport interface PartsContract {\n  readonly parent: string;\n\n  readonly family: ReadonlyArray<PartComponent>;\n\n  readonly accepts: ReadonlyArray<PartComponent>;\n\n  readonly hint?: string;\n}\n\nconst componentName = (type: unknown): string =>\n  P.isString(type)\n    ? type\n    : P.isFunction(type)\n      ? type.name || \"anonymous\"\n      : \"element\";\n\nconst fragmentChildren = (element: ReactElement<unknown>): ReactNode =>\n  hasChildren(element.props) ? element.props.children : undefined;\n\nconst hasChildren = (\n  value: unknown,\n): value is { readonly children?: ReactNode } =>\n  P.isObject(value) && \"children\" in value;\n\nconst leaves = (children: ReactNode): ReadonlyArray<unknown> =>\n  Arr.flatMap(Children.toArray(children), (child) =>\n    isValidElement<unknown>(child) && child.type === Fragment\n      ? leaves(fragmentChildren(child))\n      : [child],\n  );\n\nexport const assertParts = (\n  contract: PartsContract,\n  children: ReactNode,\n): void => {\n  Arr.forEach(leaves(children), (leaf) => {\n    if (!isValidElement(leaf)) return;\n    if (Arr.some(contract.accepts, (part) => part === leaf.type)) return;\n    if (!Arr.some(contract.family, (part) => part === leaf.type)) return;\n\n    throw new Error(\n      `${componentName(leaf.type)} cannot be used inside ${contract.parent}.` +\n        (contract.hint === undefined ? \"\" : ` ${contract.hint}`),\n    );\n  });\n};\n"
    },
    {
      "path": "packages/components/src/internal/recipe.ts",
      "target": "@ui/gpuixui/components/internal/recipe.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { createRecipeCache } from \"../../tw/index.js\";\nimport type { Theme } from \"./theme.js\";\n\nexport const recipeStyle: (recipe: string, theme: Theme) => StyleDesc =\n  createRecipeCache(512);\n"
    },
    {
      "path": "packages/components/src/internal/ref.ts",
      "target": "@ui/gpuixui/components/internal/ref.ts",
      "type": "registry:lib",
      "content": "import type { Ref, RefCallback } from \"react\";\nimport { useCallback } from \"react\";\n\nconst assign = <T>(ref: Ref<T> | undefined, value: T): void => {\n  if (ref === null || ref === undefined) return;\n  if (typeof ref === \"function\") {\n    ref(value);\n    return;\n  }\n  ref.current = value;\n};\n\nexport const useMergedRef = <T>(\n  internal: Ref<T>,\n  caller: Ref<T> | undefined,\n): RefCallback<T> =>\n  useCallback(\n    (value: T) => {\n      assign(internal, value);\n      assign(caller, value);\n    },\n    [internal, caller],\n  );\n"
    },
    {
      "path": "packages/components/src/internal/theme.ts",
      "target": "@ui/gpuixui/components/internal/theme.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport * as Schema from \"effect/Schema\";\nimport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  brandTextOn,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  pointerWash,\n  selectionOn,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n  type BoxShadow,\n  type ElevationLevel,\n  type FindHighlightSource,\n  type NativeTextFill,\n  type PlatformName,\n  type PointerWashSurface,\n  type StatusTone,\n  type SyntaxColors,\n  type SyntaxRole,\n  type TextRole,\n  type Theme,\n  type ThemeColors,\n} from \"../../tokens/index.js\";\n\nexport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n};\nexport type {\n  BoxShadow,\n  ElevationLevel,\n  FindHighlightSource,\n  NativeTextFill,\n  PlatformName,\n  StatusTone,\n  SyntaxColors,\n  SyntaxRole,\n  TextRole,\n  Theme,\n};\nexport { ThemeProvider, useTheme } from \"../../tokens/react.js\";\n\nexport const keyboardRing = (theme: Theme, invalid = false): StyleDesc =>\n  controlFocusRing(theme, { invalid }) ?? {};\n\nexport interface ControlRingState {\n  readonly disabled?: boolean;\n\n  readonly invalid?: boolean;\n}\n\nexport const controlRing = (\n  theme: Theme,\n  state: ControlRingState = {},\n): StyleDesc => controlFocusRing(theme, state) ?? {};\n\nexport const pressWash = (theme: Theme, replaced: PointerWashSurface): string =>\n  pointerWash(theme, \"active\", replaced) ?? theme.colors.active;\n\nexport const selectionWash = (theme: Theme): StyleDesc => ({\n  selectionColor: theme.colors.selection,\n});\n\nexport const keepContent: StyleDesc = { keyboardFocusDim: false };\n\nexport const disabledFade = (\n  disabled: boolean,\n): { readonly opacity: number } | undefined =>\n  disabled ? { opacity: disabledOpacity } : undefined;\n\nconst Tones = Schema.Literals([\"info\", \"success\", \"warning\", \"danger\"]);\n\nexport type Tone = typeof Tones.Type;\n\nexport const toneOf = (value: unknown): Tone =>\n  Schema.is(Tones)(value) ? value : \"info\";\n\nexport interface ToneStyle {\n  readonly fg: string;\n  readonly bg: string;\n  readonly border: string;\n}\n\nconst TOKEN_TONE: Readonly<Record<Tone, StatusTone>> = {\n  info: \"info\",\n  success: \"success\",\n  warning: \"warning\",\n  danger: \"destructive\",\n};\n\nexport const tone = (theme: Theme, value: Tone): ToneStyle => {\n  const { background, border, foreground } = statusTone(\n    theme,\n    TOKEN_TONE[value],\n  );\n  return { bg: background, border, fg: foreground };\n};\n\nconst softRoles: Readonly<Record<Tone, keyof ThemeColors>> = {\n  info: \"infoSoft\",\n  success: \"successSoft\",\n  warning: \"warningSoft\",\n  danger: \"destructiveSoft\",\n};\n\nexport const messageLink = (theme: Theme, value: Tone): string =>\n  brandTextOn(theme, softRoles[value]);\n\nexport const messageSelection = (theme: Theme, value: Tone): string =>\n  selectionOn(theme, softRoles[value]);\n\nexport const toneSurface = (theme: Theme, value: Tone): StyleDesc => {\n  const { bg, border, fg } = tone(theme, value);\n  return {\n    backgroundColor: bg,\n    color: fg,\n    borderWidth: theme.borderWidth.thin,\n    borderColor: border,\n  };\n};\n"
    },
    {
      "path": "packages/components/src/list.tsx",
      "target": "@ui/gpuixui/components/list.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type {\n  EventPayload,\n  KeyEvent,\n  Props,\n  PublicInstance,\n} from \"@gpuix/react\";\nimport { useGpuix } from \"@gpuix/react\";\nimport {\n  rowHeightOf,\n  spaceValue,\n  type DensitySource,\n  type SpaceSource,\n} from \"../primitives/core/index.js\";\nimport {\n  mergeStyles,\n  resolveStyle,\n  type StateStyle,\n} from \"@gpuix/react/floating\";\nimport {\n  cn,\n  LIST_ITEM,\n  LIST_ITEM_DESCRIPTION,\n  LIST_ITEM_TITLE,\n  LIST_ROOT,\n} from \"../tw/index.js\";\nimport { Match, pipe } from \"effect\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type ReactNode,\n} from \"react\";\nimport {\n  firstEnabled,\n  lastEnabled,\n  nextEnabled,\n  type CollectionItem,\n} from \"./internal/collection.js\";\nimport { useMergedRef } from \"./internal/ref.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport {\n  isActivation,\n  isDown,\n  isEnter,\n  isFirst,\n  isLast,\n  isSelectAll,\n  isUp,\n  swallowsDefault,\n} from \"./internal/key.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport { controlRing, pressWash, useTheme } from \"./internal/theme.js\";\nimport { keyOrigin } from \"./list/origin.js\";\nimport { typedChar, useTypeahead } from \"./typeahead.js\";\nimport { range, sameValues, selectAll, toggle } from \"./list/selection.js\";\nimport { makeRowStore, type RowStore } from \"./list/store.js\";\n\nexport type ListSelectionMode = \"none\" | \"single\" | \"multiple\";\n\nexport const listRowHeight = (\n  theme: DensitySource & SpaceSource,\n  dense: boolean,\n): number =>\n  dense ? rowHeightOf(theme) : rowHeightOf(theme) + spaceValue(theme, \"md\");\n\ninterface Row {\n  readonly value: string;\n  readonly disabled: boolean;\n  readonly nativeId: number;\n  readonly label: string;\n}\n\ninterface ListContextValue {\n  readonly selectable: boolean;\n  readonly dense: boolean;\n\n  readonly store: RowStore;\n\n  readonly activate: (value: string, event: EventPayload) => void;\n\n  readonly noteFocus: (value: string) => void;\n\n  readonly register: (row: Row) => () => void;\n}\n\nconst ListContext = createContext<ListContextValue | undefined>(undefined);\n\ninterface RowState {\n  readonly selected: boolean;\n  readonly disabled: boolean;\n}\n\nconst RowStateContext = createContext<RowState>({\n  selected: false,\n  disabled: false,\n});\n\nconst useListContext = (): ListContextValue => {\n  const value = useContext(ListContext);\n  if (value === undefined) {\n    throw new Error(\"ListItem must be used inside List\");\n  }\n  return value;\n};\n\nconst upsert = (rows: ReadonlyArray<Row>, row: Row): ReadonlyArray<Row> =>\n  Option.match(\n    Arr.findFirst(rows, (candidate) => candidate.value === row.value),\n    {\n      onNone: () => Arr.append(rows, row),\n      onSome: (current) =>\n        current.disabled === row.disabled &&\n        current.nativeId === row.nativeId &&\n        current.label === row.label\n          ? rows\n          : Arr.map(rows, (candidate) =>\n              candidate.value === row.value ? row : candidate,\n            ),\n    },\n  );\n\nconst items = (rows: ReadonlyArray<Row>): ReadonlyArray<CollectionItem> =>\n  Arr.map(rows, (row) => ({ id: row.nativeId, disabled: row.disabled }));\n\nconst indexOf = (\n  rows: ReadonlyArray<Row>,\n  value: Option.Option<string>,\n): Option.Option<number> =>\n  Option.flatMap(value, (target) =>\n    Arr.findFirstIndex(rows, (row) => row.value === target),\n  );\n\nconst rowAt = (\n  rows: ReadonlyArray<Row>,\n  index: Option.Option<number>,\n): Option.Option<Row> =>\n  Option.flatMap(index, (position) => Arr.get(rows, position));\n\nconst tabbableValue = (\n  rows: ReadonlyArray<Row>,\n  active: Option.Option<string>,\n  selected: ReadonlyArray<string>,\n  selectable: boolean,\n): Option.Option<string> =>\n  pipe(\n    Option.flatMap(active, (value) =>\n      pipe(\n        Arr.findFirst(rows, (row) => row.value === value),\n        Option.filter((row) => !row.disabled),\n        Option.map((row) => row.value),\n      ),\n    ),\n    Option.orElse(() =>\n      selectable\n        ? Option.map(\n            Arr.findFirst(\n              rows,\n              (row) => !row.disabled && Arr.contains(row.value)(selected),\n            ),\n            (row) => row.value,\n          )\n        : Option.none(),\n    ),\n    Option.orElse(() =>\n      Option.map(\n        Arr.findFirst(rows, (row) => !row.disabled),\n        (row) => row.value,\n      ),\n    ),\n  );\n\nconst controlledValues = (\n  props: ListProps,\n): ReadonlyArray<string> | undefined =>\n  Match.value(props).pipe(\n    Match.when({ selectionMode: \"multiple\" }, (p) => p.value),\n    Match.when({ selectionMode: \"single\" }, (p) =>\n      Option.getOrUndefined(Option.map(Option.fromNullishOr(p.value), Arr.of)),\n    ),\n    Match.orElse(() => undefined),\n  );\n\nconst initialValues = (props: ListProps): ReadonlyArray<string> =>\n  Match.value(props).pipe(\n    Match.when({ selectionMode: \"multiple\" }, (p) => p.defaultValue ?? []),\n    Match.when({ selectionMode: \"single\" }, (p) =>\n      Option.match(Option.fromNullishOr(p.defaultValue), {\n        onNone: () => Arr.empty<string>(),\n        onSome: (value) => (value === \"\" ? Arr.empty<string>() : Arr.of(value)),\n      }),\n    ),\n    Match.orElse(() => Arr.empty<string>()),\n  );\n\nconst report = (props: ListProps, next: ReadonlyArray<string>): void =>\n  Match.value(props).pipe(\n    Match.when({ selectionMode: \"multiple\" }, (p) => p.onValueChange?.(next)),\n    Match.when({ selectionMode: \"single\" }, (p) =>\n      p.onValueChange?.(next[0] ?? \"\"),\n    ),\n    Match.orElse(() => undefined),\n  );\n\ninterface ListBaseProps extends Omit<Props, \"children\"> {\n  readonly dense?: boolean;\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\ninterface Activatable {\n  readonly onActivate?: (value: string) => void;\n}\n\nexport type ListProps =\n  | (ListBaseProps &\n      Activatable & {\n        readonly selectionMode?: \"none\";\n        readonly value?: never;\n        readonly defaultValue?: never;\n        readonly onValueChange?: never;\n      })\n  | (ListBaseProps &\n      Activatable & {\n        readonly selectionMode: \"single\";\n        readonly value?: string;\n        readonly defaultValue?: string;\n        readonly onValueChange?: (value: string) => void;\n      })\n  | (ListBaseProps & {\n      readonly selectionMode: \"multiple\";\n\n      readonly onActivate?: never;\n      readonly value?: ReadonlyArray<string>;\n      readonly defaultValue?: ReadonlyArray<string>;\n      readonly onValueChange?: (values: ReadonlyArray<string>) => void;\n    });\n\nexport const List = (props: ListProps) => {\n  const theme = useTheme();\n  const renderer = useGpuix().renderer;\n\n  const {\n    selectionMode: _selectionMode,\n    value: _value,\n    defaultValue: _defaultValue,\n    onValueChange: _onValueChange,\n    onActivate,\n    dense = false,\n    className,\n    onKeyDown,\n    onKeyUp,\n    children,\n    style,\n    ...rest\n  } = props;\n  const selectionMode = props.selectionMode ?? \"none\";\n  const selectable = selectionMode !== \"none\";\n  const multiple = selectionMode === \"multiple\";\n\n  const controlled = Option.fromNullishOr(controlledValues(props));\n  const [uncontrolled, setUncontrolled] = useState<ReadonlyArray<string>>(() =>\n    initialValues(props),\n  );\n  const selected = Option.getOrElse(controlled, () => uncontrolled);\n  const [anchor, setAnchor] = useState<Option.Option<string>>(Option.none());\n  const [active, setActive] = useState<Option.Option<string>>(Option.none());\n  const [rows, setRows] = useState<ReadonlyArray<Row>>([]);\n  const [store] = useState(makeRowStore);\n\n  const commit = useCallback(\n    (next: ReadonlyArray<string>) => {\n      if (sameValues(next, selected)) return;\n      if (Option.isNone(controlled)) setUncontrolled(next);\n      report(props, next);\n    },\n    [controlled, props, selected],\n  );\n\n  const register = useCallback(\n    (row: Row) => {\n      setRows((current) => upsert(current, row));\n      return () =>\n        setRows((current) =>\n          Arr.filter(current, (candidate) => candidate.value !== row.value),\n        );\n    },\n    [setRows],\n  );\n\n  const focusRow = useCallback(\n    (row: Row) => {\n      setActive(Option.some(row.value));\n      renderer?.focusElement?.(row.nativeId);\n    },\n    [renderer],\n  );\n\n  const noteFocus = useCallback((value: string) => {\n    setActive(Option.some(value));\n  }, []);\n\n  const moveBy = useCallback(\n    (delta: 1 | -1, extend: boolean, from: Option.Option<string>) => {\n      pipe(\n        rowAt(rows, nextEnabled(items(rows), indexOf(rows, from), delta)),\n        Option.match({\n          onNone: () => undefined,\n          onSome: (row) => {\n            focusRow(row);\n            if (!extend || !multiple) {\n              setAnchor(Option.some(row.value));\n              return;\n            }\n            commit(\n              range(\n                rows,\n                Option.getOrElse(anchor, () => row.value),\n                row.value,\n              ),\n            );\n          },\n        }),\n      );\n    },\n    [anchor, commit, focusRow, multiple, rows],\n  );\n\n  const jumpTo = useCallback(\n    (edge: \"first\" | \"last\") => {\n      const index =\n        edge === \"first\" ? firstEnabled(items(rows)) : lastEnabled(items(rows));\n      pipe(\n        rowAt(rows, index),\n        Option.match({\n          onNone: () => undefined,\n          onSome: (row) => {\n            focusRow(row);\n            setAnchor(Option.some(row.value));\n          },\n        }),\n      );\n    },\n    [focusRow, rows],\n  );\n\n  const enabledRow = useCallback(\n    (target: string) =>\n      pipe(\n        Arr.findFirst(rows, (row) => row.value === target),\n        Option.filter((row) => !row.disabled),\n      ),\n    [rows],\n  );\n\n  const select = useCallback(\n    (target: string) => {\n      pipe(\n        enabledRow(target),\n        Option.match({\n          onNone: () => undefined,\n          onSome: (row) => {\n            setAnchor(Option.some(row.value));\n            commit(multiple ? toggle(selected, row.value) : Arr.of(row.value));\n          },\n        }),\n      );\n    },\n    [commit, enabledRow, multiple, selected],\n  );\n\n  const activate = useCallback(\n    (target: string, event: EventPayload) => {\n      pipe(\n        enabledRow(target),\n        Option.match({\n          onNone: () => undefined,\n          onSome: (row) => {\n            focusRow(row);\n            if (!selectable) {\n              onActivate?.(row.value);\n              return;\n            }\n            const extend = multiple && event.modifiers?.shift === true;\n            const additive =\n              multiple &&\n              (event.modifiers?.cmd === true || event.modifiers?.ctrl === true);\n            if (extend) {\n              commit(\n                range(\n                  rows,\n                  Option.getOrElse(anchor, () => row.value),\n                  row.value,\n                ),\n              );\n              return;\n            }\n            setAnchor(Option.some(row.value));\n            commit(additive ? toggle(selected, row.value) : Arr.of(row.value));\n            if (!multiple) onActivate?.(row.value);\n          },\n        }),\n      );\n    },\n    [\n      anchor,\n      commit,\n      enabledRow,\n      focusRow,\n      multiple,\n      onActivate,\n      rows,\n      selectable,\n      selected,\n    ],\n  );\n\n  const selectActive = useCallback(\n    (target: Option.Option<string>) => {\n      pipe(\n        target,\n        Option.match({\n          onNone: () => undefined,\n          onSome: (value) => select(value),\n        }),\n      );\n    },\n    [select],\n  );\n\n  const activateActive = useCallback(\n    (target: Option.Option<string>) => {\n      pipe(\n        Option.flatMap(target, enabledRow),\n        Option.match({\n          onNone: () => undefined,\n          onSome: (row) => onActivate?.(row.value),\n        }),\n      );\n    },\n    [enabledRow, onActivate],\n  );\n\n  const selectEveryRow = useCallback(() => {\n    if (!multiple) return;\n    const next = selectAll(rows);\n    pipe(\n      Arr.head(next),\n      Option.match({\n        onNone: () => undefined,\n        onSome: (value) => setAnchor(Option.some(value)),\n      }),\n    );\n    commit(next);\n  }, [commit, multiple, rows]);\n\n  const typeahead = useTypeahead(rows, indexOf(rows, active), (row) => {\n    focusRow(row);\n\n    setAnchor(Option.some(row.value));\n  });\n\n  const originOf = (event: EventPayload): Option.Option<string> =>\n    keyOrigin(\n      rows,\n      active,\n      Option.fromNullishOr(event.elementId),\n      Option.fromNullishOr(renderer?.getFocusedElementId?.()),\n    );\n\n  const handleKeyDown = (event: KeyEvent): void => {\n    if (!selectable) return;\n\n    if (swallowsDefault(event, \"down\")) event.preventDefault();\n    const origin = originOf(event);\n    const extend = multiple && event.modifiers?.shift === true;\n\n    pipe(\n      origin,\n      Option.match({\n        onNone: () => undefined,\n        onSome: (value) => {\n          if (!Option.contains(active, value)) setActive(Option.some(value));\n        },\n      }),\n    );\n    pipe(\n      typedChar(event),\n      Option.match({\n        onSome: typeahead,\n        onNone: () =>\n          Match.value(event).pipe(\n            Match.when(isEnter, () => {\n              if (selectable) selectActive(origin);\n              activateActive(origin);\n            }),\n            Match.when(\n              (press) => isActivation(press, \"down\"),\n              () => selectActive(origin),\n            ),\n            Match.when(isSelectAll, selectEveryRow),\n            Match.when(isDown, () => moveBy(1, extend, origin)),\n            Match.when(isUp, () => moveBy(-1, extend, origin)),\n            Match.when(isFirst, () => jumpTo(\"first\")),\n            Match.when(isLast, () => jumpTo(\"last\")),\n            Match.orElse(() => undefined),\n          ),\n      }),\n    );\n  };\n\n  const handleKeyUp = (event: KeyEvent): void => {\n    if (!selectable) return;\n    if (isActivation(event, \"up\")) selectActive(originOf(event));\n  };\n\n  const tabbable = useMemo(\n    () => tabbableValue(rows, active, selected, selectable),\n    [active, rows, selectable, selected],\n  );\n\n  const latest = useRef(activate);\n  useLayoutEffect(() => {\n    latest.current = activate;\n  });\n  const stableActivate = useCallback(\n    (value: string, event: EventPayload) => latest.current(value, event),\n    [],\n  );\n\n  useLayoutEffect(() => {\n    store.write({ active, selected, tabbable });\n  }, [active, selected, store, tabbable]);\n\n  const context = useMemo<ListContextValue>(\n    () => ({\n      selectable,\n      dense,\n      store,\n      activate: stableActivate,\n      noteFocus,\n      register,\n    }),\n    [dense, noteFocus, register, selectable, stableActivate, store],\n  );\n\n  assertParts(listRoot, children);\n\n  return (\n    <ListContext.Provider value={context}>\n      <div\n        role={selectable ? \"listbox\" : \"list\"}\n        {...rest}\n        onKeyDown={(event) => {\n          handleKeyDown(event);\n          onKeyDown?.(event);\n        }}\n        onKeyUp={(event) => {\n          handleKeyUp(event);\n          onKeyUp?.(event);\n        }}\n        style={mergeStyles(recipeStyle(cn(LIST_ROOT, className), theme), style)}\n      >\n        {children}\n      </div>\n    </ListContext.Provider>\n  );\n};\n\nexport interface ListItemProps extends Omit<Props, \"children\" | \"style\"> {\n  readonly value: string;\n\n  readonly textValue?: string;\n\n  readonly disabled?: boolean;\n\n  readonly leading?: ReactNode;\n\n  readonly trailing?: ReactNode;\n  readonly children?: PartChildren;\n\n  readonly className?: string;\n\n  readonly style?: StateStyle<ListItemState>;\n}\n\nexport interface ListItemState {\n  readonly active: boolean;\n  readonly disabled: boolean;\n  readonly selected: boolean;\n}\n\nexport const ListItem = ({\n  value,\n  textValue,\n  disabled = false,\n  leading,\n  trailing,\n  children,\n  className,\n  onFocus,\n  onClick,\n  style,\n  ref,\n  ...rest\n}: ListItemProps) => {\n  const theme = useTheme();\n  const { selectable, dense, store, activate, noteFocus, register } =\n    useListContext();\n  const row = useSyncExternalStore(store.subscribe, () => store.read(value));\n  const [instance, setInstance] = useState<PublicInstance | undefined>(\n    undefined,\n  );\n\n  const attach = useCallback((next: PublicInstance | null) => {\n    setInstance(next ?? undefined);\n  }, []);\n  const setElement = useMergedRef<PublicInstance | null>(attach, ref);\n\n  useLayoutEffect(() => {\n    if (instance === undefined) return;\n    return register({\n      value,\n      disabled,\n      nativeId: instance.id,\n      label: textValue ?? \"\",\n    });\n  }, [disabled, instance, register, textValue, value]);\n\n  const isSelected = selectable && row.selected;\n  const isActive = row.active;\n  const isTabbable = row.tabbable;\n  const state: ListItemState = {\n    active: isActive,\n    disabled,\n    selected: isSelected,\n  };\n  assertParts(listItem, children);\n\n  return (\n    <div\n      role={selectable ? \"option\" : \"listitem\"}\n      {...(selectable ? { \"aria-selected\": isSelected } : {})}\n      {...rest}\n      ref={setElement}\n      tabIndex={selectable ? (isTabbable ? 0 : -1) : undefined}\n      onFocus={(event) => {\n        noteFocus(value);\n        onFocus?.(event);\n      }}\n      onClick={\n        disabled\n          ? undefined\n          : (event) => {\n              onClick?.(event);\n              activate(value, event);\n            }\n      }\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(LIST_ITEM, className), theme), {\n          height: listRowHeight(theme, dense),\n\n          ...(selectable\n            ? {\n                cursor: disabled ? \"not-allowed\" : \"pointer\",\n                ...(disabled\n                  ? {}\n                  : {\n                      hover: { backgroundColor: theme.colors.accent },\n                      active: { backgroundColor: pressWash(theme, \"accent\") },\n                    }),\n              }\n            : {}),\n          ...(isSelected\n            ? {\n                backgroundColor: theme.colors.selected,\n                color: theme.colors.selectedForeground,\n              }\n            : {}),\n\n          ...controlRing(theme, { disabled }),\n        }),\n        resolveStyle(style, state),\n      )}\n    >\n      {leading === undefined ? null : (\n        <div\n          style={{\n            display: \"flex\",\n            flexDirection: \"row\",\n            alignItems: \"center\",\n            flexShrink: 0,\n          }}\n        >\n          {leading}\n        </div>\n      )}\n      <div\n        style={{\n          display: \"flex\",\n          flexDirection: \"column\",\n          flexGrow: 1,\n          minWidth: 0,\n        }}\n      >\n        <RowStateContext.Provider value={{ selected: isSelected, disabled }}>\n          {children}\n        </RowStateContext.Provider>\n      </div>\n      {trailing === undefined ? null : (\n        <div\n          style={{\n            display: \"flex\",\n            flexDirection: \"row\",\n            alignItems: \"center\",\n            flexShrink: 0,\n          }}\n        >\n          {trailing}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport interface ListItemTitleProps extends Props {\n  readonly className?: string;\n}\n\nexport const ListItemTitle = ({\n  className,\n  style,\n  ...rest\n}: ListItemTitleProps) => {\n  const theme = useTheme();\n  const row = useContext(RowStateContext);\n  const color = row.selected\n    ? row.disabled\n      ? theme.colors.mutedForeground\n      : theme.colors.selectedForeground\n    : row.disabled\n      ? theme.colors.disabledForeground\n      : theme.colors.foreground;\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(LIST_ITEM_TITLE, className), theme), {\n          whiteSpace: \"nowrap\",\n          textOverflow: \"ellipsis\",\n          color,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface ListItemDescriptionProps extends Props {\n  readonly className?: string;\n}\n\nexport const ListItemDescription = ({\n  className,\n  style,\n  ...rest\n}: ListItemDescriptionProps) => {\n  const theme = useTheme();\n  const row = useContext(RowStateContext);\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(\n          recipeStyle(cn(LIST_ITEM_DESCRIPTION, className), theme),\n          row.disabled && !row.selected\n            ? { color: theme.colors.disabledForeground }\n            : {},\n        ),\n        style,\n      )}\n    />\n  );\n};\n\nconst listParts: ReadonlyArray<PartComponent> = [\n  List,\n  ListItem,\n  ListItemTitle,\n  ListItemDescription,\n];\n\nconst listRoot: PartsContract = {\n  parent: \"List\",\n  family: listParts,\n  accepts: [ListItem],\n  hint: \"Put ListItem rows directly inside List.\",\n};\n\nconst listItem: PartsContract = {\n  parent: \"ListItem\",\n  family: listParts,\n  accepts: [ListItemTitle, ListItemDescription],\n  hint: \"Put ListItemTitle and ListItemDescription directly inside ListItem.\",\n};\n\nexport const listMeta = {\n  name: \"Lists / list items\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"yes\",\n  limitations: [\n    \"no aria-multiselectable in 0.11.0, so the multiple mode is announced only per row\",\n    \"type-ahead matches an explicit ListItem textValue, not rendered children\",\n    \"activation is a callback on single/none lists: no href or link role, and a multiple list has no activation\",\n    \"no drag reorder or virtualisation\",\n    \"a press can focus a disabled row (the runtime does); the keys then refuse it and the tab stop stays on an enabled row\",\n  ],\n  docs: \"docs/list.mdx\",\n  example: \"examples/list.example.tsx\",\n  testFile: \"src/behaviour/list.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/list/origin.ts",
      "target": "@ui/gpuixui/components/list/origin.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport { pipe } from \"effect\";\n\nexport interface OriginRow {\n  readonly nativeId: number;\n  readonly value: string;\n}\n\nconst rowOf = (\n  rows: ReadonlyArray<OriginRow>,\n  id: number,\n): Option.Option<string> =>\n  Option.map(\n    Arr.findFirst(rows, (row) => row.nativeId === id),\n    (row) => row.value,\n  );\n\nexport const keyOrigin = (\n  rows: ReadonlyArray<OriginRow>,\n  active: Option.Option<string>,\n  elementId: Option.Option<number>,\n  focusedId: Option.Option<number>,\n): Option.Option<string> =>\n  pipe(\n    elementId,\n    Option.flatMap((id) => rowOf(rows, id)),\n    Option.orElse(() => Option.flatMap(focusedId, (id) => rowOf(rows, id))),\n    Option.orElse(() => active),\n  );\n"
    },
    {
      "path": "packages/components/src/list/selection.ts",
      "target": "@ui/gpuixui/components/list/selection.ts",
      "type": "registry:lib",
      "content": "import { pipe } from \"effect\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\n\nexport interface SelectableRow {\n  readonly value: string;\n  readonly disabled: boolean;\n}\n\nexport const toggle = (\n  values: ReadonlyArray<string>,\n  value: string,\n): ReadonlyArray<string> =>\n  Arr.contains(value)(values)\n    ? Arr.filter(values, (candidate) => candidate !== value)\n    : Arr.append(values, value);\n\nexport const selectAll = (\n  rows: ReadonlyArray<SelectableRow>,\n): ReadonlyArray<string> =>\n  pipe(\n    rows,\n    Arr.filter((row) => !row.disabled),\n    Arr.map((row) => row.value),\n  );\n\nexport const sameValues = (\n  left: ReadonlyArray<string>,\n  right: ReadonlyArray<string>,\n): boolean =>\n  Arr.length(left) === Arr.length(right) &&\n  Arr.every(left, (value) => Arr.contains(value)(right));\n\nconst indexOfValue = (\n  rows: ReadonlyArray<SelectableRow>,\n  value: string,\n): Option.Option<number> =>\n  Arr.findFirstIndex(rows, (row) => row.value === value);\n\nconst enabledAt = (\n  rows: ReadonlyArray<SelectableRow>,\n  index: number,\n): boolean =>\n  pipe(\n    Arr.get(rows, index),\n    Option.exists((row) => !row.disabled),\n  );\n\nexport const range = (\n  rows: ReadonlyArray<SelectableRow>,\n  anchor: string,\n  target: string,\n): ReadonlyArray<string> =>\n  pipe(\n    indexOfValue(rows, target),\n    Option.filter((index) => enabledAt(rows, index)),\n    Option.match({\n      onNone: () => Arr.empty<string>(),\n      onSome: (to) =>\n        pipe(\n          indexOfValue(rows, anchor),\n          Option.filter((index) => enabledAt(rows, index)),\n          Option.match({\n            onNone: () => Arr.of(target),\n            onSome: (from) =>\n              pipe(\n                rows,\n                Arr.take(Math.max(from, to) + 1),\n                Arr.drop(Math.min(from, to)),\n                Arr.filter((row) => !row.disabled),\n                Arr.map((row) => row.value),\n              ),\n          }),\n        ),\n    }),\n  );\n"
    },
    {
      "path": "packages/components/src/list/store.ts",
      "target": "@ui/gpuixui/components/list/store.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as HashMap from \"effect/HashMap\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\n\nexport interface RowState {\n  readonly active: boolean;\n  readonly selected: boolean;\n  readonly tabbable: boolean;\n}\n\nconst idle: RowState = { active: false, selected: false, tabbable: false };\n\nexport interface ListSelection {\n  readonly active: Option.Option<string>;\n  readonly selected: ReadonlyArray<string>;\n  readonly tabbable: Option.Option<string>;\n}\n\nexport interface RowStore {\n  readonly subscribe: (listener: () => void) => () => void;\n\n  readonly read: (value: string) => RowState;\n\n  readonly write: (selection: ListSelection) => void;\n}\n\nconst sameSnapshot = (left: RowState, right: RowState): boolean =>\n  left.active === right.active &&\n  left.selected === right.selected &&\n  left.tabbable === right.tabbable;\n\nexport const makeRowStore = (): RowStore => {\n  let selection: ListSelection = {\n    active: Option.none(),\n    selected: Arr.empty(),\n    tabbable: Option.none(),\n  };\n\n  let snapshots = HashMap.empty<string, RowState>();\n  let listeners = HashSet.empty<() => void>();\n\n  const read = (value: string): RowState => {\n    const next: RowState = {\n      active: Option.exists(\n        selection.active,\n        (candidate) => candidate === value,\n      ),\n      selected: Arr.contains(value)(selection.selected),\n      tabbable: Option.exists(\n        selection.tabbable,\n        (candidate) => candidate === value,\n      ),\n    };\n    const previous = Option.getOrElse(\n      HashMap.get(snapshots, value),\n      () => idle,\n    );\n    const snapshot = sameSnapshot(previous, next) ? previous : next;\n    snapshots =\n      snapshot === idle\n        ? HashMap.remove(snapshots, value)\n        : HashMap.set(snapshots, value, snapshot);\n    return snapshot;\n  };\n\n  return {\n    subscribe: (listener) => {\n      listeners = HashSet.add(listeners, listener);\n      return () => {\n        listeners = HashSet.remove(listeners, listener);\n      };\n    },\n    read,\n    write: (next) => {\n      selection = next;\n      Arr.forEach(Arr.fromIterable(listeners), (listener) => listener());\n    },\n  };\n};\n"
    },
    {
      "path": "packages/components/src/typeahead.ts",
      "target": "@ui/gpuixui/components/typeahead.ts",
      "type": "registry:lib",
      "content": "import type { EventPayload } from \"@gpuix/react\";\nimport { pipe } from \"effect\";\nimport * as Arr from \"effect/Array\";\nimport * as Duration from \"effect/Duration\";\nimport * as Option from \"effect/Option\";\nimport * as Str from \"effect/String\";\nimport { useCallback, useRef } from \"react\";\nimport { monotonicNanos } from \"./internal/clock.js\";\n\nexport interface TypeaheadBuffer {\n  readonly text: string;\n  readonly at: number;\n}\n\nexport const idleTypeahead: TypeaheadBuffer = { text: \"\", at: 0 };\n\nexport const typeaheadWindow: Duration.Duration = Duration.millis(500);\n\nexport const typeaheadWindowMs: number = Duration.toMillis(typeaheadWindow);\n\nexport const typeaheadNowMs = (): number =>\n  Number(monotonicNanos() / 1_000_000n);\n\nexport const typeaheadPress = (\n  buffer: TypeaheadBuffer,\n  char: string,\n  now: number,\n): TypeaheadBuffer => {\n  const letter = Str.toLowerCase(char);\n  if (\n    buffer.text === \"\" ||\n    !Number.isFinite(now) ||\n    now - buffer.at > typeaheadWindowMs\n  ) {\n    return { text: letter, at: Number.isFinite(now) ? now : 0 };\n  }\n  const repeating = pipe(\n    Str.split(\"\")(buffer.text),\n    Arr.every((seen) => seen === letter),\n  );\n  return repeating\n    ? { text: buffer.text, at: now }\n    : { text: `${buffer.text}${letter}`, at: now };\n};\n\nexport interface TypeaheadItem {\n  readonly label: string;\n  readonly disabled: boolean;\n}\n\nconst normalise = (value: string): string => Str.toLowerCase(Str.trim(value));\n\nexport const typeaheadMatchIndex = <Item extends TypeaheadItem>(\n  items: ReadonlyArray<Item>,\n  from: Option.Option<number>,\n  prefix: string,\n): Option.Option<number> => {\n  const needle = normalise(prefix);\n  if (Str.isEmpty(needle)) return Option.none();\n\n  const fromIndex = Option.getOrElse(from, () => -1);\n\n  return pipe(\n    items,\n    Arr.map((item, index) => ({ index, item, label: normalise(item.label) })),\n    Arr.filter(\n      (candidate) =>\n        !candidate.item.disabled &&\n        Str.isNonEmpty(candidate.label) &&\n        Str.startsWith(needle)(candidate.label),\n    ),\n    (candidates) =>\n      pipe(\n        Arr.findFirst(candidates, (candidate) => candidate.index > fromIndex),\n        Option.orElse(() => Arr.head(candidates)),\n        Option.map((candidate) => candidate.index),\n      ),\n  );\n};\n\nexport const typeaheadMatch = <Item extends TypeaheadItem>(\n  items: ReadonlyArray<Item>,\n  from: Option.Option<number>,\n  prefix: string,\n): Option.Option<Item> =>\n  Option.flatMap(typeaheadMatchIndex(items, from, prefix), (index) =>\n    Arr.get(items, index),\n  );\n\nexport const typedChar = (event: EventPayload): Option.Option<string> =>\n  pipe(\n    Option.fromNullishOr(event.keyChar),\n    Option.filter(\n      (char) =>\n        char.length === 1 &&\n        char !== \" \" &&\n        Option.exists(\n          Option.fromNullishOr(event.key),\n          (key) =>\n            key.length === 1 &&\n            event.modifiers?.cmd !== true &&\n            event.modifiers?.ctrl !== true &&\n            event.modifiers?.alt !== true,\n        ),\n    ),\n  );\n\nexport const useTypeahead = <Item extends TypeaheadItem>(\n  items: ReadonlyArray<Item>,\n  from: Option.Option<number>,\n  onMatch: (item: Item) => void,\n): ((char: string) => void) => {\n  const buffer = useRef<TypeaheadBuffer>(idleTypeahead);\n  return useCallback(\n    (char: string) => {\n      const next = typeaheadPress(buffer.current, char, typeaheadNowMs());\n      Option.match(typeaheadMatch(items, from, next.text), {\n        onNone: () => {\n          buffer.current = idleTypeahead;\n        },\n        onSome: (item) => {\n          buffer.current = next;\n          onMatch(item);\n        },\n      });\n    },\n    [from, items, onMatch],\n  );\n};\n"
    }
  ],
  "docs": "[List](https://gpuixui.sudokw.com/components/list) — These are GPUix native components: the files render through GPUix's reconciler against `@gpuix/react`, not the DOM, and need the per-platform `@gpuix/native` binding. The CLI installs the `@gpuixui/*` runtime the components import as registry items, so the copied tree resolves on its own; wrap it in `ThemeProvider` before it renders.",
  "meta": {
    "url": "/components/list",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}