@mitcsutt/kiln-ui 0.2.1 → 0.3.0

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 (267) hide show
  1. package/README.md +6 -6
  2. package/dist/assets/fonts/archivo-latin-ext-wdth-italic.woff2 +0 -0
  3. package/dist/assets/fonts/archivo-latin-ext-wdth-normal.woff2 +0 -0
  4. package/dist/assets/fonts/archivo-latin-wdth-italic.woff2 +0 -0
  5. package/dist/assets/fonts/archivo-latin-wdth-normal.woff2 +0 -0
  6. package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-ext-wght-italic.woff2 +0 -0
  7. package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-ext-wght-normal.woff2 +0 -0
  8. package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-wght-italic.woff2 +0 -0
  9. package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-wght-normal.woff2 +0 -0
  10. package/dist/assets/fonts/b612-latin-400-italic.woff2 +0 -0
  11. package/dist/assets/fonts/b612-latin-400-normal.woff2 +0 -0
  12. package/dist/assets/fonts/b612-latin-700-italic.woff2 +0 -0
  13. package/dist/assets/fonts/b612-latin-700-normal.woff2 +0 -0
  14. package/dist/assets/fonts/b612-mono-latin-400-italic.woff2 +0 -0
  15. package/dist/assets/fonts/b612-mono-latin-400-normal.woff2 +0 -0
  16. package/dist/assets/fonts/b612-mono-latin-700-italic.woff2 +0 -0
  17. package/dist/assets/fonts/b612-mono-latin-700-normal.woff2 +0 -0
  18. package/dist/assets/fonts/golos-text-latin-ext-wght-normal.woff2 +0 -0
  19. package/dist/assets/fonts/golos-text-latin-wght-normal.woff2 +0 -0
  20. package/dist/assets/fonts/licenses/archivo.txt +93 -0
  21. package/dist/assets/fonts/licenses/atkinson-hyperlegible-mono.txt +93 -0
  22. package/dist/assets/fonts/licenses/b612-mono.txt +93 -0
  23. package/dist/assets/fonts/licenses/b612.txt +93 -0
  24. package/dist/assets/fonts/licenses/golos-text.txt +93 -0
  25. package/dist/assets/fonts/licenses/shantell-sans.txt +93 -0
  26. package/dist/assets/fonts/shantell-sans-latin-ext-full-italic.woff2 +0 -0
  27. package/dist/assets/fonts/shantell-sans-latin-ext-full-normal.woff2 +0 -0
  28. package/dist/assets/fonts/shantell-sans-latin-full-italic.woff2 +0 -0
  29. package/dist/assets/fonts/shantell-sans-latin-full-normal.woff2 +0 -0
  30. package/dist/components/actions/Button/Button.d.ts +8 -0
  31. package/dist/components/actions/Button/Button.js.map +1 -1
  32. package/dist/components/actions/ChipGroup/ChipGroup.d.ts +9 -0
  33. package/dist/components/actions/ChipGroup/ChipGroup.js.map +1 -1
  34. package/dist/components/actions/IconButton/IconButton.d.ts +8 -0
  35. package/dist/components/actions/IconButton/IconButton.js.map +1 -1
  36. package/dist/components/actions/ModeToggle/ModeToggle.d.ts +8 -0
  37. package/dist/components/actions/ModeToggle/ModeToggle.js.map +1 -1
  38. package/dist/components/actions/SegmentedControl/SegmentedControl.d.ts +9 -0
  39. package/dist/components/actions/SegmentedControl/SegmentedControl.js.map +1 -1
  40. package/dist/components/actions/ToggleChip/ToggleChip.d.ts +9 -0
  41. package/dist/components/actions/ToggleChip/ToggleChip.js.map +1 -1
  42. package/dist/components/display/Avatar/Avatar.d.ts +11 -1
  43. package/dist/components/display/Avatar/Avatar.js.map +1 -1
  44. package/dist/components/display/AvatarGroup/AvatarGroup.d.ts +7 -0
  45. package/dist/components/display/AvatarGroup/AvatarGroup.js.map +1 -1
  46. package/dist/components/display/Badge/Badge.d.ts +7 -0
  47. package/dist/components/display/Badge/Badge.js.map +1 -1
  48. package/dist/components/display/Card/Card.js.map +1 -1
  49. package/dist/components/display/CodeBlock/CodeBlock.d.ts +9 -0
  50. package/dist/components/display/CodeBlock/CodeBlock.js.map +1 -1
  51. package/dist/components/display/DataList/DataList.js.map +1 -1
  52. package/dist/components/display/Delta/Delta.d.ts +9 -0
  53. package/dist/components/display/Delta/Delta.js.map +1 -1
  54. package/dist/components/display/EmptyState/EmptyState.d.ts +8 -0
  55. package/dist/components/display/EmptyState/EmptyState.js.map +1 -1
  56. package/dist/components/display/List/List.js.map +1 -1
  57. package/dist/components/display/Marquee/Marquee.d.ts +8 -0
  58. package/dist/components/display/Marquee/Marquee.js.map +1 -1
  59. package/dist/components/display/Media/Media.d.ts +7 -0
  60. package/dist/components/display/Media/Media.js.map +1 -1
  61. package/dist/components/display/Meter/Meter.d.ts +9 -0
  62. package/dist/components/display/Meter/Meter.js.map +1 -1
  63. package/dist/components/display/Progress/Progress.d.ts +8 -0
  64. package/dist/components/display/Progress/Progress.js.map +1 -1
  65. package/dist/components/display/Skeleton/Skeleton.js.map +1 -1
  66. package/dist/components/display/Stamp/Stamp.d.ts +8 -0
  67. package/dist/components/display/Stamp/Stamp.js.map +1 -1
  68. package/dist/components/display/Stat/Stat.d.ts +8 -0
  69. package/dist/components/display/Stat/Stat.js.map +1 -1
  70. package/dist/components/display/Table/Table.js.map +1 -1
  71. package/dist/components/display/Tag/Tag.d.ts +7 -0
  72. package/dist/components/display/Tag/Tag.js.map +1 -1
  73. package/dist/components/feedback/Alert/Alert.d.ts +10 -0
  74. package/dist/components/feedback/Alert/Alert.js.map +1 -1
  75. package/dist/components/feedback/LiveIndicator/LiveIndicator.d.ts +7 -0
  76. package/dist/components/feedback/LiveIndicator/LiveIndicator.js.map +1 -1
  77. package/dist/components/feedback/RelativeTime/RelativeTime.d.ts +8 -0
  78. package/dist/components/feedback/RelativeTime/RelativeTime.js.map +1 -1
  79. package/dist/components/feedback/Spinner/Spinner.d.ts +10 -1
  80. package/dist/components/feedback/Spinner/Spinner.js.map +1 -1
  81. package/dist/components/feedback/StatusDot/StatusDot.d.ts +10 -1
  82. package/dist/components/feedback/StatusDot/StatusDot.js.map +1 -1
  83. package/dist/components/inputs/AmountField/AmountField.d.ts +7 -0
  84. package/dist/components/inputs/AmountField/AmountField.js.map +1 -1
  85. package/dist/components/inputs/AmountInput/AmountInput.d.ts +8 -0
  86. package/dist/components/inputs/AmountInput/AmountInput.js.map +1 -1
  87. package/dist/components/inputs/Checkbox/Checkbox.d.ts +8 -0
  88. package/dist/components/inputs/Checkbox/Checkbox.js.map +1 -1
  89. package/dist/components/inputs/CheckboxField/CheckboxField.d.ts +7 -0
  90. package/dist/components/inputs/CheckboxField/CheckboxField.js.map +1 -1
  91. package/dist/components/inputs/CheckboxGroup/CheckboxGroup.d.ts +8 -0
  92. package/dist/components/inputs/CheckboxGroup/CheckboxGroup.js.map +1 -1
  93. package/dist/components/inputs/CheckboxGroupField/CheckboxGroupField.d.ts +7 -0
  94. package/dist/components/inputs/CheckboxGroupField/CheckboxGroupField.js.map +1 -1
  95. package/dist/components/inputs/ChipGroupField/ChipGroupField.d.ts +7 -0
  96. package/dist/components/inputs/ChipGroupField/ChipGroupField.js.map +1 -1
  97. package/dist/components/inputs/ChoiceCards/ChoiceCards.d.ts +9 -0
  98. package/dist/components/inputs/ChoiceCards/ChoiceCards.js.map +1 -1
  99. package/dist/components/inputs/ChoiceCardsField/ChoiceCardsField.d.ts +7 -0
  100. package/dist/components/inputs/ChoiceCardsField/ChoiceCardsField.js.map +1 -1
  101. package/dist/components/inputs/ColorField/ColorField.d.ts +6 -0
  102. package/dist/components/inputs/ColorField/ColorField.js.map +1 -1
  103. package/dist/components/inputs/ColorInput/ColorInput.d.ts +8 -0
  104. package/dist/components/inputs/ColorInput/ColorInput.js.map +1 -1
  105. package/dist/components/inputs/Combobox/Combobox.d.ts +9 -0
  106. package/dist/components/inputs/Combobox/Combobox.js.map +1 -1
  107. package/dist/components/inputs/ComboboxField/ComboboxField.d.ts +7 -0
  108. package/dist/components/inputs/ComboboxField/ComboboxField.js.map +1 -1
  109. package/dist/components/inputs/DateRangeField/DateRangeField.d.ts +7 -0
  110. package/dist/components/inputs/DateRangeField/DateRangeField.js.map +1 -1
  111. package/dist/components/inputs/Field/Field.d.ts +26 -0
  112. package/dist/components/inputs/Field/Field.js.map +1 -1
  113. package/dist/components/inputs/Fieldset/Fieldset.d.ts +8 -0
  114. package/dist/components/inputs/Fieldset/Fieldset.js.map +1 -1
  115. package/dist/components/inputs/FileDrop/FileDrop.d.ts +10 -0
  116. package/dist/components/inputs/FileDrop/FileDrop.js.map +1 -1
  117. package/dist/components/inputs/FileField/FileField.d.ts +7 -0
  118. package/dist/components/inputs/FileField/FileField.js.map +1 -1
  119. package/dist/components/inputs/Input/Input.d.ts +9 -0
  120. package/dist/components/inputs/Input/Input.js.map +1 -1
  121. package/dist/components/inputs/NumberField/NumberField.d.ts +7 -0
  122. package/dist/components/inputs/NumberField/NumberField.js.map +1 -1
  123. package/dist/components/inputs/NumberInput/NumberInput.d.ts +8 -0
  124. package/dist/components/inputs/NumberInput/NumberInput.js.map +1 -1
  125. package/dist/components/inputs/OneTimeCodeField/OneTimeCodeField.d.ts +7 -0
  126. package/dist/components/inputs/OneTimeCodeField/OneTimeCodeField.js.map +1 -1
  127. package/dist/components/inputs/OneTimeCodeInput/OneTimeCodeInput.d.ts +8 -0
  128. package/dist/components/inputs/OneTimeCodeInput/OneTimeCodeInput.js.map +1 -1
  129. package/dist/components/inputs/PasswordField/PasswordField.d.ts +7 -0
  130. package/dist/components/inputs/PasswordField/PasswordField.js.map +1 -1
  131. package/dist/components/inputs/PasswordInput/PasswordInput.d.ts +10 -0
  132. package/dist/components/inputs/PasswordInput/PasswordInput.js.map +1 -1
  133. package/dist/components/inputs/RadioGroup/RadioGroup.d.ts +9 -0
  134. package/dist/components/inputs/RadioGroup/RadioGroup.js.map +1 -1
  135. package/dist/components/inputs/RadioGroupField/RadioGroupField.d.ts +8 -0
  136. package/dist/components/inputs/RadioGroupField/RadioGroupField.js.map +1 -1
  137. package/dist/components/inputs/RangeSlider/RangeSlider.d.ts +8 -0
  138. package/dist/components/inputs/RangeSlider/RangeSlider.js.map +1 -1
  139. package/dist/components/inputs/RangeSliderField/RangeSliderField.d.ts +7 -0
  140. package/dist/components/inputs/RangeSliderField/RangeSliderField.js.map +1 -1
  141. package/dist/components/inputs/Rating/Rating.d.ts +8 -0
  142. package/dist/components/inputs/Rating/Rating.js.map +1 -1
  143. package/dist/components/inputs/RatingField/RatingField.d.ts +7 -0
  144. package/dist/components/inputs/RatingField/RatingField.js.map +1 -1
  145. package/dist/components/inputs/SegmentedField/SegmentedField.d.ts +7 -0
  146. package/dist/components/inputs/SegmentedField/SegmentedField.js.map +1 -1
  147. package/dist/components/inputs/Select/Select.d.ts +9 -0
  148. package/dist/components/inputs/Select/Select.js.map +1 -1
  149. package/dist/components/inputs/SelectField/SelectField.d.ts +7 -0
  150. package/dist/components/inputs/SelectField/SelectField.js.map +1 -1
  151. package/dist/components/inputs/Slider/Slider.d.ts +8 -0
  152. package/dist/components/inputs/Slider/Slider.js.map +1 -1
  153. package/dist/components/inputs/SliderField/SliderField.d.ts +7 -0
  154. package/dist/components/inputs/SliderField/SliderField.js.map +1 -1
  155. package/dist/components/inputs/Switch/Switch.d.ts +8 -0
  156. package/dist/components/inputs/Switch/Switch.js.map +1 -1
  157. package/dist/components/inputs/SwitchField/SwitchField.d.ts +8 -0
  158. package/dist/components/inputs/SwitchField/SwitchField.js.map +1 -1
  159. package/dist/components/inputs/TagsField/TagsField.d.ts +6 -0
  160. package/dist/components/inputs/TagsField/TagsField.js.map +1 -1
  161. package/dist/components/inputs/TagsInput/TagsInput.d.ts +8 -0
  162. package/dist/components/inputs/TagsInput/TagsInput.js.map +1 -1
  163. package/dist/components/inputs/TextField/TextField.d.ts +8 -0
  164. package/dist/components/inputs/TextField/TextField.js.map +1 -1
  165. package/dist/components/inputs/Textarea/Textarea.d.ts +8 -0
  166. package/dist/components/inputs/Textarea/Textarea.js.map +1 -1
  167. package/dist/components/inputs/TextareaField/TextareaField.d.ts +7 -0
  168. package/dist/components/inputs/TextareaField/TextareaField.js.map +1 -1
  169. package/dist/components/layout/ActionBar/ActionBar.d.ts +7 -0
  170. package/dist/components/layout/ActionBar/ActionBar.js.map +1 -1
  171. package/dist/components/layout/AppShell/AppShell.js.map +1 -1
  172. package/dist/components/layout/AspectRatio/AspectRatio.d.ts +8 -0
  173. package/dist/components/layout/AspectRatio/AspectRatio.js.map +1 -1
  174. package/dist/components/layout/Box/Box.d.ts +8 -0
  175. package/dist/components/layout/Box/Box.js.map +1 -1
  176. package/dist/components/layout/Container/Container.d.ts +17 -0
  177. package/dist/components/layout/Container/Container.js.map +1 -1
  178. package/dist/components/layout/Divider/Divider.d.ts +7 -0
  179. package/dist/components/layout/Divider/Divider.js.map +1 -1
  180. package/dist/components/layout/Grid/Grid.d.ts +9 -0
  181. package/dist/components/layout/Grid/Grid.js.map +1 -1
  182. package/dist/components/layout/Inline/Inline.d.ts +8 -0
  183. package/dist/components/layout/Inline/Inline.js.map +1 -1
  184. package/dist/components/layout/Section/Section.d.ts +15 -0
  185. package/dist/components/layout/Section/Section.js.map +1 -1
  186. package/dist/components/layout/Split/Split.d.ts +9 -0
  187. package/dist/components/layout/Split/Split.js.map +1 -1
  188. package/dist/components/layout/Stack/Stack.d.ts +12 -0
  189. package/dist/components/layout/Stack/Stack.js.map +1 -1
  190. package/dist/components/layout/VisuallyHidden/VisuallyHidden.d.ts +7 -0
  191. package/dist/components/layout/VisuallyHidden/VisuallyHidden.js.map +1 -1
  192. package/dist/components/navigation/Accordion/Accordion.d.ts +7 -0
  193. package/dist/components/navigation/Accordion/Accordion.js.map +1 -1
  194. package/dist/components/navigation/BottomNav/BottomNav.d.ts +9 -0
  195. package/dist/components/navigation/BottomNav/BottomNav.js.map +1 -1
  196. package/dist/components/navigation/NavLinks/NavLinks.d.ts +9 -0
  197. package/dist/components/navigation/NavLinks/NavLinks.js.map +1 -1
  198. package/dist/components/navigation/Stepper/Stepper.d.ts +7 -0
  199. package/dist/components/navigation/Stepper/Stepper.js.map +1 -1
  200. package/dist/components/navigation/Tabs/Tabs.d.ts +8 -0
  201. package/dist/components/navigation/Tabs/Tabs.js.map +1 -1
  202. package/dist/components/overlays/Dialog/Dialog.d.ts +13 -0
  203. package/dist/components/overlays/Dialog/Dialog.js.map +1 -1
  204. package/dist/components/overlays/DropdownMenu/DropdownMenu.d.ts +8 -0
  205. package/dist/components/overlays/DropdownMenu/DropdownMenu.js.map +1 -1
  206. package/dist/components/overlays/Popover/Popover.d.ts +7 -0
  207. package/dist/components/overlays/Popover/Popover.js.map +1 -1
  208. package/dist/components/overlays/Sheet/Sheet.d.ts +8 -0
  209. package/dist/components/overlays/Sheet/Sheet.js.map +1 -1
  210. package/dist/components/overlays/Tooltip/Tooltip.d.ts +8 -0
  211. package/dist/components/overlays/Tooltip/Tooltip.js.map +1 -1
  212. package/dist/components/typography/Amount/Amount.d.ts +8 -0
  213. package/dist/components/typography/Amount/Amount.js.map +1 -1
  214. package/dist/components/typography/Code/Code.d.ts +8 -0
  215. package/dist/components/typography/Code/Code.js.map +1 -1
  216. package/dist/components/typography/Heading/Heading.d.ts +8 -0
  217. package/dist/components/typography/Heading/Heading.js.map +1 -1
  218. package/dist/components/typography/Kbd/Kbd.d.ts +6 -0
  219. package/dist/components/typography/Kbd/Kbd.js.map +1 -1
  220. package/dist/components/typography/Link/Link.d.ts +9 -0
  221. package/dist/components/typography/Link/Link.js.map +1 -1
  222. package/dist/components/typography/Numeral/Numeral.d.ts +8 -0
  223. package/dist/components/typography/Numeral/Numeral.js.map +1 -1
  224. package/dist/components/typography/Prose/Prose.d.ts +9 -0
  225. package/dist/components/typography/Prose/Prose.js.map +1 -1
  226. package/dist/components/typography/Quote/Quote.d.ts +7 -0
  227. package/dist/components/typography/Quote/Quote.js.map +1 -1
  228. package/dist/components/typography/SectionHeader/SectionHeader.d.ts +8 -0
  229. package/dist/components/typography/SectionHeader/SectionHeader.js.map +1 -1
  230. package/dist/components/typography/Text/Text.d.ts +8 -0
  231. package/dist/components/typography/Text/Text.js.map +1 -1
  232. package/dist/icons/Icon.d.ts +1 -1
  233. package/dist/icons/Icon.js.map +1 -1
  234. package/dist/styles.css +1 -1
  235. package/dist/theme/ThemeProvider.d.ts +4 -4
  236. package/dist/theme/ThemeProvider.js.map +1 -1
  237. package/dist/theme/themes.d.ts +1 -1
  238. package/dist/theme/themes.js +15 -3
  239. package/dist/theme/themes.js.map +1 -1
  240. package/dist/themes/fiesta.css +1 -1
  241. package/dist/themes/flightdeck.css +1 -0
  242. package/dist/themes/ledger.css +1 -1
  243. package/dist/themes/monograph.css +1 -1
  244. package/dist/themes/riso.css +1 -0
  245. package/package.json +4 -2
  246. package/skills/custom-theme/SKILL.md +45 -43
  247. package/skills/custom-theme/references/colour.md +1 -1
  248. package/skills/custom-theme/references/motion.md +2 -2
  249. package/skills/custom-theme/references/tokens.md +42 -42
  250. package/skills/custom-theme/references/type.md +13 -10
  251. package/skills/design-rules/SKILL.md +31 -3
  252. package/skills/layout-composition/SKILL.md +27 -88
  253. package/skills/layout-composition/references/action-bar.md +3 -3
  254. package/skills/layout-composition/references/app-shell.md +10 -3
  255. package/skills/layout-composition/references/aspect-ratio.md +2 -0
  256. package/skills/layout-composition/references/box.md +6 -6
  257. package/skills/layout-composition/references/container.md +14 -12
  258. package/skills/layout-composition/references/divider.md +4 -4
  259. package/skills/layout-composition/references/section.md +69 -0
  260. package/skills/layout-composition/references/visually-hidden.md +3 -3
  261. package/skills/setup-and-theming/SKILL.md +10 -6
  262. package/skills/setup-and-theming/references/fiesta.md +3 -1
  263. package/skills/setup-and-theming/references/flightdeck.md +39 -0
  264. package/skills/setup-and-theming/references/ledger.md +7 -7
  265. package/skills/setup-and-theming/references/monograph.md +4 -0
  266. package/skills/setup-and-theming/references/paper.md +7 -7
  267. package/skills/setup-and-theming/references/riso.md +32 -0
