@mitcsutt/kiln-ui 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/components/actions/Button/Button.d.ts +8 -0
  2. package/dist/components/actions/Button/Button.js.map +1 -1
  3. package/dist/components/actions/ChipGroup/ChipGroup.d.ts +9 -0
  4. package/dist/components/actions/ChipGroup/ChipGroup.js.map +1 -1
  5. package/dist/components/actions/IconButton/IconButton.d.ts +8 -0
  6. package/dist/components/actions/IconButton/IconButton.js.map +1 -1
  7. package/dist/components/actions/ModeToggle/ModeToggle.d.ts +8 -0
  8. package/dist/components/actions/ModeToggle/ModeToggle.js.map +1 -1
  9. package/dist/components/actions/SegmentedControl/SegmentedControl.d.ts +9 -0
  10. package/dist/components/actions/SegmentedControl/SegmentedControl.js.map +1 -1
  11. package/dist/components/actions/ToggleChip/ToggleChip.d.ts +9 -0
  12. package/dist/components/actions/ToggleChip/ToggleChip.js.map +1 -1
  13. package/dist/components/display/Avatar/Avatar.d.ts +10 -0
  14. package/dist/components/display/Avatar/Avatar.js.map +1 -1
  15. package/dist/components/display/AvatarGroup/AvatarGroup.d.ts +7 -0
  16. package/dist/components/display/AvatarGroup/AvatarGroup.js.map +1 -1
  17. package/dist/components/display/Badge/Badge.d.ts +7 -0
  18. package/dist/components/display/Badge/Badge.js.map +1 -1
  19. package/dist/components/display/Card/Card.js.map +1 -1
  20. package/dist/components/display/CodeBlock/CodeBlock.d.ts +9 -0
  21. package/dist/components/display/CodeBlock/CodeBlock.js.map +1 -1
  22. package/dist/components/display/DataList/DataList.js.map +1 -1
  23. package/dist/components/display/Delta/Delta.d.ts +9 -0
  24. package/dist/components/display/Delta/Delta.js.map +1 -1
  25. package/dist/components/display/EmptyState/EmptyState.d.ts +8 -0
  26. package/dist/components/display/EmptyState/EmptyState.js.map +1 -1
  27. package/dist/components/display/List/List.js.map +1 -1
  28. package/dist/components/display/List/List.module.js.map +1 -1
  29. package/dist/components/display/Marquee/Marquee.d.ts +8 -0
  30. package/dist/components/display/Marquee/Marquee.js.map +1 -1
  31. package/dist/components/display/Media/Media.d.ts +7 -0
  32. package/dist/components/display/Media/Media.js.map +1 -1
  33. package/dist/components/display/Meter/Meter.d.ts +9 -0
  34. package/dist/components/display/Meter/Meter.js.map +1 -1
  35. package/dist/components/display/Progress/Progress.d.ts +8 -0
  36. package/dist/components/display/Progress/Progress.js.map +1 -1
  37. package/dist/components/display/Skeleton/Skeleton.js.map +1 -1
  38. package/dist/components/display/Stamp/Stamp.d.ts +8 -0
  39. package/dist/components/display/Stamp/Stamp.js.map +1 -1
  40. package/dist/components/display/Stat/Stat.d.ts +8 -0
  41. package/dist/components/display/Stat/Stat.js.map +1 -1
  42. package/dist/components/display/Table/Table.js.map +1 -1
  43. package/dist/components/display/Table/Table.module.js.map +1 -1
  44. package/dist/components/display/Tag/Tag.d.ts +7 -0
  45. package/dist/components/display/Tag/Tag.js.map +1 -1
  46. package/dist/components/feedback/Alert/Alert.d.ts +10 -0
  47. package/dist/components/feedback/Alert/Alert.js.map +1 -1
  48. package/dist/components/feedback/LiveIndicator/LiveIndicator.d.ts +7 -0
  49. package/dist/components/feedback/LiveIndicator/LiveIndicator.js.map +1 -1
  50. package/dist/components/feedback/RelativeTime/RelativeTime.d.ts +8 -0
  51. package/dist/components/feedback/RelativeTime/RelativeTime.js.map +1 -1
  52. package/dist/components/feedback/Spinner/Spinner.d.ts +10 -1
  53. package/dist/components/feedback/Spinner/Spinner.js.map +1 -1
  54. package/dist/components/feedback/StatusDot/StatusDot.d.ts +10 -1
  55. package/dist/components/feedback/StatusDot/StatusDot.js.map +1 -1
  56. package/dist/components/inputs/AmountField/AmountField.d.ts +7 -0
  57. package/dist/components/inputs/AmountField/AmountField.js.map +1 -1
  58. package/dist/components/inputs/AmountInput/AmountInput.d.ts +8 -0
  59. package/dist/components/inputs/AmountInput/AmountInput.js.map +1 -1
  60. package/dist/components/inputs/Checkbox/Checkbox.d.ts +8 -0
  61. package/dist/components/inputs/Checkbox/Checkbox.js.map +1 -1
  62. package/dist/components/inputs/CheckboxField/CheckboxField.d.ts +7 -0
  63. package/dist/components/inputs/CheckboxField/CheckboxField.js.map +1 -1
  64. package/dist/components/inputs/CheckboxGroup/CheckboxGroup.d.ts +8 -0
  65. package/dist/components/inputs/CheckboxGroup/CheckboxGroup.js.map +1 -1
  66. package/dist/components/inputs/CheckboxGroupField/CheckboxGroupField.d.ts +7 -0
  67. package/dist/components/inputs/CheckboxGroupField/CheckboxGroupField.js.map +1 -1
  68. package/dist/components/inputs/ChipGroupField/ChipGroupField.d.ts +7 -0
  69. package/dist/components/inputs/ChipGroupField/ChipGroupField.js.map +1 -1
  70. package/dist/components/inputs/ChoiceCards/ChoiceCards.d.ts +9 -0
  71. package/dist/components/inputs/ChoiceCards/ChoiceCards.js.map +1 -1
  72. package/dist/components/inputs/ChoiceCardsField/ChoiceCardsField.d.ts +7 -0
  73. package/dist/components/inputs/ChoiceCardsField/ChoiceCardsField.js.map +1 -1
  74. package/dist/components/inputs/ColorField/ColorField.d.ts +6 -0
  75. package/dist/components/inputs/ColorField/ColorField.js.map +1 -1
  76. package/dist/components/inputs/ColorInput/ColorInput.d.ts +8 -0
  77. package/dist/components/inputs/ColorInput/ColorInput.js.map +1 -1
  78. package/dist/components/inputs/Combobox/Combobox.d.ts +9 -0
  79. package/dist/components/inputs/Combobox/Combobox.js.map +1 -1
  80. package/dist/components/inputs/ComboboxField/ComboboxField.d.ts +7 -0
  81. package/dist/components/inputs/ComboboxField/ComboboxField.js.map +1 -1
  82. package/dist/components/inputs/DateRangeField/DateRangeField.d.ts +7 -0
  83. package/dist/components/inputs/DateRangeField/DateRangeField.js.map +1 -1
  84. package/dist/components/inputs/Field/Field.d.ts +26 -0
  85. package/dist/components/inputs/Field/Field.js.map +1 -1
  86. package/dist/components/inputs/Fieldset/Fieldset.d.ts +8 -0
  87. package/dist/components/inputs/Fieldset/Fieldset.js.map +1 -1
  88. package/dist/components/inputs/FileDrop/FileDrop.d.ts +10 -0
  89. package/dist/components/inputs/FileDrop/FileDrop.js.map +1 -1
  90. package/dist/components/inputs/FileField/FileField.d.ts +7 -0
  91. package/dist/components/inputs/FileField/FileField.js.map +1 -1
  92. package/dist/components/inputs/Input/Input.d.ts +9 -0
  93. package/dist/components/inputs/Input/Input.js.map +1 -1
  94. package/dist/components/inputs/NumberField/NumberField.d.ts +7 -0
  95. package/dist/components/inputs/NumberField/NumberField.js.map +1 -1
  96. package/dist/components/inputs/NumberInput/NumberInput.d.ts +8 -0
  97. package/dist/components/inputs/NumberInput/NumberInput.js.map +1 -1
  98. package/dist/components/inputs/OneTimeCodeField/OneTimeCodeField.d.ts +7 -0
  99. package/dist/components/inputs/OneTimeCodeField/OneTimeCodeField.js.map +1 -1
  100. package/dist/components/inputs/OneTimeCodeInput/OneTimeCodeInput.d.ts +8 -0
  101. package/dist/components/inputs/OneTimeCodeInput/OneTimeCodeInput.js.map +1 -1
  102. package/dist/components/inputs/PasswordField/PasswordField.d.ts +7 -0
  103. package/dist/components/inputs/PasswordField/PasswordField.js.map +1 -1
  104. package/dist/components/inputs/PasswordInput/PasswordInput.d.ts +10 -0
  105. package/dist/components/inputs/PasswordInput/PasswordInput.js.map +1 -1
  106. package/dist/components/inputs/RadioGroup/RadioGroup.d.ts +9 -0
  107. package/dist/components/inputs/RadioGroup/RadioGroup.js.map +1 -1
  108. package/dist/components/inputs/RadioGroupField/RadioGroupField.d.ts +8 -0
  109. package/dist/components/inputs/RadioGroupField/RadioGroupField.js.map +1 -1
  110. package/dist/components/inputs/RangeSlider/RangeSlider.d.ts +8 -0
  111. package/dist/components/inputs/RangeSlider/RangeSlider.js.map +1 -1
  112. package/dist/components/inputs/RangeSliderField/RangeSliderField.d.ts +7 -0
  113. package/dist/components/inputs/RangeSliderField/RangeSliderField.js.map +1 -1
  114. package/dist/components/inputs/Rating/Rating.d.ts +8 -0
  115. package/dist/components/inputs/Rating/Rating.js.map +1 -1
  116. package/dist/components/inputs/RatingField/RatingField.d.ts +7 -0
  117. package/dist/components/inputs/RatingField/RatingField.js.map +1 -1
  118. package/dist/components/inputs/SegmentedField/SegmentedField.d.ts +7 -0
  119. package/dist/components/inputs/SegmentedField/SegmentedField.js.map +1 -1
  120. package/dist/components/inputs/Select/Select.d.ts +9 -0
  121. package/dist/components/inputs/Select/Select.js.map +1 -1
  122. package/dist/components/inputs/SelectField/SelectField.d.ts +7 -0
  123. package/dist/components/inputs/SelectField/SelectField.js.map +1 -1
  124. package/dist/components/inputs/Slider/Slider.d.ts +8 -0
  125. package/dist/components/inputs/Slider/Slider.js.map +1 -1
  126. package/dist/components/inputs/SliderField/SliderField.d.ts +7 -0
  127. package/dist/components/inputs/SliderField/SliderField.js.map +1 -1
  128. package/dist/components/inputs/Switch/Switch.d.ts +8 -0
  129. package/dist/components/inputs/Switch/Switch.js.map +1 -1
  130. package/dist/components/inputs/SwitchField/SwitchField.d.ts +8 -0
  131. package/dist/components/inputs/SwitchField/SwitchField.js.map +1 -1
  132. package/dist/components/inputs/TagsField/TagsField.d.ts +6 -0
  133. package/dist/components/inputs/TagsField/TagsField.js.map +1 -1
  134. package/dist/components/inputs/TagsInput/TagsInput.d.ts +8 -0
  135. package/dist/components/inputs/TagsInput/TagsInput.js.map +1 -1
  136. package/dist/components/inputs/TextField/TextField.d.ts +8 -0
  137. package/dist/components/inputs/TextField/TextField.js.map +1 -1
  138. package/dist/components/inputs/Textarea/Textarea.d.ts +8 -0
  139. package/dist/components/inputs/Textarea/Textarea.js.map +1 -1
  140. package/dist/components/inputs/TextareaField/TextareaField.d.ts +7 -0
  141. package/dist/components/inputs/TextareaField/TextareaField.js.map +1 -1
  142. package/dist/components/layout/ActionBar/ActionBar.d.ts +7 -0
  143. package/dist/components/layout/ActionBar/ActionBar.js.map +1 -1
  144. package/dist/components/layout/AppShell/AppShell.js.map +1 -1
  145. package/dist/components/layout/AspectRatio/AspectRatio.d.ts +8 -0
  146. package/dist/components/layout/AspectRatio/AspectRatio.js.map +1 -1
  147. package/dist/components/layout/Box/Box.d.ts +8 -0
  148. package/dist/components/layout/Box/Box.js.map +1 -1
  149. package/dist/components/layout/Container/Container.d.ts +17 -0
  150. package/dist/components/layout/Container/Container.js.map +1 -1
  151. package/dist/components/layout/Divider/Divider.d.ts +7 -0
  152. package/dist/components/layout/Divider/Divider.js.map +1 -1
  153. package/dist/components/layout/Grid/Grid.d.ts +9 -0
  154. package/dist/components/layout/Grid/Grid.js.map +1 -1
  155. package/dist/components/layout/Inline/Inline.d.ts +8 -0
  156. package/dist/components/layout/Inline/Inline.js.map +1 -1
  157. package/dist/components/layout/Section/Section.d.ts +15 -0
  158. package/dist/components/layout/Section/Section.js.map +1 -1
  159. package/dist/components/layout/Split/Split.d.ts +9 -0
  160. package/dist/components/layout/Split/Split.js.map +1 -1
  161. package/dist/components/layout/Stack/Stack.d.ts +12 -0
  162. package/dist/components/layout/Stack/Stack.js.map +1 -1
  163. package/dist/components/layout/VisuallyHidden/VisuallyHidden.d.ts +7 -0
  164. package/dist/components/layout/VisuallyHidden/VisuallyHidden.js.map +1 -1
  165. package/dist/components/navigation/Accordion/Accordion.d.ts +7 -0
  166. package/dist/components/navigation/Accordion/Accordion.js.map +1 -1
  167. package/dist/components/navigation/BottomNav/BottomNav.d.ts +9 -0
  168. package/dist/components/navigation/BottomNav/BottomNav.js.map +1 -1
  169. package/dist/components/navigation/NavLinks/NavLinks.d.ts +9 -0
  170. package/dist/components/navigation/NavLinks/NavLinks.js.map +1 -1
  171. package/dist/components/navigation/Stepper/Stepper.d.ts +7 -0
  172. package/dist/components/navigation/Stepper/Stepper.js.map +1 -1
  173. package/dist/components/navigation/Tabs/Tabs.d.ts +8 -0
  174. package/dist/components/navigation/Tabs/Tabs.js.map +1 -1
  175. package/dist/components/overlays/Dialog/Dialog.d.ts +13 -0
  176. package/dist/components/overlays/Dialog/Dialog.js.map +1 -1
  177. package/dist/components/overlays/DropdownMenu/DropdownMenu.d.ts +8 -0
  178. package/dist/components/overlays/DropdownMenu/DropdownMenu.js.map +1 -1
  179. package/dist/components/overlays/Popover/Popover.d.ts +7 -0
  180. package/dist/components/overlays/Popover/Popover.js.map +1 -1
  181. package/dist/components/overlays/Sheet/Sheet.d.ts +8 -0
  182. package/dist/components/overlays/Sheet/Sheet.js.map +1 -1
  183. package/dist/components/overlays/Tooltip/Tooltip.d.ts +8 -0
  184. package/dist/components/overlays/Tooltip/Tooltip.js.map +1 -1
  185. package/dist/components/typography/Amount/Amount.d.ts +8 -0
  186. package/dist/components/typography/Amount/Amount.js.map +1 -1
  187. package/dist/components/typography/Code/Code.d.ts +8 -0
  188. package/dist/components/typography/Code/Code.js.map +1 -1
  189. package/dist/components/typography/Heading/Heading.d.ts +8 -0
  190. package/dist/components/typography/Heading/Heading.js.map +1 -1
  191. package/dist/components/typography/Kbd/Kbd.d.ts +6 -0
  192. package/dist/components/typography/Kbd/Kbd.js.map +1 -1
  193. package/dist/components/typography/Link/Link.d.ts +9 -0
  194. package/dist/components/typography/Link/Link.js.map +1 -1
  195. package/dist/components/typography/Numeral/Numeral.d.ts +8 -0
  196. package/dist/components/typography/Numeral/Numeral.js.map +1 -1
  197. package/dist/components/typography/Prose/Prose.d.ts +9 -0
  198. package/dist/components/typography/Prose/Prose.js.map +1 -1
  199. package/dist/components/typography/Quote/Quote.d.ts +7 -0
  200. package/dist/components/typography/Quote/Quote.js.map +1 -1
  201. package/dist/components/typography/SectionHeader/SectionHeader.d.ts +8 -0
  202. package/dist/components/typography/SectionHeader/SectionHeader.js.map +1 -1
  203. package/dist/components/typography/Text/Text.d.ts +8 -0
  204. package/dist/components/typography/Text/Text.js.map +1 -1
  205. package/dist/styles.css +1 -1
  206. package/package.json +1 -1
  207. package/skills/custom-theme/SKILL.md +1 -1
  208. package/skills/layout-composition/SKILL.md +37 -98
  209. package/skills/layout-composition/references/action-bar.md +4 -4
  210. package/skills/layout-composition/references/app-shell.md +11 -4
  211. package/skills/layout-composition/references/aspect-ratio.md +3 -1
  212. package/skills/layout-composition/references/box.md +7 -7
  213. package/skills/layout-composition/references/checkout.md +1 -1
  214. package/skills/layout-composition/references/container.md +15 -13
  215. package/skills/layout-composition/references/dashboard.md +1 -1
  216. package/skills/layout-composition/references/divider.md +5 -5
  217. package/skills/layout-composition/references/section.md +69 -0
  218. package/skills/layout-composition/references/settings.md +1 -1
  219. package/skills/layout-composition/references/visually-hidden.md +4 -4
  220. package/skills/setup-and-theming/SKILL.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Field.js","names":["styles","messageStyles","messages_module_default","Field_module_default","Field","forwardRef","rest","autoId","useId","childProps","isValidElement","children","id","htmlFor","labelId","descriptionId","warningId","errorId","help","description","hint","invalid","hasError","error","showErrorMessage","hasErrorMessage","errorHidden","showWarning","warning","effectiveLabelHidden","labelHidden","layout","describedBy","joinIds","context","useMemo","validating","required","disabled","readOnly","control","isFieldAware","cloneElement","cx","className","LabelContent","label","FieldDescription","FieldContext","FieldWarning","FieldError","errorLive"],"sources":["../../../../src/components/inputs/Field/Field.tsx"],"sourcesContent":["import {\n cloneElement,\n forwardRef,\n isValidElement,\n useId,\n useMemo,\n type HTMLAttributes,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { cx } from '#utils/cx'\nimport {\n FieldContext,\n isFieldAware,\n type FieldControlContext,\n} from '#components/inputs/internal/FieldContext'\nimport {\n FieldDescription,\n FieldError,\n FieldWarning,\n LabelContent,\n} from '#components/inputs/internal/messages'\nimport { hasError, hasErrorMessage } from '#components/inputs/internal/errors'\nimport messageStyles from '#components/inputs/internal/messages.module.css'\nimport { joinIds } from '#components/inputs/internal/refs'\nimport styles from './Field.module.css'\n\n/** Props handed to a render-function child — spread them onto your control. */\nexport interface FieldRenderProps {\n id: string\n 'aria-describedby'?: string\n 'aria-invalid'?: true\n 'aria-required'?: true\n /** The Field's `required`. Spreading it onto a native input also turns on browser validation. */\n required: boolean\n disabled?: boolean\n}\n\n/** `Field`'s (and `Fieldset`'s) `layout` values. */\nexport type FieldLayout = 'stack' | 'horizontal' | 'inline'\n\n/** The label/description/error props every field shares (TextField, SelectField… extend these). */\nexport interface FieldLabelProps {\n /** Visible label. Keep it short; put guidance in `description`. */\n label: ReactNode\n /** Help text under the label, linked to the control with `aria-describedby`. */\n description?: ReactNode\n /** Alias of `description` (the legacy name). */\n hint?: ReactNode\n /**\n * Validation message. When present the control gets `aria-invalid` and its border (only)\n * turns critical. Pass `true` to mark invalid without a message.\n */\n error?: ReactNode\n /** Shows a mark after the label and sets `aria-required` on the control. */\n required?: boolean\n /** Shows \"Optional\" after the label (or your own word). Ignored when `required`. */\n optional?: boolean | string\n /** Keep the label for assistive tech only — e.g. a search box beside a visible heading. */\n labelHidden?: boolean\n /**\n * Non-blocking advice (\"Usernames are case-sensitive\"). Shown under the control in a\n * caution tone, never `role=\"alert\"`. Hidden while an error message is shown.\n */\n warning?: ReactNode\n /**\n * Whether the error message is `role=\"alert\"` (announced as soon as it appears).\n * Default `true`. Pass `false` once a form-level `ErrorSummary` owns the announcement\n * — the message stays in `aria-describedby` either way.\n */\n errorLive?: boolean\n /**\n * Keeps the invalid state (border, `aria-invalid`) but doesn't render the error text —\n * for layouts that show the message elsewhere (a sentence, a table cell).\n */\n errorHidden?: boolean\n /** Shows a small spinner after the label and sets `aria-busy` on the control. */\n validating?: boolean\n /** Focusable but not editable. Controls set native `readOnly`/`aria-readonly`. */\n readOnly?: boolean\n /**\n * `stack` (default) — label above the control.\n * `horizontal` — label/description in one column, the control in another (collapses\n * to a stack below `sm`).\n * `inline` — the label is visually hidden and the control sits in text flow.\n */\n layout?: FieldLayout\n}\n\nexport interface FieldProps\n extends FieldLabelProps, Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n /** Id for the control. Defaults to the child's own `id`, then a generated one. */\n htmlFor?: string\n /** Disables the control inside and dims the label. */\n disabled?: boolean\n /**\n * The control. Library controls pick the wiring up from context; any other single element\n * gets `id` / `aria-*` cloned in; or pass a function and spread its props yourself.\n */\n children: ReactNode | ((props: FieldRenderProps) => ReactNode)\n}\n\ninterface ControlElementProps {\n id?: string\n 'aria-describedby'?: string\n 'aria-invalid'?: unknown\n}\n\n/**\n * Wires a label, description and error to one control.\n *\n * <Field label=\"Amount\" description=\"Include GST\" error={errors.amount}>\n * <Input numeric leading=\"$\" />\n * </Field>\n */\nexport const Field = forwardRef<HTMLDivElement, FieldProps>(function Field(\n {\n label,\n description,\n hint,\n error,\n required = false,\n optional,\n labelHidden = false,\n warning,\n errorLive = true,\n errorHidden = false,\n validating = false,\n readOnly = false,\n layout = 'stack',\n htmlFor,\n disabled = false,\n className,\n children,\n ...rest\n },\n ref,\n) {\n const autoId = useId()\n const childProps = isValidElement(children) ? (children.props as ControlElementProps) : undefined\n const id = htmlFor ?? childProps?.id ?? `${autoId}control`\n const labelId = `${id}-label`\n const descriptionId = `${id}-description`\n const warningId = `${id}-warning`\n const errorId = `${id}-error`\n\n const help = description ?? hint\n const invalid = hasError(error)\n const showErrorMessage = hasErrorMessage(error) && !errorHidden\n const showWarning = warning != null && !showErrorMessage\n const effectiveLabelHidden = labelHidden || layout === 'inline'\n const describedBy = joinIds(\n help != null && descriptionId,\n showWarning && warningId,\n showErrorMessage && errorId,\n )\n\n const context = useMemo<FieldControlContext>(\n () => ({\n id,\n labelId,\n describedBy,\n invalid,\n required,\n disabled,\n readOnly,\n busy: validating,\n warningId,\n }),\n [id, labelId, describedBy, invalid, required, disabled, readOnly, validating, warningId],\n )\n\n let control: ReactNode\n if (typeof children === 'function') {\n control = children({\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': invalid || undefined,\n 'aria-required': required || undefined,\n required,\n disabled: disabled || undefined,\n })\n } else if (isValidElement(children) && !isFieldAware(children.type)) {\n control = cloneElement(\n children as ReactElement<ControlElementProps & Record<string, unknown>>,\n {\n id,\n 'aria-describedby': joinIds(childProps?.['aria-describedby'], describedBy),\n 'aria-invalid': invalid\n ? true\n : childProps?.['aria-invalid'] === false\n ? undefined\n : childProps?.['aria-invalid'],\n 'aria-required': required || undefined,\n },\n )\n } else {\n control = children\n }\n\n return (\n <div\n ref={ref}\n className={cx(styles.field, className)}\n data-invalid={invalid || undefined}\n data-disabled={disabled || undefined}\n data-readonly={readOnly || undefined}\n data-label-hidden={effectiveLabelHidden || undefined}\n data-layout={layout}\n {...rest}\n >\n <div className={styles.header}>\n <label\n id={labelId}\n htmlFor={id}\n className={cx(messageStyles.label, effectiveLabelHidden && messageStyles.visuallyHidden)}\n >\n <LabelContent required={required} optional={optional} busy={validating}>\n {label}\n </LabelContent>\n </label>\n {help != null ? <FieldDescription id={descriptionId}>{help}</FieldDescription> : null}\n </div>\n <div className={styles.controlSlot}>\n <FieldContext.Provider value={context}>{control}</FieldContext.Provider>\n </div>\n {showWarning ? (\n <div className={styles.warningSlot}>\n <FieldWarning id={warningId}>{warning}</FieldWarning>\n </div>\n ) : null}\n {showErrorMessage ? (\n <div className={styles.errorSlot}>\n <FieldError id={errorId} live={errorLive}>\n {error}\n </FieldError>\n </div>\n ) : null}\n </div>\n )\n})\n"],"mappings":";;;;;;;;;;AAmHA,IAAaI,IAAQC,EAAuC,SAC1D,EACE,UACA,gBACA,SACA,UACA,cAAW,IACX,aACA,iBAAc,IACd,YACA,eAAY,IACZ,iBAAc,IACd,gBAAa,IACb,cAAW,IACX,YAAS,SACT,YACA,cAAW,IACX,cACA,aACA,GAAGC,KAEL,GACA;CACA,IAAMC,IAASC,EAAM,GACfC,IAAaC,EAAeC,CAAQ,IAAKA,EAAS,QAAgC,KAAA,GAClFC,IAAKC,KAAWJ,GAAY,MAAM,GAAGF,EAAO,UAC5CO,IAAU,GAAGF,EAAG,SAChBG,IAAgB,GAAGH,EAAG,eACtBI,IAAY,GAAGJ,EAAG,WAClBK,IAAU,GAAGL,EAAG,SAEhBM,IAAOC,KAAeC,GACtBC,IAAUC,EAASC,CAAK,GACxBC,IAAmBC,EAAgBF,CAAK,KAAK,CAACG,GAC9CC,IAAcC,KAAW,QAAQ,CAACJ,GAClCK,IAAuBC,KAAeC,MAAW,UACjDC,IAAcC,EAClBf,KAAQ,QAAQH,GAChBY,KAAeX,GACfQ,KAAoBP,CACtB,GAEMiB,IAAUC,SACP;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMC;EACN;CACF,IACA;EAACxB;EAAIE;EAASkB;EAAaX;EAASgB;EAAUC;EAAUC;EAAUH;EAAYpB;CAAS,CACzF,GAEIwB;CA4BJ,OA3BA,AAwBEA,IAxBE,OAAO7B,KAAa,aACZA,EAAS;EACjB;EACA,oBAAoBqB;EACpB,gBAAgBX,KAAW,KAAA;EAC3B,iBAAiBgB,KAAY,KAAA;EAC7B;EACA,UAAUC,KAAY,KAAA;CACxB,CAAC,IACQ5B,EAAeC,CAAQ,KAAK,CAAC8B,EAAa9B,EAAS,IAAI,IACtD+B,EACR/B,GACA;EACE;EACA,oBAAoBsB,EAAQxB,IAAa,qBAAqBuB,CAAW;EACzE,gBAAgBX,IACZ,KACAZ,IAAa,oBAAoB,KAC/B,KAAA,IACAA,IAAa;EACnB,iBAAiB4B,KAAY,KAAA;CAC/B,CACF,IAEU1B,GAIV,kBAAC,OAAD;EACO;EACL,WAAWgC,EAAG3C,EAAO,OAAO4C,CAAS;EACrC,gBAAcvB,KAAW,KAAA;EACzB,iBAAeiB,KAAY,KAAA;EAC3B,iBAAeC,KAAY,KAAA;EAC3B,qBAAmBV,KAAwB,KAAA;EAC3C,eAAaE;EACb,GAAIzB;EARN,UAAA;GAUE,kBAAC,OAAD;IAAK,WAAWN,EAAO;IAAvB,UAAA,CACE,kBAAC,SAAD;KACE,IAAIc;KACJ,SAASF;KACT,WAAW+B,EAAG1C,EAAc,OAAO4B,KAAwB5B,EAAc,cAAc;KAEvF,UAAA,kBAAC4C,GAAD;MAAwB;MAAoB;MAAU,MAAMT;MACzD,UAAAU;KACW,CAAA;IACT,CAAA,GACN5B,KAAQ,OAAwE,OAAjE,kBAAC6B,GAAD;KAAkB,IAAIhC;KAAgB,UAAAG;IAAuB,CAAA,CAC1E;;GACL,kBAAC,OAAD;IAAK,WAAWlB,EAAO;IACrB,UAAA,kBAACgD,EAAa,UAAd;KAAuB,OAAOd;KAAU,UAAAM;IAA+B,CAAA;GACpE,CAAA;GACJb,IACC,kBAAC,OAAD;IAAK,WAAW3B,EAAO;IACrB,UAAA,kBAACiD,GAAD;KAAc,IAAIjC;KAAY,UAAAY;IAAsB,CAAA;GACjD,CAAA,IACH;GACHJ,IACC,kBAAC,OAAD;IAAK,WAAWxB,EAAO;IACrB,UAAA,kBAACkD,GAAD;KAAY,IAAIjC;KAAS,MAAMkC;KAC5B,UAAA5B;IACS,CAAA;GACT,CAAA,IACH;EACD;;AAET,CAAC"}
1
+ {"version":3,"file":"Field.js","names":["styles","messageStyles","messages_module_default","Field_module_default","Field","forwardRef","rest","autoId","useId","childProps","isValidElement","children","id","htmlFor","labelId","descriptionId","warningId","errorId","help","description","hint","invalid","hasError","error","showErrorMessage","hasErrorMessage","errorHidden","showWarning","warning","effectiveLabelHidden","labelHidden","layout","describedBy","joinIds","context","useMemo","validating","required","disabled","readOnly","control","isFieldAware","cloneElement","cx","className","LabelContent","label","FieldDescription","FieldContext","FieldWarning","FieldError","errorLive"],"sources":["../../../../src/components/inputs/Field/Field.tsx"],"sourcesContent":["import {\n cloneElement,\n forwardRef,\n isValidElement,\n useId,\n useMemo,\n type HTMLAttributes,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { cx } from '#utils/cx'\nimport {\n FieldContext,\n isFieldAware,\n type FieldControlContext,\n} from '#components/inputs/internal/FieldContext'\nimport {\n FieldDescription,\n FieldError,\n FieldWarning,\n LabelContent,\n} from '#components/inputs/internal/messages'\nimport { hasError, hasErrorMessage } from '#components/inputs/internal/errors'\nimport messageStyles from '#components/inputs/internal/messages.module.css'\nimport { joinIds } from '#components/inputs/internal/refs'\nimport styles from './Field.module.css'\n\n/** Props handed to a render-function child — spread them onto your control. */\nexport interface FieldRenderProps {\n id: string\n 'aria-describedby'?: string\n 'aria-invalid'?: true\n 'aria-required'?: true\n /** The Field's `required`. Spreading it onto a native input also turns on browser validation. */\n required: boolean\n disabled?: boolean\n}\n\n/** `Field`'s (and `Fieldset`'s) `layout` values. */\nexport type FieldLayout = 'stack' | 'horizontal' | 'inline'\n\n/** The label/description/error props every field shares (TextField, SelectField… extend these). */\nexport interface FieldLabelProps {\n /** Visible label. Keep it short; put guidance in `description`. */\n label: ReactNode\n /** Help text under the label, linked to the control with `aria-describedby`. */\n description?: ReactNode\n /** Alias of `description` (the legacy name). */\n hint?: ReactNode\n /**\n * Validation message. When present the control gets `aria-invalid` and its border (only)\n * turns critical. Pass `true` to mark invalid without a message.\n */\n error?: ReactNode\n /** Shows a mark after the label and sets `aria-required` on the control. */\n required?: boolean\n /** Shows \"Optional\" after the label (or your own word). Ignored when `required`. */\n optional?: boolean | string\n /** Keep the label for assistive tech only — e.g. a search box beside a visible heading. */\n labelHidden?: boolean\n /**\n * Non-blocking advice (\"Usernames are case-sensitive\"). Shown under the control in a\n * caution tone, never `role=\"alert\"`. Hidden while an error message is shown.\n */\n warning?: ReactNode\n /**\n * Whether the error message is `role=\"alert\"` (announced as soon as it appears).\n * Default `true`. Pass `false` once a form-level `ErrorSummary` owns the announcement\n * — the message stays in `aria-describedby` either way.\n */\n errorLive?: boolean\n /**\n * Keeps the invalid state (border, `aria-invalid`) but doesn't render the error text —\n * for layouts that show the message elsewhere (a sentence, a table cell).\n */\n errorHidden?: boolean\n /** Shows a small spinner after the label and sets `aria-busy` on the control. */\n validating?: boolean\n /** Focusable but not editable. Controls set native `readOnly`/`aria-readonly`. */\n readOnly?: boolean\n /**\n * `stack` (default) — label above the control.\n * `horizontal` — label/description in one column, the control in another (collapses\n * to a stack below `sm`).\n * `inline` — the label is visually hidden and the control sits in text flow.\n */\n layout?: FieldLayout\n}\n\nexport interface FieldProps\n extends FieldLabelProps, Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n /** Id for the control. Defaults to the child's own `id`, then a generated one. */\n htmlFor?: string\n /** Disables the control inside and dims the label. */\n disabled?: boolean\n /**\n * The control. Library controls pick the wiring up from context; any other single element\n * gets `id` / `aria-*` cloned in; or pass a function and spread its props yourself.\n */\n children: ReactNode | ((props: FieldRenderProps) => ReactNode)\n}\n\ninterface ControlElementProps {\n id?: string\n 'aria-describedby'?: string\n 'aria-invalid'?: unknown\n}\n\n/**\n * Wires a label, a description, an error and a warning to one control. Every Kiln input works\n * inside it.\n *\n * @remarks\n * Every input in Kiln exists twice. The bare control (`Input`, `NumberInput`, `Combobox`) works\n * anywhere, and the `*Field` version (`TextField`, `NumberField`, `ComboboxField`) adds the label,\n * description, error and warning. `Field` is what the `*Field`s are made of, and what you use to\n * give any other control the same treatment.\n *\n * ## Errors and warnings are two channels\n *\n * - **`error`** blocks submission. It sets `aria-invalid`, renders in the critical tone, and is\n * announced as it appears.\n * - **`warning`** is advice (\"Usernames are case-sensitive\") in the caution tone. It never blocks\n * and is never announced as an alert.\n *\n * An error replaces the warning while it shows. Plain fields show whatever `error` you pass;\n * [kiln-forms](/docs/forms) decides _when_ to show one (after the field is blurred or after a\n * submit attempt). Pass `errorLive={false}` when an error summary owns the announcement, and\n * `errorHidden` when the layout shows the message somewhere else.\n *\n * `required` marks the field for assistive technology; `optional` adds a visible \"Optional\" (or\n * your own text) instead, for forms where most fields are required. `validating` shows that an\n * async check is running.\n *\n * @privateRemarks\n * Wires a label, description and error to one control.\n *\n * <Field label=\"Amount\" description=\"Include GST\" error={errors.amount}>\n * <Input numeric leading=\"$\" />\n * </Field>\n */\nexport const Field = forwardRef<HTMLDivElement, FieldProps>(function Field(\n {\n label,\n description,\n hint,\n error,\n required = false,\n optional,\n labelHidden = false,\n warning,\n errorLive = true,\n errorHidden = false,\n validating = false,\n readOnly = false,\n layout = 'stack',\n htmlFor,\n disabled = false,\n className,\n children,\n ...rest\n },\n ref,\n) {\n const autoId = useId()\n const childProps = isValidElement(children) ? (children.props as ControlElementProps) : undefined\n const id = htmlFor ?? childProps?.id ?? `${autoId}control`\n const labelId = `${id}-label`\n const descriptionId = `${id}-description`\n const warningId = `${id}-warning`\n const errorId = `${id}-error`\n\n const help = description ?? hint\n const invalid = hasError(error)\n const showErrorMessage = hasErrorMessage(error) && !errorHidden\n const showWarning = warning != null && !showErrorMessage\n const effectiveLabelHidden = labelHidden || layout === 'inline'\n const describedBy = joinIds(\n help != null && descriptionId,\n showWarning && warningId,\n showErrorMessage && errorId,\n )\n\n const context = useMemo<FieldControlContext>(\n () => ({\n id,\n labelId,\n describedBy,\n invalid,\n required,\n disabled,\n readOnly,\n busy: validating,\n warningId,\n }),\n [id, labelId, describedBy, invalid, required, disabled, readOnly, validating, warningId],\n )\n\n let control: ReactNode\n if (typeof children === 'function') {\n control = children({\n id,\n 'aria-describedby': describedBy,\n 'aria-invalid': invalid || undefined,\n 'aria-required': required || undefined,\n required,\n disabled: disabled || undefined,\n })\n } else if (isValidElement(children) && !isFieldAware(children.type)) {\n control = cloneElement(\n children as ReactElement<ControlElementProps & Record<string, unknown>>,\n {\n id,\n 'aria-describedby': joinIds(childProps?.['aria-describedby'], describedBy),\n 'aria-invalid': invalid\n ? true\n : childProps?.['aria-invalid'] === false\n ? undefined\n : childProps?.['aria-invalid'],\n 'aria-required': required || undefined,\n },\n )\n } else {\n control = children\n }\n\n return (\n <div\n ref={ref}\n className={cx(styles.field, className)}\n data-invalid={invalid || undefined}\n data-disabled={disabled || undefined}\n data-readonly={readOnly || undefined}\n data-label-hidden={effectiveLabelHidden || undefined}\n data-layout={layout}\n {...rest}\n >\n <div className={styles.header}>\n <label\n id={labelId}\n htmlFor={id}\n className={cx(messageStyles.label, effectiveLabelHidden && messageStyles.visuallyHidden)}\n >\n <LabelContent required={required} optional={optional} busy={validating}>\n {label}\n </LabelContent>\n </label>\n {help != null ? <FieldDescription id={descriptionId}>{help}</FieldDescription> : null}\n </div>\n <div className={styles.controlSlot}>\n <FieldContext.Provider value={context}>{control}</FieldContext.Provider>\n </div>\n {showWarning ? (\n <div className={styles.warningSlot}>\n <FieldWarning id={warningId}>{warning}</FieldWarning>\n </div>\n ) : null}\n {showErrorMessage ? (\n <div className={styles.errorSlot}>\n <FieldError id={errorId} live={errorLive}>\n {error}\n </FieldError>\n </div>\n ) : null}\n </div>\n )\n})\n"],"mappings":";;;;;;;;;;AA6IA,IAAaI,IAAQC,EAAuC,SAC1D,EACE,UACA,gBACA,SACA,UACA,cAAW,IACX,aACA,iBAAc,IACd,YACA,eAAY,IACZ,iBAAc,IACd,gBAAa,IACb,cAAW,IACX,YAAS,SACT,YACA,cAAW,IACX,cACA,aACA,GAAGC,KAEL,GACA;CACA,IAAMC,IAASC,EAAM,GACfC,IAAaC,EAAeC,CAAQ,IAAKA,EAAS,QAAgC,KAAA,GAClFC,IAAKC,KAAWJ,GAAY,MAAM,GAAGF,EAAO,UAC5CO,IAAU,GAAGF,EAAG,SAChBG,IAAgB,GAAGH,EAAG,eACtBI,IAAY,GAAGJ,EAAG,WAClBK,IAAU,GAAGL,EAAG,SAEhBM,IAAOC,KAAeC,GACtBC,IAAUC,EAASC,CAAK,GACxBC,IAAmBC,EAAgBF,CAAK,KAAK,CAACG,GAC9CC,IAAcC,KAAW,QAAQ,CAACJ,GAClCK,IAAuBC,KAAeC,MAAW,UACjDC,IAAcC,EAClBf,KAAQ,QAAQH,GAChBY,KAAeX,GACfQ,KAAoBP,CACtB,GAEMiB,IAAUC,SACP;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMC;EACN;CACF,IACA;EAACxB;EAAIE;EAASkB;EAAaX;EAASgB;EAAUC;EAAUC;EAAUH;EAAYpB;CAAS,CACzF,GAEIwB;CA4BJ,OA3BA,AAwBEA,IAxBE,OAAO7B,KAAa,aACZA,EAAS;EACjB;EACA,oBAAoBqB;EACpB,gBAAgBX,KAAW,KAAA;EAC3B,iBAAiBgB,KAAY,KAAA;EAC7B;EACA,UAAUC,KAAY,KAAA;CACxB,CAAC,IACQ5B,EAAeC,CAAQ,KAAK,CAAC8B,EAAa9B,EAAS,IAAI,IACtD+B,EACR/B,GACA;EACE;EACA,oBAAoBsB,EAAQxB,IAAa,qBAAqBuB,CAAW;EACzE,gBAAgBX,IACZ,KACAZ,IAAa,oBAAoB,KAC/B,KAAA,IACAA,IAAa;EACnB,iBAAiB4B,KAAY,KAAA;CAC/B,CACF,IAEU1B,GAIV,kBAAC,OAAD;EACO;EACL,WAAWgC,EAAG3C,EAAO,OAAO4C,CAAS;EACrC,gBAAcvB,KAAW,KAAA;EACzB,iBAAeiB,KAAY,KAAA;EAC3B,iBAAeC,KAAY,KAAA;EAC3B,qBAAmBV,KAAwB,KAAA;EAC3C,eAAaE;EACb,GAAIzB;EARN,UAAA;GAUE,kBAAC,OAAD;IAAK,WAAWN,EAAO;IAAvB,UAAA,CACE,kBAAC,SAAD;KACE,IAAIc;KACJ,SAASF;KACT,WAAW+B,EAAG1C,EAAc,OAAO4B,KAAwB5B,EAAc,cAAc;KAEvF,UAAA,kBAAC4C,GAAD;MAAwB;MAAoB;MAAU,MAAMT;MACzD,UAAAU;KACW,CAAA;IACT,CAAA,GACN5B,KAAQ,OAAwE,OAAjE,kBAAC6B,GAAD;KAAkB,IAAIhC;KAAgB,UAAAG;IAAuB,CAAA,CAC1E;;GACL,kBAAC,OAAD;IAAK,WAAWlB,EAAO;IACrB,UAAA,kBAACgD,EAAa,UAAd;KAAuB,OAAOd;KAAU,UAAAM;IAA+B,CAAA;GACpE,CAAA;GACJb,IACC,kBAAC,OAAD;IAAK,WAAW3B,EAAO;IACrB,UAAA,kBAACiD,GAAD;KAAc,IAAIjC;KAAY,UAAAY;IAAsB,CAAA;GACjD,CAAA,IACH;GACHJ,IACC,kBAAC,OAAD;IAAK,WAAWxB,EAAO;IACrB,UAAA,kBAACkD,GAAD;KAAY,IAAIjC;KAAS,MAAMkC;KAC5B,UAAA5B;IACS,CAAA;GACT,CAAA,IACH;EACD;;AAET,CAAC"}
@@ -36,6 +36,14 @@ export interface FieldsetProps extends FieldsetHTMLAttributes<HTMLFieldSetElemen
36
36
  layout?: FieldLayout;
37
37
  }
38
38
  /**
39
+ * Groups related controls under one legend, with a description and an error for the group.
40
+ *
41
+ * @remarks
42
+ * `Fieldset` is a `<fieldset>` and `<legend>` with Kiln's styles: an address block, a set of
43
+ * checkboxes, a radio group. Screen readers announce the legend with each control inside, so
44
+ * "Town" becomes "Delivery address, Town".
45
+ *
46
+ * @privateRemarks
39
47
  * Groups related controls under a legend: a RadioGroup, a set of CheckboxFields, or an
40
48
  * address block. `disabled` disables every control inside (native fieldset behaviour).
41
49
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Fieldset.js","names":["styles","messageStyles","messages_module_default","Fieldset_module_default","Fieldset","forwardRef","describedByProp","rest","id","useId","legendId","descriptionId","warningId","errorId","showErrorMessage","hasErrorMessage","error","errorHidden","showWarning","warning","invalid","hasError","disabled","effectiveLegendHidden","legendHidden","layout","context","useMemo","validating","required","readOnly","cx","className","variant","joinIds","description","LabelContent","legend","FieldDescription","FieldContext","FieldWarning","FieldError","errorLive"],"sources":["../../../../src/components/inputs/Fieldset/Fieldset.tsx"],"sourcesContent":["import { forwardRef, useId, useMemo, type FieldsetHTMLAttributes, type ReactNode } from 'react'\nimport { cx } from '#utils/cx'\nimport { FieldContext, type FieldControlContext } from '#components/inputs/internal/FieldContext'\nimport {\n FieldDescription,\n FieldError,\n FieldWarning,\n LabelContent,\n} from '#components/inputs/internal/messages'\nimport { hasError, hasErrorMessage } from '#components/inputs/internal/errors'\nimport messageStyles from '#components/inputs/internal/messages.module.css'\nimport { joinIds } from '#components/inputs/internal/refs'\nimport type { FieldLayout } from '#components/inputs/Field'\nimport styles from './Fieldset.module.css'\n\nexport type FieldsetVariant = 'default' | 'section'\n\nexport interface FieldsetProps extends FieldsetHTMLAttributes<HTMLFieldSetElement> {\n /** The group's name — \"Plan\", \"Notify me about\". */\n legend: ReactNode\n description?: ReactNode\n /** A message about the group as a whole (\"Choose at least one\"). */\n error?: ReactNode\n /** Visual mark after the legend. Put `required` on the controls (or RadioGroup) too. */\n required?: boolean\n optional?: boolean | string\n legendHidden?: boolean\n /**\n * `default` — the legend reads like a field label (a radio group, a checkbox set).\n * `section` — the legend is set as a small heading with a hairline under it, for a\n * form broken into groups of fields (\"Your details\", \"Payment\"), so groups read as\n * groups rather than as one more label.\n */\n variant?: FieldsetVariant\n /** Non-blocking advice about the group as a whole. Hidden while an error message is shown. */\n warning?: ReactNode\n /** Whether the error message is `role=\"alert\"`. Default `true`. */\n errorLive?: boolean\n /** Keeps the invalid state but doesn't render the error text. */\n errorHidden?: boolean\n /** Shows a small spinner after the legend and sets `aria-busy` on every control inside. */\n validating?: boolean\n /** Every control inside sets native `readOnly`/`aria-readonly` and ignores changes. */\n readOnly?: boolean\n /**\n * Same values as `Field`'s `layout`. `stack` (default) puts the legend above the group;\n * `horizontal` puts it in a label column beside the group from `sm` up (matching a\n * horizontal `Field`), stacking below; `inline` hides the legend (like `legendHidden`).\n */\n layout?: FieldLayout\n}\n\n/**\n * Groups related controls under a legend: a RadioGroup, a set of CheckboxFields, or an\n * address block. `disabled` disables every control inside (native fieldset behaviour).\n *\n * <Fieldset legend=\"Plan\" description=\"Pay by card or bank transfer\">\n * <RadioGroup …/>\n * </Fieldset>\n *\n * <Fieldset variant=\"section\" legend=\"Your details\">…TextFields…</Fieldset>\n */\nexport const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(function Fieldset(\n {\n legend,\n description,\n error,\n required = false,\n optional,\n legendHidden = false,\n variant = 'default',\n warning,\n errorLive = true,\n errorHidden = false,\n validating = false,\n readOnly = false,\n layout = 'stack',\n className,\n children,\n 'aria-describedby': describedByProp,\n ...rest\n },\n ref,\n) {\n const id = useId()\n const legendId = `${id}legend`\n const descriptionId = `${id}description`\n const warningId = `${id}warning`\n const errorId = `${id}error`\n const showErrorMessage = hasErrorMessage(error) && !errorHidden\n const showWarning = warning != null && !showErrorMessage\n const invalid = hasError(error)\n const disabled = rest.disabled ?? false\n const effectiveLegendHidden = legendHidden || layout === 'inline'\n\n const context = useMemo<FieldControlContext>(\n () => ({\n id,\n labelId: legendId,\n invalid,\n required,\n disabled,\n readOnly,\n busy: validating,\n warningId,\n fieldset: true,\n }),\n [id, legendId, invalid, required, disabled, readOnly, validating, warningId],\n )\n\n return (\n <fieldset\n ref={ref}\n className={cx(styles.fieldset, className)}\n data-variant={variant}\n data-layout={layout}\n data-invalid={invalid || undefined}\n data-disabled={disabled || undefined}\n data-readonly={readOnly || undefined}\n aria-describedby={joinIds(\n describedByProp,\n description != null && descriptionId,\n showWarning && warningId,\n showErrorMessage && errorId,\n )}\n {...rest}\n >\n <legend\n id={legendId}\n className={cx(\n messageStyles.label,\n styles.legend,\n effectiveLegendHidden && messageStyles.visuallyHidden,\n )}\n data-disabled={disabled || undefined}\n >\n <LabelContent required={required} optional={optional} busy={validating}>\n {legend}\n </LabelContent>\n </legend>\n <div className={styles.body}>\n {description != null ? (\n <FieldDescription id={descriptionId} className={styles.description}>\n {description}\n </FieldDescription>\n ) : null}\n <div className={styles.controls}>\n <FieldContext.Provider value={context}>{children}</FieldContext.Provider>\n </div>\n {showWarning ? <FieldWarning id={warningId}>{warning}</FieldWarning> : null}\n {showErrorMessage ? (\n <FieldError id={errorId} live={errorLive}>\n {error}\n </FieldError>\n ) : null}\n </div>\n </fieldset>\n )\n})\n"],"mappings":";;;;;;;;;;AA8DA,IAAaI,IAAWC,EAA+C,SACrE,EACE,WACA,gBACA,UACA,cAAW,IACX,aACA,kBAAe,IACf,aAAU,WACV,YACA,eAAY,IACZ,iBAAc,IACd,gBAAa,IACb,cAAW,IACX,YAAS,SACT,cACA,aACA,oBAAoBC,GACpB,GAAGC,KAEL,GACA;CACA,IAAMC,IAAKC,EAAM,GACXC,IAAW,GAAGF,EAAG,SACjBG,IAAgB,GAAGH,EAAG,cACtBI,IAAY,GAAGJ,EAAG,UAClBK,IAAU,GAAGL,EAAG,QAChBM,IAAmBC,EAAgBC,CAAK,KAAK,CAACC,GAC9CC,IAAcC,KAAW,QAAQ,CAACL,GAClCM,IAAUC,EAASL,CAAK,GACxBM,IAAWf,EAAK,YAAY,IAC5BgB,IAAwBC,KAAgBC,MAAW,UAEnDC,IAAUC,SACP;EACL;EACA,SAASjB;EACT;EACA;EACA;EACA;EACA,MAAMkB;EACN;EACA,UAAU;CACZ,IACA;EAACpB;EAAIE;EAAUU;EAASS;EAAUP;EAAUQ;EAAUF;EAAYhB;CAAS,CAC7E;CAEA,OACE,kBAAC,YAAD;EACO;EACL,WAAWmB,EAAG/B,EAAO,UAAUgC,CAAS;EACxC,gBAAcC;EACd,eAAaR;EACb,gBAAcL,KAAW,KAAA;EACzB,iBAAeE,KAAY,KAAA;EAC3B,iBAAeQ,KAAY,KAAA;EAC3B,oBAAkBI,EAChB5B,GACA6B,KAAe,QAAQxB,GACvBO,KAAeN,GACfE,KAAoBD,CACtB;EACA,GAAIN;EAdN,UAAA,CAgBE,kBAAC,UAAD;GACE,IAAIG;GACJ,WAAWqB,EACT9B,EAAc,OACdD,EAAO,QACPuB,KAAyBtB,EAAc,cACzC;GACA,iBAAeqB,KAAY,KAAA;GAE3B,UAAA,kBAACc,GAAD;IAAwB;IAAoB;IAAU,MAAMR;IACzD,UAAAS;GACW,CAAA;EACR,CAAA,GACR,kBAAC,OAAD;GAAK,WAAWrC,EAAO;GAAvB,UAAA;IACGmC,KAAe,OAIZ,OAHF,kBAACG,GAAD;KAAkB,IAAI3B;KAAe,WAAWX,EAAO;KACpD,UAAAmC;IACe,CAAA;IAEpB,kBAAC,OAAD;KAAK,WAAWnC,EAAO;KACrB,UAAA,kBAACuC,EAAa,UAAd;MAAuB,OAAOb;MAAU;KAAgC,CAAA;IACrE,CAAA;IACJR,IAAc,kBAACsB,GAAD;KAAc,IAAI5B;KAAY,UAAAO;IAAsB,CAAA,IAAI;IACtEL,IACC,kBAAC2B,GAAD;KAAY,IAAI5B;KAAS,MAAM6B;KAC5B,UAAA1B;IACS,CAAA,IACV;GACD;EACG,CAAA,CAAA;;AAEd,CAAC"}
1
+ {"version":3,"file":"Fieldset.js","names":["styles","messageStyles","messages_module_default","Fieldset_module_default","Fieldset","forwardRef","describedByProp","rest","id","useId","legendId","descriptionId","warningId","errorId","showErrorMessage","hasErrorMessage","error","errorHidden","showWarning","warning","invalid","hasError","disabled","effectiveLegendHidden","legendHidden","layout","context","useMemo","validating","required","readOnly","cx","className","variant","joinIds","description","LabelContent","legend","FieldDescription","FieldContext","FieldWarning","FieldError","errorLive"],"sources":["../../../../src/components/inputs/Fieldset/Fieldset.tsx"],"sourcesContent":["import { forwardRef, useId, useMemo, type FieldsetHTMLAttributes, type ReactNode } from 'react'\nimport { cx } from '#utils/cx'\nimport { FieldContext, type FieldControlContext } from '#components/inputs/internal/FieldContext'\nimport {\n FieldDescription,\n FieldError,\n FieldWarning,\n LabelContent,\n} from '#components/inputs/internal/messages'\nimport { hasError, hasErrorMessage } from '#components/inputs/internal/errors'\nimport messageStyles from '#components/inputs/internal/messages.module.css'\nimport { joinIds } from '#components/inputs/internal/refs'\nimport type { FieldLayout } from '#components/inputs/Field'\nimport styles from './Fieldset.module.css'\n\nexport type FieldsetVariant = 'default' | 'section'\n\nexport interface FieldsetProps extends FieldsetHTMLAttributes<HTMLFieldSetElement> {\n /** The group's name — \"Plan\", \"Notify me about\". */\n legend: ReactNode\n description?: ReactNode\n /** A message about the group as a whole (\"Choose at least one\"). */\n error?: ReactNode\n /** Visual mark after the legend. Put `required` on the controls (or RadioGroup) too. */\n required?: boolean\n optional?: boolean | string\n legendHidden?: boolean\n /**\n * `default` — the legend reads like a field label (a radio group, a checkbox set).\n * `section` — the legend is set as a small heading with a hairline under it, for a\n * form broken into groups of fields (\"Your details\", \"Payment\"), so groups read as\n * groups rather than as one more label.\n */\n variant?: FieldsetVariant\n /** Non-blocking advice about the group as a whole. Hidden while an error message is shown. */\n warning?: ReactNode\n /** Whether the error message is `role=\"alert\"`. Default `true`. */\n errorLive?: boolean\n /** Keeps the invalid state but doesn't render the error text. */\n errorHidden?: boolean\n /** Shows a small spinner after the legend and sets `aria-busy` on every control inside. */\n validating?: boolean\n /** Every control inside sets native `readOnly`/`aria-readonly` and ignores changes. */\n readOnly?: boolean\n /**\n * Same values as `Field`'s `layout`. `stack` (default) puts the legend above the group;\n * `horizontal` puts it in a label column beside the group from `sm` up (matching a\n * horizontal `Field`), stacking below; `inline` hides the legend (like `legendHidden`).\n */\n layout?: FieldLayout\n}\n\n/**\n * Groups related controls under one legend, with a description and an error for the group.\n *\n * @remarks\n * `Fieldset` is a `<fieldset>` and `<legend>` with Kiln's styles: an address block, a set of\n * checkboxes, a radio group. Screen readers announce the legend with each control inside, so\n * \"Town\" becomes \"Delivery address, Town\".\n *\n * @privateRemarks\n * Groups related controls under a legend: a RadioGroup, a set of CheckboxFields, or an\n * address block. `disabled` disables every control inside (native fieldset behaviour).\n *\n * <Fieldset legend=\"Plan\" description=\"Pay by card or bank transfer\">\n * <RadioGroup …/>\n * </Fieldset>\n *\n * <Fieldset variant=\"section\" legend=\"Your details\">…TextFields…</Fieldset>\n */\nexport const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(function Fieldset(\n {\n legend,\n description,\n error,\n required = false,\n optional,\n legendHidden = false,\n variant = 'default',\n warning,\n errorLive = true,\n errorHidden = false,\n validating = false,\n readOnly = false,\n layout = 'stack',\n className,\n children,\n 'aria-describedby': describedByProp,\n ...rest\n },\n ref,\n) {\n const id = useId()\n const legendId = `${id}legend`\n const descriptionId = `${id}description`\n const warningId = `${id}warning`\n const errorId = `${id}error`\n const showErrorMessage = hasErrorMessage(error) && !errorHidden\n const showWarning = warning != null && !showErrorMessage\n const invalid = hasError(error)\n const disabled = rest.disabled ?? false\n const effectiveLegendHidden = legendHidden || layout === 'inline'\n\n const context = useMemo<FieldControlContext>(\n () => ({\n id,\n labelId: legendId,\n invalid,\n required,\n disabled,\n readOnly,\n busy: validating,\n warningId,\n fieldset: true,\n }),\n [id, legendId, invalid, required, disabled, readOnly, validating, warningId],\n )\n\n return (\n <fieldset\n ref={ref}\n className={cx(styles.fieldset, className)}\n data-variant={variant}\n data-layout={layout}\n data-invalid={invalid || undefined}\n data-disabled={disabled || undefined}\n data-readonly={readOnly || undefined}\n aria-describedby={joinIds(\n describedByProp,\n description != null && descriptionId,\n showWarning && warningId,\n showErrorMessage && errorId,\n )}\n {...rest}\n >\n <legend\n id={legendId}\n className={cx(\n messageStyles.label,\n styles.legend,\n effectiveLegendHidden && messageStyles.visuallyHidden,\n )}\n data-disabled={disabled || undefined}\n >\n <LabelContent required={required} optional={optional} busy={validating}>\n {legend}\n </LabelContent>\n </legend>\n <div className={styles.body}>\n {description != null ? (\n <FieldDescription id={descriptionId} className={styles.description}>\n {description}\n </FieldDescription>\n ) : null}\n <div className={styles.controls}>\n <FieldContext.Provider value={context}>{children}</FieldContext.Provider>\n </div>\n {showWarning ? <FieldWarning id={warningId}>{warning}</FieldWarning> : null}\n {showErrorMessage ? (\n <FieldError id={errorId} live={errorLive}>\n {error}\n </FieldError>\n ) : null}\n </div>\n </fieldset>\n )\n})\n"],"mappings":";;;;;;;;;;AAsEA,IAAaI,IAAWC,EAA+C,SACrE,EACE,WACA,gBACA,UACA,cAAW,IACX,aACA,kBAAe,IACf,aAAU,WACV,YACA,eAAY,IACZ,iBAAc,IACd,gBAAa,IACb,cAAW,IACX,YAAS,SACT,cACA,aACA,oBAAoBC,GACpB,GAAGC,KAEL,GACA;CACA,IAAMC,IAAKC,EAAM,GACXC,IAAW,GAAGF,EAAG,SACjBG,IAAgB,GAAGH,EAAG,cACtBI,IAAY,GAAGJ,EAAG,UAClBK,IAAU,GAAGL,EAAG,QAChBM,IAAmBC,EAAgBC,CAAK,KAAK,CAACC,GAC9CC,IAAcC,KAAW,QAAQ,CAACL,GAClCM,IAAUC,EAASL,CAAK,GACxBM,IAAWf,EAAK,YAAY,IAC5BgB,IAAwBC,KAAgBC,MAAW,UAEnDC,IAAUC,SACP;EACL;EACA,SAASjB;EACT;EACA;EACA;EACA;EACA,MAAMkB;EACN;EACA,UAAU;CACZ,IACA;EAACpB;EAAIE;EAAUU;EAASS;EAAUP;EAAUQ;EAAUF;EAAYhB;CAAS,CAC7E;CAEA,OACE,kBAAC,YAAD;EACO;EACL,WAAWmB,EAAG/B,EAAO,UAAUgC,CAAS;EACxC,gBAAcC;EACd,eAAaR;EACb,gBAAcL,KAAW,KAAA;EACzB,iBAAeE,KAAY,KAAA;EAC3B,iBAAeQ,KAAY,KAAA;EAC3B,oBAAkBI,EAChB5B,GACA6B,KAAe,QAAQxB,GACvBO,KAAeN,GACfE,KAAoBD,CACtB;EACA,GAAIN;EAdN,UAAA,CAgBE,kBAAC,UAAD;GACE,IAAIG;GACJ,WAAWqB,EACT9B,EAAc,OACdD,EAAO,QACPuB,KAAyBtB,EAAc,cACzC;GACA,iBAAeqB,KAAY,KAAA;GAE3B,UAAA,kBAACc,GAAD;IAAwB;IAAoB;IAAU,MAAMR;IACzD,UAAAS;GACW,CAAA;EACR,CAAA,GACR,kBAAC,OAAD;GAAK,WAAWrC,EAAO;GAAvB,UAAA;IACGmC,KAAe,OAIZ,OAHF,kBAACG,GAAD;KAAkB,IAAI3B;KAAe,WAAWX,EAAO;KACpD,UAAAmC;IACe,CAAA;IAEpB,kBAAC,OAAD;KAAK,WAAWnC,EAAO;KACrB,UAAA,kBAACuC,EAAa,UAAd;MAAuB,OAAOb;MAAU;KAAgC,CAAA;IACrE,CAAA;IACJR,IAAc,kBAACsB,GAAD;KAAc,IAAI5B;KAAY,UAAAO;IAAsB,CAAA,IAAI;IACtEL,IACC,kBAAC2B,GAAD;KAAY,IAAI5B;KAAS,MAAM6B;KAC5B,UAAA1B;IACS,CAAA,IACV;GACD;EACG,CAAA,CAAA;;AAEd,CAAC"}
@@ -48,6 +48,16 @@ export interface FileDropProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onC
48
48
  onBlur?: FocusEventHandler<HTMLElement>;
49
49
  }
50
50
  /**
51
+ * Pick or drop files. The real control is a native file input; dragging is an enhancement.
52
+ *
53
+ * @remarks
54
+ * `FileDrop` is a native `<input type="file">` (visually hidden, labelled by its Field, with the
55
+ * focus ring on the drop zone) with drag and drop on top. It holds the files in state rather than
56
+ * uploading them: the value is `File`s, plus any already-stored files you pass back in. The
57
+ * input's file list is kept in sync where the browser allows, so a native form submit sends the
58
+ * current files.
59
+ *
60
+ * @privateRemarks
51
61
  * Pick or drop files. The real control is the native `<input type="file">` (visually
52
62
  * hidden, labelled by the surrounding Field, focus ring on the zone); drag-and-drop is an
53
63
  * enhancement. Files are held in state, not uploaded: `value` is `File`s plus any
@@ -1 +1 @@
1
- {"version":3,"file":"FileDrop.js","names":["styles","messageStyles","messages_module_default","FileDrop_module_default","useObjectUrls","images","useMemo","enabled","files","isFile","file","isImage","cacheRef","useRef","urls","setUrls","useState","useEffect","cache","wanted","changed","url","defaultRemoveLabel","name","FileDrop","markFieldAware","forwardRef","valueProp","disabledProp","readOnlyProp","requiredProp","invalidProp","idProp","describedByProp","ariaInvalid","ariaLabel","ariaLabelledBy","rest","field","useResolvedField","interactive","inputRef","rootRef","composedRef","useMergedRefs","ref","setFiles","useControllableState","defaultValue","onValueChange","next","dragging","setDragging","dragDepth","preview","syncInput","input","accepted","values","transfer","addFiles","incoming","limit","multiple","maxFiles","base","rejections","matchesAccept","accept","maxSize","onReject","removeAt","i","index","handleChange","event","handleInputClick","handleZoneClick","handleDragEnter","handleDragOver","handleDragLeave","handleDrop","handleBlur","onBlur","fallbackLabel","browseLabel","cx","className","UploadIcon","dropLabel","thumb","ext","extensionOf","formatFileSize","removeLabel","CloseIcon"],"sources":["../../../../src/components/inputs/FileDrop/FileDrop.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ChangeEvent,\n type DragEvent,\n type FocusEvent,\n type FocusEventHandler,\n type HTMLAttributes,\n type MouseEvent,\n type ReactNode,\n} from 'react'\nimport { CloseIcon, UploadIcon } from '#icons'\nimport { cx } from '#utils/cx'\nimport { useControllableState } from '#components/inputs/Combobox/useControllableState'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport { useMergedRefs } from '#components/inputs/internal/refs'\nimport messageStyles from '#components/inputs/internal/messages.module.css'\nimport { extensionOf, formatFileSize, isFile, isImage, matchesAccept } from './fileUtils'\nimport styles from './FileDrop.module.css'\n\n/** A file that already lives somewhere (uploaded earlier): shown and removable, never re-sent. */\nexport interface StoredFile {\n id: string\n name: string\n /** Bytes. */\n size?: number\n /** MIME type, e.g. `image/jpeg`. */\n type?: string\n /** Where to preview it (thumbnails). */\n url?: string\n}\n\nexport type FileValue = File | StoredFile\n\nexport type FileRejectReason = 'type' | 'size' | 'count'\n\nexport interface FileRejection {\n file: File\n reason: FileRejectReason\n}\n\nexport interface FileDropProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue' | 'onBlur'\n> {\n value?: readonly FileValue[]\n defaultValue?: readonly FileValue[]\n onValueChange?: (files: FileValue[]) => void\n /** Native `accept`: extensions (`.pdf`), MIME types (`image/png`) or wildcards (`image/*`). Also checked on drop. */\n accept?: string\n /** Several files (added to the list). Without it a new file replaces the current one. */\n multiple?: boolean\n /** With `multiple`: the most files the list may hold. */\n maxFiles?: number\n /** Largest file accepted, in bytes. */\n maxSize?: number\n /** Files that weren't added, and why. One call per pick or drop. */\n onReject?: (rejections: readonly FileRejection[]) => void\n /** `list` (default): name and size rows. `thumbnails`: a grid with image previews. */\n preview?: 'list' | 'thumbnails'\n /** Default \"Drop files here or\". */\n dropLabel?: ReactNode\n /** Default \"choose files\". Also the input's name when nothing else labels it. */\n browseLabel?: string\n /** Accessible name of a file's remove button. Default `Remove <name>`. */\n removeLabel?: (name: string) => string\n name?: string\n disabled?: boolean\n readOnly?: boolean\n required?: boolean\n /** Critical border + `aria-invalid`. Inside a `<Field error>` this is set for you. */\n invalid?: boolean\n /** Fires once focus leaves the whole control (the input and the remove buttons). */\n onBlur?: FocusEventHandler<HTMLElement>\n}\n\n/**\n * Object URLs for the image Files in `files`, created as files arrive and revoked as\n * they leave and on unmount.\n */\nfunction useObjectUrls(files: readonly FileValue[], enabled: boolean): ReadonlyMap<File, string> {\n const images = useMemo(\n () => (enabled ? files.filter((file): file is File => isFile(file) && isImage(file)) : []),\n [files, enabled],\n )\n const cacheRef = useRef(new Map<File, string>())\n const [urls, setUrls] = useState<ReadonlyMap<File, string>>(() => new Map())\n\n useEffect(() => {\n const cache = cacheRef.current\n if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') return\n const wanted = new Set(images)\n let changed = false\n for (const [file, url] of cache) {\n if (!wanted.has(file)) {\n URL.revokeObjectURL(url)\n cache.delete(file)\n changed = true\n }\n }\n for (const file of images) {\n if (!cache.has(file)) {\n cache.set(file, URL.createObjectURL(file))\n changed = true\n }\n }\n if (changed) setUrls(new Map(cache))\n }, [images])\n\n useEffect(() => {\n const cache = cacheRef.current\n return () => {\n for (const url of cache.values()) URL.revokeObjectURL(url)\n cache.clear()\n }\n }, [])\n\n return urls\n}\n\nconst defaultRemoveLabel = (name: string) => `Remove ${name}`\n\n/* ─── FileDrop ────────────────────────────────────────────────────────────── */\n\n/**\n * Pick or drop files. The real control is the native `<input type=\"file\">` (visually\n * hidden, labelled by the surrounding Field, focus ring on the zone); drag-and-drop is an\n * enhancement. Files are held in state, not uploaded: `value` is `File`s plus any\n * `StoredFile`s from an earlier upload. The input's `FileList` is kept in sync where the\n * browser allows it, so a native form submit sends the current files.\n * The ref goes to the file input; `className`/`style` and other props go to the wrapper.\n *\n * <FileDrop accept=\"image/*,.pdf\" multiple maxFiles={5} maxSize={5_000_000} preview=\"thumbnails\" />\n */\nexport const FileDrop = markFieldAware(\n forwardRef<HTMLInputElement, FileDropProps>(function FileDrop(\n {\n value: valueProp,\n defaultValue,\n onValueChange,\n accept,\n multiple = false,\n maxFiles,\n maxSize,\n onReject,\n preview = 'list',\n dropLabel = 'Drop files here or',\n browseLabel = 'choose files',\n removeLabel = defaultRemoveLabel,\n name,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n required: requiredProp,\n invalid: invalidProp,\n onBlur,\n id: idProp,\n className,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n required: requiredProp,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n })\n const interactive = !field.disabled && !field.readOnly\n const inputRef = useRef<HTMLInputElement>(null)\n const rootRef = useRef<HTMLDivElement>(null)\n const composedRef = useMergedRefs(ref, inputRef)\n\n const [files, setFiles] = useControllableState<readonly FileValue[]>(\n valueProp,\n defaultValue ?? [],\n (next) => onValueChange?.([...next]),\n )\n const [dragging, setDragging] = useState(false)\n const dragDepth = useRef(0)\n const urls = useObjectUrls(files, preview === 'thumbnails')\n\n /**\n * Make the native input's FileList equal to the File values, so FormData sends them.\n * DataTransfer is the only way to build a FileList; where it's missing (or `files`\n * can't be assigned) the input is emptied when it holds no accepted file, so a rejected\n * pick is never submitted and picking the same file again still fires `change`.\n */\n const syncInput = (values: readonly FileValue[]) => {\n const input = inputRef.current\n if (!input) return\n const accepted = values.filter(isFile)\n try {\n if (typeof DataTransfer === 'undefined') throw new Error('no DataTransfer')\n const transfer = new DataTransfer()\n for (const file of accepted) transfer.items.add(file)\n input.files = transfer.files\n } catch {\n if (accepted.length === 0) input.value = ''\n }\n }\n\n useEffect(() => {\n syncInput(files)\n }, [files])\n\n /** Validates and adds; returns the value the control now holds. */\n const addFiles = (incoming: readonly File[]): readonly FileValue[] => {\n if (!interactive || incoming.length === 0) return files\n const limit = multiple ? (maxFiles ?? Number.POSITIVE_INFINITY) : 1\n const base = multiple ? files : []\n const accepted: File[] = []\n const rejections: FileRejection[] = []\n for (const file of incoming) {\n if (!matchesAccept(file, accept)) rejections.push({ file, reason: 'type' })\n else if (maxSize !== undefined && file.size > maxSize)\n rejections.push({ file, reason: 'size' })\n else if (base.length + accepted.length >= limit) rejections.push({ file, reason: 'count' })\n else accepted.push(file)\n }\n const next = accepted.length > 0 ? [...base, ...accepted] : files\n if (accepted.length > 0) setFiles(next)\n if (rejections.length > 0) onReject?.(rejections)\n return next\n }\n\n const removeAt = (index: number) => {\n if (!interactive) return\n setFiles(files.filter((_, i) => i !== index))\n inputRef.current?.focus()\n }\n\n /* ─── Events ─── */\n const handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n const next = addFiles(Array.from(event.target.files ?? []))\n // Resync even when the value didn't change (every file rejected): the input still\n // holds the rejected pick, which would be submitted and would block a re-pick.\n syncInput(next)\n }\n\n // Read-only: focusable, but the picker never opens.\n const handleInputClick = (event: MouseEvent<HTMLInputElement>) => {\n if (!interactive) event.preventDefault()\n }\n\n // The whole zone opens the picker; the input itself handles its own clicks.\n const handleZoneClick = (event: MouseEvent<HTMLDivElement>) => {\n if (event.target === inputRef.current || !interactive) return\n inputRef.current?.click()\n }\n\n const handleDragEnter = (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n if (!interactive) return\n dragDepth.current += 1\n setDragging(true)\n }\n const handleDragOver = (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.dataTransfer.dropEffect = interactive ? 'copy' : 'none'\n }\n const handleDragLeave = () => {\n dragDepth.current = Math.max(0, dragDepth.current - 1)\n if (dragDepth.current === 0) setDragging(false)\n }\n const handleDrop = (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n addFiles(Array.from(event.dataTransfer.files))\n }\n\n const handleBlur = (event: FocusEvent<HTMLDivElement>) => {\n const next = event.relatedTarget as Node | null\n if (next && rootRef.current?.contains(next)) return\n onBlur?.(event)\n }\n\n /* ─── Render ─── */\n // Outside a Field and with no aria label, the browse text names the input.\n const fallbackLabel = ariaLabel ?? ((ariaLabelledBy ?? field.labelId) ? undefined : browseLabel)\n\n return (\n <div\n ref={rootRef}\n className={cx(styles.root, className)}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readOnly || undefined}\n onBlur={handleBlur}\n {...rest}\n >\n {/* The file input inside the zone is the keyboard path; a click anywhere on the zone\n is a bigger pointer target for the same input. */}\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}\n <div\n className={styles.zone}\n data-dragging={dragging || undefined}\n data-invalid={field.invalid || undefined}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readOnly || undefined}\n onClick={handleZoneClick}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <UploadIcon className={styles.icon} />\n <span className={styles.prompt} aria-hidden=\"true\">\n {dropLabel} <span className={styles.browse}>{browseLabel}</span>\n </span>\n <input\n ref={composedRef}\n id={field.id}\n type=\"file\"\n className={cx(messageStyles.visuallyHidden, styles.input)}\n name={name}\n accept={accept}\n multiple={multiple}\n disabled={field.disabled}\n aria-label={fallbackLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={field.describedBy}\n aria-invalid={field.invalid || undefined}\n aria-required={field.required || undefined}\n aria-busy={field.busy || undefined}\n onChange={handleChange}\n onClick={handleInputClick}\n />\n </div>\n {files.length > 0 ? (\n <ul className={styles.list} data-preview={preview}>\n {files.map((file, index) => {\n const thumb =\n preview === 'thumbnails' && isImage(file)\n ? isFile(file)\n ? urls.get(file)\n : file.url\n : undefined\n const ext = extensionOf(file.name)\n return (\n <li\n key={\n isFile(file)\n ? `${String(index)}:${file.name}:${String(file.size)}`\n : `stored:${file.id}`\n }\n className={styles.item}\n >\n {preview === 'thumbnails' ? (\n <span className={styles.thumb} aria-hidden=\"true\">\n {thumb ? (\n <img src={thumb} alt=\"\" className={styles.image} />\n ) : (\n <span className={styles.ext}>{ext || 'FILE'}</span>\n )}\n </span>\n ) : null}\n <span className={styles.meta}>\n <span className={styles.name}>{file.name}</span>\n {file.size !== undefined ? (\n <span className={styles.size}>{formatFileSize(file.size)}</span>\n ) : null}\n </span>\n {interactive ? (\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={removeLabel(file.name)}\n onClick={() => {\n removeAt(index)\n }}\n >\n <CloseIcon />\n </button>\n ) : null}\n </li>\n )\n })}\n </ul>\n ) : null}\n </div>\n )\n }),\n)\n"],"mappings":"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"}
1
+ {"version":3,"file":"FileDrop.js","names":["styles","messageStyles","messages_module_default","FileDrop_module_default","useObjectUrls","images","useMemo","enabled","files","isFile","file","isImage","cacheRef","useRef","urls","setUrls","useState","useEffect","cache","wanted","changed","url","defaultRemoveLabel","name","FileDrop","markFieldAware","forwardRef","valueProp","disabledProp","readOnlyProp","requiredProp","invalidProp","idProp","describedByProp","ariaInvalid","ariaLabel","ariaLabelledBy","rest","field","useResolvedField","interactive","inputRef","rootRef","composedRef","useMergedRefs","ref","setFiles","useControllableState","defaultValue","onValueChange","next","dragging","setDragging","dragDepth","preview","syncInput","input","accepted","values","transfer","addFiles","incoming","limit","multiple","maxFiles","base","rejections","matchesAccept","accept","maxSize","onReject","removeAt","i","index","handleChange","event","handleInputClick","handleZoneClick","handleDragEnter","handleDragOver","handleDragLeave","handleDrop","handleBlur","onBlur","fallbackLabel","browseLabel","cx","className","UploadIcon","dropLabel","thumb","ext","extensionOf","formatFileSize","removeLabel","CloseIcon"],"sources":["../../../../src/components/inputs/FileDrop/FileDrop.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ChangeEvent,\n type DragEvent,\n type FocusEvent,\n type FocusEventHandler,\n type HTMLAttributes,\n type MouseEvent,\n type ReactNode,\n} from 'react'\nimport { CloseIcon, UploadIcon } from '#icons'\nimport { cx } from '#utils/cx'\nimport { useControllableState } from '#components/inputs/Combobox/useControllableState'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport { useMergedRefs } from '#components/inputs/internal/refs'\nimport messageStyles from '#components/inputs/internal/messages.module.css'\nimport { extensionOf, formatFileSize, isFile, isImage, matchesAccept } from './fileUtils'\nimport styles from './FileDrop.module.css'\n\n/** A file that already lives somewhere (uploaded earlier): shown and removable, never re-sent. */\nexport interface StoredFile {\n id: string\n name: string\n /** Bytes. */\n size?: number\n /** MIME type, e.g. `image/jpeg`. */\n type?: string\n /** Where to preview it (thumbnails). */\n url?: string\n}\n\nexport type FileValue = File | StoredFile\n\nexport type FileRejectReason = 'type' | 'size' | 'count'\n\nexport interface FileRejection {\n file: File\n reason: FileRejectReason\n}\n\nexport interface FileDropProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue' | 'onBlur'\n> {\n value?: readonly FileValue[]\n defaultValue?: readonly FileValue[]\n onValueChange?: (files: FileValue[]) => void\n /** Native `accept`: extensions (`.pdf`), MIME types (`image/png`) or wildcards (`image/*`). Also checked on drop. */\n accept?: string\n /** Several files (added to the list). Without it a new file replaces the current one. */\n multiple?: boolean\n /** With `multiple`: the most files the list may hold. */\n maxFiles?: number\n /** Largest file accepted, in bytes. */\n maxSize?: number\n /** Files that weren't added, and why. One call per pick or drop. */\n onReject?: (rejections: readonly FileRejection[]) => void\n /** `list` (default): name and size rows. `thumbnails`: a grid with image previews. */\n preview?: 'list' | 'thumbnails'\n /** Default \"Drop files here or\". */\n dropLabel?: ReactNode\n /** Default \"choose files\". Also the input's name when nothing else labels it. */\n browseLabel?: string\n /** Accessible name of a file's remove button. Default `Remove <name>`. */\n removeLabel?: (name: string) => string\n name?: string\n disabled?: boolean\n readOnly?: boolean\n required?: boolean\n /** Critical border + `aria-invalid`. Inside a `<Field error>` this is set for you. */\n invalid?: boolean\n /** Fires once focus leaves the whole control (the input and the remove buttons). */\n onBlur?: FocusEventHandler<HTMLElement>\n}\n\n/**\n * Object URLs for the image Files in `files`, created as files arrive and revoked as\n * they leave and on unmount.\n */\nfunction useObjectUrls(files: readonly FileValue[], enabled: boolean): ReadonlyMap<File, string> {\n const images = useMemo(\n () => (enabled ? files.filter((file): file is File => isFile(file) && isImage(file)) : []),\n [files, enabled],\n )\n const cacheRef = useRef(new Map<File, string>())\n const [urls, setUrls] = useState<ReadonlyMap<File, string>>(() => new Map())\n\n useEffect(() => {\n const cache = cacheRef.current\n if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function') return\n const wanted = new Set(images)\n let changed = false\n for (const [file, url] of cache) {\n if (!wanted.has(file)) {\n URL.revokeObjectURL(url)\n cache.delete(file)\n changed = true\n }\n }\n for (const file of images) {\n if (!cache.has(file)) {\n cache.set(file, URL.createObjectURL(file))\n changed = true\n }\n }\n if (changed) setUrls(new Map(cache))\n }, [images])\n\n useEffect(() => {\n const cache = cacheRef.current\n return () => {\n for (const url of cache.values()) URL.revokeObjectURL(url)\n cache.clear()\n }\n }, [])\n\n return urls\n}\n\nconst defaultRemoveLabel = (name: string) => `Remove ${name}`\n\n/* ─── FileDrop ────────────────────────────────────────────────────────────── */\n\n/**\n * Pick or drop files. The real control is a native file input; dragging is an enhancement.\n *\n * @remarks\n * `FileDrop` is a native `<input type=\"file\">` (visually hidden, labelled by its Field, with the\n * focus ring on the drop zone) with drag and drop on top. It holds the files in state rather than\n * uploading them: the value is `File`s, plus any already-stored files you pass back in. The\n * input's file list is kept in sync where the browser allows, so a native form submit sends the\n * current files.\n *\n * @privateRemarks\n * Pick or drop files. The real control is the native `<input type=\"file\">` (visually\n * hidden, labelled by the surrounding Field, focus ring on the zone); drag-and-drop is an\n * enhancement. Files are held in state, not uploaded: `value` is `File`s plus any\n * `StoredFile`s from an earlier upload. The input's `FileList` is kept in sync where the\n * browser allows it, so a native form submit sends the current files.\n * The ref goes to the file input; `className`/`style` and other props go to the wrapper.\n *\n * <FileDrop accept=\"image/*,.pdf\" multiple maxFiles={5} maxSize={5_000_000} preview=\"thumbnails\" />\n */\nexport const FileDrop = markFieldAware(\n forwardRef<HTMLInputElement, FileDropProps>(function FileDrop(\n {\n value: valueProp,\n defaultValue,\n onValueChange,\n accept,\n multiple = false,\n maxFiles,\n maxSize,\n onReject,\n preview = 'list',\n dropLabel = 'Drop files here or',\n browseLabel = 'choose files',\n removeLabel = defaultRemoveLabel,\n name,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n required: requiredProp,\n invalid: invalidProp,\n onBlur,\n id: idProp,\n className,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n required: requiredProp,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n })\n const interactive = !field.disabled && !field.readOnly\n const inputRef = useRef<HTMLInputElement>(null)\n const rootRef = useRef<HTMLDivElement>(null)\n const composedRef = useMergedRefs(ref, inputRef)\n\n const [files, setFiles] = useControllableState<readonly FileValue[]>(\n valueProp,\n defaultValue ?? [],\n (next) => onValueChange?.([...next]),\n )\n const [dragging, setDragging] = useState(false)\n const dragDepth = useRef(0)\n const urls = useObjectUrls(files, preview === 'thumbnails')\n\n /**\n * Make the native input's FileList equal to the File values, so FormData sends them.\n * DataTransfer is the only way to build a FileList; where it's missing (or `files`\n * can't be assigned) the input is emptied when it holds no accepted file, so a rejected\n * pick is never submitted and picking the same file again still fires `change`.\n */\n const syncInput = (values: readonly FileValue[]) => {\n const input = inputRef.current\n if (!input) return\n const accepted = values.filter(isFile)\n try {\n if (typeof DataTransfer === 'undefined') throw new Error('no DataTransfer')\n const transfer = new DataTransfer()\n for (const file of accepted) transfer.items.add(file)\n input.files = transfer.files\n } catch {\n if (accepted.length === 0) input.value = ''\n }\n }\n\n useEffect(() => {\n syncInput(files)\n }, [files])\n\n /** Validates and adds; returns the value the control now holds. */\n const addFiles = (incoming: readonly File[]): readonly FileValue[] => {\n if (!interactive || incoming.length === 0) return files\n const limit = multiple ? (maxFiles ?? Number.POSITIVE_INFINITY) : 1\n const base = multiple ? files : []\n const accepted: File[] = []\n const rejections: FileRejection[] = []\n for (const file of incoming) {\n if (!matchesAccept(file, accept)) rejections.push({ file, reason: 'type' })\n else if (maxSize !== undefined && file.size > maxSize)\n rejections.push({ file, reason: 'size' })\n else if (base.length + accepted.length >= limit) rejections.push({ file, reason: 'count' })\n else accepted.push(file)\n }\n const next = accepted.length > 0 ? [...base, ...accepted] : files\n if (accepted.length > 0) setFiles(next)\n if (rejections.length > 0) onReject?.(rejections)\n return next\n }\n\n const removeAt = (index: number) => {\n if (!interactive) return\n setFiles(files.filter((_, i) => i !== index))\n inputRef.current?.focus()\n }\n\n /* ─── Events ─── */\n const handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n const next = addFiles(Array.from(event.target.files ?? []))\n // Resync even when the value didn't change (every file rejected): the input still\n // holds the rejected pick, which would be submitted and would block a re-pick.\n syncInput(next)\n }\n\n // Read-only: focusable, but the picker never opens.\n const handleInputClick = (event: MouseEvent<HTMLInputElement>) => {\n if (!interactive) event.preventDefault()\n }\n\n // The whole zone opens the picker; the input itself handles its own clicks.\n const handleZoneClick = (event: MouseEvent<HTMLDivElement>) => {\n if (event.target === inputRef.current || !interactive) return\n inputRef.current?.click()\n }\n\n const handleDragEnter = (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n if (!interactive) return\n dragDepth.current += 1\n setDragging(true)\n }\n const handleDragOver = (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.dataTransfer.dropEffect = interactive ? 'copy' : 'none'\n }\n const handleDragLeave = () => {\n dragDepth.current = Math.max(0, dragDepth.current - 1)\n if (dragDepth.current === 0) setDragging(false)\n }\n const handleDrop = (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n addFiles(Array.from(event.dataTransfer.files))\n }\n\n const handleBlur = (event: FocusEvent<HTMLDivElement>) => {\n const next = event.relatedTarget as Node | null\n if (next && rootRef.current?.contains(next)) return\n onBlur?.(event)\n }\n\n /* ─── Render ─── */\n // Outside a Field and with no aria label, the browse text names the input.\n const fallbackLabel = ariaLabel ?? ((ariaLabelledBy ?? field.labelId) ? undefined : browseLabel)\n\n return (\n <div\n ref={rootRef}\n className={cx(styles.root, className)}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readOnly || undefined}\n onBlur={handleBlur}\n {...rest}\n >\n {/* The file input inside the zone is the keyboard path; a click anywhere on the zone\n is a bigger pointer target for the same input. */}\n {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}\n <div\n className={styles.zone}\n data-dragging={dragging || undefined}\n data-invalid={field.invalid || undefined}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readOnly || undefined}\n onClick={handleZoneClick}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n >\n <UploadIcon className={styles.icon} />\n <span className={styles.prompt} aria-hidden=\"true\">\n {dropLabel} <span className={styles.browse}>{browseLabel}</span>\n </span>\n <input\n ref={composedRef}\n id={field.id}\n type=\"file\"\n className={cx(messageStyles.visuallyHidden, styles.input)}\n name={name}\n accept={accept}\n multiple={multiple}\n disabled={field.disabled}\n aria-label={fallbackLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={field.describedBy}\n aria-invalid={field.invalid || undefined}\n aria-required={field.required || undefined}\n aria-busy={field.busy || undefined}\n onChange={handleChange}\n onClick={handleInputClick}\n />\n </div>\n {files.length > 0 ? (\n <ul className={styles.list} data-preview={preview}>\n {files.map((file, index) => {\n const thumb =\n preview === 'thumbnails' && isImage(file)\n ? isFile(file)\n ? urls.get(file)\n : file.url\n : undefined\n const ext = extensionOf(file.name)\n return (\n <li\n key={\n isFile(file)\n ? `${String(index)}:${file.name}:${String(file.size)}`\n : `stored:${file.id}`\n }\n className={styles.item}\n >\n {preview === 'thumbnails' ? (\n <span className={styles.thumb} aria-hidden=\"true\">\n {thumb ? (\n <img src={thumb} alt=\"\" className={styles.image} />\n ) : (\n <span className={styles.ext}>{ext || 'FILE'}</span>\n )}\n </span>\n ) : null}\n <span className={styles.meta}>\n <span className={styles.name}>{file.name}</span>\n {file.size !== undefined ? (\n <span className={styles.size}>{formatFileSize(file.size)}</span>\n ) : null}\n </span>\n {interactive ? (\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={removeLabel(file.name)}\n onClick={() => {\n removeAt(index)\n }}\n >\n <CloseIcon />\n </button>\n ) : null}\n </li>\n )\n })}\n </ul>\n ) : null}\n </div>\n )\n }),\n)\n"],"mappings":"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"}
@@ -3,6 +3,13 @@ import { type FileDropProps } from '../FileDrop/index.js';
3
3
  export interface FileFieldProps extends FieldLabelProps, Omit<FileDropProps, 'invalid' | keyof FieldLabelProps> {
4
4
  }
5
5
  /**
6
+ * A labelled file picker with drag and drop, constraints and a preview.
7
+ *
8
+ * @remarks
9
+ * `FileField` is a {@link Field | Field} around a {@link FileDrop | FileDrop}. The label names the
10
+ * native file input.
11
+ *
12
+ * @privateRemarks
6
13
  * Label + FileDrop + description + warning + error. The label names the native file
7
14
  * input (the ref goes there too); `className`/`style` go to the field wrapper.
8
15
  * `onReject` reports files that weren't added — turn them into the field's error.
@@ -1 +1 @@
1
- {"version":3,"file":"FileField.js","names":["FileField","forwardRef","fieldProps","rest","splitFieldLabelProps","props","dropProps","Field","id","FileDrop"],"sources":["../../../../src/components/inputs/FileField/FileField.tsx"],"sourcesContent":["import { forwardRef } from 'react'\nimport { Field, type FieldLabelProps } from '#components/inputs/Field'\nimport { FileDrop, type FileDropProps } from '#components/inputs/FileDrop'\nimport { splitFieldLabelProps } from '#components/inputs/internal/fieldProps'\n\nexport interface FileFieldProps\n extends FieldLabelProps, Omit<FileDropProps, 'invalid' | keyof FieldLabelProps> {}\n\n/**\n * Label + FileDrop + description + warning + error. The label names the native file\n * input (the ref goes there too); `className`/`style` go to the field wrapper.\n * `onReject` reports files that weren't added — turn them into the field's error.\n *\n * <FileField label=\"Receipts\" accept=\"image/*,.pdf\" multiple maxSize={5_000_000} preview=\"thumbnails\" />\n */\nexport const FileField = forwardRef<HTMLInputElement, FileFieldProps>(\n function FileField(props, ref) {\n const [fieldProps, rest] = splitFieldLabelProps(props)\n const { id, disabled, className, style, ...dropProps } = rest\n return (\n <Field {...fieldProps} htmlFor={id} disabled={disabled} className={className} style={style}>\n <FileDrop ref={ref} id={id} disabled={disabled} {...dropProps} />\n </Field>\n )\n },\n)\n"],"mappings":";;;;;;AAeA,IAAaA,IAAYC,EACvB,SAAmB,GAAO,GAAK;CAC7B,IAAM,CAACC,GAAYC,KAAQC,EAAqBC,CAAK,GAC/C,EAAE,OAAI,aAAU,cAAW,UAAO,GAAGC,MAAcH;CACzD,OACE,kBAACI,GAAD;EAAO,GAAIL;EAAY,SAASM;EAAc;EAAqB;EAAkB;EACnF,UAAA,kBAACC,GAAD;GAAe;GAAS;GAAc;GAAU,GAAIH;EAAY,CAAA;CAC3D,CAAA;AAEX,CACF"}
1
+ {"version":3,"file":"FileField.js","names":["FileField","forwardRef","fieldProps","rest","splitFieldLabelProps","props","dropProps","Field","id","FileDrop"],"sources":["../../../../src/components/inputs/FileField/FileField.tsx"],"sourcesContent":["import { forwardRef } from 'react'\nimport { Field, type FieldLabelProps } from '#components/inputs/Field'\nimport { FileDrop, type FileDropProps } from '#components/inputs/FileDrop'\nimport { splitFieldLabelProps } from '#components/inputs/internal/fieldProps'\n\nexport interface FileFieldProps\n extends FieldLabelProps, Omit<FileDropProps, 'invalid' | keyof FieldLabelProps> {}\n\n/**\n * A labelled file picker with drag and drop, constraints and a preview.\n *\n * @remarks\n * `FileField` is a {@link Field | Field} around a {@link FileDrop | FileDrop}. The label names the\n * native file input.\n *\n * @privateRemarks\n * Label + FileDrop + description + warning + error. The label names the native file\n * input (the ref goes there too); `className`/`style` go to the field wrapper.\n * `onReject` reports files that weren't added — turn them into the field's error.\n *\n * <FileField label=\"Receipts\" accept=\"image/*,.pdf\" multiple maxSize={5_000_000} preview=\"thumbnails\" />\n */\nexport const FileField = forwardRef<HTMLInputElement, FileFieldProps>(\n function FileField(props, ref) {\n const [fieldProps, rest] = splitFieldLabelProps(props)\n const { id, disabled, className, style, ...dropProps } = rest\n return (\n <Field {...fieldProps} htmlFor={id} disabled={disabled} className={className} style={style}>\n <FileDrop ref={ref} id={id} disabled={disabled} {...dropProps} />\n </Field>\n )\n },\n)\n"],"mappings":";;;;;;AAsBA,IAAaA,IAAYC,EACvB,SAAmB,GAAO,GAAK;CAC7B,IAAM,CAACC,GAAYC,KAAQC,EAAqBC,CAAK,GAC/C,EAAE,OAAI,aAAU,cAAW,UAAO,GAAGC,MAAcH;CACzD,OACE,kBAACI,GAAD;EAAO,GAAIL;EAAY,SAASM;EAAc;EAAqB;EAAkB;EACnF,UAAA,kBAACC,GAAD;GAAe;GAAS;GAAc;GAAU,GAAIH;EAAY,CAAA;CAC3D,CAAA;AAEX,CACF"}
@@ -17,6 +17,15 @@ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>,
17
17
  htmlSize?: number;
18
18
  }
19
19
  /**
20
+ * A single-line text input with leading and trailing slots, a numeric mode and three sizes.
21
+ *
22
+ * @remarks
23
+ * `Input` is the bare text box. Native props (`value`, `onChange`, `type`, `name`, `autoComplete`)
24
+ * and the ref go to the `<input>`; `className` and `style` go to the visible box around it. Inside
25
+ * a {@link Field | Field} it picks up its label, description and invalid state; outside one, give
26
+ * it an `aria-label`. For the usual labelled field, use {@link TextField | TextField}.
27
+ *
28
+ * @privateRemarks
20
29
  * Single-line text input. Native props (`value`, `onChange`, `type`, `name`…) go to the
21
30
  * `<input>`, as does the ref; `className`/`style` go to the visible box around it.
22
31
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Input.js","names":["control","styles","control_module_default","Input_module_default","Input","markFieldAware","forwardRef","invalidProp","idProp","disabledProp","requiredProp","describedByProp","ariaInvalid","rest","inputRef","useRef","composedRef","useMergedRefs","ref","field","useResolvedField","cx","className","size","numeric","target","event","leading","htmlSize","inputMode","trailing"],"sources":["../../../../src/components/inputs/Input/Input.tsx"],"sourcesContent":["import {\n forwardRef,\n useRef,\n type InputHTMLAttributes,\n type PointerEvent,\n type ReactNode,\n} from 'react'\nimport { cx } from '#utils/cx'\nimport type { Size } from '#utils/tokens'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport { useMergedRefs } from '#components/inputs/internal/refs'\nimport control from '#components/inputs/internal/control.module.css'\nimport styles from './Input.module.css'\n\nexport interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {\n size?: Size\n /** Content inside the box before the text: an icon, \"$\", a unit. Decorative unless it's a button. */\n leading?: ReactNode\n /** Content inside the box after the text: \"AUD\", \"kg\", a clear button. */\n trailing?: ReactNode\n /** Critical border + `aria-invalid`. Inside a `<Field error>` this is set for you. */\n invalid?: boolean\n /**\n * Money and quantities: tabular figures in the theme's numeric face, right-aligned so\n * decimals line up. Defaults `inputMode` to `decimal`.\n */\n numeric?: boolean\n /** The native `size` attribute (character width), since `size` is the control size. */\n htmlSize?: number\n}\n\n/**\n * Single-line text input. Native props (`value`, `onChange`, `type`, `name`…) go to the\n * `<input>`, as does the ref; `className`/`style` go to the visible box around it.\n *\n * <Input numeric leading=\"$\" trailing=\"AUD\" placeholder=\"0.00\" />\n */\nexport const Input = markFieldAware(\n forwardRef<HTMLInputElement, InputProps>(function Input(\n {\n size = 'md',\n leading,\n trailing,\n invalid: invalidProp,\n numeric = false,\n htmlSize,\n id: idProp,\n disabled: disabledProp,\n required: requiredProp,\n readOnly,\n inputMode,\n className,\n style,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n ...rest\n },\n ref,\n ) {\n const inputRef = useRef<HTMLInputElement>(null)\n const composedRef = useMergedRefs(ref, inputRef)\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n disabled: disabledProp,\n readOnly,\n })\n\n // Clicking the padding or a text adornment focuses the input, like one native control.\n const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n const target = event.target as HTMLElement\n if (target === inputRef.current || target.closest('button, a, input, select, textarea'))\n return\n event.preventDefault()\n inputRef.current?.focus()\n }\n\n return (\n <div\n className={cx(control.box, styles.root, className)}\n style={style}\n data-size={size}\n data-numeric={numeric || undefined}\n data-invalid={field.invalid || undefined}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readOnly || undefined}\n onPointerDown={onPointerDown}\n >\n {leading != null ? (\n <span className={styles.adornment} data-slot=\"leading\">\n {leading}\n </span>\n ) : null}\n <input\n ref={composedRef}\n id={field.id}\n className={styles.input}\n size={htmlSize}\n disabled={field.disabled}\n readOnly={field.readOnly}\n required={requiredProp ?? field.required}\n inputMode={inputMode ?? (numeric ? 'decimal' : undefined)}\n aria-describedby={field.describedBy}\n aria-invalid={field.invalid || undefined}\n aria-required={field.required || undefined}\n aria-busy={field.busy || undefined}\n {...rest}\n />\n {trailing != null ? (\n <span className={styles.adornment} data-slot=\"trailing\">\n {trailing}\n </span>\n ) : null}\n </div>\n )\n }),\n)\n"],"mappings":";;;;;;;;AAqCA,IAAaI,IAAQC,EACnBC,EAAyC,SACvC,EACE,UAAO,MACP,YACA,aACA,SAASC,GACT,aAAU,IACV,aACA,IAAIC,GACJ,UAAUC,GACV,UAAUC,GACV,aACA,cACA,cACA,UACA,oBAAoBC,GACpB,gBAAgBC,GAChB,GAAGC,KAEL,GACA;CACA,IAAMC,IAAWC,EAAyB,IAAI,GACxCC,IAAcC,EAAcC,GAAKJ,CAAQ,GACzCK,IAAQC,EAAiB;EAC7B,IAAIZ;EACJ,aAAaG;EACb,SAASJ;EACT;EACA,UAAUE;EACV;CACF,CAAC;CAWD,OACE,kBAAC,OAAD;EACE,WAAWY,EAAGrB,EAAQ,KAAKC,EAAO,MAAMqB,CAAS;EAC1C;EACP,aAAWC;EACX,gBAAcC,KAAW,KAAA;EACzB,gBAAcL,EAAM,WAAW,KAAA;EAC/B,iBAAeA,EAAM,YAAY,KAAA;EACjC,iBAAeA,EAAM,YAAY,KAAA;EAClB,gBAjBI,MAAwC;GAC7D,IAAMM,IAASC,EAAM;GACrB,AAAID,MAAWX,EAAS,WAAWW,EAAO,QAAQ,oCAAoC,MAEtFC,EAAM,eAAe,GACrBZ,EAAS,SAAS,MAAM;EAC1B;EAGE,UAAA;GAUGa,KAAW,OAIR,OAHF,kBAAC,QAAD;IAAM,WAAW1B,EAAO;IAAW,aAAU;IAC1C,UAAA0B;GACG,CAAA;GAER,kBAAC,SAAD;IACE,KAAKX;IACL,IAAIG,EAAM;IACV,WAAWlB,EAAO;IAClB,MAAM2B;IACN,UAAUT,EAAM;IAChB,UAAUA,EAAM;IAChB,UAAUT,KAAgBS,EAAM;IAChC,WAAWU,MAAcL,IAAU,YAAY,KAAA;IAC/C,oBAAkBL,EAAM;IACxB,gBAAcA,EAAM,WAAW,KAAA;IAC/B,iBAAeA,EAAM,YAAY,KAAA;IACjC,aAAWA,EAAM,QAAQ,KAAA;IACzB,GAAIN;GACL,CAAA;GACAiB,KAAY,OAIT,OAHF,kBAAC,QAAD;IAAM,WAAW7B,EAAO;IAAW,aAAU;IAC1C,UAAA6B;GACG,CAAA;EAEL;;AAET,CAAC,CACH"}
1
+ {"version":3,"file":"Input.js","names":["control","styles","control_module_default","Input_module_default","Input","markFieldAware","forwardRef","invalidProp","idProp","disabledProp","requiredProp","describedByProp","ariaInvalid","rest","inputRef","useRef","composedRef","useMergedRefs","ref","field","useResolvedField","cx","className","size","numeric","target","event","leading","htmlSize","inputMode","trailing"],"sources":["../../../../src/components/inputs/Input/Input.tsx"],"sourcesContent":["import {\n forwardRef,\n useRef,\n type InputHTMLAttributes,\n type PointerEvent,\n type ReactNode,\n} from 'react'\nimport { cx } from '#utils/cx'\nimport type { Size } from '#utils/tokens'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport { useMergedRefs } from '#components/inputs/internal/refs'\nimport control from '#components/inputs/internal/control.module.css'\nimport styles from './Input.module.css'\n\nexport interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {\n size?: Size\n /** Content inside the box before the text: an icon, \"$\", a unit. Decorative unless it's a button. */\n leading?: ReactNode\n /** Content inside the box after the text: \"AUD\", \"kg\", a clear button. */\n trailing?: ReactNode\n /** Critical border + `aria-invalid`. Inside a `<Field error>` this is set for you. */\n invalid?: boolean\n /**\n * Money and quantities: tabular figures in the theme's numeric face, right-aligned so\n * decimals line up. Defaults `inputMode` to `decimal`.\n */\n numeric?: boolean\n /** The native `size` attribute (character width), since `size` is the control size. */\n htmlSize?: number\n}\n\n/**\n * A single-line text input with leading and trailing slots, a numeric mode and three sizes.\n *\n * @remarks\n * `Input` is the bare text box. Native props (`value`, `onChange`, `type`, `name`, `autoComplete`)\n * and the ref go to the `<input>`; `className` and `style` go to the visible box around it. Inside\n * a {@link Field | Field} it picks up its label, description and invalid state; outside one, give\n * it an `aria-label`. For the usual labelled field, use {@link TextField | TextField}.\n *\n * @privateRemarks\n * Single-line text input. Native props (`value`, `onChange`, `type`, `name`…) go to the\n * `<input>`, as does the ref; `className`/`style` go to the visible box around it.\n *\n * <Input numeric leading=\"$\" trailing=\"AUD\" placeholder=\"0.00\" />\n */\nexport const Input = markFieldAware(\n forwardRef<HTMLInputElement, InputProps>(function Input(\n {\n size = 'md',\n leading,\n trailing,\n invalid: invalidProp,\n numeric = false,\n htmlSize,\n id: idProp,\n disabled: disabledProp,\n required: requiredProp,\n readOnly,\n inputMode,\n className,\n style,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n ...rest\n },\n ref,\n ) {\n const inputRef = useRef<HTMLInputElement>(null)\n const composedRef = useMergedRefs(ref, inputRef)\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n disabled: disabledProp,\n readOnly,\n })\n\n // Clicking the padding or a text adornment focuses the input, like one native control.\n const onPointerDown = (event: PointerEvent<HTMLDivElement>) => {\n const target = event.target as HTMLElement\n if (target === inputRef.current || target.closest('button, a, input, select, textarea'))\n return\n event.preventDefault()\n inputRef.current?.focus()\n }\n\n return (\n <div\n className={cx(control.box, styles.root, className)}\n style={style}\n data-size={size}\n data-numeric={numeric || undefined}\n data-invalid={field.invalid || undefined}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readOnly || undefined}\n onPointerDown={onPointerDown}\n >\n {leading != null ? (\n <span className={styles.adornment} data-slot=\"leading\">\n {leading}\n </span>\n ) : null}\n <input\n ref={composedRef}\n id={field.id}\n className={styles.input}\n size={htmlSize}\n disabled={field.disabled}\n readOnly={field.readOnly}\n required={requiredProp ?? field.required}\n inputMode={inputMode ?? (numeric ? 'decimal' : undefined)}\n aria-describedby={field.describedBy}\n aria-invalid={field.invalid || undefined}\n aria-required={field.required || undefined}\n aria-busy={field.busy || undefined}\n {...rest}\n />\n {trailing != null ? (\n <span className={styles.adornment} data-slot=\"trailing\">\n {trailing}\n </span>\n ) : null}\n </div>\n )\n }),\n)\n"],"mappings":";;;;;;;;AA8CA,IAAaI,IAAQC,EACnBC,EAAyC,SACvC,EACE,UAAO,MACP,YACA,aACA,SAASC,GACT,aAAU,IACV,aACA,IAAIC,GACJ,UAAUC,GACV,UAAUC,GACV,aACA,cACA,cACA,UACA,oBAAoBC,GACpB,gBAAgBC,GAChB,GAAGC,KAEL,GACA;CACA,IAAMC,IAAWC,EAAyB,IAAI,GACxCC,IAAcC,EAAcC,GAAKJ,CAAQ,GACzCK,IAAQC,EAAiB;EAC7B,IAAIZ;EACJ,aAAaG;EACb,SAASJ;EACT;EACA,UAAUE;EACV;CACF,CAAC;CAWD,OACE,kBAAC,OAAD;EACE,WAAWY,EAAGrB,EAAQ,KAAKC,EAAO,MAAMqB,CAAS;EAC1C;EACP,aAAWC;EACX,gBAAcC,KAAW,KAAA;EACzB,gBAAcL,EAAM,WAAW,KAAA;EAC/B,iBAAeA,EAAM,YAAY,KAAA;EACjC,iBAAeA,EAAM,YAAY,KAAA;EAClB,gBAjBI,MAAwC;GAC7D,IAAMM,IAASC,EAAM;GACrB,AAAID,MAAWX,EAAS,WAAWW,EAAO,QAAQ,oCAAoC,MAEtFC,EAAM,eAAe,GACrBZ,EAAS,SAAS,MAAM;EAC1B;EAGE,UAAA;GAUGa,KAAW,OAIR,OAHF,kBAAC,QAAD;IAAM,WAAW1B,EAAO;IAAW,aAAU;IAC1C,UAAA0B;GACG,CAAA;GAER,kBAAC,SAAD;IACE,KAAKX;IACL,IAAIG,EAAM;IACV,WAAWlB,EAAO;IAClB,MAAM2B;IACN,UAAUT,EAAM;IAChB,UAAUA,EAAM;IAChB,UAAUT,KAAgBS,EAAM;IAChC,WAAWU,MAAcL,IAAU,YAAY,KAAA;IAC/C,oBAAkBL,EAAM;IACxB,gBAAcA,EAAM,WAAW,KAAA;IAC/B,iBAAeA,EAAM,YAAY,KAAA;IACjC,aAAWA,EAAM,QAAQ,KAAA;IACzB,GAAIN;GACL,CAAA;GACAiB,KAAY,OAIT,OAHF,kBAAC,QAAD;IAAM,WAAW7B,EAAO;IAAW,aAAU;IAC1C,UAAA6B;GACG,CAAA;EAEL;;AAET,CAAC,CACH"}
@@ -3,6 +3,13 @@ import { type NumberInputProps } from '../NumberInput/index.js';
3
3
  export interface NumberFieldProps extends FieldLabelProps, Omit<NumberInputProps, 'invalid' | keyof FieldLabelProps> {
4
4
  }
5
5
  /**
6
+ * A labelled number input that formats for the locale and steps with the keyboard.
7
+ *
8
+ * @remarks
9
+ * `NumberField` is a {@link Field | Field} around a {@link NumberInput | NumberInput}: a
10
+ * spinbutton formatted for the locale, with `min`, `max`, `step` and an optional stepper.
11
+ *
12
+ * @privateRemarks
6
13
  * Label + NumberInput + description + error. Input props (`value`, `min`, `step`,
7
14
  * `formatOptions`…) and the ref go to the spinbutton; `className`/`style` go to the wrapper.
8
15
  *
@@ -1 +1 @@
1
- {"version":3,"file":"NumberField.js","names":["NumberField","forwardRef","fieldProps","rest","splitFieldLabelProps","props","inputRest","Field","id","NumberInput"],"sources":["../../../../src/components/inputs/NumberField/NumberField.tsx"],"sourcesContent":["import { forwardRef } from 'react'\nimport { Field, type FieldLabelProps } from '#components/inputs/Field'\nimport { NumberInput, type NumberInputProps } from '#components/inputs/NumberInput'\nimport { splitFieldLabelProps } from '#components/inputs/internal/fieldProps'\n\nexport interface NumberFieldProps\n extends FieldLabelProps, Omit<NumberInputProps, 'invalid' | keyof FieldLabelProps> {}\n\n/**\n * Label + NumberInput + description + error. Input props (`value`, `min`, `step`,\n * `formatOptions`…) and the ref go to the spinbutton; `className`/`style` go to the wrapper.\n *\n * <NumberField label=\"Guests\" min={1} max={12} value={guests} onValueChange={setGuests} />\n */\nexport const NumberField = forwardRef<HTMLInputElement, NumberFieldProps>(\n function NumberField(props, ref) {\n const [fieldProps, rest] = splitFieldLabelProps(props)\n const { id, disabled, className, style, ...inputRest } = rest\n return (\n <Field {...fieldProps} htmlFor={id} disabled={disabled} className={className} style={style}>\n <NumberInput ref={ref} id={id} disabled={disabled} {...inputRest} />\n </Field>\n )\n },\n)\n"],"mappings":";;;;;;AAcA,IAAaA,IAAcC,EACzB,SAAqB,GAAO,GAAK;CAC/B,IAAM,CAACC,GAAYC,KAAQC,EAAqBC,CAAK,GAC/C,EAAE,OAAI,aAAU,cAAW,UAAO,GAAGC,MAAcH;CACzD,OACE,kBAACI,GAAD;EAAO,GAAIL;EAAY,SAASM;EAAc;EAAqB;EAAkB;EACnF,UAAA,kBAACC,GAAD;GAAkB;GAAS;GAAc;GAAU,GAAIH;EAAY,CAAA;CAC9D,CAAA;AAEX,CACF"}
1
+ {"version":3,"file":"NumberField.js","names":["NumberField","forwardRef","fieldProps","rest","splitFieldLabelProps","props","inputRest","Field","id","NumberInput"],"sources":["../../../../src/components/inputs/NumberField/NumberField.tsx"],"sourcesContent":["import { forwardRef } from 'react'\nimport { Field, type FieldLabelProps } from '#components/inputs/Field'\nimport { NumberInput, type NumberInputProps } from '#components/inputs/NumberInput'\nimport { splitFieldLabelProps } from '#components/inputs/internal/fieldProps'\n\nexport interface NumberFieldProps\n extends FieldLabelProps, Omit<NumberInputProps, 'invalid' | keyof FieldLabelProps> {}\n\n/**\n * A labelled number input that formats for the locale and steps with the keyboard.\n *\n * @remarks\n * `NumberField` is a {@link Field | Field} around a {@link NumberInput | NumberInput}: a\n * spinbutton formatted for the locale, with `min`, `max`, `step` and an optional stepper.\n *\n * @privateRemarks\n * Label + NumberInput + description + error. Input props (`value`, `min`, `step`,\n * `formatOptions`…) and the ref go to the spinbutton; `className`/`style` go to the wrapper.\n *\n * <NumberField label=\"Guests\" min={1} max={12} value={guests} onValueChange={setGuests} />\n */\nexport const NumberField = forwardRef<HTMLInputElement, NumberFieldProps>(\n function NumberField(props, ref) {\n const [fieldProps, rest] = splitFieldLabelProps(props)\n const { id, disabled, className, style, ...inputRest } = rest\n return (\n <Field {...fieldProps} htmlFor={id} disabled={disabled} className={className} style={style}>\n <NumberInput ref={ref} id={id} disabled={disabled} {...inputRest} />\n </Field>\n )\n },\n)\n"],"mappings":";;;;;;AAqBA,IAAaA,IAAcC,EACzB,SAAqB,GAAO,GAAK;CAC/B,IAAM,CAACC,GAAYC,KAAQC,EAAqBC,CAAK,GAC/C,EAAE,OAAI,aAAU,cAAW,UAAO,GAAGC,MAAcH;CACzD,OACE,kBAACI,GAAD;EAAO,GAAIL;EAAY,SAASM;EAAc;EAAqB;EAAkB;EACnF,UAAA,kBAACC,GAAD;GAAkB;GAAS;GAAc;GAAU,GAAIH;EAAY,CAAA;CAC9D,CAAA;AAEX,CACF"}
@@ -25,6 +25,14 @@ export interface NumberInputProps extends Omit<InputProps, 'type' | 'value' | 'd
25
25
  clampOnBlur?: boolean;
26
26
  }
