@vielzeug/refine 26.10.6 → 26.10.9

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 (326) hide show
  1. package/README.md +6 -0
  2. package/dist/accordion-item.cjs +2 -2
  3. package/dist/accordion-item.cjs.map +1 -1
  4. package/dist/accordion-item.js +6 -6
  5. package/dist/accordion-item.js.map +1 -1
  6. package/dist/accordion.cjs +2 -2
  7. package/dist/accordion.cjs.map +1 -1
  8. package/dist/accordion.js +2 -2
  9. package/dist/accordion.js.map +1 -1
  10. package/dist/alert.cjs +2 -2
  11. package/dist/alert.cjs.map +1 -1
  12. package/dist/alert.js +2 -2
  13. package/dist/alert.js.map +1 -1
  14. package/dist/avatar.cjs +3 -3
  15. package/dist/avatar.cjs.map +1 -1
  16. package/dist/avatar.js +2 -2
  17. package/dist/avatar.js.map +1 -1
  18. package/dist/badge.cjs +2 -2
  19. package/dist/badge.cjs.map +1 -1
  20. package/dist/badge.js +2 -2
  21. package/dist/badge.js.map +1 -1
  22. package/dist/box.cjs +2 -2
  23. package/dist/box.cjs.map +1 -1
  24. package/dist/box.js +2 -2
  25. package/dist/box.js.map +1 -1
  26. package/dist/button-group.cjs +2 -2
  27. package/dist/button-group.cjs.map +1 -1
  28. package/dist/button-group.js +2 -2
  29. package/dist/button-group.js.map +1 -1
  30. package/dist/button.cjs +9 -9
  31. package/dist/button.cjs.map +1 -1
  32. package/dist/button.js +8 -8
  33. package/dist/button.js.map +1 -1
  34. package/dist/card.cjs +2 -2
  35. package/dist/card.cjs.map +1 -1
  36. package/dist/card.js +2 -2
  37. package/dist/card.js.map +1 -1
  38. package/dist/carousel.cjs +6 -6
  39. package/dist/carousel.cjs.map +1 -1
  40. package/dist/carousel.js +6 -6
  41. package/dist/carousel.js.map +1 -1
  42. package/dist/chat-message.cjs +4 -4
  43. package/dist/chat-message.cjs.map +1 -1
  44. package/dist/chat-message.js +4 -4
  45. package/dist/chat-message.js.map +1 -1
  46. package/dist/checkbox-group.cjs +1 -1
  47. package/dist/checkbox-group.cjs.map +1 -1
  48. package/dist/checkbox-group.js +1 -1
  49. package/dist/checkbox-group.js.map +1 -1
  50. package/dist/checkbox.cjs +1 -1
  51. package/dist/checkbox.cjs.map +1 -1
  52. package/dist/checkbox.js +1 -1
  53. package/dist/checkbox.js.map +1 -1
  54. package/dist/chip.cjs +8 -8
  55. package/dist/chip.cjs.map +1 -1
  56. package/dist/chip.js +8 -8
  57. package/dist/chip.js.map +1 -1
  58. package/dist/combobox.cjs +8 -8
  59. package/dist/combobox.cjs.map +1 -1
  60. package/dist/combobox.js +11 -11
  61. package/dist/combobox.js.map +1 -1
  62. package/dist/content/avatar/avatar.d.ts +2 -1
  63. package/dist/content/avatar/avatar.d.ts.map +1 -1
  64. package/dist/content/card/card.d.ts +6 -3
  65. package/dist/content/card/card.d.ts.map +1 -1
  66. package/dist/content/carousel/carousel.d.ts +5 -3
  67. package/dist/content/carousel/carousel.d.ts.map +1 -1
  68. package/dist/content/chat-message/chat-message.d.ts +2 -1
  69. package/dist/content/chat-message/chat-message.d.ts.map +1 -1
  70. package/dist/content/copy-command/copy-command.d.ts +1 -1
  71. package/dist/content/copy-command/copy-command.d.ts.map +1 -1
  72. package/dist/content/keyboard-key/keyboard-key.d.ts +2 -1
  73. package/dist/content/keyboard-key/keyboard-key.d.ts.map +1 -1
  74. package/dist/content/list/list.d.ts +2 -1
  75. package/dist/content/list/list.d.ts.map +1 -1
  76. package/dist/content/marquee/marquee.d.ts +3 -2
  77. package/dist/content/marquee/marquee.d.ts.map +1 -1
  78. package/dist/content/pagination/pagination.d.ts +1 -1
  79. package/dist/content/pagination/pagination.d.ts.map +1 -1
  80. package/dist/content/qr-code/qr-code.d.ts +0 -41
  81. package/dist/content/qr-code/qr-code.d.ts.map +1 -1
  82. package/dist/content/separator/separator.d.ts +1 -1
  83. package/dist/content/separator/separator.d.ts.map +1 -1
  84. package/dist/content/stepper/step.d.ts +1 -1
  85. package/dist/content/stepper/step.d.ts.map +1 -1
  86. package/dist/content/table/table.d.ts +2 -1
  87. package/dist/content/table/table.d.ts.map +1 -1
  88. package/dist/content/text/text.d.ts +13 -6
  89. package/dist/content/text/text.d.ts.map +1 -1
  90. package/dist/cookie-banner.cjs +1 -1
  91. package/dist/cookie-banner.cjs.map +1 -1
  92. package/dist/cookie-banner.js +1 -1
  93. package/dist/cookie-banner.js.map +1 -1
  94. package/dist/copy-command.cjs +6 -6
  95. package/dist/copy-command.cjs.map +1 -1
  96. package/dist/copy-command.js +7 -7
  97. package/dist/copy-command.js.map +1 -1
  98. package/dist/core/checkable.cjs +1 -1
  99. package/dist/core/checkable.cjs.map +1 -1
  100. package/dist/core/checkable.d.ts +6 -7
  101. package/dist/core/checkable.d.ts.map +1 -1
  102. package/dist/core/checkable.js +1 -1
  103. package/dist/core/checkable.js.map +1 -1
  104. package/dist/core/choice-field.cjs +1 -1
  105. package/dist/core/choice-field.cjs.map +1 -1
  106. package/dist/core/choice-field.d.ts +6 -6
  107. package/dist/core/choice-field.d.ts.map +1 -1
  108. package/dist/core/choice-field.js +1 -1
  109. package/dist/core/choice-field.js.map +1 -1
  110. package/dist/core/text-field.cjs +1 -1
  111. package/dist/core/text-field.cjs.map +1 -1
  112. package/dist/core/text-field.d.ts +8 -8
  113. package/dist/core/text-field.d.ts.map +1 -1
  114. package/dist/core/text-field.js +1 -1
  115. package/dist/core/text-field.js.map +1 -1
  116. package/dist/counter.cjs +2 -2
  117. package/dist/counter.cjs.map +1 -1
  118. package/dist/counter.js +2 -2
  119. package/dist/counter.js.map +1 -1
  120. package/dist/custom-elements.json +5184 -5047
  121. package/dist/datagrid.cjs +47 -47
  122. package/dist/datagrid.cjs.map +1 -1
  123. package/dist/datagrid.js +51 -51
  124. package/dist/datagrid.js.map +1 -1
  125. package/dist/date-picker.cjs +9 -9
  126. package/dist/date-picker.cjs.map +1 -1
  127. package/dist/date-picker.js +11 -11
  128. package/dist/date-picker.js.map +1 -1
  129. package/dist/dialog.cjs +3 -3
  130. package/dist/dialog.cjs.map +1 -1
  131. package/dist/dialog.js +3 -3
  132. package/dist/dialog.js.map +1 -1
  133. package/dist/disclosure/accordion/accordion.d.ts +4 -2
  134. package/dist/disclosure/accordion/accordion.d.ts.map +1 -1
  135. package/dist/disclosure/accordion-item/accordion-item.d.ts +1 -1
  136. package/dist/disclosure/accordion-item/accordion-item.d.ts.map +1 -1
  137. package/dist/disclosure/tab-item/tab-item.d.ts +1 -1
  138. package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
  139. package/dist/disclosure/tabs/tabs.d.ts +1 -1
  140. package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
  141. package/dist/drawer.cjs +4 -4
  142. package/dist/drawer.cjs.map +1 -1
  143. package/dist/drawer.js +2 -2
  144. package/dist/drawer.js.map +1 -1
  145. package/dist/feedback/alert/alert.d.ts +2 -1
  146. package/dist/feedback/alert/alert.d.ts.map +1 -1
  147. package/dist/feedback/badge/badge.d.ts +7 -4
  148. package/dist/feedback/badge/badge.d.ts.map +1 -1
  149. package/dist/feedback/chip/chip.d.ts +7 -5
  150. package/dist/feedback/chip/chip.d.ts.map +1 -1
  151. package/dist/grid-item.cjs +2 -2
  152. package/dist/grid-item.cjs.map +1 -1
  153. package/dist/grid-item.js +2 -2
  154. package/dist/grid-item.js.map +1 -1
  155. package/dist/grid.cjs +2 -2
  156. package/dist/grid.cjs.map +1 -1
  157. package/dist/grid.js +2 -2
  158. package/dist/grid.js.map +1 -1
  159. package/dist/input.cjs +7 -7
  160. package/dist/input.cjs.map +1 -1
  161. package/dist/input.js +8 -8
  162. package/dist/input.js.map +1 -1
  163. package/dist/inputs/button/button.d.ts +3 -2
  164. package/dist/inputs/button/button.d.ts.map +1 -1
  165. package/dist/inputs/button-group/button-group.d.ts +1 -37
  166. package/dist/inputs/button-group/button-group.d.ts.map +1 -1
  167. package/dist/inputs/combobox/combobox.types.d.ts +2 -2
  168. package/dist/inputs/combobox/combobox.types.d.ts.map +1 -1
  169. package/dist/inputs/counter/counter.d.ts +3 -2
  170. package/dist/inputs/counter/counter.d.ts.map +1 -1
  171. package/dist/inputs/datagrid/datagrid-model.cjs +1 -1
  172. package/dist/inputs/datagrid/datagrid-model.cjs.map +1 -1
  173. package/dist/inputs/datagrid/datagrid-model.d.ts +2 -1
  174. package/dist/inputs/datagrid/datagrid-model.d.ts.map +1 -1
  175. package/dist/inputs/datagrid/datagrid-model.js +1 -1
  176. package/dist/inputs/datagrid/datagrid-model.js.map +1 -1
  177. package/dist/inputs/datagrid/datagrid.d.ts +4 -2
  178. package/dist/inputs/datagrid/datagrid.d.ts.map +1 -1
  179. package/dist/inputs/date-picker/date-picker.d.ts +3 -2
  180. package/dist/inputs/date-picker/date-picker.d.ts.map +1 -1
  181. package/dist/inputs/input/input.d.ts +5 -3
  182. package/dist/inputs/input/input.d.ts.map +1 -1
  183. package/dist/inputs/message-composer/message-composer.d.ts +3 -2
  184. package/dist/inputs/message-composer/message-composer.d.ts.map +1 -1
  185. package/dist/inputs/number-input/number-input.d.ts +1 -1
  186. package/dist/inputs/number-input/number-input.d.ts.map +1 -1
  187. package/dist/inputs/otp-input/otp-input.d.ts +3 -2
  188. package/dist/inputs/otp-input/otp-input.d.ts.map +1 -1
  189. package/dist/inputs/query-bar/query-bar.d.ts.map +1 -1
  190. package/dist/inputs/select/select.d.ts +2 -2
  191. package/dist/inputs/select/select.d.ts.map +1 -1
  192. package/dist/inputs/textarea/textarea.d.ts +4 -3
  193. package/dist/inputs/textarea/textarea.d.ts.map +1 -1
  194. package/dist/inputs/time-picker/time-picker.d.ts +2 -2
  195. package/dist/inputs/time-picker/time-picker.d.ts.map +1 -1
  196. package/dist/keyboard-key.cjs +3 -3
  197. package/dist/keyboard-key.cjs.map +1 -1
  198. package/dist/keyboard-key.js +3 -3
  199. package/dist/keyboard-key.js.map +1 -1
  200. package/dist/layout/box/box.d.ts +6 -3
  201. package/dist/layout/box/box.d.ts.map +1 -1
  202. package/dist/layout/grid/grid.d.ts +11 -6
  203. package/dist/layout/grid/grid.d.ts.map +1 -1
  204. package/dist/layout/grid-item/grid-item.d.ts +8 -4
  205. package/dist/layout/grid-item/grid-item.d.ts.map +1 -1
  206. package/dist/layout/navbar/navbar.d.ts +4 -2
  207. package/dist/layout/navbar/navbar.d.ts.map +1 -1
  208. package/dist/layout/sidebar/sidebar.d.ts +2 -1
  209. package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
  210. package/dist/list-item.cjs +1 -1
  211. package/dist/list-item.cjs.map +1 -1
  212. package/dist/list-item.js +1 -1
  213. package/dist/list-item.js.map +1 -1
  214. package/dist/list.cjs +2 -2
  215. package/dist/list.cjs.map +1 -1
  216. package/dist/list.js +2 -2
  217. package/dist/list.js.map +1 -1
  218. package/dist/marquee.cjs +3 -3
  219. package/dist/marquee.cjs.map +1 -1
  220. package/dist/marquee.js +3 -3
  221. package/dist/marquee.js.map +1 -1
  222. package/dist/menu.cjs +3 -3
  223. package/dist/menu.cjs.map +1 -1
  224. package/dist/menu.js +3 -3
  225. package/dist/menu.js.map +1 -1
  226. package/dist/message-composer.cjs +10 -10
  227. package/dist/message-composer.cjs.map +1 -1
  228. package/dist/message-composer.js +7 -7
  229. package/dist/message-composer.js.map +1 -1
  230. package/dist/navbar.cjs +10 -10
  231. package/dist/navbar.cjs.map +1 -1
  232. package/dist/navbar.js +13 -13
  233. package/dist/navbar.js.map +1 -1
  234. package/dist/number-input.cjs +3 -3
  235. package/dist/number-input.cjs.map +1 -1
  236. package/dist/number-input.js +4 -4
  237. package/dist/number-input.js.map +1 -1
  238. package/dist/otp-input.cjs +4 -4
  239. package/dist/otp-input.cjs.map +1 -1
  240. package/dist/otp-input.js +4 -4
  241. package/dist/otp-input.js.map +1 -1
  242. package/dist/overlay/dialog/dialog.d.ts +7 -4
  243. package/dist/overlay/dialog/dialog.d.ts.map +1 -1
  244. package/dist/overlay/drawer/drawer.d.ts +4 -2
  245. package/dist/overlay/drawer/drawer.d.ts.map +1 -1
  246. package/dist/overlay/menu/menu.d.ts +2 -1
  247. package/dist/overlay/menu/menu.d.ts.map +1 -1
  248. package/dist/pagination.cjs +9 -9
  249. package/dist/pagination.cjs.map +1 -1
  250. package/dist/pagination.js +9 -9
  251. package/dist/pagination.js.map +1 -1
  252. package/dist/qr-code.cjs +2 -2
  253. package/dist/qr-code.cjs.map +1 -1
  254. package/dist/qr-code.js +2 -2
  255. package/dist/qr-code.js.map +1 -1
  256. package/dist/query-bar.cjs +7 -7
  257. package/dist/query-bar.cjs.map +1 -1
  258. package/dist/query-bar.js +8 -8
  259. package/dist/query-bar.js.map +1 -1
  260. package/dist/radio-group.cjs +1 -1
  261. package/dist/radio-group.cjs.map +1 -1
  262. package/dist/radio-group.js +1 -1
  263. package/dist/radio-group.js.map +1 -1
  264. package/dist/refine.cjs +123 -123
  265. package/dist/refine.cjs.map +1 -1
  266. package/dist/refine.iife.js +123 -123
  267. package/dist/refine.iife.js.map +1 -1
  268. package/dist/refine.js +124 -124
  269. package/dist/refine.js.map +1 -1
  270. package/dist/select.cjs +13 -13
  271. package/dist/select.cjs.map +1 -1
  272. package/dist/select.js +15 -15
  273. package/dist/select.js.map +1 -1
  274. package/dist/separator.cjs +4 -4
  275. package/dist/separator.cjs.map +1 -1
  276. package/dist/separator.js +4 -4
  277. package/dist/separator.js.map +1 -1
  278. package/dist/shared/prop-bundles.cjs +1 -1
  279. package/dist/shared/prop-bundles.cjs.map +1 -1
  280. package/dist/shared/prop-bundles.d.ts +7 -8
  281. package/dist/shared/prop-bundles.d.ts.map +1 -1
  282. package/dist/shared/prop-bundles.js +1 -1
  283. package/dist/shared/prop-bundles.js.map +1 -1
  284. package/dist/shared/types.cjs +2 -0
  285. package/dist/shared/types.cjs.map +1 -0
  286. package/dist/shared/types.d.ts +27 -9
  287. package/dist/shared/types.d.ts.map +1 -1
  288. package/dist/shared/types.js +2 -0
  289. package/dist/shared/types.js.map +1 -0
  290. package/dist/sidebar.cjs +7 -7
  291. package/dist/sidebar.cjs.map +1 -1
  292. package/dist/sidebar.js +4 -4
  293. package/dist/sidebar.js.map +1 -1
  294. package/dist/step.cjs +10 -10
  295. package/dist/step.cjs.map +1 -1
  296. package/dist/step.js +10 -10
  297. package/dist/step.js.map +1 -1
  298. package/dist/tab-item.cjs +4 -4
  299. package/dist/tab-item.cjs.map +1 -1
  300. package/dist/tab-item.js +5 -5
  301. package/dist/tab-item.js.map +1 -1
  302. package/dist/table.cjs +2 -2
  303. package/dist/table.cjs.map +1 -1
  304. package/dist/table.js +2 -2
  305. package/dist/table.js.map +1 -1
  306. package/dist/tabs.cjs +2 -2
  307. package/dist/tabs.cjs.map +1 -1
  308. package/dist/tabs.js +4 -4
  309. package/dist/tabs.js.map +1 -1
  310. package/dist/text.cjs +2 -2
  311. package/dist/text.cjs.map +1 -1
  312. package/dist/text.js +2 -2
  313. package/dist/text.js.map +1 -1
  314. package/dist/textarea.cjs +7 -7
  315. package/dist/textarea.cjs.map +1 -1
  316. package/dist/textarea.js +7 -7
  317. package/dist/textarea.js.map +1 -1
  318. package/dist/time-picker.cjs +7 -7
  319. package/dist/time-picker.cjs.map +1 -1
  320. package/dist/time-picker.js +7 -7
  321. package/dist/time-picker.js.map +1 -1
  322. package/dist/tooltip.cjs +1 -1
  323. package/dist/tooltip.cjs.map +1 -1
  324. package/dist/tooltip.js +1 -1
  325. package/dist/tooltip.js.map +1 -1
  326. package/package.json +13 -13