@@ -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":";;;;;;;;;AA+DA,IAAaI,IAAcC,EACzBC,EAA+C,SAC7C,EACE,OAAOC,GACP,iBACA,kBACA,QACA,QACA,UAAO,GACP,cACA,aAAU,IACV,oBAAiB,YACjB,oBAAiB,YACjB,kBACA,WACA,kBAAc,IACd,SACA,SACA,aACA,WAAO,MACP,IAAIC,IACJ,UAAUC,GACV,UAAUC,GACV,aACA,oBAAoBC,IACpB,gBAAgBC,IAChB,aACA,YACA,eACA,GAAGC,MAEL,IACA;CACA,IAAMC,IAAQC,EAAiB;EAAE,IAAIP;EAAQ,UAAUC;EAAc,UAAUC;CAAa,CAAC,GACvFM,IAAeT,MAAc,KAAA,GAC7B,CAACU,GAAUC,KAAeC,EAAwBC,KAAgB,IAAI,GACtEC,IAAQL,IAAeT,IAAYU,GACnC,CAACK,GAASC,KAAcJ,EAAS,EAAK,GACtC,CAACK,GAAOC,KAAYN,EAAS,EAAE,GAE/BO,IAAQC,GAAe,UAAU,YAAY,MAAM,GACnDC,IACJ,KAAK,IAAIC,EAAWC,CAAI,GAAGH,GAAe,yBAAyB,CAAC,KACnED,MAAU,MAAM,IAAI,IACjBK,IAAUC,KAAaF,IAAO,IAC9BG,KAAYC,SACV,IAAI,KAAK,aAAaC,GAAQR,CAAa,GACjD,CAACQ,GAAQR,CAAa,CACxB,GACMS,KAAW,MAAsBC,KAAK,OAAO,KAAKJ,GAAU,OAAOI,CAAC,GACpEC,KAAO,MACXD,KAAK,OAAO,KAAKE,GAAUC,EAAQH,IAAIX,GAAOE,CAAS,GAAGO,CAAM,GAC5DM,KAAS,MAAgC;EAC7C,IAAMC,IAAIC,GAAYC,GAAMT,CAAM;EAClC,OAAOO,KAAK,QAAQ,OAAO,MAAMA,CAAC,KAAQhB,MAAU,IAAdgB,IAAsBF,EAAQE,IAAIhB,GAAOE,CAAS;CAC1F;CAGA,SAAgB;EACd,IAAI,CAACN,GAAS;EACd,IAAMuB,IAASJ,EAAMjB,CAAK;EAC1B,AAAIqB,MAAWxB,MAAWwB,KAAU,QAAQxB,KAAS,SAAOI,EAASa,EAAIjB,CAAK,CAAC;CAEjF,GAAG,CAACA,CAAK,CAAC;CAEV,IAAMyB,KAAY,MAAwB;EAExC,AADK9B,KAAcE,EAAY6B,CAAI,GAC/BA,MAAS1B,KAAO2B,IAAgBD,CAAI;CAC1C,GAEME,IAAW,CAACnC,EAAM,YAAY,CAACA,EAAM,UAErCoC,KAAU,MAAkB;EAChC,IAAI,CAACD,GAAU;EACf,IAAME,IAAQ7B,IAAUmB,EAAMjB,CAAK,IAAIH,GACjC+B,IAAOD,KAAS,QAAQ,OAAO,MAAMA,CAAK,IAAI9B,IAAQ8B,GACtDJ,IACWM,EAAfD,KAAQ,OAAa,IAAqBZ,EAAQY,IAAOE,GAAO1B,CAAS,GAAjD2B,GAAKC,CAAuD;EAEtF,AADAV,EAASC,CAAI,GACTzB,KAASG,EAASa,EAAIS,CAAI,CAAC;CACjC,GAEMU,KAAU,MAA+B;EAC7C,AAAKR,KAAYS,KAAU,SAC3BZ,EAASY,CAAM,GACXpC,KAASG,EAASa,EAAIoB,CAAM,CAAC;CACnC,GAEMC,UAAe;EACnB,IAAMd,IAASJ,EAAMjB,CAAK;EAC1B,IAAIqB,KAAU,QAAQ,OAAO,MAAMA,CAAM,GAAG;GAC1C,EAASP,EAAIjB,CAAK,CAAC;GACnB;EACF;EACA,IAAM0B,IACJF,KAAU,OACN,OACAe,KACEP,EAAMb,EAAQK,GAAQjB,CAAS,GAAG2B,GAAKC,CAAG,IAC1ChB,EAAQK,GAAQjB,CAAS;EAEjC,AADAkB,EAASC,CAAI,GACbtB,EAASa,EAAIS,CAAI,CAAC;CACpB,GAEMc,MAAgB,MAAyC;EAC7D,IAAMjB,IAAOkB,EAAM,OAAO;EAC1B,EAASlB,CAAI;EACb,IAAMC,IAASJ,EAAMG,CAAI;EACzB,CAAIC,KAAU,QAAQ,CAAC,OAAO,MAAMA,CAAM,MAAGC,EAASD,CAAM;CAC9D,GAEMkB,MAAe,MAAwC;EAG3D,AAFAxC,EAAW,EAAI,GACfE,EAASa,EAAIjB,CAAK,CAAC,GACnB2C,KAAUF,CAAK;CACjB,GAEMG,MAAc,MAAwC;EAG1D,AAFAN,EAAO,GACPpC,EAAW,EAAK,GAChB2C,KAASJ,CAAK;CAChB,GAEMK,KAAiB,MAA2C;EAEhE,IADAC,KAAYN,CAAK,GACbA,EAAM,kBAAkB;EAsB5B,IAAMO,IAAS;GApBb,eAAe;IACb,EAAOvC,CAAI;GACb;GACA,iBAAiB;IACf,EAAO,CAACA,CAAI;GACd;GACA,cAAc;IACZ,EAAOC,CAAO;GAChB;GACA,gBAAgB;IACd,EAAO,CAACA,CAAO;GACjB;GACA,YAAY;IACV,EAAOwB,CAAG;GACZ;GACA,WAAW;IACT,EAAOC,CAAG;GACZ;GACA,OAAOG;EAEM,EAAQG,EAAM;EAC7B,AAAKO,MACAP,EAAM,QAAQ,UAAUP,KAAO,QAAUO,EAAM,QAAQ,SAASN,KAAO,SACxEM,EAAM,QAAQ,WAASA,EAAM,eAAe,GAChDO,EAAO;CACT,GAGMC,KAAa,MAAsB;EACvC,EAAM,eAAe;CACvB,GAEMC,KAASC,GAAM,GACfC,IAAY3D,EAAM,MAAM,GAAGyD,GAAO,SAElCG,IAAUC,IACd,kBAAA3E,GAAA,EAAA,UAAA,CACE,kBAAC4E,GAAD;EACE,WAAW3E,EAAO;EAClB,MAAK;EACL,OAAO4E;EACP,MAAM,kBAACC,GAAD,CAAY,CAAA;EAClB,UAAU;EACV,iBAAeL;EACf,UAAU,CAACxB,KAAa5B,KAAS,QAAQkC,KAAO,QAAQlC,KAASkC;EACjE,aAAae;EACb,eAAe;GACb,EAAO,CAACxC,CAAI;EACd;CACD,CAAA,GACD,kBAAC8C,GAAD;EACE,WAAW3E,EAAO;EAClB,MAAK;EACL,OAAO8E;EACP,MAAM,kBAACC,GAAD,CAAW,CAAA;EACjB,UAAU;EACV,iBAAeP;EACf,UAAU,CAACxB,KAAa5B,KAAS,QAAQmC,KAAO,QAAQnC,KAASmC;EACjE,aAAac;EACb,eAAe;GACb,EAAOxC,CAAI;EACb;CACD,CAAA,CACD,EAAA,CAAA,IACA,MAEEmD,KAAiB1B,KAAO,QAAQA,IAAM,GACtC2B,KAAY7D,KAAS,OAAO,KAAA,IAAYe,EAAQf,CAAK;CAE3D,OACE,kBAAArB,GAAA,EAAA,UAAA,CACE,kBAACmF,GAAD;EACO;EACL,GAAItE;EACJ,IAAI4D;EACE;EACN,SAAA;EACA,MAAK;EACL,MAAK;EACL,WACEQ,KAAiB,SAASpD,EAAWC,CAAI,MAAM,KAAKJ,MAAU,IAAI,YAAY;EAEhF,cAAa;EACb,aAAY;EACZ,YAAY;EACZ,UAAUjB;EACV,UAAUC;EACD;EACT,oBAAkBC;EAClB,gBAAcC;EACd,iBAAeS,KAAS,KAAA;EACxB,iBAAekC;EACf,iBAAeC;EACf,kBAAgB0B;EAChB,OAAO5D,IAAUE,IAAQY,EAAQf,CAAK;EACtC,UAAUwC;EACV,SAASE;EACT,QAAQE;EACR,WAAWE;EACL;EACN,UACEiB,KAAY,QAAQV,IAClB,kBAAA1E,GAAA,EAAA,UAAA,CACGoF,GACAV,CACD,EAAA,CAAA,IACA,KAAA;CAEP,CAAA,GAEAW,KAAQ,OAQL,OAPF,kBAAC,SAAD;EACE,MAAK;EACC;EACA;EACN,OAAOhE,KAAS,OAAO,KAAK,OAAOA,CAAK;EACxC,UAAUP,EAAM;CACjB,CAAA,CAEH,EAAA,CAAA;AAEN,CAAC,CACH"}
@@ -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"}
@@ -12,6 +12,16 @@ export interface PasswordInputProps extends Omit<InputProps, 'type' | 'trailing'
12
12
  hideLabel?: string;
