{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-drawer",
  "type": "registry:component",
  "title": "Drawer",
  "description": "A modal panel anchored to any window edge",
  "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/drawer.tsx",
      "target": "@ui/gpuixui/components/drawer.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { KeyEvent, Props, PublicInstance } from \"@gpuix/react\";\nimport { useGpuix, useWindowSize } from \"@gpuix/react\";\nimport { mergeStyles } from \"@gpuix/react/floating\";\nimport { Icon } from \"../icons/index.js\";\nimport {\n  DRAWER_CONTENT,\n  DRAWER_DESCRIPTION,\n  DRAWER_FOOTER,\n  DRAWER_HEADER,\n  DRAWER_OVERLAY,\n  DRAWER_TITLE,\n} from \"../tw/index.js\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n  type MutableRefObject,\n} from \"react\";\nimport * as Schema from \"effect/Schema\";\nimport { Button, IconButton, type ButtonProps } from \"./internal/button.js\";\nimport {\n  handleModalKeyDown,\n  useModalFocus,\n  useModalState,\n} from \"./internal/modal.js\";\nimport {\n  assertParts,\n  type PartChildren,\n  type PartComponent,\n  type PartsContract,\n} from \"./internal/parts.js\";\nimport {\n  fadeFromSide,\n  fadeOnly,\n  PresenceShield,\n  presenceMotion,\n  presenceStyle,\n  presenceTabStop,\n  useSurfacePresence,\n  useSurfaceTiming,\n} from \"./internal/presence.js\";\nimport { useMergedRef } from \"./internal/ref.js\";\nimport { dismissTopLayer } from \"./internal/dismiss-layer.js\";\nimport { DismissScope } from \"./internal/dismiss-scope.js\";\nimport { overlaySurfaceStyle, overlayVeilColor } from \"./internal/overlay.js\";\nimport { useTheme, type Theme } from \"./internal/theme.js\";\n\nexport type DrawerSide = \"left\" | \"right\" | \"top\" | \"bottom\";\n\nconst DrawerEdges = Schema.Literals([\"left\", \"right\", \"top\", \"bottom\"]);\n\nexport type DrawerSize = \"sm\" | \"md\" | \"lg\";\n\nconst DrawerSizes = Schema.Literals([\"sm\", \"md\", \"lg\"]);\n\nconst WIDTH: Readonly<Record<DrawerSize, number>> = {\n  sm: 320,\n  md: 400,\n  lg: 560,\n};\n\nconst HEIGHT: Readonly<Record<DrawerSize, number>> = {\n  sm: 240,\n  md: 320,\n  lg: 420,\n};\n\ninterface DrawerContextValue {\n  readonly open: boolean;\n  readonly setOpen: (open: boolean) => void;\n  readonly restoreTarget: MutableRefObject<number | null>;\n}\n\nconst DrawerContext = createContext<DrawerContextValue | undefined>(undefined);\n\nconst useDrawerContext = (): DrawerContextValue => {\n  const value = useContext(DrawerContext);\n  if (value === undefined) {\n    throw new Error(\"Drawer parts must be used inside Drawer\");\n  }\n  return value;\n};\n\nexport interface DrawerProps {\n  readonly open?: boolean;\n  readonly defaultOpen?: boolean;\n  readonly onOpenChange?: (open: boolean) => void;\n\n  readonly dismissible?: boolean;\n  readonly children?: PartChildren;\n}\n\nexport const Drawer = ({\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  dismissible = true,\n  children,\n}: DrawerProps) => {\n  const { open, setOpen, restoreTarget } = useModalState({\n    open: openProp,\n    defaultOpen,\n    onOpenChange,\n    dismissible,\n  });\n\n  const context = useMemo<DrawerContextValue>(\n    () => ({ open, setOpen, restoreTarget }),\n    [open, restoreTarget, setOpen],\n  );\n\n  assertParts(drawerRoot, children);\n\n  return (\n    <DrawerContext.Provider value={context}>{children}</DrawerContext.Provider>\n  );\n};\n\nexport const DrawerTrigger = ({ onClick, ...rest }: ButtonProps) => {\n  const { open, setOpen } = useDrawerContext();\n  return (\n    <Button\n      {...rest}\n      aria-expanded={open}\n      onClick={(event) => {\n        onClick?.(event);\n        setOpen(!open);\n      }}\n    />\n  );\n};\n\nexport const DrawerClose = ({ onClick, ...rest }: ButtonProps) => {\n  const { open, setOpen } = useDrawerContext();\n  return (\n    <Button\n      {...presenceTabStop(open)}\n      {...rest}\n      onClick={(event) => {\n        onClick?.(event);\n        setOpen(false);\n      }}\n    />\n  );\n};\n\nexport interface DrawerContentProps extends Omit<Props, \"children\"> {\n  readonly side?: DrawerSide;\n\n  readonly size?: DrawerSize;\n\n  readonly label: string;\n\n  readonly showClose?: boolean;\n\n  readonly inset?: number;\n\n  readonly grabber?: boolean;\n\n  readonly initialFocus?: () => number | null;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nconst drawerSide = (side: unknown): DrawerSide =>\n  Schema.is(DrawerEdges)(side) ? side : \"right\";\n\nconst drawerSize = (size: unknown): DrawerSize =>\n  Schema.is(DrawerSizes)(size) ? size : \"md\";\n\nconst isVertical = (side: DrawerSide): boolean =>\n  side === \"bottom\" || side === \"top\";\n\nconst surfaceSize = (\n  side: DrawerSide,\n  size: DrawerSize,\n  window: { readonly width: number; readonly height: number },\n): { readonly width: number; readonly height: number } =>\n  isVertical(side)\n    ? { width: window.width, height: Math.min(HEIGHT[size], window.height) }\n    : { width: Math.min(WIDTH[size], window.width), height: window.height };\n\nconst innerCorners = (side: DrawerSide, radius: number) => ({\n  borderTopLeftRadius: side === \"right\" || side === \"bottom\" ? radius : 0,\n  borderTopRightRadius: side === \"left\" || side === \"bottom\" ? radius : 0,\n  borderBottomLeftRadius: side === \"right\" || side === \"top\" ? radius : 0,\n  borderBottomRightRadius: side === \"left\" || side === \"top\" ? radius : 0,\n});\n\nconst GRABBER_WIDTH = 40;\nconst GRABBER_HEIGHT = 4;\n\nconst grabberStyle = (theme: Theme, side: DrawerSide) => ({\n  position: \"absolute\" as const,\n  left: 0,\n  right: 0,\n  top: side === \"bottom\" ? theme.spacing.sm : undefined,\n  bottom: side === \"top\" ? theme.spacing.sm : undefined,\n  display: \"flex\" as const,\n  justifyContent: \"center\" as const,\n  pointerEvents: \"none\" as const,\n});\n\nconst grabberBarStyle = (theme: Theme) => ({\n  width: GRABBER_WIDTH,\n  height: GRABBER_HEIGHT,\n  borderRadius: theme.radius.full,\n  backgroundColor: theme.colors.borderStrong,\n});\n\nconst surfaceStyle = (\n  theme: Theme,\n  box: { width: number; height: number },\n  side: DrawerSide,\n  className?: string,\n) => ({\n  ...overlaySurfaceStyle(DRAWER_CONTENT, className, theme, \"lg\"),\n  width: box.width,\n  height: box.height,\n  position: \"relative\" as const,\n  overflow: \"scroll\" as const,\n  ...innerCorners(side, theme.radius.xl),\n});\n\nconst entryTravel = (\n  side: DrawerSide,\n  box: { readonly width: number; readonly height: number },\n): number => (isVertical(side) ? box.height : box.width);\n\nexport const DrawerContent = ({\n  side: requestedSide,\n  size: requestedSize = \"md\",\n  label,\n  showClose = true,\n  inset,\n  grabber = true,\n  initialFocus,\n  className,\n  children,\n  onKeyDown,\n  onMouseDownOutside,\n  style,\n  testId = \"drawer-content\",\n  ref,\n  ...rest\n}: DrawerContentProps) => {\n  const theme = useTheme();\n  const { open, setOpen, restoreTarget } = useDrawerContext();\n  const renderer = useGpuix().renderer ?? undefined;\n  const window = useWindowSize();\n  assertParts(drawerContent, children);\n  const timing = useSurfaceTiming(\"slow\");\n  const presence = useSurfacePresence(open, timing);\n\n  const side = drawerSide(requestedSide);\n\n  const size = drawerSize(requestedSize);\n  const [content, setContent] = useState<PublicInstance | undefined>(undefined);\n  const contentRef = useCallback((next: PublicInstance | null) => {\n    setContent(next ?? undefined);\n  }, []);\n  const setContentElement = useMergedRef<PublicInstance | null>(\n    contentRef,\n    ref,\n  );\n\n  const contentId = content?.id;\n\n  const [outline, setOutline] = useState<PublicInstance | undefined>(undefined);\n  const outlineRef = useCallback((next: PublicInstance | null) => {\n    setOutline(next ?? undefined);\n  }, []);\n  const trapId = outline?.id ?? contentId;\n  useModalFocus(contentId, initialFocus, open, restoreTarget);\n\n  if (!presence.mounted) return null;\n\n  const hangs = side === \"top\" && inset !== undefined;\n  const edgeBox = surfaceSize(side, size, window);\n  const box = hangs\n    ? { ...edgeBox, width: Math.min(edgeBox.width, window.width - 80) }\n    : edgeBox;\n  const close = () => setOpen(false);\n  const handleKeyDown = (event: KeyEvent): void => {\n    handleModalKeyDown({ event, contentId: trapId, renderer, onKeyDown });\n  };\n\n  const travel = entryTravel(side, box);\n\n  return (\n    <anchored\n      position={{ x: 0, y: 0 }}\n      occlude\n      style={{ position: \"absolute\" }}\n    >\n      <div\n        testId=\"drawer-backdrop\"\n        motion={presenceMotion(fadeOnly, presence)}\n        style={mergeStyles(\n          overlaySurfaceStyle(DRAWER_OVERLAY, undefined, theme),\n          {\n            backgroundColor: overlayVeilColor(theme),\n            width: window.width,\n            height: window.height,\n\n            overflow: \"hidden\",\n            display: \"flex\",\n            flexDirection: isVertical(side) ? \"column\" : \"row\",\n            alignItems: \"stretch\",\n            justifyContent:\n              side === \"left\" || side === \"top\" ? \"flex-start\" : \"flex-end\",\n            ...(hangs ? { alignItems: \"center\", paddingTop: inset } : {}),\n            ...presenceStyle(presence.present),\n          },\n        )}\n      >\n        {}\n        <div\n          ref={outlineRef}\n          onMouseDownOutside={(event) => {\n            onMouseDownOutside?.(event);\n            dismissTopLayer(event);\n          }}\n          onKeyDown={handleKeyDown}\n          style={{\n            position: \"relative\",\n            display: \"flex\",\n            flexDirection: \"column\",\n            width: box.width,\n            height: box.height,\n          }}\n        >\n          <div\n            motion={presenceMotion(fadeFromSide(side, travel), presence)}\n            style={{\n              position: \"relative\",\n              display: \"flex\",\n              flexDirection: \"column\",\n              width: box.width,\n              height: box.height,\n              ...presenceStyle(presence.present),\n            }}\n          >\n            {grabber && isVertical(side) ? (\n              <div testId=\"drawer-grabber\" style={grabberStyle(theme, side)}>\n                <div style={grabberBarStyle(theme)} />\n              </div>\n            ) : null}\n            <div\n              role=\"dialog\"\n              aria-label={label}\n              tabIndex={-1}\n              {...rest}\n              ref={setContentElement}\n              testId={testId}\n              style={mergeStyles(\n                surfaceStyle(theme, box, side, className),\n                style,\n              )}\n            >\n              <DismissScope>{children}</DismissScope>\n            </div>\n            {showClose ? (\n              <div\n                style={{\n                  position: \"absolute\",\n                  top: theme.spacing.lg,\n                  right: theme.spacing.lg,\n                  display: \"flex\",\n                }}\n              >\n                <IconButton\n                  label=\"Close drawer\"\n                  size=\"sm\"\n                  testId=\"drawer-close\"\n                  {...presenceTabStop(presence.present)}\n                  onClick={close}\n                >\n                  <Icon name=\"x\" size=\"sm\" color={theme.colors.foreground} />\n                </IconButton>\n              </div>\n            ) : null}\n          </div>\n          <PresenceShield present={presence.present} />\n        </div>\n      </div>\n    </anchored>\n  );\n};\n\nexport interface DrawerHeaderProps extends Omit<Props, \"children\"> {\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nexport const DrawerHeader = ({\n  children,\n  className,\n  style,\n  ...rest\n}: DrawerHeaderProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        mergeStyles(overlaySurfaceStyle(DRAWER_HEADER, className, theme), {\n          paddingRight: theme.spacing[\"2xl\"],\n        }),\n        style,\n      )}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport interface DrawerTitleProps extends Props {\n  readonly className?: string;\n}\n\nexport const DrawerTitle = ({\n  className,\n  style,\n  ...rest\n}: DrawerTitleProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      role=\"heading\"\n      aria-level={2}\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(DRAWER_TITLE, className, theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface DrawerDescriptionProps extends Props {\n  readonly className?: string;\n}\n\nexport const DrawerDescription = ({\n  className,\n  style,\n  ...rest\n}: DrawerDescriptionProps) => {\n  const theme = useTheme();\n  return (\n    <text\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(DRAWER_DESCRIPTION, className, theme),\n        style,\n      )}\n    />\n  );\n};\n\nexport interface DrawerFooterProps extends Props {\n  readonly className?: string;\n}\n\nexport const DrawerFooter = ({\n  className,\n  style,\n  ...rest\n}: DrawerFooterProps) => {\n  const theme = useTheme();\n  return (\n    <div\n      {...rest}\n      style={mergeStyles(\n        overlaySurfaceStyle(DRAWER_FOOTER, className, theme),\n        style,\n      )}\n    />\n  );\n};\n\nconst drawerParts: ReadonlyArray<PartComponent> = [\n  Drawer,\n  DrawerTrigger,\n  DrawerClose,\n  DrawerContent,\n  DrawerHeader,\n  DrawerTitle,\n  DrawerDescription,\n  DrawerFooter,\n];\n\nconst drawerRoot: PartsContract = {\n  parent: \"Drawer\",\n  family: drawerParts,\n  accepts: [DrawerTrigger, DrawerContent, DrawerClose],\n  hint: \"Put DrawerTrigger and DrawerContent directly inside Drawer.\",\n};\n\nconst drawerContent: PartsContract = {\n  parent: \"DrawerContent\",\n  family: drawerParts,\n  accepts: [\n    DrawerHeader,\n    DrawerTitle,\n    DrawerDescription,\n    DrawerFooter,\n    DrawerClose,\n  ],\n  hint: \"Put the header, title, description, footer and close control inside DrawerContent.\",\n};\n\nexport const drawerMeta = {\n  name: \"Drawer\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"partial\",\n  limitations: [\n    \"no swipe-to-dismiss or snap points: 0.11.0 has no drag host, so the grabber cannot be dragged\",\n    \"the surface slides its own span from the named edge: the runtime has no transform, so the travel is a numeric left/top offset on the surface's own box, and the window-sized veil (overflow hidden) clips it\",\n    \"the veil cannot blur the page: StyleDesc has no filter or backdropFilter, so the 6px blur the drawings paint is drawing-only\",\n    \"the inner corners round (radius.xl) while the window edge stays flush, and a top/bottom sheet paints the vaul grabber as decoration\",\n    \"no aria-modal or aria-labelledby in 0.11.0: the surface is named with aria-label\",\n    \"the veil is an anchored element at window coordinates: the styled Dialog is a local overlay, not `@gpuix/react/dialog`'s portalled surface\",\n    \"sizes are layout constants: tokens carry no drawer-size role\",\n    \"a size restored from storage that names no variant paints the default `md` box: the guarded lookup never hands the renderer an undefined width or height\",\n    \"a dismissed panel stays painted for its exit and its own close control leaves the tab order for that window; a control composed inside the body keeps its stop, because 0.11.0 has no inherited `inert`\",\n  ],\n  docs: \"docs/drawer.mdx\",\n  example: \"examples/drawer.example.tsx\",\n  testFile: \"src/behaviour/drawer.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/controllable.ts",
      "target": "@ui/gpuixui/components/internal/controllable.ts",
      "type": "registry:lib",
      "content": "import { useCallback, useState } from \"react\";\n\nexport type ControllableState<Value> = readonly [Value, (next: Value) => void];\n\nexport interface ControllableStateOptions<Value> {\n  readonly value: Value | undefined;\n\n  readonly defaultValue: Value;\n\n  readonly onChange?: (next: Value) => void;\n}\n\nexport const useControllableState = <Value>({\n  value,\n  defaultValue,\n  onChange,\n}: ControllableStateOptions<Value>): ControllableState<Value> => {\n  const [internal, setInternal] = useState(defaultValue);\n  const controlled = value !== undefined;\n\n  const setValue = useCallback(\n    (next: Value) => {\n      if (!controlled) setInternal(next);\n      if (value !== next) onChange?.(next);\n    },\n    [controlled, value, onChange],\n  );\n\n  return [controlled ? value : internal, setValue];\n};\n"
    },
    {
      "path": "packages/components/src/internal/dismiss-layer.ts",
      "target": "@ui/gpuixui/components/internal/dismiss-layer.ts",
      "type": "registry:lib",
      "content": "import type { EventPayload } from \"@gpuix/react\";\nimport * as Arr from \"effect/Array\";\nimport * as Duration from \"effect/Duration\";\nimport * as Option from \"effect/Option\";\nimport * as Order from \"effect/Order\";\nimport { monotonicNanos } from \"./clock.js\";\n\nexport type DismissHandler = (event: EventPayload) => void;\n\ninterface Layer {\n  readonly onDismiss: DismissHandler;\n  readonly depth: number;\n}\n\nlet layers: ReadonlyArray<Layer> = [];\n\nlet dismissedKeystroke: Option.Option<number> = Option.none();\n\ninterface Press {\n  readonly shape: string;\n  readonly at: bigint;\n}\n\nlet dismissedPress: Option.Option<Press> = Option.none();\n\nexport const pointerGestureWindow: Duration.Duration = Duration.millis(64);\n\nconst PRESS_WINDOW_NANOS =\n  BigInt(Duration.toMillis(pointerGestureWindow)) * 1_000_000n;\n\nconst pressShape = (event: EventPayload): string =>\n  Arr.join(\":\")(\n    Arr.map(\n      [event.eventType, event.x, event.y, event.button, event.clickCount],\n      (part) => (part === undefined ? \"\" : `${part}`),\n    ),\n  );\n\nconst consumedPress = (event: EventPayload, now: bigint): boolean => {\n  const shape = pressShape(event);\n  const repeated = Option.exists(\n    dismissedPress,\n    (press) => press.shape === shape && now - press.at <= PRESS_WINDOW_NANOS,\n  );\n  if (!repeated) dismissedPress = Option.some({ shape, at: now });\n  return repeated;\n};\n\nlet lastDismissedPoint: Option.Option<{\n  readonly point: string;\n  readonly at: bigint;\n}> = Option.none();\n\nconst pointOf = (event: EventPayload): string =>\n  Arr.join(\":\")(\n    Arr.map([event.x, event.y, event.button], (part) =>\n      part === undefined ? \"\" : `${part}`,\n    ),\n  );\n\nexport const isDismissalClick = (\n  event: EventPayload,\n  now: bigint = monotonicNanos(),\n): boolean =>\n  Option.exists(\n    lastDismissedPoint,\n    (dismissed) =>\n      dismissed.point === pointOf(event) &&\n      now - dismissed.at <= PRESS_WINDOW_NANOS,\n  );\n\nconst consumedKeystroke = (keystrokeId: number): boolean => {\n  if (Option.contains(dismissedKeystroke, keystrokeId)) return true;\n  dismissedKeystroke = Option.some(keystrokeId);\n  return false;\n};\n\nconst consume = (event: EventPayload, now: bigint): boolean =>\n  Option.match(Option.fromNullishOr(event.keystrokeId), {\n    onNone: () => consumedPress(event, now),\n    onSome: consumedKeystroke,\n  });\n\nconst byDepth: Order.Order<Layer> = Order.mapInput(\n  Order.Number,\n  (layer: Layer) => layer.depth,\n);\n\nconst topmost = (): Option.Option<DismissHandler> =>\n  Option.map(Arr.last(Arr.sort(layers, byDepth)), (layer) => layer.onDismiss);\n\nexport const pushDismissLayer = (\n  onDismiss: DismissHandler,\n  depth: number,\n): (() => void) => {\n  const layer: Layer = { onDismiss, depth };\n  layers = Arr.append(layers, layer);\n  return () => {\n    layers = Arr.filter(layers, (candidate) => candidate !== layer);\n    if (Arr.length(layers) === 0) {\n      dismissedKeystroke = Option.none();\n      dismissedPress = Option.none();\n    }\n  };\n};\n\nexport const dismissTopLayer = (\n  event: EventPayload,\n  now: bigint = monotonicNanos(),\n): boolean => {\n  if (consume(event, now)) return false;\n  return Option.match(topmost(), {\n    onNone: () => false,\n    onSome: (onDismiss) => {\n      lastDismissedPoint = Option.some({ point: pointOf(event), at: now });\n      onDismiss(event);\n      return true;\n    },\n  });\n};\n\nexport const dismissLayerCount = (): number => Arr.length(layers);\n"
    },
    {
      "path": "packages/components/src/internal/dismiss-scope.tsx",
      "target": "@ui/gpuixui/components/internal/dismiss-scope.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport { createContext, useContext, type ReactNode } from \"react\";\n\nconst AmbientDepth = createContext(0);\n\nexport const useDismissDepth = (): number => useContext(AmbientDepth) + 1;\n\nexport const DismissScope = ({\n  children,\n}: {\n  readonly children: ReactNode;\n}) => (\n  <AmbientDepth.Provider value={useDismissDepth()}>\n    {children}\n  </AmbientDepth.Provider>\n);\n"
    },
    {
      "path": "packages/components/src/internal/key.ts",
      "target": "@ui/gpuixui/components/internal/key.ts",
      "type": "registry:lib",
      "content": "import type { EventPayload, KeyEvent, Props } from \"@gpuix/react\";\nimport * as Option from \"effect/Option\";\n\nexport type KeyPhase = \"down\" | \"up\";\n\nexport const isActivation = (event: EventPayload, phase: KeyPhase): boolean => {\n  if (event.isHeld === true) return false;\n  if (event.key === \"enter\") return phase === \"down\";\n  return event.key === \"space\" && phase === \"up\";\n};\n\nexport const swallowsDefault = (\n  event: EventPayload,\n  phase: KeyPhase,\n): boolean => event.key === \"space\" && phase === \"down\";\n\nexport const activationHandlers = (\n  activate: (event: KeyEvent) => void,\n  observe: Pick<Props, \"onKeyDown\" | \"onKeyUp\"> = {},\n): Pick<Props, \"onKeyDown\" | \"onKeyUp\"> => ({\n  onKeyDown: (event: KeyEvent) => {\n    if (swallowsDefault(event, \"down\")) event.preventDefault();\n    if (isActivation(event, \"down\")) activate(event);\n    observe.onKeyDown?.(event);\n  },\n  onKeyUp: (event: KeyEvent) => {\n    if (isActivation(event, \"up\")) activate(event);\n    observe.onKeyUp?.(event);\n  },\n});\n\nexport const isEnter = (event: EventPayload): boolean => event.key === \"enter\";\n\nexport const isDown = (event: EventPayload): boolean =>\n  event.key === \"down\" || (event.key === \"n\" && event.modifiers?.ctrl === true);\n\nexport const isUp = (event: EventPayload): boolean =>\n  event.key === \"up\" || (event.key === \"p\" && event.modifiers?.ctrl === true);\n\nexport const isRight = (event: EventPayload): boolean => event.key === \"right\";\n\nexport const isLeft = (event: EventPayload): boolean => event.key === \"left\";\n\nexport const isFirst = (event: EventPayload): boolean => event.key === \"home\";\n\nexport const isLast = (event: EventPayload): boolean => event.key === \"end\";\n\nexport const isPageUp = (event: EventPayload): boolean =>\n  event.key === \"pageup\";\n\nexport const isPageDown = (event: EventPayload): boolean =>\n  event.key === \"pagedown\";\n\nexport const isDismiss = (event: EventPayload): boolean =>\n  event.key === \"escape\";\n\nexport const isTab = (event: EventPayload): boolean => event.key === \"tab\";\n\nexport const isSelectAll = (event: EventPayload): boolean =>\n  event.key === \"a\" &&\n  (event.modifiers?.cmd === true || event.modifiers?.ctrl === true);\n\nexport const isMenuKey = (event: EventPayload): boolean =>\n  (event.key === \"f10\" && event.modifiers?.shift === true) ||\n  event.key === \"contextmenu\";\n\nexport const axisDirection = (\n  event: EventPayload,\n  orientation: \"horizontal\" | \"vertical\",\n): Option.Option<1 | -1> => {\n  if (event.key === \"right\") return Option.some(1);\n  if (event.key === \"left\") return Option.some(-1);\n  if (orientation === \"vertical\") {\n    if (isDown(event)) return Option.some(1);\n    if (isUp(event)) return Option.some(-1);\n  }\n  return Option.none();\n};\n"
    },
    {
      "path": "packages/components/src/internal/modal.ts",
      "target": "@ui/gpuixui/components/internal/modal.ts",
      "type": "registry:lib",
      "content": "import type { KeyEvent } from \"@gpuix/react\";\nimport { useGpuix } from \"@gpuix/react\";\nimport { Match } from \"effect\";\nimport { useLayoutEffect, useRef, type MutableRefObject } from \"react\";\nimport { useControllableState } from \"./controllable.js\";\nimport { useDismissDepth } from \"./dismiss-scope.js\";\nimport { dismissTopLayer, pushDismissLayer } from \"./dismiss-layer.js\";\nimport { isDismiss, isTab } from \"./key.js\";\n\nexport interface ModalState {\n  readonly open: boolean;\n  readonly setOpen: (open: boolean) => void;\n\n  readonly restoreTarget: MutableRefObject<number | null>;\n}\n\nexport const useModalState = ({\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  dismissible = true,\n}: {\n  readonly open?: boolean;\n  readonly defaultOpen?: boolean;\n  readonly onOpenChange?: (open: boolean) => void;\n  readonly dismissible?: boolean;\n}): ModalState => {\n  const renderer = useGpuix().renderer;\n  const [open, setOpen] = useControllableState({\n    value: openProp,\n    defaultValue: defaultOpen,\n    onChange: onOpenChange,\n  });\n\n  const restoreTarget = useRef<number | null>(null);\n  const captured = useRef(false);\n  if (open && !captured.current) {\n    restoreTarget.current = renderer?.getFocusedElementId?.() ?? null;\n    captured.current = true;\n  } else if (!open) {\n    captured.current = false;\n  }\n\n  const dismiss = useRef<() => void>(() => undefined);\n  useLayoutEffect(() => {\n    dismiss.current = () => {\n      if (dismissible) setOpen(false);\n    };\n  });\n\n  const depth = useDismissDepth();\n  useLayoutEffect(() => {\n    if (!open) return;\n    return pushDismissLayer(() => dismiss.current(), depth);\n  }, [depth, open]);\n\n  return { open, setOpen, restoreTarget };\n};\n\nexport const useModalFocus = (\n  contentId: number | undefined,\n  initialFocus: (() => number | null) | undefined,\n  open: boolean,\n  restoreTarget: MutableRefObject<number | null>,\n): void => {\n  const renderer = useGpuix().renderer;\n  const initialFocusRef = useRef(initialFocus);\n  useLayoutEffect(() => {\n    initialFocusRef.current = initialFocus;\n  });\n\n  useLayoutEffect(() => {\n    if (contentId === undefined || !open) return;\n    renderer?.focusElement?.(contentId);\n    const initial = initialFocusRef.current?.() ?? null;\n    if (initial !== null && initial !== contentId) {\n      renderer?.focusElement?.(initial);\n    }\n    return () => {\n      const previous = restoreTarget.current;\n\n      if (\n        previous === null ||\n        previous === contentId ||\n        renderer?.getElementBounds?.(previous) === null\n      ) {\n        return;\n      }\n      renderer?.focusElement?.(previous);\n    };\n  }, [contentId, renderer, open, restoreTarget]);\n};\n\nexport const handleModalKeyDown = ({\n  event,\n  contentId,\n  renderer,\n  onKeyDown,\n}: {\n  readonly event: KeyEvent;\n  readonly contentId: number | undefined;\n  readonly renderer:\n    | {\n        focusNextWithin?: (id: number) => void;\n        focusPreviousWithin?: (id: number) => void;\n      }\n    | undefined;\n  readonly onKeyDown?: (event: KeyEvent) => void;\n}): void => {\n  Match.value(event).pipe(\n    Match.when(isDismiss, (value) => {\n      dismissTopLayer(value);\n    }),\n    Match.when(isTab, () => {\n      event.preventDefault();\n      if (event.modifiers?.shift === true) {\n        renderer?.focusPreviousWithin?.(contentId ?? -1);\n      } else {\n        renderer?.focusNextWithin?.(contentId ?? -1);\n      }\n    }),\n    Match.orElse(() => undefined),\n  );\n  onKeyDown?.(event);\n};\n"
    },
    {
      "path": "packages/components/src/internal/overlay.ts",
      "target": "@ui/gpuixui/components/internal/overlay.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { cn } from \"../../tw/index.js\";\nimport * as Arr from \"effect/Array\";\nimport * as Option from \"effect/Option\";\nimport { recipeStyle } from \"./recipe.js\";\nimport {\n  alpha,\n  elevationShadow,\n  type BoxShadow,\n  type ElevationLevel,\n  type Theme,\n} from \"./theme.js\";\n\nexport const OVERLAY_WASH = 0.55;\n\nexport const overlayVeilColor = (theme: Theme): string =>\n  alpha(theme.colors.background, OVERLAY_WASH);\n\nconst isEdge = (shadow: BoxShadow): boolean =>\n  shadow.blurRadius === 0 && shadow.spreadRadius > 0;\n\nconst shadows = (painted: StyleDesc[\"boxShadow\"]): ReadonlyArray<BoxShadow> =>\n  Option.match(Option.fromNullishOr(painted), {\n    onNone: () => [],\n    onSome: (value) => Arr.ensure(value),\n  });\n\nexport const overlaySurfaceStyle = (\n  recipe: string,\n  className: string | undefined,\n  theme: Theme,\n  elevation?: ElevationLevel,\n): StyleDesc => {\n  const style = recipeStyle(cn(recipe, className), theme);\n  const lift = Option.fromNullishOr(elevation).pipe(\n    Option.flatMap((level) =>\n      Option.fromNullishOr(elevationShadow(theme, level)),\n    ),\n  );\n\n  return Option.match(lift, {\n    onNone: () => style,\n    onSome: (shadow) => {\n      const painted = shadows(style.boxShadow);\n      const lost = Arr.every(painted, isEdge) && painted.length > 0;\n      return lost ? { ...style, boxShadow: [shadow, ...painted] } : style;\n    },\n  });\n};\n"
    },
    {
      "path": "packages/components/src/internal/parts.ts",
      "target": "@ui/gpuixui/components/internal/parts.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as P from \"effect/Predicate\";\nimport {\n  Children,\n  Fragment,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n} from \"react\";\n\nexport type PartComponent = (props: never) => ReactNode;\n\nexport type PartChildren =\n  | ReactElement<unknown>\n  | boolean\n  | null\n  | undefined\n  | ReadonlyArray<PartChildren>;\n\nexport interface PartsContract {\n  readonly parent: string;\n\n  readonly family: ReadonlyArray<PartComponent>;\n\n  readonly accepts: ReadonlyArray<PartComponent>;\n\n  readonly hint?: string;\n}\n\nconst componentName = (type: unknown): string =>\n  P.isString(type)\n    ? type\n    : P.isFunction(type)\n      ? type.name || \"anonymous\"\n      : \"element\";\n\nconst fragmentChildren = (element: ReactElement<unknown>): ReactNode =>\n  hasChildren(element.props) ? element.props.children : undefined;\n\nconst hasChildren = (\n  value: unknown,\n): value is { readonly children?: ReactNode } =>\n  P.isObject(value) && \"children\" in value;\n\nconst leaves = (children: ReactNode): ReadonlyArray<unknown> =>\n  Arr.flatMap(Children.toArray(children), (child) =>\n    isValidElement<unknown>(child) && child.type === Fragment\n      ? leaves(fragmentChildren(child))\n      : [child],\n  );\n\nexport const assertParts = (\n  contract: PartsContract,\n  children: ReactNode,\n): void => {\n  Arr.forEach(leaves(children), (leaf) => {\n    if (!isValidElement(leaf)) return;\n    if (Arr.some(contract.accepts, (part) => part === leaf.type)) return;\n    if (!Arr.some(contract.family, (part) => part === leaf.type)) return;\n\n    throw new Error(\n      `${componentName(leaf.type)} cannot be used inside ${contract.parent}.` +\n        (contract.hint === undefined ? \"\" : ` ${contract.hint}`),\n    );\n  });\n};\n"
    },
    {
      "path": "packages/components/src/internal/presence.tsx",
      "target": "@ui/gpuixui/components/internal/presence.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { MotionProps } from \"@gpuix/react\";\nimport {\n  delayedTiming,\n  exitWindowMs,\n  fadeFrom,\n  fadeFromAnchor,\n  fadeFromSide,\n  fadeOnly,\n  presenceMotion as motionOfPresence,\n  presenceStyle,\n  transitionWindowMs,\n  type PresenceFrames,\n  type PresenceHitRule,\n  type PresenceSide,\n  type PresenceTiming,\n  type PresenceTransition,\n} from \"../../motion/presence.js\";\nimport {\n  MotionProvider,\n  useMotionPreference,\n  useSurfacePresence,\n  useSurfaceTiming,\n  type MotionProviderProps,\n  type SurfacePresence,\n} from \"../../motion/react/presence.js\";\nimport {\n  motionTiming,\n  type MotionLevel,\n  type MotionPreference,\n} from \"../../motion/timing.js\";\n\nexport { MotionProvider, useMotionPreference, type MotionProviderProps };\n\nexport type SurfaceFrames = PresenceFrames;\n\nexport type SurfaceTiming = PresenceTiming;\n\nexport type PanelSide = PresenceSide;\n\nexport {\n  delayedTiming,\n  exitWindowMs,\n  fadeFrom,\n  fadeFromAnchor,\n  fadeFromSide,\n  fadeOnly,\n  presenceStyle,\n  transitionWindowMs,\n};\nexport type { PresenceHitRule };\n\nexport { useSurfacePresence, useSurfaceTiming, type SurfacePresence };\n\nexport const moveTiming = (\n  level: MotionLevel,\n  preference: MotionPreference,\n): PresenceTransition => motionTiming(level, { phase: \"standard\", preference });\n\nexport const useMoveTiming = (level: MotionLevel): PresenceTransition =>\n  moveTiming(level, useMotionPreference());\n\nexport const presenceMotion = (\n  frames: SurfaceFrames,\n  presence: SurfacePresence,\n  timing: SurfaceTiming = presence.timing,\n): MotionProps => motionOfPresence(frames, presence.present, timing);\n\nexport const PresenceShield = ({ present }: { readonly present: boolean }) =>\n  present ? null : (\n    <div\n      testId=\"presence-shield\"\n      style={{ position: \"absolute\", top: 0, right: 0, bottom: 0, left: 0 }}\n    />\n  );\n\nexport const presenceTabStop = (\n  present: boolean,\n): { readonly tabIndex?: number } => (present ? {} : { tabIndex: -1 });\n"
    },
    {
      "path": "packages/components/src/internal/recipe.ts",
      "target": "@ui/gpuixui/components/internal/recipe.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { createRecipeCache } from \"../../tw/index.js\";\nimport type { Theme } from \"./theme.js\";\n\nexport const recipeStyle: (recipe: string, theme: Theme) => StyleDesc =\n  createRecipeCache(512);\n"
    },
    {
      "path": "packages/components/src/internal/ref.ts",
      "target": "@ui/gpuixui/components/internal/ref.ts",
      "type": "registry:lib",
      "content": "import type { Ref, RefCallback } from \"react\";\nimport { useCallback } from \"react\";\n\nconst assign = <T>(ref: Ref<T> | undefined, value: T): void => {\n  if (ref === null || ref === undefined) return;\n  if (typeof ref === \"function\") {\n    ref(value);\n    return;\n  }\n  ref.current = value;\n};\n\nexport const useMergedRef = <T>(\n  internal: Ref<T>,\n  caller: Ref<T> | undefined,\n): RefCallback<T> =>\n  useCallback(\n    (value: T) => {\n      assign(internal, value);\n      assign(caller, value);\n    },\n    [internal, caller],\n  );\n"
    },
    {
      "path": "packages/components/src/internal/theme.ts",
      "target": "@ui/gpuixui/components/internal/theme.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport * as Schema from \"effect/Schema\";\nimport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  brandTextOn,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  pointerWash,\n  selectionOn,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n  type BoxShadow,\n  type ElevationLevel,\n  type FindHighlightSource,\n  type NativeTextFill,\n  type PlatformName,\n  type PointerWashSurface,\n  type StatusTone,\n  type SyntaxColors,\n  type SyntaxRole,\n  type TextRole,\n  type Theme,\n  type ThemeColors,\n} from \"../../tokens/index.js\";\n\nexport {\n  alpha,\n  avatarPixels,\n  avatarSize,\n  controlFocusRing,\n  controlHeight,\n  controlPaddingX,\n  currentPlatform,\n  darkTheme,\n  disabledAction,\n  disabledOpacity,\n  elevationShadow,\n  filledHover,\n  filledPressed,\n  findHighlight,\n  focusRing,\n  keyboardFocusRing,\n  lightTheme,\n  nativeTextTheme,\n  statusTone,\n  syntaxColors,\n  targetSize,\n  textStyle,\n};\nexport type {\n  BoxShadow,\n  ElevationLevel,\n  FindHighlightSource,\n  NativeTextFill,\n  PlatformName,\n  StatusTone,\n  SyntaxColors,\n  SyntaxRole,\n  TextRole,\n  Theme,\n};\nexport { ThemeProvider, useTheme } from \"../../tokens/react.js\";\n\nexport const keyboardRing = (theme: Theme, invalid = false): StyleDesc =>\n  controlFocusRing(theme, { invalid }) ?? {};\n\nexport interface ControlRingState {\n  readonly disabled?: boolean;\n\n  readonly invalid?: boolean;\n}\n\nexport const controlRing = (\n  theme: Theme,\n  state: ControlRingState = {},\n): StyleDesc => controlFocusRing(theme, state) ?? {};\n\nexport const pressWash = (theme: Theme, replaced: PointerWashSurface): string =>\n  pointerWash(theme, \"active\", replaced) ?? theme.colors.active;\n\nexport const selectionWash = (theme: Theme): StyleDesc => ({\n  selectionColor: theme.colors.selection,\n});\n\nexport const keepContent: StyleDesc = { keyboardFocusDim: false };\n\nexport const disabledFade = (\n  disabled: boolean,\n): { readonly opacity: number } | undefined =>\n  disabled ? { opacity: disabledOpacity } : undefined;\n\nconst Tones = Schema.Literals([\"info\", \"success\", \"warning\", \"danger\"]);\n\nexport type Tone = typeof Tones.Type;\n\nexport const toneOf = (value: unknown): Tone =>\n  Schema.is(Tones)(value) ? value : \"info\";\n\nexport interface ToneStyle {\n  readonly fg: string;\n  readonly bg: string;\n  readonly border: string;\n}\n\nconst TOKEN_TONE: Readonly<Record<Tone, StatusTone>> = {\n  info: \"info\",\n  success: \"success\",\n  warning: \"warning\",\n  danger: \"destructive\",\n};\n\nexport const tone = (theme: Theme, value: Tone): ToneStyle => {\n  const { background, border, foreground } = statusTone(\n    theme,\n    TOKEN_TONE[value],\n  );\n  return { bg: background, border, fg: foreground };\n};\n\nconst softRoles: Readonly<Record<Tone, keyof ThemeColors>> = {\n  info: \"infoSoft\",\n  success: \"successSoft\",\n  warning: \"warningSoft\",\n  danger: \"destructiveSoft\",\n};\n\nexport const messageLink = (theme: Theme, value: Tone): string =>\n  brandTextOn(theme, softRoles[value]);\n\nexport const messageSelection = (theme: Theme, value: Tone): string =>\n  selectionOn(theme, softRoles[value]);\n\nexport const toneSurface = (theme: Theme, value: Tone): StyleDesc => {\n  const { bg, border, fg } = tone(theme, value);\n  return {\n    backgroundColor: bg,\n    color: fg,\n    borderWidth: theme.borderWidth.thin,\n    borderColor: border,\n  };\n};\n"
    }
  ],
  "docs": "[Drawer](https://gpuixui.sudokw.com/components/drawer) — 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/drawer",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}