@@ -1 +1 @@
1
- {"version":3,"file":"copy-command.cjs","names":[],"sources":["../src/content/copy-command/copy-command.ts"],"sourcesContent":["import { define, html, onCleanup, prop, useEmit, useSlots } from '@vielzeug/ore';\nimport { computed, signal } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\n\nimport componentStyles from './copy-command.css?inline';\n\nexport { ICON_TAG } from '../icon/icon';\n/** Copy command component properties */\nexport type OreCopyCommandProps = {\n /** Border radius size */\n rounded?: RoundedSize;\n /** Component size */\n size?: ComponentSize;\n /** The command string to display and copy to clipboard */\n value?: string;\n /** Visual style variant */\n variant?: 'flat' | 'bordered' | 'ghost';\n};\n\n/** Copy command component events */\nexport type OreCopyCommandEvents = {\n /** Emitted when the command is successfully copied to the clipboard */\n copy: { value: string };\n};\n\n/**\n * A styled, copy-to-clipboard command display component.\n * Shows a command string in a monospace code block with a copy button.\n * Clicking the component copies the value to the clipboard, shows a transient\n * check-mark confirmation, and emits a `copy` event.\n *\n * @element ore-copy-command\n *\n * @attr {string} value - The command string to display and copy\n * @attr {string} size - Component size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual style: 'flat' | 'bordered' | 'ghost'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n *\n * @fires copy - Emitted on successful clipboard write. detail: { value: string }\n *\n * @slot suffix - Optional controls appended after the copy button (e.g. a cycle button)\n *\n * @cssprop --copy-command-bg - Background color of the command block\n * @cssprop --copy-command-color - Text color of the command string\n * @cssprop --copy-command-border-color - Border color\n * @cssprop --copy-command-radius - Border radius (overrides the `rounded` attribute)\n * @cssprop --copy-command-padding - Inner padding of the command button\n * @cssprop --copy-command-font-size - Font size of the command string\n * @cssprop --copy-command-icon-color - Color of the copy/check icon\n * @cssprop --copy-command-hover-bg - Background on hover\n *\n * @part wrapper - Outer flex container\n * @part command - The copy button element\n * @part command-text - The `<code>` element displaying the command string\n * @part copy-icon - The icon wrapper span\n * @part suffix - Suffix slot container\n *\n * @example\n * ```html\n * <ore-copy-command value=\"npm install @vielzeug/ripple\"></ore-copy-command>\n *\n * <ore-copy-command value=\"npx -y @vielzeug/codex\" size=\"sm\" variant=\"bordered\"></ore-copy-command>\n *\n * <!-- With a suffix slot (e.g. a cycle button) -->\n * <ore-copy-command value=\"npm install @vielzeug/ripple\">\n * <button slot=\"suffix\" type=\"button\" aria-label=\"Next package\">›</button>\n * </ore-copy-command>\n * ```\n */\nexport const COPY_COMMAND_TAG = 'ore-copy-command' as const;\n\ndefine<OreCopyCommandProps>(COPY_COMMAND_TAG, {\n props: {\n rounded: prop.string<RoundedSize>('md'),\n size: prop.string<ComponentSize>('md'),\n value: prop.string(''),\n variant: prop.string<'flat' | 'bordered' | 'ghost'>('flat'),\n },\n\n setup(props) {\n const emit = useEmit<OreCopyCommandEvents>();\n const slots = useSlots();\n\n const copied = signal(false);\n const copyFailed = signal(false);\n let resetTimer: ReturnType<typeof setTimeout> | null = null;\n\n onCleanup(() => {\n if (resetTimer !== null) clearTimeout(resetTimer);\n });\n\n const handleCopy = async () => {\n const text = props.value.value ?? '';\n\n if (!text) return;\n\n if (resetTimer !== null) clearTimeout(resetTimer);\n\n try {\n await navigator.clipboard.writeText(text);\n copied.value = true;\n copyFailed.value = false;\n emit('copy', { value: text });\n } catch {\n // Clipboard access denied or unavailable (e.g. insecure context): surface it instead of failing silently\n copied.value = false;\n copyFailed.value = true;\n }\n\n resetTimer = setTimeout(() => {\n copied.value = false;\n copyFailed.value = false;\n resetTimer = null;\n }, 2000);\n };\n\n const hasSuffix = computed(() => slots.has('suffix').value);\n const btnLabel = computed(() => {\n if (copied.value) return 'Copied!';\n\n if (copyFailed.value) return 'Copy failed: press to try again';\n\n return `Copy: ${props.value.value ?? ''}`;\n });\n\n return html`\n <div class=\"wrapper\" part=\"wrapper\">\n <button class=\"command\" part=\"command\" type=\"button\" aria-label=\"${btnLabel}\" @click=${handleCopy}>\n <code class=\"command-text\" part=\"command-text\">${props.value}</code>\n <span class=\"copy-icon\" part=\"copy-icon\" aria-hidden=\"true\">\n ${() => {\n if (copied.value)\n return html`\n <ore-icon name=\"check\" size=\"14\" class=\"icon-copied\" aria-hidden=\"true\"></ore-icon>\n `;\n\n if (copyFailed.value)\n return html`\n <ore-icon name=\"circle-alert\" size=\"14\" class=\"icon-failed\" aria-hidden=\"true\"></ore-icon>\n `;\n\n return html`\n <ore-icon name=\"copy\" size=\"14\" aria-hidden=\"true\"></ore-icon>\n `;\n }}\n </span>\n </button>\n <div class=\"suffix\" part=\"suffix\" ?hidden=\"${() => !hasSuffix.value}\">\n <slot name=\"suffix\"></slot>\n </div>\n </div>\n <div role=\"status\" class=\"sr-only\">\n ${() => {\n if (copied.value) return 'Copied to clipboard.';\n\n if (copyFailed.value) return 'Copy failed. Select the command text and copy it manually.';\n\n return '';\n }}\n </div>\n `;\n },\n\n styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"0QAsEA,IAAa,EAAmB,oBAEhC,EAAA,EAAA,OAAA,CAA4B,EAAkB,CAC5C,MAAO,CACL,QAAS,EAAA,KAAK,OAAoB,IAAI,EACtC,KAAM,EAAA,KAAK,OAAsB,IAAI,EACrC,MAAO,EAAA,KAAK,OAAO,EAAE,EACrB,QAAS,EAAA,KAAK,OAAsC,MAAM,CAC5D,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAO,EAAA,QAAA,CAA8B,EACrC,GAAA,EAAQ,EAAA,SAAA,CAAS,EAEjB,GAAA,EAAS,EAAA,OAAA,CAAO,EAAK,EACrB,GAAA,EAAa,EAAA,OAAA,CAAO,EAAK,EAC3B,EAAmD,MAEvD,EAAA,EAAA,UAAA,KAAgB,CACV,IAAe,MAAM,aAAa,CAAU,CAClD,CAAC,EAED,IAAM,WAAa,SAAY,CAC7B,IAAM,EAAO,EAAM,MAAM,OAAS,GAE7B,KAEL,CAAI,IAAe,MAAM,aAAa,CAAU,EAEhD,GAAI,CACF,MAAM,UAAU,UAAU,UAAU,CAAI,EACxC,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAK,OAAQ,CAAE,MAAO,CAAK,CAAC,CAC9B,MAAQ,CAEN,EAAO,MAAQ,GACf,EAAW,MAAQ,EACrB,CAEA,EAAa,eAAiB,CAC5B,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAa,IACf,EAAG,GAAI,CAjByC,CAkBlD,EAEM,GAAA,EAAY,EAAA,SAAA,KAAe,EAAM,IAAI,QAAQ,CAAC,CAAC,KAAK,EACpD,GAAA,EAAW,EAAA,SAAA,KACX,EAAO,MAAc,UAErB,EAAW,MAAc,kCAEtB,SAAS,EAAM,MAAM,OAAS,IACtC,EAED,MAAO,GAAA,IAAI;;2EAE4D,EAAS,WAAW,WAAW;2DAC/C,EAAM,MAAM;;kBAGrD,EAAO,MACF,EAAA,IAAI;;kBAIT,EAAW,MACN,EAAA,IAAI;;kBAIN,EAAA,IAAI;;gBAGX;;;yDAG6C,CAAC,EAAU,MAAM;;;;;cAM9D,EAAO,MAAc,uBAErB,EAAW,MAAc,6DAEtB,GACP;;KAGR,EAEA,OAAQ,CAAC,EAAA,YAAa,EAAA,OAAe,CACvC,CAAC"}
1
+ {"version":3,"file":"copy-command.cjs","names":[],"sources":["../src/content/copy-command/copy-command.ts"],"sourcesContent":["import { define, html, onCleanup, prop, useEmit, useSlots } from '@vielzeug/ore';\nimport { computed, signal } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\nimport { COMPONENT_SIZES, type ComponentSize, ROUNDED_SIZES, type RoundedSize } from '../../types';\n\nconst COPY_COMMAND_VARIANTS = ['flat', 'bordered', 'ghost'] as const;\n\nimport componentStyles from './copy-command.css?inline';\n\nexport { ICON_TAG } from '../icon/icon';\n/** Copy command component properties */\nexport type OreCopyCommandProps = {\n /** Border radius size */\n rounded?: RoundedSize;\n /** Component size */\n size?: ComponentSize;\n /** The command string to display and copy to clipboard */\n value?: string;\n /** Visual style variant */\n variant?: 'flat' | 'bordered' | 'ghost';\n};\n\n/** Copy command component events */\nexport type OreCopyCommandEvents = {\n /** Emitted when the command is successfully copied to the clipboard */\n copy: { value: string };\n};\n\n/**\n * A styled, copy-to-clipboard command display component.\n * Shows a command string in a monospace code block with a copy button.\n * Clicking the component copies the value to the clipboard, shows a transient\n * check-mark confirmation, and emits a `copy` event.\n *\n * @element ore-copy-command\n *\n * @attr {string} value - The command string to display and copy\n * @attr {string} size - Component size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual style: 'flat' | 'bordered' | 'ghost'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n *\n * @fires copy - Emitted on successful clipboard write. detail: { value: string }\n *\n * @slot suffix - Optional controls appended after the copy button (e.g. a cycle button)\n *\n * @cssprop --copy-command-bg - Background color of the command block\n * @cssprop --copy-command-color - Text color of the command string\n * @cssprop --copy-command-border-color - Border color\n * @cssprop --copy-command-radius - Border radius (overrides the `rounded` attribute)\n * @cssprop --copy-command-padding - Inner padding of the command button\n * @cssprop --copy-command-font-size - Font size of the command string\n * @cssprop --copy-command-icon-color - Color of the copy/check icon\n * @cssprop --copy-command-hover-bg - Background on hover\n *\n * @part wrapper - Outer flex container\n * @part command - The copy button element\n * @part command-text - The `<code>` element displaying the command string\n * @part copy-icon - The icon wrapper span\n * @part suffix - Suffix slot container\n *\n * @example\n * ```html\n * <ore-copy-command value=\"npm install @vielzeug/ripple\"></ore-copy-command>\n *\n * <ore-copy-command value=\"npx -y @vielzeug/codex\" size=\"sm\" variant=\"bordered\"></ore-copy-command>\n *\n * <!-- With a suffix slot (e.g. a cycle button) -->\n * <ore-copy-command value=\"npm install @vielzeug/ripple\">\n * <button slot=\"suffix\" type=\"button\" aria-label=\"Next package\">›</button>\n * </ore-copy-command>\n * ```\n */\nexport const COPY_COMMAND_TAG = 'ore-copy-command' as const;\n\ndefine<OreCopyCommandProps>(COPY_COMMAND_TAG, {\n props: {\n rounded: prop.oneOf(ROUNDED_SIZES, 'md'),\n size: prop.oneOf(COMPONENT_SIZES, 'md'),\n value: prop.string(''),\n variant: prop.oneOf(COPY_COMMAND_VARIANTS, 'flat'),\n },\n\n setup(props) {\n const emit = useEmit<OreCopyCommandEvents>();\n const slots = useSlots();\n\n const copied = signal(false);\n const copyFailed = signal(false);\n let resetTimer: ReturnType<typeof setTimeout> | null = null;\n\n onCleanup(() => {\n if (resetTimer !== null) clearTimeout(resetTimer);\n });\n\n const handleCopy = async () => {\n const text = props.value.value ?? '';\n\n if (!text) return;\n\n if (resetTimer !== null) clearTimeout(resetTimer);\n\n try {\n await navigator.clipboard.writeText(text);\n copied.value = true;\n copyFailed.value = false;\n emit('copy', { value: text });\n } catch {\n // Clipboard access denied or unavailable (e.g. insecure context): surface it instead of failing silently\n copied.value = false;\n copyFailed.value = true;\n }\n\n resetTimer = setTimeout(() => {\n copied.value = false;\n copyFailed.value = false;\n resetTimer = null;\n }, 2000);\n };\n\n const hasSuffix = computed(() => slots.has('suffix').value);\n const btnLabel = computed(() => {\n if (copied.value) return 'Copied!';\n\n if (copyFailed.value) return 'Copy failed: press to try again';\n\n return `Copy: ${props.value.value ?? ''}`;\n });\n\n return html`\n <div class=\"wrapper\" part=\"wrapper\">\n <button class=\"command\" part=\"command\" type=\"button\" aria-label=\"${btnLabel}\" @click=${handleCopy}>\n <code class=\"command-text\" part=\"command-text\">${props.value}</code>\n <span class=\"copy-icon\" part=\"copy-icon\" aria-hidden=\"true\">\n ${() => {\n if (copied.value)\n return html`\n <ore-icon name=\"check\" size=\"14\" class=\"icon-copied\" aria-hidden=\"true\"></ore-icon>\n `;\n\n if (copyFailed.value)\n return html`\n <ore-icon name=\"circle-alert\" size=\"14\" class=\"icon-failed\" aria-hidden=\"true\"></ore-icon>\n `;\n\n return html`\n <ore-icon name=\"copy\" size=\"14\" aria-hidden=\"true\"></ore-icon>\n `;\n }}\n </span>\n </button>\n <div class=\"suffix\" part=\"suffix\" ?hidden=\"${() => !hasSuffix.value}\">\n <slot name=\"suffix\"></slot>\n </div>\n </div>\n <div role=\"status\" class=\"sr-only\">\n ${() => {\n if (copied.value) return 'Copied to clipboard.';\n\n if (copyFailed.value) return 'Copy failed. Select the command text and copy it manually.';\n\n return '';\n }}\n </div>\n `;\n },\n\n styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"0SAKA,IAAM,EAAwB,CAAC,OAAQ,WAAY,OAAO,EAmE7C,EAAmB,oBAEhC,EAAA,EAAA,OAAA,CAA4B,EAAkB,CAC5C,MAAO,CACL,QAAS,EAAA,KAAK,MAAM,EAAA,cAAe,IAAI,EACvC,KAAM,EAAA,KAAK,MAAM,EAAA,gBAAiB,IAAI,EACtC,MAAO,EAAA,KAAK,OAAO,EAAE,EACrB,QAAS,EAAA,KAAK,MAAM,EAAuB,MAAM,CACnD,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAO,EAAA,QAAA,CAA8B,EACrC,GAAA,EAAQ,EAAA,SAAA,CAAS,EAEjB,GAAA,EAAS,EAAA,OAAA,CAAO,EAAK,EACrB,GAAA,EAAa,EAAA,OAAA,CAAO,EAAK,EAC3B,EAAmD,MAEvD,EAAA,EAAA,UAAA,KAAgB,CACV,IAAe,MAAM,aAAa,CAAU,CAClD,CAAC,EAED,IAAM,WAAa,SAAY,CAC7B,IAAM,EAAO,EAAM,MAAM,OAAS,GAE7B,KAEL,CAAI,IAAe,MAAM,aAAa,CAAU,EAEhD,GAAI,CACF,MAAM,UAAU,UAAU,UAAU,CAAI,EACxC,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAK,OAAQ,CAAE,MAAO,CAAK,CAAC,CAC9B,MAAQ,CAEN,EAAO,MAAQ,GACf,EAAW,MAAQ,EACrB,CAEA,EAAa,eAAiB,CAC5B,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAa,IACf,EAAG,GAAI,CAjByC,CAkBlD,EAEM,GAAA,EAAY,EAAA,SAAA,KAAe,EAAM,IAAI,QAAQ,CAAC,CAAC,KAAK,EACpD,GAAA,EAAW,EAAA,SAAA,KACX,EAAO,MAAc,UAErB,EAAW,MAAc,kCAEtB,SAAS,EAAM,MAAM,OAAS,IACtC,EAED,MAAO,GAAA,IAAI;;2EAE4D,EAAS,WAAW,WAAW;2DAC/C,EAAM,MAAM;;kBAGrD,EAAO,MACF,EAAA,IAAI;;kBAIT,EAAW,MACN,EAAA,IAAI;;kBAIN,EAAA,IAAI;;gBAGX;;;yDAG6C,CAAC,EAAU,MAAM;;;;;cAM9D,EAAO,MAAc,uBAErB,EAAW,MAAc,6DAEtB,GACP;;KAGR,EAEA,OAAQ,CAAC,EAAA,YAAa,EAAA,OAAe,CACvC,CAAC"}
@@ -1,23 +1,23 @@
1
- import{srOnlyMixin as e}from"./styles/mixins/accessibility.css.js";import{ICON_TAG as t}from"./icon.js";import n from"./content/copy-command/copy-command.css.js";import{computed as r,signal as i}from"@vielzeug/ripple";import{define as a,html as o,onCleanup as s,prop as c,useEmit as l,useSlots as u}from"@vielzeug/ore";var d=`ore-copy-command`;a(d,{props:{rounded:c.string(`md`),size:c.string(`md`),value:c.string(``),variant:c.string(`flat`)},setup(e){let t=l(),n=u(),a=i(!1),c=i(!1),d=null;s(()=>{d!==null&&clearTimeout(d)});let handleCopy=async()=>{let n=e.value.value??``;if(n){d!==null&&clearTimeout(d);try{await navigator.clipboard.writeText(n),a.value=!0,c.value=!1,t(`copy`,{value:n})}catch{a.value=!1,c.value=!0}d=setTimeout(()=>{a.value=!1,c.value=!1,d=null},2e3)}},f=r(()=>n.has(`suffix`).value),p=r(()=>a.value?`Copied!`:c.value?`Copy failed: press to try again`:`Copy: ${e.value.value??``}`);return o`
1
+ import{srOnlyMixin as e}from"./styles/mixins/accessibility.css.js";import{COMPONENT_SIZES as t,ROUNDED_SIZES as n}from"./shared/types.js";import{ICON_TAG as r}from"./icon.js";import i from"./content/copy-command/copy-command.css.js";import{computed as a,signal as o}from"@vielzeug/ripple";import{define as s,html as c,onCleanup as l,prop as u,useEmit as d,useSlots as f}from"@vielzeug/ore";var p=[`flat`,`bordered`,`ghost`],m=`ore-copy-command`;s(m,{props:{rounded:u.oneOf(n,`md`),size:u.oneOf(t,`md`),value:u.string(``),variant:u.oneOf(p,`flat`)},setup(e){let t=d(),n=f(),r=o(!1),i=o(!1),s=null;l(()=>{s!==null&&clearTimeout(s)});let handleCopy=async()=>{let n=e.value.value??``;if(n){s!==null&&clearTimeout(s);try{await navigator.clipboard.writeText(n),r.value=!0,i.value=!1,t(`copy`,{value:n})}catch{r.value=!1,i.value=!0}s=setTimeout(()=>{r.value=!1,i.value=!1,s=null},2e3)}},u=a(()=>n.has(`suffix`).value),p=a(()=>r.value?`Copied!`:i.value?`Copy failed: press to try again`:`Copy: ${e.value.value??``}`);return c`
2
2
  <div class="wrapper" part="wrapper">
3
3
  <button class="command" part="command" type="button" aria-label="${p}" @click=${handleCopy}>
4
4
  <code class="command-text" part="command-text">${e.value}</code>
5
5
  <span class="copy-icon" part="copy-icon" aria-hidden="true">
6
- ${()=>a.value?o`
6
+ ${()=>r.value?c`
7
7
  <ore-icon name="check" size="14" class="icon-copied" aria-hidden="true"></ore-icon>
8
- `:c.value?o`
8
+ `:i.value?c`
9
9
  <ore-icon name="circle-alert" size="14" class="icon-failed" aria-hidden="true"></ore-icon>
10
- `:o`
10
+ `:c`
11
11
  <ore-icon name="copy" size="14" aria-hidden="true"></ore-icon>
12
12
  `}
13
13
  </span>
14
14
  </button>
15
- <div class="suffix" part="suffix" ?hidden="${()=>!f.value}">
15
+ <div class="suffix" part="suffix" ?hidden="${()=>!u.value}">
16
16
  <slot name="suffix"></slot>
17
17
  </div>
18
18
  </div>
19
19
  <div role="status" class="sr-only">
20
- ${()=>a.value?`Copied to clipboard.`:c.value?`Copy failed. Select the command text and copy it manually.`:``}
20
+ ${()=>r.value?`Copied to clipboard.`:i.value?`Copy failed. Select the command text and copy it manually.`:``}
21
21
  </div>
22
- `},styles:[e,n]});export{d as COPY_COMMAND_TAG,t as ICON_TAG};
22
+ `},styles:[e,i]});export{m as COPY_COMMAND_TAG,r as ICON_TAG};
23
23
  //# sourceMappingURL=copy-command.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"copy-command.js","names":[],"sources":["../src/content/copy-command/copy-command.ts"],"sourcesContent":["import { define, html, onCleanup, prop, useEmit, useSlots } from '@vielzeug/ore';\nimport { computed, signal } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\n\nimport componentStyles from './copy-command.css?inline';\n\nexport { ICON_TAG } from '../icon/icon';\n/** Copy command component properties */\nexport type OreCopyCommandProps = {\n /** Border radius size */\n rounded?: RoundedSize;\n /** Component size */\n size?: ComponentSize;\n /** The command string to display and copy to clipboard */\n value?: string;\n /** Visual style variant */\n variant?: 'flat' | 'bordered' | 'ghost';\n};\n\n/** Copy command component events */\nexport type OreCopyCommandEvents = {\n /** Emitted when the command is successfully copied to the clipboard */\n copy: { value: string };\n};\n\n/**\n * A styled, copy-to-clipboard command display component.\n * Shows a command string in a monospace code block with a copy button.\n * Clicking the component copies the value to the clipboard, shows a transient\n * check-mark confirmation, and emits a `copy` event.\n *\n * @element ore-copy-command\n *\n * @attr {string} value - The command string to display and copy\n * @attr {string} size - Component size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual style: 'flat' | 'bordered' | 'ghost'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n *\n * @fires copy - Emitted on successful clipboard write. detail: { value: string }\n *\n * @slot suffix - Optional controls appended after the copy button (e.g. a cycle button)\n *\n * @cssprop --copy-command-bg - Background color of the command block\n * @cssprop --copy-command-color - Text color of the command string\n * @cssprop --copy-command-border-color - Border color\n * @cssprop --copy-command-radius - Border radius (overrides the `rounded` attribute)\n * @cssprop --copy-command-padding - Inner padding of the command button\n * @cssprop --copy-command-font-size - Font size of the command string\n * @cssprop --copy-command-icon-color - Color of the copy/check icon\n * @cssprop --copy-command-hover-bg - Background on hover\n *\n * @part wrapper - Outer flex container\n * @part command - The copy button element\n * @part command-text - The `<code>` element displaying the command string\n * @part copy-icon - The icon wrapper span\n * @part suffix - Suffix slot container\n *\n * @example\n * ```html\n * <ore-copy-command value=\"npm install @vielzeug/ripple\"></ore-copy-command>\n *\n * <ore-copy-command value=\"npx -y @vielzeug/codex\" size=\"sm\" variant=\"bordered\"></ore-copy-command>\n *\n * <!-- With a suffix slot (e.g. a cycle button) -->\n * <ore-copy-command value=\"npm install @vielzeug/ripple\">\n * <button slot=\"suffix\" type=\"button\" aria-label=\"Next package\">›</button>\n * </ore-copy-command>\n * ```\n */\nexport const COPY_COMMAND_TAG = 'ore-copy-command' as const;\n\ndefine<OreCopyCommandProps>(COPY_COMMAND_TAG, {\n props: {\n rounded: prop.string<RoundedSize>('md'),\n size: prop.string<ComponentSize>('md'),\n value: prop.string(''),\n variant: prop.string<'flat' | 'bordered' | 'ghost'>('flat'),\n },\n\n setup(props) {\n const emit = useEmit<OreCopyCommandEvents>();\n const slots = useSlots();\n\n const copied = signal(false);\n const copyFailed = signal(false);\n let resetTimer: ReturnType<typeof setTimeout> | null = null;\n\n onCleanup(() => {\n if (resetTimer !== null) clearTimeout(resetTimer);\n });\n\n const handleCopy = async () => {\n const text = props.value.value ?? '';\n\n if (!text) return;\n\n if (resetTimer !== null) clearTimeout(resetTimer);\n\n try {\n await navigator.clipboard.writeText(text);\n copied.value = true;\n copyFailed.value = false;\n emit('copy', { value: text });\n } catch {\n // Clipboard access denied or unavailable (e.g. insecure context): surface it instead of failing silently\n copied.value = false;\n copyFailed.value = true;\n }\n\n resetTimer = setTimeout(() => {\n copied.value = false;\n copyFailed.value = false;\n resetTimer = null;\n }, 2000);\n };\n\n const hasSuffix = computed(() => slots.has('suffix').value);\n const btnLabel = computed(() => {\n if (copied.value) return 'Copied!';\n\n if (copyFailed.value) return 'Copy failed: press to try again';\n\n return `Copy: ${props.value.value ?? ''}`;\n });\n\n return html`\n <div class=\"wrapper\" part=\"wrapper\">\n <button class=\"command\" part=\"command\" type=\"button\" aria-label=\"${btnLabel}\" @click=${handleCopy}>\n <code class=\"command-text\" part=\"command-text\">${props.value}</code>\n <span class=\"copy-icon\" part=\"copy-icon\" aria-hidden=\"true\">\n ${() => {\n if (copied.value)\n return html`\n <ore-icon name=\"check\" size=\"14\" class=\"icon-copied\" aria-hidden=\"true\"></ore-icon>\n `;\n\n if (copyFailed.value)\n return html`\n <ore-icon name=\"circle-alert\" size=\"14\" class=\"icon-failed\" aria-hidden=\"true\"></ore-icon>\n `;\n\n return html`\n <ore-icon name=\"copy\" size=\"14\" aria-hidden=\"true\"></ore-icon>\n `;\n }}\n </span>\n </button>\n <div class=\"suffix\" part=\"suffix\" ?hidden=\"${() => !hasSuffix.value}\">\n <slot name=\"suffix\"></slot>\n </div>\n </div>\n <div role=\"status\" class=\"sr-only\">\n ${() => {\n if (copied.value) return 'Copied to clipboard.';\n\n if (copyFailed.value) return 'Copy failed. Select the command text and copy it manually.';\n\n return '';\n }}\n </div>\n `;\n },\n\n styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"+TAsEA,IAAa,EAAmB,mBAEhC,EAA4B,EAAkB,CAC5C,MAAO,CACL,QAAS,EAAK,OAAoB,IAAI,EACtC,KAAM,EAAK,OAAsB,IAAI,EACrC,MAAO,EAAK,OAAO,EAAE,EACrB,QAAS,EAAK,OAAsC,MAAM,CAC5D,EAEA,MAAM,EAAO,CACX,IAAM,EAAO,EAA8B,EACrC,EAAQ,EAAS,EAEjB,EAAS,EAAO,EAAK,EACrB,EAAa,EAAO,EAAK,EAC3B,EAAmD,KAEvD,MAAgB,CACV,IAAe,MAAM,aAAa,CAAU,CAClD,CAAC,EAED,IAAM,WAAa,SAAY,CAC7B,IAAM,EAAO,EAAM,MAAM,OAAS,GAE7B,KAEL,CAAI,IAAe,MAAM,aAAa,CAAU,EAEhD,GAAI,CACF,MAAM,UAAU,UAAU,UAAU,CAAI,EACxC,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAK,OAAQ,CAAE,MAAO,CAAK,CAAC,CAC9B,MAAQ,CAEN,EAAO,MAAQ,GACf,EAAW,MAAQ,EACrB,CAEA,EAAa,eAAiB,CAC5B,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAa,IACf,EAAG,GAAI,CAjByC,CAkBlD,EAEM,EAAY,MAAe,EAAM,IAAI,QAAQ,CAAC,CAAC,KAAK,EACpD,EAAW,MACX,EAAO,MAAc,UAErB,EAAW,MAAc,kCAEtB,SAAS,EAAM,MAAM,OAAS,IACtC,EAED,MAAO,EAAI;;2EAE4D,EAAS,WAAW,WAAW;2DAC/C,EAAM,MAAM;;kBAGrD,EAAO,MACF,CAAI;;kBAIT,EAAW,MACN,CAAI;;kBAIN,CAAI;;gBAGX;;;yDAG6C,CAAC,EAAU,MAAM;;;;;cAM9D,EAAO,MAAc,uBAErB,EAAW,MAAc,6DAEtB,GACP;;KAGR,EAEA,OAAQ,CAAC,EAAa,CAAe,CACvC,CAAC"}
1
+ {"version":3,"file":"copy-command.js","names":[],"sources":["../src/content/copy-command/copy-command.ts"],"sourcesContent":["import { define, html, onCleanup, prop, useEmit, useSlots } from '@vielzeug/ore';\nimport { computed, signal } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\nimport { COMPONENT_SIZES, type ComponentSize, ROUNDED_SIZES, type RoundedSize } from '../../types';\n\nconst COPY_COMMAND_VARIANTS = ['flat', 'bordered', 'ghost'] as const;\n\nimport componentStyles from './copy-command.css?inline';\n\nexport { ICON_TAG } from '../icon/icon';\n/** Copy command component properties */\nexport type OreCopyCommandProps = {\n /** Border radius size */\n rounded?: RoundedSize;\n /** Component size */\n size?: ComponentSize;\n /** The command string to display and copy to clipboard */\n value?: string;\n /** Visual style variant */\n variant?: 'flat' | 'bordered' | 'ghost';\n};\n\n/** Copy command component events */\nexport type OreCopyCommandEvents = {\n /** Emitted when the command is successfully copied to the clipboard */\n copy: { value: string };\n};\n\n/**\n * A styled, copy-to-clipboard command display component.\n * Shows a command string in a monospace code block with a copy button.\n * Clicking the component copies the value to the clipboard, shows a transient\n * check-mark confirmation, and emits a `copy` event.\n *\n * @element ore-copy-command\n *\n * @attr {string} value - The command string to display and copy\n * @attr {string} size - Component size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual style: 'flat' | 'bordered' | 'ghost'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n *\n * @fires copy - Emitted on successful clipboard write. detail: { value: string }\n *\n * @slot suffix - Optional controls appended after the copy button (e.g. a cycle button)\n *\n * @cssprop --copy-command-bg - Background color of the command block\n * @cssprop --copy-command-color - Text color of the command string\n * @cssprop --copy-command-border-color - Border color\n * @cssprop --copy-command-radius - Border radius (overrides the `rounded` attribute)\n * @cssprop --copy-command-padding - Inner padding of the command button\n * @cssprop --copy-command-font-size - Font size of the command string\n * @cssprop --copy-command-icon-color - Color of the copy/check icon\n * @cssprop --copy-command-hover-bg - Background on hover\n *\n * @part wrapper - Outer flex container\n * @part command - The copy button element\n * @part command-text - The `<code>` element displaying the command string\n * @part copy-icon - The icon wrapper span\n * @part suffix - Suffix slot container\n *\n * @example\n * ```html\n * <ore-copy-command value=\"npm install @vielzeug/ripple\"></ore-copy-command>\n *\n * <ore-copy-command value=\"npx -y @vielzeug/codex\" size=\"sm\" variant=\"bordered\"></ore-copy-command>\n *\n * <!-- With a suffix slot (e.g. a cycle button) -->\n * <ore-copy-command value=\"npm install @vielzeug/ripple\">\n * <button slot=\"suffix\" type=\"button\" aria-label=\"Next package\">›</button>\n * </ore-copy-command>\n * ```\n */\nexport const COPY_COMMAND_TAG = 'ore-copy-command' as const;\n\ndefine<OreCopyCommandProps>(COPY_COMMAND_TAG, {\n props: {\n rounded: prop.oneOf(ROUNDED_SIZES, 'md'),\n size: prop.oneOf(COMPONENT_SIZES, 'md'),\n value: prop.string(''),\n variant: prop.oneOf(COPY_COMMAND_VARIANTS, 'flat'),\n },\n\n setup(props) {\n const emit = useEmit<OreCopyCommandEvents>();\n const slots = useSlots();\n\n const copied = signal(false);\n const copyFailed = signal(false);\n let resetTimer: ReturnType<typeof setTimeout> | null = null;\n\n onCleanup(() => {\n if (resetTimer !== null) clearTimeout(resetTimer);\n });\n\n const handleCopy = async () => {\n const text = props.value.value ?? '';\n\n if (!text) return;\n\n if (resetTimer !== null) clearTimeout(resetTimer);\n\n try {\n await navigator.clipboard.writeText(text);\n copied.value = true;\n copyFailed.value = false;\n emit('copy', { value: text });\n } catch {\n // Clipboard access denied or unavailable (e.g. insecure context): surface it instead of failing silently\n copied.value = false;\n copyFailed.value = true;\n }\n\n resetTimer = setTimeout(() => {\n copied.value = false;\n copyFailed.value = false;\n resetTimer = null;\n }, 2000);\n };\n\n const hasSuffix = computed(() => slots.has('suffix').value);\n const btnLabel = computed(() => {\n if (copied.value) return 'Copied!';\n\n if (copyFailed.value) return 'Copy failed: press to try again';\n\n return `Copy: ${props.value.value ?? ''}`;\n });\n\n return html`\n <div class=\"wrapper\" part=\"wrapper\">\n <button class=\"command\" part=\"command\" type=\"button\" aria-label=\"${btnLabel}\" @click=${handleCopy}>\n <code class=\"command-text\" part=\"command-text\">${props.value}</code>\n <span class=\"copy-icon\" part=\"copy-icon\" aria-hidden=\"true\">\n ${() => {\n if (copied.value)\n return html`\n <ore-icon name=\"check\" size=\"14\" class=\"icon-copied\" aria-hidden=\"true\"></ore-icon>\n `;\n\n if (copyFailed.value)\n return html`\n <ore-icon name=\"circle-alert\" size=\"14\" class=\"icon-failed\" aria-hidden=\"true\"></ore-icon>\n `;\n\n return html`\n <ore-icon name=\"copy\" size=\"14\" aria-hidden=\"true\"></ore-icon>\n `;\n }}\n </span>\n </button>\n <div class=\"suffix\" part=\"suffix\" ?hidden=\"${() => !hasSuffix.value}\">\n <slot name=\"suffix\"></slot>\n </div>\n </div>\n <div role=\"status\" class=\"sr-only\">\n ${() => {\n if (copied.value) return 'Copied to clipboard.';\n\n if (copyFailed.value) return 'Copy failed. Select the command text and copy it manually.';\n\n return '';\n }}\n </div>\n `;\n },\n\n styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"sYAKA,IAAM,EAAwB,CAAC,OAAQ,WAAY,OAAO,EAmE7C,EAAmB,mBAEhC,EAA4B,EAAkB,CAC5C,MAAO,CACL,QAAS,EAAK,MAAM,EAAe,IAAI,EACvC,KAAM,EAAK,MAAM,EAAiB,IAAI,EACtC,MAAO,EAAK,OAAO,EAAE,EACrB,QAAS,EAAK,MAAM,EAAuB,MAAM,CACnD,EAEA,MAAM,EAAO,CACX,IAAM,EAAO,EAA8B,EACrC,EAAQ,EAAS,EAEjB,EAAS,EAAO,EAAK,EACrB,EAAa,EAAO,EAAK,EAC3B,EAAmD,KAEvD,MAAgB,CACV,IAAe,MAAM,aAAa,CAAU,CAClD,CAAC,EAED,IAAM,WAAa,SAAY,CAC7B,IAAM,EAAO,EAAM,MAAM,OAAS,GAE7B,KAEL,CAAI,IAAe,MAAM,aAAa,CAAU,EAEhD,GAAI,CACF,MAAM,UAAU,UAAU,UAAU,CAAI,EACxC,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAK,OAAQ,CAAE,MAAO,CAAK,CAAC,CAC9B,MAAQ,CAEN,EAAO,MAAQ,GACf,EAAW,MAAQ,EACrB,CAEA,EAAa,eAAiB,CAC5B,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAa,IACf,EAAG,GAAI,CAjByC,CAkBlD,EAEM,EAAY,MAAe,EAAM,IAAI,QAAQ,CAAC,CAAC,KAAK,EACpD,EAAW,MACX,EAAO,MAAc,UAErB,EAAW,MAAc,kCAEtB,SAAS,EAAM,MAAM,OAAS,IACtC,EAED,MAAO,EAAI;;2EAE4D,EAAS,WAAW,WAAW;2DAC/C,EAAM,MAAM;;kBAGrD,EAAO,MACF,CAAI;;kBAIT,EAAW,MACN,CAAI;;kBAIN,CAAI;;gBAGX;;;yDAG6C,CAAC,EAAU,MAAM;;;;;cAM9D,EAAO,MAAc,uBAErB,EAAW,MAAc,6DAEtB,GACP;;KAGR,EAEA,OAAQ,CAAC,EAAa,CAAe,CACvC,CAAC"}
@@ -1,2 +1,2 @@
1
- const e=require("./field-base.cjs"),t=require("./keyboard.cjs"),n=require("./signals.cjs");let r=require("@vielzeug/ripple");var createCheckable=i=>{let a=n.syncedSignal(i.checked,i.signal,Boolean),o=i.indeterminate?n.syncedSignal(i.indeterminate,i.signal,Boolean):(0,r.signal)(!1),s=a.value,c=o.value,l=e.createDirtyTracker(),u=(0,r.computed)(()=>o.value?null:a.value?i.value.value??``:null),d=e.createField(i),f=(0,r.computed)(()=>i.required?.value&&(!a.value||o.value)?{valueMissing:!0}:null),p=(0,r.computed)(()=>f.value?i.requiredMessage?.value||`This field is required.`:``),reset=()=>{a.value=l.isDirty?s:!!i.checked.value,o.value=l.isDirty?c:!!i.indeterminate?.value,l.clear(),d.triggerValidation(`change`)},createPayload=e=>({checked:a.value,originalEvent:e,value:i.value.value??``}),toggle=e=>{if(!d.disabled.value){if(l.markDirty(),i.group){o.value=!1,i.group.toggle(i.value.value??``,e),i.onToggle?.(createPayload(e));return}i.clearIndeterminateFirst&&o.value||(a.value=!a.value),o.value=!1,i.onToggle?.(createPayload(e))}},m=t.createInteraction({disabled:d.disabled,onPress:e=>toggle(e)});return{...d,checkableFormValue:u,checked:a,handleClick:m.handleClick,handleKeydown:m.handleKeydown,indeterminate:o,reset,toggle,validationMessage:p,validity:f}};exports.createCheckable=createCheckable;
1
+ const e=require("./field-base.cjs"),t=require("./keyboard.cjs"),n=require("./signals.cjs");let r=require("@vielzeug/ripple");var createCheckable=i=>{let a=n.syncedSignal(i.checked,i.signal,Boolean),o=i.indeterminate?n.syncedSignal(i.indeterminate,i.signal,Boolean):(0,r.signal)(!1),s=a.value,c=o.value,l=e.createDirtyTracker(),u=(0,r.computed)(()=>o.value?null:a.value?i.value.value??``:null),d=e.createField(i),f=(0,r.computed)(()=>i.required?.value&&(!a.value||o.value)?{flags:{valueMissing:!0},message:i.requiredMessage?.value||`This field is required.`}:null),reset=()=>{a.value=l.isDirty?s:!!i.checked.value,o.value=l.isDirty?c:!!i.indeterminate?.value,l.clear(),d.triggerValidation(`change`)},createPayload=e=>({checked:a.value,originalEvent:e,value:i.value.value??``}),toggle=e=>{if(!d.disabled.value){if(l.markDirty(),i.group){o.value=!1,i.group.toggle(i.value.value??``,e),i.onToggle?.(createPayload(e));return}i.clearIndeterminateFirst&&o.value||(a.value=!a.value),o.value=!1,i.onToggle?.(createPayload(e))}},p=t.createInteraction({disabled:d.disabled,onPress:e=>toggle(e)});return{...d,checkableFormValue:u,checked:a,handleClick:p.handleClick,handleKeydown:p.handleKeydown,indeterminate:o,reset,toggle,validity:f}};exports.createCheckable=createCheckable;
2
2
  //# sourceMappingURL=checkable.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkable.cjs","names":[],"sources":["../../src/core/checkable.ts"],"sourcesContent":["import { computed, type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { type ControlValidationMode, createDirtyTracker, createField, type FieldHandle } from './field-base';\nimport { createInteraction } from './keyboard';\nimport { syncedSignal } from './signals';\n\n// ── Checkable control ─────────────────────────────────────────────────────────\n\nexport type CheckableChangePayload = {\n checked: boolean;\n originalEvent?: Event;\n value: string;\n};\n\nexport type CheckableOptions = {\n checked: Readable<boolean | undefined>;\n clearIndeterminateFirst?: boolean;\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n group?: { toggle: (value: string, originalEvent?: Event) => void };\n helper?: Readable<string | undefined>;\n indeterminate?: Readable<boolean | undefined>;\n onToggle?: (payload: CheckableChangePayload) => void;\n prefix?: string;\n /** Marks an unchecked control invalid: feeds `validity`/`validationMessage` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the unchecked+required case. Defaults to `'This field is required.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. All reactive subscriptions are torn down on abort. */\n signal: AbortSignal;\n validateOn?: Readable<ControlValidationMode>;\n value: Readable<string | undefined>;\n};\n\nexport type CheckableHandle = FieldHandle & {\n /** The form submission value: null when unchecked/indeterminate, the value string when checked. */\n checkableFormValue: Readable<string | null>;\n checked: Signal<boolean>;\n handleClick: (event: MouseEvent) => boolean;\n handleKeydown: (event: KeyboardEvent) => boolean;\n indeterminate: Signal<boolean>;\n /**\n * Restores `checked`/`indeterminate` to their native \"default\" value. See `DirtyTracker`\n * for the two-state rationale. Wire into `useField({ onReset: checkable.reset })`.\n */\n reset: () => void;\n toggle: (event: Event) => void;\n /** Reactive validation message paired with `validity`. Empty string when valid. */\n validationMessage: Readable<string>;\n /**\n * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and unchecked\n * (indeterminate counts as unchecked), `null` (valid) otherwise. Pass straight to\n * `useField({ validity: checkable.validity })`.\n */\n validity: Readable<ValidityStateFlags | null>;\n};\n\nexport const createCheckable = (options: CheckableOptions): CheckableHandle => {\n const checked = syncedSignal(options.checked, options.signal, Boolean);\n const indeterminate = options.indeterminate\n ? syncedSignal(options.indeterminate, options.signal, Boolean)\n : signal(false);\n const initialChecked = checked.value;\n const initialIndeterminate = indeterminate.value;\n const dirtyTracker = createDirtyTracker();\n\n const checkableFormValue = computed<string | null>(() =>\n indeterminate.value ? null : checked.value ? (options.value.value ?? '') : null,\n );\n\n const field = createField(options);\n\n const validity = computed<ValidityStateFlags | null>(() =>\n options.required?.value && (!checked.value || indeterminate.value) ? { valueMissing: true } : null,\n );\n const validationMessage = computed(() =>\n validity.value ? options.requiredMessage?.value || 'This field is required.' : '',\n );\n\n const reset = (): void => {\n checked.value = dirtyTracker.isDirty ? initialChecked : Boolean(options.checked.value);\n indeterminate.value = dirtyTracker.isDirty ? initialIndeterminate : Boolean(options.indeterminate?.value);\n dirtyTracker.clear();\n field.triggerValidation('change');\n };\n\n const createPayload = (event: Event): CheckableChangePayload => ({\n checked: checked.value,\n originalEvent: event,\n value: options.value.value ?? '',\n });\n\n const toggle = (event: Event): void => {\n if (field.disabled.value) return;\n\n dirtyTracker.markDirty();\n\n if (options.group) {\n indeterminate.value = false;\n options.group.toggle(options.value.value ?? '', event);\n options.onToggle?.(createPayload(event));\n\n return;\n }\n\n if (options.clearIndeterminateFirst && indeterminate.value) {\n indeterminate.value = false;\n } else {\n checked.value = !checked.value;\n indeterminate.value = false;\n }\n\n options.onToggle?.(createPayload(event));\n };\n\n const interaction = createInteraction({\n disabled: field.disabled,\n onPress: (event) => toggle(event),\n });\n\n return {\n ...field,\n checkableFormValue,\n checked,\n handleClick: interaction.handleClick,\n handleKeydown: interaction.handleKeydown,\n indeterminate,\n reset,\n toggle,\n validationMessage,\n validity,\n };\n};\n"],"mappings":"6HAyDA,IAAa,gBAAmB,GAA+C,CAC7E,IAAM,EAAU,EAAA,aAAa,EAAQ,QAAS,EAAQ,OAAQ,OAAO,EAC/D,EAAgB,EAAQ,cAC1B,EAAA,aAAa,EAAQ,cAAe,EAAQ,OAAQ,OAAO,GAAA,EAC3D,EAAA,OAAA,CAAO,EAAK,EACV,EAAiB,EAAQ,MACzB,EAAuB,EAAc,MACrC,EAAe,EAAA,mBAAmB,EAElC,GAAA,EAAqB,EAAA,SAAA,KACzB,EAAc,MAAQ,KAAO,EAAQ,MAAS,EAAQ,MAAM,OAAS,GAAM,IAC7E,EAEM,EAAQ,EAAA,YAAY,CAAO,EAE3B,GAAA,EAAW,EAAA,SAAA,KACf,EAAQ,UAAU,QAAU,CAAC,EAAQ,OAAS,EAAc,OAAS,CAAE,aAAc,EAAK,EAAI,IAChG,EACM,GAAA,EAAoB,EAAA,SAAA,KACxB,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,0BAA4B,EACjF,EAEM,UAAoB,CACxB,EAAQ,MAAQ,EAAa,QAAU,EAAiB,EAAQ,EAAQ,QAAQ,MAChF,EAAc,MAAQ,EAAa,QAAU,EAAuB,EAAQ,EAAQ,eAAe,MACnG,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEM,cAAiB,IAA0C,CAC/D,QAAS,EAAQ,MACjB,cAAe,EACf,MAAO,EAAQ,MAAM,OAAS,EAChC,GAEM,OAAU,GAAuB,CACjC,MAAM,SAAS,MAInB,IAFA,EAAa,UAAU,EAEnB,EAAQ,MAAO,CACjB,EAAc,MAAQ,GACtB,EAAQ,MAAM,OAAO,EAAQ,MAAM,OAAS,GAAI,CAAK,EACrD,EAAQ,WAAW,cAAc,CAAK,CAAC,EAEvC,MACF,CAEI,EAAQ,yBAA2B,EAAc,QAGnD,EAAQ,MAAQ,CAAC,EAAQ,OAFzB,EAAc,MAAQ,GAMxB,EAAQ,WAAW,cAAc,CAAK,CAAC,CATvC,CAUF,EAEM,EAAc,EAAA,kBAAkB,CACpC,SAAU,EAAM,SAChB,QAAU,GAAU,OAAO,CAAK,CAClC,CAAC,EAED,MAAO,CACL,GAAG,EACH,qBACA,UACA,YAAa,EAAY,YACzB,cAAe,EAAY,cAC3B,gBACA,MACA,OACA,oBACA,UACF,CACF"}
1
+ {"version":3,"file":"checkable.cjs","names":[],"sources":["../../src/core/checkable.ts"],"sourcesContent":["import type { FormFieldValidity } from '@vielzeug/ore';\nimport { computed, type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { type ControlValidationMode, createDirtyTracker, createField, type FieldHandle } from './field-base';\nimport { createInteraction } from './keyboard';\nimport { syncedSignal } from './signals';\n\n// ── Checkable control ─────────────────────────────────────────────────────────\n\nexport type CheckableChangePayload = {\n checked: boolean;\n originalEvent?: Event;\n value: string;\n};\n\nexport type CheckableOptions = {\n checked: Readable<boolean | undefined>;\n clearIndeterminateFirst?: boolean;\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n group?: { toggle: (value: string, originalEvent?: Event) => void };\n helper?: Readable<string | undefined>;\n indeterminate?: Readable<boolean | undefined>;\n onToggle?: (payload: CheckableChangePayload) => void;\n prefix?: string;\n /** Marks an unchecked control invalid: feeds `validity` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the unchecked+required case. Defaults to `'This field is required.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. All reactive subscriptions are torn down on abort. */\n signal: AbortSignal;\n validateOn?: Readable<ControlValidationMode>;\n value: Readable<string | undefined>;\n};\n\nexport type CheckableHandle = FieldHandle & {\n /** The form submission value: null when unchecked/indeterminate, the value string when checked. */\n checkableFormValue: Readable<string | null>;\n checked: Signal<boolean>;\n handleClick: (event: MouseEvent) => boolean;\n handleKeydown: (event: KeyboardEvent) => boolean;\n indeterminate: Signal<boolean>;\n /**\n * Restores `checked`/`indeterminate` to their native \"default\" value. See `DirtyTracker`\n * for the two-state rationale. Wire into `useField({ onReset: checkable.reset })`.\n */\n reset: () => void;\n toggle: (event: Event) => void;\n /**\n * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`\n * and unchecked (indeterminate counts as unchecked), `null` (valid) otherwise. Pass\n * straight to `useField({ validity: checkable.validity })`.\n */\n validity: Readable<FormFieldValidity | null>;\n};\n\nexport const createCheckable = (options: CheckableOptions): CheckableHandle => {\n const checked = syncedSignal(options.checked, options.signal, Boolean);\n const indeterminate = options.indeterminate\n ? syncedSignal(options.indeterminate, options.signal, Boolean)\n : signal(false);\n const initialChecked = checked.value;\n const initialIndeterminate = indeterminate.value;\n const dirtyTracker = createDirtyTracker();\n\n const checkableFormValue = computed<string | null>(() =>\n indeterminate.value ? null : checked.value ? (options.value.value ?? '') : null,\n );\n\n const field = createField(options);\n\n const validity = computed<FormFieldValidity | null>(() =>\n options.required?.value && (!checked.value || indeterminate.value)\n ? { flags: { valueMissing: true }, message: options.requiredMessage?.value || 'This field is required.' }\n : null,\n );\n\n const reset = (): void => {\n checked.value = dirtyTracker.isDirty ? initialChecked : Boolean(options.checked.value);\n indeterminate.value = dirtyTracker.isDirty ? initialIndeterminate : Boolean(options.indeterminate?.value);\n dirtyTracker.clear();\n field.triggerValidation('change');\n };\n\n const createPayload = (event: Event): CheckableChangePayload => ({\n checked: checked.value,\n originalEvent: event,\n value: options.value.value ?? '',\n });\n\n const toggle = (event: Event): void => {\n if (field.disabled.value) return;\n\n dirtyTracker.markDirty();\n\n if (options.group) {\n indeterminate.value = false;\n options.group.toggle(options.value.value ?? '', event);\n options.onToggle?.(createPayload(event));\n\n return;\n }\n\n if (options.clearIndeterminateFirst && indeterminate.value) {\n indeterminate.value = false;\n } else {\n checked.value = !checked.value;\n indeterminate.value = false;\n }\n\n options.onToggle?.(createPayload(event));\n };\n\n const interaction = createInteraction({\n disabled: field.disabled,\n onPress: (event) => toggle(event),\n });\n\n return {\n ...field,\n checkableFormValue,\n checked,\n handleClick: interaction.handleClick,\n handleKeydown: interaction.handleKeydown,\n indeterminate,\n reset,\n toggle,\n validity,\n };\n};\n"],"mappings":"6HAwDA,IAAa,gBAAmB,GAA+C,CAC7E,IAAM,EAAU,EAAA,aAAa,EAAQ,QAAS,EAAQ,OAAQ,OAAO,EAC/D,EAAgB,EAAQ,cAC1B,EAAA,aAAa,EAAQ,cAAe,EAAQ,OAAQ,OAAO,GAAA,EAC3D,EAAA,OAAA,CAAO,EAAK,EACV,EAAiB,EAAQ,MACzB,EAAuB,EAAc,MACrC,EAAe,EAAA,mBAAmB,EAElC,GAAA,EAAqB,EAAA,SAAA,KACzB,EAAc,MAAQ,KAAO,EAAQ,MAAS,EAAQ,MAAM,OAAS,GAAM,IAC7E,EAEM,EAAQ,EAAA,YAAY,CAAO,EAE3B,GAAA,EAAW,EAAA,SAAA,KACf,EAAQ,UAAU,QAAU,CAAC,EAAQ,OAAS,EAAc,OACxD,CAAE,MAAO,CAAE,aAAc,EAAK,EAAG,QAAS,EAAQ,iBAAiB,OAAS,yBAA0B,EACtG,IACN,EAEM,UAAoB,CACxB,EAAQ,MAAQ,EAAa,QAAU,EAAiB,EAAQ,EAAQ,QAAQ,MAChF,EAAc,MAAQ,EAAa,QAAU,EAAuB,EAAQ,EAAQ,eAAe,MACnG,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEM,cAAiB,IAA0C,CAC/D,QAAS,EAAQ,MACjB,cAAe,EACf,MAAO,EAAQ,MAAM,OAAS,EAChC,GAEM,OAAU,GAAuB,CACjC,MAAM,SAAS,MAInB,IAFA,EAAa,UAAU,EAEnB,EAAQ,MAAO,CACjB,EAAc,MAAQ,GACtB,EAAQ,MAAM,OAAO,EAAQ,MAAM,OAAS,GAAI,CAAK,EACrD,EAAQ,WAAW,cAAc,CAAK,CAAC,EAEvC,MACF,CAEI,EAAQ,yBAA2B,EAAc,QAGnD,EAAQ,MAAQ,CAAC,EAAQ,OAFzB,EAAc,MAAQ,GAMxB,EAAQ,WAAW,cAAc,CAAK,CAAC,CATvC,CAUF,EAEM,EAAc,EAAA,kBAAkB,CACpC,SAAU,EAAM,SAChB,QAAU,GAAU,OAAO,CAAK,CAClC,CAAC,EAED,MAAO,CACL,GAAG,EACH,qBACA,UACA,YAAa,EAAY,YACzB,cAAe,EAAY,cAC3B,gBACA,MACA,OACA,UACF,CACF"}
@@ -1,3 +1,4 @@
1
+ import type { FormFieldValidity } from '@vielzeug/ore';
1
2
  import { type Readable, type Signal } from '@vielzeug/ripple';
2
3
  import { type ControlValidationMode, type FieldHandle } from './field-base';
3
4
  export type CheckableChangePayload = {
@@ -17,7 +18,7 @@ export type CheckableOptions = {
17
18
  indeterminate?: Readable<boolean | undefined>;
18
19
  onToggle?: (payload: CheckableChangePayload) => void;
19
20
  prefix?: string;
20
- /** Marks an unchecked control invalid: feeds `validity`/`validationMessage` (see below). */
21
+ /** Marks an unchecked control invalid: feeds `validity` (see below). */
21
22
  required?: Readable<boolean | undefined>;
22
23
  /** Message for the unchecked+required case. Defaults to `'This field is required.'`. */
23
24
  requiredMessage?: Readable<string | undefined>;
@@ -39,14 +40,12 @@ export type CheckableHandle = FieldHandle & {
39
40
  */
40
41
  reset: () => void;
41
42
  toggle: (event: Event) => void;
42
- /** Reactive validation message paired with `validity`. Empty string when valid. */
43
- validationMessage: Readable<string>;
44
43
  /**
45
- * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and unchecked
46
- * (indeterminate counts as unchecked), `null` (valid) otherwise. Pass straight to
47
- * `useField({ validity: checkable.validity })`.
44
+ * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`
45
+ * and unchecked (indeterminate counts as unchecked), `null` (valid) otherwise. Pass
46
+ * straight to `useField({ validity: checkable.validity })`.
48
47
  */
49
- validity: Readable<ValidityStateFlags | null>;
48
+ validity: Readable<FormFieldValidity | null>;
50
49
  };
51
50
  export declare const createCheckable: (options: CheckableOptions) => CheckableHandle;
52
51
  //# sourceMappingURL=checkable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkable.d.ts","sourceRoot":"","sources":["../../src/core/checkable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAU,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAE,KAAK,qBAAqB,EAAmC,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAM7G,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACvC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,KAAK,KAAK,IAAI,CAAA;KAAE,CAAC;IACnE,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,wFAAwF;IACxF,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/C,qGAAqG;IACrG,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IAC7C,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C,mGAAmG;IACnG,kBAAkB,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,WAAW,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC;IAC5C,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;IACjD,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B;;;OAGG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,mFAAmF;IACnF,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC;;;;OAIG;IACH,QAAQ,EAAE,QAAQ,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,eA2E3D,CAAC"}
1
+ {"version":3,"file":"checkable.d.ts","sourceRoot":"","sources":["../../src/core/checkable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAY,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAU,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAE,KAAK,qBAAqB,EAAmC,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAM7G,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACvC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,KAAK,KAAK,IAAI,CAAA;KAAE,CAAC;IACnE,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,wFAAwF;IACxF,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/C,qGAAqG;IACrG,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IAC7C,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C,mGAAmG;IACnG,kBAAkB,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,WAAW,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC;IAC5C,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;IACjD,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B;;;OAGG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,eAyE3D,CAAC"}
@@ -1,2 +1,2 @@
1
- import{createDirtyTracker as e,createField as t}from"./field-base.js";import{createInteraction as n}from"./keyboard.js";import{syncedSignal as r}from"./signals.js";import{computed as i,signal as a}from"@vielzeug/ripple";var createCheckable=o=>{let s=r(o.checked,o.signal,Boolean),c=o.indeterminate?r(o.indeterminate,o.signal,Boolean):a(!1),l=s.value,u=c.value,d=e(),f=i(()=>c.value?null:s.value?o.value.value??``:null),p=t(o),m=i(()=>o.required?.value&&(!s.value||c.value)?{valueMissing:!0}:null),h=i(()=>m.value?o.requiredMessage?.value||`This field is required.`:``),reset=()=>{s.value=d.isDirty?l:!!o.checked.value,c.value=d.isDirty?u:!!o.indeterminate?.value,d.clear(),p.triggerValidation(`change`)},createPayload=e=>({checked:s.value,originalEvent:e,value:o.value.value??``}),toggle=e=>{if(!p.disabled.value){if(d.markDirty(),o.group){c.value=!1,o.group.toggle(o.value.value??``,e),o.onToggle?.(createPayload(e));return}o.clearIndeterminateFirst&&c.value||(s.value=!s.value),c.value=!1,o.onToggle?.(createPayload(e))}},g=n({disabled:p.disabled,onPress:e=>toggle(e)});return{...p,checkableFormValue:f,checked:s,handleClick:g.handleClick,handleKeydown:g.handleKeydown,indeterminate:c,reset,toggle,validationMessage:h,validity:m}};export{createCheckable};
1
+ import{createDirtyTracker as e,createField as t}from"./field-base.js";import{createInteraction as n}from"./keyboard.js";import{syncedSignal as r}from"./signals.js";import{computed as i,signal as a}from"@vielzeug/ripple";var createCheckable=o=>{let s=r(o.checked,o.signal,Boolean),c=o.indeterminate?r(o.indeterminate,o.signal,Boolean):a(!1),l=s.value,u=c.value,d=e(),f=i(()=>c.value?null:s.value?o.value.value??``:null),p=t(o),m=i(()=>o.required?.value&&(!s.value||c.value)?{flags:{valueMissing:!0},message:o.requiredMessage?.value||`This field is required.`}:null),reset=()=>{s.value=d.isDirty?l:!!o.checked.value,c.value=d.isDirty?u:!!o.indeterminate?.value,d.clear(),p.triggerValidation(`change`)},createPayload=e=>({checked:s.value,originalEvent:e,value:o.value.value??``}),toggle=e=>{if(!p.disabled.value){if(d.markDirty(),o.group){c.value=!1,o.group.toggle(o.value.value??``,e),o.onToggle?.(createPayload(e));return}o.clearIndeterminateFirst&&c.value||(s.value=!s.value),c.value=!1,o.onToggle?.(createPayload(e))}},h=n({disabled:p.disabled,onPress:e=>toggle(e)});return{...p,checkableFormValue:f,checked:s,handleClick:h.handleClick,handleKeydown:h.handleKeydown,indeterminate:c,reset,toggle,validity:m}};export{createCheckable};
2
2
  //# sourceMappingURL=checkable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkable.js","names":[],"sources":["../../src/core/checkable.ts"],"sourcesContent":["import { computed, type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { type ControlValidationMode, createDirtyTracker, createField, type FieldHandle } from './field-base';\nimport { createInteraction } from './keyboard';\nimport { syncedSignal } from './signals';\n\n// ── Checkable control ─────────────────────────────────────────────────────────\n\nexport type CheckableChangePayload = {\n checked: boolean;\n originalEvent?: Event;\n value: string;\n};\n\nexport type CheckableOptions = {\n checked: Readable<boolean | undefined>;\n clearIndeterminateFirst?: boolean;\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n group?: { toggle: (value: string, originalEvent?: Event) => void };\n helper?: Readable<string | undefined>;\n indeterminate?: Readable<boolean | undefined>;\n onToggle?: (payload: CheckableChangePayload) => void;\n prefix?: string;\n /** Marks an unchecked control invalid: feeds `validity`/`validationMessage` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the unchecked+required case. Defaults to `'This field is required.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. All reactive subscriptions are torn down on abort. */\n signal: AbortSignal;\n validateOn?: Readable<ControlValidationMode>;\n value: Readable<string | undefined>;\n};\n\nexport type CheckableHandle = FieldHandle & {\n /** The form submission value: null when unchecked/indeterminate, the value string when checked. */\n checkableFormValue: Readable<string | null>;\n checked: Signal<boolean>;\n handleClick: (event: MouseEvent) => boolean;\n handleKeydown: (event: KeyboardEvent) => boolean;\n indeterminate: Signal<boolean>;\n /**\n * Restores `checked`/`indeterminate` to their native \"default\" value. See `DirtyTracker`\n * for the two-state rationale. Wire into `useField({ onReset: checkable.reset })`.\n */\n reset: () => void;\n toggle: (event: Event) => void;\n /** Reactive validation message paired with `validity`. Empty string when valid. */\n validationMessage: Readable<string>;\n /**\n * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and unchecked\n * (indeterminate counts as unchecked), `null` (valid) otherwise. Pass straight to\n * `useField({ validity: checkable.validity })`.\n */\n validity: Readable<ValidityStateFlags | null>;\n};\n\nexport const createCheckable = (options: CheckableOptions): CheckableHandle => {\n const checked = syncedSignal(options.checked, options.signal, Boolean);\n const indeterminate = options.indeterminate\n ? syncedSignal(options.indeterminate, options.signal, Boolean)\n : signal(false);\n const initialChecked = checked.value;\n const initialIndeterminate = indeterminate.value;\n const dirtyTracker = createDirtyTracker();\n\n const checkableFormValue = computed<string | null>(() =>\n indeterminate.value ? null : checked.value ? (options.value.value ?? '') : null,\n );\n\n const field = createField(options);\n\n const validity = computed<ValidityStateFlags | null>(() =>\n options.required?.value && (!checked.value || indeterminate.value) ? { valueMissing: true } : null,\n );\n const validationMessage = computed(() =>\n validity.value ? options.requiredMessage?.value || 'This field is required.' : '',\n );\n\n const reset = (): void => {\n checked.value = dirtyTracker.isDirty ? initialChecked : Boolean(options.checked.value);\n indeterminate.value = dirtyTracker.isDirty ? initialIndeterminate : Boolean(options.indeterminate?.value);\n dirtyTracker.clear();\n field.triggerValidation('change');\n };\n\n const createPayload = (event: Event): CheckableChangePayload => ({\n checked: checked.value,\n originalEvent: event,\n value: options.value.value ?? '',\n });\n\n const toggle = (event: Event): void => {\n if (field.disabled.value) return;\n\n dirtyTracker.markDirty();\n\n if (options.group) {\n indeterminate.value = false;\n options.group.toggle(options.value.value ?? '', event);\n options.onToggle?.(createPayload(event));\n\n return;\n }\n\n if (options.clearIndeterminateFirst && indeterminate.value) {\n indeterminate.value = false;\n } else {\n checked.value = !checked.value;\n indeterminate.value = false;\n }\n\n options.onToggle?.(createPayload(event));\n };\n\n const interaction = createInteraction({\n disabled: field.disabled,\n onPress: (event) => toggle(event),\n });\n\n return {\n ...field,\n checkableFormValue,\n checked,\n handleClick: interaction.handleClick,\n handleKeydown: interaction.handleKeydown,\n indeterminate,\n reset,\n toggle,\n validationMessage,\n validity,\n };\n};\n"],"mappings":"4NAyDA,IAAa,gBAAmB,GAA+C,CAC7E,IAAM,EAAU,EAAa,EAAQ,QAAS,EAAQ,OAAQ,OAAO,EAC/D,EAAgB,EAAQ,cAC1B,EAAa,EAAQ,cAAe,EAAQ,OAAQ,OAAO,EAC3D,EAAO,EAAK,EACV,EAAiB,EAAQ,MACzB,EAAuB,EAAc,MACrC,EAAe,EAAmB,EAElC,EAAqB,MACzB,EAAc,MAAQ,KAAO,EAAQ,MAAS,EAAQ,MAAM,OAAS,GAAM,IAC7E,EAEM,EAAQ,EAAY,CAAO,EAE3B,EAAW,MACf,EAAQ,UAAU,QAAU,CAAC,EAAQ,OAAS,EAAc,OAAS,CAAE,aAAc,EAAK,EAAI,IAChG,EACM,EAAoB,MACxB,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,0BAA4B,EACjF,EAEM,UAAoB,CACxB,EAAQ,MAAQ,EAAa,QAAU,EAAiB,EAAQ,EAAQ,QAAQ,MAChF,EAAc,MAAQ,EAAa,QAAU,EAAuB,EAAQ,EAAQ,eAAe,MACnG,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEM,cAAiB,IAA0C,CAC/D,QAAS,EAAQ,MACjB,cAAe,EACf,MAAO,EAAQ,MAAM,OAAS,EAChC,GAEM,OAAU,GAAuB,CACjC,MAAM,SAAS,MAInB,IAFA,EAAa,UAAU,EAEnB,EAAQ,MAAO,CACjB,EAAc,MAAQ,GACtB,EAAQ,MAAM,OAAO,EAAQ,MAAM,OAAS,GAAI,CAAK,EACrD,EAAQ,WAAW,cAAc,CAAK,CAAC,EAEvC,MACF,CAEI,EAAQ,yBAA2B,EAAc,QAGnD,EAAQ,MAAQ,CAAC,EAAQ,OAFzB,EAAc,MAAQ,GAMxB,EAAQ,WAAW,cAAc,CAAK,CAAC,CATvC,CAUF,EAEM,EAAc,EAAkB,CACpC,SAAU,EAAM,SAChB,QAAU,GAAU,OAAO,CAAK,CAClC,CAAC,EAED,MAAO,CACL,GAAG,EACH,qBACA,UACA,YAAa,EAAY,YACzB,cAAe,EAAY,cAC3B,gBACA,MACA,OACA,oBACA,UACF,CACF"}
1
+ {"version":3,"file":"checkable.js","names":[],"sources":["../../src/core/checkable.ts"],"sourcesContent":["import type { FormFieldValidity } from '@vielzeug/ore';\nimport { computed, type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { type ControlValidationMode, createDirtyTracker, createField, type FieldHandle } from './field-base';\nimport { createInteraction } from './keyboard';\nimport { syncedSignal } from './signals';\n\n// ── Checkable control ─────────────────────────────────────────────────────────\n\nexport type CheckableChangePayload = {\n checked: boolean;\n originalEvent?: Event;\n value: string;\n};\n\nexport type CheckableOptions = {\n checked: Readable<boolean | undefined>;\n clearIndeterminateFirst?: boolean;\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n group?: { toggle: (value: string, originalEvent?: Event) => void };\n helper?: Readable<string | undefined>;\n indeterminate?: Readable<boolean | undefined>;\n onToggle?: (payload: CheckableChangePayload) => void;\n prefix?: string;\n /** Marks an unchecked control invalid: feeds `validity` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the unchecked+required case. Defaults to `'This field is required.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. All reactive subscriptions are torn down on abort. */\n signal: AbortSignal;\n validateOn?: Readable<ControlValidationMode>;\n value: Readable<string | undefined>;\n};\n\nexport type CheckableHandle = FieldHandle & {\n /** The form submission value: null when unchecked/indeterminate, the value string when checked. */\n checkableFormValue: Readable<string | null>;\n checked: Signal<boolean>;\n handleClick: (event: MouseEvent) => boolean;\n handleKeydown: (event: KeyboardEvent) => boolean;\n indeterminate: Signal<boolean>;\n /**\n * Restores `checked`/`indeterminate` to their native \"default\" value. See `DirtyTracker`\n * for the two-state rationale. Wire into `useField({ onReset: checkable.reset })`.\n */\n reset: () => void;\n toggle: (event: Event) => void;\n /**\n * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`\n * and unchecked (indeterminate counts as unchecked), `null` (valid) otherwise. Pass\n * straight to `useField({ validity: checkable.validity })`.\n */\n validity: Readable<FormFieldValidity | null>;\n};\n\nexport const createCheckable = (options: CheckableOptions): CheckableHandle => {\n const checked = syncedSignal(options.checked, options.signal, Boolean);\n const indeterminate = options.indeterminate\n ? syncedSignal(options.indeterminate, options.signal, Boolean)\n : signal(false);\n const initialChecked = checked.value;\n const initialIndeterminate = indeterminate.value;\n const dirtyTracker = createDirtyTracker();\n\n const checkableFormValue = computed<string | null>(() =>\n indeterminate.value ? null : checked.value ? (options.value.value ?? '') : null,\n );\n\n const field = createField(options);\n\n const validity = computed<FormFieldValidity | null>(() =>\n options.required?.value && (!checked.value || indeterminate.value)\n ? { flags: { valueMissing: true }, message: options.requiredMessage?.value || 'This field is required.' }\n : null,\n );\n\n const reset = (): void => {\n checked.value = dirtyTracker.isDirty ? initialChecked : Boolean(options.checked.value);\n indeterminate.value = dirtyTracker.isDirty ? initialIndeterminate : Boolean(options.indeterminate?.value);\n dirtyTracker.clear();\n field.triggerValidation('change');\n };\n\n const createPayload = (event: Event): CheckableChangePayload => ({\n checked: checked.value,\n originalEvent: event,\n value: options.value.value ?? '',\n });\n\n const toggle = (event: Event): void => {\n if (field.disabled.value) return;\n\n dirtyTracker.markDirty();\n\n if (options.group) {\n indeterminate.value = false;\n options.group.toggle(options.value.value ?? '', event);\n options.onToggle?.(createPayload(event));\n\n return;\n }\n\n if (options.clearIndeterminateFirst && indeterminate.value) {\n indeterminate.value = false;\n } else {\n checked.value = !checked.value;\n indeterminate.value = false;\n }\n\n options.onToggle?.(createPayload(event));\n };\n\n const interaction = createInteraction({\n disabled: field.disabled,\n onPress: (event) => toggle(event),\n });\n\n return {\n ...field,\n checkableFormValue,\n checked,\n handleClick: interaction.handleClick,\n handleKeydown: interaction.handleKeydown,\n indeterminate,\n reset,\n toggle,\n validity,\n };\n};\n"],"mappings":"4NAwDA,IAAa,gBAAmB,GAA+C,CAC7E,IAAM,EAAU,EAAa,EAAQ,QAAS,EAAQ,OAAQ,OAAO,EAC/D,EAAgB,EAAQ,cAC1B,EAAa,EAAQ,cAAe,EAAQ,OAAQ,OAAO,EAC3D,EAAO,EAAK,EACV,EAAiB,EAAQ,MACzB,EAAuB,EAAc,MACrC,EAAe,EAAmB,EAElC,EAAqB,MACzB,EAAc,MAAQ,KAAO,EAAQ,MAAS,EAAQ,MAAM,OAAS,GAAM,IAC7E,EAEM,EAAQ,EAAY,CAAO,EAE3B,EAAW,MACf,EAAQ,UAAU,QAAU,CAAC,EAAQ,OAAS,EAAc,OACxD,CAAE,MAAO,CAAE,aAAc,EAAK,EAAG,QAAS,EAAQ,iBAAiB,OAAS,yBAA0B,EACtG,IACN,EAEM,UAAoB,CACxB,EAAQ,MAAQ,EAAa,QAAU,EAAiB,EAAQ,EAAQ,QAAQ,MAChF,EAAc,MAAQ,EAAa,QAAU,EAAuB,EAAQ,EAAQ,eAAe,MACnG,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEM,cAAiB,IAA0C,CAC/D,QAAS,EAAQ,MACjB,cAAe,EACf,MAAO,EAAQ,MAAM,OAAS,EAChC,GAEM,OAAU,GAAuB,CACjC,MAAM,SAAS,MAInB,IAFA,EAAa,UAAU,EAEnB,EAAQ,MAAO,CACjB,EAAc,MAAQ,GACtB,EAAQ,MAAM,OAAO,EAAQ,MAAM,OAAS,GAAI,CAAK,EACrD,EAAQ,WAAW,cAAc,CAAK,CAAC,EAEvC,MACF,CAEI,EAAQ,yBAA2B,EAAc,QAGnD,EAAQ,MAAQ,CAAC,EAAQ,OAFzB,EAAc,MAAQ,GAMxB,EAAQ,WAAW,cAAc,CAAK,CAAC,CATvC,CAUF,EAEM,EAAc,EAAkB,CACpC,SAAU,EAAM,SAChB,QAAU,GAAU,OAAO,CAAK,CAClC,CAAC,EAED,MAAO,CACL,GAAG,EACH,qBACA,UACA,YAAa,EAAY,YACzB,cAAe,EAAY,cAC3B,gBACA,MACA,OACA,UACF,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("./field-base.cjs"),t=require("./signals.cjs");let n=require("@vielzeug/ripple");var parseChoiceValues=e=>e?Array.isArray(e)?e.filter(Boolean):e.split(`,`).map(e=>e.trim()).filter(Boolean):[],createChoiceField=r=>{let i=(0,n.computed)(()=>!!r.multiple?.value),normalizeValues=e=>{let t=i.value?e:e.slice(0,1);return[...new Set(t.filter(Boolean))]},a=t.syncedSignal(r.value,r.signal,e=>normalizeValues(parseChoiceValues(e))),o=(0,n.computed)(()=>i.value?a.value.join(`,`):a.value[0]??``),s=e.createDirtyTracker(),applyValues=e=>{a.value=normalizeValues(e)},setValues=e=>{s.markDirty(),applyValues(e)},clear=()=>setValues([]),removeValue=e=>{s.markDirty(),a.value=a.value.filter(t=>t!==e)},selectValue=e=>{if(i.value){a.value.includes(e)||setValues([...a.value,e]);return}setValues([e])},toggleValue=e=>{i.value&&a.value.includes(e)?removeValue(e):selectValue(e)},syncFromProp=e=>{applyValues(parseChoiceValues(e))},c=e.createField(r),l=(0,n.computed)(()=>r.required?.value&&a.value.length===0?{valueMissing:!0}:null),u=(0,n.computed)(()=>l.value?r.requiredMessage?.value||`Please make a selection.`:``);if(r.multiple){let e=(0,n.watch)(r.multiple,()=>syncFromProp(r.value.value));r.signal.addEventListener(`abort`,()=>e.dispose(),{once:!0})}let d=a.value,reset=()=>{applyValues(s.isDirty?d:parseChoiceValues(r.value.value)),s.clear(),c.triggerValidation(`change`)};return{...c,clear,formValue:o,removeValue,reset,selectedValue:(0,n.computed)(()=>a.value[0]),selectedValues:a,selectValue,setValues,toggleValue,validationMessage:u,validity:l}};exports.createChoiceField=createChoiceField;
1
+ const e=require("./field-base.cjs"),t=require("./signals.cjs");let n=require("@vielzeug/ripple");var parseChoiceValues=e=>e?Array.isArray(e)?e.filter(Boolean):e.split(`,`).map(e=>e.trim()).filter(Boolean):[],createChoiceField=r=>{let i=(0,n.computed)(()=>!!r.multiple?.value),normalizeValues=e=>{let t=i.value?e:e.slice(0,1);return[...new Set(t.filter(Boolean))]},a=t.syncedSignal(r.value,r.signal,e=>normalizeValues(parseChoiceValues(e))),o=(0,n.computed)(()=>i.value?a.value.join(`,`):a.value[0]??``),s=e.createDirtyTracker(),applyValues=e=>{a.value=normalizeValues(e)},setValues=e=>{s.markDirty(),applyValues(e)},clear=()=>setValues([]),removeValue=e=>{s.markDirty(),a.value=a.value.filter(t=>t!==e)},selectValue=e=>{if(i.value){a.value.includes(e)||setValues([...a.value,e]);return}setValues([e])},toggleValue=e=>{i.value&&a.value.includes(e)?removeValue(e):selectValue(e)},syncFromProp=e=>{applyValues(parseChoiceValues(e))},c=e.createField(r),l=(0,n.computed)(()=>r.required?.value&&a.value.length===0?{flags:{valueMissing:!0},message:r.requiredMessage?.value||`Please make a selection.`}:null);if(r.multiple){let e=(0,n.watch)(r.multiple,()=>syncFromProp(r.value.value));r.signal.addEventListener(`abort`,()=>e.dispose(),{once:!0})}let u=a.value,reset=()=>{applyValues(s.isDirty?u:parseChoiceValues(r.value.value)),s.clear(),c.triggerValidation(`change`)};return{...c,clear,formValue:o,removeValue,reset,selectedValue:(0,n.computed)(()=>a.value[0]),selectedValues:a,selectValue,setValues,toggleValue,validity:l}};exports.createChoiceField=createChoiceField;
2
2
  //# sourceMappingURL=choice-field.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"choice-field.cjs","names":[],"sources":["../../src/core/choice-field.ts"],"sourcesContent":["import { computed, type Readable, watch } from '@vielzeug/ripple';\n\nimport { createDirtyTracker, createField, type FieldHandle, type FieldOptions } from './field-base';\nimport { syncedSignal } from './signals';\n\n// ── Choice change event payload ──────────────────────────────────────────────────────────────\n\nexport type ChoiceChangeDetail = {\n labels: string[];\n originalEvent?: Event;\n values: string[];\n};\n\n// ── Choice field ──────────────────────────────────────────────────────────────────────\n\nexport type ChoiceFieldOptions = FieldOptions & {\n multiple?: Readable<boolean | undefined>;\n /** Marks an empty selection invalid: feeds `validity`/`validationMessage` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the empty-selection+required case. Defaults to `'Please make a selection.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. All internal subscriptions are disposed on abort. */\n signal: AbortSignal;\n /**\n * Current selection value(s). Accepts a comma-separated string\n * (legacy/single-select form serialisation) or a `string[]` array.\n * The field normalises both formats internally via `parseChoiceValues`.\n */\n value: Readable<string | string[] | undefined>;\n};\n\nexport type ChoiceFieldHandle = FieldHandle & {\n clear: () => void;\n formValue: Readable<string>;\n removeValue: (value: string) => void;\n /**\n * Restores the selection to its native \"default\". See `DirtyTracker` for the two-state\n * rationale.\n */\n reset: () => void;\n selectedValue: Readable<string | undefined>;\n selectedValues: Readable<string[]>;\n selectValue: (value: string) => void;\n setValues: (values: string[]) => void;\n /**\n * Toggles `value` in the selection.\n * - **Multiple mode**: adds if absent, removes if present.\n * - **Single mode**: always selects `value` (acts like `selectValue`: cannot\n * deselect; use `clear()` to reset a single-select field).\n */\n toggleValue: (value: string) => void;\n /** Reactive validation message paired with `validity`. Empty string when valid. */\n validationMessage: Readable<string>;\n /**\n * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and no option is\n * selected, `null` (valid) otherwise. Pass straight to `useField({ validity: choice.validity })`.\n */\n validity: Readable<ValidityStateFlags | null>;\n};\n\nconst parseChoiceValues = (raw: string | string[] | undefined): string[] => {\n if (!raw) return [];\n\n if (Array.isArray(raw)) return raw.filter(Boolean);\n\n return raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n};\n\nexport const createChoiceField = (options: ChoiceFieldOptions): ChoiceFieldHandle => {\n const isMultiple = computed(() => Boolean(options.multiple?.value));\n\n const normalizeValues = (values: string[]): string[] => {\n const normalized = isMultiple.value ? values : values.slice(0, 1);\n\n return [...new Set(normalized.filter(Boolean))];\n };\n\n // Kept in sync with `options.value` via the shared helper (see `checkable.ts`/`text-field.ts`\n // for the same pattern) rather than a hand-rolled signal + watch pair.\n const selectedValues = syncedSignal(options.value, options.signal, (raw) => normalizeValues(parseChoiceValues(raw)));\n const formValue = computed(() =>\n isMultiple.value ? selectedValues.value.join(',') : (selectedValues.value[0] ?? ''),\n );\n\n const dirtyTracker = createDirtyTracker();\n\n // The actual state setter, shared by every mutation path. Deliberately *not* where the dirty\n // tracker gets marked: `syncFromProp` (below) calls this directly to stay exempt from it,\n // since mirroring an external prop change is never \"the user changed the selection\".\n const applyValues = (values: string[]): void => {\n selectedValues.value = normalizeValues(values);\n };\n\n const setValues = (values: string[]): void => {\n dirtyTracker.markDirty();\n applyValues(values);\n };\n const clear = (): void => setValues([]);\n const removeValue = (value: string): void => {\n dirtyTracker.markDirty();\n selectedValues.value = selectedValues.value.filter((v) => v !== value);\n };\n\n const selectValue = (value: string): void => {\n if (isMultiple.value) {\n if (!selectedValues.value.includes(value)) setValues([...selectedValues.value, value]);\n\n return;\n }\n\n setValues([value]);\n };\n\n const toggleValue = (value: string): void => {\n if (isMultiple.value) {\n if (selectedValues.value.includes(value)) removeValue(value);\n else selectValue(value);\n } else {\n selectValue(value);\n }\n };\n\n const syncFromProp = (nextValue: string | string[] | undefined): void => {\n applyValues(parseChoiceValues(nextValue));\n };\n\n const field = createField(options);\n\n const validity = computed<ValidityStateFlags | null>(() =>\n options.required?.value && selectedValues.value.length === 0 ? { valueMissing: true } : null,\n );\n const validationMessage = computed(() =>\n validity.value ? options.requiredMessage?.value || 'Please make a selection.' : '',\n );\n\n if (options.multiple) {\n const multipleSub = watch(options.multiple, () => syncFromProp(options.value.value));\n\n options.signal.addEventListener('abort', () => multipleSub.dispose(), { once: true });\n }\n\n const initialValues = selectedValues.value;\n const reset = (): void => {\n applyValues(dirtyTracker.isDirty ? initialValues : parseChoiceValues(options.value.value));\n dirtyTracker.clear();\n field.triggerValidation('change');\n };\n\n return {\n ...field,\n clear,\n formValue,\n removeValue,\n reset,\n selectedValue: computed(() => selectedValues.value[0]),\n selectedValues,\n selectValue,\n setValues,\n toggleValue,\n validationMessage,\n validity,\n };\n};\n"],"mappings":"iGA4DA,IAAM,kBAAqB,GACpB,EAED,MAAM,QAAQ,CAAG,EAAU,EAAI,OAAO,OAAO,EAE1C,EACJ,MAAM,GAAG,CAAC,CACV,IAAK,GAAU,EAAM,KAAK,CAAC,CAAC,CAC5B,OAAO,OAAO,EAPA,CAAC,EAUP,kBAAqB,GAAmD,CACnF,IAAM,GAAA,EAAa,EAAA,SAAA,KAAe,EAAQ,EAAQ,UAAU,KAAM,EAE5D,gBAAmB,GAA+B,CACtD,IAAM,EAAa,EAAW,MAAQ,EAAS,EAAO,MAAM,EAAG,CAAC,EAEhE,MAAO,CAAC,GAAG,IAAI,IAAI,EAAW,OAAO,OAAO,CAAC,CAAC,CAChD,EAIM,EAAiB,EAAA,aAAa,EAAQ,MAAO,EAAQ,OAAS,GAAQ,gBAAgB,kBAAkB,CAAG,CAAC,CAAC,EAC7G,GAAA,EAAY,EAAA,SAAA,KAChB,EAAW,MAAQ,EAAe,MAAM,KAAK,GAAG,EAAK,EAAe,MAAM,IAAM,EAClF,EAEM,EAAe,EAAA,mBAAmB,EAKlC,YAAe,GAA2B,CAC9C,EAAe,MAAQ,gBAAgB,CAAM,CAC/C,EAEM,UAAa,GAA2B,CAC5C,EAAa,UAAU,EACvB,YAAY,CAAM,CACpB,EACM,UAAoB,UAAU,CAAC,CAAC,EAChC,YAAe,GAAwB,CAC3C,EAAa,UAAU,EACvB,EAAe,MAAQ,EAAe,MAAM,OAAQ,GAAM,IAAM,CAAK,CACvE,EAEM,YAAe,GAAwB,CAC3C,GAAI,EAAW,MAAO,CACf,EAAe,MAAM,SAAS,CAAK,GAAG,UAAU,CAAC,GAAG,EAAe,MAAO,CAAK,CAAC,EAErF,MACF,CAEA,UAAU,CAAC,CAAK,CAAC,CACnB,EAEM,YAAe,GAAwB,CACvC,EAAW,OACT,EAAe,MAAM,SAAS,CAAK,EAAG,YAAY,CAAK,EACtD,YAAY,CAAK,CAI1B,EAEM,aAAgB,GAAmD,CACvE,YAAY,kBAAkB,CAAS,CAAC,CAC1C,EAEM,EAAQ,EAAA,YAAY,CAAO,EAE3B,GAAA,EAAW,EAAA,SAAA,KACf,EAAQ,UAAU,OAAS,EAAe,MAAM,SAAW,EAAI,CAAE,aAAc,EAAK,EAAI,IAC1F,EACM,GAAA,EAAoB,EAAA,SAAA,KACxB,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,2BAA6B,EAClF,EAEA,GAAI,EAAQ,SAAU,CACpB,IAAM,GAAA,EAAc,EAAA,MAAA,CAAM,EAAQ,aAAgB,aAAa,EAAQ,MAAM,KAAK,CAAC,EAEnF,EAAQ,OAAO,iBAAiB,YAAe,EAAY,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,CACtF,CAEA,IAAM,EAAgB,EAAe,MAC/B,UAAoB,CACxB,YAAY,EAAa,QAAU,EAAgB,kBAAkB,EAAQ,MAAM,KAAK,CAAC,EACzF,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEA,MAAO,CACL,GAAG,EACH,MACA,YACA,YACA,MACA,eAAA,EAAe,EAAA,SAAA,KAAe,EAAe,MAAM,EAAE,EACrD,iBACA,YACA,UACA,YACA,oBACA,UACF,CACF"}
1
+ {"version":3,"file":"choice-field.cjs","names":[],"sources":["../../src/core/choice-field.ts"],"sourcesContent":["import type { FormFieldValidity } from '@vielzeug/ore';\nimport { computed, type Readable, watch } from '@vielzeug/ripple';\n\nimport { createDirtyTracker, createField, type FieldHandle, type FieldOptions } from './field-base';\nimport { syncedSignal } from './signals';\n\n// ── Choice change event payload ──────────────────────────────────────────────────────────────\n\nexport type ChoiceChangeDetail = {\n labels: string[];\n originalEvent?: Event;\n values: string[];\n};\n\n// ── Choice field ──────────────────────────────────────────────────────────────────────\n\nexport type ChoiceFieldOptions = FieldOptions & {\n multiple?: Readable<boolean | undefined>;\n /** Marks an empty selection invalid: feeds `validity` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the empty-selection+required case. Defaults to `'Please make a selection.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. All internal subscriptions are disposed on abort. */\n signal: AbortSignal;\n /**\n * Current selection value(s). Accepts a comma-separated string\n * (legacy/single-select form serialisation) or a `string[]` array.\n * The field normalises both formats internally via `parseChoiceValues`.\n */\n value: Readable<string | string[] | undefined>;\n};\n\nexport type ChoiceFieldHandle = FieldHandle & {\n clear: () => void;\n formValue: Readable<string>;\n removeValue: (value: string) => void;\n /**\n * Restores the selection to its native \"default\". See `DirtyTracker` for the two-state\n * rationale.\n */\n reset: () => void;\n selectedValue: Readable<string | undefined>;\n selectedValues: Readable<string[]>;\n selectValue: (value: string) => void;\n setValues: (values: string[]) => void;\n /**\n * Toggles `value` in the selection.\n * - **Multiple mode**: adds if absent, removes if present.\n * - **Single mode**: always selects `value` (acts like `selectValue`: cannot\n * deselect; use `clear()` to reset a single-select field).\n */\n toggleValue: (value: string) => void;\n /**\n * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`\n * and no option is selected, `null` (valid) otherwise. Pass straight to\n * `useField({ validity: choice.validity })`.\n */\n validity: Readable<FormFieldValidity | null>;\n};\n\nconst parseChoiceValues = (raw: string | string[] | undefined): string[] => {\n if (!raw) return [];\n\n if (Array.isArray(raw)) return raw.filter(Boolean);\n\n return raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n};\n\nexport const createChoiceField = (options: ChoiceFieldOptions): ChoiceFieldHandle => {\n const isMultiple = computed(() => Boolean(options.multiple?.value));\n\n const normalizeValues = (values: string[]): string[] => {\n const normalized = isMultiple.value ? values : values.slice(0, 1);\n\n return [...new Set(normalized.filter(Boolean))];\n };\n\n // Kept in sync with `options.value` via the shared helper (see `checkable.ts`/`text-field.ts`\n // for the same pattern) rather than a hand-rolled signal + watch pair.\n const selectedValues = syncedSignal(options.value, options.signal, (raw) => normalizeValues(parseChoiceValues(raw)));\n const formValue = computed(() =>\n isMultiple.value ? selectedValues.value.join(',') : (selectedValues.value[0] ?? ''),\n );\n\n const dirtyTracker = createDirtyTracker();\n\n // The actual state setter, shared by every mutation path. Deliberately *not* where the dirty\n // tracker gets marked: `syncFromProp` (below) calls this directly to stay exempt from it,\n // since mirroring an external prop change is never \"the user changed the selection\".\n const applyValues = (values: string[]): void => {\n selectedValues.value = normalizeValues(values);\n };\n\n const setValues = (values: string[]): void => {\n dirtyTracker.markDirty();\n applyValues(values);\n };\n const clear = (): void => setValues([]);\n const removeValue = (value: string): void => {\n dirtyTracker.markDirty();\n selectedValues.value = selectedValues.value.filter((v) => v !== value);\n };\n\n const selectValue = (value: string): void => {\n if (isMultiple.value) {\n if (!selectedValues.value.includes(value)) setValues([...selectedValues.value, value]);\n\n return;\n }\n\n setValues([value]);\n };\n\n const toggleValue = (value: string): void => {\n if (isMultiple.value) {\n if (selectedValues.value.includes(value)) removeValue(value);\n else selectValue(value);\n } else {\n selectValue(value);\n }\n };\n\n const syncFromProp = (nextValue: string | string[] | undefined): void => {\n applyValues(parseChoiceValues(nextValue));\n };\n\n const field = createField(options);\n\n const validity = computed<FormFieldValidity | null>(() =>\n options.required?.value && selectedValues.value.length === 0\n ? { flags: { valueMissing: true }, message: options.requiredMessage?.value || 'Please make a selection.' }\n : null,\n );\n\n if (options.multiple) {\n const multipleSub = watch(options.multiple, () => syncFromProp(options.value.value));\n\n options.signal.addEventListener('abort', () => multipleSub.dispose(), { once: true });\n }\n\n const initialValues = selectedValues.value;\n const reset = (): void => {\n applyValues(dirtyTracker.isDirty ? initialValues : parseChoiceValues(options.value.value));\n dirtyTracker.clear();\n field.triggerValidation('change');\n };\n\n return {\n ...field,\n clear,\n formValue,\n removeValue,\n reset,\n selectedValue: computed(() => selectedValues.value[0]),\n selectedValues,\n selectValue,\n setValues,\n toggleValue,\n validity,\n };\n};\n"],"mappings":"iGA4DA,IAAM,kBAAqB,GACpB,EAED,MAAM,QAAQ,CAAG,EAAU,EAAI,OAAO,OAAO,EAE1C,EACJ,MAAM,GAAG,CAAC,CACV,IAAK,GAAU,EAAM,KAAK,CAAC,CAAC,CAC5B,OAAO,OAAO,EAPA,CAAC,EAUP,kBAAqB,GAAmD,CACnF,IAAM,GAAA,EAAa,EAAA,SAAA,KAAe,EAAQ,EAAQ,UAAU,KAAM,EAE5D,gBAAmB,GAA+B,CACtD,IAAM,EAAa,EAAW,MAAQ,EAAS,EAAO,MAAM,EAAG,CAAC,EAEhE,MAAO,CAAC,GAAG,IAAI,IAAI,EAAW,OAAO,OAAO,CAAC,CAAC,CAChD,EAIM,EAAiB,EAAA,aAAa,EAAQ,MAAO,EAAQ,OAAS,GAAQ,gBAAgB,kBAAkB,CAAG,CAAC,CAAC,EAC7G,GAAA,EAAY,EAAA,SAAA,KAChB,EAAW,MAAQ,EAAe,MAAM,KAAK,GAAG,EAAK,EAAe,MAAM,IAAM,EAClF,EAEM,EAAe,EAAA,mBAAmB,EAKlC,YAAe,GAA2B,CAC9C,EAAe,MAAQ,gBAAgB,CAAM,CAC/C,EAEM,UAAa,GAA2B,CAC5C,EAAa,UAAU,EACvB,YAAY,CAAM,CACpB,EACM,UAAoB,UAAU,CAAC,CAAC,EAChC,YAAe,GAAwB,CAC3C,EAAa,UAAU,EACvB,EAAe,MAAQ,EAAe,MAAM,OAAQ,GAAM,IAAM,CAAK,CACvE,EAEM,YAAe,GAAwB,CAC3C,GAAI,EAAW,MAAO,CACf,EAAe,MAAM,SAAS,CAAK,GAAG,UAAU,CAAC,GAAG,EAAe,MAAO,CAAK,CAAC,EAErF,MACF,CAEA,UAAU,CAAC,CAAK,CAAC,CACnB,EAEM,YAAe,GAAwB,CACvC,EAAW,OACT,EAAe,MAAM,SAAS,CAAK,EAAG,YAAY,CAAK,EACtD,YAAY,CAAK,CAI1B,EAEM,aAAgB,GAAmD,CACvE,YAAY,kBAAkB,CAAS,CAAC,CAC1C,EAEM,EAAQ,EAAA,YAAY,CAAO,EAE3B,GAAA,EAAW,EAAA,SAAA,KACf,EAAQ,UAAU,OAAS,EAAe,MAAM,SAAW,EACvD,CAAE,MAAO,CAAE,aAAc,EAAK,EAAG,QAAS,EAAQ,iBAAiB,OAAS,0BAA2B,EACvG,IACN,EAEA,GAAI,EAAQ,SAAU,CACpB,IAAM,GAAA,EAAc,EAAA,MAAA,CAAM,EAAQ,aAAgB,aAAa,EAAQ,MAAM,KAAK,CAAC,EAEnF,EAAQ,OAAO,iBAAiB,YAAe,EAAY,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,CACtF,CAEA,IAAM,EAAgB,EAAe,MAC/B,UAAoB,CACxB,YAAY,EAAa,QAAU,EAAgB,kBAAkB,EAAQ,MAAM,KAAK,CAAC,EACzF,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEA,MAAO,CACL,GAAG,EACH,MACA,YACA,YACA,MACA,eAAA,EAAe,EAAA,SAAA,KAAe,EAAe,MAAM,EAAE,EACrD,iBACA,YACA,UACA,YACA,UACF,CACF"}
@@ -1,3 +1,4 @@
1
+ import type { FormFieldValidity } from '@vielzeug/ore';
1
2
  import { type Readable } from '@vielzeug/ripple';
2
3
  import { type FieldHandle, type FieldOptions } from './field-base';
3
4
  export type ChoiceChangeDetail = {
@@ -7,7 +8,7 @@ export type ChoiceChangeDetail = {
7
8
  };
8
9
  export type ChoiceFieldOptions = FieldOptions & {
9
10
  multiple?: Readable<boolean | undefined>;
10
- /** Marks an empty selection invalid: feeds `validity`/`validationMessage` (see below). */
11
+ /** Marks an empty selection invalid: feeds `validity` (see below). */
11
12
  required?: Readable<boolean | undefined>;
12
13
  /** Message for the empty-selection+required case. Defaults to `'Please make a selection.'`. */
13
14
  requiredMessage?: Readable<string | undefined>;
@@ -40,13 +41,12 @@ export type ChoiceFieldHandle = FieldHandle & {
40
41
  * deselect; use `clear()` to reset a single-select field).
41
42
  */
42
43
  toggleValue: (value: string) => void;
43
- /** Reactive validation message paired with `validity`. Empty string when valid. */
44
- validationMessage: Readable<string>;
45
44
  /**
46
- * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and no option is
47
- * selected, `null` (valid) otherwise. Pass straight to `useField({ validity: choice.validity })`.
45
+ * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`
46
+ * and no option is selected, `null` (valid) otherwise. Pass straight to
47
+ * `useField({ validity: choice.validity })`.
48
48
  */
49
- validity: Readable<ValidityStateFlags | null>;
49
+ validity: Readable<FormFieldValidity | null>;
50
50
  };
51
51
  export declare const createChoiceField: (options: ChoiceFieldOptions) => ChoiceFieldHandle;
52
52
  //# sourceMappingURL=choice-field.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"choice-field.d.ts","sourceRoot":"","sources":["../../src/core/choice-field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAS,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAmC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAKpG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAIF,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG;IAC9C,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,+FAA+F;IAC/F,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/C,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB;;;;OAIG;IACH,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG;IAC5C,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,aAAa,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtC;;;;;OAKG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,mFAAmF;IACnF,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;CAC/C,CAAC;AAaF,eAAO,MAAM,iBAAiB,GAAI,SAAS,kBAAkB,KAAG,iBA8F/D,CAAC"}
1
+ {"version":3,"file":"choice-field.d.ts","sourceRoot":"","sources":["../../src/core/choice-field.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAY,KAAK,QAAQ,EAAS,MAAM,kBAAkB,CAAC;AAElE,OAAO,EAAmC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAKpG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAIF,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG;IAC9C,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,+FAA+F;IAC/F,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/C,oGAAoG;IACpG,MAAM,EAAE,WAAW,CAAC;IACpB;;;;OAIG;IACH,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG;IAC5C,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,aAAa,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtC;;;;;OAKG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC;;;;OAIG;IACH,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CAC9C,CAAC;AAaF,eAAO,MAAM,iBAAiB,GAAI,SAAS,kBAAkB,KAAG,iBA4F/D,CAAC"}
@@ -1,2 +1,2 @@
1
- import{createDirtyTracker as e,createField as t}from"./field-base.js";import{syncedSignal as n}from"./signals.js";import{computed as r,watch as i}from"@vielzeug/ripple";var parseChoiceValues=e=>e?Array.isArray(e)?e.filter(Boolean):e.split(`,`).map(e=>e.trim()).filter(Boolean):[],createChoiceField=a=>{let o=r(()=>!!a.multiple?.value),normalizeValues=e=>{let t=o.value?e:e.slice(0,1);return[...new Set(t.filter(Boolean))]},s=n(a.value,a.signal,e=>normalizeValues(parseChoiceValues(e))),c=r(()=>o.value?s.value.join(`,`):s.value[0]??``),l=e(),applyValues=e=>{s.value=normalizeValues(e)},setValues=e=>{l.markDirty(),applyValues(e)},clear=()=>setValues([]),removeValue=e=>{l.markDirty(),s.value=s.value.filter(t=>t!==e)},selectValue=e=>{if(o.value){s.value.includes(e)||setValues([...s.value,e]);return}setValues([e])},toggleValue=e=>{o.value&&s.value.includes(e)?removeValue(e):selectValue(e)},syncFromProp=e=>{applyValues(parseChoiceValues(e))},u=t(a),d=r(()=>a.required?.value&&s.value.length===0?{valueMissing:!0}:null),f=r(()=>d.value?a.requiredMessage?.value||`Please make a selection.`:``);if(a.multiple){let e=i(a.multiple,()=>syncFromProp(a.value.value));a.signal.addEventListener(`abort`,()=>e.dispose(),{once:!0})}let p=s.value,reset=()=>{applyValues(l.isDirty?p:parseChoiceValues(a.value.value)),l.clear(),u.triggerValidation(`change`)};return{...u,clear,formValue:c,removeValue,reset,selectedValue:r(()=>s.value[0]),selectedValues:s,selectValue,setValues,toggleValue,validationMessage:f,validity:d}};export{createChoiceField};
1
+ import{createDirtyTracker as e,createField as t}from"./field-base.js";import{syncedSignal as n}from"./signals.js";import{computed as r,watch as i}from"@vielzeug/ripple";var parseChoiceValues=e=>e?Array.isArray(e)?e.filter(Boolean):e.split(`,`).map(e=>e.trim()).filter(Boolean):[],createChoiceField=a=>{let o=r(()=>!!a.multiple?.value),normalizeValues=e=>{let t=o.value?e:e.slice(0,1);return[...new Set(t.filter(Boolean))]},s=n(a.value,a.signal,e=>normalizeValues(parseChoiceValues(e))),c=r(()=>o.value?s.value.join(`,`):s.value[0]??``),l=e(),applyValues=e=>{s.value=normalizeValues(e)},setValues=e=>{l.markDirty(),applyValues(e)},clear=()=>setValues([]),removeValue=e=>{l.markDirty(),s.value=s.value.filter(t=>t!==e)},selectValue=e=>{if(o.value){s.value.includes(e)||setValues([...s.value,e]);return}setValues([e])},toggleValue=e=>{o.value&&s.value.includes(e)?removeValue(e):selectValue(e)},syncFromProp=e=>{applyValues(parseChoiceValues(e))},u=t(a),d=r(()=>a.required?.value&&s.value.length===0?{flags:{valueMissing:!0},message:a.requiredMessage?.value||`Please make a selection.`}:null);if(a.multiple){let e=i(a.multiple,()=>syncFromProp(a.value.value));a.signal.addEventListener(`abort`,()=>e.dispose(),{once:!0})}let f=s.value,reset=()=>{applyValues(l.isDirty?f:parseChoiceValues(a.value.value)),l.clear(),u.triggerValidation(`change`)};return{...u,clear,formValue:c,removeValue,reset,selectedValue:r(()=>s.value[0]),selectedValues:s,selectValue,setValues,toggleValue,validity:d}};export{createChoiceField};
2
2
  //# sourceMappingURL=choice-field.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"choice-field.js","names":[],"sources":["../../src/core/choice-field.ts"],"sourcesContent":["import { computed, type Readable, watch } from '@vielzeug/ripple';\n\nimport { createDirtyTracker, createField, type FieldHandle, type FieldOptions } from './field-base';\nimport { syncedSignal } from './signals';\n\n// ── Choice change event payload ──────────────────────────────────────────────────────────────\n\nexport type ChoiceChangeDetail = {\n labels: string[];\n originalEvent?: Event;\n values: string[];\n};\n\n// ── Choice field ──────────────────────────────────────────────────────────────────────\n\nexport type ChoiceFieldOptions = FieldOptions & {\n multiple?: Readable<boolean | undefined>;\n /** Marks an empty selection invalid: feeds `validity`/`validationMessage` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the empty-selection+required case. Defaults to `'Please make a selection.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. All internal subscriptions are disposed on abort. */\n signal: AbortSignal;\n /**\n * Current selection value(s). Accepts a comma-separated string\n * (legacy/single-select form serialisation) or a `string[]` array.\n * The field normalises both formats internally via `parseChoiceValues`.\n */\n value: Readable<string | string[] | undefined>;\n};\n\nexport type ChoiceFieldHandle = FieldHandle & {\n clear: () => void;\n formValue: Readable<string>;\n removeValue: (value: string) => void;\n /**\n * Restores the selection to its native \"default\". See `DirtyTracker` for the two-state\n * rationale.\n */\n reset: () => void;\n selectedValue: Readable<string | undefined>;\n selectedValues: Readable<string[]>;\n selectValue: (value: string) => void;\n setValues: (values: string[]) => void;\n /**\n * Toggles `value` in the selection.\n * - **Multiple mode**: adds if absent, removes if present.\n * - **Single mode**: always selects `value` (acts like `selectValue`: cannot\n * deselect; use `clear()` to reset a single-select field).\n */\n toggleValue: (value: string) => void;\n /** Reactive validation message paired with `validity`. Empty string when valid. */\n validationMessage: Readable<string>;\n /**\n * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and no option is\n * selected, `null` (valid) otherwise. Pass straight to `useField({ validity: choice.validity })`.\n */\n validity: Readable<ValidityStateFlags | null>;\n};\n\nconst parseChoiceValues = (raw: string | string[] | undefined): string[] => {\n if (!raw) return [];\n\n if (Array.isArray(raw)) return raw.filter(Boolean);\n\n return raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n};\n\nexport const createChoiceField = (options: ChoiceFieldOptions): ChoiceFieldHandle => {\n const isMultiple = computed(() => Boolean(options.multiple?.value));\n\n const normalizeValues = (values: string[]): string[] => {\n const normalized = isMultiple.value ? values : values.slice(0, 1);\n\n return [...new Set(normalized.filter(Boolean))];\n };\n\n // Kept in sync with `options.value` via the shared helper (see `checkable.ts`/`text-field.ts`\n // for the same pattern) rather than a hand-rolled signal + watch pair.\n const selectedValues = syncedSignal(options.value, options.signal, (raw) => normalizeValues(parseChoiceValues(raw)));\n const formValue = computed(() =>\n isMultiple.value ? selectedValues.value.join(',') : (selectedValues.value[0] ?? ''),\n );\n\n const dirtyTracker = createDirtyTracker();\n\n // The actual state setter, shared by every mutation path. Deliberately *not* where the dirty\n // tracker gets marked: `syncFromProp` (below) calls this directly to stay exempt from it,\n // since mirroring an external prop change is never \"the user changed the selection\".\n const applyValues = (values: string[]): void => {\n selectedValues.value = normalizeValues(values);\n };\n\n const setValues = (values: string[]): void => {\n dirtyTracker.markDirty();\n applyValues(values);\n };\n const clear = (): void => setValues([]);\n const removeValue = (value: string): void => {\n dirtyTracker.markDirty();\n selectedValues.value = selectedValues.value.filter((v) => v !== value);\n };\n\n const selectValue = (value: string): void => {\n if (isMultiple.value) {\n if (!selectedValues.value.includes(value)) setValues([...selectedValues.value, value]);\n\n return;\n }\n\n setValues([value]);\n };\n\n const toggleValue = (value: string): void => {\n if (isMultiple.value) {\n if (selectedValues.value.includes(value)) removeValue(value);\n else selectValue(value);\n } else {\n selectValue(value);\n }\n };\n\n const syncFromProp = (nextValue: string | string[] | undefined): void => {\n applyValues(parseChoiceValues(nextValue));\n };\n\n const field = createField(options);\n\n const validity = computed<ValidityStateFlags | null>(() =>\n options.required?.value && selectedValues.value.length === 0 ? { valueMissing: true } : null,\n );\n const validationMessage = computed(() =>\n validity.value ? options.requiredMessage?.value || 'Please make a selection.' : '',\n );\n\n if (options.multiple) {\n const multipleSub = watch(options.multiple, () => syncFromProp(options.value.value));\n\n options.signal.addEventListener('abort', () => multipleSub.dispose(), { once: true });\n }\n\n const initialValues = selectedValues.value;\n const reset = (): void => {\n applyValues(dirtyTracker.isDirty ? initialValues : parseChoiceValues(options.value.value));\n dirtyTracker.clear();\n field.triggerValidation('change');\n };\n\n return {\n ...field,\n clear,\n formValue,\n removeValue,\n reset,\n selectedValue: computed(() => selectedValues.value[0]),\n selectedValues,\n selectValue,\n setValues,\n toggleValue,\n validationMessage,\n validity,\n };\n};\n"],"mappings":"yKA4DA,IAAM,kBAAqB,GACpB,EAED,MAAM,QAAQ,CAAG,EAAU,EAAI,OAAO,OAAO,EAE1C,EACJ,MAAM,GAAG,CAAC,CACV,IAAK,GAAU,EAAM,KAAK,CAAC,CAAC,CAC5B,OAAO,OAAO,EAPA,CAAC,EAUP,kBAAqB,GAAmD,CACnF,IAAM,EAAa,MAAe,EAAQ,EAAQ,UAAU,KAAM,EAE5D,gBAAmB,GAA+B,CACtD,IAAM,EAAa,EAAW,MAAQ,EAAS,EAAO,MAAM,EAAG,CAAC,EAEhE,MAAO,CAAC,GAAG,IAAI,IAAI,EAAW,OAAO,OAAO,CAAC,CAAC,CAChD,EAIM,EAAiB,EAAa,EAAQ,MAAO,EAAQ,OAAS,GAAQ,gBAAgB,kBAAkB,CAAG,CAAC,CAAC,EAC7G,EAAY,MAChB,EAAW,MAAQ,EAAe,MAAM,KAAK,GAAG,EAAK,EAAe,MAAM,IAAM,EAClF,EAEM,EAAe,EAAmB,EAKlC,YAAe,GAA2B,CAC9C,EAAe,MAAQ,gBAAgB,CAAM,CAC/C,EAEM,UAAa,GAA2B,CAC5C,EAAa,UAAU,EACvB,YAAY,CAAM,CACpB,EACM,UAAoB,UAAU,CAAC,CAAC,EAChC,YAAe,GAAwB,CAC3C,EAAa,UAAU,EACvB,EAAe,MAAQ,EAAe,MAAM,OAAQ,GAAM,IAAM,CAAK,CACvE,EAEM,YAAe,GAAwB,CAC3C,GAAI,EAAW,MAAO,CACf,EAAe,MAAM,SAAS,CAAK,GAAG,UAAU,CAAC,GAAG,EAAe,MAAO,CAAK,CAAC,EAErF,MACF,CAEA,UAAU,CAAC,CAAK,CAAC,CACnB,EAEM,YAAe,GAAwB,CACvC,EAAW,OACT,EAAe,MAAM,SAAS,CAAK,EAAG,YAAY,CAAK,EACtD,YAAY,CAAK,CAI1B,EAEM,aAAgB,GAAmD,CACvE,YAAY,kBAAkB,CAAS,CAAC,CAC1C,EAEM,EAAQ,EAAY,CAAO,EAE3B,EAAW,MACf,EAAQ,UAAU,OAAS,EAAe,MAAM,SAAW,EAAI,CAAE,aAAc,EAAK,EAAI,IAC1F,EACM,EAAoB,MACxB,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,2BAA6B,EAClF,EAEA,GAAI,EAAQ,SAAU,CACpB,IAAM,EAAc,EAAM,EAAQ,aAAgB,aAAa,EAAQ,MAAM,KAAK,CAAC,EAEnF,EAAQ,OAAO,iBAAiB,YAAe,EAAY,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,CACtF,CAEA,IAAM,EAAgB,EAAe,MAC/B,UAAoB,CACxB,YAAY,EAAa,QAAU,EAAgB,kBAAkB,EAAQ,MAAM,KAAK,CAAC,EACzF,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEA,MAAO,CACL,GAAG,EACH,MACA,YACA,YACA,MACA,cAAe,MAAe,EAAe,MAAM,EAAE,EACrD,iBACA,YACA,UACA,YACA,oBACA,UACF,CACF"}
1
+ {"version":3,"file":"choice-field.js","names":[],"sources":["../../src/core/choice-field.ts"],"sourcesContent":["import type { FormFieldValidity } from '@vielzeug/ore';\nimport { computed, type Readable, watch } from '@vielzeug/ripple';\n\nimport { createDirtyTracker, createField, type FieldHandle, type FieldOptions } from './field-base';\nimport { syncedSignal } from './signals';\n\n// ── Choice change event payload ──────────────────────────────────────────────────────────────\n\nexport type ChoiceChangeDetail = {\n labels: string[];\n originalEvent?: Event;\n values: string[];\n};\n\n// ── Choice field ──────────────────────────────────────────────────────────────────────\n\nexport type ChoiceFieldOptions = FieldOptions & {\n multiple?: Readable<boolean | undefined>;\n /** Marks an empty selection invalid: feeds `validity` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the empty-selection+required case. Defaults to `'Please make a selection.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. All internal subscriptions are disposed on abort. */\n signal: AbortSignal;\n /**\n * Current selection value(s). Accepts a comma-separated string\n * (legacy/single-select form serialisation) or a `string[]` array.\n * The field normalises both formats internally via `parseChoiceValues`.\n */\n value: Readable<string | string[] | undefined>;\n};\n\nexport type ChoiceFieldHandle = FieldHandle & {\n clear: () => void;\n formValue: Readable<string>;\n removeValue: (value: string) => void;\n /**\n * Restores the selection to its native \"default\". See `DirtyTracker` for the two-state\n * rationale.\n */\n reset: () => void;\n selectedValue: Readable<string | undefined>;\n selectedValues: Readable<string[]>;\n selectValue: (value: string) => void;\n setValues: (values: string[]) => void;\n /**\n * Toggles `value` in the selection.\n * - **Multiple mode**: adds if absent, removes if present.\n * - **Single mode**: always selects `value` (acts like `selectValue`: cannot\n * deselect; use `clear()` to reset a single-select field).\n */\n toggleValue: (value: string) => void;\n /**\n * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`\n * and no option is selected, `null` (valid) otherwise. Pass straight to\n * `useField({ validity: choice.validity })`.\n */\n validity: Readable<FormFieldValidity | null>;\n};\n\nconst parseChoiceValues = (raw: string | string[] | undefined): string[] => {\n if (!raw) return [];\n\n if (Array.isArray(raw)) return raw.filter(Boolean);\n\n return raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n};\n\nexport const createChoiceField = (options: ChoiceFieldOptions): ChoiceFieldHandle => {\n const isMultiple = computed(() => Boolean(options.multiple?.value));\n\n const normalizeValues = (values: string[]): string[] => {\n const normalized = isMultiple.value ? values : values.slice(0, 1);\n\n return [...new Set(normalized.filter(Boolean))];\n };\n\n // Kept in sync with `options.value` via the shared helper (see `checkable.ts`/`text-field.ts`\n // for the same pattern) rather than a hand-rolled signal + watch pair.\n const selectedValues = syncedSignal(options.value, options.signal, (raw) => normalizeValues(parseChoiceValues(raw)));\n const formValue = computed(() =>\n isMultiple.value ? selectedValues.value.join(',') : (selectedValues.value[0] ?? ''),\n );\n\n const dirtyTracker = createDirtyTracker();\n\n // The actual state setter, shared by every mutation path. Deliberately *not* where the dirty\n // tracker gets marked: `syncFromProp` (below) calls this directly to stay exempt from it,\n // since mirroring an external prop change is never \"the user changed the selection\".\n const applyValues = (values: string[]): void => {\n selectedValues.value = normalizeValues(values);\n };\n\n const setValues = (values: string[]): void => {\n dirtyTracker.markDirty();\n applyValues(values);\n };\n const clear = (): void => setValues([]);\n const removeValue = (value: string): void => {\n dirtyTracker.markDirty();\n selectedValues.value = selectedValues.value.filter((v) => v !== value);\n };\n\n const selectValue = (value: string): void => {\n if (isMultiple.value) {\n if (!selectedValues.value.includes(value)) setValues([...selectedValues.value, value]);\n\n return;\n }\n\n setValues([value]);\n };\n\n const toggleValue = (value: string): void => {\n if (isMultiple.value) {\n if (selectedValues.value.includes(value)) removeValue(value);\n else selectValue(value);\n } else {\n selectValue(value);\n }\n };\n\n const syncFromProp = (nextValue: string | string[] | undefined): void => {\n applyValues(parseChoiceValues(nextValue));\n };\n\n const field = createField(options);\n\n const validity = computed<FormFieldValidity | null>(() =>\n options.required?.value && selectedValues.value.length === 0\n ? { flags: { valueMissing: true }, message: options.requiredMessage?.value || 'Please make a selection.' }\n : null,\n );\n\n if (options.multiple) {\n const multipleSub = watch(options.multiple, () => syncFromProp(options.value.value));\n\n options.signal.addEventListener('abort', () => multipleSub.dispose(), { once: true });\n }\n\n const initialValues = selectedValues.value;\n const reset = (): void => {\n applyValues(dirtyTracker.isDirty ? initialValues : parseChoiceValues(options.value.value));\n dirtyTracker.clear();\n field.triggerValidation('change');\n };\n\n return {\n ...field,\n clear,\n formValue,\n removeValue,\n reset,\n selectedValue: computed(() => selectedValues.value[0]),\n selectedValues,\n selectValue,\n setValues,\n toggleValue,\n validity,\n };\n};\n"],"mappings":"yKA4DA,IAAM,kBAAqB,GACpB,EAED,MAAM,QAAQ,CAAG,EAAU,EAAI,OAAO,OAAO,EAE1C,EACJ,MAAM,GAAG,CAAC,CACV,IAAK,GAAU,EAAM,KAAK,CAAC,CAAC,CAC5B,OAAO,OAAO,EAPA,CAAC,EAUP,kBAAqB,GAAmD,CACnF,IAAM,EAAa,MAAe,EAAQ,EAAQ,UAAU,KAAM,EAE5D,gBAAmB,GAA+B,CACtD,IAAM,EAAa,EAAW,MAAQ,EAAS,EAAO,MAAM,EAAG,CAAC,EAEhE,MAAO,CAAC,GAAG,IAAI,IAAI,EAAW,OAAO,OAAO,CAAC,CAAC,CAChD,EAIM,EAAiB,EAAa,EAAQ,MAAO,EAAQ,OAAS,GAAQ,gBAAgB,kBAAkB,CAAG,CAAC,CAAC,EAC7G,EAAY,MAChB,EAAW,MAAQ,EAAe,MAAM,KAAK,GAAG,EAAK,EAAe,MAAM,IAAM,EAClF,EAEM,EAAe,EAAmB,EAKlC,YAAe,GAA2B,CAC9C,EAAe,MAAQ,gBAAgB,CAAM,CAC/C,EAEM,UAAa,GAA2B,CAC5C,EAAa,UAAU,EACvB,YAAY,CAAM,CACpB,EACM,UAAoB,UAAU,CAAC,CAAC,EAChC,YAAe,GAAwB,CAC3C,EAAa,UAAU,EACvB,EAAe,MAAQ,EAAe,MAAM,OAAQ,GAAM,IAAM,CAAK,CACvE,EAEM,YAAe,GAAwB,CAC3C,GAAI,EAAW,MAAO,CACf,EAAe,MAAM,SAAS,CAAK,GAAG,UAAU,CAAC,GAAG,EAAe,MAAO,CAAK,CAAC,EAErF,MACF,CAEA,UAAU,CAAC,CAAK,CAAC,CACnB,EAEM,YAAe,GAAwB,CACvC,EAAW,OACT,EAAe,MAAM,SAAS,CAAK,EAAG,YAAY,CAAK,EACtD,YAAY,CAAK,CAI1B,EAEM,aAAgB,GAAmD,CACvE,YAAY,kBAAkB,CAAS,CAAC,CAC1C,EAEM,EAAQ,EAAY,CAAO,EAE3B,EAAW,MACf,EAAQ,UAAU,OAAS,EAAe,MAAM,SAAW,EACvD,CAAE,MAAO,CAAE,aAAc,EAAK,EAAG,QAAS,EAAQ,iBAAiB,OAAS,0BAA2B,EACvG,IACN,EAEA,GAAI,EAAQ,SAAU,CACpB,IAAM,EAAc,EAAM,EAAQ,aAAgB,aAAa,EAAQ,MAAM,KAAK,CAAC,EAEnF,EAAQ,OAAO,iBAAiB,YAAe,EAAY,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,CACtF,CAEA,IAAM,EAAgB,EAAe,MAC/B,UAAoB,CACxB,YAAY,EAAa,QAAU,EAAgB,kBAAkB,EAAQ,MAAM,KAAK,CAAC,EACzF,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEA,MAAO,CACL,GAAG,EACH,MACA,YACA,YACA,MACA,cAAe,MAAe,EAAe,MAAM,EAAE,EACrD,iBACA,YACA,UACA,YACA,UACF,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("./field-base.cjs"),t=require("./signals.cjs");let n=require("@vielzeug/ripple");var isBlank=e=>e.trim()===``,toFieldValue=e=>String(e??``),createTextField=r=>{let i=t.syncedSignal(r.value,r.signal,toFieldValue),a=e.createField(r),o=r.maxLength?e.createCounterState({maxLength:r.maxLength,value:i}):null,s=(0,n.computed)(()=>!r.readonly?.value&&r.required?.value&&isBlank(i.value)?{valueMissing:!0}:null),c=(0,n.computed)(()=>s.value?r.requiredMessage?.value||`This field is required.`:``),reset=()=>{i.value=toFieldValue(r.value.value),a.triggerValidation(`change`)},clear=e=>{e?.preventDefault?.(),i.value=``,r.onInput?.(e??new InputEvent(`input`,{bubbles:!0}),``),r.onChange?.(e??new Event(`change`,{bubbles:!0}),``),a.triggerValidation(`change`)},wire=(e,t)=>{let n=[],on=(t,r)=>{e.addEventListener(t,r),n.push(()=>e.removeEventListener(t,r))};r.onFocus&&on(`focus`,e=>r.onFocus?.(e)),on(`input`,t=>{t.stopPropagation(),r.onBeforeInput?.(t),i.value=e.value,r.onInput?.(t,e.value)}),on(`change`,t=>{t.stopPropagation(),i.value=e.value,r.onChange?.(t,e.value),a.triggerValidation(`change`)}),on(`blur`,e=>{r.onBlur?.(e),a.triggerValidation(`blur`)});let o=!1,detach=()=>{if(!o){o=!0;for(let e of n)e()}};return t?.addEventListener(`abort`,detach,{once:!0}),detach};return{...a,clear,counter:o,reset,validationMessage:c,validity:s,value:i,wire}};exports.createTextField=createTextField;
1
+ const e=require("./field-base.cjs"),t=require("./signals.cjs");let n=require("@vielzeug/ripple");var isBlank=e=>e.trim()===``,toFieldValue=e=>String(e??``),createTextField=r=>{let i=t.syncedSignal(r.value,r.signal,toFieldValue),a=e.createField(r),o=r.maxLength?e.createCounterState({maxLength:r.maxLength,value:i}):null,s=(0,n.computed)(()=>!r.readonly?.value&&r.required?.value&&isBlank(i.value)?{flags:{valueMissing:!0},message:r.requiredMessage?.value||`This field is required.`}:null),reset=()=>{i.value=toFieldValue(r.value.value),a.triggerValidation(`change`)},clear=e=>{e?.preventDefault?.(),i.value=``,r.onInput?.(e??new InputEvent(`input`,{bubbles:!0}),``),r.onChange?.(e??new Event(`change`,{bubbles:!0}),``),a.triggerValidation(`change`)},wire=(e,t)=>{let n=[],on=(t,r)=>{e.addEventListener(t,r),n.push(()=>e.removeEventListener(t,r))};r.onFocus&&on(`focus`,e=>r.onFocus?.(e)),on(`input`,t=>{t.stopPropagation(),r.onBeforeInput?.(t),i.value=e.value,r.onInput?.(t,e.value)}),on(`change`,t=>{t.stopPropagation(),i.value=e.value,r.onChange?.(t,e.value),a.triggerValidation(`change`)}),on(`blur`,e=>{r.onBlur?.(e),a.triggerValidation(`blur`)});let o=!1,detach=()=>{if(!o){o=!0;for(let e of n)e()}};return t?.addEventListener(`abort`,detach,{once:!0}),detach};return{...a,clear,counter:o,reset,validity:s,value:i,wire}};exports.createTextField=createTextField;
2
2
  //# sourceMappingURL=text-field.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-field.cjs","names":[],"sources":["../../src/core/text-field.ts"],"sourcesContent":["import { computed, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { type CounterState, createCounterState, createField, type FieldHandle, type FieldOptions } from './field-base';\nimport { syncedSignal } from './signals';\n\n// ── Text field ────────────────────────────────────────────────────────────────\n\n/** Detach function returned by `wire()`. Call to remove element listeners. */\nexport type TextFieldDetach = () => void;\n\nexport type TextFieldOptions = FieldOptions & {\n maxLength?: Readable<number | undefined>;\n /**\n * Called synchronously before value extraction on every input event.\n * Use only for DOM side-effects (e.g. auto-resize measurement) that must\n * run before the reactive value is updated.\n */\n onBeforeInput?: (event: Event) => void;\n onBlur?: (event: FocusEvent) => void;\n onChange?: (event: Event, value: string) => void;\n onFocus?: (event: FocusEvent) => void;\n onInput?: (event: Event, value: string) => void;\n /**\n * Bars the field from constraint validation while true: matches the native HTML rule that a\n * `readonly` field is never a candidate for constraint validation (a `readonly required`\n * field is always valid, per spec), so `validity`/`validationMessage` don't feed a false\n * `valueMissing` into `useField()` just because a read-only field happens to be blank.\n */\n readonly?: Readable<boolean | undefined>;\n /** Marks a blank value invalid: feeds `validity`/`validationMessage` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the blank+required case. Defaults to `'This field is required.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. The internal value-sync watcher is disposed on abort. */\n signal: AbortSignal;\n value: Readable<string | undefined>;\n};\n\nexport type TextFieldHandle = FieldHandle & {\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()`: called internally on blur/change: can call its\n * `reportValidity()`. `useField()` itself needs `value` (below) to exist first, so this\n * is a two-step wiring rather than a constructor option:\n *\n * ```ts\n * const tf = createTextField({ value: props.value, ... });\n * const formField = useField({ value: tf.value, ... });\n * tf.attachFormField(formField);\n * ```\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n /** Clears the field value and fires synthetic input/change events. */\n clear: (event?: Event) => void;\n /**\n * Reactive counter state. Non-null when `maxLength` was provided; `null` otherwise.\n * Components should only render a counter element when this is non-null.\n */\n counter: Readable<CounterState> | null;\n /**\n * Restores the value to whatever the `value` option currently holds (native form\n * `reset()` semantics: a `<input>` reverts to its *current* `value` content attribute,\n * not a frozen snapshot from element creation, so setting the attribute programmatically\n * after mount changes what a later reset reverts to). Wire into `useField({ onReset: tf.reset })`.\n */\n reset: () => void;\n /** Reactive validation message paired with `validity`. Empty string when valid. */\n validationMessage: Readable<string>;\n /**\n * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and blank,\n * `null` (valid) otherwise. Pass straight to `useField({ validity: tf.validity })`.\n */\n validity: Readable<ValidityStateFlags | null>;\n /** The local mutable field value (two-way bound to the input element via `wire()`). */\n value: Signal<string>;\n /**\n * Attaches event listeners to the underlying input or textarea element.\n *\n * When `signal` is provided, the listeners are detached automatically when\n * the signal's controller calls `abort()`. Without a signal the returned\n * `TextFieldDetach` must be called manually.\n *\n * Safe to call multiple times: each call attaches a fresh set of listeners\n * and returns a distinct detach function. Calling detach multiple times is\n * a no-op (guarded internally).\n */\n wire: (el: HTMLInputElement | HTMLTextAreaElement, signal?: AbortSignal) => TextFieldDetach;\n};\n\nconst isBlank = (value: string): boolean => value.trim() === '';\nconst toFieldValue = (v: string | undefined): string => String(v ?? '');\n\nexport const createTextField = (options: TextFieldOptions): TextFieldHandle => {\n const value = syncedSignal(options.value, options.signal, toFieldValue);\n\n const field = createField(options);\n const counter = options.maxLength ? createCounterState({ maxLength: options.maxLength, value }) : null;\n\n const validity = computed<ValidityStateFlags | null>(() =>\n !options.readonly?.value && options.required?.value && isBlank(value.value) ? { valueMissing: true } : null,\n );\n const validationMessage = computed(() =>\n validity.value ? options.requiredMessage?.value || 'This field is required.' : '',\n );\n\n const reset = (): void => {\n value.value = toFieldValue(options.value.value);\n field.triggerValidation('change');\n };\n\n const clear = (event?: Event): void => {\n event?.preventDefault?.();\n value.value = '';\n options.onInput?.(event ?? new InputEvent('input', { bubbles: true }), '');\n options.onChange?.(event ?? new Event('change', { bubbles: true }), '');\n field.triggerValidation('change');\n };\n\n const wire = (element: HTMLInputElement | HTMLTextAreaElement, signal?: AbortSignal): TextFieldDetach => {\n const cleanups: Array<() => void> = [];\n const on = (type: string, handler: EventListener): void => {\n element.addEventListener(type, handler);\n cleanups.push(() => element.removeEventListener(type, handler));\n };\n\n if (options.onFocus) {\n on('focus', (e) => options.onFocus?.(e as FocusEvent));\n }\n\n on('input', (e) => {\n e.stopPropagation();\n options.onBeforeInput?.(e);\n value.value = element.value;\n options.onInput?.(e, element.value);\n });\n\n on('change', (e) => {\n e.stopPropagation();\n value.value = element.value;\n options.onChange?.(e, element.value);\n field.triggerValidation('change');\n });\n\n on('blur', (e) => {\n options.onBlur?.(e as FocusEvent);\n field.triggerValidation('blur');\n });\n\n let detached = false;\n const detach = (): void => {\n if (detached) return;\n\n detached = true;\n for (const dispose of cleanups) dispose();\n };\n\n signal?.addEventListener('abort', detach, { once: true });\n\n return detach;\n };\n\n return { ...field, clear, counter, reset, validationMessage, validity, value, wire };\n};\n"],"mappings":"iGAyFA,IAAM,QAAW,GAA2B,EAAM,KAAK,IAAM,GACvD,aAAgB,GAAkC,OAAO,GAAK,EAAE,EAEzD,gBAAmB,GAA+C,CAC7E,IAAM,EAAQ,EAAA,aAAa,EAAQ,MAAO,EAAQ,OAAQ,YAAY,EAEhE,EAAQ,EAAA,YAAY,CAAO,EAC3B,EAAU,EAAQ,UAAY,EAAA,mBAAmB,CAAE,UAAW,EAAQ,UAAW,OAAM,CAAC,EAAI,KAE5F,GAAA,EAAW,EAAA,SAAA,KACf,CAAC,EAAQ,UAAU,OAAS,EAAQ,UAAU,OAAS,QAAQ,EAAM,KAAK,EAAI,CAAE,aAAc,EAAK,EAAI,IACzG,EACM,GAAA,EAAoB,EAAA,SAAA,KACxB,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,0BAA4B,EACjF,EAEM,UAAoB,CACxB,EAAM,MAAQ,aAAa,EAAQ,MAAM,KAAK,EAC9C,EAAM,kBAAkB,QAAQ,CAClC,EAEM,MAAS,GAAwB,CACrC,GAAO,iBAAiB,EACxB,EAAM,MAAQ,GACd,EAAQ,UAAU,GAAS,IAAI,WAAW,QAAS,CAAE,QAAS,EAAK,CAAC,EAAG,EAAE,EACzE,EAAQ,WAAW,GAAS,IAAI,MAAM,SAAU,CAAE,QAAS,EAAK,CAAC,EAAG,EAAE,EACtE,EAAM,kBAAkB,QAAQ,CAClC,EAEM,MAAQ,EAAiD,IAA0C,CACvG,IAAM,EAA8B,CAAC,EAC/B,IAAM,EAAc,IAAiC,CACzD,EAAQ,iBAAiB,EAAM,CAAO,EACtC,EAAS,SAAW,EAAQ,oBAAoB,EAAM,CAAO,CAAC,CAChE,EAEI,EAAQ,SACV,GAAG,QAAU,GAAM,EAAQ,UAAU,CAAe,CAAC,EAGvD,GAAG,QAAU,GAAM,CACjB,EAAE,gBAAgB,EAClB,EAAQ,gBAAgB,CAAC,EACzB,EAAM,MAAQ,EAAQ,MACtB,EAAQ,UAAU,EAAG,EAAQ,KAAK,CACpC,CAAC,EAED,GAAG,SAAW,GAAM,CAClB,EAAE,gBAAgB,EAClB,EAAM,MAAQ,EAAQ,MACtB,EAAQ,WAAW,EAAG,EAAQ,KAAK,EACnC,EAAM,kBAAkB,QAAQ,CAClC,CAAC,EAED,GAAG,OAAS,GAAM,CAChB,EAAQ,SAAS,CAAe,EAChC,EAAM,kBAAkB,MAAM,CAChC,CAAC,EAED,IAAI,EAAW,GACT,WAAqB,CACrB,MAEJ,GAAW,GACX,IAAK,IAAM,KAAW,EAAU,EAAQ,CAD7B,CAEb,EAIA,OAFA,GAAQ,iBAAiB,QAAS,OAAQ,CAAE,KAAM,EAAK,CAAC,EAEjD,MACT,EAEA,MAAO,CAAE,GAAG,EAAO,MAAO,UAAS,MAAO,oBAAmB,WAAU,QAAO,IAAK,CACrF"}
1
+ {"version":3,"file":"text-field.cjs","names":[],"sources":["../../src/core/text-field.ts"],"sourcesContent":["import type { FormFieldValidity } from '@vielzeug/ore';\nimport { computed, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { type CounterState, createCounterState, createField, type FieldHandle, type FieldOptions } from './field-base';\nimport { syncedSignal } from './signals';\n\n// ── Text field ────────────────────────────────────────────────────────────────\n\n/** Detach function returned by `wire()`. Call to remove element listeners. */\nexport type TextFieldDetach = () => void;\n\nexport type TextFieldOptions = FieldOptions & {\n maxLength?: Readable<number | undefined>;\n /**\n * Called synchronously before value extraction on every input event.\n * Use only for DOM side-effects (e.g. auto-resize measurement) that must\n * run before the reactive value is updated.\n */\n onBeforeInput?: (event: Event) => void;\n onBlur?: (event: FocusEvent) => void;\n onChange?: (event: Event, value: string) => void;\n onFocus?: (event: FocusEvent) => void;\n onInput?: (event: Event, value: string) => void;\n /**\n * Bars the field from constraint validation while true: matches the native HTML rule that a\n * `readonly` field is never a candidate for constraint validation (a `readonly required`\n * field is always valid, per spec), so `validity` doesn't feed a false `valueMissing`\n * into `useField()` just because a read-only field happens to be blank.\n */\n readonly?: Readable<boolean | undefined>;\n /** Marks a blank value invalid: feeds `validity` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the blank+required case. Defaults to `'This field is required.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. The internal value-sync watcher is disposed on abort. */\n signal: AbortSignal;\n value: Readable<string | undefined>;\n};\n\nexport type TextFieldHandle = FieldHandle & {\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()`: called internally on blur/change: can call its\n * `reportValidity()`. `useField()` itself needs `value` (below) to exist first, so this\n * is a two-step wiring rather than a constructor option:\n *\n * ```ts\n * const tf = createTextField({ value: props.value, ... });\n * const formField = useField({ value: tf.value, ... });\n * tf.attachFormField(formField);\n * ```\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n /** Clears the field value and fires synthetic input/change events. */\n clear: (event?: Event) => void;\n /**\n * Reactive counter state. Non-null when `maxLength` was provided; `null` otherwise.\n * Components should only render a counter element when this is non-null.\n */\n counter: Readable<CounterState> | null;\n /**\n * Restores the value to whatever the `value` option currently holds (native form\n * `reset()` semantics: a `<input>` reverts to its *current* `value` content attribute,\n * not a frozen snapshot from element creation, so setting the attribute programmatically\n * after mount changes what a later reset reverts to). Wire into `useField({ onReset: tf.reset })`.\n */\n reset: () => void;\n /**\n * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`\n * and blank (a readonly field is always valid, per spec), `null` (valid) otherwise.\n * Pass straight to `useField({ validity: tf.validity })`.\n */\n validity: Readable<FormFieldValidity | null>;\n /** The local mutable field value (two-way bound to the input element via `wire()`). */\n value: Signal<string>;\n /**\n * Attaches event listeners to the underlying input or textarea element.\n *\n * When `signal` is provided, the listeners are detached automatically when\n * the signal's controller calls `abort()`. Without a signal the returned\n * `TextFieldDetach` must be called manually.\n *\n * Safe to call multiple times: each call attaches a fresh set of listeners\n * and returns a distinct detach function. Calling detach multiple times is\n * a no-op (guarded internally).\n */\n wire: (el: HTMLInputElement | HTMLTextAreaElement, signal?: AbortSignal) => TextFieldDetach;\n};\n\nconst isBlank = (value: string): boolean => value.trim() === '';\nconst toFieldValue = (v: string | undefined): string => String(v ?? '');\n\nexport const createTextField = (options: TextFieldOptions): TextFieldHandle => {\n const value = syncedSignal(options.value, options.signal, toFieldValue);\n\n const field = createField(options);\n const counter = options.maxLength ? createCounterState({ maxLength: options.maxLength, value }) : null;\n\n const validity = computed<FormFieldValidity | null>(() =>\n !options.readonly?.value && options.required?.value && isBlank(value.value)\n ? { flags: { valueMissing: true }, message: options.requiredMessage?.value || 'This field is required.' }\n : null,\n );\n\n const reset = (): void => {\n value.value = toFieldValue(options.value.value);\n field.triggerValidation('change');\n };\n\n const clear = (event?: Event): void => {\n event?.preventDefault?.();\n value.value = '';\n options.onInput?.(event ?? new InputEvent('input', { bubbles: true }), '');\n options.onChange?.(event ?? new Event('change', { bubbles: true }), '');\n field.triggerValidation('change');\n };\n\n const wire = (element: HTMLInputElement | HTMLTextAreaElement, signal?: AbortSignal): TextFieldDetach => {\n const cleanups: Array<() => void> = [];\n const on = (type: string, handler: EventListener): void => {\n element.addEventListener(type, handler);\n cleanups.push(() => element.removeEventListener(type, handler));\n };\n\n if (options.onFocus) {\n on('focus', (e) => options.onFocus?.(e as FocusEvent));\n }\n\n on('input', (e) => {\n e.stopPropagation();\n options.onBeforeInput?.(e);\n value.value = element.value;\n options.onInput?.(e, element.value);\n });\n\n on('change', (e) => {\n e.stopPropagation();\n value.value = element.value;\n options.onChange?.(e, element.value);\n field.triggerValidation('change');\n });\n\n on('blur', (e) => {\n options.onBlur?.(e as FocusEvent);\n field.triggerValidation('blur');\n });\n\n let detached = false;\n const detach = (): void => {\n if (detached) return;\n\n detached = true;\n for (const dispose of cleanups) dispose();\n };\n\n signal?.addEventListener('abort', detach, { once: true });\n\n return detach;\n };\n\n return { ...field, clear, counter, reset, validity, value, wire };\n};\n"],"mappings":"iGAyFA,IAAM,QAAW,GAA2B,EAAM,KAAK,IAAM,GACvD,aAAgB,GAAkC,OAAO,GAAK,EAAE,EAEzD,gBAAmB,GAA+C,CAC7E,IAAM,EAAQ,EAAA,aAAa,EAAQ,MAAO,EAAQ,OAAQ,YAAY,EAEhE,EAAQ,EAAA,YAAY,CAAO,EAC3B,EAAU,EAAQ,UAAY,EAAA,mBAAmB,CAAE,UAAW,EAAQ,UAAW,OAAM,CAAC,EAAI,KAE5F,GAAA,EAAW,EAAA,SAAA,KACf,CAAC,EAAQ,UAAU,OAAS,EAAQ,UAAU,OAAS,QAAQ,EAAM,KAAK,EACtE,CAAE,MAAO,CAAE,aAAc,EAAK,EAAG,QAAS,EAAQ,iBAAiB,OAAS,yBAA0B,EACtG,IACN,EAEM,UAAoB,CACxB,EAAM,MAAQ,aAAa,EAAQ,MAAM,KAAK,EAC9C,EAAM,kBAAkB,QAAQ,CAClC,EAEM,MAAS,GAAwB,CACrC,GAAO,iBAAiB,EACxB,EAAM,MAAQ,GACd,EAAQ,UAAU,GAAS,IAAI,WAAW,QAAS,CAAE,QAAS,EAAK,CAAC,EAAG,EAAE,EACzE,EAAQ,WAAW,GAAS,IAAI,MAAM,SAAU,CAAE,QAAS,EAAK,CAAC,EAAG,EAAE,EACtE,EAAM,kBAAkB,QAAQ,CAClC,EAEM,MAAQ,EAAiD,IAA0C,CACvG,IAAM,EAA8B,CAAC,EAC/B,IAAM,EAAc,IAAiC,CACzD,EAAQ,iBAAiB,EAAM,CAAO,EACtC,EAAS,SAAW,EAAQ,oBAAoB,EAAM,CAAO,CAAC,CAChE,EAEI,EAAQ,SACV,GAAG,QAAU,GAAM,EAAQ,UAAU,CAAe,CAAC,EAGvD,GAAG,QAAU,GAAM,CACjB,EAAE,gBAAgB,EAClB,EAAQ,gBAAgB,CAAC,EACzB,EAAM,MAAQ,EAAQ,MACtB,EAAQ,UAAU,EAAG,EAAQ,KAAK,CACpC,CAAC,EAED,GAAG,SAAW,GAAM,CAClB,EAAE,gBAAgB,EAClB,EAAM,MAAQ,EAAQ,MACtB,EAAQ,WAAW,EAAG,EAAQ,KAAK,EACnC,EAAM,kBAAkB,QAAQ,CAClC,CAAC,EAED,GAAG,OAAS,GAAM,CAChB,EAAQ,SAAS,CAAe,EAChC,EAAM,kBAAkB,MAAM,CAChC,CAAC,EAED,IAAI,EAAW,GACT,WAAqB,CACrB,MAEJ,GAAW,GACX,IAAK,IAAM,KAAW,EAAU,EAAQ,CAD7B,CAEb,EAIA,OAFA,GAAQ,iBAAiB,QAAS,OAAQ,CAAE,KAAM,EAAK,CAAC,EAEjD,MACT,EAEA,MAAO,CAAE,GAAG,EAAO,MAAO,UAAS,MAAO,WAAU,QAAO,IAAK,CAClE"}
@@ -1,3 +1,4 @@
1
+ import type { FormFieldValidity } from '@vielzeug/ore';
1
2
  import { type Readable, type Signal } from '@vielzeug/ripple';
2
3
  import { type CounterState, type FieldHandle, type FieldOptions } from './field-base';
3
4
  /** Detach function returned by `wire()`. Call to remove element listeners. */
@@ -17,11 +18,11 @@ export type TextFieldOptions = FieldOptions & {
17
18
  /**
18
19
  * Bars the field from constraint validation while true: matches the native HTML rule that a
19
20
  * `readonly` field is never a candidate for constraint validation (a `readonly required`
20
- * field is always valid, per spec), so `validity`/`validationMessage` don't feed a false
21
- * `valueMissing` into `useField()` just because a read-only field happens to be blank.
21
+ * field is always valid, per spec), so `validity` doesn't feed a false `valueMissing`
22
+ * into `useField()` just because a read-only field happens to be blank.
22
23
  */
23
24
  readonly?: Readable<boolean | undefined>;
24
- /** Marks a blank value invalid: feeds `validity`/`validationMessage` (see below). */
25
+ /** Marks a blank value invalid: feeds `validity` (see below). */
25
26
  required?: Readable<boolean | undefined>;
26
27
  /** Message for the blank+required case. Defaults to `'This field is required.'`. */
27
28
  requiredMessage?: Readable<string | undefined>;
@@ -59,13 +60,12 @@ export type TextFieldHandle = FieldHandle & {
59
60
  * after mount changes what a later reset reverts to). Wire into `useField({ onReset: tf.reset })`.
60
61
  */
61
62
  reset: () => void;
62
- /** Reactive validation message paired with `validity`. Empty string when valid. */
63
- validationMessage: Readable<string>;
64
63
  /**
65
- * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and blank,
66
- * `null` (valid) otherwise. Pass straight to `useField({ validity: tf.validity })`.
64
+ * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`
65
+ * and blank (a readonly field is always valid, per spec), `null` (valid) otherwise.
66
+ * Pass straight to `useField({ validity: tf.validity })`.
67
67
  */
68
- validity: Readable<ValidityStateFlags | null>;
68
+ validity: Readable<FormFieldValidity | null>;
69
69
  /** The local mutable field value (two-way bound to the input element via `wire()`). */
70
70
  value: Signal<string>;
71
71
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"text-field.d.ts","sourceRoot":"","sources":["../../src/core/text-field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAExE,OAAO,EAAE,KAAK,YAAY,EAAmC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAKvH,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG;IAC5C,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,qFAAqF;IACrF,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,oFAAoF;IACpF,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/C,wGAAwG;IACxG,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C;;;;;;;;;;;OAWG;IACH,eAAe,EAAE,CAAC,SAAS,EAAE;QAAE,cAAc,IAAI,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IACjE,sEAAsE;IACtE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B;;;OAGG;IACH,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC;IACvC;;;;;OAKG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mFAAmF;IACnF,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAC9C,uFAAuF;IACvF,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACtB;;;;;;;;;;OAUG;IACH,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,GAAG,mBAAmB,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,eAAe,CAAC;CAC7F,CAAC;AAKF,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,eAsE3D,CAAC"}
1
+ {"version":3,"file":"text-field.d.ts","sourceRoot":"","sources":["../../src/core/text-field.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAY,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAExE,OAAO,EAAE,KAAK,YAAY,EAAmC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAKvH,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG;IAC5C,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,oFAAoF;IACpF,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC/C,wGAAwG;IACxG,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C;;;;;;;;;;;OAWG;IACH,eAAe,EAAE,CAAC,SAAS,EAAE;QAAE,cAAc,IAAI,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IACjE,sEAAsE;IACtE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B;;;OAGG;IACH,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC;IACvC;;;;;OAKG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB;;;;OAIG;IACH,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC7C,uFAAuF;IACvF,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACtB;;;;;;;;;;OAUG;IACH,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,GAAG,mBAAmB,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,eAAe,CAAC;CAC7F,CAAC;AAKF,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,eAqE3D,CAAC"}
@@ -1,2 +1,2 @@
1
- import{createCounterState as e,createField as t}from"./field-base.js";import{syncedSignal as n}from"./signals.js";import{computed as r}from"@vielzeug/ripple";var isBlank=e=>e.trim()===``,toFieldValue=e=>String(e??``),createTextField=i=>{let a=n(i.value,i.signal,toFieldValue),o=t(i),s=i.maxLength?e({maxLength:i.maxLength,value:a}):null,c=r(()=>!i.readonly?.value&&i.required?.value&&isBlank(a.value)?{valueMissing:!0}:null),l=r(()=>c.value?i.requiredMessage?.value||`This field is required.`:``),reset=()=>{a.value=toFieldValue(i.value.value),o.triggerValidation(`change`)},clear=e=>{e?.preventDefault?.(),a.value=``,i.onInput?.(e??new InputEvent(`input`,{bubbles:!0}),``),i.onChange?.(e??new Event(`change`,{bubbles:!0}),``),o.triggerValidation(`change`)},wire=(e,t)=>{let n=[],on=(t,r)=>{e.addEventListener(t,r),n.push(()=>e.removeEventListener(t,r))};i.onFocus&&on(`focus`,e=>i.onFocus?.(e)),on(`input`,t=>{t.stopPropagation(),i.onBeforeInput?.(t),a.value=e.value,i.onInput?.(t,e.value)}),on(`change`,t=>{t.stopPropagation(),a.value=e.value,i.onChange?.(t,e.value),o.triggerValidation(`change`)}),on(`blur`,e=>{i.onBlur?.(e),o.triggerValidation(`blur`)});let r=!1,detach=()=>{if(!r){r=!0;for(let e of n)e()}};return t?.addEventListener(`abort`,detach,{once:!0}),detach};return{...o,clear,counter:s,reset,validationMessage:l,validity:c,value:a,wire}};export{createTextField};
1
+ import{createCounterState as e,createField as t}from"./field-base.js";import{syncedSignal as n}from"./signals.js";import{computed as r}from"@vielzeug/ripple";var isBlank=e=>e.trim()===``,toFieldValue=e=>String(e??``),createTextField=i=>{let a=n(i.value,i.signal,toFieldValue),o=t(i),s=i.maxLength?e({maxLength:i.maxLength,value:a}):null,c=r(()=>!i.readonly?.value&&i.required?.value&&isBlank(a.value)?{flags:{valueMissing:!0},message:i.requiredMessage?.value||`This field is required.`}:null),reset=()=>{a.value=toFieldValue(i.value.value),o.triggerValidation(`change`)},clear=e=>{e?.preventDefault?.(),a.value=``,i.onInput?.(e??new InputEvent(`input`,{bubbles:!0}),``),i.onChange?.(e??new Event(`change`,{bubbles:!0}),``),o.triggerValidation(`change`)},wire=(e,t)=>{let n=[],on=(t,r)=>{e.addEventListener(t,r),n.push(()=>e.removeEventListener(t,r))};i.onFocus&&on(`focus`,e=>i.onFocus?.(e)),on(`input`,t=>{t.stopPropagation(),i.onBeforeInput?.(t),a.value=e.value,i.onInput?.(t,e.value)}),on(`change`,t=>{t.stopPropagation(),a.value=e.value,i.onChange?.(t,e.value),o.triggerValidation(`change`)}),on(`blur`,e=>{i.onBlur?.(e),o.triggerValidation(`blur`)});let r=!1,detach=()=>{if(!r){r=!0;for(let e of n)e()}};return t?.addEventListener(`abort`,detach,{once:!0}),detach};return{...o,clear,counter:s,reset,validity:c,value:a,wire}};export{createTextField};
2
2
  //# sourceMappingURL=text-field.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-field.js","names":[],"sources":["../../src/core/text-field.ts"],"sourcesContent":["import { computed, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { type CounterState, createCounterState, createField, type FieldHandle, type FieldOptions } from './field-base';\nimport { syncedSignal } from './signals';\n\n// ── Text field ────────────────────────────────────────────────────────────────\n\n/** Detach function returned by `wire()`. Call to remove element listeners. */\nexport type TextFieldDetach = () => void;\n\nexport type TextFieldOptions = FieldOptions & {\n maxLength?: Readable<number | undefined>;\n /**\n * Called synchronously before value extraction on every input event.\n * Use only for DOM side-effects (e.g. auto-resize measurement) that must\n * run before the reactive value is updated.\n */\n onBeforeInput?: (event: Event) => void;\n onBlur?: (event: FocusEvent) => void;\n onChange?: (event: Event, value: string) => void;\n onFocus?: (event: FocusEvent) => void;\n onInput?: (event: Event, value: string) => void;\n /**\n * Bars the field from constraint validation while true: matches the native HTML rule that a\n * `readonly` field is never a candidate for constraint validation (a `readonly required`\n * field is always valid, per spec), so `validity`/`validationMessage` don't feed a false\n * `valueMissing` into `useField()` just because a read-only field happens to be blank.\n */\n readonly?: Readable<boolean | undefined>;\n /** Marks a blank value invalid: feeds `validity`/`validationMessage` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the blank+required case. Defaults to `'This field is required.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. The internal value-sync watcher is disposed on abort. */\n signal: AbortSignal;\n value: Readable<string | undefined>;\n};\n\nexport type TextFieldHandle = FieldHandle & {\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()`: called internally on blur/change: can call its\n * `reportValidity()`. `useField()` itself needs `value` (below) to exist first, so this\n * is a two-step wiring rather than a constructor option:\n *\n * ```ts\n * const tf = createTextField({ value: props.value, ... });\n * const formField = useField({ value: tf.value, ... });\n * tf.attachFormField(formField);\n * ```\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n /** Clears the field value and fires synthetic input/change events. */\n clear: (event?: Event) => void;\n /**\n * Reactive counter state. Non-null when `maxLength` was provided; `null` otherwise.\n * Components should only render a counter element when this is non-null.\n */\n counter: Readable<CounterState> | null;\n /**\n * Restores the value to whatever the `value` option currently holds (native form\n * `reset()` semantics: a `<input>` reverts to its *current* `value` content attribute,\n * not a frozen snapshot from element creation, so setting the attribute programmatically\n * after mount changes what a later reset reverts to). Wire into `useField({ onReset: tf.reset })`.\n */\n reset: () => void;\n /** Reactive validation message paired with `validity`. Empty string when valid. */\n validationMessage: Readable<string>;\n /**\n * Reactive `ValidityStateFlags`: `{ valueMissing: true }` while `required` and blank,\n * `null` (valid) otherwise. Pass straight to `useField({ validity: tf.validity })`.\n */\n validity: Readable<ValidityStateFlags | null>;\n /** The local mutable field value (two-way bound to the input element via `wire()`). */\n value: Signal<string>;\n /**\n * Attaches event listeners to the underlying input or textarea element.\n *\n * When `signal` is provided, the listeners are detached automatically when\n * the signal's controller calls `abort()`. Without a signal the returned\n * `TextFieldDetach` must be called manually.\n *\n * Safe to call multiple times: each call attaches a fresh set of listeners\n * and returns a distinct detach function. Calling detach multiple times is\n * a no-op (guarded internally).\n */\n wire: (el: HTMLInputElement | HTMLTextAreaElement, signal?: AbortSignal) => TextFieldDetach;\n};\n\nconst isBlank = (value: string): boolean => value.trim() === '';\nconst toFieldValue = (v: string | undefined): string => String(v ?? '');\n\nexport const createTextField = (options: TextFieldOptions): TextFieldHandle => {\n const value = syncedSignal(options.value, options.signal, toFieldValue);\n\n const field = createField(options);\n const counter = options.maxLength ? createCounterState({ maxLength: options.maxLength, value }) : null;\n\n const validity = computed<ValidityStateFlags | null>(() =>\n !options.readonly?.value && options.required?.value && isBlank(value.value) ? { valueMissing: true } : null,\n );\n const validationMessage = computed(() =>\n validity.value ? options.requiredMessage?.value || 'This field is required.' : '',\n );\n\n const reset = (): void => {\n value.value = toFieldValue(options.value.value);\n field.triggerValidation('change');\n };\n\n const clear = (event?: Event): void => {\n event?.preventDefault?.();\n value.value = '';\n options.onInput?.(event ?? new InputEvent('input', { bubbles: true }), '');\n options.onChange?.(event ?? new Event('change', { bubbles: true }), '');\n field.triggerValidation('change');\n };\n\n const wire = (element: HTMLInputElement | HTMLTextAreaElement, signal?: AbortSignal): TextFieldDetach => {\n const cleanups: Array<() => void> = [];\n const on = (type: string, handler: EventListener): void => {\n element.addEventListener(type, handler);\n cleanups.push(() => element.removeEventListener(type, handler));\n };\n\n if (options.onFocus) {\n on('focus', (e) => options.onFocus?.(e as FocusEvent));\n }\n\n on('input', (e) => {\n e.stopPropagation();\n options.onBeforeInput?.(e);\n value.value = element.value;\n options.onInput?.(e, element.value);\n });\n\n on('change', (e) => {\n e.stopPropagation();\n value.value = element.value;\n options.onChange?.(e, element.value);\n field.triggerValidation('change');\n });\n\n on('blur', (e) => {\n options.onBlur?.(e as FocusEvent);\n field.triggerValidation('blur');\n });\n\n let detached = false;\n const detach = (): void => {\n if (detached) return;\n\n detached = true;\n for (const dispose of cleanups) dispose();\n };\n\n signal?.addEventListener('abort', detach, { once: true });\n\n return detach;\n };\n\n return { ...field, clear, counter, reset, validationMessage, validity, value, wire };\n};\n"],"mappings":"8JAyFA,IAAM,QAAW,GAA2B,EAAM,KAAK,IAAM,GACvD,aAAgB,GAAkC,OAAO,GAAK,EAAE,EAEzD,gBAAmB,GAA+C,CAC7E,IAAM,EAAQ,EAAa,EAAQ,MAAO,EAAQ,OAAQ,YAAY,EAEhE,EAAQ,EAAY,CAAO,EAC3B,EAAU,EAAQ,UAAY,EAAmB,CAAE,UAAW,EAAQ,UAAW,OAAM,CAAC,EAAI,KAE5F,EAAW,MACf,CAAC,EAAQ,UAAU,OAAS,EAAQ,UAAU,OAAS,QAAQ,EAAM,KAAK,EAAI,CAAE,aAAc,EAAK,EAAI,IACzG,EACM,EAAoB,MACxB,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,0BAA4B,EACjF,EAEM,UAAoB,CACxB,EAAM,MAAQ,aAAa,EAAQ,MAAM,KAAK,EAC9C,EAAM,kBAAkB,QAAQ,CAClC,EAEM,MAAS,GAAwB,CACrC,GAAO,iBAAiB,EACxB,EAAM,MAAQ,GACd,EAAQ,UAAU,GAAS,IAAI,WAAW,QAAS,CAAE,QAAS,EAAK,CAAC,EAAG,EAAE,EACzE,EAAQ,WAAW,GAAS,IAAI,MAAM,SAAU,CAAE,QAAS,EAAK,CAAC,EAAG,EAAE,EACtE,EAAM,kBAAkB,QAAQ,CAClC,EAEM,MAAQ,EAAiD,IAA0C,CACvG,IAAM,EAA8B,CAAC,EAC/B,IAAM,EAAc,IAAiC,CACzD,EAAQ,iBAAiB,EAAM,CAAO,EACtC,EAAS,SAAW,EAAQ,oBAAoB,EAAM,CAAO,CAAC,CAChE,EAEI,EAAQ,SACV,GAAG,QAAU,GAAM,EAAQ,UAAU,CAAe,CAAC,EAGvD,GAAG,QAAU,GAAM,CACjB,EAAE,gBAAgB,EAClB,EAAQ,gBAAgB,CAAC,EACzB,EAAM,MAAQ,EAAQ,MACtB,EAAQ,UAAU,EAAG,EAAQ,KAAK,CACpC,CAAC,EAED,GAAG,SAAW,GAAM,CAClB,EAAE,gBAAgB,EAClB,EAAM,MAAQ,EAAQ,MACtB,EAAQ,WAAW,EAAG,EAAQ,KAAK,EACnC,EAAM,kBAAkB,QAAQ,CAClC,CAAC,EAED,GAAG,OAAS,GAAM,CAChB,EAAQ,SAAS,CAAe,EAChC,EAAM,kBAAkB,MAAM,CAChC,CAAC,EAED,IAAI,EAAW,GACT,WAAqB,CACrB,MAEJ,GAAW,GACX,IAAK,IAAM,KAAW,EAAU,EAAQ,CAD7B,CAEb,EAIA,OAFA,GAAQ,iBAAiB,QAAS,OAAQ,CAAE,KAAM,EAAK,CAAC,EAEjD,MACT,EAEA,MAAO,CAAE,GAAG,EAAO,MAAO,UAAS,MAAO,oBAAmB,WAAU,QAAO,IAAK,CACrF"}
1
+ {"version":3,"file":"text-field.js","names":[],"sources":["../../src/core/text-field.ts"],"sourcesContent":["import type { FormFieldValidity } from '@vielzeug/ore';\nimport { computed, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { type CounterState, createCounterState, createField, type FieldHandle, type FieldOptions } from './field-base';\nimport { syncedSignal } from './signals';\n\n// ── Text field ────────────────────────────────────────────────────────────────\n\n/** Detach function returned by `wire()`. Call to remove element listeners. */\nexport type TextFieldDetach = () => void;\n\nexport type TextFieldOptions = FieldOptions & {\n maxLength?: Readable<number | undefined>;\n /**\n * Called synchronously before value extraction on every input event.\n * Use only for DOM side-effects (e.g. auto-resize measurement) that must\n * run before the reactive value is updated.\n */\n onBeforeInput?: (event: Event) => void;\n onBlur?: (event: FocusEvent) => void;\n onChange?: (event: Event, value: string) => void;\n onFocus?: (event: FocusEvent) => void;\n onInput?: (event: Event, value: string) => void;\n /**\n * Bars the field from constraint validation while true: matches the native HTML rule that a\n * `readonly` field is never a candidate for constraint validation (a `readonly required`\n * field is always valid, per spec), so `validity` doesn't feed a false `valueMissing`\n * into `useField()` just because a read-only field happens to be blank.\n */\n readonly?: Readable<boolean | undefined>;\n /** Marks a blank value invalid: feeds `validity` (see below). */\n required?: Readable<boolean | undefined>;\n /** Message for the blank+required case. Defaults to `'This field is required.'`. */\n requiredMessage?: Readable<string | undefined>;\n /** `AbortSignal` from the component lifecycle. The internal value-sync watcher is disposed on abort. */\n signal: AbortSignal;\n value: Readable<string | undefined>;\n};\n\nexport type TextFieldHandle = FieldHandle & {\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()`: called internally on blur/change: can call its\n * `reportValidity()`. `useField()` itself needs `value` (below) to exist first, so this\n * is a two-step wiring rather than a constructor option:\n *\n * ```ts\n * const tf = createTextField({ value: props.value, ... });\n * const formField = useField({ value: tf.value, ... });\n * tf.attachFormField(formField);\n * ```\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n /** Clears the field value and fires synthetic input/change events. */\n clear: (event?: Event) => void;\n /**\n * Reactive counter state. Non-null when `maxLength` was provided; `null` otherwise.\n * Components should only render a counter element when this is non-null.\n */\n counter: Readable<CounterState> | null;\n /**\n * Restores the value to whatever the `value` option currently holds (native form\n * `reset()` semantics: a `<input>` reverts to its *current* `value` content attribute,\n * not a frozen snapshot from element creation, so setting the attribute programmatically\n * after mount changes what a later reset reverts to). Wire into `useField({ onReset: tf.reset })`.\n */\n reset: () => void;\n /**\n * Reactive field validity: `{ flags: { valueMissing: true }, message }` while `required`\n * and blank (a readonly field is always valid, per spec), `null` (valid) otherwise.\n * Pass straight to `useField({ validity: tf.validity })`.\n */\n validity: Readable<FormFieldValidity | null>;\n /** The local mutable field value (two-way bound to the input element via `wire()`). */\n value: Signal<string>;\n /**\n * Attaches event listeners to the underlying input or textarea element.\n *\n * When `signal` is provided, the listeners are detached automatically when\n * the signal's controller calls `abort()`. Without a signal the returned\n * `TextFieldDetach` must be called manually.\n *\n * Safe to call multiple times: each call attaches a fresh set of listeners\n * and returns a distinct detach function. Calling detach multiple times is\n * a no-op (guarded internally).\n */\n wire: (el: HTMLInputElement | HTMLTextAreaElement, signal?: AbortSignal) => TextFieldDetach;\n};\n\nconst isBlank = (value: string): boolean => value.trim() === '';\nconst toFieldValue = (v: string | undefined): string => String(v ?? '');\n\nexport const createTextField = (options: TextFieldOptions): TextFieldHandle => {\n const value = syncedSignal(options.value, options.signal, toFieldValue);\n\n const field = createField(options);\n const counter = options.maxLength ? createCounterState({ maxLength: options.maxLength, value }) : null;\n\n const validity = computed<FormFieldValidity | null>(() =>\n !options.readonly?.value && options.required?.value && isBlank(value.value)\n ? { flags: { valueMissing: true }, message: options.requiredMessage?.value || 'This field is required.' }\n : null,\n );\n\n const reset = (): void => {\n value.value = toFieldValue(options.value.value);\n field.triggerValidation('change');\n };\n\n const clear = (event?: Event): void => {\n event?.preventDefault?.();\n value.value = '';\n options.onInput?.(event ?? new InputEvent('input', { bubbles: true }), '');\n options.onChange?.(event ?? new Event('change', { bubbles: true }), '');\n field.triggerValidation('change');\n };\n\n const wire = (element: HTMLInputElement | HTMLTextAreaElement, signal?: AbortSignal): TextFieldDetach => {\n const cleanups: Array<() => void> = [];\n const on = (type: string, handler: EventListener): void => {\n element.addEventListener(type, handler);\n cleanups.push(() => element.removeEventListener(type, handler));\n };\n\n if (options.onFocus) {\n on('focus', (e) => options.onFocus?.(e as FocusEvent));\n }\n\n on('input', (e) => {\n e.stopPropagation();\n options.onBeforeInput?.(e);\n value.value = element.value;\n options.onInput?.(e, element.value);\n });\n\n on('change', (e) => {\n e.stopPropagation();\n value.value = element.value;\n options.onChange?.(e, element.value);\n field.triggerValidation('change');\n });\n\n on('blur', (e) => {\n options.onBlur?.(e as FocusEvent);\n field.triggerValidation('blur');\n });\n\n let detached = false;\n const detach = (): void => {\n if (detached) return;\n\n detached = true;\n for (const dispose of cleanups) dispose();\n };\n\n signal?.addEventListener('abort', detach, { once: true });\n\n return detach;\n };\n\n return { ...field, clear, counter, reset, validity, value, wire };\n};\n"],"mappings":"8JAyFA,IAAM,QAAW,GAA2B,EAAM,KAAK,IAAM,GACvD,aAAgB,GAAkC,OAAO,GAAK,EAAE,EAEzD,gBAAmB,GAA+C,CAC7E,IAAM,EAAQ,EAAa,EAAQ,MAAO,EAAQ,OAAQ,YAAY,EAEhE,EAAQ,EAAY,CAAO,EAC3B,EAAU,EAAQ,UAAY,EAAmB,CAAE,UAAW,EAAQ,UAAW,OAAM,CAAC,EAAI,KAE5F,EAAW,MACf,CAAC,EAAQ,UAAU,OAAS,EAAQ,UAAU,OAAS,QAAQ,EAAM,KAAK,EACtE,CAAE,MAAO,CAAE,aAAc,EAAK,EAAG,QAAS,EAAQ,iBAAiB,OAAS,yBAA0B,EACtG,IACN,EAEM,UAAoB,CACxB,EAAM,MAAQ,aAAa,EAAQ,MAAM,KAAK,EAC9C,EAAM,kBAAkB,QAAQ,CAClC,EAEM,MAAS,GAAwB,CACrC,GAAO,iBAAiB,EACxB,EAAM,MAAQ,GACd,EAAQ,UAAU,GAAS,IAAI,WAAW,QAAS,CAAE,QAAS,EAAK,CAAC,EAAG,EAAE,EACzE,EAAQ,WAAW,GAAS,IAAI,MAAM,SAAU,CAAE,QAAS,EAAK,CAAC,EAAG,EAAE,EACtE,EAAM,kBAAkB,QAAQ,CAClC,EAEM,MAAQ,EAAiD,IAA0C,CACvG,IAAM,EAA8B,CAAC,EAC/B,IAAM,EAAc,IAAiC,CACzD,EAAQ,iBAAiB,EAAM,CAAO,EACtC,EAAS,SAAW,EAAQ,oBAAoB,EAAM,CAAO,CAAC,CAChE,EAEI,EAAQ,SACV,GAAG,QAAU,GAAM,EAAQ,UAAU,CAAe,CAAC,EAGvD,GAAG,QAAU,GAAM,CACjB,EAAE,gBAAgB,EAClB,EAAQ,gBAAgB,CAAC,EACzB,EAAM,MAAQ,EAAQ,MACtB,EAAQ,UAAU,EAAG,EAAQ,KAAK,CACpC,CAAC,EAED,GAAG,SAAW,GAAM,CAClB,EAAE,gBAAgB,EAClB,EAAM,MAAQ,EAAQ,MACtB,EAAQ,WAAW,EAAG,EAAQ,KAAK,EACnC,EAAM,kBAAkB,QAAQ,CAClC,CAAC,EAED,GAAG,OAAS,GAAM,CAChB,EAAQ,SAAS,CAAe,EAChC,EAAM,kBAAkB,MAAM,CAChC,CAAC,EAED,IAAI,EAAW,GACT,WAAqB,CACrB,MAEJ,GAAW,GACX,IAAK,IAAM,KAAW,EAAU,EAAQ,CAD7B,CAEb,EAIA,OAFA,GAAQ,iBAAiB,QAAS,OAAQ,CAAE,KAAM,EAAK,CAAC,EAEjD,MACT,EAEA,MAAO,CAAE,GAAG,EAAO,MAAO,UAAS,MAAO,WAAU,QAAO,IAAK,CAClE"}