{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-command-menu",
  "type": "registry:component",
  "title": "Command menu",
  "description": "A searchable action list, the ⌘K palette",
  "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-icons.json",
    "https://gpuixui.sudokw.com/gpuix-motion.json",
    "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/command-menu.tsx",
      "target": "@ui/gpuixui/components/command-menu.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type {\n  EventPayload,\n  Props,\n  PublicInstance,\n  StyleDesc,\n} from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport {\n  COMMAND_EMPTY,\n  COMMAND_HINT,\n  COMMAND_INPUT,\n  COMMAND_LIST,\n  COMMAND_SHORTCUT,\n} from \"../tw/index.js\";\nimport * as Arr from \"effect/Array\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\nimport * as Str from \"effect/String\";\nimport {\n  createContext,\n  Fragment,\n  useContext,\n  useRef,\n  useState,\n  type ReactNode,\n} from \"react\";\nimport { Dialog, DialogContent } from \"./dialog.js\";\nimport { dismissTopLayer } from \"./internal/dismiss-layer.js\";\nimport {\n  fieldHelp,\n  fieldHint,\n  hintHost,\n  renderFieldHint,\n} from \"../primitives/components/hint.js\";\nimport { isDismiss, isDown, isFirst, isLast, isUp } from \"./internal/key.js\";\nimport { overlaySurfaceStyle } from \"./internal/overlay.js\";\nimport { presenceTabStop } from \"./internal/presence.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport {\n  controlHeight,\n  controlRing,\n  keepContent,\n  nativeTextTheme,\n  selectionWash,\n  useTheme,\n} from \"./internal/theme.js\";\nimport { commandListOf, recentIdsOf } from \"./menu/fuzzy.js\";\nimport { MenuLabel, menuRowStyle } from \"./menu/item.js\";\nimport { fuzzyMatches, rankByGroup } from \"./menu/fuzzy.js\";\nimport {\n  move,\n  type MenuAction,\n  type MenuItemSpec,\n  type MenuState,\n} from \"./menu/logic.js\";\n\nexport interface CommandAction {\n  readonly id: string;\n  readonly label: string;\n\n  readonly group?: string;\n\n  readonly shortcut?: string;\n\n  readonly disabled?: boolean;\n  readonly perform: () => void;\n}\n\ninterface CommandMenuContextValue {\n  readonly query: string;\n\n  readonly setQuery: (query: string) => void;\n  readonly matches: ReadonlyArray<CommandAction>;\n  readonly isActive: (action: CommandAction) => boolean;\n  readonly run: (action: CommandAction) => void;\n  readonly runActive: () => void;\n  readonly handleKeyDown: (event: EventPayload) => void;\n  readonly registerInput: (instance: PublicInstance | null) => void;\n\n  readonly present: boolean;\n}\n\nconst CommandMenuContext = createContext<CommandMenuContextValue | undefined>(\n  undefined,\n);\n\nconst useCommandMenu = (): CommandMenuContextValue => {\n  const value = useContext(CommandMenuContext);\n  if (value === undefined) {\n    throw new Error(\"CommandMenu parts must be used inside CommandMenu\");\n  }\n  return value;\n};\n\nexport interface CommandMenuProps {\n  readonly open: boolean;\n  readonly onOpenChange: (open: boolean) => void;\n\n  readonly actions: ReadonlyArray<CommandAction>;\n\n  readonly recents?: ReadonlyArray<string>;\n\n  readonly filter?: (action: CommandAction, query: string) => boolean;\n  readonly children?: PartChildren;\n}\n\nconst defaultMatches = (\n  actions: ReadonlyArray<CommandAction>,\n  query: string,\n): ReadonlyArray<CommandAction> =>\n  rankByGroup(\n    fuzzyMatches(actions, query, (action) => action.label),\n    (action) => action.group,\n  );\n\nconst withRecents = (\n  actions: ReadonlyArray<CommandAction>,\n  recents: ReadonlyArray<string>,\n): ReadonlyArray<CommandAction> => {\n  const ids = HashSet.fromIterable(recents);\n  const recent = Arr.flatMap(Arr.dedupe(recents), (id) =>\n    Option.match(\n      Arr.findFirst(actions, (action) => action.id === id),\n      { onNone: () => [], onSome: (action) => [action] },\n    ),\n  );\n  if (Arr.isReadonlyArrayEmpty(recent)) return actions;\n  return Arr.appendAll(\n    Arr.map(recent, (action) => ({ ...action, group: \"Recent\" })),\n    Arr.filter(actions, (action) => !HashSet.has(ids, action.id)),\n  );\n};\n\nconst specs = (\n  actions: ReadonlyArray<CommandAction>,\n): ReadonlyArray<MenuItemSpec> =>\n  Arr.map(actions, (action, index) => ({\n    id: index,\n    disabled: action.disabled === true,\n    label: action.label,\n  }));\n\nconst firstEnabled = (\n  actions: ReadonlyArray<CommandAction>,\n): Option.Option<number> =>\n  Arr.findFirstIndex(actions, (action) => action.disabled !== true);\n\nexport const CommandMenu = ({\n  open,\n  onOpenChange,\n  actions,\n  recents,\n  filter,\n  children,\n}: CommandMenuProps) => {\n  const theme = useTheme();\n  const [query, setQuery] = useState(\"\");\n  const [activeId, setActiveId] = useState<Option.Option<number>>(\n    Option.none(),\n  );\n  const input = useRef<PublicInstance | null>(null);\n\n  const commands = commandListOf(actions);\n  const recentIds = recentIdsOf(recents);\n  const recent =\n    Str.isEmpty(Str.trim(query)) && recentIds.length > 0\n      ? withRecents(commands, recentIds)\n      : undefined;\n  const matches =\n    filter !== undefined\n      ? Arr.filter(commands, (action) => filter(action, query))\n      : (recent ?? defaultMatches(commands, query));\n  const rows = specs(matches);\n\n  const active: Option.Option<number> = Option.orElse(\n    Option.filter(activeId, (id) =>\n      Option.exists(Arr.get(rows, id), (row) => !row.disabled),\n    ),\n    () => firstEnabled(matches),\n  );\n  const activeAction = Option.flatMap(active, (index) =>\n    Arr.get(matches, index),\n  );\n\n  const close = (): void => {\n    setQuery(\"\");\n    setActiveId(Option.none());\n    onOpenChange(false);\n  };\n\n  const run = (action: CommandAction): void => {\n    if (action.disabled === true) return;\n    action.perform();\n    close();\n  };\n\n  const runActive = (): void => {\n    Option.match(activeAction, {\n      onNone: () => undefined,\n      onSome: run,\n    });\n  };\n\n  const writeQuery = (next: string): void => {\n    setQuery(next);\n    setActiveId(Option.none());\n  };\n\n  const step = (action: MenuAction): void => {\n    const state: MenuState = { activeId: active };\n    setActiveId(move(state, rows, action).activeId);\n  };\n\n  const handleKeyDown = (event: EventPayload): void => {\n    if (isDismiss(event)) {\n      dismissTopLayer(event);\n      return;\n    }\n    if (isDown(event)) step({ _tag: \"Move\", delta: 1 });\n    else if (isUp(event)) step({ _tag: \"Move\", delta: -1 });\n    else if (isFirst(event)) step({ _tag: \"First\" });\n    else if (isLast(event)) step({ _tag: \"Last\" });\n  };\n\n  assertParts(commandMenuRoot, children);\n\n  return (\n    <CommandMenuContext.Provider\n      value={{\n        query,\n        setQuery: writeQuery,\n        matches,\n        present: open,\n        isActive: (action) =>\n          Option.exists(activeAction, (current) => current.id === action.id),\n        run,\n        runActive,\n        handleKeyDown,\n        registerInput: (instance) => {\n          input.current = instance;\n        },\n      }}\n    >\n      <Dialog\n        open={open}\n        onOpenChange={(next) => (next ? onOpenChange(true) : close())}\n      >\n        <DialogContent\n          size=\"md\"\n          label=\"Command menu\"\n          showClose={false}\n          testId=\"command-menu\"\n          initialFocus={() => input.current?.id ?? null}\n          style={{ gap: theme.spacing.md }}\n        >\n          {children}\n        </DialogContent>\n      </Dialog>\n    </CommandMenuContext.Provider>\n  );\n};\n\nexport interface CommandMenuInputProps {\n  readonly placeholder?: string;\n\n  readonly label?: string;\n\n  readonly className?: string;\n}\n\nexport const CommandMenuInput = ({\n  placeholder = \"Type a command…\",\n  label = \"Search commands\",\n  className,\n}: CommandMenuInputProps) => {\n  const theme = useTheme();\n  const { query, setQuery, runActive, handleKeyDown, registerInput, present } =\n    useCommandMenu();\n\n  const fieldStyle: StyleDesc = {\n    ...overlaySurfaceStyle(COMMAND_INPUT, className, theme),\n    height: controlHeight(theme, \"md\"),\n    paddingLeft: theme.spacing.md,\n    paddingRight: theme.spacing.md,\n    backgroundColor: theme.colors.input,\n    color: theme.colors.inputForeground,\n    borderWidth: theme.borderWidth.thin,\n    borderColor: theme.colors.border,\n    borderRadius: theme.radius.md,\n    ...controlRing(theme),\n    ...selectionWash(theme),\n    ...keepContent,\n  };\n  const hint = fieldHint({ placeholder }, theme, query, {\n    origin: \"host\",\n    align: \"center\",\n    style: fieldStyle,\n  });\n\n  return (\n    <div style={hintHost()}>\n      <input\n        ref={registerInput}\n        testId=\"command-input\"\n        aria-label={label}\n        {...presenceTabStop(present)}\n        aria-description={fieldHelp({ placeholder }, query)}\n        theme={nativeTextTheme(theme, \"input\")}\n        value={query}\n        onChange={(event) => setQuery(event.value ?? \"\")}\n        onSubmit={runActive}\n        onKeyDown={handleKeyDown}\n        style={fieldStyle}\n      />\n      {Option.match(hint, {\n        onNone: () => null,\n        onSome: (painted) => renderFieldHint(painted, \"command-input\"),\n      })}\n    </div>\n  );\n};\n\nexport interface CommandMenuListProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n}\n\nexport const CommandMenuList = ({\n  className,\n  style,\n  ...rest\n}: CommandMenuListProps) => {\n  const theme = useTheme();\n  const { matches } = useCommandMenu();\n\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(COMMAND_LIST, className, theme),\n        style,\n      )}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          flexDirection: \"column\",\n          gap: theme.spacing[\"2xs\"],\n          maxHeight: 320,\n          overflow: \"scroll\",\n        }}\n      >\n        {Arr.map(matches, (action, index) => {\n          const previous =\n            index === 0 ? Option.none() : Arr.get(matches, index - 1);\n          const newGroup =\n            action.group !== undefined &&\n            !Option.exists(previous, (entry) => entry.group === action.group);\n          return (\n            <Fragment key={action.id}>\n              {newGroup ? <MenuLabel>{action.group}</MenuLabel> : null}\n              <CommandMenuItem action={action} />\n            </Fragment>\n          );\n        })}\n      </div>\n      <text style={overlaySurfaceStyle(COMMAND_HINT, undefined, theme)}>\n        ↑↓ to navigate · ↵ to run · esc to close\n      </text>\n    </div>\n  );\n};\n\nexport interface CommandMenuItemProps extends Omit<Props, \"children\"> {\n  readonly action: CommandAction;\n\n  readonly className?: string;\n}\n\nexport const CommandMenuItem = ({\n  action,\n  className,\n  style,\n  ...rest\n}: CommandMenuItemProps) => {\n  const theme = useTheme();\n  const { isActive, run } = useCommandMenu();\n  const highlighted = isActive(action);\n\n  return (\n    <div\n      role=\"option\"\n      aria-selected={highlighted}\n      testId={`command-${action.id}`}\n      onClick={() => run(action)}\n      {...rest}\n      style={mergeStyles(\n        menuRowStyle(\n          theme,\n          {\n            tone: \"default\",\n            active: highlighted,\n            disabled: action.disabled === true,\n          },\n          className,\n        ),\n        style,\n      )}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          flexDirection: \"row\",\n          flexGrow: 1,\n          minWidth: 0,\n        }}\n      >\n        {action.label}\n      </div>\n      {action.shortcut === undefined ? null : (\n        <text style={overlaySurfaceStyle(COMMAND_SHORTCUT, undefined, theme)}>\n          {action.shortcut}\n        </text>\n      )}\n    </div>\n  );\n};\n\nexport interface CommandMenuEmptyProps {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const CommandMenuEmpty = ({\n  className,\n  children,\n}: CommandMenuEmptyProps) => {\n  const theme = useTheme();\n  const { matches } = useCommandMenu();\n  if (matches.length > 0) return null;\n\n  return (\n    <text\n      testId=\"command-empty\"\n      style={overlaySurfaceStyle(COMMAND_EMPTY, className, theme)}\n    >\n      {children ?? \"No matching commands\"}\n    </text>\n  );\n};\n\nconst commandMenuParts: ReadonlyArray<PartComponent> = [\n  CommandMenu,\n  CommandMenuInput,\n  CommandMenuList,\n  CommandMenuItem,\n  CommandMenuEmpty,\n];\n\nconst commandMenuRoot: PartsContract = {\n  parent: \"CommandMenu\",\n  family: commandMenuParts,\n  accepts: [CommandMenuInput, CommandMenuList, CommandMenuEmpty],\n  hint: \"Put the input, list and empty state directly inside CommandMenu.\",\n};\n\nexport const commandMenuMeta = {\n  name: \"Command menus\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"ranking is per group, not global, so a group's heading never repeats; a custom filter opts out and keeps declaration order\",\n    \"recents are caller-owned data (`recents` action ids) shown only for an empty query with the default filter; the component records nothing itself\",\n    \"the input is a GPUix <input> painted by this palette's fixed recipe; @gpuixui/primitives' Input paints its own density-scaled chrome (ADR 0006), so it is not adopted here\",\n    \"the active row is highlighted, not focused, so typing keeps working; screen readers get aria-selected but no aria-activedescendant\",\n  ],\n  docs: \"docs/command-menu.mdx\",\n  example: \"examples/command-menu.example.tsx\",\n  testFile: \"src/behaviour/command-menu.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/dialog.tsx",
      "target": "@ui/gpuixui/components/dialog.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { KeyEvent, Props, PublicInstance } from \"@gpuix/react\";\nimport { useGpuix, useWindowSize } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { Icon } from \"../icons/index.js\";\nimport {\n  DIALOG_CONTENT,\n  DIALOG_DESCRIPTION,\n  DIALOG_FOOTER,\n  DIALOG_HEADER,\n  DIALOG_OVERLAY,\n  DIALOG_TITLE,\n} from \"../tw/index.js\";\nimport * as Schema from \"effect/Schema\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n  type MutableRefObject,\n} from \"react\";\nimport { Button, IconButton, type ButtonProps } from \"./internal/button.js\";\nimport { dismissTopLayer } from \"./internal/dismiss-layer.js\";\nimport { DismissScope } from \"./internal/dismiss-scope.js\";\nimport {\n  handleModalKeyDown,\n  useModalFocus,\n  useModalState,\n} from \"./internal/modal.js\";\nimport { overlaySurfaceStyle, overlayVeilColor } from \"./internal/overlay.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport {\n  fadeFrom,\n  fadeOnly,\n  PresenceShield,\n  presenceMotion,\n  presenceStyle,\n  presenceTabStop,\n  useSurfacePresence,\n  useSurfaceTiming,\n} from \"./internal/presence.js\";\nimport { useMergedRef } from \"./internal/ref.js\";\nimport { useTheme } from \"./internal/theme.js\";\n\nexport type DialogSize = \"sm\" | \"md\" | \"lg\";\n\nconst DialogSizes = Schema.Literals([\"sm\", \"md\", \"lg\"]);\n\nconst MAX_WIDTH: Readonly<Record<DialogSize, number>> = {\n  sm: 360,\n  md: 480,\n  lg: 640,\n};\n\nconst dialogSize = (size: unknown): DialogSize =>\n  Schema.is(DialogSizes)(size) ? size : \"md\";\n\ninterface DialogContextValue {\n  readonly open: boolean;\n  readonly setOpen: (open: boolean) => void;\n  readonly restoreTarget: MutableRefObject<number | null>;\n}\n\nconst DialogContext = createContext<DialogContextValue | undefined>(undefined);\n\nconst useDialogContext = (): DialogContextValue => {\n  const value = useContext(DialogContext);\n  if (value === undefined) {\n    throw new Error(\"Dialog parts must be used inside Dialog\");\n  }\n  return value;\n};\n\nexport interface DialogProps {\n  readonly open?: boolean;\n\n  readonly defaultOpen?: boolean;\n  readonly onOpenChange?: (open: boolean) => void;\n\n  readonly dismissible?: boolean;\n  readonly children?: PartChildren;\n}\n\nexport const Dialog = ({\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  dismissible = true,\n  children,\n}: DialogProps) => {\n  const { open, setOpen, restoreTarget } = useModalState({\n    open: openProp,\n    defaultOpen,\n    onOpenChange,\n    dismissible,\n  });\n\n  const context = useMemo<DialogContextValue>(\n    () => ({ open, setOpen, restoreTarget }),\n    [open, restoreTarget, setOpen],\n  );\n\n  assertParts(dialogRoot, children);\n\n  return (\n    <DialogContext.Provider value={context}>{children}</DialogContext.Provider>\n  );\n};\n\nexport const DialogTrigger = ({ onClick, ...rest }: ButtonProps) => {\n  const { open, setOpen } = useDialogContext();\n  return (\n    <Button\n      {...rest}\n      aria-expanded={open}\n      onClick={(event) => {\n        onClick?.(event);\n        setOpen(!open);\n      }}\n    />\n  );\n};\n\nexport const DialogClose = ({ onClick, ...rest }: ButtonProps) => {\n  const { open, setOpen } = useDialogContext();\n  return (\n    <Button\n      {...presenceTabStop(open)}\n      {...rest}\n      onClick={(event) => {\n        onClick?.(event);\n        setOpen(false);\n      }}\n    />\n  );\n};\n\nexport interface DialogContentProps extends Omit<Props, \"children\"> {\n  readonly size?: DialogSize;\n\n  readonly className?: string;\n\n  readonly label: string;\n\n  readonly role?: \"dialog\" | \"alertdialog\";\n\n  readonly showClose?: boolean;\n\n  readonly initialFocus?: () => number | null;\n  readonly children?: PartChildren;\n}\n\nexport const DialogContent = ({\n  size: requestedSize = \"md\",\n  label,\n  role = \"dialog\",\n  showClose = true,\n  initialFocus,\n  children,\n  onKeyDown,\n  onMouseDownOutside,\n  className,\n  style,\n  testId = \"dialog-content\",\n  ref,\n  ...rest\n}: DialogContentProps) => {\n  const theme = useTheme();\n  const { open, setOpen, restoreTarget } = useDialogContext();\n  const renderer = useGpuix().renderer ?? undefined;\n  const window = useWindowSize();\n  assertParts(dialogContent, children);\n  const timing = useSurfaceTiming(\"slow\");\n  const presence = useSurfacePresence(open, timing);\n  const [content, setContent] = useState<PublicInstance | undefined>(undefined);\n  const contentRef = useCallback((next: PublicInstance | null) => {\n    setContent(next ?? undefined);\n  }, []);\n  const setContentElement = useMergedRef<PublicInstance | null>(\n    contentRef,\n    ref,\n  );\n  const contentId = content?.id;\n\n  const [outline, setOutline] = useState<PublicInstance | undefined>(undefined);\n  const outlineRef = useCallback((next: PublicInstance | null) => {\n    setOutline(next ?? undefined);\n  }, []);\n  const trapId = outline?.id ?? contentId;\n  useModalFocus(contentId, initialFocus, open, restoreTarget);\n\n  if (!presence.mounted) return null;\n\n  const size = dialogSize(requestedSize);\n  const close = () => setOpen(false);\n\n  const handleKeyDown = (event: KeyEvent): void => {\n    handleModalKeyDown({ event, contentId: trapId, renderer, onKeyDown });\n  };\n\n  const available = window.width - 2 * theme.spacing[\"3xl\"];\n\n  return (\n    <anchored\n      position={{ x: 0, y: 0 }}\n      occlude\n      style={{ position: \"absolute\" }}\n    >\n      <div\n        testId=\"dialog-backdrop\"\n        motion={presenceMotion(fadeOnly, presence)}\n        style={mergeStyles(\n          overlaySurfaceStyle(DIALOG_OVERLAY, undefined, theme),\n          {\n            backgroundColor: overlayVeilColor(theme),\n            width: window.width,\n            height: window.height,\n            display: \"flex\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n            padding: theme.spacing[\"3xl\"],\n            ...presenceStyle(presence.present),\n          },\n        )}\n      >\n        {}\n        <div\n          ref={outlineRef}\n          onMouseDownOutside={(event) => {\n            onMouseDownOutside?.(event);\n            dismissTopLayer(event);\n          }}\n          onKeyDown={handleKeyDown}\n          style={{\n            position: \"relative\",\n            display: \"flex\",\n            flexDirection: \"column\",\n          }}\n        >\n          <div\n            role={role}\n            aria-label={label}\n            tabIndex={-1}\n            {...rest}\n            ref={setContentElement}\n            testId={testId}\n            motion={presenceMotion(fadeFrom(\"top\", 8), presence)}\n            style={mergeStyles(\n              mergeStyles(\n                overlaySurfaceStyle(DIALOG_CONTENT, className, theme, \"lg\"),\n                {\n                  display: \"flex\",\n                  flexDirection: \"column\",\n                  position: \"relative\",\n                  width: Math.min(MAX_WIDTH[size], available),\n                  maxWidth: available,\n                  maxHeight: window.height - 2 * theme.spacing[\"3xl\"],\n                  overflow: \"scroll\",\n                  ...presenceStyle(presence.present),\n                },\n              ),\n              style,\n            )}\n          >\n            <DismissScope>{children}</DismissScope>\n          </div>\n          {showClose ? (\n            <div\n              style={{\n                position: \"absolute\",\n                top: theme.spacing.lg,\n                right: theme.spacing.lg,\n                display: \"flex\",\n              }}\n            >\n              <IconButton\n                label=\"Close\"\n                size=\"sm\"\n                testId=\"dialog-close\"\n                {...presenceTabStop(presence.present)}\n                onClick={close}\n                motion={presenceMotion(fadeFrom(\"top\", 8), presence)}\n              >\n                <Icon name=\"x\" size=\"sm\" color={theme.colors.foreground} />\n              </IconButton>\n            </div>\n          ) : null}\n          <PresenceShield present={presence.present} />\n        </div>\n      </div>\n    </anchored>\n  );\n};\n\nexport interface DialogHeaderProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const DialogHeader = ({\n  children,\n  className,\n  style,\n  ...rest\n}: DialogHeaderProps) => {\n  const theme = useTheme();\n  assertParts(dialogHeader, children);\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(overlaySurfaceStyle(DIALOG_HEADER, className, theme), {\n          paddingRight: theme.spacing[\"2xl\"],\n        }),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport interface DialogTitleProps extends Props {\n  readonly className?: string;\n}\n\nexport const DialogTitle = ({\n  className,\n  style,\n  ...rest\n}: DialogTitleProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      role=\"heading\"\n      aria-level={2}\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(DIALOG_TITLE, className, theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface DialogDescriptionProps extends Props {\n  readonly className?: string;\n}\n\nexport const DialogDescription = ({\n  className,\n  style,\n  ...rest\n}: DialogDescriptionProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(DIALOG_DESCRIPTION, className, theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface DialogFooterProps extends Props {\n  readonly className?: string;\n}\n\nexport const DialogFooter = ({\n  className,\n  style,\n  ...rest\n}: DialogFooterProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(DIALOG_FOOTER, className, theme),\n        style,\n      )}\n    />\n  );\n};\n\nconst dialogParts: ReadonlyArray<PartComponent> = [\n  Dialog,\n  DialogTrigger,\n  DialogClose,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogDescription,\n  DialogFooter,\n];\n\nconst dialogRoot: PartsContract = {\n  parent: \"Dialog\",\n  family: dialogParts,\n  accepts: [DialogTrigger, DialogContent, DialogClose],\n  hint: \"Put DialogTrigger and DialogContent directly inside Dialog.\",\n};\n\nconst dialogContent: PartsContract = {\n  parent: \"DialogContent\",\n  family: dialogParts,\n  accepts: [\n    DialogHeader,\n    DialogTitle,\n    DialogDescription,\n    DialogFooter,\n    DialogClose,\n  ],\n  hint: \"Put the header, title, description, footer and close control inside DialogContent.\",\n};\n\nconst dialogHeader: PartsContract = {\n  parent: \"DialogHeader\",\n  family: dialogParts,\n  accepts: [DialogTitle, DialogDescription],\n  hint: \"Put DialogTitle and DialogDescription directly inside DialogHeader.\",\n};\n\nexport const dialogMeta = {\n  name: \"Dialogs\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"no aria-modal and no aria-labelledby in 0.11.0: the dialog is named with aria-label and its title is a heading\",\n    \"the veil is an anchored element at window coordinates: the styled Dialog is a local overlay, not `@gpuix/react/dialog`'s portalled surface; a dialog mounted inside a scroll container stays painted at the window origin, not inside that container\",\n    \"a size restored from storage that names no variant paints the default `md` width: the guarded lookup never hands the renderer an undefined width\",\n    \"a dismissed surface stays painted for its exit and its own close control leaves the tab order for that window; a control composed inside the body keeps its stop, because 0.11.0 has no inherited `inert`\",\n  ],\n  docs: \"docs/dialog.mdx\",\n  example: \"examples/dialog.example.tsx\",\n  testFile: \"src/behaviour/dialog.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/internal/button.tsx",
      "target": "@ui/gpuixui/components/internal/button.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport type { ReactNode } from \"react\";\nimport {\n  Button as PrimitiveButton,\n  IconButton as PrimitiveIconButton,\n  actionOnColor as primitiveActionOnColor,\n  type ButtonVariant as PrimitiveVariant,\n} from \"../../primitives/button.js\";\nimport { cn } from \"../../tw/index.js\";\nimport * as Schema from \"effect/Schema\";\nimport type { Theme } from \"./theme.js\";\n\nconst ButtonVariants = Schema.Literals([\n  \"primary\",\n  \"secondary\",\n  \"ghost\",\n  \"danger\",\n  \"link\",\n]);\n\nexport type ButtonVariant = typeof ButtonVariants.Type;\n\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface ButtonProps extends Props {\n  readonly variant?: ButtonVariant;\n\n  readonly size?: ButtonSize;\n\n  readonly disabled?: boolean;\n\n  readonly loading?: boolean;\n\n  readonly loadingLabel?: string;\n\n  readonly reducedMotion?: boolean;\n\n  readonly leading?: ReactNode;\n\n  readonly trailing?: ReactNode;\n}\n\nexport interface IconButtonProps extends Props {\n  readonly label: string;\n\n  readonly size?: ButtonSize;\n  readonly disabled?: boolean;\n}\n\ninterface Look {\n  readonly variant: PrimitiveVariant;\n  readonly className: string;\n}\n\nconst LOOKS: Readonly<Record<ButtonVariant, Look>> = {\n  primary: { variant: \"primary\", className: \"\" },\n  secondary: { variant: \"secondary\", className: \"border border-strong\" },\n  ghost: { variant: \"ghost\", className: \"\" },\n  danger: { variant: \"destructive\", className: \"\" },\n\n  link: { variant: \"link\", className: \"underline underline-offset-4\" },\n};\n\nconst lookOf = (variant: unknown): Look =>\n  LOOKS[Schema.is(ButtonVariants)(variant) ? variant : \"secondary\"];\n\nexport const actionOnColor = (theme: Theme, variant: ButtonVariant): string =>\n  primitiveActionOnColor(theme, lookOf(variant).variant);\n\nexport const Button = ({\n  variant = \"secondary\",\n  size = \"md\",\n  disabled = false,\n  onClick,\n  style,\n  \"aria-label\": ariaLabel,\n  ...rest\n}: ButtonProps) => {\n  const look = lookOf(variant);\n  return (\n    <PrimitiveButton\n      {...rest}\n      variant={look.variant}\n      size={size}\n      disabled={disabled}\n      label={ariaLabel}\n      className={cn(look.className)}\n      onActivate={onClick}\n      style={style}\n    />\n  );\n};\n\nexport const IconButton = ({\n  label,\n  size = \"md\",\n  disabled = false,\n  onClick,\n  style,\n  children,\n  ...rest\n}: IconButtonProps) => (\n  <PrimitiveIconButton\n    {...rest}\n    icon={children}\n    variant=\"ghost\"\n    size={size}\n    disabled={disabled}\n    label={label}\n    className=\"text-muted-foreground hover:text-foreground\"\n    onActivate={onClick}\n    style={style}\n  />\n);\n"
    },
    {
      "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/controllable.ts",
      "target": "@ui/gpuixui/components/internal/controllable.ts",
      "type": "registry:lib",
      "content": "import { useCallback, useState } from \"react\";\n\nexport type ControllableState<Value> = readonly [Value, (next: Value) => void];\n\nexport interface ControllableStateOptions<Value> {\n  readonly value: Value | undefined;\n\n  readonly defaultValue: Value;\n\n  readonly onChange?: (next: Value) => void;\n}\n\nexport const useControllableState = <Value>({\n  value,\n  defaultValue,\n  onChange,\n}: ControllableStateOptions<Value>): ControllableState<Value> => {\n  const [internal, setInternal] = useState(defaultValue);\n  const controlled = value !== undefined;\n\n  const setValue = useCallback(\n    (next: Value) => {\n      if (!controlled) setInternal(next);\n      if (value !== next) onChange?.(next);\n    },\n    [controlled, value, onChange],\n  );\n\n  return [controlled ? value : internal, setValue];\n};\n"
    },
    {
      "path": "packages/components/src/internal/dismiss-layer.ts",
      "target": "@ui/gpuixui/components/internal/dismiss-layer.ts",
      "type": "registry:lib",
      "content": "import type { EventPayload } from \"@gpuix/react\";\nimport * as Arr from \"effect/Array\";\nimport * as Duration from \"effect/Duration\";\nimport * as Option from \"effect/Option\";\nimport * as Order from \"effect/Order\";\nimport { monotonicNanos } from \"./clock.js\";\n\nexport type DismissHandler = (event: EventPayload) => void;\n\ninterface Layer {\n  readonly onDismiss: DismissHandler;\n  readonly depth: number;\n}\n\nlet layers: ReadonlyArray<Layer> = [];\n\nlet dismissedKeystroke: Option.Option<number> = Option.none();\n\ninterface Press {\n  readonly shape: string;\n  readonly at: bigint;\n}\n\nlet dismissedPress: Option.Option<Press> = Option.none();\n\nexport const pointerGestureWindow: Duration.Duration = Duration.millis(64);\n\nconst PRESS_WINDOW_NANOS =\n  BigInt(Duration.toMillis(pointerGestureWindow)) * 1_000_000n;\n\nconst pressShape = (event: EventPayload): string =>\n  Arr.join(\":\")(\n    Arr.map(\n      [event.eventType, event.x, event.y, event.button, event.clickCount],\n      (part) => (part === undefined ? \"\" : `${part}`),\n    ),\n  );\n\nconst consumedPress = (event: EventPayload, now: bigint): boolean => {\n  const shape = pressShape(event);\n  const repeated = Option.exists(\n    dismissedPress,\n    (press) => press.shape === shape && now - press.at <= PRESS_WINDOW_NANOS,\n  );\n  if (!repeated) dismissedPress = Option.some({ shape, at: now });\n  return repeated;\n};\n\nlet lastDismissedPoint: Option.Option<{\n  readonly point: string;\n  readonly at: bigint;\n}> = Option.none();\n\nconst pointOf = (event: EventPayload): string =>\n  Arr.join(\":\")(\n    Arr.map([event.x, event.y, event.button], (part) =>\n      part === undefined ? \"\" : `${part}`,\n    ),\n  );\n\nexport const isDismissalClick = (\n  event: EventPayload,\n  now: bigint = monotonicNanos(),\n): boolean =>\n  Option.exists(\n    lastDismissedPoint,\n    (dismissed) =>\n      dismissed.point === pointOf(event) &&\n      now - dismissed.at <= PRESS_WINDOW_NANOS,\n  );\n\nconst consumedKeystroke = (keystrokeId: number): boolean => {\n  if (Option.contains(dismissedKeystroke, keystrokeId)) return true;\n  dismissedKeystroke = Option.some(keystrokeId);\n  return false;\n};\n\nconst consume = (event: EventPayload, now: bigint): boolean =>\n  Option.match(Option.fromNullishOr(event.keystrokeId), {\n    onNone: () => consumedPress(event, now),\n    onSome: consumedKeystroke,\n  });\n\nconst byDepth: Order.Order<Layer> = Order.mapInput(\n  Order.Number,\n  (layer: Layer) => layer.depth,\n);\n\nconst topmost = (): Option.Option<DismissHandler> =>\n  Option.map(Arr.last(Arr.sort(layers, byDepth)), (layer) => layer.onDismiss);\n\nexport const pushDismissLayer = (\n  onDismiss: DismissHandler,\n  depth: number,\n): (() => void) => {\n  const layer: Layer = { onDismiss, depth };\n  layers = Arr.append(layers, layer);\n  return () => {\n    layers = Arr.filter(layers, (candidate) => candidate !== layer);\n    if (Arr.length(layers) === 0) {\n      dismissedKeystroke = Option.none();\n      dismissedPress = Option.none();\n    }\n  };\n};\n\nexport const dismissTopLayer = (\n  event: EventPayload,\n  now: bigint = monotonicNanos(),\n): boolean => {\n  if (consume(event, now)) return false;\n  return Option.match(topmost(), {\n    onNone: () => false,\n    onSome: (onDismiss) => {\n      lastDismissedPoint = Option.some({ point: pointOf(event), at: now });\n      onDismiss(event);\n      return true;\n    },\n  });\n};\n\nexport const dismissLayerCount = (): number => Arr.length(layers);\n"
    },
    {
      "path": "packages/components/src/internal/dismiss-scope.tsx",
      "target": "@ui/gpuixui/components/internal/dismiss-scope.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport { createContext, useContext, type ReactNode } from \"react\";\n\nconst AmbientDepth = createContext(0);\n\nexport const useDismissDepth = (): number => useContext(AmbientDepth) + 1;\n\nexport const DismissScope = ({\n  children,\n}: {\n  readonly children: ReactNode;\n}) => (\n  <AmbientDepth.Provider value={useDismissDepth()}>\n    {children}\n  </AmbientDepth.Provider>\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/modal.ts",
      "target": "@ui/gpuixui/components/internal/modal.ts",
      "type": "registry:lib",
      "content": "import type { KeyEvent } from \"@gpuix/react\";\nimport { useGpuix } from \"@gpuix/react\";\nimport { Match } from \"effect\";\nimport { useLayoutEffect, useRef, type MutableRefObject } from \"react\";\nimport { useControllableState } from \"./controllable.js\";\nimport { useDismissDepth } from \"./dismiss-scope.js\";\nimport { dismissTopLayer, pushDismissLayer } from \"./dismiss-layer.js\";\nimport { isDismiss, isTab } from \"./key.js\";\n\nexport interface ModalState {\n  readonly open: boolean;\n  readonly setOpen: (open: boolean) => void;\n\n  readonly restoreTarget: MutableRefObject<number | null>;\n}\n\nexport const useModalState = ({\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  dismissible = true,\n}: {\n  readonly open?: boolean;\n  readonly defaultOpen?: boolean;\n  readonly onOpenChange?: (open: boolean) => void;\n  readonly dismissible?: boolean;\n}): ModalState => {\n  const renderer = useGpuix().renderer;\n  const [open, setOpen] = useControllableState({\n    value: openProp,\n    defaultValue: defaultOpen,\n    onChange: onOpenChange,\n  });\n\n  const restoreTarget = useRef<number | null>(null);\n  const captured = useRef(false);\n  if (open && !captured.current) {\n    restoreTarget.current = renderer?.getFocusedElementId?.() ?? null;\n    captured.current = true;\n  } else if (!open) {\n    captured.current = false;\n  }\n\n  const dismiss = useRef<() => void>(() => undefined);\n  useLayoutEffect(() => {\n    dismiss.current = () => {\n      if (dismissible) setOpen(false);\n    };\n  });\n\n  const depth = useDismissDepth();\n  useLayoutEffect(() => {\n    if (!open) return;\n    return pushDismissLayer(() => dismiss.current(), depth);\n  }, [depth, open]);\n\n  return { open, setOpen, restoreTarget };\n};\n\nexport const useModalFocus = (\n  contentId: number | undefined,\n  initialFocus: (() => number | null) | undefined,\n  open: boolean,\n  restoreTarget: MutableRefObject<number | null>,\n): void => {\n  const renderer = useGpuix().renderer;\n  const initialFocusRef = useRef(initialFocus);\n  useLayoutEffect(() => {\n    initialFocusRef.current = initialFocus;\n  });\n\n  useLayoutEffect(() => {\n    if (contentId === undefined || !open) return;\n    renderer?.focusElement?.(contentId);\n    const initial = initialFocusRef.current?.() ?? null;\n    if (initial !== null && initial !== contentId) {\n      renderer?.focusElement?.(initial);\n    }\n    return () => {\n      const previous = restoreTarget.current;\n\n      if (\n        previous === null ||\n        previous === contentId ||\n        renderer?.getElementBounds?.(previous) === null\n      ) {\n        return;\n      }\n      renderer?.focusElement?.(previous);\n    };\n  }, [contentId, renderer, open, restoreTarget]);\n};\n\nexport const handleModalKeyDown = ({\n  event,\n  contentId,\n  renderer,\n  onKeyDown,\n}: {\n  readonly event: KeyEvent;\n  readonly contentId: number | undefined;\n  readonly renderer:\n    | {\n        focusNextWithin?: (id: number) => void;\n        focusPreviousWithin?: (id: number) => void;\n      }\n    | undefined;\n  readonly onKeyDown?: (event: KeyEvent) => void;\n}): void => {\n  Match.value(event).pipe(\n    Match.when(isDismiss, (value) => {\n      dismissTopLayer(value);\n    }),\n    Match.when(isTab, () => {\n      event.preventDefault();\n      if (event.modifiers?.shift === true) {\n        renderer?.focusPreviousWithin?.(contentId ?? -1);\n      } else {\n        renderer?.focusNextWithin?.(contentId ?? -1);\n      }\n    }),\n    Match.orElse(() => undefined),\n  );\n  onKeyDown?.(event);\n};\n"
    },
    {
      "path": "packages/components/src/internal/overlay.ts",
      "target": "@ui/gpuixui/components/internal/overlay.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { cn } from \"../../tw/index.js\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport { recipeStyle } from \"./recipe.js\";\nimport {\n  alpha,\n  elevationShadow,\n  type BoxShadow,\n  type ElevationLevel,\n  type Theme,\n} from \"./theme.js\";\n\nexport const OVERLAY_WASH = 0.55;\n\nexport const overlayVeilColor = (theme: Theme): string =>\n  alpha(theme.colors.background, OVERLAY_WASH);\n\nconst isEdge = (shadow: BoxShadow): boolean =>\n  shadow.blurRadius === 0 && shadow.spreadRadius > 0;\n\nconst shadows = (painted: StyleDesc[\"boxShadow\"]): ReadonlyArray<BoxShadow> =>\n  Option.match(Option.fromNullishOr(painted), {\n    onNone: () => [],\n    onSome: (value) => Arr.ensure(value),\n  });\n\nexport const overlaySurfaceStyle = (\n  recipe: string,\n  className: string | undefined,\n  theme: Theme,\n  elevation?: ElevationLevel,\n): StyleDesc => {\n  const style = recipeStyle(cn(recipe, className), theme);\n  const lift = Option.fromNullishOr(elevation).pipe(\n    Option.flatMap((level) =>\n      Option.fromNullishOr(elevationShadow(theme, level)),\n    ),\n  );\n\n  return Option.match(lift, {\n    onNone: () => style,\n    onSome: (shadow) => {\n      const painted = shadows(style.boxShadow);\n      const lost = Arr.every(painted, isEdge) && painted.length > 0;\n      return lost ? { ...style, boxShadow: [shadow, ...painted] } : style;\n    },\n  });\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/presence.tsx",
      "target": "@ui/gpuixui/components/internal/presence.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { MotionProps } from \"@gpuix/react\";\nimport {\n  delayedTiming,\n  exitWindowMs,\n  fadeFrom,\n  fadeFromAnchor,\n  fadeFromSide,\n  fadeOnly,\n  presenceMotion as motionOfPresence,\n  presenceStyle,\n  transitionWindowMs,\n  type PresenceFrames,\n  type PresenceHitRule,\n  type PresenceSide,\n  type PresenceTiming,\n  type PresenceTransition,\n} from \"../../motion/presence.js\";\nimport {\n  MotionProvider,\n  useMotionPreference,\n  useSurfacePresence,\n  useSurfaceTiming,\n  type MotionProviderProps,\n  type SurfacePresence,\n} from \"../../motion/react/presence.js\";\nimport {\n  motionTiming,\n  type MotionLevel,\n  type MotionPreference,\n} from \"../../motion/timing.js\";\n\nexport { MotionProvider, useMotionPreference, type MotionProviderProps };\n\nexport type SurfaceFrames = PresenceFrames;\n\nexport type SurfaceTiming = PresenceTiming;\n\nexport type PanelSide = PresenceSide;\n\nexport {\n  delayedTiming,\n  exitWindowMs,\n  fadeFrom,\n  fadeFromAnchor,\n  fadeFromSide,\n  fadeOnly,\n  presenceStyle,\n  transitionWindowMs,\n};\nexport type { PresenceHitRule };\n\nexport { useSurfacePresence, useSurfaceTiming, type SurfacePresence };\n\nexport const moveTiming = (\n  level: MotionLevel,\n  preference: MotionPreference,\n): PresenceTransition => motionTiming(level, { phase: \"standard\", preference });\n\nexport const useMoveTiming = (level: MotionLevel): PresenceTransition =>\n  moveTiming(level, useMotionPreference());\n\nexport const presenceMotion = (\n  frames: SurfaceFrames,\n  presence: SurfacePresence,\n  timing: SurfaceTiming = presence.timing,\n): MotionProps => motionOfPresence(frames, presence.present, timing);\n\nexport const PresenceShield = ({ present }: { readonly present: boolean }) =>\n  present ? null : (\n    <div\n      testId=\"presence-shield\"\n      style={{ position: \"absolute\", top: 0, right: 0, bottom: 0, left: 0 }}\n    />\n  );\n\nexport const presenceTabStop = (\n  present: boolean,\n): { readonly tabIndex?: number } => (present ? {} : { tabIndex: -1 });\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/menu/fuzzy.ts",
      "target": "@ui/gpuixui/components/menu/fuzzy.ts",
      "type": "registry:lib",
      "content": "import { Order, pipe } from \"effect\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport * as P from \"effect/Predicate\";\nimport * as Str from \"effect/String\";\n\nexport interface FuzzyMatch<A> {\n  readonly item: A;\n  readonly score: number;\n\n  readonly index: number;\n}\n\nconst normalise = (value: string): string =>\n  pipe(value, Str.trim, Str.toLowerCase);\n\nconst charAt = (text: string, index: number): string =>\n  Option.getOrElse(pipe(text, Str.charAt(index)), () => \"\");\n\nconst WORD_CHARACTER = /[a-z0-9]/;\n\nconst isWordStart = (text: string, index: number): boolean =>\n  index === 0 || !WORD_CHARACTER.test(charAt(text, index - 1));\n\nconst stepScore = (text: string, index: number, streak: number): number =>\n  10 + (isWordStart(text, index) ? 8 : streak * 2);\n\nconst walk = (\n  query: string,\n  text: string,\n  queryIndex: number,\n  textIndex: number,\n  score: number,\n  streak: number,\n): Option.Option<number> => {\n  if (queryIndex >= Str.length(query)) return Option.some(score);\n  if (textIndex >= Str.length(text)) return Option.none();\n  return charAt(text, textIndex) === charAt(query, queryIndex)\n    ? walk(\n        query,\n        text,\n        queryIndex + 1,\n        textIndex + 1,\n        score + stepScore(text, textIndex, streak),\n        streak + 1,\n      )\n    : walk(query, text, queryIndex, textIndex + 1, score, 0);\n};\n\nexport const fuzzyScore = (\n  query: string,\n  text: string,\n): Option.Option<number> => walk(normalise(query), normalise(text), 0, 0, 0, 0);\n\nexport const fuzzyMatches = <A>(\n  items: ReadonlyArray<A>,\n  query: string,\n  textOf: (item: A) => string,\n): ReadonlyArray<FuzzyMatch<A>> =>\n  pipe(\n    Arr.map(items, (item, index) =>\n      Option.map(fuzzyScore(query, textOf(item)), (score) => ({\n        item,\n        score,\n        index,\n      })),\n    ),\n    Arr.getSomes,\n  );\n\nconst rankOrder = <A>(): Order.Order<FuzzyMatch<A>> =>\n  Order.combine(\n    Order.mapInput(Order.Number, (match: FuzzyMatch<A>) => -match.score),\n    Order.mapInput(Order.Number, (match: FuzzyMatch<A>) => match.index),\n  );\n\nexport const rankByGroup = <A>(\n  matches: ReadonlyArray<FuzzyMatch<A>>,\n  groupOf: (item: A) => string | undefined,\n): ReadonlyArray<A> => {\n  const groups = Arr.dedupe(Arr.map(matches, (match) => groupOf(match.item)));\n\n  return Arr.flatMap(groups, (group) =>\n    pipe(\n      Arr.filter(matches, (match) => groupOf(match.item) === group),\n      (inGroup) => Arr.sort(inGroup, rankOrder<A>()),\n      Arr.map((match) => match.item),\n    ),\n  );\n};\n\nexport const commandListOf = <A>(\n  value: ReadonlyArray<A> | null | undefined,\n): ReadonlyArray<A> => (Arr.isArray(value) ? value : []);\n\nexport const recentIdsOf = (value: unknown): ReadonlyArray<string> =>\n  Arr.isArray(value) ? Arr.filter(value, P.isString) : [];\n"
    },
    {
      "path": "packages/components/src/menu/item.tsx",
      "target": "@ui/gpuixui/components/menu/item.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props, PublicInstance } from \"@gpuix/react\";\nimport {\n  mergeStyles,\n  resolveStyle,\n  type StateStyle,\n} from \"@gpuix/react/floating\";\nimport { Icon } from \"../../icons/index.js\";\nimport { MENU_GROUP, MENU_ITEM, MENU_LABEL, MENU_SEPARATOR } from \"../../tw/index.js\";\nimport { Match } from \"effect\";\nimport * as Option from \"effect/Option\";\nimport * as P from \"effect/Predicate\";\nimport * as Schema from \"effect/Schema\";\nimport * as Str from \"effect/String\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useLayoutEffect,\n  useRef,\n  useState,\n  type ReactNode,\n} from \"react\";\nimport { overlaySurfaceStyle } from \"../internal/overlay.js\";\nimport {\n  controlHeight,\n  pressWash,\n  statusTone,\n  useTheme,\n} from \"../internal/theme.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"../internal/parts.js\";\nimport { useMergedRef } from \"../internal/ref.js\";\nimport { useMenuContext, useMenuLeaving } from \"./state.js\";\n\nconst MenuTones = Schema.Literals([\"default\", \"destructive\"]);\n\nexport type MenuItemTone = typeof MenuTones.Type;\n\nexport const menuToneOf = (value: unknown): MenuItemTone =>\n  Schema.is(MenuTones)(value) ? value : \"default\";\n\nexport interface MenuItemProps extends Omit<Props, \"style\"> {\n  readonly onSelect?: () => void;\n  readonly disabled?: boolean;\n\n  readonly tone?: MenuItemTone;\n\n  readonly leading?: ReactNode;\n\n  readonly trailing?: ReactNode;\n\n  readonly textValue?: string;\n\n  readonly closeOnSelect?: boolean;\n\n  readonly className?: string;\n\n  readonly style?: StateStyle<MenuItemState>;\n}\n\nexport interface MenuItemState {\n  readonly disabled: boolean;\n\n  readonly highlighted: boolean;\n}\n\nexport interface MenuRowState {\n  readonly tone: MenuItemTone;\n  readonly active: boolean;\n  readonly disabled: boolean;\n}\n\nexport const menuRowStyle = (\n  theme: ReturnType<typeof useTheme>,\n  row: MenuRowState,\n  className?: string,\n): Props[\"style\"] => {\n  const { tone, active, disabled } = row;\n  const color = disabled\n    ? theme.colors.disabledForeground\n    : Match.value(tone).pipe(\n        Match.when(\n          \"destructive\",\n          () => statusTone(theme, \"destructive\").foreground,\n        ),\n        Match.when(\"default\", () => undefined),\n        Match.exhaustive,\n      );\n\n  const appkit = theme.interaction === \"appkit\";\n  const pointerWash = { backgroundColor: theme.colors.hover };\n  const accentWash = {\n    backgroundColor: theme.colors.primary,\n    color: theme.colors.primaryForeground,\n  };\n  const highlight = appkit ? accentWash : pointerWash;\n\n  return {\n    ...overlaySurfaceStyle(MENU_ITEM, className, theme),\n    height: controlHeight(theme, \"sm\"),\n\n    ...(color === undefined ? {} : { color }),\n    ...(active && !disabled ? highlight : {}),\n    cursor: disabled ? \"not-allowed\" : \"pointer\",\n    hover: disabled ? undefined : highlight,\n    active: disabled\n      ? undefined\n      : appkit\n        ? accentWash\n        : { backgroundColor: pressWash(theme, \"hover\") },\n  };\n};\n\nconst Row = ({\n  leading,\n  trailing,\n  children,\n}: {\n  readonly leading?: ReactNode;\n  readonly trailing?: ReactNode;\n  readonly children: ReactNode;\n}) => (\n  <>\n    {leading === undefined ? null : leading}\n    <div\n      style={{\n        display: \"flex\",\n        flexDirection: \"row\",\n        flexGrow: 1,\n        minWidth: 0,\n      }}\n    >\n      {children}\n    </div>\n    {trailing === undefined ? null : trailing}\n  </>\n);\n\nexport const MenuItem = ({\n  onSelect,\n  disabled = false,\n  tone: requestedTone = \"default\",\n  leading,\n  trailing,\n  textValue,\n  closeOnSelect = true,\n  children,\n  onFocus,\n  onClick,\n  onMouseEnter,\n  onMouseLeave,\n  className,\n  style,\n  ref,\n  ...rest\n}: MenuItemProps) => {\n  const theme = useTheme();\n\n  const emphasis = menuToneOf(requestedTone);\n\n  const { activeId, register, noteFocus, notePointer, close, closeSubmenu } =\n    useMenuContext();\n\n  const leaving = useMenuLeaving();\n  const [instance, setInstance] = useState<PublicInstance | undefined>(\n    undefined,\n  );\n  const action = useRef<() => void>(() => undefined);\n\n  useLayoutEffect(() => {\n    action.current = () => {\n      onSelect?.();\n      if (closeOnSelect) close(\"activate\");\n    };\n  });\n\n  const attach = useCallback((next: PublicInstance | null) => {\n    setInstance(next ?? undefined);\n  }, []);\n  const setRow = useMergedRef<PublicInstance | null>(attach, ref);\n\n  const run = useCallback(() => action.current(), []);\n\n  const searchable = textValue ?? (P.isString(children) ? children : \"\");\n\n  useLayoutEffect(() => {\n    if (instance === undefined) return;\n    return register({\n      id: instance.id,\n      disabled,\n      label: searchable,\n      activate: run,\n    });\n  }, [disabled, instance, register, run, searchable]);\n\n  const active =\n    instance !== undefined && Option.contains(activeId, instance.id);\n  const state: MenuItemState = { disabled, highlighted: active };\n\n  return (\n    <div\n      role=\"menuitem\"\n      aria-label={Str.isEmpty(searchable) ? undefined : searchable}\n      {...rest}\n      ref={setRow}\n      tabIndex={disabled || leaving ? -1 : 0}\n      onFocus={(event) => {\n        if (instance !== undefined) noteFocus(instance.id);\n        onFocus?.(event);\n      }}\n      onClick={(event) => {\n        if (disabled) return;\n        onClick?.(event);\n        action.current();\n      }}\n      onMouseEnter={(event) => {\n        notePointer(true);\n        closeSubmenu(instance?.id);\n        onMouseEnter?.(event);\n      }}\n      onMouseLeave={(event) => {\n        notePointer(false);\n        onMouseLeave?.(event);\n      }}\n      style={mergeStyles(\n        menuRowStyle(theme, { tone: emphasis, active, disabled }, className),\n        resolveStyle(style, state),\n      )}\n    >\n      <Row leading={leading} trailing={trailing}>\n        {children}\n      </Row>\n    </div>\n  );\n};\n\nconst MenuCheckMark = ({ checked }: { readonly checked: boolean }) => {\n  const theme = useTheme();\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        flexDirection: \"row\",\n        alignItems: \"center\",\n        width: theme.iconSize.md,\n      }}\n    >\n      {checked ? (\n        <Icon\n          name=\"check\"\n          size=\"sm\"\n          color={theme.colors.primary}\n          label=\"Checked\"\n        />\n      ) : null}\n    </div>\n  );\n};\n\nexport interface MenuCheckboxItemProps extends Omit<MenuItemProps, \"style\"> {\n  readonly checked: boolean;\n  readonly onCheckedChange: (checked: boolean) => void;\n\n  readonly style?: StateStyle<MenuCheckboxItemState>;\n}\n\nexport interface MenuCheckboxItemState {\n  readonly checked: boolean;\n  readonly disabled: boolean;\n  readonly highlighted: boolean;\n}\n\nexport const MenuCheckboxItem = ({\n  checked,\n  onCheckedChange,\n  onSelect,\n  disabled = false,\n  leading,\n  trailing,\n  children,\n  style,\n  ref,\n  ...rest\n}: MenuCheckboxItemProps) => {\n  const { activeId } = useMenuContext();\n  const instance = useRef<PublicInstance | null>(null);\n  const attach = useCallback((next: PublicInstance | null) => {\n    instance.current = next;\n  }, []);\n  const setRow = useMergedRef<PublicInstance | null>(attach, ref);\n  const highlighted =\n    instance.current !== null && Option.contains(activeId, instance.current.id);\n  const state: MenuCheckboxItemState = { checked, disabled, highlighted };\n\n  return (\n    <MenuItem\n      role=\"menuitemcheckbox\"\n      aria-selected={checked}\n      {...rest}\n      ref={setRow}\n      disabled={disabled}\n      style={resolveStyle(style, state)}\n      closeOnSelect={false}\n      onSelect={() => {\n        onSelect?.();\n        onCheckedChange(!checked);\n      }}\n      leading={leading ?? <MenuCheckMark checked={checked} />}\n      trailing={trailing}\n    >\n      {children}\n    </MenuItem>\n  );\n};\n\nexport interface MenuRadioGroupProps extends Omit<Props, \"children\"> {\n  readonly value: string;\n\n  readonly onValueChange: (value: string) => void;\n\n  readonly label?: string;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\ninterface MenuRadioGroupValue {\n  readonly value: string;\n  readonly choose: (value: string) => void;\n}\n\nconst MenuRadioGroupContext = createContext<MenuRadioGroupValue | undefined>(\n  undefined,\n);\n\nconst useMenuRadioGroup = (): MenuRadioGroupValue => {\n  const value = useContext(MenuRadioGroupContext);\n  if (value === undefined) {\n    throw new Error(\n      \"MenuRadioItem must be used inside a MenuRadioGroup: the group owns the choice.\",\n    );\n  }\n  return value;\n};\n\nexport const MenuRadioGroup = ({\n  value,\n  onValueChange,\n  label,\n  className,\n  children,\n  style,\n  ...rest\n}: MenuRadioGroupProps) => {\n  assertParts(menuRadioGroup, children);\n\n  const group: MenuRadioGroupValue = { value, choose: onValueChange };\n\n  return (\n    <MenuRadioGroupContext.Provider value={group}>\n      <MenuGroupSurface\n        label={label}\n        className={className}\n        style={style}\n        {...rest}\n      >\n        {children}\n      </MenuGroupSurface>\n    </MenuRadioGroupContext.Provider>\n  );\n};\n\nexport interface MenuRadioItemProps extends Omit<\n  MenuItemProps,\n  \"style\" | \"onSelect\"\n> {\n  readonly value: string;\n\n  readonly style?: StateStyle<MenuRadioItemState>;\n}\n\nexport interface MenuRadioItemState {\n  readonly checked: boolean;\n  readonly disabled: boolean;\n  readonly highlighted: boolean;\n}\n\nexport const MenuRadioItem = ({\n  value,\n  disabled = false,\n  leading,\n  trailing,\n  children,\n  style,\n  ref,\n  ...rest\n}: MenuRadioItemProps) => {\n  const { value: chosen, choose } = useMenuRadioGroup();\n  const { activeId } = useMenuContext();\n  const instance = useRef<PublicInstance | null>(null);\n  const attach = useCallback((next: PublicInstance | null) => {\n    instance.current = next;\n  }, []);\n  const setRow = useMergedRef<PublicInstance | null>(attach, ref);\n  const checked = chosen === value;\n  const highlighted =\n    instance.current !== null && Option.contains(activeId, instance.current.id);\n  const state: MenuRadioItemState = { checked, disabled, highlighted };\n\n  return (\n    <MenuItem\n      role=\"menuitemradio\"\n      aria-selected={checked}\n      {...rest}\n      ref={setRow}\n      disabled={disabled}\n      style={resolveStyle(style, state)}\n      onSelect={() => {\n        if (!checked) choose(value);\n      }}\n      leading={leading ?? <MenuCheckMark checked={checked} />}\n      trailing={trailing}\n    >\n      {children}\n    </MenuItem>\n  );\n};\n\nexport interface MenuSeparatorProps extends Props {\n  readonly className?: string;\n}\n\nexport const MenuSeparator = ({\n  className,\n  style,\n  ...rest\n}: MenuSeparatorProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      role=\"separator\"\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(MENU_SEPARATOR, className, theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface MenuLabelProps extends Props {\n  readonly className?: string;\n}\n\nexport const MenuLabel = ({ className, style, ...rest }: MenuLabelProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(MENU_LABEL, className, theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface MenuGroupProps extends Omit<Props, \"children\"> {\n  readonly label?: string;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nconst MenuGroupSurface = ({\n  label,\n  className,\n  children,\n  style,\n  ...rest\n}: MenuGroupProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      {...rest}\n      role=\"group\"\n      aria-label={label}\n      style={mergeStyles(\n        overlaySurfaceStyle(MENU_GROUP, className, theme),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport const MenuGroup = ({\n  className,\n  children,\n  style,\n  ...rest\n}: MenuGroupProps) => {\n  assertParts(menuGroup, children);\n  return (\n    <MenuGroupSurface className={className} style={style} {...rest}>\n      {children}\n    </MenuGroupSurface>\n  );\n};\n\nconst menuRowParts: ReadonlyArray<PartComponent> = [\n  MenuItem,\n  MenuCheckboxItem,\n  MenuRadioGroup,\n  MenuRadioItem,\n  MenuGroup,\n  MenuLabel,\n  MenuSeparator,\n];\n\nconst menuGroup: PartsContract = {\n  parent: \"menu group\",\n  family: menuRowParts,\n  accepts: [\n    MenuItem,\n    MenuCheckboxItem,\n    MenuRadioGroup,\n    MenuLabel,\n    MenuSeparator,\n  ],\n  hint: \"Put rows, labels and separators directly inside the group.\",\n};\n\nconst menuRadioGroup: PartsContract = {\n  parent: \"MenuRadioGroup\",\n  family: menuRowParts,\n  accepts: [MenuRadioItem, MenuLabel, MenuSeparator],\n  hint: \"Put MenuRadioItem rows directly inside the radio group.\",\n};\n"
    },
    {
      "path": "packages/components/src/menu/logic.ts",
      "target": "@ui/gpuixui/components/menu/logic.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport { Match, pipe } from \"effect\";\nimport {\n  firstEnabled,\n  indexOfId,\n  lastEnabled,\n  nextEnabled,\n  type CollectionItem,\n} from \"../internal/collection.js\";\nimport type { TypeaheadItem } from \"../typeahead.js\";\n\nexport interface MenuItemSpec extends CollectionItem, TypeaheadItem {\n  readonly label: string;\n}\n\nexport interface MenuState {\n  readonly activeId: Option.Option<number>;\n}\n\nexport const initial: MenuState = { activeId: Option.none() };\n\nexport type MenuAction =\n  | { readonly _tag: \"Reset\" }\n  | { readonly _tag: \"Focus\"; readonly id: number }\n  | { readonly _tag: \"Move\"; readonly delta: 1 | -1 }\n  | { readonly _tag: \"First\" }\n  | { readonly _tag: \"Last\" };\n\nconst idAt = (\n  items: ReadonlyArray<MenuItemSpec>,\n  index: Option.Option<number>,\n): Option.Option<number> =>\n  pipe(\n    index,\n    Option.flatMap((at) => Option.map(Arr.get(items, at), (item) => item.id)),\n  );\n\nconst activeIndex = (\n  state: MenuState,\n  items: ReadonlyArray<MenuItemSpec>,\n): Option.Option<number> =>\n  pipe(\n    state.activeId,\n    Option.flatMap((id) => indexOfId(items, id)),\n  );\n\nexport const move = (\n  state: MenuState,\n  items: ReadonlyArray<MenuItemSpec>,\n  action: MenuAction,\n): MenuState =>\n  Match.value(action).pipe(\n    Match.when({ _tag: \"Reset\" }, () => initial),\n    Match.when({ _tag: \"Focus\" }, (focus) =>\n      pipe(\n        Arr.findFirst(items, (item) => item.id === focus.id),\n        Option.filter((item) => !item.disabled),\n        Option.match({\n          onNone: () => state,\n          onSome: () => ({ activeId: Option.some(focus.id) }),\n        }),\n      ),\n    ),\n    Match.when({ _tag: \"Move\" }, (step) => ({\n      activeId: pipe(\n        nextEnabled(items, activeIndex(state, items), step.delta),\n        Option.flatMap((at) => idAt(items, Option.some(at))),\n        Option.orElse(() => state.activeId),\n      ),\n    })),\n    Match.when({ _tag: \"First\" }, () => ({\n      activeId: idAt(items, firstEnabled(items)),\n    })),\n    Match.when({ _tag: \"Last\" }, () => ({\n      activeId: idAt(items, lastEnabled(items)),\n    })),\n    Match.exhaustive,\n  );\n\nexport const resolveActiveIndex = activeIndex;\n\nexport const isActive = (state: MenuState, id: number): boolean =>\n  Option.contains(state.activeId, id);\n"
    },
    {
      "path": "packages/components/src/menu/state.ts",
      "target": "@ui/gpuixui/components/menu/state.ts",
      "type": "registry:lib",
      "content": "import { useGpuix } from \"@gpuix/react\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { useTypeahead } from \"../typeahead.js\";\nimport {\n  initial,\n  move as moveActive,\n  resolveActiveIndex,\n  type MenuAction,\n  type MenuItemSpec,\n} from \"./logic.js\";\n\nexport interface MenuRow extends MenuItemSpec {\n  readonly activate: () => void;\n}\n\nexport type MenuCloseReason =\n  | \"activate\"\n  | \"dismiss\"\n  | \"outside\"\n  | \"leave\"\n  | \"pointer\";\n\nexport interface MenuContextValue {\n  readonly rows: ReadonlyArray<MenuRow>;\n  readonly activeId: Option.Option<number>;\n  readonly register: (row: MenuRow) => () => void;\n\n  readonly noteFocus: (id: number) => void;\n\n  readonly focusRow: (id: number) => void;\n\n  readonly step: (action: MenuAction) => void;\n\n  readonly typeahead: (char: string) => void;\n\n  readonly activateActive: () => void;\n\n  readonly notePointer: (present: boolean) => void;\n\n  readonly close: (reason: MenuCloseReason) => void;\n\n  readonly closeSubmenu: (rowId?: number) => void;\n\n  readonly claimSubmenu: (close: () => void, triggerId?: number) => () => void;\n}\n\nexport const MenuContext = createContext<MenuContextValue | undefined>(\n  undefined,\n);\n\nexport const MenuLeaving = createContext(false);\n\nexport const useMenuLeaving = (): boolean => useContext(MenuLeaving);\n\nexport const useMenuContext = (): MenuContextValue => {\n  const value = useContext(MenuContext);\n  if (value === undefined) {\n    throw new Error(\"Menu parts must be used inside a menu content\");\n  }\n  return value;\n};\n\nconst ignorePointer = (): void => undefined;\n\nconst upsert = (\n  rows: ReadonlyArray<MenuRow>,\n  row: MenuRow,\n): ReadonlyArray<MenuRow> => {\n  const existing = Arr.findFirst(rows, (candidate) => candidate.id === row.id);\n  return Option.match(existing, {\n    onNone: () => Arr.append(rows, row),\n    onSome: (current) =>\n      current.disabled === row.disabled && current.label === row.label\n        ? rows\n        : Arr.map(rows, (candidate) =>\n            candidate.id === row.id ? row : candidate,\n          ),\n  });\n};\n\nexport const useMenuState = (\n  close: (reason: MenuCloseReason) => void,\n  notePointer: (present: boolean) => void = ignorePointer,\n): MenuContextValue => {\n  const renderer = useGpuix().renderer;\n  const [rows, setRows] = useState<ReadonlyArray<MenuRow>>([]);\n  const [activeId, setActiveId] = useState<Option.Option<number>>(\n    initial.activeId,\n  );\n\n  const register = useCallback((row: MenuRow) => {\n    setRows((current) => upsert(current, row));\n    return () => {\n      setRows((current) =>\n        Arr.filter(current, (candidate) => candidate.id !== row.id),\n      );\n    };\n  }, []);\n\n  const noteFocus = useCallback((id: number) => {\n    setActiveId(Option.some(id));\n  }, []);\n\n  const focusRow = useCallback(\n    (id: number) => {\n      setActiveId(Option.some(id));\n      renderer?.focusElement?.(id);\n    },\n    [renderer],\n  );\n\n  const step = useCallback(\n    (action: MenuAction) => {\n      Option.match(moveActive({ activeId }, rows, action).activeId, {\n        onNone: () => undefined,\n        onSome: focusRow,\n      });\n    },\n    [activeId, focusRow, rows],\n  );\n\n  const typeahead = useTypeahead(\n    rows,\n    resolveActiveIndex({ activeId }, rows),\n    (row) => focusRow(row.id),\n  );\n\n  const activate = useCallback(\n    (id: number) => {\n      Option.match(\n        Arr.findFirst(rows, (row) => row.id === id),\n        {\n          onNone: () => undefined,\n          onSome: (row) => {\n            if (!row.disabled) row.activate();\n          },\n        },\n      );\n    },\n    [rows],\n  );\n\n  const activateActive = useCallback(() => {\n    Option.match(activeId, {\n      onNone: () => undefined,\n      onSome: activate,\n    });\n  }, [activate, activeId]);\n\n  const submenu = useRef<{\n    readonly close: () => void;\n    readonly triggerId: number | undefined;\n  } | null>(null);\n\n  const closeSubmenu = useCallback((rowId?: number) => {\n    const open = submenu.current;\n    if (open === null || (rowId !== undefined && open.triggerId === rowId)) {\n      return;\n    }\n    submenu.current = null;\n    open.close();\n  }, []);\n\n  const claimSubmenu = useCallback((close: () => void, triggerId?: number) => {\n    const previous = submenu.current;\n    submenu.current = { close, triggerId };\n    if (previous !== null && previous.close !== close) previous.close();\n    return () => {\n      if (submenu.current?.close === close) submenu.current = null;\n    };\n  }, []);\n\n  return useMemo(\n    () => ({\n      rows,\n      activeId,\n      register,\n      noteFocus,\n      focusRow,\n      step,\n      typeahead,\n      activateActive,\n      close,\n      notePointer,\n      closeSubmenu,\n      claimSubmenu,\n    }),\n    [\n      activateActive,\n      activeId,\n      claimSubmenu,\n      close,\n      closeSubmenu,\n      focusRow,\n      noteFocus,\n      notePointer,\n      register,\n      rows,\n      step,\n      typeahead,\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": "[Command menu](https://gpuixui.sudokw.com/components/command-menu) — 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/command-menu",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}