{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"input","title":"Input and Field","description":"Base UI-backed text input and accessible field boundary.","dependencies":["@base-ui/react@1.7.0"],"registryDependencies":["@siteplane/base","@siteplane/motion","@siteplane/tooltip"],"files":[{"path":"packages/ui/src/components/input/input.tsx","content":"\"use client\";\n\nimport { Input as InputPrimitive } from \"@base-ui/react/input\";\nimport * as React from \"react\";\n\nimport {\n  type SiteplaneControlSize,\n  useResolvedControlSize,\n} from \"../../system/provider/siteplane-ui-provider\";\nimport {\n  formControlSurfaceClassNames,\n  formControlTextClassNames,\n  surfaceClassNames,\n  type FormControlTextSize,\n  type FormControlVariant,\n} from \"../../system/styles/surface\";\nimport { cn, type WithStringClassName } from \"../../system/utils/cn\";\nimport { dropSlotClass, slotClass } from \"../../system/utils/slot-class\";\nimport { useFieldLabelPlacement } from \"../field\";\nimport type { KitInputProps } from \"./input-internal\";\n\nimport \"./input.css\";\n\ntype InputRenderElementProps = React.ComponentProps<\"input\"> & {\n  \"data-slot\"?: string;\n};\n\nexport type InputProps = Omit<\n  WithStringClassName<InputPrimitive.Props> &\n    React.RefAttributes<HTMLInputElement>,\n  \"size\"\n> & {\n  size?: SiteplaneControlSize | number;\n  textSize?: FormControlTextSize;\n  variant?: FormControlVariant;\n  unstyled?: boolean;\n  nativeInput?: boolean;\n  renderInput?: React.ReactElement<InputRenderElementProps>;\n  hidePickerIndicator?: boolean;\n  insetStart?: boolean | undefined;\n  insetEnd?: boolean | undefined;\n};\n\nconst ExternalInputOwnerContext = React.createContext(false);\n\n/**\n * Kit-internal boundary; the component index does not export it. Components\n * whose own behavior owner already connects the input to its field (Base UI\n * Combobox and Autocomplete inputs, React Aria color fields, hidden form\n * adapters) render the Input chassis inside it. There, `nativeInput` and\n * `renderInput` pass the element through unchanged instead of registering it\n * with an enclosing Field a second time.\n */\nexport function ExternalInputOwner({\n  children,\n}: {\n  children?: React.ReactNode;\n}): React.ReactElement {\n  return (\n    <ExternalInputOwnerContext.Provider value>\n      {children}\n    </ExternalInputOwnerContext.Provider>\n  );\n}\n\nexport function Input(inputProps: InputProps): React.ReactElement {\n  // Kit components pass the internal profiles through KitInput\n  // (input-internal.ts), the same function with the wider props type.\n  const {\n    className,\n    size,\n    textSize,\n    variant = \"default\",\n    unstyled = false,\n    nativeInput = false,\n    renderInput,\n    hidePickerIndicator = false,\n    insetEnd = false,\n    insetStart = false,\n    profile = \"default\",\n    profileMode,\n    style,\n    ...props\n  } = inputProps as KitInputProps;\n  const externalOwner = React.useContext(ExternalInputOwnerContext);\n  const externalElement = externalOwner && Boolean(renderInput || nativeInput);\n  // A floating Field lays out the text field it holds (design-rework.css,\n  // \"Floating label fields\"). A hidden or file input keeps its own layout, and\n  // so does an element another owner renders, such as the hidden proxy of a\n  // segmented date field, unless it is the input of a Combobox or Autocomplete.\n  // A floating field takes no leading icon, so every label and value in a form\n  // starts on one line: an input whose start edge is inset for an adjoining\n  // element, such as an icon, a Combobox or Autocomplete start addon or an\n  // InputGroup addon, keeps the label above it.\n  const floating =\n    useFieldLabelPlacement() === \"floating\" &&\n    !insetStart &&\n    props.type !== \"hidden\" &&\n    props.type !== \"file\" &&\n    (!externalElement ||\n      (profile === \"combobox\" && profileMode !== \"embedded\"));\n  // A blank placeholder lets CSS tell an empty field (:placeholder-shown), so\n  // the label rests in an empty field and floats over a value before hydration.\n  const floatingPlaceholder =\n    floating && props.placeholder === undefined ? { placeholder: \" \" } : {};\n  const visualSize = typeof size === \"number\" ? undefined : size;\n  const nativeSize = typeof size === \"number\" ? size : undefined;\n  const resolvedSize = useResolvedControlSize(visualSize);\n  const resolvedTextSize =\n    textSize ??\n    (resolvedSize === \"lg\" || resolvedSize === \"xl\" ? \"base\" : undefined);\n  // The native input fills its control: the field has no border, so a height\n  // per size left a 2 px strip under it, or overflowed below 640 px. Its\n  // height, radius, inline padding, line, placeholder and disabled cursor are\n  // the kit base of every Input (design-rework.css).\n  const inputClassName = cn(\n    \"w-full min-w-0 outline-none\",\n    props.type === \"search\" &&\n      \"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none\",\n    hidePickerIndicator && \"sp-input-hide-picker\",\n    props.type === \"date\" && \"sp-date-input\",\n  );\n  const nativeInputProps = props as React.ComponentProps<\"input\">;\n  const nativeInputStyle =\n    typeof style !== \"function\" && style !== undefined ? { style } : {};\n\n  // A hidden input only carries form data. The Legacy wrapper would otherwise\n  // produce an empty visible surface.\n  if (props.type === \"hidden\") {\n    return <input {...nativeInputProps} type=\"hidden\" {...nativeInputStyle} />;\n  }\n\n  return (\n    <span\n      className={\n        cn(\n          \"sp-input-control sp-base-input\",\n          !unstyled &&\n            (variant === \"soft\"\n              ? surfaceClassNames.formControlSoft\n              : cn(\n                  surfaceClassNames.formControl,\n                  formControlSurfaceClassNames[variant],\n                )),\n          resolvedTextSize && formControlTextClassNames[resolvedTextSize],\n          profile === \"sidebar\" && \"w-full\",\n          // A host's slot reaches the native input with the other props,\n          // so its class stays off the control.\n          dropSlotClass(className, props),\n        ) || undefined\n      }\n      data-foundation=\"input\"\n      data-inset-end={insetEnd || undefined}\n      data-inset-start={insetStart || undefined}\n      data-label-placement={floating ? \"floating\" : undefined}\n      data-profile={profile}\n      data-profile-mode={profileMode}\n      data-size={resolvedSize}\n      data-slot=\"input-control\"\n      // Only an explicit textSize replaces the size-based field text.\n      data-text-size={textSize}\n      data-unstyled={unstyled || undefined}\n      data-variant={variant}\n    >\n      {externalElement ? (\n        // The surrounding behavior owner already supplies id, labelling,\n        // description and validation for this element.\n        React.cloneElement(renderInput ?? <input />, {\n          ...nativeInputProps,\n          ...floatingPlaceholder,\n          className: cn(\n            \"sp-input\",\n            inputClassName,\n            renderInput?.props.className,\n          ),\n          \"data-slot\": \"input\",\n          size: nativeSize,\n          ...nativeInputStyle,\n        })\n      ) : (\n        // `nativeInput` and `renderInput` use the same Field control as the\n        // default input, so FieldLabel, FieldDescription, FieldError and Field\n        // state reach the rendered element.\n        <InputPrimitive\n          className={\n            renderInput\n              ? undefined\n              : cn(slotClass(\"input\", props), inputClassName)\n          }\n          data-slot=\"input\"\n          size={nativeSize}\n          style={style}\n          {...props}\n          {...floatingPlaceholder}\n          render={\n            renderInput\n              ? React.cloneElement(renderInput, {\n                  className: cn(\n                    \"sp-input\",\n                    inputClassName,\n                    renderInput.props.className,\n                  ),\n                  \"data-slot\": \"input\",\n                })\n              : props.render\n          }\n        />\n      )}\n    </span>\n  );\n}\n\nexport { InputPrimitive };\n","type":"registry:ui","target":"~/src/siteplane/components/input/input.tsx"},{"path":"packages/ui/src/components/input/input-internal.ts","content":"import type * as React from \"react\";\n\nimport { Input, type InputProps } from \"./input\";\n\n/*\n * Kit-internal Input. Not public API: the input index does not export this\n * module, and its names and profiles may change in any release. Command,\n * Combobox, Autocomplete, InputGroup and Sidebar render their input with these\n * composition profiles.\n */\nexport type InputProfile =\n  \"command\" | \"combobox\" | \"default\" | \"input-group\" | \"sidebar\";\nexport type InputProfileMode = \"default\" | \"embedded\";\n\nexport type KitInputProps = InputProps & {\n  profile?: InputProfile;\n  profileMode?: InputProfileMode;\n};\n\n/** Input with the kit's composition profiles; the same component. */\nexport const KitInput = Input as (props: KitInputProps) => React.ReactElement;\n","type":"registry:ui","target":"~/src/siteplane/components/input/input-internal.ts"},{"path":"packages/ui/src/components/input/input.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  .sp-input-control:not([data-unstyled]) {\n    --sp-input-segment-background: color-mix(\n      in srgb,\n      var(--color-blue-500) 16%,\n      transparent\n    );\n    --sp-input-segment-padding-block: calc(var(--spacing) * 0.5);\n    --sp-input-segment-padding-inline: var(--spacing);\n    --sp-input-surface-subtle: rgb(0 0 0 / 4%);\n    --sp-semantic-color-background: var(--color-white);\n    --sp-semantic-color-border: rgb(0 0 0 / 8%);\n    --sp-semantic-color-destructive: var(--color-red-500);\n    --sp-semantic-color-foreground: var(--color-neutral-800);\n    --sp-semantic-color-input: rgb(0 0 0 / 10%);\n    --sp-semantic-color-muted-foreground: color-mix(\n      in srgb,\n      var(--color-neutral-500) 90%,\n      var(--color-black)\n    );\n    --sp-semantic-color-ring: var(--color-neutral-400);\n  }\n\n  .sp-base-input[data-profile=\"input-group\"] {\n    display: contents;\n  }\n\n  .sp-base-input[data-profile=\"input-group\"]::before {\n    display: none;\n  }\n\n  /* Embedded Combobox inputs keep the public Input layout contract while the\n   * surrounding popup owns the visible surface and focus treatment. */\n  .sp-base-input[data-profile=\"combobox\"][data-profile-mode=\"embedded\"] {\n    position: relative;\n    display: inline-flex;\n    width: 100%;\n    min-width: 0;\n    height: var(--sp-rw-control-md);\n  }\n\n  .sp-base-input[data-profile=\"combobox\"][data-profile-mode=\"embedded\"][data-size=\"sm\"] {\n    height: var(--sp-rw-control-sm);\n  }\n\n  .sp-base-input[data-profile=\"combobox\"][data-profile-mode=\"embedded\"][data-size=\"lg\"] {\n    height: var(--sp-rw-control-lg);\n  }\n\n  .sp-base-input[data-profile=\"combobox\"][data-profile-mode=\"embedded\"][data-size=\"xl\"] {\n    height: var(--sp-rw-control-xl);\n  }\n\n  .sp-base-input[data-profile=\"input-group\"] .sp-input {\n    flex: 1 1 0%;\n    height: var(--sp-composite-control-height, 100%);\n    border-radius: 0;\n    padding-block-start: var(--sp-composite-control-padding-block-start, 0);\n    padding-block-end: var(--sp-composite-control-padding-block-end, 0);\n    padding-inline-start: var(\n      --sp-composite-control-padding-inline-start,\n      var(--sp-input-padding-inline, calc(var(--spacing) * 3))\n    );\n    padding-inline-end: var(\n      --sp-composite-control-padding-inline-end,\n      var(--sp-input-padding-inline, calc(var(--spacing) * 3))\n    );\n  }\n\n  .dark .sp-input-control:not([data-unstyled]),\n  [data-theme=\"dark\"] .sp-input-control:not([data-unstyled]) {\n    --sp-input-surface-subtle: rgb(255 255 255 / 4%);\n    --sp-semantic-color-background: color-mix(\n      in srgb,\n      var(--color-neutral-950) 95%,\n      var(--color-white)\n    );\n    --sp-semantic-color-border: rgb(255 255 255 / 6%);\n    --sp-semantic-color-destructive: color-mix(\n      in srgb,\n      var(--color-red-500) 90%,\n      var(--color-white)\n    );\n    --sp-semantic-color-foreground: var(--color-neutral-100);\n    --sp-semantic-color-input: rgb(255 255 255 / 8%);\n    --sp-semantic-color-muted-foreground: color-mix(\n      in srgb,\n      var(--color-neutral-500) 90%,\n      var(--color-white)\n    );\n    --sp-semantic-color-ring: var(--color-neutral-500);\n  }\n\n  .sp-input-control:not([data-unstyled])[data-variant=\"soft\"] {\n    background-color: var(--sp-input-surface-subtle);\n  }\n\n  .sp-input-control:not([data-unstyled])[data-variant=\"soft\"]:focus-within {\n    background-color: var(--sp-semantic-color-background);\n  }\n\n  .sp-input-control:not([data-unstyled]):has(:disabled) {\n    background-color: var(--sp-input-surface-subtle);\n  }\n\n  .sp-input-control:not([data-unstyled]):has(:autofill) {\n    background-color: color-mix(\n      in oklab,\n      var(--sp-semantic-color-foreground) 4%,\n      transparent\n    );\n  }\n\n  :is(.dark, [data-theme=\"dark\"])\n    .sp-input-control:not([data-unstyled]):has(:autofill) {\n    background-color: color-mix(\n      in oklab,\n      var(--sp-semantic-color-foreground) 8%,\n      transparent\n    );\n  }\n\n  .sp-input-hide-picker::-webkit-calendar-picker-indicator {\n    display: none;\n  }\n\n  .sp-date-input {\n    --input-segment-background: var(--sp-input-segment-background);\n    --input-segment-padding-block: var(--sp-input-segment-padding-block);\n    --input-segment-padding-inline: var(--sp-input-segment-padding-inline);\n  }\n\n  .sp-date-input::-webkit-calendar-picker-indicator {\n    cursor: pointer;\n  }\n\n  .sp-date-input::-webkit-datetime-edit-day-field,\n  .sp-date-input::-webkit-datetime-edit-month-field,\n  .sp-date-input::-webkit-datetime-edit-year-field {\n    border-radius: var(--radius-md);\n    padding-block: var(--input-segment-padding-block);\n    padding-inline: var(--input-segment-padding-inline);\n  }\n\n  .sp-date-input::-webkit-datetime-edit-day-field:focus,\n  .sp-date-input::-webkit-datetime-edit-month-field:focus,\n  .sp-date-input::-webkit-datetime-edit-year-field:focus {\n    background-color: var(--input-segment-background);\n  }\n\n  /* One rule per selector: Chrome rejects a ::selection after another\n   * pseudo-element, and one selector it rejects drops a whole list, so the\n   * input's own selection rule stands alone. */\n  .sp-date-input::selection {\n    background-color: transparent;\n  }\n\n  .sp-date-input::-webkit-datetime-edit-day-field::selection {\n    background-color: transparent;\n  }\n\n  .sp-date-input::-webkit-datetime-edit-month-field::selection {\n    background-color: transparent;\n  }\n\n  .sp-date-input::-webkit-datetime-edit-year-field::selection {\n    background-color: transparent;\n  }\n\n  /* Embedded search headers: Input owns sizing, typography and native states. */\n  :root .sp-base-input[data-profile=\"command\"] {\n    display: flex;\n    width: 100%;\n    min-width: 0;\n    height: var(--sp-rw-line-24);\n  }\n  :root .sp-base-input[data-profile=\"command\"] .sp-input {\n    flex: 1;\n    height: var(--sp-rw-line-24);\n    padding: 0;\n    font-size: var(--sp-rw-font-16);\n    line-height: var(--sp-rw-line-24);\n  }\n  :root .sp-base-input[data-profile=\"command\"]:has([aria-invalid=\"true\"]) {\n    outline: 1px solid var(--destructive);\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/input/input.css"},{"path":"packages/ui/src/components/input/index.ts","content":"export { Input, InputPrimitive } from \"./input\";\nexport type { InputProps } from \"./input\";\n","type":"registry:ui","target":"~/src/siteplane/components/input/index.ts"},{"path":"packages/ui/src/components/field/field.tsx","content":"\"use client\";\n\nimport { Field as FieldPrimitive } from \"@base-ui/react/field\";\nimport { DEFAULT_VALIDITY_STATE } from \"@base-ui/react/internals/field-constants\";\nimport { useFieldRootContext } from \"@base-ui/react/internals/field-root-context\";\nimport { useFormContext } from \"@base-ui/react/internals/form-context\";\nimport { LabelableProvider } from \"@base-ui/react/internals/labelable-provider\";\nimport * as React from \"react\";\n\nimport { useFormReset } from \"../../system/form/use-form-reset\";\nimport { useMediaQuery } from \"../../system/hooks/use-media-query\";\nimport { cn, type WithStringClassName } from \"../../system/utils/cn\";\nimport { slotClass } from \"../../system/utils/slot-class\";\nimport { MotionReveal } from \"../motion\";\nimport { Tooltip, TooltipPopup } from \"../tooltip\";\nimport {\n  type FieldErrorState,\n  FieldErrorStateProvider,\n  isFieldErrorShown,\n  useFieldErrorState,\n} from \"./field-error-presence\";\n\nexport type FieldLabelPlacement = \"top\" | \"floating\";\n\nconst FieldLabelPlacementContext =\n  React.createContext<FieldLabelPlacement>(\"top\");\n\n/**\n * Where the enclosing Field draws its label: \"top\" above the control (the\n * default, and outside a Field) or \"floating\" inside it. Controls read it to\n * lay themselves out around a floating label.\n */\nexport function useFieldLabelPlacement(): FieldLabelPlacement {\n  return React.useContext(FieldLabelPlacementContext);\n}\n\n/** Whether focus is in the enclosing Field (see `fieldHoldsFocus`). */\nconst FieldFocusContext = React.createContext(false);\n\n/**\n * Focus is in a field while it is in its control, in an action inside it such\n * as a password's show/hide button, or in an open popup of it, whose trigger\n * inside the field is then expanded.\n */\nfunction fieldHoldsFocus(field: HTMLElement): boolean {\n  return (\n    field.contains(field.ownerDocument.activeElement) ||\n    field.querySelector('[aria-expanded=\"true\"]') !== null\n  );\n}\n\n/**\n * The space between a Field's label, control, description and error is\n * `--field-gap` (8 px). FieldError reads the same value, so the space it adds\n * opens and closes with the error instead of appearing in one step.\n *\n * `labelPlacement=\"floating\"` draws the label inside the control: at rest it\n * stands in for the placeholder, and it glides to the top once the field has\n * focus or a value (design-rework.css, \"Floating label fields\").\n *\n * The Field follows whether focus is in it (`fieldHoldsFocus`): its error\n * tooltip shows only then, so one bubble shows at a time. With\n * `validationMode=\"onBlur\"`, from the Field or its Form, leaving the field\n * validates it in full, so a required field that is left empty warns. Base UI\n * reports a missing value only once the value has changed; the Field asks Base\n * UI's own validate action instead.\n *\n * `requiredIndicator` marks the label of a required field with a red asterisk\n * (design-rework.css). It is a choice per form; the control's own `required`\n * stays the one source, so the asterisk shows only while the control is\n * required.\n *\n * A native reset of the form around the Field returns it to how it was before\n * the form's first submit (FieldFormReset).\n */\nexport function Field({\n  actionsRef,\n  children,\n  className,\n  labelPlacement = \"top\",\n  onBlur,\n  onFocus,\n  ref,\n  requiredIndicator = false,\n  ...props\n}: WithStringClassName<FieldPrimitive.Root.Props> & {\n  labelPlacement?: FieldLabelPlacement;\n  /** Marks the label with a red asterisk while the control is required. */\n  requiredIndicator?: boolean | undefined;\n}): React.ReactElement {\n  const formValidationMode = useFormContext().validationMode;\n  const validatesOnBlur =\n    (props.validationMode ?? formValidationMode) === \"onBlur\";\n  const actions = React.useRef<FieldPrimitive.Root.Actions | null>(null);\n  // The caller's actionsRef reaches the same validate action.\n  React.useImperativeHandle(\n    actionsRef,\n    () => ({ validate: () => actions.current?.validate() }),\n    [],\n  );\n\n  // Whether focus is in the field, and what leaving it does.\n  const fieldRef = React.useRef<HTMLDivElement | null>(null);\n  const [focused, setFocused] = React.useState(false);\n  const focusedRef = React.useRef(false);\n  const validatesOnBlurRef = React.useRef(validatesOnBlur);\n  React.useLayoutEffect(() => {\n    validatesOnBlurRef.current = validatesOnBlur;\n  });\n  const follow = React.useCallback((holds: boolean) => {\n    const left = focusedRef.current && !holds;\n    focusedRef.current = holds;\n    setFocused(holds);\n    if (left && validatesOnBlurRef.current) actions.current?.validate();\n  }, []);\n  // Wait until focus has settled: a field action keeps it inside the field,\n  // and an opening popup marks its trigger expanded.\n  const settle = React.useCallback(() => {\n    requestAnimationFrame(() => {\n      const field = fieldRef.current;\n      if (field) follow(fieldHoldsFocus(field));\n    });\n  }, [follow]);\n  // A control that has focus before hydration, such as an autofocused one.\n  React.useLayoutEffect(() => {\n    const field = fieldRef.current;\n    if (field && fieldHoldsFocus(field)) follow(true);\n  }, [follow]);\n  // While focus is in the field, a popup of it can close with focus elsewhere,\n  // so the field follows the document's focus moves until focus has left.\n  React.useEffect(() => {\n    if (!focused) return undefined;\n    const document = fieldRef.current?.ownerDocument;\n    if (!document) return undefined;\n    document.addEventListener(\"focusin\", settle);\n    document.addEventListener(\"focusout\", settle);\n    return () => {\n      document.removeEventListener(\"focusin\", settle);\n      document.removeEventListener(\"focusout\", settle);\n    };\n  }, [focused, settle]);\n  // The reset is cleared inside the Field root, where its state lives.\n  const resetField = React.useRef<(() => void) | null>(null);\n  const formResetRef = useFormReset<HTMLDivElement>(undefined, () =>\n    resetField.current?.(),\n  );\n  const setFieldRef = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      fieldRef.current = element;\n      formResetRef.current = element;\n      if (typeof ref === \"function\") ref(element);\n      else if (ref) ref.current = element;\n    },\n    [formResetRef, ref],\n  );\n\n  return (\n    <FieldLabelPlacementContext.Provider value={labelPlacement}>\n      <FieldFocusContext.Provider value={focused}>\n        <FieldPrimitive.Root\n          actionsRef={actions}\n          className={cn(\n            // min-w-0: in a grid or flex form a Field never grows past its\n            // track for a long value, such as a Select's nowrap trigger.\n            slotClass(\"field\", props),\n            \"flex min-w-0 flex-col items-start gap-(--field-gap) [--field-gap:calc(var(--spacing)*2)]\",\n            className,\n          )}\n          data-label-placement={\n            labelPlacement === \"floating\" ? \"floating\" : undefined\n          }\n          data-required-indicator={requiredIndicator ? \"\" : undefined}\n          data-slot=\"field\"\n          onBlur={(event) => {\n            onBlur?.(event);\n            settle();\n          }}\n          onFocus={(event) => {\n            onFocus?.(event);\n            follow(true);\n          }}\n          ref={setFieldRef}\n          {...props}\n        >\n          <FieldFormReset field={fieldRef} handle={resetField} />\n          {children}\n        </FieldPrimitive.Root>\n      </FieldFocusContext.Provider>\n    </FieldLabelPlacementContext.Provider>\n  );\n}\n\n/**\n * A native reset puts the form's values back to their defaults, and Base UI\n * keeps the Field's validity, its touched and dirty marks and the Form's\n * submitted state as they were; it does not follow a reset. The reset clears\n * them, so the Field is as it was before the form's first submit: its error\n * collapses as when the value becomes valid, its controls drop a custom\n * validity message, and the Form validates again on its next submit. Errors\n * the application passes to Form clear with the same reset (Form). It renders\n * nothing.\n */\nfunction FieldFormReset({\n  field: fieldElement,\n  handle,\n}: {\n  field: React.RefObject<HTMLDivElement | null>;\n  handle: React.RefObject<(() => void) | null>;\n}): null {\n  const field = useFieldRootContext(false);\n  const form = useFormContext();\n  React.useLayoutEffect(() => {\n    handle.current = () => {\n      form.submitAttemptedRef.current = false;\n      for (const control of fieldElement.current?.querySelectorAll<\n        HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement\n      >(\"input, select, textarea\") ?? [])\n        control.setCustomValidity(\"\");\n      field.setValidityData((data) => ({\n        ...data,\n        error: \"\",\n        errors: [],\n        state: DEFAULT_VALIDITY_STATE,\n        value: data.initialValue,\n      }));\n      field.setTouched(false);\n      field.setDirty(false);\n    };\n  });\n  return null;\n}\n\n/**\n * A label above a field sits on the 16 px line; one beside a checkbox, radio\n * or switch (holding it, next to it or leading the text column next to it)\n * takes the 21 px body line, since such labels are sentences and wrap (S13).\n * Label and FieldLabel share that rule in design-rework.css. A label of text\n * alone lays out as a block, so a required asterisk follows its last word when\n * it wraps.\n */\nexport function FieldLabel({\n  className,\n  ...props\n}: WithStringClassName<FieldPrimitive.Label.Props>): React.ReactElement {\n  return (\n    <FieldPrimitive.Label\n      className={cn(\n        slotClass(\"field-label\", props),\n        \"items-center gap-2\",\n        className,\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nexport function FieldItem({\n  className,\n  ...props\n}: WithStringClassName<FieldPrimitive.Item.Props>): React.ReactElement {\n  return (\n    <FieldPrimitive.Item\n      className={cn(slotClass(\"field-item\", props), \"flex\", className)}\n      data-slot=\"field-item\"\n      {...props}\n    />\n  );\n}\n\nexport function FieldDescription({\n  className,\n  ...props\n}: WithStringClassName<FieldPrimitive.Description.Props>): React.ReactElement {\n  return (\n    <FieldPrimitive.Description\n      className={cn(\n        // Under the label text when the label holds a choice control (S13).\n        slotClass(\"field-description\", props),\n        \"ps-[var(--field-choice-indent,0px)] text-muted-foreground\",\n        className,\n      )}\n      data-slot=\"field-description\"\n      {...props}\n    />\n  );\n}\n\nexport type FieldErrorVariant = \"inline\" | \"tooltip\";\n\n/**\n * The message of a failed validation. It expands with a fade when it appears\n * and collapses while it still shows its last message when it goes, so the\n * form below it never jumps. Showing and hiding follow Base UI's Field.Error.\n *\n * The block's negative margin takes back the Field's gap and the message's own\n * padding gives it again, so the space opens and closes with the block.\n *\n * `variant=\"tooltip\"` shows the same message in a bubble beside the control\n * instead, so nothing below it moves. It is the default in a floating Field,\n * and `inline` everywhere else.\n */\nexport function FieldError({\n  className,\n  match,\n  variant,\n  ...props\n}: WithStringClassName<FieldPrimitive.Error.Props> & {\n  /**\n   * `inline` reveals the message below the control; `tooltip` shows it in a\n   * destructive bubble beside the control, out of the flow. The default is\n   * `tooltip` in a floating Field and `inline` otherwise.\n   */\n  variant?: FieldErrorVariant | undefined;\n}): React.ReactElement {\n  const state = useFieldErrorState();\n  const placement = useFieldLabelPlacement();\n  const resolvedVariant =\n    variant ?? (placement === \"floating\" ? \"tooltip\" : \"inline\");\n\n  if (resolvedVariant === \"tooltip\")\n    return (\n      <FieldErrorTooltip\n        className={className}\n        shown={isFieldErrorShown(match, state)}\n        state={state}\n        {...props}\n      />\n    );\n\n  return (\n    <MotionReveal\n      className=\"-mt-(--field-gap)\"\n      open={isFieldErrorShown(match, state)}\n    >\n      <FieldErrorStateProvider {...state}>\n        {/* The motion component decides whether the error is shown, so the\n            element itself always is. */}\n        <FieldPrimitive.Error\n          className={cn(\n            // Under the label text when the label holds a choice control (S13).\n            slotClass(\"field-error\", props),\n            \"ps-[var(--field-choice-indent,0px)] pt-(--field-gap) text-destructive-foreground text-xs\",\n            className,\n          )}\n          data-slot=\"field-error\"\n          match\n          {...props}\n        />\n      </FieldErrorStateProvider>\n    </MotionReveal>\n  );\n}\n\n/** Parts of a Field that are not its control. */\nconst fieldParts = new Set([\n  \"field-error-anchor\",\n  \"field-error-description\",\n  \"field-description\",\n  \"field-label\",\n  \"motion-reveal\",\n]);\n\n/**\n * The visible control of the Field around `element`: its first child that is\n * no label, description, error or bare input (such as a Select's hidden form\n * input), or the Field itself.\n */\nfunction fieldControlOf(element: Element | null): Element | null {\n  const field = element?.closest('[data-slot=\"field\"]') ?? null;\n  if (!field) return null;\n  for (const child of Array.from(field.children)) {\n    if (\n      child instanceof HTMLInputElement ||\n      child.hasAttribute(\"hidden\") ||\n      fieldParts.has(child.getAttribute(\"data-slot\") ?? \"\")\n    )\n      continue;\n    return child;\n  }\n  return field;\n}\n\n/**\n * The tooltip variant of FieldError: the kit Tooltip, anchored to the Field's\n * control, in the destructive tone with an arrow. It shows while the error is\n * shown and focus is in the field, so at most one bubble shows at a time, as\n * the browser's own validation bubble does; other invalid fields show the\n * invalid frame. It sits at the control's inline end, centred, or below it,\n * start-aligned, on a narrow screen; Base UI places it and flips it when there\n * is no room. It never takes the pointer or focus. The bubble keeps the message\n * of its last shown render while it fades out.\n *\n * The control's error description is a visually hidden Field.Error beside the\n * anchor, there whenever the error is shown, with focus in the field or not;\n * the bubble only presents the same message, so it registers no description\n * of its own and is hidden from assistive technology.\n */\nfunction FieldErrorTooltip({\n  className,\n  shown,\n  state,\n  ...props\n}: WithStringClassName<FieldPrimitive.Error.Props> & {\n  shown: boolean;\n  state: FieldErrorState;\n}): React.ReactElement {\n  // The description keeps a given id; the bubble takes its own.\n  const bubbleProps = { ...props, id: undefined };\n  const anchorRef = React.useRef<HTMLSpanElement>(null);\n  const focused = React.useContext(FieldFocusContext);\n  const beside = useMediaQuery(\"sm\");\n  const [heldState, setHeldState] = React.useState(state);\n  if (\n    shown &&\n    (heldState.field !== state.field || heldState.form !== state.form)\n  )\n    setHeldState(state);\n\n  return (\n    <>\n      {/* Finds the Field in the document; the bubble itself is portalled. */}\n      <span\n        className=\"sp-field-error-anchor\"\n        data-slot=\"field-error-anchor\"\n        hidden\n        ref={anchorRef}\n      />\n      {shown ? (\n        <FieldPrimitive.Error\n          className={cn(slotClass(\"field-error-description\", props), \"sr-only\")}\n          data-slot=\"field-error-description\"\n          match\n          {...props}\n        />\n      ) : null}\n      <Tooltip open={shown && focused}>\n        <TooltipPopup\n          align={beside ? \"center\" : \"start\"}\n          anchor={() => fieldControlOf(anchorRef.current)}\n          arrow\n          interactive={false}\n          side={beside ? \"inline-end\" : \"bottom\"}\n          tone=\"error\"\n        >\n          <FieldErrorStateProvider {...heldState}>\n            {/* Its own labelable scope keeps the bubble out of the control's\n                description. */}\n            <LabelableProvider>\n              <FieldPrimitive.Error\n                aria-hidden=\"true\"\n                className={cn(\n                  slotClass(\"field-error\", undefined, bubbleProps.render),\n                  className,\n                )}\n                data-slot=\"field-error\"\n                data-variant=\"tooltip\"\n                match\n                {...bubbleProps}\n              />\n            </LabelableProvider>\n          </FieldErrorStateProvider>\n        </TooltipPopup>\n      </Tooltip>\n    </>\n  );\n}\n\n// Base UI's own Control type, with className narrowed to a string: its ref\n// still takes any HTMLElement, as a rendered textarea needs.\nexport const FieldControl: React.ForwardRefExoticComponent<\n  WithStringClassName<Omit<FieldPrimitive.Control.Props, \"ref\">> &\n    React.RefAttributes<HTMLElement>\n> = FieldPrimitive.Control;\nexport const FieldValidity: typeof FieldPrimitive.Validity =\n  FieldPrimitive.Validity;\n\nexport { FieldPrimitive };\n","type":"registry:ui","target":"~/src/siteplane/components/field/field.tsx"},{"path":"packages/ui/src/components/field/index.ts","content":"export * from \"./field\";\n","type":"registry:ui","target":"~/src/siteplane/components/field/index.ts"},{"path":"packages/ui/src/components/field/field-control-bridge.ts","content":"import {\n  DEFAULT_FIELD_ROOT_STATE,\n  DEFAULT_VALIDITY_STATE,\n} from \"@base-ui/react/internals/field-constants\";\nimport { useFieldRootContext } from \"@base-ui/react/internals/field-root-context\";\nimport { useRegisterFieldControl } from \"@base-ui/react/internals/field-register-control\";\nimport { useFormContext } from \"@base-ui/react/internals/form-context\";\nimport {\n  useLabelableContext,\n  useLabelableId,\n} from \"@base-ui/react/internals/labelable-provider\";\nimport * as React from \"react\";\n\n/*\n * Kit-internal Field adapter for controls whose own foundation (the React Aria\n * date, time and color controls) owns the value and, for date and time fields,\n * segments and constraint validation; the component index does not export it.\n * It connects such a control to an enclosing Field the way Base UI's own\n * Select does: FieldLabel names the control, FieldDescription and FieldError\n * describe it, Field and Form see its value and validity, and FieldError shows\n * the control's validation message.\n */\n\n/** Validation result reported by the control's own validation owner. */\nexport type FieldControlValidation = {\n  isInvalid: boolean;\n  validationDetails: ValidityState;\n  validationErrors: string[];\n};\n\n/**\n * For a control without constraints of its own, such as a color picker: the\n * proxy input checks `required` natively.\n */\nexport const noOwnerValidation: FieldControlValidation = {\n  isInvalid: false,\n  validationDetails: {\n    badInput: false,\n    customError: false,\n    patternMismatch: false,\n    rangeOverflow: false,\n    rangeUnderflow: false,\n    stepMismatch: false,\n    tooLong: false,\n    tooShort: false,\n    typeMismatch: false,\n    valid: true,\n    valueMissing: false,\n  },\n  validationErrors: [],\n};\n\nexport type FieldControlBridge = {\n  /** Explicit `aria-describedby` ids, then FieldDescription and FieldError ids. */\n  describedBy: string | undefined;\n  /** The control's own disabled state or a disabled Field or Fieldset. */\n  disabled: boolean;\n  inField: boolean;\n  /** Application-controlled Field state: Field `invalid` or a Form error. */\n  invalid: boolean;\n  /** Explicit `aria-labelledby`, otherwise the FieldLabel id. */\n  labelledBy: string | undefined;\n  /** A Field `name` takes precedence over the control's own name. */\n  name: string | undefined;\n  /** `false` while the Field reports an error. */\n  valid: boolean | null;\n};\n\nexport function useFieldControlBridge({\n  \"aria-describedby\": ariaDescribedBy,\n  \"aria-labelledby\": ariaLabelledBy,\n  disabled,\n  name,\n}: {\n  \"aria-describedby\"?: string | undefined;\n  \"aria-labelledby\"?: string | undefined;\n  disabled?: boolean | undefined;\n  name?: string | undefined;\n}): FieldControlBridge {\n  const field = useFieldRootContext();\n  const { getDescriptionProps, labelId } = useLabelableContext();\n  const describedBy = getDescriptionProps({\n    \"aria-describedby\": ariaDescribedBy,\n  })[\"aria-describedby\"];\n\n  return {\n    describedBy: describedBy || undefined,\n    disabled: Boolean(disabled || field.disabled),\n    inField: field.state !== DEFAULT_FIELD_ROOT_STATE,\n    invalid: field.invalid === true,\n    labelledBy: ariaLabelledBy ?? labelId,\n    name: field.name ?? name,\n    valid: field.state.valid,\n  };\n}\n\nconst segmentSelector = '[role=\"spinbutton\"], [role=\"textbox\"]';\n\n/**\n * Sets or removes the invalid state on the editable segments in `container`.\n */\nexport function markFieldErrorSegments(\n  container: ParentNode | null | undefined,\n  invalid: boolean,\n): void {\n  for (const segment of container?.querySelectorAll(segmentSelector) ?? []) {\n    if (invalid) {\n      segment.setAttribute(\"aria-invalid\", \"true\");\n      segment.setAttribute(\"data-invalid\", \"true\");\n    } else {\n      segment.removeAttribute(\"aria-invalid\");\n      segment.removeAttribute(\"data-invalid\");\n    }\n  }\n}\n\n/**\n * Marks the owner's segments invalid while the Field reports an error that\n * the owner doesn't show itself, such as a Field `validate` result. The owner\n * renders `aria-invalid` and `data-invalid` from its own validation only, so\n * the segments get the same attributes as for the owner's own checks. The\n * owner's validation and the Field's result stay unchanged.\n */\nexport function useFieldErrorSegments(\n  container: React.RefObject<HTMLElement | null>,\n  {\n    controlled,\n    ownerInvalid,\n  }: {\n    /** The application controls the invalid state. */\n    controlled: boolean;\n    /** The owner renders its own invalid state. */\n    ownerInvalid: boolean;\n  },\n): void {\n  const field = useFieldRootContext();\n  const fieldError = !controlled && field.state.valid === false;\n  const markedRef = React.useRef(false);\n\n  // The owner re-renders its segments from its own state, so this runs after\n  // every render. Attributes the owner renders itself stay untouched.\n  React.useLayoutEffect(() => {\n    if (ownerInvalid || (!fieldError && !markedRef.current)) return;\n    markFieldErrorSegments(container.current, fieldError);\n    markedRef.current = fieldError;\n  });\n}\n\nconst validityKeys = Object.keys(DEFAULT_VALIDITY_STATE).filter(\n  (key) => key !== \"valid\",\n) as Exclude<keyof ValidityState, \"valid\">[];\n\nfunction isSameValidation(\n  left: FieldControlValidation,\n  right: FieldControlValidation,\n): boolean {\n  return (\n    left === right ||\n    (left.isInvalid === right.isInvalid &&\n      left.validationErrors.join(\"\\n\") === right.validationErrors.join(\"\\n\") &&\n      validityKeys.every(\n        (key) =>\n          Boolean(left.validationDetails[key]) ===\n          Boolean(right.validationDetails[key]),\n      ))\n  );\n}\n\nexport type FieldControlProxyProps = {\n  \"aria-hidden\": true;\n  autoComplete: \"off\";\n  className: string;\n  disabled: boolean;\n  form: string | undefined;\n  id: string | undefined;\n  inputMode: \"none\";\n  onChange: () => void;\n  onFocus: () => void;\n  onInvalid: (event: React.FormEvent<HTMLInputElement>) => void;\n  ref: React.RefCallback<HTMLInputElement>;\n  required: boolean | undefined;\n  tabIndex: -1;\n  value: string;\n};\n\n/**\n * Registers the control with the enclosing Field and Form and returns props\n * for a visually hidden proxy input, or `null` outside a Field. FieldLabel\n * points to the proxy, which passes focus on to the control. Field and Form\n * validation read the proxy's native validity, which mirrors the owner's\n * constraint validation plus `required`.\n */\nexport function useFieldControlProxy({\n  controlled,\n  disabled,\n  displayValidation,\n  focusTarget,\n  form,\n  name,\n  onRevalidate,\n  realtimeValidation,\n  required,\n  value,\n}: {\n  /** The application controls the invalid state and explains it itself. */\n  controlled: boolean;\n  disabled: boolean;\n  /** The validation the owner currently displays. */\n  displayValidation: FieldControlValidation;\n  /** The element that receives focus for the Field, such as the first segment. */\n  focusTarget: () => HTMLElement | null;\n  form: string | undefined;\n  name: string | undefined;\n  /** Asks the owner to display its current validation. */\n  onRevalidate: () => void;\n  /** The owner's current validation, displayed or not. */\n  realtimeValidation: FieldControlValidation;\n  required: boolean | undefined;\n  /** Serialized value; an empty string while the control is empty. */\n  value: string;\n}): FieldControlProxyProps | null {\n  const field = useFieldRootContext();\n  const { clearErrors } = useFormContext();\n  const inField = field.state !== DEFAULT_FIELD_ROOT_STATE;\n  const controlId = useLabelableId();\n  const inputRef = React.useRef<HTMLInputElement | null>(null);\n  const valueRef = React.useRef(value);\n  const focusTargetRef = React.useRef(focusTarget);\n  const changeRef = React.useRef(noop);\n  const displaysErrorRef = React.useRef(displayValidation.isInvalid);\n  const revalidateRef = React.useRef(onRevalidate);\n  const controlRef = React.useMemo<React.RefObject<HTMLElement | null>>(\n    () => ({\n      get current() {\n        return focusTargetRef.current();\n      },\n    }),\n    [],\n  );\n  const getValue = React.useCallback(() => valueRef.current, []);\n  const {\n    name: fieldName,\n    setDirty,\n    setFilled,\n    setValidityData,\n    validation,\n    validityData,\n  } = field;\n\n  useRegisterFieldControl(\n    controlRef,\n    controlId,\n    value,\n    getValue,\n    inField && !disabled,\n    name,\n  );\n\n  React.useLayoutEffect(() => {\n    valueRef.current = value;\n    focusTargetRef.current = focusTarget;\n    displaysErrorRef.current = displayValidation.isInvalid;\n    revalidateRef.current = onRevalidate;\n    // A value change validates again, like a Base UI control; an error that\n    // is already shown follows the value instead of waiting for the next blur.\n    changeRef.current = () => {\n      clearErrors(fieldName);\n      setDirty(value !== (validityData.initialValue ?? \"\"));\n      validation.change(value);\n      if (!controlled && displayValidation.isInvalid) onRevalidate();\n    };\n    // The proxy's native validity mirrors the owner's constraint validation,\n    // so Field and Form validation see the result the owner reports.\n    inputRef.current?.setCustomValidity(\n      realtimeValidation.isInvalid\n        ? realtimeValidation.validationErrors.join(\" \") || \"Invalid value.\"\n        : \"\",\n    );\n  });\n\n  React.useLayoutEffect(() => {\n    if (inField) setFilled(value !== \"\");\n  }, [inField, setFilled, value]);\n\n  const previousValueRef = React.useRef(value);\n  React.useEffect(() => {\n    if (previousValueRef.current === value) return;\n    previousValueRef.current = value;\n    if (inField) changeRef.current();\n  }, [inField, value]);\n\n  // Field or Form validation, such as a Form submission, found an error that\n  // the owner does not display yet. The owner then displays its own result,\n  // which marks the control invalid as well.\n  const fieldValid = field.state.valid;\n  React.useEffect(() => {\n    if (inField && !controlled && fieldValid === false) {\n      if (!displaysErrorRef.current) revalidateRef.current();\n    }\n  }, [controlled, fieldValid, inField]);\n\n  // The owner displays validation after blur, native form submission or\n  // reset; the Field shows the same result, so FieldError explains it.\n  const displayedRef = React.useRef<FieldControlValidation | null>(null);\n  React.useEffect(() => {\n    const previous = displayedRef.current;\n    displayedRef.current = displayValidation;\n    if (!inField || controlled || previous === null) return;\n    if (isSameValidation(previous, displayValidation)) return;\n    const details = displayValidation.validationDetails;\n    setValidityData((current) => ({\n      ...current,\n      error: displayValidation.validationErrors[0] ?? \"\",\n      errors: displayValidation.validationErrors,\n      state: {\n        badInput: details.badInput,\n        customError: details.customError,\n        patternMismatch: details.patternMismatch,\n        rangeOverflow: details.rangeOverflow,\n        rangeUnderflow: details.rangeUnderflow,\n        stepMismatch: details.stepMismatch,\n        tooLong: details.tooLong,\n        tooShort: details.tooShort,\n        typeMismatch: details.typeMismatch,\n        valid: !displayValidation.isInvalid,\n        valueMissing: details.valueMissing,\n      },\n      value: valueRef.current,\n    }));\n  }, [controlled, displayValidation, inField, setValidityData]);\n\n  const validationInputRef = validation.inputRef;\n  const setInputRef = React.useCallback(\n    (element: HTMLInputElement | null) => {\n      inputRef.current = element;\n      validationInputRef.current = element;\n    },\n    [validationInputRef],\n  );\n\n  if (!inField) return null;\n\n  return {\n    \"aria-hidden\": true,\n    autoComplete: \"off\",\n    className: \"sr-only\",\n    disabled,\n    form,\n    id: controlId,\n    inputMode: \"none\",\n    onChange: noop,\n    onFocus: () => focusTargetRef.current()?.focus(),\n    // The control reports the error itself; the browser bubble would repeat it.\n    onInvalid: (event) => event.preventDefault(),\n    ref: setInputRef,\n    required,\n    tabIndex: -1,\n    value,\n  };\n}\n\nfunction noop(): void {}\n\n/**\n * Whether an editable segment of a React Aria date or time field shows its\n * placeholder, so the field shows no complete value.\n */\nexport function hasClearedSegment(\n  state:\n    | {\n        segments: readonly { isEditable: boolean; isPlaceholder: boolean }[];\n      }\n    | null\n    | undefined,\n): boolean {\n  return (\n    state?.segments.some(\n      (segment) => segment.isEditable && segment.isPlaceholder,\n    ) ?? false\n  );\n}\n\n// The day conversions live with the system so the Calendar, which ships\n// without Input, converts days the same way as the date and time fields.\nexport { nativeDayTime, toNativeDay } from \"../../system/date/native-day\";\n\nexport type SegmentedValueOptions<T> = {\n  /** The application controls the value. */\n  controlled: boolean;\n  /** The owner value of the `value` prop, read while controlled. */\n  value: T | null;\n  /** The owner value the field starts with while uncontrolled. */\n  defaultValue: T | null;\n  /** The form value; an empty string while the field holds no value. */\n  serialize: (value: T | null) => string;\n  /** Reports a value the user set, or `null` when the user cleared it. */\n  onValueChange: (value: T | null) => void;\n  required: boolean | undefined;\n  /** The error of a required field that shows an incomplete value. */\n  incompleteMessage: string;\n};\n\nexport type SegmentedValue<T> = {\n  /** A segment shows its placeholder, so the field holds no value. */\n  incomplete: boolean;\n  /** The owner's `value` while controlled. */\n  ownerValue: T | null;\n  /** The owner's change callback. */\n  onOwnerChange: (value: T | null) => void;\n  /** The owner's `validate`: a required field is invalid while incomplete. */\n  validate: () => string | undefined;\n  /**\n   * Called after every render of the field's content with what its segments\n   * show: whether a segment is cleared, the owner's value and the value the\n   * field holds, which is empty while a segment is cleared.\n   */\n  sync: (shown: {\n    cleared: boolean;\n    effective: T | null;\n    value: T | null;\n  }) => void;\n};\n\n/**\n * The value of a segmented date or time field as its segments show it.\n * React Aria keeps the last complete value while the user clears a segment\n * and reports nothing until the field is complete or empty again. The kit\n * treats such a field as empty: its form value is empty, `onValueChange`\n * reports the clear once, and a required field is invalid. When the segments\n * are complete again, the value is reported again, also when React Aria still\n * held it. A controlled field passes the held value on to React Aria while\n * the application holds the clear, so the segments the user left stay as\n * they are.\n */\nexport function useSegmentedValue<T>({\n  controlled,\n  defaultValue,\n  incompleteMessage,\n  onValueChange,\n  required,\n  serialize,\n  value,\n}: SegmentedValueOptions<T>): SegmentedValue<T> {\n  const [incomplete, setIncomplete] = React.useState(false);\n  const [held, setHeld] = React.useState<T | null>(null);\n  // The value the application knows: the last one reported, or its own. A\n  // new value from the application is known at once, before the content\n  // reports what its segments show after the same render.\n  const reported = React.useRef<string | null>(null);\n  reported.current ??= serialize(controlled ? value : defaultValue);\n  const valueKey = controlled ? serialize(value) : null;\n  // React Aria resets the segments whenever the value it gets is a new object,\n  // so a render with the same value, such as a parent update during an edit,\n  // passes the same object on; the value is keyed by its form value.\n  const ownValue = React.useMemo(() => value, [valueKey]);\n  const [lastValueKey, setLastValueKey] = React.useState(valueKey);\n  if (valueKey !== lastValueKey) {\n    setLastValueKey(valueKey);\n    if (valueKey !== null) reported.current = valueKey;\n    // A value from the application ends a held clear.\n    if (value !== null) setHeld(null);\n  }\n\n  const report = (next: T | null) => {\n    const key = serialize(next);\n    if (key === reported.current) return;\n    reported.current = key;\n    onValueChange(next);\n  };\n\n  return {\n    incomplete,\n    ownerValue: controlled ? (ownValue ?? held) : null,\n    onOwnerChange: (next) => {\n      setHeld(null);\n      report(next);\n    },\n    validate: () => (required && incomplete ? incompleteMessage : undefined),\n    sync: ({ cleared, effective, value: shown }) => {\n      if (cleared !== incomplete) setIncomplete(cleared);\n      if (serialize(effective) === reported.current) return;\n      if (controlled && cleared) setHeld(shown);\n      report(effective);\n    },\n  };\n}\n","type":"registry:ui","target":"~/src/siteplane/components/field/field-control-bridge.ts"},{"path":"packages/ui/src/components/field/field-error-presence.tsx","content":"\"use client\";\n\nimport {\n  FieldRootContext,\n  useFieldRootContext,\n} from \"@base-ui/react/internals/field-root-context\";\nimport {\n  FormContext,\n  useFormContext,\n} from \"@base-ui/react/internals/form-context\";\nimport type * as React from \"react\";\n\n/*\n * Kit-internal helpers of FieldError: when the error is shown, and the Field\n * state a leaving error keeps. Like the control bridge next to it, they read\n * Base UI's Field and Form context, which the pinned @base-ui/react version\n * exports; the component index does not export them.\n */\n\nexport type FieldErrorMatch = boolean | keyof ValidityState | undefined;\n\n/** The Field and Form state a FieldError reads, as it is at one render. */\nexport type FieldErrorState = {\n  field: FieldRootContext;\n  form: FormContext;\n};\n\n/**\n * The state of the enclosing Field and Form. Like Base UI's own Field.Error it\n * needs an enclosing Field.\n */\nexport function useFieldErrorState(): FieldErrorState {\n  return { field: useFieldRootContext(false), form: useFormContext() };\n}\n\n/**\n * Whether a FieldError shows its message. This is the rule of Base UI's own\n * Field.Error: `match` shows it always, a disabled Field never, a validity key\n * when that check fails, otherwise a failed validation or a Form error for the\n * Field's name. FieldError needs the answer outside the element, because the\n * motion component keeps an error on screen while it leaves.\n */\nexport function isFieldErrorShown(\n  match: FieldErrorMatch,\n  { field, form }: Pick<FieldErrorState, \"field\" | \"form\">,\n): boolean {\n  if (match === true) return true;\n  if (field.state.disabled) return false;\n  if (typeof match === \"string\")\n    return Boolean(field.validityData.state[match]);\n\n  const formError =\n    field.name && Object.hasOwn(form.errors, field.name)\n      ? form.errors[field.name]\n      : null;\n  const hasFormError = Array.isArray(formError)\n    ? formError.length > 0\n    : Boolean(formError);\n  return hasFormError || field.validityData.state.valid === false;\n}\n\n/**\n * Hands the Field and Form state it is given to everything inside. FieldError\n * passes the state of its latest render. While the error leaves, the motion\n * component keeps rendering the element of that last render, so the message\n * stays what it was and does not change as the Field becomes valid, which the\n * validity and Form state that clears the error would otherwise do.\n */\nexport function FieldErrorStateProvider({\n  children,\n  field,\n  form,\n}: FieldErrorState & { children: React.ReactNode }): React.ReactElement {\n  return (\n    <FieldRootContext.Provider value={field}>\n      <FormContext.Provider value={form}>{children}</FormContext.Provider>\n    </FieldRootContext.Provider>\n  );\n}\n","type":"registry:ui","target":"~/src/siteplane/components/field/field-error-presence.tsx"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/input.md","categories":["Forms & selection"],"meta":{"avoidWhen":["Avoid for several lines of text; use Textarea.","Avoid for a number with stepping; use Number Field."],"description":"Base UI-backed text input and accessible field boundary.","knownLimitations":["A controlled Input does not reset with its form; reset your state in onReset."],"providerNotes":[],"releaseStatus":"stable","searchTerms":["input"],"useWhen":["Use for a single line of text or a native input value, such as an email address, a password or a file."]}}