{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-user-menu",
  "type": "registry:component",
  "title": "User menu",
  "description": "An avatar and name that open the account actions",
  "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/avatar.tsx",
      "target": "@ui/gpuixui/components/avatar.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { AVATAR_DISC, AVATAR_FALLBACK, AVATAR_ROOT, cn } from \"../tw/index.js\";\nimport { Match } from \"effect\";\nimport { initials, isAvatarStatus } from \"./avatar/logic.js\";\nimport type { AvatarStatus } from \"./avatar/logic.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport {\n  avatarPixels,\n  avatarSize,\n  statusTone,\n  useTheme,\n  type Theme,\n} from \"./internal/theme.js\";\n\nexport type AvatarSize = keyof typeof avatarSize;\n\nexport type { AvatarStatus };\n\nexport interface AvatarProps extends Props {\n  readonly name: string;\n\n  readonly src?: string;\n\n  readonly size?: AvatarSize;\n\n  readonly status?: AvatarStatus;\n\n  readonly color?: string;\n\n  readonly initials?: boolean;\n\n  readonly radius?: number;\n\n  readonly clickThrough?: boolean;\n\n  readonly className?: string;\n}\n\nconst DOT = 10;\n\nconst statusFill = (theme: Theme, status: AvatarStatus): string =>\n  Match.value(status).pipe(\n    Match.when(\"online\", () => theme.colors.success),\n    Match.when(\"away\", () => statusTone(theme, \"warning\").foreground),\n    Match.when(\"busy\", () => theme.colors.destructive),\n    Match.when(\"offline\", () => theme.colors.mutedForeground),\n    Match.exhaustive,\n  );\n\nexport const Avatar = ({\n  name,\n  src,\n  size = \"md\",\n  status: statusProp,\n  color,\n  initials: showInitials = true,\n  clickThrough: requestedClickThrough,\n  className,\n  radius,\n  style,\n  ...rest\n}: AvatarProps) => {\n  const clickThrough =\n    requestedClickThrough ??\n    (rest.onClick === undefined &&\n      rest.onMouseDown === undefined &&\n      rest.onMouseUp === undefined);\n  const theme = useTheme();\n  const diameter = avatarPixels(size);\n\n  const status = isAvatarStatus(statusProp) ? statusProp : undefined;\n\n  return (\n    <div\n      role=\"img\"\n      aria-label={name}\n      aria-description={status ?? rest[\"aria-description\"]}\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(AVATAR_ROOT, className), theme), {\n          width: diameter,\n          height: diameter,\n          ...(clickThrough ? { pointerEvents: \"none\" as const } : {}),\n        }),\n        style,\n      )}\n    >\n      {}\n      <div\n        style={mergeStyles(recipeStyle(AVATAR_DISC, theme), {\n          pointerEvents: \"none\",\n          ...(color === undefined ? {} : { backgroundColor: color }),\n          ...(radius === undefined ? {} : { borderRadius: radius }),\n        })}\n      >\n        {src === undefined && !showInitials ? null : src === undefined ? (\n          <text\n            role=\"presentation\"\n            style={mergeStyles(recipeStyle(AVATAR_FALLBACK, theme), {\n              fontSize: Math.round(diameter * 0.44),\n            })}\n          >\n            {initials(name)}\n          </text>\n        ) : (\n          <img\n            src={src}\n            alt=\"\"\n            role=\"presentation\"\n            objectFit=\"cover\"\n            style={{ width: \"100%\", height: \"100%\" }}\n          />\n        )}\n      </div>\n      {status === undefined ? null : (\n        <div\n          style={{\n            position: \"absolute\",\n            right: -1,\n            bottom: -1,\n            width: DOT,\n            height: DOT,\n            borderRadius: theme.radius.full,\n            backgroundColor: statusFill(theme, status),\n            borderWidth: theme.borderWidth.strong,\n            borderColor: theme.colors.card,\n            pointerEvents: \"none\",\n          }}\n        />\n      )}\n    </div>\n  );\n};\n\nexport const avatarMeta = {\n  name: \"Avatars\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"yes\",\n  limitations: [\n    \"no image loading or error state in v1: the initials fallback is what shows until the picture paints\",\n  ],\n  docs: \"docs/avatar.mdx\",\n  example: \"examples/avatar.example.tsx\",\n  testFile: \"src/behaviour/avatar.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/avatar/logic.ts",
      "target": "@ui/gpuixui/components/avatar/logic.ts",
      "type": "registry:lib",
      "content": "import { pipe } from \"effect\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport * as Schema from \"effect/Schema\";\nimport * as Str from \"effect/String\";\n\nexport const AVATAR_STATUSES = [\"online\", \"away\", \"busy\", \"offline\"] as const;\n\nexport const AvatarStatus = Schema.Literals(AVATAR_STATUSES);\n\nexport type AvatarStatus = typeof AvatarStatus.Type;\n\nexport const isAvatarStatus = Schema.is(AvatarStatus);\n\nconst SEPARATORS = /[\\s-]+/;\n\nconst parts = (name: string): ReadonlyArray<string> =>\n  pipe(Str.trim(name), Str.split(SEPARATORS), Arr.filter(Str.isNonEmpty));\n\nconst leadingLetters = (name: string): ReadonlyArray<string> =>\n  Arr.getSomes(\n    Arr.map(parts(name), (part) =>\n      Option.map(Arr.head(Arr.fromIterable(part)), Str.toUpperCase),\n    ),\n  );\n\nexport const initials = (name: string): string => {\n  const letters = leadingLetters(name);\n\n  const last = Option.getOrElse(Arr.last(letters), () => \"\");\n\n  return Option.match(Arr.head(letters), {\n    onNone: () => \"?\",\n    onSome: (first) => (first === last ? first : `${first}${last}`),\n  });\n};\n"
    },
    {
      "path": "packages/components/src/dropdown-menu.tsx",
      "target": "@ui/gpuixui/components/dropdown-menu.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport { useGpuix } from \"@gpuix/react\";\nimport type { PublicInstance } from \"@gpuix/react\";\nimport {\n  FloatingLayer,\n  floatingRootStyle,\n  mergeStyles,\n  type FloatingContentProps,\n} from \"@gpuix/react/floating\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  type RefObject,\n} from \"react\";\nimport { Button, type ButtonProps } from \"./internal/button.js\";\nimport { useControllableState } from \"./internal/controllable.js\";\nimport {\n  dismissTopLayer,\n  isDismissalClick,\n  pushDismissLayer,\n} from \"./internal/dismiss-layer.js\";\nimport { DismissScope, useDismissDepth } from \"./internal/dismiss-scope.js\";\nimport { isActivation, isDown, isUp, swallowsDefault } from \"./internal/key.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport {\n  fadeFromAnchor,\n  PresenceShield,\n  presenceMotion,\n  presenceStyle,\n  useSurfacePresence,\n  useSurfaceTiming,\n} from \"./internal/presence.js\";\nimport { useMergedRef } from \"./internal/ref.js\";\nimport { useTheme } from \"./internal/theme.js\";\nimport { usePlacement } from \"./internal/use-placement.js\";\nimport {\n  MenuCheckboxItem,\n  MenuGroup,\n  MenuItem,\n  MenuLabel,\n  MenuRadioGroup,\n  MenuRadioItem,\n  MenuSeparator,\n} from \"./menu/item.js\";\nimport {\n  MenuContext,\n  MenuLeaving,\n  useMenuContext,\n  useMenuState,\n  type MenuCloseReason,\n} from \"./menu/state.js\";\nimport { MenuSub, MenuSubContent, MenuSubTrigger } from \"./menu/sub.js\";\nimport { menuKeyHandlers, menuSurfaceStyle } from \"./menu/surface.js\";\n\ninterface DropdownContextValue {\n  readonly open: boolean;\n  readonly setOpen: (open: boolean) => void;\n  readonly trigger: RefObject<PublicInstance | null>;\n\n  readonly pendingFocus: RefObject<\"first\" | \"last\" | null>;\n}\n\nconst DropdownContext = createContext<DropdownContextValue | undefined>(\n  undefined,\n);\n\nconst useDropdownContext = (): DropdownContextValue => {\n  const value = useContext(DropdownContext);\n  if (value === undefined) {\n    throw new Error(\"DropdownMenu parts must be used inside DropdownMenu\");\n  }\n  return value;\n};\n\nexport interface DropdownMenuProps {\n  readonly open?: boolean;\n\n  readonly defaultOpen?: boolean;\n  readonly onOpenChange?: (open: boolean) => void;\n  readonly children?: PartChildren;\n}\n\nexport const DropdownMenu = ({\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  children,\n}: DropdownMenuProps) => {\n  const [open, setOpen] = useControllableState({\n    value: openProp,\n    defaultValue: defaultOpen,\n    onChange: onOpenChange,\n  });\n  const renderer = useGpuix().renderer;\n  const trigger = useRef<PublicInstance | null>(null);\n  const restoreFocus = useRef(false);\n  const wasOpen = useRef(false);\n\n  const pendingFocus = useRef<\"first\" | \"last\" | null>(null);\n\n  const setOpenRef = useRef(setOpen);\n  useLayoutEffect(() => {\n    setOpenRef.current = setOpen;\n  });\n\n  const close = useCallback((reason: MenuCloseReason) => {\n    restoreFocus.current = reason === \"activate\" || reason === \"dismiss\";\n    if (reason === \"outside\") {\n    }\n    setOpenRef.current(false);\n  }, []);\n\n  const menu = useMenuState(close);\n\n  const depth = useDismissDepth();\n  useLayoutEffect(() => {\n    if (!open) return;\n    return pushDismissLayer((event) => {\n      close(event.eventType === \"keyDown\" ? \"dismiss\" : \"outside\");\n    }, depth);\n  }, [close, depth, open]);\n\n  useLayoutEffect(() => {\n    if (open) {\n      wasOpen.current = true;\n      return;\n    }\n    if (!wasOpen.current) return;\n    wasOpen.current = false;\n    if (!restoreFocus.current) return;\n    const target = trigger.current;\n    if (target !== null) renderer?.focusElement?.(target.id);\n  }, [open, renderer]);\n\n  useLayoutEffect(() => {\n    if (!open) pendingFocus.current = null;\n  }, [open]);\n\n  const context = useMemo<DropdownContextValue>(\n    () => ({ open, setOpen, trigger, pendingFocus }),\n    [open, setOpen],\n  );\n\n  assertParts(dropdownRoot, children);\n\n  return (\n    <DropdownContext.Provider value={context}>\n      <MenuContext.Provider value={menu}>\n        <div style={floatingRootStyle()}>{children}</div>\n      </MenuContext.Provider>\n    </DropdownContext.Provider>\n  );\n};\n\nexport const DropdownMenuTrigger = ({\n  onClick,\n  onKeyDown,\n  onKeyUp,\n  ref,\n  disabled = false,\n  ...rest\n}: ButtonProps) => {\n  const { open, setOpen, trigger, pendingFocus } = useDropdownContext();\n  const setTrigger = useMergedRef<PublicInstance | null>(trigger, ref);\n\n  const openAt = (edge: \"first\" | \"last\"): void => {\n    pendingFocus.current = edge;\n    setOpen(true);\n  };\n\n  return (\n    <Button\n      {...rest}\n      ref={setTrigger}\n      disabled={disabled}\n      aria-expanded={open}\n      onKeyDown={(event) => {\n        if (!disabled && !open) {\n          if (isDown(event)) openAt(\"first\");\n          else if (isUp(event)) openAt(\"last\");\n          else if (swallowsDefault(event, \"down\")) event.preventDefault();\n          else if (isActivation(event, \"down\")) openAt(\"first\");\n        }\n        onKeyDown?.(event);\n      }}\n      onKeyUp={(event) => {\n        if (!disabled && !open && isActivation(event, \"up\")) openAt(\"first\");\n        onKeyUp?.(event);\n      }}\n      onClick={(event) => {\n        onClick?.(event);\n\n        pendingFocus.current = null;\n\n        if (isDismissalClick(event)) return;\n        setOpen(!open);\n      }}\n    />\n  );\n};\n\nexport interface DropdownMenuContentProps extends FloatingContentProps {\n  readonly label?: string;\n\n  readonly className?: string;\n\n  readonly side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n\n  readonly align?: \"start\" | \"center\" | \"end\";\n\n  readonly sideOffset?: number;\n\n  readonly alignOffset?: number;\n  readonly children?: PartChildren;\n}\n\nexport const DropdownMenuContent = ({\n  label,\n  className,\n  side = \"bottom\",\n  align = \"start\",\n  sideOffset = 4,\n  alignOffset = 0,\n  collisionPadding = 8,\n  children,\n  onKeyDown,\n  onKeyUp,\n  onMouseDownOutside,\n  style,\n  ref,\n  ...rest\n}: DropdownMenuContentProps) => {\n  const theme = useTheme();\n  const { open, trigger, pendingFocus } = useDropdownContext();\n  const menu = useMenuContext();\n  const keys = menuKeyHandlers(menu);\n  const surface = useRef<PublicInstance | null>(null);\n  const setSurface = useMergedRef<PublicInstance | null>(surface, ref);\n  const timing = useSurfaceTiming(\"normal\");\n  const presence = useSurfacePresence(open, timing);\n  const placement = usePlacement({\n    open: presence.mounted,\n    side,\n    align,\n    margin: collisionPadding,\n    trigger,\n    surface,\n  });\n  assertParts(dropdownContent, children);\n\n  useLayoutEffect(() => {\n    if (!open) return;\n    const edge = pendingFocus.current;\n    if (edge === null || menu.rows.length === 0) return;\n    pendingFocus.current = null;\n    menu.step({ _tag: edge === \"first\" ? \"First\" : \"Last\" });\n  }, [menu, open, pendingFocus]);\n\n  if (!presence.mounted) return null;\n\n  return (\n    <FloatingLayer\n      motion={presenceMotion(fadeFromAnchor(placement.side, 4), presence)}\n      side={placement.side}\n      align={placement.align}\n      sideOffset={sideOffset}\n      alignOffset={placement.alignOffset + alignOffset}\n      collisionPadding={collisionPadding}\n      role=\"menu\"\n      {...(label === undefined ? {} : { \"aria-label\": label })}\n      tabIndex={-1}\n      autoFocus\n      ref={setSurface}\n      {...rest}\n      onMouseDownOutside={(event) => {\n        onMouseDownOutside?.(event);\n        dismissTopLayer(event);\n      }}\n      onKeyDown={(event) => {\n        keys.onKeyDown(event);\n        onKeyDown?.(event);\n      }}\n      onKeyUp={(event) => {\n        keys.onKeyUp(event);\n        onKeyUp?.(event);\n      }}\n      style={mergeStyles(\n        mergeStyles(\n          menuSurfaceStyle(theme, className),\n\n          { position: \"relative\", ...presenceStyle(presence.present) },\n        ),\n        style,\n      )}\n    >\n      <MenuLeaving.Provider value={!presence.present}>\n        <DismissScope>{children}</DismissScope>\n      </MenuLeaving.Provider>\n      <PresenceShield present={presence.present} />\n    </FloatingLayer>\n  );\n};\n\nexport {\n  MenuSub as DropdownMenuSub,\n  MenuSubContent as DropdownMenuSubContent,\n  MenuSubTrigger as DropdownMenuSubTrigger,\n} from \"./menu/sub.js\";\n\nexport type {\n  MenuSubContentProps as DropdownMenuSubContentProps,\n  MenuSubProps as DropdownMenuSubProps,\n  MenuSubTriggerProps as DropdownMenuSubTriggerProps,\n} from \"./menu/sub.js\";\n\nexport {\n  MenuCheckboxItem as DropdownMenuCheckboxItem,\n  MenuGroup as DropdownMenuGroup,\n  MenuItem as DropdownMenuItem,\n  MenuLabel as DropdownMenuLabel,\n  MenuRadioGroup as DropdownMenuRadioGroup,\n  MenuRadioItem as DropdownMenuRadioItem,\n  MenuSeparator as DropdownMenuSeparator,\n};\n\nexport type {\n  MenuCheckboxItemProps as DropdownMenuCheckboxItemProps,\n  MenuItemProps as DropdownMenuItemProps,\n  MenuRadioGroupProps as DropdownMenuRadioGroupProps,\n  MenuRadioItemProps as DropdownMenuRadioItemProps,\n} from \"./menu/item.js\";\n\nconst dropdownParts: ReadonlyArray<PartComponent> = [\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  MenuItem,\n  MenuCheckboxItem,\n  MenuRadioGroup,\n  MenuRadioItem,\n  MenuGroup,\n  MenuLabel,\n  MenuSeparator,\n  MenuSub,\n  MenuSubTrigger,\n  MenuSubContent,\n];\n\nconst dropdownRoot: PartsContract = {\n  parent: \"DropdownMenu\",\n  family: dropdownParts,\n  accepts: [DropdownMenuTrigger, DropdownMenuContent],\n  hint: \"Put DropdownMenuTrigger and DropdownMenuContent directly inside DropdownMenu.\",\n};\n\nconst dropdownContent: PartsContract = {\n  parent: \"DropdownMenuContent\",\n  family: dropdownParts,\n  accepts: [\n    MenuItem,\n    MenuCheckboxItem,\n    MenuRadioGroup,\n    MenuGroup,\n    MenuLabel,\n    MenuSeparator,\n    MenuSub,\n  ],\n  hint: \"Put rows, groups, labels, separators and submenus directly inside DropdownMenuContent.\",\n};\n\nexport const dropdownMenuMeta = {\n  name: \"Dropdown menus\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"one level of submenus is tested; deeper nesting follows the same rule but is untested\",\n    'the root surface flips once when snapping would cover its trigger, and align=\"center\" is measured from the trigger; the snap margin wins near a window edge',\n    'submenus open to the right and are clamped by fit=\"snap\"; there is no left-side flip',\n    \"no aria-haspopup in 0.11.0; a submenu trigger announces aria-expanded only\",\n    \"type-ahead is a case-insensitive 500 ms prefix search with no fuzzy scoring; a composed label needs textValue\",\n    \"the checkbox and radio rows use aria-selected and a check icon because 0.11.0 has no aria-checked\",\n    \"a radio group is controlled: the popup unmounts when it closes, so the caller owns the choice\",\n  ],\n  docs: \"docs/dropdown-menu.mdx\",\n  example: \"examples/dropdown-menu.example.tsx\",\n  testFile: \"src/behaviour/dropdown-menu.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/field.tsx",
      "target": "@ui/gpuixui/components/field.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { InputProps, Props } from \"@gpuix/react\";\nimport {\n  mergeStyles,\n  resolveStyle,\n  type StateStyle,\n} from \"@gpuix/react/floating\";\nimport {\n  cn,\n  FIELD_DESCRIPTION,\n  FIELD_ERROR,\n  FIELD_INPUT,\n  FIELD_LABEL,\n  FIELD_LABEL_ROW,\n  FIELD_REQUIRED,\n  FIELD_ROOT,\n} from \"../tw/index.js\";\nimport * as Option from \"effect/Option\";\nimport { createContext, useContext, useState } from \"react\";\nimport { useFieldsetDisabled } from \"./fieldset.js\";\nimport {\n  fieldHelp,\n  fieldHint,\n  hintHost,\n  renderFieldHint,\n} from \"../primitives/components/hint.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport {\n  controlHeight,\n  keepContent,\n  keyboardRing,\n  nativeTextTheme,\n  selectionWash,\n  statusTone,\n  useTheme,\n} from \"./internal/theme.js\";\n\ninterface FieldContextValue {\n  readonly label: string;\n  readonly description: string | undefined;\n  readonly error: string | undefined;\n  readonly required: boolean;\n}\n\nconst FieldContext = createContext<FieldContextValue | undefined>(undefined);\n\nexport interface FieldProps extends Props {\n  readonly label: string;\n\n  readonly description?: string;\n\n  readonly error?: string;\n  readonly required?: boolean;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const Field = ({\n  label,\n  description,\n  error,\n  required = false,\n  className,\n  children,\n  style,\n  ...rest\n}: FieldProps) => {\n  const theme = useTheme();\n  const context: FieldContextValue = { label, description, error, required };\n  assertParts(fieldRoot, children);\n\n  return (\n    <FieldContext.Provider value={context}>\n      <div\n        {...rest}\n        style={mergeStyles(\n          recipeStyle(cn(FIELD_ROOT, className), theme),\n          style,\n        )}\n      >\n        <FieldLabel required={required}>{label}</FieldLabel>\n        {children}\n        {error === undefined ? (\n          description === undefined ? null : (\n            <FieldDescription>{description}</FieldDescription>\n          )\n        ) : (\n          <FieldError>{error}</FieldError>\n        )}\n      </div>\n    </FieldContext.Provider>\n  );\n};\n\nexport interface FieldLabelProps extends Props {\n  readonly required?: boolean;\n\n  readonly className?: string;\n}\n\nexport const FieldLabel = ({\n  required = false,\n  className,\n  children,\n  style,\n  ...rest\n}: FieldLabelProps) => {\n  const theme = useTheme();\n  return (\n    <div style={recipeStyle(FIELD_LABEL_ROW, theme)}>\n      <text\n        {...rest}\n        style={mergeStyles(\n          recipeStyle(cn(FIELD_LABEL, className), theme),\n          style,\n        )}\n      >\n        {children}\n      </text>\n      {required ? (\n        <text\n          aria-label=\"Required\"\n          style={{\n            ...recipeStyle(FIELD_REQUIRED, theme),\n            color: statusTone(theme, \"destructive\").foreground,\n          }}\n        >\n          *\n        </text>\n      ) : null}\n    </div>\n  );\n};\n\nexport interface FieldDescriptionProps extends Props {\n  readonly className?: string;\n}\n\nexport const FieldDescription = ({\n  className,\n  style,\n  ...rest\n}: FieldDescriptionProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(FIELD_DESCRIPTION, className), theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface FieldErrorProps extends Props {\n  readonly className?: string;\n}\n\nexport const FieldError = ({ className, style, ...rest }: FieldErrorProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      role=\"alert\"\n      {...rest}\n      style={mergeStyles(\n        {\n          ...recipeStyle(cn(FIELD_ERROR, className), theme),\n          color: statusTone(theme, \"destructive\").foreground,\n        },\n        style,\n      )}\n    />\n  );\n};\n\nexport interface FieldInputProps extends Omit<InputProps, \"style\"> {\n  readonly invalid?: boolean;\n\n  readonly disabled?: boolean;\n\n  readonly className?: string;\n\n  readonly style?: StateStyle<FieldInputState>;\n}\n\nexport interface FieldInputState {\n  readonly disabled: boolean;\n  readonly focused: boolean;\n  readonly invalid: boolean;\n}\n\nexport const FieldInput = ({\n  invalid,\n  disabled = false,\n  className,\n  onFocus,\n  onBlur,\n  style,\n  placeholder,\n  value,\n  onChange,\n  ...rest\n}: FieldInputProps) => {\n  const theme = useTheme();\n  const field = useContext(FieldContext);\n  const fieldsetDisabled = useFieldsetDisabled();\n  const [focused, setFocused] = useState(false);\n\n  const [draft, setDraft] = useState(\"\");\n  const text = value ?? draft;\n  const isInvalid = invalid ?? field?.error !== undefined;\n  const isDisabled = disabled || fieldsetDisabled;\n  const state: FieldInputState = {\n    disabled: isDisabled,\n    focused,\n    invalid: isInvalid,\n  };\n\n  const message = field?.error ?? field?.description;\n  const description = Option.match(Option.fromNullishOr(message), {\n    onNone: () => (field?.required === true ? \"Required\" : undefined),\n    onSome: (text) => (field?.required === true ? `${text}. Required` : text),\n  });\n\n  const help = fieldHelp(\n    { description: description ?? rest[\"aria-description\"], placeholder },\n    text,\n  );\n\n  const hostStyle = mergeStyles(\n    mergeStyles(recipeStyle(cn(FIELD_INPUT, className), theme), {\n      height: controlHeight(theme, \"md\"),\n      backgroundColor: isDisabled ? theme.colors.disabled : theme.colors.input,\n      color: isDisabled\n        ? theme.colors.disabledForeground\n        : theme.colors.inputForeground,\n      borderColor:\n        isInvalid && !isDisabled\n          ? theme.colors.destructive\n          : theme.colors.border,\n      ...(isDisabled ? { cursor: \"not-allowed\" } : {}),\n      ...(isDisabled ? {} : keyboardRing(theme, isInvalid)),\n      ...selectionWash(theme),\n      ...keepContent,\n    }),\n    resolveStyle(style, state),\n  );\n  const hint = fieldHint({ placeholder, disabled: isDisabled }, theme, text, {\n    origin: \"host\",\n    align: \"center\",\n    style: hostStyle,\n  });\n\n  return (\n    <div style={hintHost()}>\n      <input\n        {...rest}\n        theme={nativeTextTheme(theme, \"input\")}\n        value={value}\n        {...(isDisabled ? { readOnly: true, tabIndex: -1 } : {})}\n        {...(field === undefined ? {} : { \"aria-label\": field.label })}\n        {...(help === undefined ? {} : { \"aria-description\": help })}\n        onChange={(event) => {\n          if (value === undefined) setDraft(event.value ?? \"\");\n          onChange?.(event);\n        }}\n        onFocus={(event) => {\n          setFocused(true);\n          onFocus?.(event);\n        }}\n        onBlur={(event) => {\n          setFocused(false);\n          onBlur?.(event);\n        }}\n        style={hostStyle}\n      />\n      {Option.match(hint, {\n        onNone: () => null,\n        onSome: (painted) => renderFieldHint(painted, rest.testId),\n      })}\n    </div>\n  );\n};\n\nconst fieldParts: ReadonlyArray<PartComponent> = [\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldInput,\n];\n\nconst fieldRoot: PartsContract = {\n  parent: \"Field\",\n  family: fieldParts,\n  accepts: [FieldInput],\n  hint: \"Field renders its label and message itself; put only the control inside it.\",\n};\n\nexport const fieldMeta = {\n  name: \"Inline validation\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"no aria-describedby in 0.11.0: the message is an aria-description, not an id reference\",\n    \"the control is a GPUix <input> painted by this package's fixed field recipe; @gpuixui/primitives' Input paints its own density-scaled chrome (ADR 0006), so it is not adopted here\",\n    \"not a form library: the caller owns the value and when validation runs\",\n  ],\n  docs: \"docs/field.mdx\",\n  example: \"examples/field.example.tsx\",\n  testFile: \"src/behaviour/field.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/fieldset.tsx",
      "target": "@ui/gpuixui/components/fieldset.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { cn, FIELDSET_LEGEND, FIELDSET_ROOT } from \"../tw/index.js\";\nimport { createContext, useContext } from \"react\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport { useTheme } from \"./internal/theme.js\";\n\ninterface FieldsetContextValue {\n  readonly disabled: boolean;\n}\n\nconst FieldsetContext = createContext<FieldsetContextValue | undefined>(\n  undefined,\n);\n\nexport const useFieldsetDisabled = (): boolean =>\n  useContext(FieldsetContext)?.disabled ?? false;\n\nexport interface FieldsetProps extends Omit<Props, \"children\"> {\n  readonly legend: string;\n\n  readonly disabled?: boolean;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const Fieldset = ({\n  legend,\n  disabled = false,\n  className,\n  children,\n  style,\n  ...rest\n}: FieldsetProps) => {\n  const theme = useTheme();\n  assertParts(fieldsetRoot, children);\n\n  return (\n    <FieldsetContext.Provider value={{ disabled }}>\n      <div\n        role=\"group\"\n        aria-label={legend}\n        {...rest}\n        style={mergeStyles(\n          recipeStyle(cn(FIELDSET_ROOT, className), theme),\n          style,\n        )}\n      >\n        <FieldsetLegend disabled={disabled}>{legend}</FieldsetLegend>\n        {children}\n      </div>\n    </FieldsetContext.Provider>\n  );\n};\n\nexport interface FieldsetLegendProps extends Props {\n  readonly disabled?: boolean;\n\n  readonly className?: string;\n}\n\nexport const FieldsetLegend = ({\n  disabled = false,\n  className,\n  style,\n  ...rest\n}: FieldsetLegendProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(FIELDSET_LEGEND, className), theme), {\n          color: disabled\n            ? theme.colors.disabledForeground\n            : theme.colors.foreground,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nconst fieldsetParts: ReadonlyArray<PartComponent> = [Fieldset, FieldsetLegend];\n\nconst fieldsetRoot: PartsContract = {\n  parent: \"Fieldset\",\n  family: fieldsetParts,\n  accepts: [],\n  hint: \"Fieldset renders its legend itself; put Field, or any other control, inside it.\",\n};\n\nexport const fieldsetMeta = {\n  name: \"Fieldset\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"no fieldset element and no aria-disabled in 0.11.0: the group is role=group named by its legend, and disabled reaches only FieldInput\",\n    \"only the legend dims with disabled; controls that do not read the context keep their own disabled prop\",\n  ],\n  docs: \"docs/fieldset.mdx\",\n  example: \"examples/fieldset.example.tsx\",\n  testFile: \"src/behaviour/fieldset.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/event-claim.ts",
      "target": "@ui/gpuixui/components/internal/event-claim.ts",
      "type": "registry:lib",
      "content": "import type { EventPayload } from \"@gpuix/react\";\nimport * as Option from \"effect/Option\";\n\ninterface KeyClaim {\n  readonly eventType: string;\n  readonly key: string | undefined;\n  readonly keyChar: string | undefined;\n  readonly modifiers: string;\n}\n\nlet claim: Option.Option<KeyClaim> = Option.none();\n\nconst modifiersOf = (event: EventPayload): string => {\n  const modifiers = event.modifiers;\n  return [\n    modifiers?.cmd === true ? \"cmd\" : \"\",\n    modifiers?.ctrl === true ? \"ctrl\" : \"\",\n    modifiers?.alt === true ? \"alt\" : \"\",\n    modifiers?.shift === true ? \"shift\" : \"\",\n  ].join(\"+\");\n};\n\nconst claimOf = (event: EventPayload): KeyClaim => ({\n  eventType: event.eventType,\n  key: event.key,\n  keyChar: event.keyChar,\n  modifiers: modifiersOf(event),\n});\n\nconst sameClaim = (left: KeyClaim, right: KeyClaim): boolean =>\n  left.eventType === right.eventType &&\n  left.key === right.key &&\n  left.keyChar === right.keyChar &&\n  left.modifiers === right.modifiers;\n\nexport const claimEvent = (event: EventPayload): void => {\n  claim = Option.some(claimOf(event));\n  queueMicrotask(() => {\n    claim = Option.none();\n  });\n};\n\nexport const isEventClaimed = (event: EventPayload): boolean =>\n  Option.match(claim, {\n    onNone: () => false,\n    onSome: (current) => {\n      if (!sameClaim(current, claimOf(event))) return false;\n\n      claim = Option.none();\n      return true;\n    },\n  });\n"
    },
    {
      "path": "packages/components/src/internal/hover.ts",
      "target": "@ui/gpuixui/components/internal/hover.ts",
      "type": "registry:lib",
      "content": "import { Effect } from \"effect\";\nimport { useRef } from \"react\";\nimport { makeLocalState, send, useLocalStore } from \"./local-state.js\";\n\nexport type HoverSurfaceName = \"trigger\" | \"content\";\n\ninterface HoverHold {\n  readonly pointer: Readonly<Record<HoverSurfaceName, boolean>>;\n  readonly focus: Readonly<Record<HoverSurfaceName, boolean>>;\n}\n\nconst released: HoverHold = {\n  pointer: { trigger: false, content: false },\n  focus: { trigger: false, content: false },\n};\n\nconst holdStore = Effect.map(makeLocalState(released), (local) => ({\n  local,\n}));\n\nexport interface HoverTiming {\n  readonly open: boolean;\n  readonly setOpen: (open: boolean) => void;\n\n  readonly openDelay: number;\n\n  readonly closeDelay: number;\n}\n\nexport interface HoverControls {\n  readonly pointer: (surface: HoverSurfaceName, present: boolean) => void;\n\n  readonly focus: (surface: HoverSurfaceName, present: boolean) => void;\n\n  readonly cancel: () => void;\n\n  readonly cancelOpening: () => void;\n\n  readonly request: (open: boolean) => void;\n\n  readonly held: () => boolean;\n}\n\nconst holds = (hold: HoverHold): boolean =>\n  hold.pointer.trigger ||\n  hold.pointer.content ||\n  hold.focus.trigger ||\n  hold.focus.content;\n\nexport const useHoverDelays = (timing: HoverTiming): HoverControls => {\n  const { local } = useLocalStore(holdStore);\n  const latest = useRef(timing);\n  latest.current = timing;\n\n  const cancel = (): void => {\n    send(local.cancel);\n  };\n\n  const request = (open: boolean): void => {\n    if (latest.current.open === open) return;\n    latest.current.setOpen(open);\n  };\n\n  const aim = (open: boolean, delay: number): void => {\n    cancel();\n    send(\n      local.after(\n        delay,\n        Effect.sync(() => {\n          request(open);\n        }),\n      ),\n    );\n  };\n\n  const hold = (\n    channel: \"pointer\" | \"focus\",\n    surface: HoverSurfaceName,\n    present: boolean,\n  ): void => {\n    const state = local.read();\n    const next: HoverHold = {\n      ...state,\n      [channel]: { ...state[channel], [surface]: present },\n    };\n    send(local.write(next));\n\n    if (present) {\n      cancel();\n      if (latest.current.open) return;\n      if (channel === \"focus\") {\n        request(true);\n        return;\n      }\n      aim(true, latest.current.openDelay);\n      return;\n    }\n    if (holds(next)) return;\n    if (!latest.current.open) {\n      cancel();\n      return;\n    }\n    aim(false, latest.current.closeDelay);\n  };\n\n  return {\n    pointer: (surface, present) => hold(\"pointer\", surface, present),\n    focus: (surface, present) => hold(\"focus\", surface, present),\n    cancel,\n    cancelOpening: () => {\n      if (!latest.current.open) cancel();\n    },\n    request,\n    held: () => holds(local.read()),\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/local-state.ts",
      "target": "@ui/gpuixui/components/internal/local-state.ts",
      "type": "registry:lib",
      "content": "import { Effect } from \"effect\";\nimport * as Arr from \"effect/Array\";\nimport * as Duration from \"effect/Duration\";\nimport * as Fiber from \"effect/Fiber\";\nimport * as Option from \"effect/Option\";\nimport * as Ref from \"effect/Ref\";\nimport * as SubscriptionRef from \"effect/SubscriptionRef\";\nimport { useEffect, useState, useSyncExternalStore } from \"react\";\n\nexport interface LocalState<A> {\n  readonly state: SubscriptionRef.SubscriptionRef<A>;\n\n  readonly read: () => A;\n\n  readonly subscribe: (listener: () => void) => () => void;\n\n  readonly write: (next: A) => Effect.Effect<void>;\n\n  readonly after: (\n    millis: number,\n    effect: Effect.Effect<void>,\n  ) => Effect.Effect<void>;\n\n  readonly cancel: Effect.Effect<void>;\n\n  readonly running: () => boolean;\n}\n\nexport const makeLocalState = <A>(initial: A): Effect.Effect<LocalState<A>> =>\n  Effect.gen(function* () {\n    const state = yield* SubscriptionRef.make(initial);\n    const timer = yield* Ref.make(Option.none<Fiber.Fiber<void>>());\n    const listeners = yield* Ref.make<ReadonlyArray<() => void>>(Arr.empty());\n\n    const write = Effect.fnUntraced(function* (next: A) {\n      yield* SubscriptionRef.set(state, next);\n      const subscribers = yield* Ref.get(listeners);\n      yield* Effect.sync(() => {\n        Arr.forEach(subscribers, (listener) => listener());\n      });\n    });\n\n    const cancel = Effect.gen(function* () {\n      const held = yield* Ref.getAndSet(timer, Option.none());\n      yield* Option.match(held, {\n        onNone: () => Effect.void,\n        onSome: Fiber.interrupt,\n      });\n    });\n\n    const after = Effect.fnUntraced(function* (\n      millis: number,\n      effect: Effect.Effect<void>,\n    ) {\n      yield* cancel;\n      const fiber = yield* Effect.forkDetach(\n        Effect.sleep(Duration.millis(millis)).pipe(Effect.andThen(effect)),\n      );\n      yield* Ref.set(timer, Option.some(fiber));\n    });\n\n    const running = (): boolean =>\n      Option.exists(\n        Ref.getUnsafe(timer),\n        (fiber) => fiber.pollUnsafe() === undefined,\n      );\n\n    const subscribe = (listener: () => void): (() => void) => {\n      Effect.runSync(\n        Ref.update(listeners, (current) => Arr.append(current, listener)),\n      );\n      return () => {\n        Effect.runSync(\n          Ref.update(listeners, (current) =>\n            Arr.filter(current, (held) => held !== listener),\n          ),\n        );\n      };\n    };\n\n    return {\n      state,\n\n      read: () => SubscriptionRef.getUnsafe(state),\n      subscribe,\n      write,\n      after,\n      cancel,\n      running,\n    };\n  });\n\ninterface Teardown {\n  readonly local: { readonly cancel: Effect.Effect<void> };\n}\n\nexport const useLocalStore = <Store extends Teardown>(\n  make: Effect.Effect<Store>,\n): Store => {\n  const [store] = useState(() => Effect.runSync(make));\n  useEffect(() => () => send(store.local.cancel), [store]);\n  return store;\n};\n\nexport const send = (command: Effect.Effect<void>): void => {\n  Effect.runFork(command);\n};\n\nexport const useLocalState = <A>(local: LocalState<A>): A =>\n  useSyncExternalStore(local.subscribe, local.read, local.read);\n\nexport const useLocalSlice = <A, B>(\n  local: LocalState<A>,\n  select: (state: A) => B,\n): B =>\n  useSyncExternalStore(\n    local.subscribe,\n    () => select(local.read()),\n    () => select(local.read()),\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/placement.ts",
      "target": "@ui/gpuixui/components/internal/placement.ts",
      "type": "registry:lib",
      "content": "import type { FloatingAlign, FloatingSide } from \"@gpuix/react/floating\";\nimport { Match } from \"effect\";\nimport * as Schema from \"effect/Schema\";\n\nexport interface FloatingBounds {\n  readonly x: number;\n  readonly y: number;\n  readonly width: number;\n  readonly height: number;\n}\n\nexport interface WindowBounds {\n  readonly width: number;\n  readonly height: number;\n}\n\nexport interface PlacementInput {\n  readonly side: unknown;\n\n  readonly align: unknown;\n  readonly trigger: FloatingBounds;\n\n  readonly surface: FloatingBounds;\n  readonly window: WindowBounds;\n  readonly margin: number;\n}\n\nexport interface Placement {\n  readonly side: FloatingSide;\n  readonly align: FloatingAlign;\n  readonly alignOffset: number;\n}\n\nconst PlacementSide = Schema.Literals([\"top\", \"right\", \"bottom\", \"left\"]);\n\nexport const isAlign = Schema.is(Schema.Literals([\"start\", \"center\", \"end\"]));\n\nconst sideOf = (side: unknown): FloatingSide =>\n  Schema.is(PlacementSide)(side) ? side : \"bottom\";\n\nconst alignOf = (align: unknown): FloatingAlign =>\n  isAlign(align) ? align : \"start\";\n\nconst oppositeSide = (side: unknown): FloatingSide =>\n  Match.value(sideOf(side)).pipe(\n    Match.when(\"top\", () => \"bottom\" as const),\n    Match.when(\"bottom\", () => \"top\" as const),\n    Match.when(\"left\", () => \"right\" as const),\n    Match.when(\"right\", () => \"left\" as const),\n    Match.exhaustive,\n  );\n\nconst roomOn = (side: unknown, input: PlacementInput): number =>\n  Match.value(sideOf(side)).pipe(\n    Match.when(\"top\", () => input.trigger.y - input.margin),\n    Match.when(\n      \"bottom\",\n      () =>\n        input.window.height -\n        input.margin -\n        (input.trigger.y + input.trigger.height),\n    ),\n    Match.when(\"left\", () => input.trigger.x - input.margin),\n    Match.when(\n      \"right\",\n      () =>\n        input.window.width -\n        input.margin -\n        (input.trigger.x + input.trigger.width),\n    ),\n    Match.exhaustive,\n  );\n\nconst extentOn = (side: unknown, surface: FloatingBounds): number =>\n  Match.value(sideOf(side)).pipe(\n    Match.when(\"top\", () => surface.height),\n    Match.when(\"bottom\", () => surface.height),\n    Match.when(\"left\", () => surface.width),\n    Match.when(\"right\", () => surface.width),\n    Match.exhaustive,\n  );\n\nexport const resolveSide = (input: PlacementInput): FloatingSide => {\n  const side = sideOf(input.side);\n  const extent = extentOn(side, input.surface);\n  if (roomOn(side, input) >= extent) return side;\n\n  const opposite = oppositeSide(side);\n  const oppositeRoom = roomOn(opposite, input);\n  if (oppositeRoom >= extentOn(opposite, input.surface)) return opposite;\n\n  return oppositeRoom > roomOn(side, input) ? opposite : side;\n};\n\nconst alignOffsetOn = (\n  side: unknown,\n  align: unknown,\n  trigger: FloatingBounds,\n  surface: FloatingBounds,\n): number =>\n  Match.value(alignOf(align)).pipe(\n    Match.when(\"center\", () =>\n      Match.value(sideOf(side)).pipe(\n        Match.when(\"top\", () => (trigger.width - surface.width) / 2),\n        Match.when(\"bottom\", () => (trigger.width - surface.width) / 2),\n        Match.when(\"left\", () => (trigger.height - surface.height) / 2),\n        Match.when(\"right\", () => (trigger.height - surface.height) / 2),\n        Match.exhaustive,\n      ),\n    ),\n    Match.when(\"start\", () => 0),\n    Match.when(\"end\", () => 0),\n    Match.exhaustive,\n  );\n\nconst hostAlign = (align: FloatingAlign): \"start\" | \"end\" =>\n  align === \"end\" ? \"end\" : \"start\";\n\nexport const requestedPlacement = (\n  side: unknown,\n  align: unknown,\n): Placement => ({\n  side: sideOf(side),\n  align: hostAlign(alignOf(align)),\n  alignOffset: 0,\n});\n\nexport const resolvePlacement = (input: PlacementInput): Placement => {\n  const side = resolveSide(input);\n  return {\n    side,\n    align: hostAlign(alignOf(input.align)),\n    alignOffset: alignOffsetOn(side, input.align, input.trigger, input.surface),\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/internal/use-placement.ts",
      "target": "@ui/gpuixui/components/internal/use-placement.ts",
      "type": "registry:lib",
      "content": "import { useGpuix, type PublicInstance } from \"@gpuix/react\";\nimport type { FloatingAlign, FloatingSide } from \"@gpuix/react/floating\";\nimport * as Option from \"effect/Option\";\nimport { useLayoutEffect, useRef, useState, type RefObject } from \"react\";\nimport {\n  requestedPlacement,\n  resolvePlacement,\n  type Placement,\n} from \"./placement.js\";\n\nexport interface UsePlacementInput {\n  readonly open: boolean;\n  readonly side: FloatingSide;\n  readonly align: FloatingAlign;\n  readonly margin: number;\n  readonly trigger: RefObject<PublicInstance | null>;\n  readonly surface: RefObject<PublicInstance | null>;\n}\n\nconst boundsOf = (\n  renderer: ReturnType<typeof useGpuix>[\"renderer\"],\n  instance: PublicInstance | null,\n): Option.Option<{ x: number; y: number; width: number; height: number }> =>\n  Option.flatMap(Option.fromNullishOr(instance), (element) =>\n    Option.fromNullishOr(renderer?.getElementBounds?.(element.id)),\n  );\n\nexport const usePlacement = ({\n  open,\n  side,\n  align,\n  margin,\n  trigger,\n  surface,\n}: UsePlacementInput): Placement => {\n  const renderer = useGpuix().renderer;\n  const [placement, setPlacement] = useState<Option.Option<Placement>>(\n    Option.none(),\n  );\n  const measuredFor = useRef<string | null>(null);\n  const key = `${side}:${align}:${margin}`;\n\n  useLayoutEffect(() => {\n    if (!open) {\n      measuredFor.current = null;\n      setPlacement(Option.none());\n      return;\n    }\n    if (measuredFor.current === key) return;\n\n    const measured = Option.all({\n      trigger: boundsOf(renderer, trigger.current),\n      surface: boundsOf(renderer, surface.current),\n      window: Option.fromNullishOr(renderer?.getWindowSize?.()),\n    });\n    if (Option.isNone(measured)) return;\n\n    measuredFor.current = key;\n    setPlacement(\n      Option.some(resolvePlacement({ ...measured.value, side, align, margin })),\n    );\n  }, [align, key, margin, open, renderer, side, surface, trigger]);\n\n  return Option.getOrElse(placement, () => requestedPlacement(side, align));\n};\n"
    },
    {
      "path": "packages/components/src/menu/hover.ts",
      "target": "@ui/gpuixui/components/menu/hover.ts",
      "type": "registry:lib",
      "content": "import { createContext } from \"react\";\nimport type { HoverControls } from \"../internal/hover.js\";\n\nexport const submenuOpenDelayMs = 100;\n\nexport const menuCloseGraceMs = 80;\n\nexport const MenuPointer = createContext<HoverControls | undefined>(undefined);\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/menu/sub.tsx",
      "target": "@ui/gpuixui/components/menu/sub.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { EventPayload, Props, PublicInstance } from \"@gpuix/react\";\nimport { useGpuix } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { Icon } from \"../../icons/index.js\";\nimport { Match } from \"effect\";\nimport * as Option from \"effect/Option\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  type RefObject,\n} from \"react\";\nimport { pushDismissLayer } from \"../internal/dismiss-layer.js\";\nimport { useDismissDepth } from \"../internal/dismiss-scope.js\";\nimport { claimEvent } from \"../internal/event-claim.js\";\nimport { useHoverDelays } from \"../internal/hover.js\";\nimport { isDown, isLeft, isRight, isUp } from \"../internal/key.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"../internal/parts.js\";\nimport {\n  fadeFromAnchor,\n  presenceMotion,\n  presenceStyle,\n  useSurfacePresence,\n  useSurfaceTiming,\n} from \"../internal/presence.js\";\nimport { useMergedRef } from \"../internal/ref.js\";\nimport { useTheme } from \"../internal/theme.js\";\nimport {\n  MenuCheckboxItem,\n  MenuGroup,\n  MenuItem,\n  MenuLabel,\n  MenuSeparator,\n  type MenuItemProps,\n} from \"./item.js\";\nimport { MenuPointer, menuCloseGraceMs, submenuOpenDelayMs } from \"./hover.js\";\nimport {\n  MenuContext,\n  useMenuContext,\n  useMenuState,\n  type MenuCloseReason,\n} from \"./state.js\";\nimport { MenuSurface } from \"./surface.js\";\n\ninterface SubContextValue {\n  readonly open: boolean;\n\n  readonly openNow: () => void;\n\n  readonly close: (reason: MenuCloseReason) => void;\n\n  readonly hoverTrigger: (present: boolean) => void;\n\n  readonly hoverContent: (present: boolean) => void;\n\n  readonly trigger: RefObject<PublicInstance | null>;\n\n  readonly focusEdge: RefObject<((edge: \"first\" | \"last\") => void) | null>;\n}\n\nconst SubContext = createContext<SubContextValue | undefined>(undefined);\n\nconst useSubContext = (): SubContextValue => {\n  const value = useContext(SubContext);\n  if (value === undefined) {\n    throw new Error(\"Submenu parts must be used inside a Submenu\");\n  }\n  return value;\n};\n\nexport interface MenuSubProps {\n  readonly children?: PartChildren;\n}\n\nexport const MenuSub = ({ children }: MenuSubProps) => {\n  const renderer = useGpuix().renderer;\n  const parent = useMenuContext();\n  const [open, setOpen] = useState(false);\n  const trigger = useRef<PublicInstance | null>(null);\n  const focusEdge = useRef<((edge: \"first\" | \"last\") => void) | null>(null);\n  const restoreFocus = useRef(false);\n\n  const focusTrigger = useCallback(() => {\n    Option.match(Option.fromNullishOr(trigger.current), {\n      onNone: () => undefined,\n      onSome: (instance) => renderer?.focusElement?.(instance.id),\n    });\n  }, [renderer, trigger]);\n\n  const hover = useHoverDelays({\n    open,\n    setOpen: (next) => {\n      if (!next) restoreFocus.current = true;\n      setOpen(next);\n    },\n    openDelay: submenuOpenDelayMs,\n    closeDelay: menuCloseGraceMs,\n  });\n\n  const openNow = useCallback(() => {\n    hover.cancel();\n    setOpen(true);\n  }, [hover]);\n\n  const close = useCallback(\n    (reason: MenuCloseReason) => {\n      restoreFocus.current = reason === \"dismiss\" || reason === \"pointer\";\n      hover.cancel();\n      setOpen(false);\n\n      if (reason === \"pointer\") return;\n      Match.value(reason).pipe(\n        Match.when(\"activate\", () => parent.close(\"activate\")),\n        Match.when(\"leave\", () => parent.close(\"leave\")),\n        Match.when(\"outside\", () => parent.close(\"outside\")),\n        Match.when(\"dismiss\", () => undefined),\n        Match.exhaustive,\n      );\n    },\n    [hover, parent],\n  );\n\n  useLayoutEffect(() => {\n    if (open || !restoreFocus.current) return;\n    restoreFocus.current = false;\n    focusTrigger();\n  }, [focusTrigger, open]);\n\n  const value = useMemo<SubContextValue>(\n    () => ({\n      open,\n      openNow,\n      close,\n      hoverTrigger: (present) => hover.pointer(\"trigger\", present),\n      hoverContent: (present) => hover.pointer(\"content\", present),\n      trigger,\n      focusEdge,\n    }),\n    [close, hover, open, openNow],\n  );\n  assertParts(menuSub, children);\n\n  return <SubContext.Provider value={value}>{children}</SubContext.Provider>;\n};\n\nexport interface MenuSubTriggerProps extends Omit<\n  MenuItemProps,\n  \"onSelect\" | \"closeOnSelect\" | \"tone\"\n> {}\n\nexport const MenuSubTrigger = ({\n  children,\n  trailing,\n  textValue,\n  disabled = false,\n  onKeyDown,\n  onFocus,\n  onMouseEnter,\n  onMouseLeave,\n  ref,\n  ...rest\n}: MenuSubTriggerProps) => {\n  const theme = useTheme();\n  const { open, openNow, close, hoverTrigger, trigger, focusEdge } =\n    useSubContext();\n  const setTrigger = useMergedRef<PublicInstance | null>(trigger, ref);\n\n  return (\n    <MenuItem\n      {...rest}\n      ref={setTrigger}\n      disabled={disabled}\n      closeOnSelect={false}\n      aria-expanded={open}\n      textValue={textValue}\n      onSelect={() => (open ? close(\"dismiss\") : openNow())}\n      onFocus={(event) => {\n        if (open) focusEdge.current?.(\"first\");\n        onFocus?.(event);\n      }}\n      onMouseEnter={(event) => {\n        if (!disabled) hoverTrigger(true);\n        onMouseEnter?.(event);\n      }}\n      onMouseLeave={(event) => {\n        if (!disabled) hoverTrigger(false);\n        onMouseLeave?.(event);\n      }}\n      onKeyDown={(event) => {\n        if (!open) {\n          if (isRight(event) || isDown(event) || isUp(event)) openNow();\n        } else if (isDown(event) || isUp(event)) {\n          claimEvent(event);\n          focusEdge.current?.(isDown(event) ? \"first\" : \"last\");\n        } else if (isRight(event)) {\n          claimEvent(event);\n          focusEdge.current?.(\"first\");\n        } else if (isLeft(event)) {\n          close(\"dismiss\");\n        }\n        onKeyDown?.(event);\n      }}\n      trailing={\n        trailing ?? (\n          <Icon\n            name=\"chevron-right\"\n            size=\"xs\"\n            color={theme.colors.mutedForeground}\n          />\n        )\n      }\n    >\n      {children}\n    </MenuItem>\n  );\n};\n\nconst SUBMENU_GAP = 4;\n\nconst SUBMENU_MAX_HEIGHT = 320;\n\nexport interface MenuSubContentProps extends Omit<Props, \"children\"> {\n  readonly label?: string;\n  readonly children?: PartChildren;\n}\n\nexport const MenuSubContent = ({\n  label,\n  children,\n  onKeyDown,\n  style,\n  testId,\n  ...rest\n}: MenuSubContentProps) => {\n  const renderer = useGpuix().renderer;\n  const { open, close, hoverContent, trigger, focusEdge } = useSubContext();\n  const parent = useMenuContext();\n  const pointer = useContext(MenuPointer);\n  assertParts(menuSubContent, children);\n  const parentClaim = parent.claimSubmenu;\n  const surface = useRef<PublicInstance | null>(null);\n\n  const menu = useMenuState(close, (present) => {\n    hoverContent(present);\n    pointer?.pointer(\"trigger\", present);\n  });\n\n  const depth = useDismissDepth();\n\n  useLayoutEffect(() => {\n    focusEdge.current = (edge) =>\n      menu.step({ _tag: edge === \"first\" ? \"First\" : \"Last\" });\n  }, [focusEdge, menu]);\n\n  useLayoutEffect(\n    () => () => {\n      focusEdge.current = null;\n    },\n    [focusEdge],\n  );\n\n  const closeRef = useRef(close);\n  useLayoutEffect(() => {\n    closeRef.current = close;\n  });\n\n  const insideSurface = useCallback(\n    (event: EventPayload): boolean => {\n      const instance = surface.current;\n      if (instance === null) return false;\n      const bounds = renderer?.getElementBounds?.(instance.id);\n      if (bounds === null || bounds === undefined) return false;\n      const { x, y } = event;\n      return (\n        x !== undefined &&\n        y !== undefined &&\n        x >= bounds.x &&\n        x <= bounds.x + bounds.width &&\n        y >= bounds.y &&\n        y <= bounds.y + bounds.height\n      );\n    },\n    [renderer],\n  );\n\n  const setSurface = useCallback((next: PublicInstance | null) => {\n    surface.current = next;\n  }, []);\n\n  useLayoutEffect(() => {\n    if (!open) return;\n    const release = parentClaim(\n      () => closeRef.current(\"pointer\"),\n      trigger.current?.id,\n    );\n    const pop = pushDismissLayer((event) => {\n      if (event.eventType === \"mouseDownOutside\" && insideSurface(event))\n        return;\n      closeRef.current(event.eventType === \"keyDown\" ? \"dismiss\" : \"outside\");\n    }, depth);\n    return () => {\n      pop();\n      release();\n    };\n  }, [depth, insideSurface, open, parentClaim, trigger]);\n\n  const [point, setPoint] = useState<{ x: number; y: number } | undefined>();\n\n  const timing = useSurfaceTiming(\"normal\");\n  const presence = useSurfacePresence(open, timing);\n\n  useLayoutEffect(() => {\n    if (!open) return;\n    const instance = trigger.current;\n    if (instance === null) return;\n    Option.match(\n      Option.fromNullishOr(renderer?.getElementBounds?.(instance.id)),\n      {\n        onNone: () => undefined,\n        onSome: (bounds) =>\n          setPoint({\n            x: bounds.x + bounds.width + SUBMENU_GAP,\n            y: bounds.y,\n          }),\n      },\n    );\n  }, [open, renderer, trigger]);\n\n  const pendingFocus = useRef(true);\n  useLayoutEffect(() => {\n    if (!open) {\n      pendingFocus.current = true;\n      return;\n    }\n    if (!pendingFocus.current || menu.rows.length === 0) return;\n    pendingFocus.current = false;\n    menu.step({ _tag: \"First\" });\n  }, [menu, open]);\n\n  if (!presence.mounted || point === undefined) return null;\n\n  return (\n    <MenuContext.Provider value={menu}>\n      <anchored\n        position={point}\n        fit=\"snap\"\n        snapMargin={8}\n        deferred\n        priority={1}\n        occlude={presence.present}\n      >\n        <MenuSurface\n          ref={setSurface}\n          testId={testId}\n          label={label}\n          leaving={!presence.present}\n          motion={presenceMotion(fadeFromAnchor(\"right\", 4), presence)}\n          {...rest}\n          onKeyDown={(event) => {\n            claimEvent(event);\n            if (isLeft(event)) close(\"dismiss\");\n            onKeyDown?.(event);\n          }}\n          style={mergeStyles(\n            {\n              position: \"relative\",\n              ...presenceStyle(presence.present),\n            },\n            style,\n          )}\n        >\n          {}\n          <div\n            style={{\n              display: \"flex\",\n              flexDirection: \"column\",\n              maxHeight: SUBMENU_MAX_HEIGHT,\n              overflowY: \"scroll\",\n            }}\n          >\n            {children}\n          </div>\n        </MenuSurface>\n      </anchored>\n    </MenuContext.Provider>\n  );\n};\n\nconst subParts: ReadonlyArray<PartComponent> = [\n  MenuSub,\n  MenuSubTrigger,\n  MenuSubContent,\n  MenuItem,\n  MenuCheckboxItem,\n  MenuGroup,\n  MenuLabel,\n  MenuSeparator,\n];\n\nconst menuSub: PartsContract = {\n  parent: \"MenuSub\",\n  family: subParts,\n  accepts: [MenuSubTrigger, MenuSubContent],\n  hint: \"Put the submenu trigger and its content directly inside MenuSub.\",\n};\n\nconst menuSubContent: PartsContract = {\n  parent: \"MenuSubContent\",\n  family: subParts,\n  accepts: [\n    MenuItem,\n    MenuCheckboxItem,\n    MenuGroup,\n    MenuLabel,\n    MenuSeparator,\n    MenuSub,\n  ],\n  hint: \"Put rows, groups, labels, separators and nested submenus directly inside MenuSubContent.\",\n};\n"
    },
    {
      "path": "packages/components/src/menu/surface.tsx",
      "target": "@ui/gpuixui/components/menu/surface.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { KeyEvent, Props, StyleDesc } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { MENU_CONTENT } from \"../../tw/index.js\";\nimport { Match, pipe } from \"effect\";\nimport * as Option from \"effect/Option\";\nimport type { ReactNode } from \"react\";\nimport { isEventClaimed } from \"../internal/event-claim.js\";\nimport { dismissTopLayer } from \"../internal/dismiss-layer.js\";\nimport { DismissScope } from \"../internal/dismiss-scope.js\";\nimport {\n  isActivation,\n  isDismiss,\n  isDown,\n  isFirst,\n  isLast,\n  isTab,\n  isUp,\n  swallowsDefault,\n} from \"../internal/key.js\";\nimport { overlaySurfaceStyle } from \"../internal/overlay.js\";\nimport { PresenceShield } from \"../internal/presence.js\";\nimport { useTheme, type Theme } from \"../internal/theme.js\";\nimport { typedChar } from \"../typeahead.js\";\nimport { MenuLeaving, useMenuContext, type MenuContextValue } from \"./state.js\";\n\nexport const menuSurfaceStyle = (theme: Theme, className?: string): StyleDesc =>\n  overlaySurfaceStyle(MENU_CONTENT, className, theme, \"lg\");\n\nexport const menuKeyHandlers = (\n  menu: MenuContextValue,\n): {\n  readonly onKeyDown: (event: KeyEvent) => void;\n  readonly onKeyUp: (event: KeyEvent) => void;\n} => ({\n  onKeyDown: (event: KeyEvent): void => {\n    if (isEventClaimed(event)) return;\n    if (swallowsDefault(event, \"down\")) event.preventDefault();\n    pipe(\n      typedChar(event),\n      Option.match({\n        onSome: menu.typeahead,\n        onNone: () =>\n          Match.value(event).pipe(\n            Match.when(isDown, () => menu.step({ _tag: \"Move\", delta: 1 })),\n            Match.when(isUp, () => menu.step({ _tag: \"Move\", delta: -1 })),\n            Match.when(isFirst, () => menu.step({ _tag: \"First\" })),\n            Match.when(isLast, () => menu.step({ _tag: \"Last\" })),\n            Match.when(\n              (press) => isActivation(press, \"down\"),\n              menu.activateActive,\n            ),\n            Match.when(isDismiss, (value) => {\n              dismissTopLayer(value);\n            }),\n\n            Match.when(isTab, () => menu.close(\"leave\")),\n            Match.orElse(() => undefined),\n          ),\n      }),\n    );\n  },\n  onKeyUp: (event: KeyEvent): void => {\n    if (isEventClaimed(event)) return;\n    if (isActivation(event, \"up\")) menu.activateActive();\n  },\n});\n\nexport interface MenuSurfaceProps extends Props {\n  readonly label?: string;\n\n  readonly className?: string;\n  readonly children?: ReactNode;\n\n  readonly leaving?: boolean;\n}\n\nexport const MenuSurface = ({\n  label,\n  children,\n  leaving = false,\n  onKeyDown,\n  onKeyUp,\n  onMouseEnter,\n  onMouseLeave,\n  onMouseMove,\n  className,\n  style,\n  ...rest\n}: MenuSurfaceProps) => {\n  const theme = useTheme();\n  const menu = useMenuContext();\n  const keys = menuKeyHandlers(menu);\n\n  return (\n    <div\n      role=\"menu\"\n      aria-label={label}\n      tabIndex={-1}\n      autoFocus\n      {...rest}\n      onMouseEnter={(event) => {\n        menu.notePointer(true);\n        onMouseEnter?.(event);\n      }}\n      onMouseMove={(event) => {\n        menu.notePointer(true);\n        onMouseMove?.(event);\n      }}\n      onMouseLeave={(event) => {\n        menu.notePointer(false);\n        onMouseLeave?.(event);\n      }}\n      onKeyDown={(event) => {\n        keys.onKeyDown(event);\n        onKeyDown?.(event);\n      }}\n      onKeyUp={(event) => {\n        keys.onKeyUp(event);\n        onKeyUp?.(event);\n      }}\n      style={mergeStyles(\n        mergeStyles(menuSurfaceStyle(theme, className), {\n          pointerEvents: \"auto\",\n\n          position: \"relative\",\n        }),\n        style,\n      )}\n    >\n      <MenuLeaving.Provider value={leaving}>\n        <DismissScope>{children}</DismissScope>\n      </MenuLeaving.Provider>\n      <PresenceShield present={!leaving} />\n    </div>\n  );\n};\n"
    },
    {
      "path": "packages/components/src/sidebar.tsx",
      "target": "@ui/gpuixui/components/sidebar.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type {\n  EventPayload,\n  KeyEvent,\n  Props,\n  PublicInstance,\n  StyleDesc,\n} from \"@gpuix/react\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n  useGpuix,\n  useWindowSize,\n} from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { Icon } from \"../icons/index.js\";\nimport { Button as Action } from \"../primitives/button.js\";\nimport {\n  cn,\n  SIDEBAR_CONTENT,\n  SIDEBAR_FLOATING,\n  SIDEBAR_FOOTER,\n  SIDEBAR_GROUP,\n  SIDEBAR_GROUP_ACTION,\n  SIDEBAR_GROUP_CONTENT,\n  SIDEBAR_GROUP_LABEL,\n  SIDEBAR_GROUP_LABEL_TEXT,\n  SIDEBAR_HEADER,\n  SIDEBAR_INNER,\n  SIDEBAR_INPUT,\n  SIDEBAR_INSET,\n  SIDEBAR_MENU,\n  SIDEBAR_MENU_ACTION,\n  SIDEBAR_MENU_BADGE,\n  SIDEBAR_MENU_BUTTON,\n  SIDEBAR_MENU_BUTTON_SIZES,\n  SIDEBAR_MENU_ITEM,\n  SIDEBAR_MENU_LABEL,\n  SIDEBAR_MENU_SKELETON,\n  SIDEBAR_MENU_SKELETON_ICON,\n  SIDEBAR_MENU_SKELETON_TEXT,\n  SIDEBAR_MENU_SUB,\n  SIDEBAR_MENU_SUB_BUTTON,\n  SIDEBAR_MENU_SUB_ITEM,\n  SIDEBAR_ROOT,\n  SIDEBAR_SCRIM,\n  SIDEBAR_SEPARATOR,\n  SIDEBAR_SHEET,\n  SIDEBAR_TOOLTIP,\n  SIDEBAR_TOOLTIP_TEXT,\n} from \"../tw/index.js\";\nimport * as Option from \"effect/Option\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type ReactNode,\n} from \"react\";\nimport { FieldInput } from \"./field.js\";\nimport { IconButton } 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 {\n  fadeFromSide,\n  fadeOnly,\n  presenceMotion,\n  presenceStyle,\n  PresenceShield,\n  useMoveTiming,\n  useSurfacePresence,\n  useSurfaceTiming,\n} from \"./internal/presence.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport {\n  controlHeight,\n  controlRing,\n  disabledFade,\n  elevationShadow,\n  pressWash,\n  textStyle,\n  useTheme,\n} from \"./internal/theme.js\";\nimport {\n  collapsibleOf,\n  isMobileWidth,\n  railPadding,\n  sidebarGap,\n  sidebarInsetBox,\n  sidebarOffset,\n  sidebarWidth,\n  sideOf,\n  skeletonWidth,\n  variantOf,\n  SIDEBAR_ICON_ROW,\n  SIDEBAR_KEYBOARD_SHORTCUT,\n  SIDEBAR_WIDTH,\n  SIDEBAR_WIDTH_ICON,\n  SIDEBAR_WIDTH_MOBILE,\n  type SidebarCollapsible,\n  type SidebarSide,\n  type SidebarVariant,\n} from \"./sidebar/logic.js\";\nexport type {\n  SidebarCollapsible,\n  SidebarSide,\n  SidebarState,\n  SidebarVariant,\n} from \"./sidebar/logic.js\";\nexport {\n  SIDEBAR_BREAKPOINT,\n  SIDEBAR_KEYBOARD_SHORTCUT,\n  SIDEBAR_WIDTH,\n  SIDEBAR_WIDTH_ICON,\n  SIDEBAR_WIDTH_MOBILE,\n} from \"./sidebar/logic.js\";\nimport type { SidebarPreference } from \"./sidebar/preference.js\";\nimport { send, useLocalState, useLocalStore } from \"./internal/local-state.js\";\nimport { makeSidebarStore, tooltipShown } from \"./sidebar/store.js\";\n\nexport interface SidebarContextValue {\n  readonly state: \"expanded\" | \"collapsed\";\n  readonly open: boolean;\n  readonly setOpen: (open: boolean) => void;\n  readonly openMobile: boolean;\n  readonly setOpenMobile: (open: boolean) => void;\n  readonly isMobile: boolean;\n  readonly toggleSidebar: () => void;\n  readonly side: SidebarSide;\n  readonly variant: SidebarVariant;\n  readonly collapsible: SidebarCollapsible;\n\n  readonly width: number;\n\n  readonly iconWidth: number;\n\n  readonly mobileWidth: number;\n\n  readonly tooltip: Option.Option<string>;\n  readonly showTooltip: (label: string) => void;\n  readonly hideTooltip: () => void;\n}\n\nconst SidebarContext = createContext<SidebarContextValue | undefined>(\n  undefined,\n);\n\nexport const useSidebar = (): SidebarContextValue => {\n  const value = useContext(SidebarContext);\n  if (value === undefined) {\n    throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n  }\n  return value;\n};\n\nexport const isSidebarShortcut = (event: KeyEvent): boolean =>\n  event.isHeld !== true &&\n  event.key?.toLowerCase() === SIDEBAR_KEYBOARD_SHORTCUT &&\n  (event.modifiers?.cmd === true || event.modifiers?.ctrl === true);\n\nconst positiveWidth = (value: number | undefined, fallback: number): number =>\n  value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;\n\nconst iconCollapsed = (context: SidebarContextValue): boolean =>\n  context.state === \"collapsed\" && context.collapsible === \"icon\";\n\nconst useRailPadding = () => {\n  const context = useSidebar();\n  if (!iconCollapsed(context)) return undefined;\n  const pad = railPadding(context.iconWidth);\n  return { paddingLeft: pad, paddingRight: pad };\n};\n\nconst layoutOf = (context: SidebarContextValue) => ({\n  open: context.open,\n  variant: context.variant,\n  collapsible: context.collapsible,\n  width: context.width,\n  iconWidth: context.iconWidth,\n});\n\nexport interface SidebarProviderProps extends Omit<Props, \"children\"> {\n  readonly defaultOpen?: boolean;\n\n  readonly open?: boolean;\n  readonly onOpenChange?: (open: boolean) => void;\n\n  readonly preference?: SidebarPreference;\n\n  readonly side?: SidebarSide;\n\n  readonly variant?: SidebarVariant;\n\n  readonly collapsible?: SidebarCollapsible;\n\n  readonly width?: number;\n\n  readonly iconWidth?: number;\n\n  readonly mobileWidth?: number;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarProvider = ({\n  defaultOpen,\n  open: openProp,\n  onOpenChange,\n  preference,\n  side: requestedSide = \"left\",\n  variant: requestedVariant = \"sidebar\",\n  collapsible: requestedCollapsible = \"offcanvas\",\n  width: requestedWidth,\n  iconWidth: requestedIconWidth,\n  mobileWidth: requestedMobileWidth,\n  onKeyDown,\n  style,\n  children,\n  ...rest\n}: SidebarProviderProps) => {\n  const theme = useTheme();\n  const window = useWindowSize();\n  const isMobile = isMobileWidth(window.width);\n  const side = sideOf(requestedSide);\n  const variant = variantOf(requestedVariant);\n  const collapsible = collapsibleOf(requestedCollapsible);\n  const width = positiveWidth(requestedWidth, SIDEBAR_WIDTH);\n  const iconWidth = positiveWidth(requestedIconWidth, SIDEBAR_WIDTH_ICON);\n  const mobileWidth = positiveWidth(requestedMobileWidth, SIDEBAR_WIDTH_MOBILE);\n\n  const store = useLocalStore(\n    makeSidebarStore({\n      initialOpen: openProp ?? defaultOpen,\n      preference,\n    }),\n  );\n  const snapshot = useLocalState(store.local);\n\n  useEffect(() => {\n    if (openProp === undefined || openProp === snapshot.open) return;\n    send(store.setOpen(openProp));\n  }, [openProp, snapshot.open, store]);\n\n  const open = snapshot.open;\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (openProp === undefined) send(store.setOpen(next));\n      else onOpenChange?.(next);\n    },\n    [onOpenChange, openProp, store],\n  );\n\n  const setOpenMobile = useCallback(\n    (next: boolean) => send(store.setMobileOpen(next)),\n    [store],\n  );\n\n  const toggleSidebar = useCallback(() => {\n    if (isMobile) send(store.toggleMobile);\n    else setOpen(!open);\n  }, [isMobile, open, setOpen, store]);\n\n  const showTooltip = useCallback(\n    (label: string) => send(store.showTooltip(label)),\n    [store],\n  );\n  const hideTooltip = useCallback(() => send(store.hideTooltip), [store]);\n\n  const handleKeyDown = (event: KeyEvent): void => {\n    onKeyDown?.(event);\n    if (!isSidebarShortcut(event)) return;\n    event.preventDefault();\n    toggleSidebar();\n  };\n\n  const value = useMemo<SidebarContextValue>(\n    () => ({\n      state: open ? \"expanded\" : \"collapsed\",\n      open,\n      setOpen,\n      openMobile: snapshot.mobileOpen,\n      setOpenMobile,\n      isMobile,\n      toggleSidebar,\n      side,\n      variant,\n      collapsible,\n      width,\n      iconWidth,\n      mobileWidth,\n      tooltip: snapshot.tooltip,\n      showTooltip,\n      hideTooltip,\n    }),\n    [\n      collapsible,\n      hideTooltip,\n      iconWidth,\n      isMobile,\n      mobileWidth,\n      open,\n      setOpen,\n      setOpenMobile,\n      showTooltip,\n      side,\n      snapshot.mobileOpen,\n      snapshot.tooltip,\n      toggleSidebar,\n      variant,\n      width,\n    ],\n  );\n\n  return (\n    <SidebarContext.Provider value={value}>\n      <div\n        {...rest}\n        testId={rest.testId ?? \"sidebar-provider\"}\n        onKeyDown={handleKeyDown}\n        style={mergeStyles(\n          mergeStyles(recipeStyle(SIDEBAR_ROOT, theme), {\n            backgroundColor:\n              variant === \"inset\" ? theme.colors.card : theme.colors.background,\n          }),\n          style,\n        )}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n};\n\nexport interface SidebarProps extends Omit<Props, \"children\"> {\n  readonly side?: SidebarSide;\n\n  readonly variant?: SidebarVariant;\n\n  readonly collapsible?: SidebarCollapsible;\n\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const Sidebar = ({\n  side: requestedSide,\n  variant: requestedVariant,\n  collapsible: requestedCollapsible,\n  className,\n  style,\n  children,\n  ...rest\n}: SidebarProps) => {\n  const context = useSidebar();\n  const theme = useTheme();\n  const timing = useMoveTiming(\"normal\");\n  const side =\n    requestedSide === undefined ? context.side : sideOf(requestedSide);\n  const variant =\n    requestedVariant === undefined\n      ? context.variant\n      : variantOf(requestedVariant);\n  const collapsible =\n    requestedCollapsible === undefined\n      ? context.collapsible\n      : collapsibleOf(requestedCollapsible);\n\n  if (context.isMobile) {\n    return (\n      <SidebarSheet side={side} className={className} style={style}>\n        {children}\n      </SidebarSheet>\n    );\n  }\n\n  const layout = layoutOf(context);\n  const inner = mergeStyles(\n    mergeStyles(recipeStyle(cn(SIDEBAR_INNER, className), theme), {\n      position: \"relative\",\n      ...(variant === \"sidebar\" ? {} : recipeStyle(SIDEBAR_FLOATING, theme)),\n    }),\n    style,\n  );\n\n  if (collapsible === \"none\") {\n    return (\n      <div\n        {...rest}\n        testId={rest.testId ?? \"sidebar\"}\n        style={mergeStyles(inner, {\n          width: sidebarWidth(layout),\n          flexShrink: 0,\n        })}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  const width = sidebarWidth(layout);\n  const gap = sidebarGap(layout);\n  const offset = sidebarOffset(layout);\n\n  return (\n    <>\n      <div\n        testId=\"sidebar-gap\"\n        motion={{ initial: false, animate: { width: gap }, transition: timing }}\n        style={{ flexShrink: 0, height: \"100%\" }}\n      />\n      <div\n        {...rest}\n        testId={rest.testId ?? \"sidebar\"}\n        motion={{\n          initial: false,\n          animate:\n            side === \"left\"\n              ? { width, left: offset }\n              : { width, right: offset },\n          transition: timing,\n        }}\n        style={{\n          position: \"absolute\",\n          top: 0,\n          bottom: 0,\n          width,\n          ...(side === \"left\" ? { left: offset } : { right: offset }),\n          padding: variant === \"sidebar\" ? 0 : 8,\n\n          ...(variant === \"sidebar\"\n            ? {\n                borderColor: theme.colors.border,\n                ...(side === \"left\"\n                  ? { borderRightWidth: 1 }\n                  : { borderLeftWidth: 1 }),\n              }\n            : {}),\n        }}\n      >\n        <div style={inner}>{children}</div>\n      </div>\n    </>\n  );\n};\n\nconst SidebarSheet = ({\n  side,\n  className,\n  style,\n  children,\n}: {\n  readonly side: SidebarSide;\n  readonly className?: string;\n  readonly style?: StyleDesc;\n  readonly children?: ReactNode;\n}) => {\n  const theme = useTheme();\n  const renderer = useGpuix().renderer ?? undefined;\n  const { openMobile, setOpenMobile, mobileWidth } = useSidebar();\n  const window = useWindowSize();\n  const modal = useModalState({\n    open: openMobile,\n    onOpenChange: setOpenMobile,\n  });\n  const timing = useSurfaceTiming(\"normal\");\n  const presence = useSurfacePresence(openMobile, timing);\n  const [content, setContent] = useState<PublicInstance | undefined>(undefined);\n  const contentRef = useCallback((next: PublicInstance | null) => {\n    setContent(next ?? undefined);\n  }, []);\n  const width = Math.min(mobileWidth, window.width);\n\n  useModalFocus(content?.id, undefined, openMobile, modal.restoreTarget);\n\n  if (!presence.mounted) return null;\n\n  return (\n    <anchored\n      position={{ x: 0, y: 0 }}\n      occlude\n      style={{ position: \"absolute\" }}\n    >\n      <div\n        testId=\"sidebar-scrim\"\n        motion={presenceMotion(fadeOnly, presence)}\n        style={mergeStyles(recipeStyle(SIDEBAR_SCRIM, theme), {\n          width: window.width,\n          height: window.height,\n          display: \"flex\",\n          flexDirection: \"row\",\n          alignItems: \"stretch\",\n          justifyContent: side === \"left\" ? \"flex-start\" : \"flex-end\",\n          ...presenceStyle(presence.present),\n        })}\n      >\n        <div\n          onMouseDownOutside={(event) => dismissTopLayer(event)}\n          onKeyDown={(event: KeyEvent) => {\n            handleModalKeyDown({\n              event,\n              contentId: content?.id,\n              renderer,\n            });\n          }}\n          style={{\n            display: \"flex\",\n            flexDirection: \"column\",\n            width,\n            height: window.height,\n          }}\n        >\n          <div\n            ref={contentRef}\n            role=\"dialog\"\n            aria-label=\"Sidebar\"\n            tabIndex={-1}\n            testId=\"sidebar-sheet\"\n            motion={presenceMotion(fadeFromSide(side, 12), presence)}\n            style={mergeStyles(\n              mergeStyles(recipeStyle(cn(SIDEBAR_SHEET, className), theme), {\n                width,\n                height: window.height,\n                borderRightWidth: side === \"left\" ? 1 : 0,\n                borderLeftWidth: side === \"right\" ? 1 : 0,\n                borderColor: theme.colors.border,\n              }),\n              mergeStyles(style, presenceStyle(presence.present)),\n            )}\n          >\n            <DismissScope>{children}</DismissScope>\n          </div>\n          <PresenceShield present={presence.present} />\n        </div>\n      </div>\n    </anchored>\n  );\n};\n\nexport interface SidebarTriggerProps extends Omit<Props, \"children\"> {\n  readonly size?: \"sm\" | \"md\" | \"lg\";\n}\n\nexport const SidebarTrigger = ({\n  size = \"sm\",\n  testId = \"sidebar-trigger\",\n  ...rest\n}: SidebarTriggerProps) => {\n  const theme = useTheme();\n  const { toggleSidebar } = useSidebar();\n  return (\n    <IconButton\n      {...rest}\n      label=\"Toggle Sidebar\"\n      size={size}\n      testId={testId}\n      onClick={toggleSidebar}\n    >\n      <Icon name=\"panel-left\" size=\"sm\" color={theme.colors.foreground} />\n    </IconButton>\n  );\n};\n\nexport interface SidebarRailProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n}\n\nexport const SidebarRail = ({\n  className,\n  style,\n  onClick,\n  onMouseDown,\n  onMouseMove,\n  ...rest\n}: SidebarRailProps) => {\n  const theme = useTheme();\n  const { toggleSidebar, side, isMobile, collapsible, state } = useSidebar();\n  const start = useRef<number | null>(null);\n  const dragged = useRef(false);\n\n  if (isMobile || collapsible === \"none\") return null;\n\n  const collapsed = state === \"collapsed\";\n  const cursor =\n    side === \"left\"\n      ? collapsed\n        ? \"e-resize\"\n        : \"w-resize\"\n      : collapsed\n        ? \"w-resize\"\n        : \"e-resize\";\n\n  return (\n    <div\n      {...rest}\n      role=\"button\"\n      aria-label=\"Toggle Sidebar\"\n      tabIndex={-1}\n      onClick={(event) => {\n        onClick?.(event);\n        if (dragged.current) {\n          dragged.current = false;\n          return;\n        }\n        toggleSidebar();\n      }}\n      onMouseDown={(event) => {\n        onMouseDown?.(event);\n        start.current = event.x ?? null;\n        dragged.current = false;\n      }}\n      onMouseMove={(event) => {\n        onMouseMove?.(event);\n        if (event.pressedButton !== 0 || start.current === null) return;\n        const travel = (event.x ?? start.current) - start.current;\n        if (Math.abs(travel) < 8 || dragged.current) return;\n        dragged.current = true;\n        toggleSidebar();\n      }}\n      testId={rest.testId ?? \"sidebar-rail\"}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(className), theme), {\n          position: \"absolute\",\n          top: 0,\n          bottom: 0,\n          width: 16,\n\n          ...(side === \"left\" ? { right: 0 } : { left: 0 }),\n          cursor,\n          hover: { backgroundColor: theme.colors.hover },\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarInsetProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarInset = ({\n  className,\n  style,\n  children,\n  ...rest\n}: SidebarInsetProps) => {\n  const theme = useTheme();\n  const context = useSidebar();\n  const box = context.isMobile\n    ? { margin: 0, marginLeft: 0, marginRight: 0, radius: 0 }\n    : sidebarInsetBox(layoutOf(context), context.side);\n\n  return (\n    <div\n      role=\"main\"\n      {...rest}\n      testId={rest.testId ?? \"sidebar-inset\"}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_INSET, className), theme), {\n          margin: box.margin,\n          marginLeft: box.marginLeft,\n          marginRight: box.marginRight,\n          borderRadius: box.radius,\n          ...(box.radius === 0\n            ? {}\n            : { boxShadow: elevationShadow(theme, \"sm\") }),\n        }),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport interface SidebarHeaderProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarHeader = ({\n  className,\n  style,\n  ...rest\n}: SidebarHeaderProps) => {\n  const theme = useTheme();\n  const rail = useRailPadding();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_HEADER, className), theme), {\n          flexShrink: 0,\n          ...rail,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarFooterProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarFooter = ({\n  className,\n  style,\n  ...rest\n}: SidebarFooterProps) => {\n  const theme = useTheme();\n  const rail = useRailPadding();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_FOOTER, className), theme), {\n          flexShrink: 0,\n          ...rail,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarContentProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarContent = ({\n  className,\n  style,\n  ...rest\n}: SidebarContentProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_CONTENT, className), theme), {\n          overflowY: \"scroll\",\n          scrollbarColor: theme.colors.scrollbar,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarSeparatorProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n}\n\nexport const SidebarSeparator = ({\n  className,\n  style,\n  ...rest\n}: SidebarSeparatorProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      role=\"separator\"\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_SEPARATOR, className), theme), {\n          flexShrink: 0,\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarGroupProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarGroup = ({\n  className,\n  style,\n  ...rest\n}: SidebarGroupProps) => {\n  const theme = useTheme();\n  const rail = useRailPadding();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_GROUP, className), theme), rail),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarGroupLabelProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n\n  readonly children?: string;\n}\n\nexport const SidebarGroupLabel = ({\n  className,\n  children,\n  style,\n  ...rest\n}: SidebarGroupLabelProps) => {\n  const theme = useTheme();\n  const context = useSidebar();\n  const icon = iconCollapsed(context);\n  const timing = useMoveTiming(\"normal\");\n\n  return (\n    <div\n      {...rest}\n      motion={{\n        initial: false,\n        animate: { height: icon ? 0 : 32, opacity: icon ? 0 : 1 },\n        transition: timing,\n      }}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_GROUP_LABEL, className), theme), {\n          height: 32,\n          opacity: 1,\n          flexShrink: 0,\n        }),\n        style,\n      )}\n    >\n      <text\n        style={mergeStyles(\n          textStyle(theme, \"caption\"),\n          recipeStyle(SIDEBAR_GROUP_LABEL_TEXT, theme),\n        )}\n      >\n        {children}\n      </text>\n    </div>\n  );\n};\n\nexport interface SidebarGroupActionProps extends Omit<Props, \"children\"> {\n  readonly label: string;\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarGroupAction = ({\n  label,\n  className,\n  style,\n  children,\n  ...rest\n}: SidebarGroupActionProps) => {\n  const theme = useTheme();\n  const context = useSidebar();\n\n  return (\n    <Action\n      {...rest}\n      variant=\"ghost\"\n      size=\"sm\"\n      label={label}\n      leading={children}\n      className={cn(SIDEBAR_GROUP_ACTION, className)}\n      style={mergeStyles(\n        {\n          position: \"absolute\",\n          top: 14,\n          right: 12,\n          width: 20,\n          height: 20,\n          padding: 0,\n          display: iconCollapsed(context) ? \"none\" : \"flex\",\n          ...controlRing(theme),\n        },\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarGroupContentProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarGroupContent = ({\n  className,\n  style,\n  ...rest\n}: SidebarGroupContentProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(SIDEBAR_GROUP_CONTENT, className), theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarMenuProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarMenu = ({\n  className,\n  style,\n  ...rest\n}: SidebarMenuProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      role=\"list\"\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(SIDEBAR_MENU, className), theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarMenuItemProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarMenuItem = ({\n  className,\n  style,\n  ...rest\n}: SidebarMenuItemProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      role=\"listitem\"\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(SIDEBAR_MENU_ITEM, className), theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport type SidebarMenuButtonSize = keyof typeof SIDEBAR_MENU_BUTTON_SIZES;\n\nexport interface SidebarMenuButtonProps extends Omit<Props, \"children\"> {\n  readonly isActive?: boolean;\n\n  readonly variant?: \"default\" | \"outline\";\n\n  readonly size?: SidebarMenuButtonSize;\n\n  readonly tooltip?: string;\n\n  readonly label?: string;\n\n  readonly children?: ReactNode;\n\n  readonly trailing?: ReactNode;\n  readonly disabled?: boolean;\n  readonly className?: string;\n\n  readonly onClick?: (event: EventPayload) => void;\n}\n\nexport const SidebarMenuButton = ({\n  isActive = false,\n  variant = \"default\",\n  size = \"default\",\n  tooltip,\n  label,\n  trailing,\n  disabled = false,\n  className,\n  style,\n  children,\n  onClick,\n  testId,\n  ...rest\n}: SidebarMenuButtonProps) => {\n  const theme = useTheme();\n  const context = useSidebar();\n  const icon = iconCollapsed(context);\n  const tip = tooltip?.trim();\n  const tipReady = tip !== undefined && tip !== \"\" && icon && !context.isMobile;\n  const shown = tipReady && tooltipShown(context.tooltip, tip);\n\n  const button = (\n    <Action\n      {...rest}\n      variant={variant === \"outline\" ? \"outline\" : \"ghost\"}\n      size={size === \"sm\" ? \"sm\" : \"md\"}\n      label={label}\n      disabled={disabled}\n      testId={testId}\n      aria-selected={isActive}\n      aria-description={tooltip}\n      onActivate={onClick}\n      leading={children}\n      trailing={trailing}\n      className={cn(\n        SIDEBAR_MENU_BUTTON,\n        SIDEBAR_MENU_BUTTON_SIZES[size],\n        variant === \"outline\" ? \"bg-background\" : \"\",\n        className,\n      )}\n      style={mergeStyles(\n        {\n          flexShrink: 0,\n          backgroundColor: isActive ? theme.colors.accent : undefined,\n          color: isActive\n            ? theme.colors.accentForeground\n            : theme.colors.cardForeground,\n          ...(icon\n            ? {\n                width: SIDEBAR_ICON_ROW,\n                height: SIDEBAR_ICON_ROW,\n                padding: 8,\n                justifyContent: \"center\",\n              }\n            : {}),\n          ...disabledFade(disabled),\n          hover: disabled\n            ? undefined\n            : {\n                backgroundColor: isActive\n                  ? theme.colors.selected\n                  : theme.colors.hover,\n              },\n          active: disabled\n            ? undefined\n            : {\n                backgroundColor: pressWash(\n                  theme,\n                  isActive ? \"accent\" : \"hover\",\n                ),\n              },\n        },\n        style,\n      )}\n    >\n      {}\n      {label === undefined || icon ? null : (\n        <text\n          style={mergeStyles(recipeStyle(SIDEBAR_MENU_LABEL, theme), {\n            color: isActive\n              ? theme.colors.accentForeground\n              : theme.colors.cardForeground,\n          })}\n        >\n          {label}\n        </text>\n      )}\n    </Action>\n  );\n\n  return (\n    <Tooltip\n      open={shown}\n      onOpenChange={(next) => {\n        if (!tipReady) return;\n        if (next) context.showTooltip(tip);\n        else context.hideTooltip();\n      }}\n    >\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\n      {shown ? (\n        <TooltipContent\n          side=\"right\"\n          align=\"start\"\n          sideOffset={6 + (context.iconWidth - 32) / 2}\n          testId={testId === undefined ? undefined : `${testId}-tip`}\n          style={recipeStyle(SIDEBAR_TOOLTIP, theme)}\n        >\n          <text\n            style={mergeStyles(\n              textStyle(theme, \"caption\"),\n              recipeStyle(SIDEBAR_TOOLTIP_TEXT, theme),\n            )}\n          >\n            {tip}\n          </text>\n        </TooltipContent>\n      ) : null}\n    </Tooltip>\n  );\n};\n\nexport interface SidebarMenuActionProps extends Omit<Props, \"children\"> {\n  readonly label: string;\n\n  readonly showOnHover?: boolean;\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarMenuAction = ({\n  label,\n  showOnHover = false,\n  className,\n  style,\n  children,\n  onMouseEnter,\n  onMouseLeave,\n  onFocus,\n  onBlur,\n  ...rest\n}: SidebarMenuActionProps) => {\n  const theme = useTheme();\n  const context = useSidebar();\n  const [hovered, setHovered] = useState(false);\n  const [focused, setFocused] = useState(false);\n\n  return (\n    <Action\n      {...rest}\n      variant=\"ghost\"\n      size=\"sm\"\n      label={label}\n      leading={children}\n      className={cn(SIDEBAR_MENU_ACTION, className)}\n      onMouseEnter={(event) => {\n        setHovered(true);\n        onMouseEnter?.(event);\n      }}\n      onMouseLeave={(event) => {\n        setHovered(false);\n        onMouseLeave?.(event);\n      }}\n      onFocus={(event) => {\n        setFocused(true);\n        onFocus?.(event);\n      }}\n      onBlur={(event) => {\n        setFocused(false);\n        onBlur?.(event);\n      }}\n      style={mergeStyles(\n        {\n          position: \"absolute\",\n          right: 4,\n          top: 6,\n          width: 20,\n          height: 20,\n          padding: 0,\n          display: iconCollapsed(context) ? \"none\" : \"flex\",\n          opacity: showOnHover && !hovered && !focused ? 0 : 1,\n          ...controlRing(theme),\n        },\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarMenuBadgeProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: string;\n}\n\nexport const SidebarMenuBadge = ({\n  className,\n  style,\n  children,\n  ...rest\n}: SidebarMenuBadgeProps) => {\n  const theme = useTheme();\n  const context = useSidebar();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_MENU_BADGE, className), theme), {\n          position: \"absolute\",\n          right: 4,\n          top: 6,\n          display: iconCollapsed(context) ? \"none\" : \"flex\",\n          pointerEvents: \"none\",\n        }),\n        style,\n      )}\n    >\n      <text\n        style={mergeStyles(textStyle(theme, \"caption\"), {\n          color: theme.colors.mutedForeground,\n        })}\n      >\n        {children}\n      </text>\n    </div>\n  );\n};\n\nexport interface SidebarMenuSkeletonProps extends Omit<Props, \"children\"> {\n  readonly showIcon?: boolean;\n  readonly className?: string;\n}\n\nexport const SidebarMenuSkeleton = ({\n  showIcon = false,\n  className,\n  style,\n  ...rest\n}: SidebarMenuSkeletonProps) => {\n  const theme = useTheme();\n  const id = useId();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(SIDEBAR_MENU_SKELETON, className), theme),\n        style,\n      )}\n    >\n      {showIcon ? (\n        <div style={recipeStyle(SIDEBAR_MENU_SKELETON_ICON, theme)} />\n      ) : null}\n      <div\n        style={mergeStyles(recipeStyle(SIDEBAR_MENU_SKELETON_TEXT, theme), {\n          maxWidth: skeletonWidth(id),\n        })}\n      />\n    </div>\n  );\n};\n\nexport interface SidebarMenuSubProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarMenuSub = ({\n  className,\n  style,\n  ...rest\n}: SidebarMenuSubProps) => {\n  const theme = useTheme();\n  const context = useSidebar();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(recipeStyle(cn(SIDEBAR_MENU_SUB, className), theme), {\n          marginLeft: 14,\n          paddingLeft: 10,\n          borderLeftWidth: 1,\n          borderColor: theme.colors.border,\n          display: iconCollapsed(context) ? \"none\" : \"flex\",\n        }),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarMenuSubItemProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: ReactNode;\n}\n\nexport const SidebarMenuSubItem = ({\n  className,\n  style,\n  ...rest\n}: SidebarMenuSubItemProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        recipeStyle(cn(SIDEBAR_MENU_SUB_ITEM, className), theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface SidebarMenuSubButtonProps extends Omit<Props, \"children\"> {\n  readonly isActive?: boolean;\n\n  readonly size?: \"sm\" | \"md\";\n\n  readonly label?: string;\n  readonly className?: string;\n  readonly children?: ReactNode;\n  readonly disabled?: boolean;\n\n  readonly onClick?: (event: EventPayload) => void;\n}\n\nexport const SidebarMenuSubButton = ({\n  isActive = false,\n  size = \"md\",\n  label,\n  className,\n  style,\n  children,\n  disabled = false,\n  onClick,\n  ...rest\n}: SidebarMenuSubButtonProps) => {\n  const theme = useTheme();\n  const context = useSidebar();\n\n  return (\n    <Action\n      {...rest}\n      variant=\"ghost\"\n      size=\"sm\"\n      label={label}\n      disabled={disabled}\n      aria-selected={isActive}\n      onActivate={onClick}\n      leading={children}\n      className={cn(\n        SIDEBAR_MENU_SUB_BUTTON,\n        size === \"sm\" ? \"h-8 text-xs\" : \"h-9 text-sm\",\n        className,\n      )}\n      style={mergeStyles(\n        {\n          backgroundColor: isActive ? theme.colors.accent : undefined,\n          color: isActive\n            ? theme.colors.accentForeground\n            : theme.colors.mutedForeground,\n          display: iconCollapsed(context) ? \"none\" : \"flex\",\n          ...disabledFade(disabled),\n          hover: disabled ? undefined : { backgroundColor: theme.colors.hover },\n          active: disabled\n            ? undefined\n            : { backgroundColor: pressWash(theme, \"hover\") },\n        },\n        style,\n      )}\n    >\n      {label === undefined ? null : (\n        <text\n          style={mergeStyles(recipeStyle(SIDEBAR_MENU_LABEL, theme), {\n            color: isActive\n              ? theme.colors.accentForeground\n              : theme.colors.mutedForeground,\n          })}\n        >\n          {label}\n        </text>\n      )}\n    </Action>\n  );\n};\n\nexport interface SidebarInputProps {\n  readonly value?: string;\n  readonly placeholder?: string;\n\n  readonly readOnly?: boolean;\n\n  readonly disabled?: boolean;\n\n  readonly className?: string;\n\n  readonly style?: StyleDesc;\n\n  readonly testId?: string;\n\n  readonly onChange?: (event: EventPayload) => void;\n}\n\nexport const SidebarInput = ({\n  className,\n  style,\n  ...rest\n}: SidebarInputProps) => {\n  const theme = useTheme();\n  return (\n    <FieldInput\n      {...rest}\n      className={cn(SIDEBAR_INPUT, className)}\n      style={mergeStyles(\n        {\n          height: controlHeight(theme, \"sm\"),\n          backgroundColor: theme.colors.background,\n        },\n        style,\n      )}\n    />\n  );\n};\n\nexport const sidebarMeta = {\n  name: \"Sidebar\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"no transform in 0.11.0: the offcanvas panel animates left/right and width, and the mobile sheet fades and travels 12px from its edge rather than sliding its full width\",\n    \"the ⌘/Ctrl+B shortcut runs on the provider's wrapper: 0.11.0 accepts a window-level key handler only at render(), so a key typed while focus is outside the provider subtree is not seen\",\n    \"no cookie jar: the default preference is an in-process sidebar_state jar with shadcn's seven-day lifetime; pass `preference` for durability across launches\",\n    \"the provider owns side/variant/collapsible because SidebarInset is a sibling of Sidebar and the renderer has no parent selector; Sidebar accepts the same props as overrides\",\n    \"icon-mode labels are clipped rather than truncated by a CSS descendant selector; the menu button owns its label text\",\n    \"the rail toggles on a press and on an 8px drag inside the panel's own edge; there is no resize gesture because the runtime has no pointer capture, and an offcanvas panel parked outside the window is reopened by the trigger or ⌘/Ctrl+B because a strip outside the panel would paint under the inset\",\n    \"a rail strip outside the panel would be covered by the inset: paint order is tree order and 0.11.0 has no zIndex\",\n  ],\n  docs: \"docs/sidebar.mdx\",\n  example: \"examples/sidebar.example.tsx\",\n  testFile: \"src/behaviour/sidebar.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/sidebar/logic.ts",
      "target": "@ui/gpuixui/components/sidebar/logic.ts",
      "type": "registry:lib",
      "content": "import * as Schema from \"effect/Schema\";\n\nexport type SidebarSide = \"left\" | \"right\";\n\nexport type SidebarVariant = \"sidebar\" | \"floating\" | \"inset\";\n\nexport type SidebarCollapsible = \"offcanvas\" | \"icon\" | \"none\";\n\nexport type SidebarState = \"expanded\" | \"collapsed\";\n\nexport const SIDEBAR_WIDTH = 256;\n\nexport const SIDEBAR_WIDTH_ICON = 48;\n\nexport const SIDEBAR_ICON_ROW = 32;\n\nexport const railPadding = (iconWidth: number): number =>\n  Math.max(0, (iconWidth - SIDEBAR_ICON_ROW) / 2);\n\nexport const SIDEBAR_WIDTH_MOBILE = 288;\n\nexport const SIDEBAR_GUTTER = 8;\n\nexport const SIDEBAR_BREAKPOINT = 768;\n\nexport const SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\n\nconst Sides = Schema.Literals([\"left\", \"right\"]);\nconst Variants = Schema.Literals([\"sidebar\", \"floating\", \"inset\"]);\nconst Collapsibles = Schema.Literals([\"offcanvas\", \"icon\", \"none\"]);\n\nexport const sideOf = (side: unknown): SidebarSide =>\n  Schema.is(Sides)(side) ? side : \"left\";\n\nexport const variantOf = (variant: unknown): SidebarVariant =>\n  Schema.is(Variants)(variant) ? variant : \"sidebar\";\n\nexport const collapsibleOf = (collapsible: unknown): SidebarCollapsible =>\n  Schema.is(Collapsibles)(collapsible) ? collapsible : \"offcanvas\";\n\nexport const isMobileWidth = (width: number): boolean =>\n  Number.isFinite(width) && width < SIDEBAR_BREAKPOINT;\n\nexport interface SidebarLayout {\n  readonly open: boolean;\n  readonly variant: SidebarVariant;\n  readonly collapsible: SidebarCollapsible;\n\n  readonly width?: number;\n\n  readonly iconWidth?: number;\n}\n\nexport const isIconCollapsed = ({\n  open,\n  collapsible,\n}: SidebarLayout): boolean => !open && collapsible === \"icon\";\n\nexport const sidebarWidth = (layout: SidebarLayout): number => {\n  const { open, variant, collapsible } = layout;\n  const full = layout.width ?? SIDEBAR_WIDTH;\n  const icon = layout.iconWidth ?? SIDEBAR_WIDTH_ICON;\n  if (collapsible === \"none\" || open) return full;\n  if (collapsible === \"offcanvas\") return full;\n  return variant === \"sidebar\" ? icon : icon + SIDEBAR_GUTTER * 2;\n};\n\nexport const sidebarGap = (layout: SidebarLayout): number => {\n  const full = layout.width ?? SIDEBAR_WIDTH;\n  const icon = layout.iconWidth ?? SIDEBAR_WIDTH_ICON;\n  if (layout.collapsible === \"none\" || layout.open) return full;\n  if (layout.collapsible === \"offcanvas\") return 0;\n  return layout.variant === \"sidebar\" ? icon : icon + SIDEBAR_GUTTER * 2;\n};\n\nexport const sidebarOffset = (layout: SidebarLayout): number =>\n  !layout.open && layout.collapsible === \"offcanvas\"\n    ? -sidebarWidth(layout)\n    : 0;\n\nexport interface SidebarInsetBox {\n  readonly margin: number;\n  readonly marginLeft: number;\n  readonly marginRight: number;\n  readonly radius: number;\n}\n\nexport const sidebarInsetBox = (\n  layout: SidebarLayout,\n  side: SidebarSide,\n): SidebarInsetBox =>\n  layout.variant !== \"inset\"\n    ? { margin: 0, marginLeft: 0, marginRight: 0, radius: 0 }\n    : {\n        margin: SIDEBAR_GUTTER,\n        marginLeft:\n          side === \"left\" ? (layout.open ? 0 : SIDEBAR_GUTTER) : SIDEBAR_GUTTER,\n        marginRight:\n          side === \"right\"\n            ? layout.open\n              ? 0\n              : SIDEBAR_GUTTER\n            : SIDEBAR_GUTTER,\n        radius: 12,\n      };\n\nexport const skeletonWidth = (seed: string): string => {\n  let hash = 0;\n  for (const character of seed) {\n    hash = (hash * 31 + character.charCodeAt(0)) % 41;\n  }\n  return `${50 + hash}%`;\n};\n"
    },
    {
      "path": "packages/components/src/sidebar/preference.ts",
      "target": "@ui/gpuixui/components/sidebar/preference.ts",
      "type": "registry:lib",
      "content": "import { Effect } from \"effect\";\nimport * as Clock from \"effect/Clock\";\nimport * as Duration from \"effect/Duration\";\nimport * as Option from \"effect/Option\";\n\nexport const SIDEBAR_STATE_KEY = \"sidebar_state\";\n\nexport const SIDEBAR_STATE_MAX_AGE: Duration.Duration = Duration.days(7);\n\nexport interface SidebarPreference {\n  readonly read: Effect.Effect<Option.Option<boolean>>;\n\n  readonly write: (open: boolean) => Effect.Effect<void>;\n}\n\ninterface Entry {\n  readonly open: boolean;\n  readonly expiresAt: number;\n}\n\nconst jar = new Map<string, Entry>();\n\nexport const sidebarStateJar = (): SidebarPreference => ({\n  read: Effect.gen(function* () {\n    const now = yield* Clock.currentTimeMillis;\n    const entry = jar.get(SIDEBAR_STATE_KEY);\n    if (entry === undefined) return Option.none<boolean>();\n    if (entry.expiresAt <= now) {\n      yield* Effect.sync(() => jar.delete(SIDEBAR_STATE_KEY));\n      return Option.none<boolean>();\n    }\n    return Option.some(entry.open);\n  }),\n  write: (open) =>\n    Effect.gen(function* () {\n      const now = yield* Clock.currentTimeMillis;\n      const entry: Entry = {\n        open,\n        expiresAt: now + Duration.toMillis(SIDEBAR_STATE_MAX_AGE),\n      };\n      yield* Effect.sync(() => jar.set(SIDEBAR_STATE_KEY, entry));\n    }),\n});\n"
    },
    {
      "path": "packages/components/src/sidebar/store.ts",
      "target": "@ui/gpuixui/components/sidebar/store.ts",
      "type": "registry:lib",
      "content": "import { Effect, pipe } from \"effect\";\nimport * as Option from \"effect/Option\";\nimport { makeLocalState, type LocalState } from \"../internal/local-state.js\";\nimport { sidebarStateJar, type SidebarPreference } from \"./preference.js\";\n\nexport interface SidebarSnapshot {\n  readonly open: boolean;\n  readonly mobileOpen: boolean;\n\n  readonly tooltip: Option.Option<string>;\n}\n\nexport interface SidebarStoreService {\n  readonly local: LocalState<SidebarSnapshot>;\n\n  readonly setOpen: (open: boolean) => Effect.Effect<void>;\n\n  readonly toggle: Effect.Effect<void>;\n\n  readonly setMobileOpen: (open: boolean) => Effect.Effect<void>;\n\n  readonly toggleMobile: Effect.Effect<void>;\n\n  readonly showTooltip: (label: string) => Effect.Effect<void>;\n\n  readonly hideTooltip: Effect.Effect<void>;\n}\n\nexport interface SidebarStoreOptions {\n  readonly initialOpen?: boolean;\n\n  readonly preference?: SidebarPreference;\n}\n\nexport const makeSidebarStore = (\n  options?: SidebarStoreOptions,\n): Effect.Effect<SidebarStoreService> =>\n  Effect.gen(function* () {\n    const preference = options?.preference ?? sidebarStateJar();\n    const stored =\n      options?.initialOpen === undefined\n        ? yield* preference.read\n        : Option.none<boolean>();\n    const open = options?.initialOpen ?? Option.getOrElse(stored, () => true);\n\n    const local = yield* makeLocalState<SidebarSnapshot>({\n      open,\n      mobileOpen: false,\n      tooltip: Option.none(),\n    });\n\n    const setOpen = Effect.fn(\"SidebarStore.setOpen\")(function* (\n      next: boolean,\n    ) {\n      yield* local.write({ ...local.read(), open: next });\n      yield* preference.write(next);\n    });\n\n    const toggle = Effect.gen(function* () {\n      yield* setOpen(!local.read().open);\n    });\n\n    const setMobileOpen = Effect.fn(\"SidebarStore.setMobileOpen\")(function* (\n      next: boolean,\n    ) {\n      yield* local.write({ ...local.read(), mobileOpen: next });\n    });\n\n    const toggleMobile = Effect.gen(function* () {\n      yield* setMobileOpen(!local.read().mobileOpen);\n    });\n\n    const showTooltip = Effect.fn(\"SidebarStore.showTooltip\")(function* (\n      label: string,\n    ) {\n      const current = local.read();\n      if (Option.exists(current.tooltip, (shown) => shown === label)) return;\n      yield* local.write({ ...current, tooltip: Option.some(label) });\n    });\n\n    const hideTooltip = Effect.gen(function* () {\n      const current = local.read();\n      if (Option.isNone(current.tooltip)) return;\n      yield* local.write({ ...current, tooltip: Option.none() });\n    });\n\n    return {\n      local,\n      setOpen,\n      toggle,\n      setMobileOpen,\n      toggleMobile,\n      showTooltip,\n      hideTooltip,\n    };\n  });\n\nexport const tooltipShown = (\n  tooltip: Option.Option<string>,\n  label: string,\n): boolean =>\n  pipe(\n    tooltip,\n    Option.exists((shown) => shown === label),\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"
    },
    {
      "path": "packages/components/src/user-menu.tsx",
      "target": "@ui/gpuixui/components/user-menu.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { Props } from \"@gpuix/react\";\nimport type { FloatingAlign, FloatingSide } from \"@gpuix/react/floating\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { Icon } from \"../icons/index.js\";\nimport {\n  cn,\n  USER_MENU_EMAIL,\n  USER_MENU_IDENTITY,\n  USER_MENU_NAME,\n} from \"../tw/index.js\";\nimport { Avatar } from \"./avatar.js\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./dropdown-menu.js\";\nimport { recipeStyle } from \"./internal/recipe.js\";\nimport { useTheme } from \"./internal/theme.js\";\nimport { SidebarMenu, SidebarMenuItem, useSidebar } from \"./sidebar.js\";\n\nexport interface UserMenuUser {\n  readonly name: string;\n  readonly email: string;\n\n  readonly avatar?: string;\n}\n\nexport interface UserMenuActions {\n  readonly onUpgrade?: () => void;\n  readonly onAccount?: () => void;\n  readonly onBilling?: () => void;\n  readonly onNotifications?: () => void;\n  readonly onLogOut?: () => void;\n}\n\nexport interface UserMenuProps\n  extends UserMenuActions, Omit<Props, \"children\"> {\n  readonly user: UserMenuUser;\n\n  readonly side?: FloatingSide;\n\n  readonly align?: FloatingAlign;\n\n  readonly collapsed?: boolean;\n\n  readonly className?: string;\n\n  readonly testId?: string;\n\n  readonly menuTestId?: string;\n}\n\nconst IDENTITY_HEIGHT = 48;\nconst IDENTITY_HEIGHT_COLLAPSED = 32;\nconst MENU_WIDTH = 224;\n\nexport const UserMenu = ({\n  user,\n  onUpgrade,\n  onAccount,\n  onBilling,\n  onNotifications,\n  onLogOut,\n  side = \"bottom\",\n  align = \"end\",\n  collapsed = false,\n  className,\n  testId = \"user-menu-trigger\",\n  menuTestId = \"user-menu-content\",\n  style,\n  ...rest\n}: UserMenuProps) => {\n  const theme = useTheme();\n  const avatar = (\n    <Avatar\n      name={user.name}\n      src={user.avatar}\n      size={collapsed ? \"sm\" : \"md\"}\n      clickThrough\n    />\n  );\n  const identity = (\n    <div style={recipeStyle(USER_MENU_IDENTITY, theme)}>\n      <text\n        style={mergeStyles(recipeStyle(USER_MENU_NAME, theme), {\n          color: theme.colors.cardForeground,\n        })}\n      >\n        {user.name}\n      </text>\n      <text\n        style={mergeStyles(recipeStyle(USER_MENU_EMAIL, theme), {\n          color: theme.colors.mutedForeground,\n        })}\n      >\n        {user.email}\n      </text>\n    </div>\n  );\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        {...rest}\n        variant=\"ghost\"\n        size=\"md\"\n        aria-label={`${user.name} account menu`}\n        testId={testId}\n        style={mergeStyles(\n          {\n            height: collapsed ? IDENTITY_HEIGHT_COLLAPSED : IDENTITY_HEIGHT,\n\n            ...(collapsed\n              ? { width: IDENTITY_HEIGHT_COLLAPSED, padding: 3 }\n              : { paddingLeft: 8, paddingRight: 8 }),\n            justifyContent: collapsed ? \"center\" : \"flex-start\",\n            color: theme.colors.cardForeground,\n            ...recipeStyle(cn(className), theme),\n          },\n          style,\n        )}\n      >\n        {}\n        {collapsed ? (\n          avatar\n        ) : (\n          <div\n            style={{\n              display: \"flex\",\n              flexDirection: \"row\",\n              alignItems: \"center\",\n              gap: 8,\n              flexGrow: 1,\n              minWidth: 0,\n            }}\n          >\n            {avatar}\n            {identity}\n          </div>\n        )}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent\n        label={`${user.name} account`}\n        side={side}\n        align={align}\n        sideOffset={4}\n        testId={menuTestId}\n        style={{ width: MENU_WIDTH }}\n      >\n        <div\n          style={{\n            display: \"flex\",\n            flexDirection: \"row\",\n            alignItems: \"center\",\n            gap: 8,\n            paddingLeft: 4,\n            paddingRight: 4,\n            paddingTop: 4,\n            paddingBottom: 4,\n          }}\n        >\n          {avatar}\n          {identity}\n        </div>\n        {}\n        {onUpgrade === undefined ? null : (\n          <>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup>\n              <DropdownMenuItem\n                testId=\"user-menu-upgrade\"\n                onSelect={onUpgrade}\n                leading={\n                  <Icon\n                    name=\"sparkles\"\n                    size=\"sm\"\n                    color={theme.colors.mutedForeground}\n                  />\n                }\n              >\n                Upgrade to Pro\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n          </>\n        )}\n        {onAccount === undefined &&\n        onBilling === undefined &&\n        onNotifications === undefined ? null : (\n          <>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup>\n              {onAccount === undefined ? null : (\n                <DropdownMenuItem\n                  testId=\"user-menu-account\"\n                  onSelect={onAccount}\n                  leading={\n                    <Icon\n                      name=\"badge-check\"\n                      size=\"sm\"\n                      color={theme.colors.mutedForeground}\n                    />\n                  }\n                >\n                  Account\n                </DropdownMenuItem>\n              )}\n              {onBilling === undefined ? null : (\n                <DropdownMenuItem\n                  testId=\"user-menu-billing\"\n                  onSelect={onBilling}\n                  leading={\n                    <Icon\n                      name=\"credit-card\"\n                      size=\"sm\"\n                      color={theme.colors.mutedForeground}\n                    />\n                  }\n                >\n                  Billing\n                </DropdownMenuItem>\n              )}\n              {onNotifications === undefined ? null : (\n                <DropdownMenuItem\n                  testId=\"user-menu-notifications\"\n                  onSelect={onNotifications}\n                  leading={\n                    <Icon\n                      name=\"bell\"\n                      size=\"sm\"\n                      color={theme.colors.mutedForeground}\n                    />\n                  }\n                >\n                  Notifications\n                </DropdownMenuItem>\n              )}\n            </DropdownMenuGroup>\n          </>\n        )}\n        {onLogOut === undefined ? null : (\n          <>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem\n              testId=\"user-menu-log-out\"\n              onSelect={onLogOut}\n              leading={\n                <Icon\n                  name=\"log-out\"\n                  size=\"sm\"\n                  color={theme.colors.mutedForeground}\n                />\n              }\n            >\n              Log out\n            </DropdownMenuItem>\n          </>\n        )}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n};\n\nexport type SidebarUserMenuProps = Omit<UserMenuProps, \"collapsed\">;\n\nexport const SidebarUserMenu = (props: SidebarUserMenuProps) => {\n  const { state, collapsible, isMobile } = useSidebar();\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <UserMenu\n          {...props}\n          collapsed={state === \"collapsed\" && collapsible === \"icon\"}\n          side={props.side ?? (isMobile ? \"bottom\" : \"right\")}\n        />\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n};\n\nexport const userMenuMeta = {\n  name: \"User menu\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"no aria-haspopup in 0.11.0: the trigger announces aria-expanded only\",\n    \"the trigger is the local DropdownMenu's own action, because it owns the element it anchors and measures; a row composed inside SidebarMenu keeps the list semantics\",\n    \"the avatar is painted from a path or URL; there is no image-loading or error state\",\n  ],\n  docs: \"docs/user-menu.mdx\",\n  example: \"examples/user-menu.example.tsx\",\n  testFile: \"src/behaviour/user-menu.test.tsx\",\n} as const;\n"
    }
  ],
  "docs": "[User menu](https://gpuixui.sudokw.com/components/user-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/user-menu",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}