{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "form",
  "type": "registry:ui",
  "title": "Form",
  "description": "Form primitives for Tile UI.",
  "dependencies": [
    "@radix-ui/react-slot"
  ],
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/core",
    "label",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/form/form.tsx",
      "type": "registry:ui",
      "target": "components/ui/form/form.tsx",
      "content": "import React, { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { createFormStore, formStyleKeys, getFormFieldIds, normalizeFormValue, FormStore } from '../lib/core';\nimport type { FormBaseProps, FormControllerField, FormFieldError, FormRegisterOptions, FormSnapshot } from '../lib/core';\nimport { Label } from '../label';\nimport styles from './form.module.scss';\n\nconst FormStoreContext = createContext<FormStore | null>(null);\n\nfunction useFormStore(): FormStore {\n\tconst store = useContext(FormStoreContext);\n\tif (!store) {\n\t\tthrow new Error('Form sub-components must be used within <Form>.');\n\t}\n\treturn store;\n}\n\n/**\n * 订阅表单存储快照，触发组件重渲染\n */\nfunction useFormSnapshot(store: FormStore): FormSnapshot {\n\treturn useSyncExternalStore(\n\t\t(listener) => store.subscribe(listener),\n\t\t() => store.getSnapshot(),\n\t\t() => store.getSnapshot(),\n\t);\n}\n\n/**\n * 返回当前表单实例 (FormStore)，可调用 handleSubmit / trigger / getValues 等\n */\nexport function useForm(): FormStore {\n\treturn useFormStore();\n}\n\nconst FormFieldContext = createContext<{ name: string }>({ name: '' });\n\ninterface FormItemContextValue {\n\tid: string;\n\tdeclaredDescriptionId: string | undefined;\n\tdeclaredMessageId: string | undefined;\n\tdescriptionIds: () => string[];\n\tmessageIds: () => string[];\n\tregisterDescription: (id: string) => () => void;\n\tregisterMessage: (id: string) => () => void;\n}\n\nconst FormItemContext = createContext<FormItemContextValue>({\n\tid: '',\n\tdeclaredDescriptionId: undefined,\n\tdeclaredMessageId: undefined,\n\tdescriptionIds: () => [],\n\tmessageIds: () => [],\n\tregisterDescription: () => () => {},\n\tregisterMessage: () => () => {},\n});\n\n/**\n * 注册一个已挂载节点的 ID；返回值用于在卸载时注销。\n */\nfunction registerPresence(registry: Set<string>, bump: () => void, registeredId: string) {\n\tregistry.add(registeredId);\n\tbump();\n\tlet active = true;\n\treturn () => {\n\t\tif (!active) return;\n\t\tactive = false;\n\t\tregistry.delete(registeredId);\n\t\tbump();\n\t};\n}\n\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\nexport interface FormFieldResult {\n\tid: string;\n\tname: string;\n\tformItemId: string;\n\tformDescriptionId: string;\n\tformMessageId: string;\n\terror?: FormFieldError;\n\tisTouched: boolean;\n\tisDirty: boolean;\n}\n\n/**\n * 读取当前字段上下文 (name + 表单项 id + 错误信息)\n */\nexport function useFormField(): FormFieldResult {\n\tconst fieldContext = useContext(FormFieldContext);\n\tconst itemContext = useContext(FormItemContext);\n\tconst store = useFormStore();\n\tuseFormSnapshot(store);\n\n\tif (!fieldContext.name) {\n\t\tthrow new Error('useFormField should be used within <FormField>');\n\t}\n\n\tconst ids = getFormFieldIds(itemContext.id);\n\tconst fieldState = store.getFieldState(fieldContext.name);\n\n\treturn {\n\t\tid: ids.id,\n\t\tname: fieldContext.name,\n\t\tformItemId: ids.formItemId,\n\t\tformDescriptionId: ids.formDescriptionId,\n\t\tformMessageId: ids.formMessageId,\n\t\terror: fieldState.error,\n\t\tisTouched: fieldState.isTouched,\n\t\tisDirty: fieldState.isDirty,\n\t};\n}\n\nexport interface FormProps extends FormBaseProps {\n\tchildren?: React.ReactNode;\n}\n\n/**\n * 表单容器：创建并提供 FormStore 上下文\n */\nfunction Form({ defaultValues = {}, resolver, form, children }: FormProps) {\n\tconst [store] = useState<FormStore>(() => (form ? (form as FormStore) : createFormStore(defaultValues, resolver)));\n\n\treturn <FormStoreContext.Provider value={store}>{children}</FormStoreContext.Provider>;\n}\n\nForm.displayName = 'Form';\n\nexport interface FormFieldProps {\n\tname: string;\n\toptions?: FormRegisterOptions;\n\trender?: (props: { field: FormControllerField }) => React.ReactNode;\n\tchildren?: React.ReactNode;\n}\n\n/** 共享的空注册选项，避免默认值在每次渲染时产生新引用导致重复注册。 */\nconst EMPTY_REGISTER_OPTIONS: FormRegisterOptions = {};\n\n/**\n * 表单字段：注册字段并提供字段渲染上下文\n */\nfunction FormField({ name, options = EMPTY_REGISTER_OPTIONS, render, children }: FormFieldProps) {\n\tconst store = useFormStore();\n\tuseFormSnapshot(store);\n\n\tuseEffect(() => {\n\t\tconst registration = store.registerField(name, options);\n\t\treturn registration.unregister;\n\t}, [store, name, options]);\n\n\tconst field: FormControllerField = {\n\t\tname,\n\t\tvalue: store.getValue(name),\n\t\tonChange: (value: unknown) => store.setValue(name, normalizeFormValue(value, options.valueAsNumber)),\n\t\tonBlur: () => store.blurField(name),\n\t\tref: name,\n\t};\n\n\treturn <FormFieldContext.Provider value={{ name }}>{render ? render({ field }) : children}</FormFieldContext.Provider>;\n}\n\nFormField.displayName = 'FormField';\n\nexport interface FormItemProps extends React.HTMLAttributes<HTMLDivElement> {\n\t/** SSR 时预声明已渲染的描述节点 ID；同时作为 FormDescription 的默认 ID。 */\n\tdescriptionId?: string;\n\t/** SSR 时预声明错误消息节点 ID；同时作为 FormMessage 的默认 ID。 */\n\tmessageId?: string;\n}\n\nfunction FormItem({ className = '', descriptionId, messageId, ...props }: FormItemProps) {\n\tconst id = useId();\n\tconst descriptionIdsRef = useRef(new Set<string>(descriptionId === undefined ? [] : [descriptionId]));\n\tconst messageIdsRef = useRef(new Set<string>(messageId === undefined ? [] : [messageId]));\n\tconst [presence, setPresence] = useState(0);\n\tconst bump = useCallback(() => setPresence((version) => version + 1), []);\n\n\tuseIsomorphicLayoutEffect(() => {\n\t\tif (descriptionId === undefined) return;\n\t\treturn registerPresence(descriptionIdsRef.current, bump, descriptionId);\n\t}, [descriptionId, bump]);\n\n\tuseIsomorphicLayoutEffect(() => {\n\t\tif (messageId === undefined) return;\n\t\treturn registerPresence(messageIdsRef.current, bump, messageId);\n\t}, [messageId, bump]);\n\n\tconst registerDescription = useCallback((registeredId: string) => registerPresence(descriptionIdsRef.current, bump, registeredId), [bump]);\n\tconst registerMessage = useCallback((registeredId: string) => registerPresence(messageIdsRef.current, bump, registeredId), [bump]);\n\n\tconst context: FormItemContextValue = {\n\t\tid,\n\t\tdeclaredDescriptionId: descriptionId,\n\t\tdeclaredMessageId: messageId,\n\t\tdescriptionIds: () => {\n\t\t\tvoid presence;\n\t\t\treturn [...descriptionIdsRef.current];\n\t\t},\n\t\tmessageIds: () => {\n\t\t\tvoid presence;\n\t\t\treturn [...messageIdsRef.current];\n\t\t},\n\t\tregisterDescription,\n\t\tregisterMessage,\n\t};\n\n\treturn (\n\t\t<FormItemContext.Provider value={context}>\n\t\t\t<div data-slot=\"form-item\" className={`${styles[formStyleKeys.item]} ${className}`} {...props} />\n\t\t</FormItemContext.Provider>\n\t);\n}\n\nFormItem.displayName = 'FormItem';\n\nexport interface FormLabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {}\n\nfunction FormLabel({ className = '', ...props }: FormLabelProps) {\n\tconst { error, formItemId } = useFormField();\n\n\treturn <Label data-slot=\"form-label\" data-error={!!error} className={`${styles[formStyleKeys.label]} ${className}`} htmlFor={formItemId} {...props} />;\n}\n\nFormLabel.displayName = 'FormLabel';\n\nexport interface FormControlProps extends React.ComponentProps<typeof Slot> {}\n\nfunction FormControl({ ...props }: FormControlProps) {\n\tconst { error, formItemId } = useFormField();\n\tconst item = useContext(FormItemContext);\n\n\tconst describedByIds = [...item.descriptionIds()];\n\tif (error) describedByIds.push(...item.messageIds());\n\n\treturn <Slot data-slot=\"form-control\" id={formItemId} aria-describedby={describedByIds.length > 0 ? describedByIds.join(' ') : undefined} aria-invalid={!!error} {...props} />;\n}\n\nFormControl.displayName = 'FormControl';\n\nexport interface FormDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}\n\nfunction FormDescription({ className = '', ...props }: FormDescriptionProps) {\n\tconst { formDescriptionId } = useFormField();\n\tconst item = useContext(FormItemContext);\n\tconst effectiveId = props.id ?? item.declaredDescriptionId ?? formDescriptionId;\n\tconst { registerDescription } = item;\n\n\tuseIsomorphicLayoutEffect(() => registerDescription(effectiveId), [registerDescription, effectiveId]);\n\n\treturn <p data-slot=\"form-description\" id={effectiveId} className={`${styles[formStyleKeys.description]} ${className}`} {...props} />;\n}\n\nFormDescription.displayName = 'FormDescription';\n\nexport interface FormMessageProps extends React.HTMLAttributes<HTMLParagraphElement> {}\n\nfunction FormMessage({ className = '', children, ...props }: FormMessageProps) {\n\tconst { error, formMessageId } = useFormField();\n\tconst item = useContext(FormItemContext);\n\tconst effectiveId = props.id ?? item.declaredMessageId ?? formMessageId;\n\tconst { registerMessage } = item;\n\tconst hasBody = error ? !!String(error.message ?? '') : !!children;\n\n\tuseIsomorphicLayoutEffect(() => {\n\t\tif (!hasBody) return;\n\t\treturn registerMessage(effectiveId);\n\t}, [registerMessage, effectiveId, hasBody]);\n\n\tif (!hasBody) {\n\t\treturn null;\n\t}\n\n\tconst body = error ? String(error.message ?? '') : children;\n\n\treturn (\n\t\t<p data-slot=\"form-message\" id={effectiveId} className={`${styles[formStyleKeys.message]} ${className}`} {...props}>\n\t\t\t{body}\n\t\t</p>\n\t);\n}\n\nFormMessage.displayName = 'FormMessage';\n\nexport { Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };\nexport default Form;\n"
    },
    {
      "path": "packages/styles/scss/components/form.module.scss",
      "type": "registry:file",
      "target": "components/ui/form/form.module.scss",
      "content": "@use '../../../styles/variables/colors' as *;\n\n.item {\n\tdisplay: grid;\n\tgap: var(--tile-form-item-gap, #{$spacing-2});\n}\n\n.label {\n\tfont-size: var(--tile-form-label-font-size, #{$text-sm});\n\tfont-weight: $font-medium;\n\tline-height: var(--tile-form-label-line-height, #{$leading-none});\n\tcolor: $foreground;\n\n\t&[data-error='true'] {\n\t\tcolor: $destructive;\n\t}\n}\n\n.control {\n\tdisplay: block;\n}\n\n.description {\n\tmargin: 0;\n\tfont-size: $text-sm;\n\tline-height: var(--tile-form-description-line-height, #{$leading-normal});\n\tcolor: var(--tile-form-description-color, #{$muted-foreground});\n}\n\n.message {\n\tmargin: 0;\n\tfont-size: $text-sm;\n\tline-height: var(--tile-form-description-line-height, #{$leading-normal});\n\tcolor: $destructive;\n}\n"
    },
    {
      "path": "packages/react/src/components/form/index.ts",
      "type": "registry:ui",
      "target": "components/ui/form/index.ts",
      "content": "export {\n\tForm,\n\tFormItem,\n\tFormLabel,\n\tFormControl,\n\tFormDescription,\n\tFormMessage,\n\tFormField,\n\tuseForm,\n\tuseFormField,\n\ttype FormProps,\n\ttype FormFieldProps,\n\ttype FormItemProps,\n\ttype FormLabelProps,\n\ttype FormControlProps,\n\ttype FormDescriptionProps,\n\ttype FormMessageProps,\n\ttype FormFieldResult,\n} from './form';\n"
    }
  ]
}
