{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-time-picker",
  "type": "registry:component",
  "title": "Time picker",
  "description": "A time of day, entered as an HH:mm field",
  "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-primitives.json",
    "https://gpuixui.sudokw.com/gpuix-tokens.json",
    "https://gpuixui.sudokw.com/gpuix-tw.json"
  ],
  "files": [
    {
      "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/key.ts",
      "target": "@ui/gpuixui/components/internal/key.ts",
      "type": "registry:lib",
      "content": "import type { EventPayload, KeyEvent, Props } from \"@gpuix/react\";\nimport * as Option from \"effect/Option\";\n\nexport type KeyPhase = \"down\" | \"up\";\n\nexport const isActivation = (event: EventPayload, phase: KeyPhase): boolean => {\n  if (event.isHeld === true) return false;\n  if (event.key === \"enter\") return phase === \"down\";\n  return event.key === \"space\" && phase === \"up\";\n};\n\nexport const swallowsDefault = (\n  event: EventPayload,\n  phase: KeyPhase,\n): boolean => event.key === \"space\" && phase === \"down\";\n\nexport const activationHandlers = (\n  activate: (event: KeyEvent) => void,\n  observe: Pick<Props, \"onKeyDown\" | \"onKeyUp\"> = {},\n): Pick<Props, \"onKeyDown\" | \"onKeyUp\"> => ({\n  onKeyDown: (event: KeyEvent) => {\n    if (swallowsDefault(event, \"down\")) event.preventDefault();\n    if (isActivation(event, \"down\")) activate(event);\n    observe.onKeyDown?.(event);\n  },\n  onKeyUp: (event: KeyEvent) => {\n    if (isActivation(event, \"up\")) activate(event);\n    observe.onKeyUp?.(event);\n  },\n});\n\nexport const isEnter = (event: EventPayload): boolean => event.key === \"enter\";\n\nexport const isDown = (event: EventPayload): boolean =>\n  event.key === \"down\" || (event.key === \"n\" && event.modifiers?.ctrl === true);\n\nexport const isUp = (event: EventPayload): boolean =>\n  event.key === \"up\" || (event.key === \"p\" && event.modifiers?.ctrl === true);\n\nexport const isRight = (event: EventPayload): boolean => event.key === \"right\";\n\nexport const isLeft = (event: EventPayload): boolean => event.key === \"left\";\n\nexport const isFirst = (event: EventPayload): boolean => event.key === \"home\";\n\nexport const isLast = (event: EventPayload): boolean => event.key === \"end\";\n\nexport const isPageUp = (event: EventPayload): boolean =>\n  event.key === \"pageup\";\n\nexport const isPageDown = (event: EventPayload): boolean =>\n  event.key === \"pagedown\";\n\nexport const isDismiss = (event: EventPayload): boolean =>\n  event.key === \"escape\";\n\nexport const isTab = (event: EventPayload): boolean => event.key === \"tab\";\n\nexport const isSelectAll = (event: EventPayload): boolean =>\n  event.key === \"a\" &&\n  (event.modifiers?.cmd === true || event.modifiers?.ctrl === true);\n\nexport const isMenuKey = (event: EventPayload): boolean =>\n  (event.key === \"f10\" && event.modifiers?.shift === true) ||\n  event.key === \"contextmenu\";\n\nexport const axisDirection = (\n  event: EventPayload,\n  orientation: \"horizontal\" | \"vertical\",\n): Option.Option<1 | -1> => {\n  if (event.key === \"right\") return Option.some(1);\n  if (event.key === \"left\") return Option.some(-1);\n  if (orientation === \"vertical\") {\n    if (isDown(event)) return Option.some(1);\n    if (isUp(event)) return Option.some(-1);\n  }\n  return Option.none();\n};\n"
    },
    {
      "path": "packages/components/src/internal/parts.ts",
      "target": "@ui/gpuixui/components/internal/parts.ts",
      "type": "registry:lib",
      "content": "import * as Arr from \"effect/Array\";\nimport * as P from \"effect/Predicate\";\nimport {\n  Children,\n  Fragment,\n  isValidElement,\n  type ReactElement,\n  type ReactNode,\n} from \"react\";\n\nexport type PartComponent = (props: never) => ReactNode;\n\nexport type PartChildren =\n  | ReactElement<unknown>\n  | boolean\n  | null\n  | undefined\n  | ReadonlyArray<PartChildren>;\n\nexport interface PartsContract {\n  readonly parent: string;\n\n  readonly family: ReadonlyArray<PartComponent>;\n\n  readonly accepts: ReadonlyArray<PartComponent>;\n\n  readonly hint?: string;\n}\n\nconst componentName = (type: unknown): string =>\n  P.isString(type)\n    ? type\n    : P.isFunction(type)\n      ? type.name || \"anonymous\"\n      : \"element\";\n\nconst fragmentChildren = (element: ReactElement<unknown>): ReactNode =>\n  hasChildren(element.props) ? element.props.children : undefined;\n\nconst hasChildren = (\n  value: unknown,\n): value is { readonly children?: ReactNode } =>\n  P.isObject(value) && \"children\" in value;\n\nconst leaves = (children: ReactNode): ReadonlyArray<unknown> =>\n  Arr.flatMap(Children.toArray(children), (child) =>\n    isValidElement<unknown>(child) && child.type === Fragment\n      ? leaves(fragmentChildren(child))\n      : [child],\n  );\n\nexport const assertParts = (\n  contract: PartsContract,\n  children: ReactNode,\n): void => {\n  Arr.forEach(leaves(children), (leaf) => {\n    if (!isValidElement(leaf)) return;\n    if (Arr.some(contract.accepts, (part) => part === leaf.type)) return;\n    if (!Arr.some(contract.family, (part) => part === leaf.type)) return;\n\n    throw new Error(\n      `${componentName(leaf.type)} cannot be used inside ${contract.parent}.` +\n        (contract.hint === undefined ? \"\" : ` ${contract.hint}`),\n    );\n  });\n};\n"
    },
    {
      "path": "packages/components/src/internal/recipe.ts",
      "target": "@ui/gpuixui/components/internal/recipe.ts",
      "type": "registry:lib",
      "content": "import type { StyleDesc } from \"@gpuix/react\";\nimport { createRecipeCache } from \"../../tw/index.js\";\nimport type { Theme } from \"./theme.js\";\n\nexport const recipeStyle: (recipe: string, theme: Theme) => StyleDesc =\n  createRecipeCache(512);\n"
    },
    {
      "path": "packages/components/src/internal/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/time-picker.tsx",
      "target": "@ui/gpuixui/components/time-picker.tsx",
      "type": "registry:component",
      "content": "/** @jsxImportSource @gpuix/react */\nimport type { EventPayload, InputProps, PublicInstance } from \"@gpuix/react\";\nimport { useGpuix } from \"@gpuix/react\";\nimport {\n  mergeStyles,\n  resolveStyle,\n  type StateStyle,\n} from \"@gpuix/react/floating\";\nimport { Icon } from \"../icons/index.js\";\nimport {\n  cn,\n  TIME_PICKER_CLEAR,\n  TIME_PICKER_ROOT,\n  TIME_PICKER_SEGMENT,\n  TIME_PICKER_SEPARATOR,\n} from \"../tw/index.js\";\nimport { Match } from \"effect\";\nimport * as Option from \"effect/Option\";\nimport {\n  createContext,\n  useContext,\n  useLayoutEffect,\n  useRef,\n  useState,\n  type ReactNode,\n} from \"react\";\nimport { useControllableState } from \"./internal/controllable.js\";\nimport {\n  fieldHelp,\n  fieldHint,\n  hintHost,\n  renderFieldHint,\n} from \"../primitives/components/hint.js\";\nimport { activationHandlers } from \"./internal/key.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  addMinutes,\n  clampSegment,\n  composeIsoTime,\n  digitsOf,\n  formatIsoTime,\n  normalizeStep,\n  padSegment,\n  parseIsoTime,\n  type TimeOfDay,\n} from \"./time-picker/time.js\";\nimport {\n  controlHeight,\n  disabledFade,\n  keepContent,\n  keyboardRing,\n  nativeTextTheme,\n  selectionWash,\n  targetSize,\n  useTheme,\n} from \"./internal/theme.js\";\n\nexport type TimeSegment = \"hours\" | \"minutes\";\n\ninterface TimePickerContextValue {\n  readonly hoursText: string;\n  readonly minutesText: string;\n  readonly disabled: boolean;\n  readonly groupLabel: string;\n  readonly testId: string;\n  readonly clearable: boolean;\n  readonly hasValue: boolean;\n  readonly edit: (segment: TimeSegment, raw: string) => void;\n  readonly handleKeyDown: (segment: TimeSegment, event: EventPayload) => void;\n  readonly normalize: () => void;\n  readonly clear: () => void;\n  readonly registerHours: (instance: PublicInstance | null) => void;\n}\n\nconst TimePickerContext = createContext<TimePickerContextValue | undefined>(\n  undefined,\n);\n\nconst useTimePicker = (): TimePickerContextValue => {\n  const value = useContext(TimePickerContext);\n  if (value === undefined) {\n    throw new Error(\"TimePicker parts must be used inside TimePicker\");\n  }\n  return value;\n};\n\nexport interface TimePickerProps {\n  readonly value?: string | null;\n  readonly defaultValue?: string | null;\n\n  readonly onValueChange?: (value: string | null) => void;\n\n  readonly label?: string;\n\n  readonly minuteStep?: number;\n\n  readonly clearable?: boolean;\n  readonly disabled?: boolean;\n\n  readonly testId?: string;\n\n  readonly className?: string;\n  readonly children?: PartChildren;\n}\n\nconst SEGMENT_WIDTH = 26;\n\nexport const TimePicker = ({\n  value,\n  defaultValue,\n  onValueChange,\n  label,\n  minuteStep,\n  clearable = false,\n  disabled = false,\n  testId = \"time-picker\",\n  className,\n  children,\n}: TimePickerProps) => {\n  const theme = useTheme();\n  const renderer = useGpuix().renderer;\n  const step = normalizeStep(minuteStep);\n  const groupLabel = label ?? \"Time\";\n  const initialValue = value !== undefined ? value : defaultValue;\n  const initialTime = Option.flatMap(\n    Option.fromNullishOr(initialValue),\n    parseIsoTime,\n  );\n  const [hoursText, setHoursText] = useState(() =>\n    Option.match(initialTime, {\n      onNone: () => \"\",\n      onSome: (time) => padSegment(time.hours),\n    }),\n  );\n  const [minutesText, setMinutesText] = useState(() =>\n    Option.match(initialTime, {\n      onNone: () => \"\",\n      onSome: (time) => padSegment(time.minutes),\n    }),\n  );\n  const [selected, setSelected] = useControllableState<string | null>({\n    value,\n    defaultValue: defaultValue ?? null,\n    onChange: onValueChange,\n  });\n  const hoursInput = useRef<PublicInstance | null>(null);\n\n  useLayoutEffect(() => {\n    if (Option.getOrNull(composeIsoTime(hoursText, minutesText)) === selected) {\n      return;\n    }\n    Option.match(Option.flatMap(Option.fromNullishOr(selected), parseIsoTime), {\n      onNone: () => {\n        setHoursText(\"\");\n        setMinutesText(\"\");\n      },\n      onSome: (time) => {\n        setHoursText(padSegment(time.hours));\n        setMinutesText(padSegment(time.minutes));\n      },\n    });\n  }, [selected, hoursText, minutesText]);\n\n  const emit = (next: string | null): void => setSelected(next);\n\n  const edit = (segment: TimeSegment, raw: string): void => {\n    if (disabled) return;\n    const digits = digitsOf(raw);\n    if (digits === \"\") {\n      setHoursText(\"\");\n      setMinutesText(\"\");\n      emit(null);\n      return;\n    }\n    const max = segment === \"hours\" ? 23 : 59;\n    const clamped = Option.getOrElse(clampSegment(digits, max), () => 0);\n    const text = Number(digits) > max ? padSegment(clamped) : digits;\n    const nextHours = segment === \"hours\" ? text : hoursText;\n    const nextMinutes = segment === \"minutes\" ? text : minutesText;\n    if (segment === \"hours\") setHoursText(text);\n    else setMinutesText(text);\n    emit(Option.getOrNull(composeIsoTime(nextHours, nextMinutes)));\n  };\n\n  const shift = (delta: number): void => {\n    const from = Option.getOrElse(\n      Option.flatMap(Option.fromNullishOr(selected), parseIsoTime),\n      (): TimeOfDay => ({ hours: 0, minutes: 0 }),\n    );\n    const next = addMinutes(from, delta);\n    setHoursText(padSegment(next.hours));\n    setMinutesText(padSegment(next.minutes));\n    emit(formatIsoTime(next));\n  };\n\n  const handleKeyDown = (segment: TimeSegment, event: EventPayload): void => {\n    if (disabled) return;\n    Match.value(event.key).pipe(\n      Match.when(\"up\", () => shift(segment === \"hours\" ? 60 : step)),\n      Match.when(\"down\", () => shift(segment === \"hours\" ? -60 : -step)),\n      Match.orElse(() => undefined),\n    );\n  };\n\n  const normalize = (): void => {\n    Option.match(Option.flatMap(Option.fromNullishOr(selected), parseIsoTime), {\n      onNone: () => undefined,\n      onSome: (time) => {\n        setHoursText(padSegment(time.hours));\n        setMinutesText(padSegment(time.minutes));\n      },\n    });\n  };\n\n  const clear = (): void => {\n    setHoursText(\"\");\n    setMinutesText(\"\");\n    emit(null);\n    const instance = hoursInput.current;\n    if (instance !== null) renderer?.focusElement?.(instance.id);\n  };\n\n  const hasValue = Option.isSome(\n    Option.flatMap(Option.fromNullishOr(selected), parseIsoTime),\n  );\n\n  assertParts(timePickerRoot, children);\n\n  return (\n    <TimePickerContext.Provider\n      value={{\n        hoursText,\n        minutesText,\n        disabled,\n        groupLabel,\n        testId,\n        clearable,\n        hasValue,\n        edit,\n        handleKeyDown,\n        normalize,\n        clear,\n        registerHours: (instance) => {\n          hoursInput.current = instance;\n        },\n      }}\n    >\n      <div\n        role=\"group\"\n        aria-label={groupLabel}\n        testId={testId}\n        style={mergeStyles(\n          recipeStyle(cn(TIME_PICKER_ROOT, className), theme),\n          {\n            height: controlHeight(theme, \"md\"),\n            ...disabledFade(disabled),\n          },\n        )}\n      >\n        {children}\n      </div>\n    </TimePickerContext.Provider>\n  );\n};\n\nexport interface TimePickerSegmentProps {\n  readonly segment: TimeSegment;\n\n  readonly style?: StateStyle<TimePickerSegmentState>;\n}\n\nexport interface TimePickerSegmentState {\n  readonly disabled: boolean;\n  readonly focused: boolean;\n}\n\nexport const TimePickerSegment = ({\n  segment,\n  style,\n}: TimePickerSegmentProps) => {\n  const theme = useTheme();\n  const [focused, setFocusedSegment] = useState(false);\n  const {\n    hoursText,\n    minutesText,\n    disabled,\n    groupLabel,\n    testId,\n    edit,\n    handleKeyDown,\n    normalize,\n    registerHours,\n  } = useTimePicker();\n  const text = segment === \"hours\" ? hoursText : minutesText;\n  const placeholder = segment === \"hours\" ? \"HH\" : \"MM\";\n  const state: TimePickerSegmentState = { disabled, focused };\n\n  const fieldStyle = mergeStyles(\n    mergeStyles(recipeStyle(TIME_PICKER_SEGMENT, theme), {\n      width: SEGMENT_WIDTH,\n      backgroundColor: undefined,\n      ...(disabled ? {} : keyboardRing(theme)),\n      ...selectionWash(theme),\n      ...keepContent,\n    }),\n    resolveStyle(style, state),\n  );\n  const props: InputProps = {\n    testId: `${testId}-${segment}`,\n    \"aria-label\": `${groupLabel} ${segment}`,\n\n    \"aria-description\": fieldHelp({ placeholder }, text),\n\n    theme: nativeTextTheme(theme, \"input\"),\n    value: text,\n    readOnly: disabled,\n    tabIndex: disabled ? -1 : 0,\n    onChange: (event) => edit(segment, event.value ?? \"\"),\n    onKeyDown: (event) => handleKeyDown(segment, event),\n    onFocus: () => setFocusedSegment(true),\n    onBlur: () => {\n      normalize();\n      setFocusedSegment(false);\n    },\n    style: fieldStyle,\n  };\n  const hint = fieldHint({ placeholder, disabled }, theme, text, {\n    origin: \"host\",\n    align: \"center\",\n    style: fieldStyle,\n  });\n\n  return (\n    <div\n      style={hintHost({\n        width: SEGMENT_WIDTH,\n        alignSelf: \"stretch\",\n      })}\n    >\n      <input\n        {...props}\n        {...(segment === \"hours\" ? { ref: registerHours } : {})}\n      />\n      {Option.match(hint, {\n        onNone: () => null,\n        onSome: (painted) => renderFieldHint(painted, `${testId}-${segment}`),\n      })}\n    </div>\n  );\n};\n\nexport interface TimePickerSeparatorProps {\n  readonly children?: ReactNode;\n}\n\nexport const TimePickerSeparator = ({\n  children = \":\",\n}: TimePickerSeparatorProps) => {\n  const theme = useTheme();\n\n  return (\n    <text role=\"presentation\" style={recipeStyle(TIME_PICKER_SEPARATOR, theme)}>\n      {children}\n    </text>\n  );\n};\n\nexport interface TimePickerClearProps {\n  readonly label?: string;\n}\n\nexport const TimePickerClear = ({\n  label = \"Clear time\",\n}: TimePickerClearProps) => {\n  const theme = useTheme();\n  const { clearable, hasValue, disabled, testId, clear } = useTimePicker();\n  if (!clearable || !hasValue || disabled) return null;\n\n  return (\n    <div\n      role=\"button\"\n      aria-label={label}\n      testId={`${testId}-clear`}\n      tabIndex={0}\n      onClick={clear}\n      {...activationHandlers(clear)}\n      style={mergeStyles(recipeStyle(TIME_PICKER_CLEAR, theme), {\n        ...keyboardRing(theme),\n        width: targetSize.minimum,\n        height: targetSize.minimum,\n        flexShrink: 0,\n        hover: { backgroundColor: theme.colors.accent },\n      })}\n    >\n      <Icon name=\"x\" size=\"sm\" color={theme.colors.mutedForeground} />\n    </div>\n  );\n};\n\nconst timePickerParts: ReadonlyArray<PartComponent> = [\n  TimePicker,\n  TimePickerSegment,\n  TimePickerSeparator,\n  TimePickerClear,\n];\n\nconst timePickerRoot: PartsContract = {\n  parent: \"TimePicker\",\n  family: timePickerParts,\n  accepts: [TimePickerSegment, TimePickerSeparator, TimePickerClear],\n  hint: \"Put the segments, separator and clear control directly inside TimePicker.\",\n};\n\nexport const timePickerMeta = {\n  name: \"Time pickers\",\n  tier: 2,\n  status: \"done\",\n  gpuixSupport: \"yes\",\n  limitations: [\n    \"fixed 24-hour display: no AM/PM and no locale, because the repo has no i18n layer\",\n    \"two labelled text segments instead of a spinbutton: 0.11.0 has no aria-valuenow\",\n    \"minuteStep affects the arrow keys only; typed minutes accept 00..59\",\n    \"disabled is readOnly plus dimming: 0.11.0 has no aria-disabled\",\n    \"a disabled field refuses every edit path — its own guard stops the arrow step, which a read-only host still delivers\",\n    \"no seconds and no timezone: the value is a time of day, not an instant\",\n  ],\n  docs: \"docs/time-picker.mdx\",\n  example: \"examples/time-picker.example.tsx\",\n  testFile: \"src/behaviour/time-picker.test.tsx\",\n} as const;\n"
    },
    {
      "path": "packages/components/src/time-picker/time.ts",
      "target": "@ui/gpuixui/components/time-picker/time.ts",
      "type": "registry:lib",
      "content": "import { pipe } from \"effect\";\nimport * as Option from \"effect/Option\";\nimport * as Str from \"effect/String\";\n\nexport interface TimeOfDay {\n  readonly hours: number;\n  readonly minutes: number;\n}\n\nconst ISO_TIME = /^([01]\\d|2[0-3]):([0-5]\\d)$/;\nconst SEGMENT = /^\\d{1,2}$/;\nconst DIGITS = /^\\d+$/;\n\nexport const parseIsoTime = (value: string): Option.Option<TimeOfDay> => {\n  const match = ISO_TIME.exec(value);\n  if (match === null) return Option.none();\n  const hours = match[1];\n  const minutes = match[2];\n  if (hours === undefined || minutes === undefined) return Option.none();\n  return Option.some({ hours: Number(hours), minutes: Number(minutes) });\n};\n\nexport const formatIsoTime = (time: TimeOfDay): string =>\n  `${padSegment(time.hours)}:${padSegment(time.minutes)}`;\n\nexport const addMinutes = (time: TimeOfDay, amount: number): TimeOfDay => {\n  const total =\n    (((time.hours * 60 + time.minutes + amount) % 1440) + 1440) % 1440;\n  return { hours: Math.floor(total / 60), minutes: total % 60 };\n};\n\nexport const parseSegment = (\n  text: string,\n  max: number,\n): Option.Option<number> =>\n  SEGMENT.test(text) && Number(text) <= max\n    ? Option.some(Number(text))\n    : Option.none();\n\nexport const clampSegment = (\n  text: string,\n  max: number,\n): Option.Option<number> =>\n  DIGITS.test(text) ? Option.some(Math.min(Number(text), max)) : Option.none();\n\nexport const padSegment = (value: number): string =>\n  pipe(String(value), Str.padStart(2, \"0\"));\n\nexport const normalizeStep = (step: number | undefined): number =>\n  step === undefined || !Number.isFinite(step)\n    ? 1\n    : Math.min(59, Math.max(1, Math.floor(step)));\n\nexport const digitsOf = (raw: string): string =>\n  pipe(raw, Str.replace(/\\D/g, \"\"), Str.takeRight(2));\n\nconst segment = (text: string, max: number): Option.Option<number> =>\n  text === \"\" ? Option.some(0) : parseSegment(text, max);\n\nexport const composeIsoTime = (\n  hours: string,\n  minutes: string,\n): Option.Option<string> =>\n  hours === \"\" && minutes === \"\"\n    ? Option.none()\n    : pipe(\n        Option.all([segment(hours, 23), segment(minutes, 59)]),\n        Option.map(([hour, minute]) =>\n          formatIsoTime({ hours: hour, minutes: minute }),\n        ),\n      );\n"
    }
  ],
  "docs": "[Time picker](https://gpuixui.sudokw.com/components/time-picker) — 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/time-picker",
    "kind": "component",
    "runtime": "gpuix",
    "registry": "gpuixui"
  }
}