13
13
  }
14
14
  /**
15
+ * A password input with a show and hide toggle that says what pressing it does.
16
+ *
17
+ * @remarks
18
+ * `PasswordInput` adds a toggle to the end of the box that shows or hides the password. The toggle
19
+ * is an `IconButton` whose name says what pressing it will do ("Show password"), and submitting or
20
+ * resetting the form hides the password again. `autoComplete` is required (`current-password` or
21
+ * `new-password`), so password managers do the right thing. `onBlur` fires only when focus leaves
22
+ * both the input and its toggle.
23
+ *
24
+ * @privateRemarks
15
25
  * A password input with a show/hide toggle at the end of the box (Radix
16
26
  * PasswordToggleField). The toggle is an IconButton whose name says what pressing it
17
27
  * does; submitting or resetting the form hides the password again. The ref goes to the
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordInput.js","names":["PasswordToggleField","styles","PasswordInput_module_default","PasswordInput","markFieldAware","forwardRef","visibleProp","idProp","disabledProp","rest","field","useResolvedField","autoId","useId","inputId","inputRef","useRef","composedRef","useMergedRefs","ref","internalVisible","setInternalVisible","useState","defaultVisible","visible","setVisible","next","onVisibleChange","setVisibleRef","useEffect","formElement","hide","handleBlur","box","event","onBlur","handleToggleBlur","input","keepFocus","unstable_PasswordToggleField","Input","hideLabel","showLabel","IconButton","jsx","EyeOffIcon","EyeIcon"],"sources":["../../../../src/components/inputs/PasswordInput/PasswordInput.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useId,\n useRef,\n useState,\n type FocusEvent,\n type MouseEvent,\n} from 'react'\nimport { unstable_PasswordToggleField as PasswordToggleField } from 'radix-ui'\nimport { EyeIcon, EyeOffIcon } 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 { useMergedRefs } from '#components/inputs/internal/refs'\nimport styles from './PasswordInput.module.css'\n\nexport interface PasswordInputProps extends Omit<InputProps, 'type' | 'trailing'> {\n /** Required: tells password managers whether to fill a saved password or offer a new one. */\n autoComplete: 'current-password' | 'new-password'\n /** Whether the password shows as text. Controlled; pair with `onVisibleChange`. */\n visible?: boolean\n defaultVisible?: boolean\n onVisibleChange?: (visible: boolean) => void\n /** Accessible name of the toggle while hidden. Default `'Show password'`. */\n showLabel?: string\n /** Accessible name of the toggle while shown. Default `'Hide password'`. */\n hideLabel?: string\n}\n\n/**\n * A password input with a show/hide toggle at the end of the box (Radix\n * PasswordToggleField). The toggle is an IconButton whose name says what pressing it\n * does; submitting or resetting the form hides the password again. The ref goes to the\n * input; `onBlur` fires only when focus leaves the input *and* its toggle.\n *\n * <PasswordInput autoComplete=\"current-password\" />\n */\nexport const PasswordInput = markFieldAware(\n forwardRef<HTMLInputElement, PasswordInputProps>(function PasswordInput(\n {\n autoComplete,\n visible: visibleProp,\n defaultVisible = false,\n onVisibleChange,\n showLabel = 'Show password',\n hideLabel = 'Hide password',\n size = 'md',\n id: idProp,\n disabled: disabledProp,\n onBlur,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({ id: idProp, disabled: disabledProp })\n const autoId = useId()\n const inputId = field.id ?? `${autoId}password`\n const inputRef = useRef<HTMLInputElement>(null)\n const composedRef = useMergedRefs(ref, inputRef)\n const [internalVisible, setInternalVisible] = useState(defaultVisible)\n const visible = visibleProp ?? internalVisible\n const setVisible = (next: boolean) => {\n if (visibleProp === undefined) setInternalVisible(next)\n if (next !== visible) onVisibleChange?.(next)\n }\n const setVisibleRef = useRef(setVisible)\n setVisibleRef.current = setVisible\n\n // Submitting or resetting the form hides the password (a shown password shouldn't\n // survive into the next screen or a reset form).\n useEffect(() => {\n const formElement = inputRef.current?.form\n if (!formElement) return\n const hide = () => {\n setVisibleRef.current(false)\n }\n formElement.addEventListener('submit', hide)\n formElement.addEventListener('reset', hide)\n return () => {\n formElement.removeEventListener('submit', hide)\n formElement.removeEventListener('reset', hide)\n }\n }, [])\n\n // Blur counts only when focus leaves the whole box (input + toggle).\n const handleBlur = (event: FocusEvent<HTMLInputElement>) => {\n const box = event.currentTarget.parentElement\n if (box && event.relatedTarget instanceof Node && box.contains(event.relatedTarget)) return\n onBlur?.(event)\n }\n const handleToggleBlur = (event: FocusEvent<HTMLButtonElement>) => {\n const input = inputRef.current\n if (!input || !onBlur) return\n const box = input.parentElement\n if (box && event.relatedTarget instanceof Node && box.contains(event.relatedTarget)) return\n // Same event, typed for the input's handler: the target is the toggle that lost focus.\n onBlur(event as unknown as FocusEvent<HTMLInputElement>)\n }\n\n // A pointer press keeps focus (and the caret) in the input.\n const keepFocus = (event: MouseEvent) => {\n if (document.activeElement === inputRef.current) event.preventDefault()\n }\n\n return (\n <PasswordToggleField.Root visible={visible} onVisibilityChange={setVisible}>\n <Input\n ref={composedRef}\n {...rest}\n id={inputId}\n size={size}\n disabled={disabledProp}\n type={visible ? 'text' : 'password'}\n autoComplete={autoComplete}\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n onBlur={handleBlur}\n trailing={\n <PasswordToggleField.Toggle\n asChild\n id={`${inputId}-toggle`}\n aria-controls={inputId}\n aria-label={visible ? hideLabel : showLabel}\n >\n <IconButton\n className={styles.toggle}\n size=\"sm\"\n label={visible ? hideLabel : showLabel}\n icon={visible ? <EyeOffIcon /> : <EyeIcon />}\n disabled={field.disabled}\n onMouseDown={keepFocus}\n onBlur={handleToggleBlur}\n />\n </PasswordToggleField.Toggle>\n }\n />\n </PasswordToggleField.Root>\n )\n }),\n)\n"],"mappings":";;;;;;;;;;AAsCA,IAAaG,IAAgBC,EAC3BC,EAAiD,SAC/C,EACE,iBACA,SAASC,GACT,oBAAiB,IACjB,oBACA,eAAY,iBACZ,eAAY,iBACZ,UAAO,MACP,IAAIC,GACJ,UAAUC,GACV,WACA,GAAGC,KAEL,GACA;CACA,IAAMC,IAAQC,EAAiB;EAAE,IAAIJ;EAAQ,UAAUC;CAAa,CAAC,GAC/DI,IAASC,EAAM,GACfC,IAAUJ,EAAM,MAAM,GAAGE,EAAO,WAChCG,IAAWC,EAAyB,IAAI,GACxCC,IAAcC,EAAcC,GAAKJ,CAAQ,GACzC,CAACK,GAAiBC,KAAsBC,EAASC,CAAc,GAC/DC,IAAUlB,KAAec,GACzBK,KAAc,MAAkB;EAEpC,AADInB,MAAgB,KAAA,KAAWe,EAAmBK,CAAI,GAClDA,MAASF,KAASG,IAAkBD,CAAI;CAC9C,GACME,IAAgBZ,EAAOS,CAAU;CAKvC,AAJA,EAAc,UAAUA,GAIxBI,QAAgB;EACd,IAAMC,IAAcf,EAAS,SAAS;EACtC,IAAI,CAACe,GAAa;EAClB,IAAMC,UAAa;GACjB,EAAc,QAAQ,EAAK;EAC7B;EAGA,OAFAD,EAAY,iBAAiB,UAAUC,CAAI,GAC3CD,EAAY,iBAAiB,SAASC,CAAI,SAC7B;GAEX,AADAD,EAAY,oBAAoB,UAAUC,CAAI,GAC9CD,EAAY,oBAAoB,SAASC,CAAI;EAC/C;CACF,GAAG,CAAC,CAAC;CAGL,IAAMC,KAAc,MAAwC;EAC1D,IAAMC,IAAMC,EAAM,cAAc;EAChC,AAAID,KAAOC,EAAM,yBAAyB,QAAQD,EAAI,SAASC,EAAM,aAAa,KAClFC,IAASD,CAAK;CAChB,GACME,KAAoB,MAAyC;EACjE,IAAMC,IAAQtB,EAAS;EACvB,IAAI,CAACsB,KAAS,CAACF,GAAQ;EACvB,IAAMF,IAAMI,EAAM;EAClB,AAAIJ,KAAOC,EAAM,yBAAyB,QAAQD,EAAI,SAASC,EAAM,aAAa,KAElFC,EAAOD,CAAgD;CACzD,GAGMI,KAAa,MAAsB;EACvC,AAAI,SAAS,kBAAkBvB,EAAS,WAASmB,EAAM,eAAe;CACxE;CAEA,OACE,kBAAClC,EAAoB,MAArB;EAAmC;EAAS,oBAAoByB;EAC9D,UAAA,kBAACe,GAAD;GACE,KAAKvB;GACL,GAAIR;GACJ,IAAIK;GACE;GACN,UAAUN;GACV,MAAMgB,IAAU,SAAS;GACX;GACd,gBAAe;GACf,aAAY;GACZ,YAAY;GACZ,QAAQQ;GACR,UACE,kBAAChC,EAAoB,QAArB;IACE,SAAA;IACA,IAAI,GAAGc,EAAQ;IACf,iBAAeA;IACf,cAAYU,IAAUiB,IAAYC;IAElC,UAAA,kBAACC,GAAD;KACE,WAAW1C,EAAO;KAClB,MAAK;KACL,OAAOuB,IAAUiB,IAAYC;KAC7B,MAAgBE,EAAVpB,IAAWqB,IAAiBC,GAAlB,CAAa,CAAc;KAC3C,UAAUpC,EAAM;KAChB,aAAa4B;KACb,QAAQF;IACT,CAAA;GACyB,CAAA;EAE/B,CAAA;CACuB,CAAA;AAE9B,CAAC,CACH"}
1
+ {"version":3,"file":"PasswordInput.js","names":["PasswordToggleField","styles","PasswordInput_module_default","PasswordInput","markFieldAware","forwardRef","visibleProp","idProp","disabledProp","rest","field","useResolvedField","autoId","useId","inputId","inputRef","useRef","composedRef","useMergedRefs","ref","internalVisible","setInternalVisible","useState","defaultVisible","visible","setVisible","next","onVisibleChange","setVisibleRef","useEffect","formElement","hide","handleBlur","box","event","onBlur","handleToggleBlur","input","keepFocus","unstable_PasswordToggleField","Input","hideLabel","showLabel","IconButton","jsx","EyeOffIcon","EyeIcon"],"sources":["../../../../src/components/inputs/PasswordInput/PasswordInput.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useId,\n useRef,\n useState,\n type FocusEvent,\n type MouseEvent,\n} from 'react'\nimport { unstable_PasswordToggleField as PasswordToggleField } from 'radix-ui'\nimport { EyeIcon, EyeOffIcon } 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 { useMergedRefs } from '#components/inputs/internal/refs'\nimport styles from './PasswordInput.module.css'\n\nexport interface PasswordInputProps extends Omit<InputProps, 'type' | 'trailing'> {\n /** Required: tells password managers whether to fill a saved password or offer a new one. */\n autoComplete: 'current-password' | 'new-password'\n /** Whether the password shows as text. Controlled; pair with `onVisibleChange`. */\n visible?: boolean\n defaultVisible?: boolean\n onVisibleChange?: (visible: boolean) => void\n /** Accessible name of the toggle while hidden. Default `'Show password'`. */\n showLabel?: string\n /** Accessible name of the toggle while shown. Default `'Hide password'`. */\n hideLabel?: string\n}\n\n/**\n * A password input with a show and hide toggle that says what pressing it does.\n *\n * @remarks\n * `PasswordInput` adds a toggle to the end of the box that shows or hides the password. The toggle\n * is an `IconButton` whose name says what pressing it will do (\"Show password\"), and submitting or\n * resetting the form hides the password again. `autoComplete` is required (`current-password` or\n * `new-password`), so password managers do the right thing. `onBlur` fires only when focus leaves\n * both the input and its toggle.\n *\n * @privateRemarks\n * A password input with a show/hide toggle at the end of the box (Radix\n * PasswordToggleField). The toggle is an IconButton whose name says what pressing it\n * does; submitting or resetting the form hides the password again. The ref goes to the\n * input; `onBlur` fires only when focus leaves the input *and* its toggle.\n *\n * <PasswordInput autoComplete=\"current-password\" />\n */\nexport const PasswordInput = markFieldAware(\n forwardRef<HTMLInputElement, PasswordInputProps>(function PasswordInput(\n {\n autoComplete,\n visible: visibleProp,\n defaultVisible = false,\n onVisibleChange,\n showLabel = 'Show password',\n hideLabel = 'Hide password',\n size = 'md',\n id: idProp,\n disabled: disabledProp,\n onBlur,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({ id: idProp, disabled: disabledProp })\n const autoId = useId()\n const inputId = field.id ?? `${autoId}password`\n const inputRef = useRef<HTMLInputElement>(null)\n const composedRef = useMergedRefs(ref, inputRef)\n const [internalVisible, setInternalVisible] = useState(defaultVisible)\n const visible = visibleProp ?? internalVisible\n const setVisible = (next: boolean) => {\n if (visibleProp === undefined) setInternalVisible(next)\n if (next !== visible) onVisibleChange?.(next)\n }\n const setVisibleRef = useRef(setVisible)\n setVisibleRef.current = setVisible\n\n // Submitting or resetting the form hides the password (a shown password shouldn't\n // survive into the next screen or a reset form).\n useEffect(() => {\n const formElement = inputRef.current?.form\n if (!formElement) return\n const hide = () => {\n setVisibleRef.current(false)\n }\n formElement.addEventListener('submit', hide)\n formElement.addEventListener('reset', hide)\n return () => {\n formElement.removeEventListener('submit', hide)\n formElement.removeEventListener('reset', hide)\n }\n }, [])\n\n // Blur counts only when focus leaves the whole box (input + toggle).\n const handleBlur = (event: FocusEvent<HTMLInputElement>) => {\n const box = event.currentTarget.parentElement\n if (box && event.relatedTarget instanceof Node && box.contains(event.relatedTarget)) return\n onBlur?.(event)\n }\n const handleToggleBlur = (event: FocusEvent<HTMLButtonElement>) => {\n const input = inputRef.current\n if (!input || !onBlur) return\n const box = input.parentElement\n if (box && event.relatedTarget instanceof Node && box.contains(event.relatedTarget)) return\n // Same event, typed for the input's handler: the target is the toggle that lost focus.\n onBlur(event as unknown as FocusEvent<HTMLInputElement>)\n }\n\n // A pointer press keeps focus (and the caret) in the input.\n const keepFocus = (event: MouseEvent) => {\n if (document.activeElement === inputRef.current) event.preventDefault()\n }\n\n return (\n <PasswordToggleField.Root visible={visible} onVisibilityChange={setVisible}>\n <Input\n ref={composedRef}\n {...rest}\n id={inputId}\n size={size}\n disabled={disabledProp}\n type={visible ? 'text' : 'password'}\n autoComplete={autoComplete}\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n onBlur={handleBlur}\n trailing={\n <PasswordToggleField.Toggle\n asChild\n id={`${inputId}-toggle`}\n aria-controls={inputId}\n aria-label={visible ? hideLabel : showLabel}\n >\n <IconButton\n className={styles.toggle}\n size=\"sm\"\n label={visible ? hideLabel : showLabel}\n icon={visible ? <EyeOffIcon /> : <EyeIcon />}\n disabled={field.disabled}\n onMouseDown={keepFocus}\n onBlur={handleToggleBlur}\n />\n </PasswordToggleField.Toggle>\n }\n />\n </PasswordToggleField.Root>\n )\n }),\n)\n"],"mappings":";;;;;;;;;;AAgDA,IAAaG,IAAgBC,EAC3BC,EAAiD,SAC/C,EACE,iBACA,SAASC,GACT,oBAAiB,IACjB,oBACA,eAAY,iBACZ,eAAY,iBACZ,UAAO,MACP,IAAIC,GACJ,UAAUC,GACV,WACA,GAAGC,KAEL,GACA;CACA,IAAMC,IAAQC,EAAiB;EAAE,IAAIJ;EAAQ,UAAUC;CAAa,CAAC,GAC/DI,IAASC,EAAM,GACfC,IAAUJ,EAAM,MAAM,GAAGE,EAAO,WAChCG,IAAWC,EAAyB,IAAI,GACxCC,IAAcC,EAAcC,GAAKJ,CAAQ,GACzC,CAACK,GAAiBC,KAAsBC,EAASC,CAAc,GAC/DC,IAAUlB,KAAec,GACzBK,KAAc,MAAkB;EAEpC,AADInB,MAAgB,KAAA,KAAWe,EAAmBK,CAAI,GAClDA,MAASF,KAASG,IAAkBD,CAAI;CAC9C,GACME,IAAgBZ,EAAOS,CAAU;CAKvC,AAJA,EAAc,UAAUA,GAIxBI,QAAgB;EACd,IAAMC,IAAcf,EAAS,SAAS;EACtC,IAAI,CAACe,GAAa;EAClB,IAAMC,UAAa;GACjB,EAAc,QAAQ,EAAK;EAC7B;EAGA,OAFAD,EAAY,iBAAiB,UAAUC,CAAI,GAC3CD,EAAY,iBAAiB,SAASC,CAAI,SAC7B;GAEX,AADAD,EAAY,oBAAoB,UAAUC,CAAI,GAC9CD,EAAY,oBAAoB,SAASC,CAAI;EAC/C;CACF,GAAG,CAAC,CAAC;CAGL,IAAMC,KAAc,MAAwC;EAC1D,IAAMC,IAAMC,EAAM,cAAc;EAChC,AAAID,KAAOC,EAAM,yBAAyB,QAAQD,EAAI,SAASC,EAAM,aAAa,KAClFC,IAASD,CAAK;CAChB,GACME,KAAoB,MAAyC;EACjE,IAAMC,IAAQtB,EAAS;EACvB,IAAI,CAACsB,KAAS,CAACF,GAAQ;EACvB,IAAMF,IAAMI,EAAM;EAClB,AAAIJ,KAAOC,EAAM,yBAAyB,QAAQD,EAAI,SAASC,EAAM,aAAa,KAElFC,EAAOD,CAAgD;CACzD,GAGMI,KAAa,MAAsB;EACvC,AAAI,SAAS,kBAAkBvB,EAAS,WAASmB,EAAM,eAAe;CACxE;CAEA,OACE,kBAAClC,EAAoB,MAArB;EAAmC;EAAS,oBAAoByB;EAC9D,UAAA,kBAACe,GAAD;GACE,KAAKvB;GACL,GAAIR;GACJ,IAAIK;GACE;GACN,UAAUN;GACV,MAAMgB,IAAU,SAAS;GACX;GACd,gBAAe;GACf,aAAY;GACZ,YAAY;GACZ,QAAQQ;GACR,UACE,kBAAChC,EAAoB,QAArB;IACE,SAAA;IACA,IAAI,GAAGc,EAAQ;IACf,iBAAeA;IACf,cAAYU,IAAUiB,IAAYC;IAElC,UAAA,kBAACC,GAAD;KACE,WAAW1C,EAAO;KAClB,MAAK;KACL,OAAOuB,IAAUiB,IAAYC;KAC7B,MAAgBE,EAAVpB,IAAWqB,IAAiBC,GAAlB,CAAa,CAAc;KAC3C,UAAUpC,EAAM;KAChB,aAAa4B;KACb,QAAQF;IACT,CAAA;GACyB,CAAA;EAE/B,CAAA;CACuB,CAAA;AAE9B,CAAC,CACH"}
@@ -14,6 +14,15 @@ export interface RadioGroupItemProps extends Omit<ComponentPropsWithoutRef<typeo
14
14
  /** Secondary line under the label, linked with `aria-describedby`. */
15
15
  description?: ReactNode;
16
16
  }
17
+ /**
18
+ * One choice from a short list. One tab stop, with arrow keys to move and choose.
19
+ *
20
+ * @remarks
21
+ * `RadioGroup` is Radix RadioGroup with Kiln's look. The group is one tab stop, and arrow keys
22
+ * move between options and select them. Compose `RadioGroup.Item`s with your own labels, or use
23
+ * {@link RadioGroupField | RadioGroupField}, which lays the options out with labels and
24
+ * descriptions under a legend.
25
+ */
17
26
  export declare const RadioGroup: import("react").ForwardRefExoticComponent<RadioGroupProps & import("react").RefAttributes<HTMLDivElement>> & {
18
27
  Item: import("react").ForwardRefExoticComponent<RadioGroupItemProps & import("react").RefAttributes<HTMLButtonElement>>;
19
28
  };