27
27
  /**
28
+ * A number typed as text, as a spinbutton. Formatted when you leave it, raw while you edit it.
29
+ *
30
+ * @remarks
31
+ * `NumberInput` is a WAI-ARIA spinbutton, never `type="number"` (which mangles locales, scrolls on
32
+ * the wheel and hides what you typed). It shows the number formatted for the locale when blurred
33
+ * ("1,450.5") and raw while focused ("1450.5"), so editing never fights the grouping.
34
+ *
35
+ * @privateRemarks
28
36
  * A number typed as text — a WAI-ARIA spinbutton, never `type="number"`. Formatted in the
29
37
  * locale when blurred ("1,450.5"), raw while focused ("1450.5"), so editing never fights the
30
38
  * grouping. ↑/↓ step, PgUp/PgDn step ×10, Home/End jump to `min`/`max`, Enter commits.
@@ -1 +1 @@
1
- {"version":3,"file":"NumberInput.js","names":["_Fragment","styles","NumberInput_module_default","Fragment$1","NumberInput","markFieldAware","forwardRef","valueProp","idProp","disabledProp","readOnlyProp","describedBy","ariaInvalid","rest","field","useResolvedField","isControlled","internal","setInternal","useState","defaultValue","value","focused","setFocused","draft","setDraft","scale","formatOptions","precision","decimalsOf","step","bigStep","largeStep","formatter","useMemo","locale","display","v","raw","formatRaw","roundTo","parse","n","parseNumber","text","parsed","setValue","next","onValueChange","editable","stepBy","typed","base","clamp","delta","min","max","jumpTo","target","commit","clampOnBlur","handleChange","event","handleFocus","onFocus","handleBlur","onBlur","handleKeyDown","onKeyDown","action","keepFocus","autoId","useId","controlId","buttons","stepper","IconButton","decrementLabel","MinusIcon","incrementLabel","PlusIcon","allowsNegative","valueText","Input","trailing","name"],"sources":["../../../../src/components/inputs/NumberInput/NumberInput.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useState,\n type ChangeEvent,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n} from 'react'\nimport { MinusIcon, PlusIcon } from '#icons'\nimport { IconButton } from '#components/actions/IconButton'\nimport { Input, type InputProps } from '#components/inputs/Input'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport { clamp, decimalsOf, formatRaw, parseNumber, roundTo } from './numberFormat'\nimport styles from './NumberInput.module.css'\n\nexport interface NumberInputProps extends Omit<\n InputProps,\n 'type' | 'value' | 'defaultValue' | 'onChange' | 'inputMode' | 'numeric' | 'min' | 'max' | 'step'\n> {\n /** The number, or `null` when empty. Never `NaN`. */\n value?: number | null\n defaultValue?: number | null\n /** Called while typing (on every text that parses), on stepping, and on commit. `null` when cleared. */\n onValueChange?: (value: number | null) => void\n min?: number\n max?: number\n /** Arrow-key / button increment. Also sets the rounding precision on commit. Default `1`. */\n step?: number\n /** PageUp / PageDown increment. Default `10 × step`. */\n largeStep?: number\n /** Show − / + buttons at the end of the box (out of the tab order — the keyboard uses arrows). Default `true`. */\n stepper?: boolean\n /** Accessible name of the − button. Default `'Decrease'`. */\n decrementLabel?: string\n /** Accessible name of the + button. Default `'Increase'`. */\n incrementLabel?: string\n /** How the number reads when the field isn't focused (`{ style: 'percent' }`, `{ maximumFractionDigits: 1 }`). */\n formatOptions?: Intl.NumberFormatOptions\n /** Locale for parsing and formatting. Defaults to the browser's. */\n locale?: string\n /** Clamp to `min`/`max` when the typed value is committed (blur, Enter). Default `true`. */\n clampOnBlur?: boolean\n}\n\n/**\n * A number typed as text — a WAI-ARIA spinbutton, never `type=\"number\"`. Formatted in the\n * locale when blurred (\"1,450.5\"), raw while focused (\"1450.5\"), so editing never fights the\n * grouping. ↑/↓ step, PgUp/PgDn step ×10, Home/End jump to `min`/`max`, Enter commits.\n * The ref goes to the text input; `name` is submitted as a plain number (\"1450.5\").\n *\n * <NumberInput min={1} max={12} value={guests} onValueChange={setGuests} />\n */\nexport const NumberInput = markFieldAware(\n forwardRef<HTMLInputElement, NumberInputProps>(function NumberInput(\n {\n value: valueProp,\n defaultValue,\n onValueChange,\n min,\n max,\n step = 1,\n largeStep,\n stepper = true,\n decrementLabel = 'Decrease',\n incrementLabel = 'Increase',\n formatOptions,\n locale,\n clampOnBlur = true,\n name,\n form,\n trailing,\n size = 'md',\n id: idProp,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n invalid,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n onFocus,\n onBlur,\n onKeyDown,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({ id: idProp, disabled: disabledProp, readOnly: readOnlyProp })\n const isControlled = valueProp !== undefined\n const [internal, setInternal] = useState<number | null>(defaultValue ?? null)\n const value = isControlled ? valueProp : internal\n const [focused, setFocused] = useState(false)\n const [draft, setDraft] = useState('')\n\n const scale = formatOptions?.style === 'percent' ? 100 : 1\n const precision =\n Math.max(decimalsOf(step), formatOptions?.maximumFractionDigits ?? 0) +\n (scale === 100 ? 2 : 0)\n const bigStep = largeStep ?? step * 10\n const formatter = useMemo(\n () => new Intl.NumberFormat(locale, formatOptions),\n [locale, formatOptions],\n )\n const display = (v: number | null) => (v == null ? '' : formatter.format(v))\n const raw = (v: number | null) =>\n v == null ? '' : formatRaw(roundTo(v * scale, precision), locale)\n const parse = (text: string): number | null => {\n const n = parseNumber(text, locale)\n return n == null || Number.isNaN(n) ? n : scale === 1 ? n : roundTo(n / scale, precision)\n }\n\n // An outside change while focused (a form reset, a linked field) replaces the draft.\n useEffect(() => {\n if (!focused) return\n const parsed = parse(draft)\n if (parsed !== value && !(parsed == null && value == null)) setDraft(raw(value))\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only react to the value changing\n }, [value])\n\n const setValue = (next: number | null) => {\n if (!isControlled) setInternal(next)\n if (next !== value) onValueChange?.(next)\n }\n\n const editable = !field.disabled && !field.readOnly\n\n const stepBy = (delta: number) => {\n if (!editable) return\n const typed = focused ? parse(draft) : value\n const base = typed == null || Number.isNaN(typed) ? value : typed\n const next =\n base == null ? clamp(0, min, max) : clamp(roundTo(base + delta, precision), min, max)\n setValue(next)\n if (focused) setDraft(raw(next))\n }\n\n const jumpTo = (target: number | undefined) => {\n if (!editable || target == null) return\n setValue(target)\n if (focused) setDraft(raw(target))\n }\n\n const commit = () => {\n const parsed = parse(draft)\n if (parsed != null && Number.isNaN(parsed)) {\n setDraft(raw(value))\n return\n }\n const next =\n parsed == null\n ? null\n : clampOnBlur\n ? clamp(roundTo(parsed, precision), min, max)\n : roundTo(parsed, precision)\n setValue(next)\n setDraft(raw(next))\n }\n\n const handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n const text = event.target.value\n setDraft(text)\n const parsed = parse(text)\n if (parsed == null || !Number.isNaN(parsed)) setValue(parsed)\n }\n\n const handleFocus = (event: FocusEvent<HTMLInputElement>) => {\n setFocused(true)\n setDraft(raw(value))\n onFocus?.(event)\n }\n\n const handleBlur = (event: FocusEvent<HTMLInputElement>) => {\n commit()\n setFocused(false)\n onBlur?.(event)\n }\n\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n onKeyDown?.(event)\n if (event.defaultPrevented) return\n const actions: Record<string, () => void> = {\n ArrowUp: () => {\n stepBy(step)\n },\n ArrowDown: () => {\n stepBy(-step)\n },\n PageUp: () => {\n stepBy(bigStep)\n },\n PageDown: () => {\n stepBy(-bigStep)\n },\n Home: () => {\n jumpTo(min)\n },\n End: () => {\n jumpTo(max)\n },\n Enter: commit,\n }\n const action = actions[event.key]\n if (!action) return\n if ((event.key === 'Home' && min == null) || (event.key === 'End' && max == null)) return\n if (event.key !== 'Enter') event.preventDefault()\n action()\n }\n\n // Keep focus where it is when a stepper button is pressed.\n const keepFocus = (event: MouseEvent) => {\n event.preventDefault()\n }\n // The stepper buttons point at the input, so it always has an id (the Field's when inside one).\n const autoId = useId()\n const controlId = field.id ?? `${autoId}number`\n\n const buttons = stepper ? (\n <>\n <IconButton\n className={styles.stepper}\n size=\"sm\"\n label={decrementLabel}\n icon={<MinusIcon />}\n tabIndex={-1}\n aria-controls={controlId}\n disabled={!editable || (value != null && min != null && value <= min)}\n onMouseDown={keepFocus}\n onClick={() => {\n stepBy(-step)\n }}\n />\n <IconButton\n className={styles.stepper}\n size=\"sm\"\n label={incrementLabel}\n icon={<PlusIcon />}\n tabIndex={-1}\n aria-controls={controlId}\n disabled={!editable || (value != null && max != null && value >= max)}\n onMouseDown={keepFocus}\n onClick={() => {\n stepBy(step)\n }}\n />\n </>\n ) : null\n\n const allowsNegative = min == null || min < 0\n const valueText = value == null ? undefined : display(value)\n\n return (\n <>\n <Input\n ref={ref}\n {...rest}\n id={controlId}\n size={size}\n numeric\n type=\"text\"\n role=\"spinbutton\"\n inputMode={\n allowsNegative ? 'text' : decimalsOf(step) === 0 && scale === 1 ? 'numeric' : 'decimal'\n }\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n disabled={disabledProp}\n readOnly={readOnlyProp}\n invalid={invalid}\n aria-describedby={describedBy}\n aria-invalid={ariaInvalid}\n aria-valuenow={value ?? undefined}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuetext={valueText}\n value={focused ? draft : display(value)}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n form={form}\n trailing={\n trailing != null || buttons ? (\n <>\n {trailing}\n {buttons}\n </>\n ) : undefined\n }\n />\n {/* Disabled like the control it stands for: a disabled control submits nothing. */}\n {name != null ? (\n <input\n type=\"hidden\"\n name={name}\n form={form}\n value={value == null ? '' : String(value)}\n disabled={field.disabled}\n />\n ) : null}\n </>\n )\n }),\n)\n"],"mappings":"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"}
1
+ {"version":3,"file":"NumberInput.js","names":["_Fragment","styles","NumberInput_module_default","Fragment$1","NumberInput","markFieldAware","forwardRef","valueProp","idProp","disabledProp","readOnlyProp","describedBy","ariaInvalid","rest","field","useResolvedField","isControlled","internal","setInternal","useState","defaultValue","value","focused","setFocused","draft","setDraft","scale","formatOptions","precision","decimalsOf","step","bigStep","largeStep","formatter","useMemo","locale","display","v","raw","formatRaw","roundTo","parse","n","parseNumber","text","parsed","setValue","next","onValueChange","editable","stepBy","typed","base","clamp","delta","min","max","jumpTo","target","commit","clampOnBlur","handleChange","event","handleFocus","onFocus","handleBlur","onBlur","handleKeyDown","onKeyDown","action","keepFocus","autoId","useId","controlId","buttons","stepper","IconButton","decrementLabel","MinusIcon","incrementLabel","PlusIcon","allowsNegative","valueText","Input","trailing","name"],"sources":["../../../../src/components/inputs/NumberInput/NumberInput.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useState,\n type ChangeEvent,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n} from 'react'\nimport { MinusIcon, PlusIcon } from '#icons'\nimport { IconButton } from '#components/actions/IconButton'\nimport { Input, type InputProps } from '#components/inputs/Input'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport { clamp, decimalsOf, formatRaw, parseNumber, roundTo } from './numberFormat'\nimport styles from './NumberInput.module.css'\n\nexport interface NumberInputProps extends Omit<\n InputProps,\n 'type' | 'value' | 'defaultValue' | 'onChange' | 'inputMode' | 'numeric' | 'min' | 'max' | 'step'\n> {\n /** The number, or `null` when empty. Never `NaN`. */\n value?: number | null\n defaultValue?: number | null\n /** Called while typing (on every text that parses), on stepping, and on commit. `null` when cleared. */\n onValueChange?: (value: number | null) => void\n min?: number\n max?: number\n /** Arrow-key / button increment. Also sets the rounding precision on commit. Default `1`. */\n step?: number\n /** PageUp / PageDown increment. Default `10 × step`. */\n largeStep?: number\n /** Show − / + buttons at the end of the box (out of the tab order — the keyboard uses arrows). Default `true`. */\n stepper?: boolean\n /** Accessible name of the − button. Default `'Decrease'`. */\n decrementLabel?: string\n /** Accessible name of the + button. Default `'Increase'`. */\n incrementLabel?: string\n /** How the number reads when the field isn't focused (`{ style: 'percent' }`, `{ maximumFractionDigits: 1 }`). */\n formatOptions?: Intl.NumberFormatOptions\n /** Locale for parsing and formatting. Defaults to the browser's. */\n locale?: string\n /** Clamp to `min`/`max` when the typed value is committed (blur, Enter). Default `true`. */\n clampOnBlur?: boolean\n}\n\n/**\n * A number typed as text, as a spinbutton. Formatted when you leave it, raw while you edit it.\n *\n * @remarks\n * `NumberInput` is a WAI-ARIA spinbutton, never `type=\"number\"` (which mangles locales, scrolls on\n * the wheel and hides what you typed). It shows the number formatted for the locale when blurred\n * (\"1,450.5\") and raw while focused (\"1450.5\"), so editing never fights the grouping.\n *\n * @privateRemarks\n * A number typed as text — a WAI-ARIA spinbutton, never `type=\"number\"`. Formatted in the\n * locale when blurred (\"1,450.5\"), raw while focused (\"1450.5\"), so editing never fights the\n * grouping. ↑/↓ step, PgUp/PgDn step ×10, Home/End jump to `min`/`max`, Enter commits.\n * The ref goes to the text input; `name` is submitted as a plain number (\"1450.5\").\n *\n * <NumberInput min={1} max={12} value={guests} onValueChange={setGuests} />\n */\nexport const NumberInput = markFieldAware(\n forwardRef<HTMLInputElement, NumberInputProps>(function NumberInput(\n {\n value: valueProp,\n defaultValue,\n onValueChange,\n min,\n max,\n step = 1,\n largeStep,\n stepper = true,\n decrementLabel = 'Decrease',\n incrementLabel = 'Increase',\n formatOptions,\n locale,\n clampOnBlur = true,\n name,\n form,\n trailing,\n size = 'md',\n id: idProp,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n invalid,\n 'aria-describedby': describedBy,\n 'aria-invalid': ariaInvalid,\n onFocus,\n onBlur,\n onKeyDown,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({ id: idProp, disabled: disabledProp, readOnly: readOnlyProp })\n const isControlled = valueProp !== undefined\n const [internal, setInternal] = useState<number | null>(defaultValue ?? null)\n const value = isControlled ? valueProp : internal\n const [focused, setFocused] = useState(false)\n const [draft, setDraft] = useState('')\n\n const scale = formatOptions?.style === 'percent' ? 100 : 1\n const precision =\n Math.max(decimalsOf(step), formatOptions?.maximumFractionDigits ?? 0) +\n (scale === 100 ? 2 : 0)\n const bigStep = largeStep ?? step * 10\n const formatter = useMemo(\n () => new Intl.NumberFormat(locale, formatOptions),\n [locale, formatOptions],\n )\n const display = (v: number | null) => (v == null ? '' : formatter.format(v))\n const raw = (v: number | null) =>\n v == null ? '' : formatRaw(roundTo(v * scale, precision), locale)\n const parse = (text: string): number | null => {\n const n = parseNumber(text, locale)\n return n == null || Number.isNaN(n) ? n : scale === 1 ? n : roundTo(n / scale, precision)\n }\n\n // An outside change while focused (a form reset, a linked field) replaces the draft.\n useEffect(() => {\n if (!focused) return\n const parsed = parse(draft)\n if (parsed !== value && !(parsed == null && value == null)) setDraft(raw(value))\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only react to the value changing\n }, [value])\n\n const setValue = (next: number | null) => {\n if (!isControlled) setInternal(next)\n if (next !== value) onValueChange?.(next)\n }\n\n const editable = !field.disabled && !field.readOnly\n\n const stepBy = (delta: number) => {\n if (!editable) return\n const typed = focused ? parse(draft) : value\n const base = typed == null || Number.isNaN(typed) ? value : typed\n const next =\n base == null ? clamp(0, min, max) : clamp(roundTo(base + delta, precision), min, max)\n setValue(next)\n if (focused) setDraft(raw(next))\n }\n\n const jumpTo = (target: number | undefined) => {\n if (!editable || target == null) return\n setValue(target)\n if (focused) setDraft(raw(target))\n }\n\n const commit = () => {\n const parsed = parse(draft)\n if (parsed != null && Number.isNaN(parsed)) {\n setDraft(raw(value))\n return\n }\n const next =\n parsed == null\n ? null\n : clampOnBlur\n ? clamp(roundTo(parsed, precision), min, max)\n : roundTo(parsed, precision)\n setValue(next)\n setDraft(raw(next))\n }\n\n const handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n const text = event.target.value\n setDraft(text)\n const parsed = parse(text)\n if (parsed == null || !Number.isNaN(parsed)) setValue(parsed)\n }\n\n const handleFocus = (event: FocusEvent<HTMLInputElement>) => {\n setFocused(true)\n setDraft(raw(value))\n onFocus?.(event)\n }\n\n const handleBlur = (event: FocusEvent<HTMLInputElement>) => {\n commit()\n setFocused(false)\n onBlur?.(event)\n }\n\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n onKeyDown?.(event)\n if (event.defaultPrevented) return\n const actions: Record<string, () => void> = {\n ArrowUp: () => {\n stepBy(step)\n },\n ArrowDown: () => {\n stepBy(-step)\n },\n PageUp: () => {\n stepBy(bigStep)\n },\n PageDown: () => {\n stepBy(-bigStep)\n },\n Home: () => {\n jumpTo(min)\n },\n End: () => {\n jumpTo(max)\n },\n Enter: commit,\n }\n const action = actions[event.key]\n if (!action) return\n if ((event.key === 'Home' && min == null) || (event.key === 'End' && max == null)) return\n if (event.key !== 'Enter') event.preventDefault()\n action()\n }\n\n // Keep focus where it is when a stepper button is pressed.\n const keepFocus = (event: MouseEvent) => {\n event.preventDefault()\n }\n // The stepper buttons point at the input, so it always has an id (the Field's when inside one).\n const autoId = useId()\n const controlId = field.id ?? `${autoId}number`\n\n const buttons = stepper ? (\n <>\n <IconButton\n className={styles.stepper}\n size=\"sm\"\n label={decrementLabel}\n icon={<MinusIcon />}\n tabIndex={-1}\n aria-controls={controlId}\n disabled={!editable || (value != null && min != null && value <= min)}\n onMouseDown={keepFocus}\n onClick={() => {\n stepBy(-step)\n }}\n />\n <IconButton\n className={styles.stepper}\n size=\"sm\"\n label={incrementLabel}\n icon={<PlusIcon />}\n tabIndex={-1}\n aria-controls={controlId}\n disabled={!editable || (value != null && max != null && value >= max)}\n onMouseDown={keepFocus}\n onClick={() => {\n stepBy(step)\n }}\n />\n </>\n ) : null\n\n const allowsNegative = min == null || min < 0\n const valueText = value == null ? undefined : display(value)\n\n return (\n <>\n <Input\n ref={ref}\n {...rest}\n id={controlId}\n size={size}\n numeric\n type=\"text\"\n role=\"spinbutton\"\n inputMode={\n allowsNegative ? 'text' : decimalsOf(step) === 0 && scale === 1 ? 'numeric' : 'decimal'\n }\n autoComplete=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n disabled={disabledProp}\n readOnly={readOnlyProp}\n invalid={invalid}\n aria-describedby={describedBy}\n aria-invalid={ariaInvalid}\n aria-valuenow={value ?? undefined}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuetext={valueText}\n value={focused ? draft : display(value)}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n form={form}\n trailing={\n trailing != null || buttons ? (\n <>\n {trailing}\n {buttons}\n </>\n ) : undefined\n }\n />\n {/* Disabled like the control it stands for: a disabled control submits nothing. */}\n {name != null ? (\n <input\n type=\"hidden\"\n name={name}\n form={form}\n value={value == null ? '' : String(value)}\n disabled={field.disabled}\n />\n ) : null}\n </>\n )\n }),\n)\n"],"mappings":"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"}
@@ -3,6 +3,13 @@ import { type OneTimeCodeInputProps } from '../OneTimeCodeInput/index.js';
3
3
  export interface OneTimeCodeFieldProps extends FieldLabelProps, Omit<OneTimeCodeInputProps, 'invalid' | keyof FieldLabelProps> {
4
4
  }
5
5
  /**
6
+ * A labelled one-time code input, filled by typing, pasting or a text message.
7
+ *
8
+ * @remarks
9
+ * `OneTimeCodeField` is a {@link Field | Field} around a {@link OneTimeCodeInput |
10
+ * OneTimeCodeInput}. The label names the group of cells.
11
+ *
12
+ * @privateRemarks
6
13
  * Label + OneTimeCodeInput + description + error. The label names the group of cells
7
14
  * (and clicking it focuses the first); `className`/`style` go to the wrapper, the ref to
8
15
  * the group.
@@ -1 +1 @@
1
- {"version":3,"file":"OneTimeCodeField.js","names":["OneTimeCodeField","forwardRef","fieldProps","rest","splitFieldLabelProps","props","inputRest","Field","id","OneTimeCodeInput"],"sources":["../../../../src/components/inputs/OneTimeCodeField/OneTimeCodeField.tsx"],"sourcesContent":["import { forwardRef } from 'react'\nimport { Field, type FieldLabelProps } from '#components/inputs/Field'\nimport { OneTimeCodeInput, type OneTimeCodeInputProps } from '#components/inputs/OneTimeCodeInput'\nimport { splitFieldLabelProps } from '#components/inputs/internal/fieldProps'\n\nexport interface OneTimeCodeFieldProps\n extends FieldLabelProps, Omit<OneTimeCodeInputProps, 'invalid' | keyof FieldLabelProps> {}\n\n/**\n * Label + OneTimeCodeInput + description + error. The label names the group of cells\n * (and clicking it focuses the first); `className`/`style` go to the wrapper, the ref to\n * the group.\n *\n * <OneTimeCodeField label=\"Verification code\" description=\"Sent to 0412 345 678\" onComplete={verify} />\n */\nexport const OneTimeCodeField = forwardRef<HTMLDivElement, OneTimeCodeFieldProps>(\n function OneTimeCodeField(props, ref) {\n const [fieldProps, rest] = splitFieldLabelProps(props)\n const { id, disabled, className, style, ...inputRest } = rest\n return (\n <Field {...fieldProps} htmlFor={id} disabled={disabled} className={className} style={style}>\n <OneTimeCodeInput ref={ref} id={id} disabled={disabled} {...inputRest} />\n </Field>\n )\n },\n)\n"],"mappings":";;;;;;AAeA,IAAaA,IAAmBC,EAC9B,SAA0B,GAAO,GAAK;CACpC,IAAM,CAACC,GAAYC,KAAQC,EAAqBC,CAAK,GAC/C,EAAE,OAAI,aAAU,cAAW,UAAO,GAAGC,MAAcH;CACzD,OACE,kBAACI,GAAD;EAAO,GAAIL;EAAY,SAASM;EAAc;EAAqB;EAAkB;EACnF,UAAA,kBAACC,GAAD;GAAuB;GAAS;GAAc;GAAU,GAAIH;EAAY,CAAA;CACnE,CAAA;AAEX,CACF"}
1
+ {"version":3,"file":"OneTimeCodeField.js","names":["OneTimeCodeField","forwardRef","fieldProps","rest","splitFieldLabelProps","props","inputRest","Field","id","OneTimeCodeInput"],"sources":["../../../../src/components/inputs/OneTimeCodeField/OneTimeCodeField.tsx"],"sourcesContent":["import { forwardRef } from 'react'\nimport { Field, type FieldLabelProps } from '#components/inputs/Field'\nimport { OneTimeCodeInput, type OneTimeCodeInputProps } from '#components/inputs/OneTimeCodeInput'\nimport { splitFieldLabelProps } from '#components/inputs/internal/fieldProps'\n\nexport interface OneTimeCodeFieldProps\n extends FieldLabelProps, Omit<OneTimeCodeInputProps, 'invalid' | keyof FieldLabelProps> {}\n\n/**\n * A labelled one-time code input, filled by typing, pasting or a text message.\n *\n * @remarks\n * `OneTimeCodeField` is a {@link Field | Field} around a {@link OneTimeCodeInput |\n * OneTimeCodeInput}. The label names the group of cells.\n *\n * @privateRemarks\n * Label + OneTimeCodeInput + description + error. The label names the group of cells\n * (and clicking it focuses the first); `className`/`style` go to the wrapper, the ref to\n * the group.\n *\n * <OneTimeCodeField label=\"Verification code\" description=\"Sent to 0412 345 678\" onComplete={verify} />\n */\nexport const OneTimeCodeField = forwardRef<HTMLDivElement, OneTimeCodeFieldProps>(\n function OneTimeCodeField(props, ref) {\n const [fieldProps, rest] = splitFieldLabelProps(props)\n const { id, disabled, className, style, ...inputRest } = rest\n return (\n <Field {...fieldProps} htmlFor={id} disabled={disabled} className={className} style={style}>\n <OneTimeCodeInput ref={ref} id={id} disabled={disabled} {...inputRest} />\n </Field>\n )\n },\n)\n"],"mappings":";;;;;;AAsBA,IAAaA,IAAmBC,EAC9B,SAA0B,GAAO,GAAK;CACpC,IAAM,CAACC,GAAYC,KAAQC,EAAqBC,CAAK,GAC/C,EAAE,OAAI,aAAU,cAAW,UAAO,GAAGC,MAAcH;CACzD,OACE,kBAACI,GAAD;EAAO,GAAIL;EAAY,SAASM;EAAc;EAAqB;EAAkB;EACnF,UAAA,kBAACC,GAAD;GAAuB;GAAS;GAAc;GAAU,GAAIH;EAAY,CAAA;CACnE,CAAA;AAEX,CACF"}
@@ -25,6 +25,14 @@ export interface OneTimeCodeInputProps extends Omit<HTMLAttributes<HTMLDivElemen
25
25
  invalid?: boolean;
26
26
  }
27
27
  /**
28
+ * A one-time code as a row of single-character cells that fill from a paste or a text message.
29
+ *
30
+ * @remarks
31
+ * `OneTimeCodeInput` is Radix OneTimePasswordField with Kiln's look. Typing advances, Backspace
32
+ * goes back, a pasted or autofilled code fills every cell, and `autoComplete="one-time-code"` lets
33
+ * a phone offer the code from a text message. `onComplete` fires when every cell is filled.
34
+ *
35
+ * @privateRemarks
28
36
  * A one-time code as a row of single-character cells (Radix OneTimePasswordField): typing
29
37
  * advances, Backspace goes back, a pasted or autofilled code fills every cell, and
30
38
  * `autoComplete="one-time-code"` lets phones offer the code from a text message. Cells
@@ -1 +1 @@
1
- {"version":3,"file":"OneTimeCodeInput.js","names":["OTP","styles","OneTimeCodeInput_module_default","OneTimeCodeInput","markFieldAware","forwardRef","valueProp","disabledProp","readOnlyProp","invalidProp","idProp","describedByProp","ariaInvalid","labelledByProp","rest","field","useResolvedField","internal","setInternal","useState","defaultValue","value","handleValueChange","next","onValueChange","length","onComplete","handleBlur","event","onBlur","noun","validationType","splitAt","unstable_OneTimePasswordField","cx","className","size","masked","index"],"sources":["../../../../src/components/inputs/OneTimeCodeInput/OneTimeCodeInput.tsx"],"sourcesContent":["import { forwardRef, useState, type FocusEvent, type HTMLAttributes } from 'react'\nimport { unstable_OneTimePasswordField as OTP } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport type { Size } from '#utils/tokens'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport styles from './OneTimeCodeInput.module.css'\n\nexport type OneTimeCodeValidation = 'numeric' | 'alpha' | 'alphanumeric'\n\nexport interface OneTimeCodeInputProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue' | 'dir'\n> {\n /** Reading direction of the cells (arrow keys follow it). Inherits by default. */\n dir?: 'ltr' | 'rtl'\n /** Number of characters. Default `6`. */\n length?: number\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n /** Called once every cell is filled — verify here, or submit. */\n onComplete?: (value: string) => void\n /** Which characters a cell accepts; anything else is ignored (typing and paste). Default `numeric`. */\n validationType?: OneTimeCodeValidation\n /** Show dots instead of characters. */\n masked?: boolean\n /** Submitted as one value from a hidden input. */\n name?: string\n disabled?: boolean\n readOnly?: boolean\n autoFocus?: boolean\n size?: Size\n /** Critical borders + `aria-invalid`. Inside a `<Field error>` this is set for you. */\n invalid?: boolean\n}\n\n/**\n * A one-time code as a row of single-character cells (Radix OneTimePasswordField): typing\n * advances, Backspace goes back, a pasted or autofilled code fills every cell, and\n * `autoComplete=\"one-time-code\"` lets phones offer the code from a text message. Cells\n * are a `group` labelled by the surrounding Field. The ref goes to the group.\n *\n * <OneTimeCodeInput length={6} onComplete={verify} />\n */\nexport const OneTimeCodeInput = markFieldAware(\n forwardRef<HTMLDivElement, OneTimeCodeInputProps>(function OneTimeCodeInput(\n {\n length = 6,\n value: valueProp,\n defaultValue,\n onValueChange,\n onComplete,\n validationType = 'numeric',\n masked = false,\n name,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n autoFocus,\n size = 'md',\n invalid: invalidProp,\n id: idProp,\n className,\n onBlur,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n 'aria-labelledby': labelledByProp,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n })\n const [internal, setInternal] = useState(defaultValue ?? '')\n const value = valueProp ?? internal\n\n const handleValueChange = (next: string) => {\n if (field.readOnly) return\n if (valueProp === undefined) setInternal(next)\n onValueChange?.(next)\n if (next.length === length) onComplete?.(next)\n }\n\n // Moving between cells isn't a blur; leaving the whole group is.\n const handleBlur = (event: FocusEvent<HTMLDivElement>) => {\n if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget))\n return\n onBlur?.(event)\n }\n\n const noun = validationType === 'numeric' ? 'Digit' : 'Character'\n const splitAt = length >= 6 && length % 2 === 0 ? length / 2 : -1\n\n return (\n <OTP.Root\n ref={ref}\n {...rest}\n className={cx(styles.root, className)}\n data-size={size}\n data-invalid={field.invalid || undefined}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readOnly || undefined}\n value={value.slice(0, length)}\n onValueChange={handleValueChange}\n validationType={validationType}\n type={masked ? 'password' : 'text'}\n autoComplete=\"one-time-code\"\n // Only when the consumer asks for it, e.g. a code screen with nothing else to do.\n // eslint-disable-next-line jsx-a11y/no-autofocus\n autoFocus={autoFocus}\n disabled={field.disabled}\n readOnly={field.readOnly}\n name={name}\n aria-labelledby={labelledByProp ?? field.labelId}\n aria-describedby={field.describedBy}\n aria-busy={field.busy || undefined}\n onBlur={handleBlur}\n >\n {Array.from({ length }, (_, index) => (\n <OTP.Input\n key={index}\n index={index}\n id={index === 0 ? field.id : undefined}\n className={styles.cell}\n data-group-start={index === splitAt || undefined}\n aria-label={`${noun} ${String(index + 1)} of ${String(length)}`}\n aria-invalid={field.invalid || undefined}\n aria-required={field.required || undefined}\n />\n ))}\n {/* Radix's hidden input can't be disabled (its props omit `disabled`), so it isn't\n rendered at all while disabled: a disabled control must submit nothing. */}\n {field.disabled ? null : <OTP.HiddenInput />}\n </OTP.Root>\n )\n }),\n)\n"],"mappings":";;;;;;;AA4CA,IAAaG,IAAmBC,EAC9BC,EAAkD,SAChD,EACE,YAAS,GACT,OAAOC,GACP,iBACA,kBACA,eACA,oBAAiB,WACjB,YAAS,IACT,SACA,UAAUC,GACV,UAAUC,GACV,cACA,UAAO,MACP,SAASC,GACT,IAAIC,GACJ,cACA,WACA,oBAAoBC,GACpB,gBAAgBC,GAChB,mBAAmBC,GACnB,GAAGC,KAEL,GACA;CACA,IAAMC,IAAQC,EAAiB;EAC7B,IAAIN;EACJ,aAAaC;EACb,SAASF;EACT;EACA,UAAUF;EACV,UAAUC;CACZ,CAAC,GACK,CAACS,GAAUC,KAAeC,EAASC,KAAgB,EAAE,GACrDC,IAAQf,KAAaW,GAErBK,KAAqB,MAAiB;EAC1C,AAAIP,EAAM,aACNT,MAAc,KAAA,KAAWY,EAAYK,CAAI,GAC7CC,IAAgBD,CAAI,GAChBA,EAAK,WAAWE,KAAQC,IAAaH,CAAI;CAC/C,GAGMI,KAAc,MAAsC;EACxD,AAAIC,EAAM,yBAAyB,QAAQA,EAAM,cAAc,SAASA,EAAM,aAAa,KAE3FC,IAASD,CAAK;CAChB,GAEME,IAAOC,MAAmB,YAAY,UAAU,aAChDC,IAAUP,KAAU,KAAKA,IAAS,KAAM,IAAIA,IAAS,IAAI;CAE/D,OACE,kBAACzB,EAAI,MAAL;EACO;EACL,GAAIc;EACJ,WAAWoB,EAAGjC,EAAO,MAAMkC,CAAS;EACpC,aAAWC;EACX,gBAAcrB,EAAM,WAAW,KAAA;EAC/B,iBAAeA,EAAM,YAAY,KAAA;EACjC,iBAAeA,EAAM,YAAY,KAAA;EACjC,OAAOM,EAAM,MAAM,GAAGI,CAAM;EAC5B,eAAeH;EACC;EAChB,MAAMe,IAAS,aAAa;EAC5B,cAAa;EAGF;EACX,UAAUtB,EAAM;EAChB,UAAUA,EAAM;EACV;EACN,mBAAiBF,KAAkBE,EAAM;EACzC,oBAAkBA,EAAM;EACxB,aAAWA,EAAM,QAAQ,KAAA;EACzB,QAAQY;EAtBV,UAAA,CAwBG,MAAM,KAAK,EAAE,UAAO,IAAI,GAAG,MAC1B,kBAAC3B,EAAI,OAAL;GAES;GACP,IAAIsC,MAAU,IAAIvB,EAAM,KAAK,KAAA;GAC7B,WAAWd,EAAO;GAClB,oBAAkBqC,MAAUN,KAAW,KAAA;GACvC,cAAY,GAAGF,EAAK,GAAG,OAAOQ,IAAQ,CAAC,EAAE,MAAM,OAAOb,CAAM;GAC5D,gBAAcV,EAAM,WAAW,KAAA;GAC/B,iBAAeA,EAAM,YAAY,KAAA;EAClC,GARMuB,CAQN,CACF,GAGAvB,EAAM,WAAW,OAAO,kBAACf,EAAI,aAAL,CAAkB,CAAA,CACnC;;AAEd,CAAC,CACH"}
1
+ {"version":3,"file":"OneTimeCodeInput.js","names":["OTP","styles","OneTimeCodeInput_module_default","OneTimeCodeInput","markFieldAware","forwardRef","valueProp","disabledProp","readOnlyProp","invalidProp","idProp","describedByProp","ariaInvalid","labelledByProp","rest","field","useResolvedField","internal","setInternal","useState","defaultValue","value","handleValueChange","next","onValueChange","length","onComplete","handleBlur","event","onBlur","noun","validationType","splitAt","unstable_OneTimePasswordField","cx","className","size","masked","index"],"sources":["../../../../src/components/inputs/OneTimeCodeInput/OneTimeCodeInput.tsx"],"sourcesContent":["import { forwardRef, useState, type FocusEvent, type HTMLAttributes } from 'react'\nimport { unstable_OneTimePasswordField as OTP } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport type { Size } from '#utils/tokens'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport styles from './OneTimeCodeInput.module.css'\n\nexport type OneTimeCodeValidation = 'numeric' | 'alpha' | 'alphanumeric'\n\nexport interface OneTimeCodeInputProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue' | 'dir'\n> {\n /** Reading direction of the cells (arrow keys follow it). Inherits by default. */\n dir?: 'ltr' | 'rtl'\n /** Number of characters. Default `6`. */\n length?: number\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n /** Called once every cell is filled — verify here, or submit. */\n onComplete?: (value: string) => void\n /** Which characters a cell accepts; anything else is ignored (typing and paste). Default `numeric`. */\n validationType?: OneTimeCodeValidation\n /** Show dots instead of characters. */\n masked?: boolean\n /** Submitted as one value from a hidden input. */\n name?: string\n disabled?: boolean\n readOnly?: boolean\n autoFocus?: boolean\n size?: Size\n /** Critical borders + `aria-invalid`. Inside a `<Field error>` this is set for you. */\n invalid?: boolean\n}\n\n/**\n * A one-time code as a row of single-character cells that fill from a paste or a text message.\n *\n * @remarks\n * `OneTimeCodeInput` is Radix OneTimePasswordField with Kiln's look. Typing advances, Backspace\n * goes back, a pasted or autofilled code fills every cell, and `autoComplete=\"one-time-code\"` lets\n * a phone offer the code from a text message. `onComplete` fires when every cell is filled.\n *\n * @privateRemarks\n * A one-time code as a row of single-character cells (Radix OneTimePasswordField): typing\n * advances, Backspace goes back, a pasted or autofilled code fills every cell, and\n * `autoComplete=\"one-time-code\"` lets phones offer the code from a text message. Cells\n * are a `group` labelled by the surrounding Field. The ref goes to the group.\n *\n * <OneTimeCodeInput length={6} onComplete={verify} />\n */\nexport const OneTimeCodeInput = markFieldAware(\n forwardRef<HTMLDivElement, OneTimeCodeInputProps>(function OneTimeCodeInput(\n {\n length = 6,\n value: valueProp,\n defaultValue,\n onValueChange,\n onComplete,\n validationType = 'numeric',\n masked = false,\n name,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n autoFocus,\n size = 'md',\n invalid: invalidProp,\n id: idProp,\n className,\n onBlur,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n 'aria-labelledby': labelledByProp,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n })\n const [internal, setInternal] = useState(defaultValue ?? '')\n const value = valueProp ?? internal\n\n const handleValueChange = (next: string) => {\n if (field.readOnly) return\n if (valueProp === undefined) setInternal(next)\n onValueChange?.(next)\n if (next.length === length) onComplete?.(next)\n }\n\n // Moving between cells isn't a blur; leaving the whole group is.\n const handleBlur = (event: FocusEvent<HTMLDivElement>) => {\n if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget))\n return\n onBlur?.(event)\n }\n\n const noun = validationType === 'numeric' ? 'Digit' : 'Character'\n const splitAt = length >= 6 && length % 2 === 0 ? length / 2 : -1\n\n return (\n <OTP.Root\n ref={ref}\n {...rest}\n className={cx(styles.root, className)}\n data-size={size}\n data-invalid={field.invalid || undefined}\n data-disabled={field.disabled || undefined}\n data-readonly={field.readOnly || undefined}\n value={value.slice(0, length)}\n onValueChange={handleValueChange}\n validationType={validationType}\n type={masked ? 'password' : 'text'}\n autoComplete=\"one-time-code\"\n // Only when the consumer asks for it, e.g. a code screen with nothing else to do.\n // eslint-disable-next-line jsx-a11y/no-autofocus\n autoFocus={autoFocus}\n disabled={field.disabled}\n readOnly={field.readOnly}\n name={name}\n aria-labelledby={labelledByProp ?? field.labelId}\n aria-describedby={field.describedBy}\n aria-busy={field.busy || undefined}\n onBlur={handleBlur}\n >\n {Array.from({ length }, (_, index) => (\n <OTP.Input\n key={index}\n index={index}\n id={index === 0 ? field.id : undefined}\n className={styles.cell}\n data-group-start={index === splitAt || undefined}\n aria-label={`${noun} ${String(index + 1)} of ${String(length)}`}\n aria-invalid={field.invalid || undefined}\n aria-required={field.required || undefined}\n />\n ))}\n {/* Radix's hidden input can't be disabled (its props omit `disabled`), so it isn't\n rendered at all while disabled: a disabled control must submit nothing. */}\n {field.disabled ? null : <OTP.HiddenInput />}\n </OTP.Root>\n )\n }),\n)\n"],"mappings":";;;;;;;AAoDA,IAAaG,IAAmBC,EAC9BC,EAAkD,SAChD,EACE,YAAS,GACT,OAAOC,GACP,iBACA,kBACA,eACA,oBAAiB,WACjB,YAAS,IACT,SACA,UAAUC,GACV,UAAUC,GACV,cACA,UAAO,MACP,SAASC,GACT,IAAIC,GACJ,cACA,WACA,oBAAoBC,GACpB,gBAAgBC,GAChB,mBAAmBC,GACnB,GAAGC,KAEL,GACA;CACA,IAAMC,IAAQC,EAAiB;EAC7B,IAAIN;EACJ,aAAaC;EACb,SAASF;EACT;EACA,UAAUF;EACV,UAAUC;CACZ,CAAC,GACK,CAACS,GAAUC,KAAeC,EAASC,KAAgB,EAAE,GACrDC,IAAQf,KAAaW,GAErBK,KAAqB,MAAiB;EAC1C,AAAIP,EAAM,aACNT,MAAc,KAAA,KAAWY,EAAYK,CAAI,GAC7CC,IAAgBD,CAAI,GAChBA,EAAK,WAAWE,KAAQC,IAAaH,CAAI;CAC/C,GAGMI,KAAc,MAAsC;EACxD,AAAIC,EAAM,yBAAyB,QAAQA,EAAM,cAAc,SAASA,EAAM,aAAa,KAE3FC,IAASD,CAAK;CAChB,GAEME,IAAOC,MAAmB,YAAY,UAAU,aAChDC,IAAUP,KAAU,KAAKA,IAAS,KAAM,IAAIA,IAAS,IAAI;CAE/D,OACE,kBAACzB,EAAI,MAAL;EACO;EACL,GAAIc;EACJ,WAAWoB,EAAGjC,EAAO,MAAMkC,CAAS;EACpC,aAAWC;EACX,gBAAcrB,EAAM,WAAW,KAAA;EAC/B,iBAAeA,EAAM,YAAY,KAAA;EACjC,iBAAeA,EAAM,YAAY,KAAA;EACjC,OAAOM,EAAM,MAAM,GAAGI,CAAM;EAC5B,eAAeH;EACC;EAChB,MAAMe,IAAS,aAAa;EAC5B,cAAa;EAGF;EACX,UAAUtB,EAAM;EAChB,UAAUA,EAAM;EACV;EACN,mBAAiBF,KAAkBE,EAAM;EACzC,oBAAkBA,EAAM;EACxB,aAAWA,EAAM,QAAQ,KAAA;EACzB,QAAQY;EAtBV,UAAA,CAwBG,MAAM,KAAK,EAAE,UAAO,IAAI,GAAG,MAC1B,kBAAC3B,EAAI,OAAL;GAES;GACP,IAAIsC,MAAU,IAAIvB,EAAM,KAAK,KAAA;GAC7B,WAAWd,EAAO;GAClB,oBAAkBqC,MAAUN,KAAW,KAAA;GACvC,cAAY,GAAGF,EAAK,GAAG,OAAOQ,IAAQ,CAAC,EAAE,MAAM,OAAOb,CAAM;GAC5D,gBAAcV,EAAM,WAAW,KAAA;GAC/B,iBAAeA,EAAM,YAAY,KAAA;EAClC,GARMuB,CAQN,CACF,GAGAvB,EAAM,WAAW,OAAO,kBAACf,EAAI,aAAL,CAAkB,CAAA,CACnC;;AAEd,CAAC,CACH"}
@@ -5,6 +5,13 @@ export interface PasswordFieldProps extends FieldLabelProps, Omit<PasswordInputP
5
5
  onValueChange?: (value: string) => void;
6
6
  }
7
7
  /**
8
+ * A labelled password input with a show and hide toggle.
9
+ *
10
+ * @remarks
11
+ * `PasswordField` is a {@link Field | Field} around a {@link PasswordInput | PasswordInput}.
12
+ * `autoComplete` is required: `current-password` to sign in, `new-password` to set one.
13
+ *
14
+ * @privateRemarks
8
15
  * Label + PasswordInput + description + error. Input props (`autoComplete`, `value`,
9
16
  * `visible`…) and the ref go to the input; `className`/`style` go to the wrapper.
10
17
  *
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordField.js","names":["PasswordField","forwardRef","fieldProps","rest","splitFieldLabelProps","props","inputRest","handleChange","onChange","event","onValueChange","Field","id","PasswordInput"],"sources":["../../../../src/components/inputs/PasswordField/PasswordField.tsx"],"sourcesContent":["import { forwardRef, type ChangeEvent } from 'react'\nimport { Field, type FieldLabelProps } from '#components/inputs/Field'\nimport { PasswordInput, type PasswordInputProps } from '#components/inputs/PasswordInput'\nimport { splitFieldLabelProps } from '#components/inputs/internal/fieldProps'\n\nexport interface PasswordFieldProps\n extends FieldLabelProps, Omit<PasswordInputProps, 'invalid' | keyof FieldLabelProps> {\n /** Called with the new string on every change (alongside the native `onChange`). */\n onValueChange?: (value: string) => void\n}\n\n/**\n * Label + PasswordInput + description + error. Input props (`autoComplete`, `value`,\n * `visible`…) and the ref go to the input; `className`/`style` go to the wrapper.\n *\n * <PasswordField label=\"New password\" autoComplete=\"new-password\" description=\"At least 12 characters\" />\n */\nexport const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(\n function PasswordField(props, ref) {\n const [fieldProps, rest] = splitFieldLabelProps(props)\n const { id, disabled, className, style, onChange, onValueChange, ...inputRest } = rest\n const handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n onChange?.(event)\n onValueChange?.(event.target.value)\n }\n return (\n <Field {...fieldProps} htmlFor={id} disabled={disabled} className={className} style={style}>\n <PasswordInput\n ref={ref}\n id={id}\n disabled={disabled}\n onChange={handleChange}\n {...inputRest}\n />\n </Field>\n )\n },\n)\n"],"mappings":";;;;;;AAiBA,IAAaA,IAAgBC,EAC3B,SAAuB,GAAO,GAAK;CACjC,IAAM,CAACC,GAAYC,KAAQC,EAAqBC,CAAK,GAC/C,EAAE,OAAI,aAAU,cAAW,UAAO,aAAU,kBAAe,GAAGC,MAAcH,GAC5EI,KAAgB,MAAyC;EAE7D,AADAC,IAAWC,CAAK,GAChBC,IAAgBD,EAAM,OAAO,KAAK;CACpC;CACA,OACE,kBAACE,GAAD;EAAO,GAAIT;EAAY,SAASU;EAAc;EAAqB;EAAkB;EACnF,UAAA,kBAACC,GAAD;GACO;GACD;GACM;GACV,UAAUN;GACV,GAAID;EACL,CAAA;CACI,CAAA;AAEX,CACF"}
1
+ {"version":3,"file":"PasswordField.js","names":["PasswordField","forwardRef","fieldProps","rest","splitFieldLabelProps","props","inputRest","handleChange","onChange","event","onValueChange","Field","id","PasswordInput"],"sources":["../../../../src/components/inputs/PasswordField/PasswordField.tsx"],"sourcesContent":["import { forwardRef, type ChangeEvent } from 'react'\nimport { Field, type FieldLabelProps } from '#components/inputs/Field'\nimport { PasswordInput, type PasswordInputProps } from '#components/inputs/PasswordInput'\nimport { splitFieldLabelProps } from '#components/inputs/internal/fieldProps'\n\nexport interface PasswordFieldProps\n extends FieldLabelProps, Omit<PasswordInputProps, 'invalid' | keyof FieldLabelProps> {\n /** Called with the new string on every change (alongside the native `onChange`). */\n onValueChange?: (value: string) => void\n}\n\n/**\n * A labelled password input with a show and hide toggle.\n *\n * @remarks\n * `PasswordField` is a {@link Field | Field} around a {@link PasswordInput | PasswordInput}.\n * `autoComplete` is required: `current-password` to sign in, `new-password` to set one.\n *\n * @privateRemarks\n * Label + PasswordInput + description + error. Input props (`autoComplete`, `value`,\n * `visible`…) and the ref go to the input; `className`/`style` go to the wrapper.\n *\n * <PasswordField label=\"New password\" autoComplete=\"new-password\" description=\"At least 12 characters\" />\n */\nexport const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(\n function PasswordField(props, ref) {\n const [fieldProps, rest] = splitFieldLabelProps(props)\n const { id, disabled, className, style, onChange, onValueChange, ...inputRest } = rest\n const handleChange = (event: ChangeEvent<HTMLInputElement>) => {\n onChange?.(event)\n onValueChange?.(event.target.value)\n }\n return (\n <Field {...fieldProps} htmlFor={id} disabled={disabled} className={className} style={style}>\n <PasswordInput\n ref={ref}\n id={id}\n disabled={disabled}\n onChange={handleChange}\n {...inputRest}\n />\n </Field>\n )\n },\n)\n"],"mappings":";;;;;;AAwBA,IAAaA,IAAgBC,EAC3B,SAAuB,GAAO,GAAK;CACjC,IAAM,CAACC,GAAYC,KAAQC,EAAqBC,CAAK,GAC/C,EAAE,OAAI,aAAU,cAAW,UAAO,aAAU,kBAAe,GAAGC,MAAcH,GAC5EI,KAAgB,MAAyC;EAE7D,AADAC,IAAWC,CAAK,GAChBC,IAAgBD,EAAM,OAAO,KAAK;CACpC;CACA,OACE,kBAACE,GAAD;EAAO,GAAIT;EAAY,SAASU;EAAc;EAAqB;EAAkB;EACnF,UAAA,kBAACC,GAAD;GACO;GACD;GACM;GACV,UAAUN;GACV,GAAID;EACL,CAAA;CACI,CAAA;AAEX,CACF"}