@vielzeug/refine 1.5.3 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/dist/accordion.cjs +1 -1
  2. package/dist/accordion.cjs.map +1 -1
  3. package/dist/accordion.js +30 -28
  4. package/dist/accordion.js.map +1 -1
  5. package/dist/button-group.cjs.map +1 -1
  6. package/dist/button-group.js.map +1 -1
  7. package/dist/button.cjs +1 -1
  8. package/dist/button.cjs.map +1 -1
  9. package/dist/button.js +1 -0
  10. package/dist/button.js.map +1 -1
  11. package/dist/checkbox-group.cjs +3 -3
  12. package/dist/checkbox-group.cjs.map +1 -1
  13. package/dist/checkbox-group.js +51 -58
  14. package/dist/checkbox-group.js.map +1 -1
  15. package/dist/combobox.cjs +29 -28
  16. package/dist/combobox.cjs.map +1 -1
  17. package/dist/combobox.js +201 -195
  18. package/dist/combobox.js.map +1 -1
  19. package/dist/content/card/card.css.cjs +1 -1
  20. package/dist/content/card/card.css.cjs.map +1 -1
  21. package/dist/content/card/card.css.js +1 -1
  22. package/dist/content/card/card.css.js.map +1 -1
  23. package/dist/content/chat-message/chat-message.css.cjs +1 -1
  24. package/dist/content/chat-message/chat-message.css.cjs.map +1 -1
  25. package/dist/content/chat-message/chat-message.css.js +1 -1
  26. package/dist/content/chat-message/chat-message.css.js.map +1 -1
  27. package/dist/content/icon/icon.d.ts.map +1 -1
  28. package/dist/content/index.d.ts +4 -0
  29. package/dist/content/index.d.ts.map +1 -1
  30. package/dist/content/list/_list-item-reveal.cjs +2 -0
  31. package/dist/content/list/_list-item-reveal.cjs.map +1 -0
  32. package/dist/content/list/_list-item-reveal.d.ts +25 -0
  33. package/dist/content/list/_list-item-reveal.d.ts.map +1 -0
  34. package/dist/content/list/_list-item-reveal.js +33 -0
  35. package/dist/content/list/_list-item-reveal.js.map +1 -0
  36. package/dist/content/list/list-item.css.cjs +2 -0
  37. package/dist/content/list/list-item.css.cjs.map +1 -0
  38. package/dist/content/list/list-item.css.js +6 -0
  39. package/dist/content/list/list-item.css.js.map +1 -0
  40. package/dist/content/list/list-item.d.ts +111 -0
  41. package/dist/content/list/list-item.d.ts.map +1 -0
  42. package/dist/content/list/list.css.cjs +2 -0
  43. package/dist/content/list/list.css.cjs.map +1 -0
  44. package/dist/content/list/list.css.js +6 -0
  45. package/dist/content/list/list.css.js.map +1 -0
  46. package/dist/content/list/list.d.ts +93 -0
  47. package/dist/content/list/list.d.ts.map +1 -0
  48. package/dist/custom-elements.json +5224 -4621
  49. package/dist/date-picker.cjs +2 -1
  50. package/dist/date-picker.cjs.map +1 -1
  51. package/dist/date-picker.js +2 -0
  52. package/dist/date-picker.js.map +1 -1
  53. package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
  54. package/dist/disclosure/tab-panel/tab-panel.d.ts.map +1 -1
  55. package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
  56. package/dist/drawer.cjs +1 -1
  57. package/dist/drawer.cjs.map +1 -1
  58. package/dist/drawer.js +47 -48
  59. package/dist/drawer.js.map +1 -1
  60. package/dist/file-input.cjs +111 -45
  61. package/dist/file-input.cjs.map +1 -1
  62. package/dist/file-input.js +216 -156
  63. package/dist/file-input.js.map +1 -1
  64. package/dist/headless/aria-reflection.cjs +2 -0
  65. package/dist/headless/aria-reflection.cjs.map +1 -0
  66. package/dist/headless/aria-reflection.d.ts +27 -0
  67. package/dist/headless/aria-reflection.d.ts.map +1 -0
  68. package/dist/headless/aria-reflection.js +18 -0
  69. package/dist/headless/aria-reflection.js.map +1 -0
  70. package/dist/headless/checkable.cjs +1 -1
  71. package/dist/headless/checkable.cjs.map +1 -1
  72. package/dist/headless/checkable.d.ts +2 -10
  73. package/dist/headless/checkable.d.ts.map +1 -1
  74. package/dist/headless/checkable.js +29 -29
  75. package/dist/headless/checkable.js.map +1 -1
  76. package/dist/headless/choice-field.cjs +1 -1
  77. package/dist/headless/choice-field.cjs.map +1 -1
  78. package/dist/headless/choice-field.d.ts +2 -7
  79. package/dist/headless/choice-field.d.ts.map +1 -1
  80. package/dist/headless/choice-field.js +37 -37
  81. package/dist/headless/choice-field.js.map +1 -1
  82. package/dist/headless/field-base.cjs +1 -1
  83. package/dist/headless/field-base.cjs.map +1 -1
  84. package/dist/headless/field-base.d.ts +17 -0
  85. package/dist/headless/field-base.d.ts.map +1 -1
  86. package/dist/headless/field-base.js +21 -6
  87. package/dist/headless/field-base.js.map +1 -1
  88. package/dist/headless/focus.cjs +1 -1
  89. package/dist/headless/focus.cjs.map +1 -1
  90. package/dist/headless/focus.d.ts +7 -0
  91. package/dist/headless/focus.d.ts.map +1 -1
  92. package/dist/headless/focus.js +6 -6
  93. package/dist/headless/focus.js.map +1 -1
  94. package/dist/headless/index.d.ts +4 -2
  95. package/dist/headless/index.d.ts.map +1 -1
  96. package/dist/headless/nav.cjs +1 -1
  97. package/dist/headless/nav.cjs.map +1 -1
  98. package/dist/headless/nav.d.ts +12 -0
  99. package/dist/headless/nav.d.ts.map +1 -1
  100. package/dist/headless/nav.js +68 -50
  101. package/dist/headless/nav.js.map +1 -1
  102. package/dist/headless/overlay.cjs +1 -1
  103. package/dist/headless/overlay.cjs.map +1 -1
  104. package/dist/headless/overlay.d.ts +2 -0
  105. package/dist/headless/overlay.d.ts.map +1 -1
  106. package/dist/headless/overlay.js +8 -5
  107. package/dist/headless/overlay.js.map +1 -1
  108. package/dist/headless/ref-callback.cjs +2 -0
  109. package/dist/headless/ref-callback.cjs.map +1 -0
  110. package/dist/headless/ref-callback.d.ts +26 -0
  111. package/dist/headless/ref-callback.d.ts.map +1 -0
  112. package/dist/headless/ref-callback.js +13 -0
  113. package/dist/headless/ref-callback.js.map +1 -0
  114. package/dist/headless/swipe.cjs +1 -1
  115. package/dist/headless/swipe.cjs.map +1 -1
  116. package/dist/headless/swipe.d.ts +2 -0
  117. package/dist/headless/swipe.d.ts.map +1 -1
  118. package/dist/headless/swipe.js +26 -21
  119. package/dist/headless/swipe.js.map +1 -1
  120. package/dist/headless.cjs +1 -1
  121. package/dist/headless.js +28 -26
  122. package/dist/icon.cjs +1 -1
  123. package/dist/icon.cjs.map +1 -1
  124. package/dist/icon.js +36 -38
  125. package/dist/icon.js.map +1 -1
  126. package/dist/index.cjs +1 -1
  127. package/dist/index.js +63 -61
  128. package/dist/input.cjs +29 -26
  129. package/dist/input.cjs.map +1 -1
  130. package/dist/input.js +91 -79
  131. package/dist/input.js.map +1 -1
  132. package/dist/inputs/button/button.css.cjs +1 -1
  133. package/dist/inputs/button/button.css.cjs.map +1 -1
  134. package/dist/inputs/button/button.css.js +1 -1
  135. package/dist/inputs/button/button.css.js.map +1 -1
  136. package/dist/inputs/button/button.d.ts +8 -0
  137. package/dist/inputs/button/button.d.ts.map +1 -1
  138. package/dist/inputs/checkbox/checkbox.css.cjs +1 -1
  139. package/dist/inputs/checkbox/checkbox.css.cjs.map +1 -1
  140. package/dist/inputs/checkbox/checkbox.css.js +1 -1
  141. package/dist/inputs/checkbox/checkbox.css.js.map +1 -1
  142. package/dist/inputs/combobox/combobox.d.ts +1 -0
  143. package/dist/inputs/combobox/combobox.d.ts.map +1 -1
  144. package/dist/inputs/date-picker/date-picker.css.cjs +1 -1
  145. package/dist/inputs/date-picker/date-picker.css.cjs.map +1 -1
  146. package/dist/inputs/date-picker/date-picker.css.js +1 -1
  147. package/dist/inputs/date-picker/date-picker.css.js.map +1 -1
  148. package/dist/inputs/date-picker/date-picker.d.ts +6 -0
  149. package/dist/inputs/date-picker/date-picker.d.ts.map +1 -1
  150. package/dist/inputs/file-input/file-input-upload.cjs +2 -0
  151. package/dist/inputs/file-input/file-input-upload.cjs.map +1 -0
  152. package/dist/inputs/file-input/file-input-upload.d.ts +76 -0
  153. package/dist/inputs/file-input/file-input-upload.d.ts.map +1 -0
  154. package/dist/inputs/file-input/file-input-upload.js +145 -0
  155. package/dist/inputs/file-input/file-input-upload.js.map +1 -0
  156. package/dist/inputs/file-input/file-input.css.cjs +1 -1
  157. package/dist/inputs/file-input/file-input.css.cjs.map +1 -1
  158. package/dist/inputs/file-input/file-input.css.js +1 -1
  159. package/dist/inputs/file-input/file-input.css.js.map +1 -1
  160. package/dist/inputs/file-input/file-input.d.ts +54 -1
  161. package/dist/inputs/file-input/file-input.d.ts.map +1 -1
  162. package/dist/inputs/input/input.css.cjs +1 -1
  163. package/dist/inputs/input/input.css.cjs.map +1 -1
  164. package/dist/inputs/input/input.css.js +1 -1
  165. package/dist/inputs/input/input.css.js.map +1 -1
  166. package/dist/inputs/input/input.d.ts +13 -0
  167. package/dist/inputs/input/input.d.ts.map +1 -1
  168. package/dist/inputs/message-composer/message-composer.css.cjs +1 -1
  169. package/dist/inputs/message-composer/message-composer.css.cjs.map +1 -1
  170. package/dist/inputs/message-composer/message-composer.css.js +1 -1
  171. package/dist/inputs/message-composer/message-composer.css.js.map +1 -1
  172. package/dist/inputs/message-composer/message-composer.d.ts +7 -0
  173. package/dist/inputs/message-composer/message-composer.d.ts.map +1 -1
  174. package/dist/inputs/number-input/number-input.css.cjs +1 -1
  175. package/dist/inputs/number-input/number-input.css.cjs.map +1 -1
  176. package/dist/inputs/number-input/number-input.css.js +1 -1
  177. package/dist/inputs/number-input/number-input.css.js.map +1 -1
  178. package/dist/inputs/number-input/number-input.d.ts +15 -0
  179. package/dist/inputs/number-input/number-input.d.ts.map +1 -1
  180. package/dist/inputs/otp-input/otp-input.css.cjs +1 -1
  181. package/dist/inputs/otp-input/otp-input.css.cjs.map +1 -1
  182. package/dist/inputs/otp-input/otp-input.css.js +1 -1
  183. package/dist/inputs/otp-input/otp-input.css.js.map +1 -1
  184. package/dist/inputs/radio/radio.d.ts.map +1 -1
  185. package/dist/inputs/select/select.d.ts +1 -0
  186. package/dist/inputs/select/select.d.ts.map +1 -1
  187. package/dist/inputs/shared/index.d.ts +1 -1
  188. package/dist/inputs/shared/index.d.ts.map +1 -1
  189. package/dist/inputs/shared/templates.cjs +26 -3
  190. package/dist/inputs/shared/templates.cjs.map +1 -1
  191. package/dist/inputs/shared/templates.d.ts +20 -0
  192. package/dist/inputs/shared/templates.d.ts.map +1 -1
  193. package/dist/inputs/shared/templates.js +29 -5
  194. package/dist/inputs/shared/templates.js.map +1 -1
  195. package/dist/inputs/switch/switch.css.cjs +1 -1
  196. package/dist/inputs/switch/switch.css.cjs.map +1 -1
  197. package/dist/inputs/switch/switch.css.js +1 -1
  198. package/dist/inputs/switch/switch.css.js.map +1 -1
  199. package/dist/inputs/switch/switch.d.ts.map +1 -1
  200. package/dist/inputs/textarea/textarea.css.cjs +1 -1
  201. package/dist/inputs/textarea/textarea.css.cjs.map +1 -1
  202. package/dist/inputs/textarea/textarea.css.js +1 -1
  203. package/dist/inputs/textarea/textarea.css.js.map +1 -1
  204. package/dist/inputs/textarea/textarea.d.ts +14 -0
  205. package/dist/inputs/textarea/textarea.d.ts.map +1 -1
  206. package/dist/inputs/time-picker/time-picker.css.cjs +1 -1
  207. package/dist/inputs/time-picker/time-picker.css.cjs.map +1 -1
  208. package/dist/inputs/time-picker/time-picker.css.js +1 -1
  209. package/dist/inputs/time-picker/time-picker.css.js.map +1 -1
  210. package/dist/inputs/time-picker/time-picker.d.ts +6 -0
  211. package/dist/inputs/time-picker/time-picker.d.ts.map +1 -1
  212. package/dist/list-item.cjs +22 -0
  213. package/dist/list-item.cjs.map +1 -0
  214. package/dist/list-item.js +91 -0
  215. package/dist/list-item.js.map +1 -0
  216. package/dist/list.cjs +2 -0
  217. package/dist/list.cjs.map +1 -0
  218. package/dist/list.js +64 -0
  219. package/dist/list.js.map +1 -0
  220. package/dist/menu.cjs +4 -4
  221. package/dist/menu.cjs.map +1 -1
  222. package/dist/menu.js +60 -59
  223. package/dist/menu.js.map +1 -1
  224. package/dist/message-composer.cjs +14 -20
  225. package/dist/message-composer.cjs.map +1 -1
  226. package/dist/message-composer.js +63 -64
  227. package/dist/message-composer.js.map +1 -1
  228. package/dist/number-input.cjs +5 -3
  229. package/dist/number-input.cjs.map +1 -1
  230. package/dist/number-input.js +8 -4
  231. package/dist/number-input.js.map +1 -1
  232. package/dist/otp-input.cjs +9 -9
  233. package/dist/otp-input.cjs.map +1 -1
  234. package/dist/otp-input.js +70 -62
  235. package/dist/otp-input.js.map +1 -1
  236. package/dist/overlay/menu/menu-item.css.cjs +1 -1
  237. package/dist/overlay/menu/menu-item.css.cjs.map +1 -1
  238. package/dist/overlay/menu/menu-item.css.js +1 -1
  239. package/dist/overlay/menu/menu-item.css.js.map +1 -1
  240. package/dist/overlay/menu/menu-separator.css.cjs +1 -1
  241. package/dist/overlay/menu/menu-separator.css.cjs.map +1 -1
  242. package/dist/overlay/menu/menu-separator.css.js +1 -1
  243. package/dist/overlay/menu/menu-separator.css.js.map +1 -1
  244. package/dist/overlay/menu/menu.css.cjs +1 -1
  245. package/dist/overlay/menu/menu.css.cjs.map +1 -1
  246. package/dist/overlay/menu/menu.css.js +1 -1
  247. package/dist/overlay/menu/menu.css.js.map +1 -1
  248. package/dist/overlay/shared/use-dialog.cjs +1 -1
  249. package/dist/overlay/shared/use-dialog.cjs.map +1 -1
  250. package/dist/overlay/shared/use-dialog.d.ts.map +1 -1
  251. package/dist/overlay/shared/use-dialog.js +7 -6
  252. package/dist/overlay/shared/use-dialog.js.map +1 -1
  253. package/dist/radio-group.cjs +3 -3
  254. package/dist/radio-group.cjs.map +1 -1
  255. package/dist/radio-group.js +64 -70
  256. package/dist/radio-group.js.map +1 -1
  257. package/dist/radio.cjs +4 -4
  258. package/dist/radio.cjs.map +1 -1
  259. package/dist/radio.js +58 -58
  260. package/dist/radio.js.map +1 -1
  261. package/dist/rating.cjs.map +1 -1
  262. package/dist/rating.js.map +1 -1
  263. package/dist/refine.cjs +426 -233
  264. package/dist/refine.cjs.map +1 -1
  265. package/dist/refine.iife.js +431 -238
  266. package/dist/refine.iife.js.map +1 -1
  267. package/dist/refine.js +452 -259
  268. package/dist/refine.js.map +1 -1
  269. package/dist/select.cjs +21 -21
  270. package/dist/select.cjs.map +1 -1
  271. package/dist/select.js +135 -127
  272. package/dist/select.js.map +1 -1
  273. package/dist/shared/size-presets.cjs +1 -1
  274. package/dist/shared/size-presets.cjs.map +1 -1
  275. package/dist/shared/size-presets.d.ts +74 -0
  276. package/dist/shared/size-presets.d.ts.map +1 -1
  277. package/dist/shared/size-presets.js +48 -1
  278. package/dist/shared/size-presets.js.map +1 -1
  279. package/dist/shared/types.d.ts +5 -0
  280. package/dist/shared/types.d.ts.map +1 -1
  281. package/dist/styles/index.cjs +1 -1
  282. package/dist/styles/index.cjs.map +1 -1
  283. package/dist/styles/index.d.ts +1 -0
  284. package/dist/styles/index.d.ts.map +1 -1
  285. package/dist/styles/index.js +5 -4
  286. package/dist/styles/index.js.map +1 -1
  287. package/dist/styles/mixins/field-variants.css.cjs +85 -0
  288. package/dist/styles/mixins/field-variants.css.cjs.map +1 -0
  289. package/dist/styles/mixins/field-variants.css.d.ts +66 -0
  290. package/dist/styles/mixins/field-variants.css.d.ts.map +1 -0
  291. package/dist/styles/mixins/field-variants.css.js +90 -0
  292. package/dist/styles/mixins/field-variants.css.js.map +1 -0
  293. package/dist/switch.cjs +3 -3
  294. package/dist/switch.cjs.map +1 -1
  295. package/dist/switch.js +38 -36
  296. package/dist/switch.js.map +1 -1
  297. package/dist/tab-item.cjs +8 -8
  298. package/dist/tab-item.cjs.map +1 -1
  299. package/dist/tab-item.js +38 -28
  300. package/dist/tab-item.js.map +1 -1
  301. package/dist/tab-panel.cjs +7 -7
  302. package/dist/tab-panel.cjs.map +1 -1
  303. package/dist/tab-panel.js +34 -26
  304. package/dist/tab-panel.js.map +1 -1
  305. package/dist/tabs.cjs +4 -4
  306. package/dist/tabs.cjs.map +1 -1
  307. package/dist/tabs.js +64 -60
  308. package/dist/tabs.js.map +1 -1
  309. package/dist/textarea.cjs +15 -18
  310. package/dist/textarea.cjs.map +1 -1
  311. package/dist/textarea.js +83 -74
  312. package/dist/textarea.js.map +1 -1
  313. package/dist/time-picker.cjs +34 -31
  314. package/dist/time-picker.cjs.map +1 -1
  315. package/dist/time-picker.js +121 -109
  316. package/dist/time-picker.js.map +1 -1
  317. package/package.json +21 -9
  318. package/dist/headless/dialog-focus.cjs +0 -2
  319. package/dist/headless/dialog-focus.cjs.map +0 -1
  320. package/dist/headless/dialog-focus.d.ts +0 -40
  321. package/dist/headless/dialog-focus.d.ts.map +0 -1
  322. package/dist/headless/dialog-focus.js +0 -22
  323. package/dist/headless/dialog-focus.js.map +0 -1
  324. package/dist/headless/focus-trap.cjs +0 -2
  325. package/dist/headless/focus-trap.cjs.map +0 -1
  326. package/dist/headless/focus-trap.d.ts +0 -45
  327. package/dist/headless/focus-trap.d.ts.map +0 -1
  328. package/dist/headless/focus-trap.js +0 -51
  329. package/dist/headless/focus-trap.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"file-input.js","names":[],"sources":["../src/inputs/file-input/file-input.ts"],"sourcesContent":["import { createDropZone, matchesAccept } from '@vielzeug/dnd';\nimport {\n createStableId,\n define,\n html,\n inject,\n prop,\n ref,\n bind,\n onCleanup,\n onElement,\n onEvent,\n onMounted,\n useEmit,\n} from '@vielzeug/ore';\nimport { useField } from '@vielzeug/ore/forms';\nimport { computed, signal, watch } from '@vielzeug/ripple';\n\nimport { createInteraction } from '../../headless';\nimport { FILE_INPUT_SIZE_PRESET } from '../../shared';\nimport { fieldMixins, forcedColorsFocusMixin, sizeVariantMixin } from '../../styles';\nimport { FORM_CTX, useFormContext } from '../shared/form-context';\nimport componentStyles from './file-input.css?inline';\n\nconst formatBytes = (bytes: number) => {\n if (bytes === 0) return '0 B';\n\n const k = 1024;\n const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];\n};\n\nconst isFileSizeAllowed = (file: File, maxSize?: number) => !maxSize || file.size <= maxSize;\n\nconst isImageFile = (file: File): boolean => file.type.startsWith('image/');\n\n/** File input component properties */\nexport type OreFileInputProps = {\n /** Accepted file types (comma-separated, e.g. '.jpg, .png, image/*') */\n accept?: string;\n /** Theme color tint */\n color?: string;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /**\n * Render selected files as a grid of image thumbnails/previews instead of the default\n * single-column list. Non-image files fall back to a generic file icon in the same grid.\n */\n gallery?: boolean;\n /** Helper text displayed below the input */\n helper?: string;\n /** Input label text */\n label?: string;\n /** Max number of files allowed (only used if multiple is true) */\n 'max-files'?: number;\n /** Max size of a single file in bytes */\n 'max-size'?: number;\n /** Allow multiple files selection */\n multiple?: boolean;\n /** Form field name */\n name?: string;\n /**\n * JS-only callback fired with the inner `<input type=\"file\">` element when it\n * mounts, and with `null` when it unmounts. Intended for composed components\n * that need imperative access to the raw element.\n * Set as a JS property: `bitFileInput.ref = (el) => { ... }`.\n */\n ref?: ((el: HTMLInputElement | null) => void) | null;\n /** Required field */\n required?: boolean;\n /** Field size preset */\n size?: string;\n};\n\n/** Events emitted by the file-input component */\nexport type OreFileInputEvents = {\n /** Emitted when files are added or removed */\n change: { files: File[]; originalEvent?: Event; value: File[] };\n /** Emitted when a specific file is removed */\n remove: { file: File; files: File[]; originalEvent?: Event; value: File[] };\n};\n\n/**\n * A file upload field with drag-and-drop support and built-in validation messaging.\n *\n * @element ore-file-input\n *\n * @attr {string} accept - Comma-separated file extensions or MIME types\n * @attr {boolean} multiple - Enable multiple files selection\n * @attr {boolean} gallery - Show selected files as an image thumbnail/preview grid\n * @attr {number} max-files - Max number of files allowed\n * @attr {number} max-size - Max size of each file in bytes\n * @attr {boolean} disabled - Disable interaction\n * @attr {string} error - Show an error state/message\n * @attr {string} helper - Provide helper context below the dropzone\n *\n * @fires change - detail: { files: File[], value: File[] }\n * @fires remove - detail: { file: File, files: File[] }\n *\n * @cssprop --file-input-bg - Dropzone background color\n * @cssprop --file-input-border-color - Dropzone border color\n * @cssprop --file-input-font-size - Font size\n * @cssprop --file-input-radius - Dropzone border radius\n * @cssprop --file-input-min-height - Minimum dropzone height\n * @cssprop --file-input-hover-bg - Dropzone background on hover (flat/ghost variants)\n * @cssprop --file-input-hover-border-color - Dropzone border on hover (flat/bordered variants)\n * @cssprop --file-input-focus-bg - Dropzone background when focused/drag-over (flat variant)\n * @cssprop --file-input-focus-border-color - Dropzone border when focused/drag-over (flat variant)\n * @cssprop --file-input-thumb-size - Gallery thumbnail width/height\n * @part wrapper - Root wrapper around the file input field\n * @part label - Visible label rendered above the dropzone\n * @part dropzone - Interactive drag-and-drop target\n * @part input - Native file input element\n * @part gallery - Gallery grid container (rendered instead of `file-list` when `gallery` is set)\n * @part helper - Helper text shown beneath the dropzone\n * @part error - Error message shown beneath the field\n * @example\n * ```html\n * <ore-file-input label=\"Upload files\" accept=\"image/*\" multiple />\n * <ore-file-input label=\"Photos\" accept=\"image/*\" multiple gallery />\n * <ore-file-input label=\"Resume\" accept=\".pdf,.doc,.docx\" max-size=\"5242880\" />\n * <ore-file-input variant=\"bordered\" color=\"primary\" />\n * ```\n */\nexport const FILE_INPUT_TAG = 'ore-file-input' as const;\ndefine<OreFileInputProps>(FILE_INPUT_TAG, {\n formAssociated: true,\n props: {\n accept: prop.string(),\n color: prop.string(),\n disabled: prop.bool(false),\n error: prop.string(),\n gallery: prop.bool(false),\n helper: prop.string(),\n label: prop.string(),\n 'max-files': prop.number(0),\n 'max-size': prop.number(0),\n multiple: prop.bool(false),\n name: prop.string(),\n ref: prop.json(undefined as ((el: HTMLInputElement | null) => void) | null | undefined),\n required: prop.bool(false),\n size: prop.string(),\n },\n setup(props) {\n const emit = useEmit<OreFileInputEvents>();\n\n // ============================================\n // State\n // ============================================\n\n const files = signal<File[]>([]);\n const isDragging = signal(false);\n const formCtx = inject(FORM_CTX);\n const fCtxProps = useFormContext(props, formCtx);\n const isDisabled = fCtxProps.disabled;\n const maxFilesLimit = computed(() => props['max-files'].value ?? 0);\n const maxSizeLimit = computed(() => props['max-size'].value ?? 0);\n\n // ============================================\n // Form Integration\n // ============================================\n\n useField({\n disabled: isDisabled,\n toFormValue: (fi: File[]) => {\n if (fi.length === 0) return null;\n\n const name = props.name.value || 'file';\n const fd = new FormData();\n\n for (const file of fi) fd.append(name, file);\n\n return fd;\n },\n value: files,\n });\n\n // Sync host attributes for CSS selectors\n const isInvalid = computed(() => Boolean(props.error.value));\n\n bind({\n attr: {\n 'drag-over': () => (isDragging.value ? true : undefined),\n invalid: () => (isInvalid.value ? true : undefined),\n size: fCtxProps.size,\n },\n });\n\n // ============================================\n // IDs\n // ============================================\n const fileInputId = createStableId('file-input');\n const labelId = `label-${fileInputId}`;\n const helperId = `helper-${fileInputId}`;\n const errorId = `error-${fileInputId}`;\n\n // ============================================\n // Refs\n // ============================================\n const dropzoneRef = ref<HTMLDivElement>();\n const inputRef = ref<HTMLInputElement>();\n const hintText = computed(() => {\n const parts: string[] = [];\n\n if (props.accept.value) {\n parts.push(\n props.accept.value\n .split(',')\n .map((s: string) => s.trim())\n .join(', '),\n );\n }\n\n const maxSize = maxSizeLimit.value;\n\n if (maxSize > 0) parts.push(`max ${formatBytes(maxSize)}`);\n\n const maxFiles = maxFilesLimit.value;\n\n if (maxFiles > 0) parts.push(`up to ${maxFiles} file${maxFiles !== 1 ? 's' : ''}`);\n\n return parts.join(' · ');\n });\n\n // ============================================\n // File Management\n // ============================================\n function addFiles(newFiles: File[], originalEvent?: Event): void {\n if (isDisabled.value) return;\n\n const acceptVal = props.accept.value;\n const isMultiple = Boolean(props.multiple.value);\n let incoming = Array.from(newFiles);\n\n if (!isMultiple) incoming = incoming.slice(0, 1);\n\n incoming = incoming.filter(\n (f) =>\n matchesAccept(f, acceptVal ? acceptVal.split(',').map((t) => t.trim()) : []) &&\n isFileSizeAllowed(f, maxSizeLimit.value),\n );\n\n let updated: File[] = isMultiple ? [...files.value] : [];\n\n for (const f of incoming) {\n if (!updated.includes(f)) updated.push(f);\n }\n\n if (maxFilesLimit.value > 0 && updated.length > maxFilesLimit.value) {\n updated = updated.slice(0, maxFilesLimit.value);\n }\n\n files.value = updated;\n emit('change', { files: files.value, originalEvent, value: files.value });\n }\n function removeFile(file: File, originalEvent?: Event): void {\n files.value = files.value.filter((f) => f !== file);\n emit('remove', { file, files: files.value, originalEvent, value: files.value });\n emit('change', { files: files.value, originalEvent, value: files.value });\n }\n\n // ============================================\n // Gallery Preview URLs\n // ============================================\n // Object URLs are created lazily (only while `gallery` is enabled) and revoked as soon\n // as their file is no longer selected, plus unconditionally on disconnect — otherwise\n // each preview leaks its backing blob for the life of the page.\n const previewUrls = new Map<File, string>();\n\n function getPreviewUrl(file: File): string {\n let url = previewUrls.get(file);\n\n if (!url) {\n url = URL.createObjectURL(file);\n previewUrls.set(file, url);\n }\n\n return url;\n }\n\n function revokeStalePreviewUrls(activeFiles: File[]): void {\n const keep = new Set(activeFiles);\n\n for (const [file, url] of previewUrls) {\n if (!keep.has(file)) {\n URL.revokeObjectURL(url);\n previewUrls.delete(file);\n }\n }\n }\n\n watch(files, revokeStalePreviewUrls);\n\n onCleanup(() => {\n for (const url of previewUrls.values()) URL.revokeObjectURL(url);\n\n previewUrls.clear();\n });\n\n // ============================================\n // Mount\n // ============================================\n // ============================================\n // Template\n // ============================================\n onElement(inputRef, (inp) => {\n props.ref.value?.(inp);\n\n const sub = watch(props.ref, (cb) => {\n cb?.(inp);\n });\n\n return () => {\n sub.dispose();\n props.ref.value?.(null);\n };\n });\n\n onMounted(() => {\n const inp = inputRef.value!;\n const dz = dropzoneRef.value!;\n let skipNextClick = false;\n const pressControl = createInteraction({\n disabled: isDisabled,\n onPress: () => {\n inp.click();\n },\n });\n\n // Native input → add files\n onEvent(inp, 'change', (e: Event) => {\n const input = e.target as HTMLInputElement;\n\n if (input.files?.length) addFiles(Array.from(input.files), e);\n\n input.value = ''; // reset so the same file triggers change again\n });\n // Click dropzone → open file picker\n onEvent(dz, 'click', (e: MouseEvent) => {\n if (e.target === inp) return;\n\n if (skipNextClick) {\n skipNextClick = false;\n\n return;\n }\n\n if (!isDisabled.value) inp.click();\n });\n // Keyboard: Enter / Space → open picker\n onEvent(dz, 'keydown', (e: KeyboardEvent) => {\n skipNextClick = pressControl.handleKeydown(e) && e.key === 'Enter';\n });\n\n const dropZone = createDropZone({\n disabled: isDisabled.value,\n element: dz,\n onDrop: (droppedFiles) => addFiles(droppedFiles),\n onHoverChange: (hovered) => {\n isDragging.value = hovered;\n },\n });\n\n onCleanup(() => dropZone.dispose());\n });\n\n return html`\n <div class=\"file-input-wrapper\" part=\"wrapper\">\n <label class=\"label-outside\" id=\"${labelId}\" part=\"label\" ?hidden=${() => !props.label.value}\n >${props.label}</label\n >\n <div\n class=\"dropzone\"\n part=\"dropzone\"\n ref=${dropzoneRef}\n role=\"button\"\n :tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n :aria-disabled=\"${() => String(isDisabled.value)}\"\n :aria-label=\"${() => (!props.label.value ? 'File upload drop zone' : null)}\"\n :aria-labelledby=\"${() => (props.label.value ? labelId : null)}\"\n aria-describedby=\"${helperId}\">\n <input\n type=\"file\"\n ref=${inputRef}\n part=\"input\"\n id=\"${fileInputId}\"\n :accept=\"${props.accept}\"\n ?multiple=\"${props.multiple}\"\n ?required=\"${props.required}\"\n ?disabled=\"${isDisabled}\"\n :name=\"${props.name}\"\n hidden\n inert\n tabindex=\"-1\" />\n <div class=\"dropzone-content\">\n <span class=\"dropzone-icon\" aria-hidden=\"true\">\n <ore-icon name=\"upload\" size=\"36\" stroke-width=\"1.5\" aria-hidden=\"true\"></ore-icon>\n </span>\n <span class=\"dropzone-title\">Drop files here or <u>click to browse</u></span>\n <span class=\"dropzone-hint\" ?hidden=${() => !hintText.value}>${hintText}</span>\n </div>\n </div>\n <ul\n class=\"${() => (props.gallery.value ? 'file-grid' : 'file-list')}\"\n part=\"${() => (props.gallery.value ? 'gallery' : null)}\"\n role=\"list\"\n aria-label=\"Selected files\"\n ?hidden=${() => files.value.length === 0}>\n ${() =>\n files.value.map((file: File) =>\n props.gallery.value\n ? html`\n <li class=\"file-card\">\n ${\n // Decorative: the file name is already announced via the visible\n // `.file-card-name` caption below — repeating it as `alt` text would\n // duplicate it (and often trips redundant-alt checks, since real\n // filenames commonly contain words like \"photo\" or \"image\").\n isImageFile(file)\n ? // Object URLs use the `blob:` scheme, which ore's attribute-level\n // XSS guard blocks unconditionally on `src` (and other\n // URL-accepting attributes) — set it as a DOM property via `ref`\n // instead, bypassing that string-based check. Safe here: the value\n // comes from `URL.createObjectURL(file)` on a real `File` object we\n // control, never from untrusted text.\n html`<img\n class=\"file-thumb\"\n alt=\"\"\n ref=\"${(el: HTMLImageElement | null) => {\n if (el) el.src = getPreviewUrl(file);\n }}\" />`\n : html`<span class=\"file-thumb file-thumb-generic\" aria-hidden=\"true\">\n <ore-icon name=\"file\" size=\"28\" stroke-width=\"1.5\" aria-hidden=\"true\"></ore-icon>\n </span>`\n }\n <span class=\"file-card-name\" title=\"${file.name}\">${file.name}</span>\n <button\n class=\"file-card-remove\"\n type=\"button\"\n aria-label=\"${`Remove ${file.name}`}\"\n @click=${(e: Event) => removeFile(file, e)}>\n <ore-icon name=\"x\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : html`\n <li class=\"file-item\">\n <span class=\"file-icon\" aria-hidden=\"true\">\n <ore-icon name=\"file\" size=\"18\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n </span>\n <span class=\"file-meta\">\n <span class=\"file-name\" title=\"${file.name}\">${file.name}</span>\n <span class=\"file-size\">${formatBytes(file.size)}</span>\n </span>\n <button\n class=\"file-remove\"\n type=\"button\"\n aria-label=\"${`Remove ${file.name}`}\"\n @click=${(e: Event) => removeFile(file, e)}>\n <ore-icon name=\"x\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `,\n )}\n </ul>\n <div class=\"helper-text\" id=\"${helperId}\" part=\"helper\" ?hidden=${() => isInvalid.value || !props.helper.value}>\n ${props.helper}\n </div>\n <div\n class=\"helper-text helper-text-error\"\n id=\"${errorId}\"\n role=\"alert\"\n part=\"error\"\n ?hidden=${() => !isInvalid.value}>\n ${() => props.error.value ?? ''}\n </div>\n </div>\n `;\n },\n shadow: { delegatesFocus: true },\n styles: [\n ...fieldMixins,\n sizeVariantMixin(FILE_INPUT_SIZE_PRESET),\n forcedColorsFocusMixin('.dropzone'),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-input.js","names":[],"sources":["../src/inputs/file-input/file-input.ts"],"sourcesContent":["import { createDropZone } from '@vielzeug/dnd';\nimport {\n createStableId,\n define,\n html,\n inject,\n prop,\n ref,\n bind,\n onCleanup,\n onElement,\n onEvent,\n onMounted,\n useEmit,\n} from '@vielzeug/ore';\nimport { when } from '@vielzeug/ore/directives';\nimport { useField } from '@vielzeug/ore/forms';\nimport { computed, signal, watch } from '@vielzeug/ripple';\n\nimport '../../content/icon/icon';\nimport '../../feedback/progress/progress';\nimport { bindRefCallback, createInteraction } from '../../headless';\nimport { FILE_INPUT_SIZE_PRESET } from '../../shared';\nimport { fieldMixins, forcedColorsFocusMixin, sizeVariantMixin } from '../../styles';\nimport { FORM_CTX, useFormContext } from '../shared/form-context';\nimport { createFileQueue, formatBytes, type FileUploadFn } from './file-input-upload';\nimport componentStyles from './file-input.css?inline';\n\nexport type { FileUploadFn, FileUploadState, FileUploadStatus } from './file-input-upload';\n\nconst isImageFile = (file: File): boolean => file.type.startsWith('image/');\n\n/** File input component properties */\nexport type OreFileInputProps = {\n /** Accepted file types (comma-separated, e.g. '.jpg, .png, image/*') */\n accept?: string;\n /** Theme color tint */\n color?: string;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /**\n * Render selected files as a grid of image thumbnails/previews instead of the default\n * single-column list. Non-image files fall back to a generic file icon in the same grid.\n */\n gallery?: boolean;\n /** Helper text displayed below the input */\n helper?: string;\n /** Input label text */\n label?: string;\n /** Max number of files allowed (only used if multiple is true) */\n 'max-files'?: number;\n /** Max size of a single file in bytes */\n 'max-size'?: number;\n /** Allow multiple files selection */\n multiple?: boolean;\n /** Form field name */\n name?: string;\n /**\n * JS-only callback fired with the inner `<input type=\"file\">` element when it\n * mounts, and with `null` when it unmounts. Intended for composed components\n * that need imperative access to the raw element.\n * Set as a JS property: `bitFileInput.ref = (el) => { ... }`.\n */\n ref?: ((el: HTMLInputElement | null) => void) | null;\n /** Required field */\n required?: boolean;\n /** Field size preset */\n size?: string;\n /**\n * JS-only upload transport (see `FileUploadFn`). When set, every newly added file starts\n * uploading immediately and independently — progress/speed/ETA, retry-on-failure, and a\n * success confirmation card are all driven from here. Omit it to keep the component in its\n * original picker-only mode (select files, no upload lifecycle).\n * Set as a JS property: `fileInput.upload = async (file, { onProgress, signal }) => { ... }`.\n */\n upload?: FileUploadFn | null;\n};\n\n/** Events emitted by the file-input component */\nexport type OreFileInputEvents = {\n /** Emitted when files are added or removed */\n change: { files: File[]; originalEvent?: Event; value: File[] };\n /** Emitted when a specific file is removed */\n remove: { file: File; files: File[]; originalEvent?: Event; value: File[] };\n /** Emitted when `upload` rejects for a file (after a fresh attempt or a retry) */\n 'upload-error': { error: unknown; file: File };\n /** Emitted whenever `upload`'s `onProgress` reports new bytes for a file */\n 'upload-progress': { file: File; loaded: number; total: number };\n /** Emitted when `upload` resolves for a file */\n 'upload-success': { file: File };\n};\n\n/**\n * A file upload field with drag-and-drop support, built-in validation messaging, and — once a\n * `upload` transport is wired up — a full per-file upload lifecycle: live progress/speed/ETA,\n * retry-on-failure, a success confirmation card, and fully independent handling of concurrent\n * files (one failing never blocks or cancels the others). Picking files with no `upload` set\n * keeps the original, upload-free selection-only behavior.\n *\n * @element ore-file-input\n *\n * @attr {string} accept - Comma-separated file extensions or MIME types\n * @attr {boolean} multiple - Enable multiple files selection\n * @attr {boolean} gallery - Show selected files as an image thumbnail/preview grid\n * @attr {number} max-files - Max number of files allowed\n * @attr {number} max-size - Max size of each file in bytes\n * @attr {boolean} disabled - Disable interaction\n * @attr {string} error - Show an error state/message\n * @attr {string} helper - Provide helper context below the dropzone\n *\n * @fires change - detail: { files: File[], value: File[] }\n * @fires remove - detail: { file: File, files: File[] }\n * @fires upload-progress - detail: { file: File, loaded: number, total: number }\n * @fires upload-success - detail: { file: File }\n * @fires upload-error - detail: { file: File, error: unknown }\n *\n * @cssprop --file-input-bg - Dropzone background color\n * @cssprop --file-input-border-color - Dropzone border color\n * @cssprop --file-input-font-size - Font size\n * @cssprop --file-input-radius - Dropzone border radius\n * @cssprop --file-input-min-height - Minimum dropzone height\n * @cssprop --file-input-hover-bg - Dropzone background on hover (flat/ghost variants)\n * @cssprop --file-input-hover-border-color - Dropzone border on hover (flat/bordered variants)\n * @cssprop --file-input-focus-bg - Dropzone background when focused/drag-over (flat variant)\n * @cssprop --file-input-focus-border-color - Dropzone border when focused/drag-over (flat variant)\n * @cssprop --file-input-thumb-size - Gallery thumbnail width/height\n * @part wrapper - Root wrapper around the file input field\n * @part label - Visible label rendered above the dropzone\n * @part dropzone - Interactive drag-and-drop target\n * @part input - Native file input element\n * @part gallery - Gallery grid container (rendered instead of `file-list` when `gallery` is set)\n * @part helper - Helper text shown beneath the dropzone\n * @part error - Error message shown beneath the field\n * @example\n * ```html\n * <ore-file-input label=\"Upload files\" accept=\"image/*\" multiple />\n * <ore-file-input label=\"Photos\" accept=\"image/*\" multiple gallery />\n * <ore-file-input label=\"Resume\" accept=\".pdf,.doc,.docx\" max-size=\"5242880\" />\n * <ore-file-input variant=\"bordered\" color=\"primary\" />\n * ```\n * ```ts\n * // Wire up a real upload transport — progress/retry/success are then handled automatically.\n * // `XMLHttpRequest` is used here (not `fetch`) because it's the only browser API that reports\n * // upload progress; swap in whatever transport the app already uses.\n * const input = document.querySelector('ore-file-input');\n * input.upload = (file, { onProgress, signal, resumeFrom }) =>\n * new Promise((resolve, reject) => {\n * const xhr = new XMLHttpRequest();\n *\n * xhr.upload.addEventListener('progress', (e) => onProgress(resumeFrom + e.loaded, file.size));\n * xhr.addEventListener('load', () => (xhr.status < 400 ? resolve() : reject(new Error(xhr.statusText))));\n * xhr.addEventListener('error', () => reject(new Error('Network error')));\n * signal.addEventListener('abort', () => xhr.abort());\n *\n * xhr.open('PUT', '/uploads');\n * if (resumeFrom > 0) xhr.setRequestHeader('Content-Range', `bytes ${resumeFrom}-/${file.size}`);\n * xhr.send(file);\n * });\n * ```\n */\nexport const FILE_INPUT_TAG = 'ore-file-input' as const;\ndefine<OreFileInputProps>(FILE_INPUT_TAG, {\n formAssociated: true,\n props: {\n accept: prop.string(),\n color: prop.string(),\n disabled: prop.bool(false),\n error: prop.string(),\n gallery: prop.bool(false),\n helper: prop.string(),\n label: prop.string(),\n 'max-files': prop.number(0),\n 'max-size': prop.number(0),\n multiple: prop.bool(false),\n name: prop.string(),\n ref: prop.data<((el: HTMLInputElement | null) => void) | null>(),\n required: prop.bool(false),\n size: prop.string(),\n upload: prop.data<FileUploadFn>(),\n },\n setup(props) {\n const emit = useEmit<OreFileInputEvents>();\n\n // ============================================\n // State\n // ============================================\n\n const isDragging = signal(false);\n // Set while a \"Replace\" action is waiting for the (shared) native file input's next\n // `change` — routes that selection into `queue.replaceFile()` instead of `queue.addFiles()`.\n const replaceTarget = signal<File | null>(null);\n\n const formCtx = inject(FORM_CTX);\n const fCtxProps = useFormContext(props, formCtx);\n const isDisabled = fCtxProps.disabled;\n const maxFilesLimit = computed(() => props['max-files'].value ?? 0);\n const maxSizeLimit = computed(() => props['max-size'].value ?? 0);\n\n // File selection + the opt-in upload lifecycle (progress, retry, replace, per-file\n // isolation) live in `createFileQueue` — see file-input-upload.ts for why they're one unit.\n const queue = createFileQueue({\n accept: props.accept,\n disabled: isDisabled,\n maxFiles: maxFilesLimit,\n maxSize: maxSizeLimit,\n multiple: computed(() => Boolean(props.multiple.value)),\n onChange: (changedFiles, originalEvent) =>\n emit('change', { files: changedFiles, originalEvent, value: changedFiles }),\n onRemove: (file, remainingFiles, originalEvent) =>\n emit('remove', { file, files: remainingFiles, originalEvent, value: remainingFiles }),\n onUploadError: (file, error) => emit('upload-error', { error, file }),\n onUploadProgress: (file, loaded, total) => emit('upload-progress', { file, loaded, total }),\n onUploadSuccess: (file) => emit('upload-success', { file }),\n upload: computed(() => props.upload.value ?? undefined),\n });\n\n onCleanup(() => queue.dispose());\n\n // ============================================\n // Form Integration\n // ============================================\n\n useField({\n disabled: isDisabled,\n toFormValue: (fi: File[]) => {\n if (fi.length === 0) return null;\n\n const name = props.name.value || 'file';\n const fd = new FormData();\n\n for (const file of fi) fd.append(name, file);\n\n return fd;\n },\n value: queue.files,\n });\n\n // Sync host attributes for CSS selectors\n const isInvalid = computed(() => Boolean(props.error.value));\n\n bind({\n attr: {\n 'drag-over': () => (isDragging.value ? true : undefined),\n invalid: () => (isInvalid.value ? true : undefined),\n size: fCtxProps.size,\n },\n });\n\n // ============================================\n // IDs\n // ============================================\n const fileInputId = createStableId('file-input');\n const labelId = `label-${fileInputId}`;\n const helperId = `helper-${fileInputId}`;\n const errorId = `error-${fileInputId}`;\n\n // ============================================\n // Refs\n // ============================================\n const dropzoneRef = ref<HTMLDivElement>();\n const inputRef = ref<HTMLInputElement>();\n const hintText = computed(() => {\n const parts: string[] = [];\n\n if (props.accept.value) {\n parts.push(\n props.accept.value\n .split(',')\n .map((s: string) => s.trim())\n .join(', '),\n );\n }\n\n const maxSize = maxSizeLimit.value;\n\n if (maxSize > 0) parts.push(`max ${formatBytes(maxSize)}`);\n\n const maxFiles = maxFilesLimit.value;\n\n if (maxFiles > 0) parts.push(`up to ${maxFiles} file${maxFiles !== 1 ? 's' : ''}`);\n\n return parts.join(' · ');\n });\n\n // ============================================\n // Replace (\"swap this one file\" — see file-input-upload.ts's `replaceFile`)\n // ============================================\n function beginReplace(file: File): void {\n replaceTarget.value = file;\n inputRef.value?.click();\n }\n\n // ============================================\n // Gallery Preview URLs\n // ============================================\n // Object URLs are created lazily (only while `gallery` is enabled) and revoked as soon\n // as their file is no longer selected, plus unconditionally on disconnect — otherwise\n // each preview leaks its backing blob for the life of the page.\n const previewUrls = new Map<File, string>();\n\n function getPreviewUrl(file: File): string {\n let url = previewUrls.get(file);\n\n if (!url) {\n url = URL.createObjectURL(file);\n previewUrls.set(file, url);\n }\n\n return url;\n }\n\n function revokeStalePreviewUrls(activeFiles: File[]): void {\n const keep = new Set(activeFiles);\n\n for (const [file, url] of previewUrls) {\n if (!keep.has(file)) {\n URL.revokeObjectURL(url);\n previewUrls.delete(file);\n }\n }\n }\n\n watch(queue.files, revokeStalePreviewUrls);\n\n onCleanup(() => {\n for (const url of previewUrls.values()) URL.revokeObjectURL(url);\n\n previewUrls.clear();\n });\n\n // ============================================\n // Mount\n // ============================================\n // ============================================\n // Template\n // ============================================\n onElement(inputRef, (inp) => bindRefCallback(props.ref, inp));\n\n onMounted(() => {\n const inp = inputRef.value!;\n const dz = dropzoneRef.value!;\n let skipNextClick = false;\n const pressControl = createInteraction({\n disabled: isDisabled,\n onPress: () => {\n inp.click();\n },\n });\n\n // Native input → add files, or — if a \"Replace\" action opened the picker — swap the one\n // file it targeted instead of appending a new entry.\n onEvent(inp, 'change', (e: Event) => {\n const input = e.target as HTMLInputElement;\n const target = replaceTarget.value;\n\n replaceTarget.value = null;\n\n if (target && input.files?.[0]) queue.replaceFile(target, input.files[0], e);\n else if (input.files?.length) queue.addFiles(Array.from(input.files), e);\n\n input.value = ''; // reset so the same file triggers change again\n });\n // Click dropzone → open file picker\n onEvent(dz, 'click', (e: MouseEvent) => {\n if (e.target === inp) return;\n\n if (skipNextClick) {\n skipNextClick = false;\n\n return;\n }\n\n if (!isDisabled.value) inp.click();\n });\n // Keyboard: Enter / Space → open picker\n onEvent(dz, 'keydown', (e: KeyboardEvent) => {\n skipNextClick = pressControl.handleKeydown(e) && e.key === 'Enter';\n });\n\n // `createDropZone` has no way to update `disabled` after creation — recreate the zone\n // whenever the prop changes instead of capturing a stale snapshot from this one `onMounted`\n // run. `watch`'s own returned cleanup (not a second `onCleanup`) disposes the current zone\n // both on the next toggle and on final teardown.\n watch(\n isDisabled,\n (disabled) => {\n const dropZone = createDropZone({\n disabled,\n element: dz,\n onDrop: (droppedFiles) => queue.addFiles(droppedFiles),\n onHoverChange: (hovered) => {\n isDragging.value = hovered;\n },\n });\n\n return () => dropZone.dispose();\n },\n { immediate: true },\n );\n });\n\n return html`\n <div class=\"file-input-wrapper\" part=\"wrapper\">\n <label class=\"label-outside\" id=\"${labelId}\" part=\"label\" ?hidden=${() => !props.label.value}\n >${props.label}</label\n >\n <div\n class=\"dropzone\"\n part=\"dropzone\"\n ref=${dropzoneRef}\n role=\"button\"\n :tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n :aria-disabled=\"${() => String(isDisabled.value)}\"\n :aria-label=\"${() => (!props.label.value ? 'File upload drop zone' : null)}\"\n :aria-labelledby=\"${() => (props.label.value ? labelId : null)}\"\n aria-describedby=\"${helperId}\">\n <input\n type=\"file\"\n ref=${inputRef}\n part=\"input\"\n id=\"${fileInputId}\"\n :accept=\"${props.accept}\"\n ?multiple=\"${props.multiple}\"\n ?required=\"${props.required}\"\n ?disabled=\"${isDisabled}\"\n :name=\"${props.name}\"\n hidden\n inert\n tabindex=\"-1\" />\n <div class=\"dropzone-content\">\n <span class=\"dropzone-icon\" aria-hidden=\"true\" :data-status=\"${() => (isInvalid.value ? 'error' : null)}\">\n ${() =>\n isInvalid.value\n ? html`<ore-icon name=\"alert-circle\" size=\"36\" stroke-width=\"1.5\" aria-hidden=\"true\"></ore-icon>`\n : html`<ore-icon name=\"upload\" size=\"36\" stroke-width=\"1.5\" aria-hidden=\"true\"></ore-icon>`}\n </span>\n <!-- Signal 01: the copy itself shifts on drag-entry (not just border/glow) — a\n static \"Drop files here\" during an active drag reads as if the drop target\n hasn't noticed the file yet. -->\n ${when(\n () => isDragging.value,\n () => html`<span class=\"dropzone-title dropzone-title-active\">Release to upload</span>`,\n )}\n ${when(\n () => !isDragging.value,\n () => html`<span class=\"dropzone-title\">Drop files here or <u>click to browse</u></span>`,\n )}\n <span class=\"dropzone-hint\" ?hidden=${() => !hintText.value}>${hintText}</span>\n </div>\n </div>\n <ul\n class=\"${() => (props.gallery.value ? 'file-grid' : 'file-list')}\"\n part=\"${() => (props.gallery.value ? 'gallery' : null)}\"\n role=\"list\"\n aria-label=\"Selected files\"\n ?hidden=${() => queue.files.value.length === 0}>\n ${() =>\n queue.files.value.map((file: File) =>\n props.gallery.value\n ? html`\n <li class=\"file-card\" data-status=${() => queue.fileState(file).status}>\n <span class=\"file-thumb-frame\">\n ${\n // Decorative: the file name is already announced via the visible\n // `.file-card-name` caption below — repeating it as `alt` text would\n // duplicate it (and often trips redundant-alt checks, since real\n // filenames commonly contain words like \"photo\" or \"image\").\n isImageFile(file)\n ? // Object URLs use the `blob:` scheme, which ore's attribute-level\n // XSS guard blocks unconditionally on `src` (and other\n // URL-accepting attributes) — set it as a DOM property via `ref`\n // instead, bypassing that string-based check. Safe here: the value\n // comes from `URL.createObjectURL(file)` on a real `File` object we\n // control, never from untrusted text.\n html`<img\n class=\"file-thumb\"\n alt=\"\"\n ref=\"${(el: HTMLImageElement | null) => {\n if (el) el.src = getPreviewUrl(file);\n }}\" />`\n : html`<span class=\"file-thumb file-thumb-generic\" aria-hidden=\"true\">\n <ore-icon name=\"file\" size=\"28\" stroke-width=\"1.5\" aria-hidden=\"true\"></ore-icon>\n </span>`\n }\n ${when(\n () => queue.fileState(file).status === 'success',\n () =>\n html`<span class=\"file-status-badge file-status-badge-success\" aria-hidden=\"true\">\n <ore-icon name=\"check\" size=\"11\" stroke-width=\"3\" aria-hidden=\"true\"></ore-icon>\n </span>`,\n )}\n ${when(\n () => queue.fileState(file).status === 'error',\n () =>\n html`<span class=\"file-status-badge file-status-badge-error\" aria-hidden=\"true\">\n <ore-icon name=\"alert-circle\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </span>`,\n )}\n <span class=\"file-card-actions\">\n ${when(\n () => queue.fileState(file).status === 'error',\n () =>\n html`<button\n class=\"file-card-action\"\n type=\"button\"\n aria-label=\"${`Retry uploading ${file.name}`}\"\n @click=${() => queue.retryUpload(file)}>\n <ore-icon name=\"refresh-cw\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>`,\n )}\n ${when(\n () =>\n queue.fileState(file).status === 'success' &&\n Boolean(props.multiple.value) &&\n Boolean(props.upload.value),\n () =>\n html`<button\n class=\"file-card-action\"\n type=\"button\"\n aria-label=\"${`Replace ${file.name}`}\"\n @click=${() => beginReplace(file)}>\n <ore-icon name=\"upload\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>`,\n )}\n <button\n class=\"file-card-action file-card-remove\"\n type=\"button\"\n aria-label=\"${`Remove ${file.name}`}\"\n @click=${(e: Event) => queue.removeFile(file, e)}>\n <ore-icon name=\"x\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>\n </span>\n </span>\n <span class=\"file-card-name\" title=\"${file.name}\">${file.name}</span>\n ${when(\n () => queue.fileState(file).status === 'uploading',\n () => html`\n <ore-progress\n type=\"linear\"\n size=\"sm\"\n :value=${() => queue.uploadPercent(file)}\n label=${() => `${queue.uploadPercent(file)}%`}></ore-progress>\n <span class=\"file-progress-meta\">${() => queue.uploadMetaText(file)}</span>\n `,\n )}\n ${when(\n () => queue.fileState(file).status === 'error',\n () => html`<span class=\"file-error-text\">${() => queue.fileState(file).error}</span>`,\n )}\n </li>\n `\n : html`\n <li class=\"file-item\" data-status=${() => queue.fileState(file).status}>\n <span class=\"file-icon\" aria-hidden=\"true\">\n <ore-icon\n name=${() => queue.statusIconName(file)}\n size=\"18\"\n stroke-width=\"1.75\"\n aria-hidden=\"true\"></ore-icon>\n </span>\n <span class=\"file-meta\">\n <span class=\"file-name\" title=\"${file.name}\">${file.name}</span>\n ${when(\n () => queue.fileState(file).status === 'uploading',\n () => html`\n <ore-progress\n type=\"linear\"\n size=\"sm\"\n :value=${() => queue.uploadPercent(file)}\n label=${() => `${queue.uploadPercent(file)}%`}></ore-progress>\n <span class=\"file-progress-meta\">${() => queue.uploadMetaText(file)}</span>\n `,\n )}\n ${when(\n () => queue.fileState(file).status === 'error',\n () => html`<span class=\"file-error-text\">${() => queue.fileState(file).error}</span>`,\n )}\n ${when(\n () => queue.fileState(file).status === 'idle' || queue.fileState(file).status === 'success',\n () => html`<span class=\"file-size\">${formatBytes(file.size)}</span>`,\n )}\n </span>\n <span class=\"file-actions\">\n ${when(\n () => queue.fileState(file).status === 'error',\n () =>\n html`<button\n class=\"file-action\"\n type=\"button\"\n aria-label=\"${`Retry uploading ${file.name}`}\"\n @click=${() => queue.retryUpload(file)}>\n <ore-icon name=\"refresh-cw\" size=\"14\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </button>`,\n )}\n ${when(\n () =>\n queue.fileState(file).status === 'success' &&\n Boolean(props.multiple.value) &&\n Boolean(props.upload.value),\n () =>\n html`<button\n class=\"file-action\"\n type=\"button\"\n aria-label=\"${`Replace ${file.name}`}\"\n @click=${() => beginReplace(file)}>\n <ore-icon name=\"upload\" size=\"14\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </button>`,\n )}\n <button\n class=\"file-remove\"\n type=\"button\"\n aria-label=\"${`Remove ${file.name}`}\"\n @click=${(e: Event) => queue.removeFile(file, e)}>\n <ore-icon name=\"x\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>\n </span>\n </li>\n `,\n )}\n </ul>\n <div class=\"helper-text\" id=\"${helperId}\" part=\"helper\" ?hidden=${() => isInvalid.value || !props.helper.value}>\n ${props.helper}\n </div>\n <div\n class=\"helper-text helper-text-error\"\n id=\"${errorId}\"\n role=\"alert\"\n part=\"error\"\n ?hidden=${() => !isInvalid.value}>\n ${() => props.error.value ?? ''}\n </div>\n </div>\n `;\n },\n shadow: { delegatesFocus: true },\n styles: [\n ...fieldMixins,\n sizeVariantMixin(FILE_INPUT_SIZE_PRESET),\n forcedColorsFocusMixin('.dropzone'),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e={ariaControlsElements:`aria-controls`,ariaDescribedByElements:`aria-describedby`,ariaLabelledByElements:`aria-labelledby`};function t(t,n,r){if(n in t){t[n]=r.length>0?r:null;return}let i=e[n],a=r.map(e=>e.id).filter(Boolean);a.length===0?t.removeAttribute(i):t.setAttribute(i,a.join(` `))}exports.setAriaReflection=t;
2
+ //# sourceMappingURL=aria-reflection.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aria-reflection.cjs","names":[],"sources":["../../src/headless/aria-reflection.ts"],"sourcesContent":["/**\n * Cross-shadow-root ARIA relationship helper.\n *\n * Plain IDREF attributes (`aria-controls`, `aria-describedby`, `aria-labelledby`, …) cannot\n * resolve across shadow-tree boundaries — the browser only looks up an `id` within the same\n * root as the referencing element, so `<div id=\"x\">` inside one component's shadow root is\n * invisible to `aria-controls=\"x\"` set inside another component's shadow root, even when both\n * custom elements are siblings in the light DOM. This is a platform limitation, not a bug —\n * see the WICG \"reference target\" explainer for the full rationale.\n *\n * Modern browsers instead expose element-reflection IDL properties\n * (`ariaControlsElements`, `ariaDescribedByElements`, `ariaLabelledByElements`, …) that accept\n * live `Element` references directly, sidestepping ID lookup entirely — these work across\n * shadow roots by design. Where unsupported, this falls back to the plain IDREF attribute,\n * which is still correct for same-root pairings and degrades gracefully (no relationship)\n * for cross-root ones instead of throwing.\n */\n\nexport type AriaReflectionProperty = 'ariaControlsElements' | 'ariaDescribedByElements' | 'ariaLabelledByElements';\n\nconst ATTR_BY_PROPERTY: Record<AriaReflectionProperty, string> = {\n ariaControlsElements: 'aria-controls',\n ariaDescribedByElements: 'aria-describedby',\n ariaLabelledByElements: 'aria-labelledby',\n};\n\n/**\n * Associates `el` with `targets` via the given ARIA relationship. Prefers the\n * cross-shadow-root-safe element-reflection property; falls back to a plain IDREF attribute\n * built from `targets`' own `id`s when the browser doesn't support element reflection yet.\n *\n * Pass an empty array to clear the relationship.\n */\nexport function setAriaReflection(el: Element, property: AriaReflectionProperty, targets: readonly Element[]): void {\n if (property in el) {\n (el as unknown as Record<AriaReflectionProperty, readonly Element[] | null>)[property] =\n targets.length > 0 ? targets : null;\n\n return;\n }\n\n const attr = ATTR_BY_PROPERTY[property];\n const ids = targets.map((target) => target.id).filter(Boolean);\n\n if (ids.length === 0) el.removeAttribute(attr);\n else el.setAttribute(attr, ids.join(' '));\n}\n"],"mappings":"AAoBA,IAAM,EAA2D,CAC/D,qBAAsB,gBACtB,wBAAyB,mBACzB,uBAAwB,iBAC1B,EASA,SAAgB,EAAkB,EAAa,EAAkC,EAAmC,CAClH,GAAI,KAAY,EAAI,CAClB,EAA6E,GAC3E,EAAQ,OAAS,EAAI,EAAU,KAEjC,MACF,CAEA,IAAM,EAAO,EAAiB,GACxB,EAAM,EAAQ,IAAK,GAAW,EAAO,EAAE,CAAC,CAAC,OAAO,OAAO,EAEzD,EAAI,SAAW,EAAG,EAAG,gBAAgB,CAAI,EACxC,EAAG,aAAa,EAAM,EAAI,KAAK,GAAG,CAAC,CAC1C"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Cross-shadow-root ARIA relationship helper.
3
+ *
4
+ * Plain IDREF attributes (`aria-controls`, `aria-describedby`, `aria-labelledby`, …) cannot
5
+ * resolve across shadow-tree boundaries — the browser only looks up an `id` within the same
6
+ * root as the referencing element, so `<div id="x">` inside one component's shadow root is
7
+ * invisible to `aria-controls="x"` set inside another component's shadow root, even when both
8
+ * custom elements are siblings in the light DOM. This is a platform limitation, not a bug —
9
+ * see the WICG "reference target" explainer for the full rationale.
10
+ *
11
+ * Modern browsers instead expose element-reflection IDL properties
12
+ * (`ariaControlsElements`, `ariaDescribedByElements`, `ariaLabelledByElements`, …) that accept
13
+ * live `Element` references directly, sidestepping ID lookup entirely — these work across
14
+ * shadow roots by design. Where unsupported, this falls back to the plain IDREF attribute,
15
+ * which is still correct for same-root pairings and degrades gracefully (no relationship)
16
+ * for cross-root ones instead of throwing.
17
+ */
18
+ export type AriaReflectionProperty = 'ariaControlsElements' | 'ariaDescribedByElements' | 'ariaLabelledByElements';
19
+ /**
20
+ * Associates `el` with `targets` via the given ARIA relationship. Prefers the
21
+ * cross-shadow-root-safe element-reflection property; falls back to a plain IDREF attribute
22
+ * built from `targets`' own `id`s when the browser doesn't support element reflection yet.
23
+ *
24
+ * Pass an empty array to clear the relationship.
25
+ */
26
+ export declare function setAriaReflection(el: Element, property: AriaReflectionProperty, targets: readonly Element[]): void;
27
+ //# sourceMappingURL=aria-reflection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aria-reflection.d.ts","sourceRoot":"","sources":["../../src/headless/aria-reflection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG,yBAAyB,GAAG,wBAAwB,CAAC;AAQnH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAalH"}
@@ -0,0 +1,18 @@
1
+ //#region src/headless/aria-reflection.ts
2
+ var e = {
3
+ ariaControlsElements: "aria-controls",
4
+ ariaDescribedByElements: "aria-describedby",
5
+ ariaLabelledByElements: "aria-labelledby"
6
+ };
7
+ function t(t, n, r) {
8
+ if (n in t) {
9
+ t[n] = r.length > 0 ? r : null;
10
+ return;
11
+ }
12
+ let i = e[n], a = r.map((e) => e.id).filter(Boolean);
13
+ a.length === 0 ? t.removeAttribute(i) : t.setAttribute(i, a.join(" "));
14
+ }
15
+ //#endregion
16
+ export { t as setAriaReflection };
17
+
18
+ //# sourceMappingURL=aria-reflection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aria-reflection.js","names":[],"sources":["../../src/headless/aria-reflection.ts"],"sourcesContent":["/**\n * Cross-shadow-root ARIA relationship helper.\n *\n * Plain IDREF attributes (`aria-controls`, `aria-describedby`, `aria-labelledby`, …) cannot\n * resolve across shadow-tree boundaries — the browser only looks up an `id` within the same\n * root as the referencing element, so `<div id=\"x\">` inside one component's shadow root is\n * invisible to `aria-controls=\"x\"` set inside another component's shadow root, even when both\n * custom elements are siblings in the light DOM. This is a platform limitation, not a bug —\n * see the WICG \"reference target\" explainer for the full rationale.\n *\n * Modern browsers instead expose element-reflection IDL properties\n * (`ariaControlsElements`, `ariaDescribedByElements`, `ariaLabelledByElements`, …) that accept\n * live `Element` references directly, sidestepping ID lookup entirely — these work across\n * shadow roots by design. Where unsupported, this falls back to the plain IDREF attribute,\n * which is still correct for same-root pairings and degrades gracefully (no relationship)\n * for cross-root ones instead of throwing.\n */\n\nexport type AriaReflectionProperty = 'ariaControlsElements' | 'ariaDescribedByElements' | 'ariaLabelledByElements';\n\nconst ATTR_BY_PROPERTY: Record<AriaReflectionProperty, string> = {\n ariaControlsElements: 'aria-controls',\n ariaDescribedByElements: 'aria-describedby',\n ariaLabelledByElements: 'aria-labelledby',\n};\n\n/**\n * Associates `el` with `targets` via the given ARIA relationship. Prefers the\n * cross-shadow-root-safe element-reflection property; falls back to a plain IDREF attribute\n * built from `targets`' own `id`s when the browser doesn't support element reflection yet.\n *\n * Pass an empty array to clear the relationship.\n */\nexport function setAriaReflection(el: Element, property: AriaReflectionProperty, targets: readonly Element[]): void {\n if (property in el) {\n (el as unknown as Record<AriaReflectionProperty, readonly Element[] | null>)[property] =\n targets.length > 0 ? targets : null;\n\n return;\n }\n\n const attr = ATTR_BY_PROPERTY[property];\n const ids = targets.map((target) => target.id).filter(Boolean);\n\n if (ids.length === 0) el.removeAttribute(attr);\n else el.setAttribute(attr, ids.join(' '));\n}\n"],"mappings":";AAoBA,IAAM,IAA2D;CAC/D,sBAAsB;CACtB,yBAAyB;CACzB,wBAAwB;AAC1B;AASA,SAAgB,EAAkB,GAAa,GAAkC,GAAmC;CAClH,IAAI,KAAY,GAAI;EAClB,EAA6E,KAC3E,EAAQ,SAAS,IAAI,IAAU;EAEjC;CACF;CAEA,IAAM,IAAO,EAAiB,IACxB,IAAM,EAAQ,KAAK,MAAW,EAAO,EAAE,CAAC,CAAC,OAAO,OAAO;CAE7D,AAAI,EAAI,WAAW,IAAG,EAAG,gBAAgB,CAAI,IACxC,EAAG,aAAa,GAAM,EAAI,KAAK,GAAG,CAAC;AAC1C"}
@@ -1,2 +1,2 @@
1
- const e=require("./field-base.cjs"),t=require("./signals.cjs"),n=require("./keyboard.cjs");let r=require("@vielzeug/ripple");var i=i=>{let a=t.syncedSignal(i.checked,i.signal,Boolean),o=i.indeterminate?t.syncedSignal(i.indeterminate,i.signal,Boolean):(0,r.signal)(!1),s=a.value,c=o.value,l=!1,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.`:``),m=()=>{a.value=l?s:!!i.checked.value,o.value=l?c:!!i.indeterminate?.value,l=!1,d.triggerValidation(`change`)},h=e=>({checked:a.value,originalEvent:e,value:i.value.value??``}),g=e=>{if(!d.disabled.value){if(l=!0,i.group){o.value=!1,i.group.toggle(i.value.value??``,e),i.onToggle?.(h(e));return}i.clearIndeterminateFirst&&o.value||(a.value=!a.value),o.value=!1,i.onToggle?.(h(e))}},_=n.createInteraction({disabled:d.disabled,onPress:e=>g(e)});return{...d,checkableFormValue:u,checked:a,handleClick:_.handleClick,handleKeydown:_.handleKeydown,indeterminate:o,reset:m,toggle:g,validationMessage:p,validity:f}};exports.createCheckable=i;
1
+ const e=require("./field-base.cjs"),t=require("./signals.cjs"),n=require("./keyboard.cjs");let r=require("@vielzeug/ripple");var i=i=>{let a=t.syncedSignal(i.checked,i.signal,Boolean),o=i.indeterminate?t.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.`:``),m=()=>{a.value=l.isDirty?s:!!i.checked.value,o.value=l.isDirty?c:!!i.indeterminate?.value,l.clear(),d.triggerValidation(`change`)},h=e=>({checked:a.value,originalEvent:e,value:i.value.value??``}),g=e=>{if(!d.disabled.value){if(l.markDirty(),i.group){o.value=!1,i.group.toggle(i.value.value??``,e),i.onToggle?.(h(e));return}i.clearIndeterminateFirst&&o.value||(a.value=!a.value),o.value=!1,i.onToggle?.(h(e))}},_=n.createInteraction({disabled:d.disabled,onPress:e=>g(e)});return{...d,checkableFormValue:u,checked:a,handleClick:_.handleClick,handleKeydown:_.handleKeydown,indeterminate:o,reset:m,toggle:g,validationMessage:p,validity:f}};exports.createCheckable=i;
2
2
  //# sourceMappingURL=checkable.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkable.cjs","names":[],"sources":["../../src/headless/checkable.ts"],"sourcesContent":["import { computed, type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { createField, type ControlValidationMode, 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: whatever `checked`/\n * `indeterminate` currently hold, if the user has never interacted with this control yet, or\n * the value snapshotted at creation, if they have. Two states, not one, because this\n * component reflects the interactive `checked` state back onto the host's `checked` attribute\n * (for `:host([checked])` styling, since shadow-DOM custom elements have no native `:checked`\n * pseudo-class to style against) — so `options.checked` itself changes on every click and\n * can't double as \"the default to revert to\" post-interaction, the way an uncontrolled\n * `<input>`'s `value` attribute can (see `createTextField`'s `reset()`, which has no\n * \"post-interaction\" case to handle for exactly that reason). Wire into\n * `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 // Set the moment the user first interacts — see `reset()`'s doc comment for why this\n // matters: before that point, `options.checked` is still a reliable \"current default\" to\n // resync from (e.g. an async-loaded value arriving after mount); after it, `options.checked`\n // is contaminated by the click-driven attribute reflection and the frozen snapshot is the\n // only value that still represents \"the default\" in the native sense.\n let dirty = false;\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 = dirty ? initialChecked : Boolean(options.checked.value);\n indeterminate.value = dirty ? initialIndeterminate : Boolean(options.indeterminate?.value);\n dirty = false;\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 dirty = true;\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":"6HAiEA,IAAa,EAAmB,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,EAAA,EAAA,OAAA,CACpD,EAAK,EACV,EAAiB,EAAQ,MACzB,EAAuB,EAAc,MAMvC,EAAQ,GAEN,GAAA,EAAA,EAAA,SAAA,KACJ,EAAc,MAAQ,KAAO,EAAQ,MAAS,EAAQ,MAAM,OAAS,GAAM,IAC7E,EAEM,EAAQ,EAAA,YAAY,CAAO,EAE3B,GAAA,EAAA,EAAA,SAAA,KACJ,EAAQ,UAAU,QAAU,CAAC,EAAQ,OAAS,EAAc,OAAS,CAAE,aAAc,EAAK,EAAI,IAChG,EACM,GAAA,EAAA,EAAA,SAAA,KACJ,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,0BAA4B,EACjF,EAEM,MAAoB,CACxB,EAAQ,MAAQ,EAAQ,EAAiB,EAAQ,EAAQ,QAAQ,MACjE,EAAc,MAAQ,EAAQ,EAAuB,EAAQ,EAAQ,eAAe,MACpF,EAAQ,GACR,EAAM,kBAAkB,QAAQ,CAClC,EAEM,EAAiB,IAA0C,CAC/D,QAAS,EAAQ,MACjB,cAAe,EACf,MAAO,EAAQ,MAAM,OAAS,EAChC,GAEM,EAAU,GAAuB,CACjC,MAAM,SAAS,MAInB,IAFA,EAAQ,GAEJ,EAAQ,MAAO,CACjB,EAAc,MAAQ,GACtB,EAAQ,MAAM,OAAO,EAAQ,MAAM,OAAS,GAAI,CAAK,EACrD,EAAQ,WAAW,EAAc,CAAK,CAAC,EAEvC,MACF,CAEI,EAAQ,yBAA2B,EAAc,QAGnD,EAAQ,MAAQ,CAAC,EAAQ,OAFzB,EAAc,MAAQ,GAMxB,EAAQ,WAAW,EAAc,CAAK,CAAC,CATvC,CAUF,EAEM,EAAc,EAAA,kBAAkB,CACpC,SAAU,EAAM,SAChB,QAAU,GAAU,EAAO,CAAK,CAClC,CAAC,EAED,MAAO,CACL,GAAG,EACH,qBACA,UACA,YAAa,EAAY,YACzB,cAAe,EAAY,cAC3B,gBACA,QACA,SACA,oBACA,UACF,CACF"}
1
+ {"version":3,"file":"checkable.cjs","names":[],"sources":["../../src/headless/checkable.ts"],"sourcesContent":["import { computed, type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { createDirtyTracker, createField, type ControlValidationMode, 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,EAAmB,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,EAAA,EAAA,OAAA,CACpD,EAAK,EACV,EAAiB,EAAQ,MACzB,EAAuB,EAAc,MACrC,EAAe,EAAA,mBAAmB,EAElC,GAAA,EAAA,EAAA,SAAA,KACJ,EAAc,MAAQ,KAAO,EAAQ,MAAS,EAAQ,MAAM,OAAS,GAAM,IAC7E,EAEM,EAAQ,EAAA,YAAY,CAAO,EAE3B,GAAA,EAAA,EAAA,SAAA,KACJ,EAAQ,UAAU,QAAU,CAAC,EAAQ,OAAS,EAAc,OAAS,CAAE,aAAc,EAAK,EAAI,IAChG,EACM,GAAA,EAAA,EAAA,SAAA,KACJ,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,0BAA4B,EACjF,EAEM,MAAoB,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,EAAiB,IAA0C,CAC/D,QAAS,EAAQ,MACjB,cAAe,EACf,MAAO,EAAQ,MAAM,OAAS,EAChC,GAEM,EAAU,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,EAAc,CAAK,CAAC,EAEvC,MACF,CAEI,EAAQ,yBAA2B,EAAc,QAGnD,EAAQ,MAAQ,CAAC,EAAQ,OAFzB,EAAc,MAAQ,GAMxB,EAAQ,WAAW,EAAc,CAAK,CAAC,CATvC,CAUF,EAEM,EAAc,EAAA,kBAAkB,CACpC,SAAU,EAAM,SAChB,QAAU,GAAU,EAAO,CAAK,CAClC,CAAC,EAED,MAAO,CACL,GAAG,EACH,qBACA,UACA,YAAa,EAAY,YACzB,cAAe,EAAY,cAC3B,gBACA,QACA,SACA,oBACA,UACF,CACF"}
@@ -34,16 +34,8 @@ export type CheckableHandle = FieldHandle & {
34
34
  handleKeydown: (event: KeyboardEvent) => boolean;
35
35
  indeterminate: Signal<boolean>;
36
36
  /**
37
- * Restores `checked`/`indeterminate` to their native "default" value: whatever `checked`/
38
- * `indeterminate` currently hold, if the user has never interacted with this control yet, or
39
- * the value snapshotted at creation, if they have. Two states, not one, because this
40
- * component reflects the interactive `checked` state back onto the host's `checked` attribute
41
- * (for `:host([checked])` styling, since shadow-DOM custom elements have no native `:checked`
42
- * pseudo-class to style against) — so `options.checked` itself changes on every click and
43
- * can't double as "the default to revert to" post-interaction, the way an uncontrolled
44
- * `<input>`'s `value` attribute can (see `createTextField`'s `reset()`, which has no
45
- * "post-interaction" case to handle for exactly that reason). Wire into
46
- * `useField({ onReset: checkable.reset })`.
37
+ * Restores `checked`/`indeterminate` to their native "default" value. See `DirtyTracker`
38
+ * for the two-state rationale. Wire into `useField({ onReset: checkable.reset })`.
47
39
  */
48
40
  reset: () => void;
49
41
  toggle: (event: Event) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"checkable.d.ts","sourceRoot":"","sources":["../../src/headless/checkable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAU,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAe,KAAK,qBAAqB,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAMzF,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,6FAA6F;IAC7F,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;;;;;;;;;;;OAWG;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,eAgF3D,CAAC"}
1
+ {"version":3,"file":"checkable.d.ts","sourceRoot":"","sources":["../../src/headless/checkable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAU,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAmC,KAAK,qBAAqB,EAAE,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,6FAA6F;IAC7F,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,41 +1,41 @@
1
- import { createField as e } from "./field-base.js";
2
- import { syncedSignal as t } from "./signals.js";
3
- import { createInteraction as n } from "./keyboard.js";
4
- import { computed as r, signal as i } from "@vielzeug/ripple";
1
+ import { createDirtyTracker as e, createField as t } from "./field-base.js";
2
+ import { syncedSignal as n } from "./signals.js";
3
+ import { createInteraction as r } from "./keyboard.js";
4
+ import { computed as i, signal as a } from "@vielzeug/ripple";
5
5
  //#region src/headless/checkable.ts
6
- var a = (a) => {
7
- let o = t(a.checked, a.signal, Boolean), s = a.indeterminate ? t(a.indeterminate, a.signal, Boolean) : i(!1), c = o.value, l = s.value, u = !1, d = r(() => s.value ? null : o.value ? a.value.value ?? "" : null), f = e(a), p = r(() => a.required?.value && (!o.value || s.value) ? { valueMissing: !0 } : null), m = r(() => p.value ? a.requiredMessage?.value || "This field is required." : ""), h = () => {
8
- o.value = u ? c : !!a.checked.value, s.value = u ? l : !!a.indeterminate?.value, u = !1, f.triggerValidation("change");
9
- }, g = (e) => ({
10
- checked: o.value,
6
+ var o = (o) => {
7
+ let s = n(o.checked, o.signal, Boolean), c = o.indeterminate ? n(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." : ""), g = () => {
8
+ s.value = d.isDirty ? l : !!o.checked.value, c.value = d.isDirty ? u : !!o.indeterminate?.value, d.clear(), p.triggerValidation("change");
9
+ }, _ = (e) => ({
10
+ checked: s.value,
11
11
  originalEvent: e,
12
- value: a.value.value ?? ""
13
- }), _ = (e) => {
14
- if (!f.disabled.value) {
15
- if (u = !0, a.group) {
16
- s.value = !1, a.group.toggle(a.value.value ?? "", e), a.onToggle?.(g(e));
12
+ value: o.value.value ?? ""
13
+ }), v = (e) => {
14
+ if (!p.disabled.value) {
15
+ if (d.markDirty(), o.group) {
16
+ c.value = !1, o.group.toggle(o.value.value ?? "", e), o.onToggle?.(_(e));
17
17
  return;
18
18
  }
19
- a.clearIndeterminateFirst && s.value || (o.value = !o.value), s.value = !1, a.onToggle?.(g(e));
19
+ o.clearIndeterminateFirst && c.value || (s.value = !s.value), c.value = !1, o.onToggle?.(_(e));
20
20
  }
21
- }, v = n({
22
- disabled: f.disabled,
23
- onPress: (e) => _(e)
21
+ }, y = r({
22
+ disabled: p.disabled,
23
+ onPress: (e) => v(e)
24
24
  });
25
25
  return {
26
- ...f,
27
- checkableFormValue: d,
28
- checked: o,
29
- handleClick: v.handleClick,
30
- handleKeydown: v.handleKeydown,
31
- indeterminate: s,
32
- reset: h,
33
- toggle: _,
34
- validationMessage: m,
35
- validity: p
26
+ ...p,
27
+ checkableFormValue: f,
28
+ checked: s,
29
+ handleClick: y.handleClick,
30
+ handleKeydown: y.handleKeydown,
31
+ indeterminate: c,
32
+ reset: g,
33
+ toggle: v,
34
+ validationMessage: h,
35
+ validity: m
36
36
  };
37
37
  };
38
38
  //#endregion
39
- export { a as createCheckable };
39
+ export { o as createCheckable };
40
40
 
41
41
  //# sourceMappingURL=checkable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkable.js","names":[],"sources":["../../src/headless/checkable.ts"],"sourcesContent":["import { computed, type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { createField, type ControlValidationMode, 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: whatever `checked`/\n * `indeterminate` currently hold, if the user has never interacted with this control yet, or\n * the value snapshotted at creation, if they have. Two states, not one, because this\n * component reflects the interactive `checked` state back onto the host's `checked` attribute\n * (for `:host([checked])` styling, since shadow-DOM custom elements have no native `:checked`\n * pseudo-class to style against) — so `options.checked` itself changes on every click and\n * can't double as \"the default to revert to\" post-interaction, the way an uncontrolled\n * `<input>`'s `value` attribute can (see `createTextField`'s `reset()`, which has no\n * \"post-interaction\" case to handle for exactly that reason). Wire into\n * `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 // Set the moment the user first interacts — see `reset()`'s doc comment for why this\n // matters: before that point, `options.checked` is still a reliable \"current default\" to\n // resync from (e.g. an async-loaded value arriving after mount); after it, `options.checked`\n // is contaminated by the click-driven attribute reflection and the frozen snapshot is the\n // only value that still represents \"the default\" in the native sense.\n let dirty = false;\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 = dirty ? initialChecked : Boolean(options.checked.value);\n indeterminate.value = dirty ? initialIndeterminate : Boolean(options.indeterminate?.value);\n dirty = false;\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 dirty = true;\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":";;;;;AAiEA,IAAa,KAAmB,MAA+C;CAC7E,IAAM,IAAU,EAAa,EAAQ,SAAS,EAAQ,QAAQ,OAAO,GAC/D,IAAgB,EAAQ,gBAC1B,EAAa,EAAQ,eAAe,EAAQ,QAAQ,OAAO,IAC3D,EAAO,EAAK,GACV,IAAiB,EAAQ,OACzB,IAAuB,EAAc,OAMvC,IAAQ,IAEN,IAAqB,QACzB,EAAc,QAAQ,OAAO,EAAQ,QAAS,EAAQ,MAAM,SAAS,KAAM,IAC7E,GAEM,IAAQ,EAAY,CAAO,GAE3B,IAAW,QACf,EAAQ,UAAU,UAAU,CAAC,EAAQ,SAAS,EAAc,SAAS,EAAE,cAAc,GAAK,IAAI,IAChG,GACM,IAAoB,QACxB,EAAS,QAAQ,EAAQ,iBAAiB,SAAS,4BAA4B,EACjF,GAEM,UAAoB;EAIxB,AAHA,EAAQ,QAAQ,IAAQ,IAAiB,EAAQ,EAAQ,QAAQ,OACjE,EAAc,QAAQ,IAAQ,IAAuB,EAAQ,EAAQ,eAAe,OACpF,IAAQ,IACR,EAAM,kBAAkB,QAAQ;CAClC,GAEM,KAAiB,OAA0C;EAC/D,SAAS,EAAQ;EACjB,eAAe;EACf,OAAO,EAAQ,MAAM,SAAS;CAChC,IAEM,KAAU,MAAuB;EACjC,OAAM,SAAS,OAInB;OAFA,IAAQ,IAEJ,EAAQ,OAAO;IAGjB,AAFA,EAAc,QAAQ,IACtB,EAAQ,MAAM,OAAO,EAAQ,MAAM,SAAS,IAAI,CAAK,GACrD,EAAQ,WAAW,EAAc,CAAK,CAAC;IAEvC;GACF;GASA,AAPI,EAAQ,2BAA2B,EAAc,UAGnD,EAAQ,QAAQ,CAAC,EAAQ,QAFzB,EAAc,QAAQ,IAMxB,EAAQ,WAAW,EAAc,CAAK,CAAC;EATvC;CAUF,GAEM,IAAc,EAAkB;EACpC,UAAU,EAAM;EAChB,UAAU,MAAU,EAAO,CAAK;CAClC,CAAC;CAED,OAAO;EACL,GAAG;EACH;EACA;EACA,aAAa,EAAY;EACzB,eAAe,EAAY;EAC3B;EACA;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"checkable.js","names":[],"sources":["../../src/headless/checkable.ts"],"sourcesContent":["import { computed, type Readable, type Signal, signal } from '@vielzeug/ripple';\n\nimport { createDirtyTracker, createField, type ControlValidationMode, 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":";;;;;AAyDA,IAAa,KAAmB,MAA+C;CAC7E,IAAM,IAAU,EAAa,EAAQ,SAAS,EAAQ,QAAQ,OAAO,GAC/D,IAAgB,EAAQ,gBAC1B,EAAa,EAAQ,eAAe,EAAQ,QAAQ,OAAO,IAC3D,EAAO,EAAK,GACV,IAAiB,EAAQ,OACzB,IAAuB,EAAc,OACrC,IAAe,EAAmB,GAElC,IAAqB,QACzB,EAAc,QAAQ,OAAO,EAAQ,QAAS,EAAQ,MAAM,SAAS,KAAM,IAC7E,GAEM,IAAQ,EAAY,CAAO,GAE3B,IAAW,QACf,EAAQ,UAAU,UAAU,CAAC,EAAQ,SAAS,EAAc,SAAS,EAAE,cAAc,GAAK,IAAI,IAChG,GACM,IAAoB,QACxB,EAAS,QAAQ,EAAQ,iBAAiB,SAAS,4BAA4B,EACjF,GAEM,UAAoB;EAIxB,AAHA,EAAQ,QAAQ,EAAa,UAAU,IAAiB,EAAQ,EAAQ,QAAQ,OAChF,EAAc,QAAQ,EAAa,UAAU,IAAuB,EAAQ,EAAQ,eAAe,OACnG,EAAa,MAAM,GACnB,EAAM,kBAAkB,QAAQ;CAClC,GAEM,KAAiB,OAA0C;EAC/D,SAAS,EAAQ;EACjB,eAAe;EACf,OAAO,EAAQ,MAAM,SAAS;CAChC,IAEM,KAAU,MAAuB;EACjC,OAAM,SAAS,OAInB;OAFA,EAAa,UAAU,GAEnB,EAAQ,OAAO;IAGjB,AAFA,EAAc,QAAQ,IACtB,EAAQ,MAAM,OAAO,EAAQ,MAAM,SAAS,IAAI,CAAK,GACrD,EAAQ,WAAW,EAAc,CAAK,CAAC;IAEvC;GACF;GASA,AAPI,EAAQ,2BAA2B,EAAc,UAGnD,EAAQ,QAAQ,CAAC,EAAQ,QAFzB,EAAc,QAAQ,IAMxB,EAAQ,WAAW,EAAc,CAAK,CAAC;EATvC;CAUF,GAEM,IAAc,EAAkB;EACpC,UAAU,EAAM;EAChB,UAAU,MAAU,EAAO,CAAK;CAClC,CAAC;CAED,OAAO;EACL,GAAG;EACH;EACA;EACA,aAAa,EAAY;EACzB,eAAe,EAAY;EAC3B;EACA;EACA;EACA;EACA;CACF;AACF"}
@@ -1,2 +1,2 @@
1
- const e=require("./field-base.cjs");let t=require("@vielzeug/ripple");var n=e=>e?Array.isArray(e)?e.filter(Boolean):e.split(`,`).map(e=>e.trim()).filter(Boolean):[],r=r=>{let i=(0,t.signal)([]),a=(0,t.computed)(()=>!!r.multiple?.value),o=(0,t.computed)(()=>a.value?i.value.join(`,`):i.value[0]??``),s=e=>{let t=a.value?e:e.slice(0,1);return[...new Set(t.filter(Boolean))]},c=!1,l=e=>{i.value=s(e)},u=e=>{c=!0,l(e)},d=()=>u([]),f=e=>{c=!0,i.value=i.value.filter(t=>t!==e)},p=e=>{if(a.value){i.value.includes(e)||u([...i.value,e]);return}u([e])},m=e=>{a.value&&i.value.includes(e)?f(e):p(e)},h=e=>{l(n(e))},g=e.createField(r),_=(0,t.computed)(()=>r.required?.value&&i.value.length===0?{valueMissing:!0}:null),v=(0,t.computed)(()=>_.value?r.requiredMessage?.value||`Please make a selection.`:``),y=(0,t.watch)(r.value,h,{immediate:!0});if(r.signal.addEventListener(`abort`,()=>y.dispose(),{once:!0}),r.multiple){let e=(0,t.watch)(r.multiple,()=>h(r.value.value));r.signal.addEventListener(`abort`,()=>e.dispose(),{once:!0})}let b=i.value,x=()=>{l(c?b:n(r.value.value)),c=!1,g.triggerValidation(`change`)};return{...g,clear:d,formValue:o,removeValue:f,reset:x,selectedValue:(0,t.computed)(()=>i.value[0]),selectedValues:i,selectValue:p,setValues:u,toggleValue:m,validationMessage:v,validity:_}};exports.createChoiceField=r;
1
+ const e=require("./field-base.cjs");let t=require("@vielzeug/ripple");var n=e=>e?Array.isArray(e)?e.filter(Boolean):e.split(`,`).map(e=>e.trim()).filter(Boolean):[],r=r=>{let i=(0,t.signal)([]),a=(0,t.computed)(()=>!!r.multiple?.value),o=(0,t.computed)(()=>a.value?i.value.join(`,`):i.value[0]??``),s=e=>{let t=a.value?e:e.slice(0,1);return[...new Set(t.filter(Boolean))]},c=e.createDirtyTracker(),l=e=>{i.value=s(e)},u=e=>{c.markDirty(),l(e)},d=()=>u([]),f=e=>{c.markDirty(),i.value=i.value.filter(t=>t!==e)},p=e=>{if(a.value){i.value.includes(e)||u([...i.value,e]);return}u([e])},m=e=>{a.value&&i.value.includes(e)?f(e):p(e)},h=e=>{l(n(e))},g=e.createField(r),_=(0,t.computed)(()=>r.required?.value&&i.value.length===0?{valueMissing:!0}:null),v=(0,t.computed)(()=>_.value?r.requiredMessage?.value||`Please make a selection.`:``),y=(0,t.watch)(r.value,h,{immediate:!0});if(r.signal.addEventListener(`abort`,()=>y.dispose(),{once:!0}),r.multiple){let e=(0,t.watch)(r.multiple,()=>h(r.value.value));r.signal.addEventListener(`abort`,()=>e.dispose(),{once:!0})}let b=i.value,x=()=>{l(c.isDirty?b:n(r.value.value)),c.clear(),g.triggerValidation(`change`)};return{...g,clear:d,formValue:o,removeValue:f,reset:x,selectedValue:(0,t.computed)(()=>i.value[0]),selectedValues:i,selectValue:p,setValues:u,toggleValue:m,validationMessage:v,validity:_}};exports.createChoiceField=r;
2
2
  //# sourceMappingURL=choice-field.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"choice-field.cjs","names":[],"sources":["../../src/headless/choice-field.ts"],"sourcesContent":["import { computed, type Readable, signal, watch } from '@vielzeug/ripple';\n\nimport { createField, type FieldHandle, type FieldOptions } from './field-base';\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\": whatever `value` currently holds, if the\n * user has never changed the selection yet, or the value snapshotted at creation, if they\n * have. Two states, not one — see `createCheckable`'s `reset()` for the identical situation\n * with `checked`, and why: `ore-radio-group`/`ore-checkbox-group` reflect the current\n * selection back onto the host's `value`/`values` attribute for `:host([value])` styling\n * (there's no native form control here to carry it), so `options.value` changes on every\n * selection and can't double as \"the default to revert to\" post-interaction.\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 selectedValues = signal<string[]>([]);\n const isMultiple = computed(() => Boolean(options.multiple?.value));\n const formValue = computed(() =>\n isMultiple.value ? selectedValues.value.join(',') : (selectedValues.value[0] ?? ''),\n );\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 // Set the moment the user first changes the selection — see `reset()`'s doc comment for why\n // this matters: before that point, `options.value` is still a reliable \"current default\" to\n // resync from (e.g. an async-loaded value arriving after mount); after it, `options.value` is\n // contaminated by the selection-driven attribute reflection (on the components where that\n // applies) and the frozen snapshot is the only value that still represents \"the default\".\n let dirty = false;\n\n // The actual state setter, shared by every mutation path. Deliberately *not* where `dirty`\n // gets set — `syncFromProp` (below) calls this directly to stay exempt from it, since mirroring\n // 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 dirty = true;\n applyValues(values);\n };\n const clear = (): void => setValues([]);\n const removeValue = (value: string): void => {\n dirty = true;\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 const valueSub = watch(options.value, syncFromProp, { immediate: true });\n\n options.signal.addEventListener('abort', () => valueSub.dispose(), { once: true });\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(dirty ? initialValues : parseChoiceValues(options.value.value));\n dirty = false;\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":"sEAgEA,IAAM,EAAqB,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,EAAqB,GAAmD,CACnF,IAAM,GAAA,EAAA,EAAA,OAAA,CAAkC,CAAC,CAAC,EACpC,GAAA,EAAA,EAAA,SAAA,KAA4B,EAAQ,EAAQ,UAAU,KAAM,EAC5D,GAAA,EAAA,EAAA,SAAA,KACJ,EAAW,MAAQ,EAAe,MAAM,KAAK,GAAG,EAAK,EAAe,MAAM,IAAM,EAClF,EAEM,EAAmB,GAA+B,CACtD,IAAM,EAAa,EAAW,MAAQ,EAAS,EAAO,MAAM,EAAG,CAAC,EAEhE,MAAO,CAAC,GAAG,IAAI,IAAI,EAAW,OAAO,OAAO,CAAC,CAAC,CAChD,EAOI,EAAQ,GAKN,EAAe,GAA2B,CAC9C,EAAe,MAAQ,EAAgB,CAAM,CAC/C,EAEM,EAAa,GAA2B,CAC5C,EAAQ,GACR,EAAY,CAAM,CACpB,EACM,MAAoB,EAAU,CAAC,CAAC,EAChC,EAAe,GAAwB,CAC3C,EAAQ,GACR,EAAe,MAAQ,EAAe,MAAM,OAAQ,GAAM,IAAM,CAAK,CACvE,EAEM,EAAe,GAAwB,CAC3C,GAAI,EAAW,MAAO,CACf,EAAe,MAAM,SAAS,CAAK,GAAG,EAAU,CAAC,GAAG,EAAe,MAAO,CAAK,CAAC,EAErF,MACF,CAEA,EAAU,CAAC,CAAK,CAAC,CACnB,EAEM,EAAe,GAAwB,CACvC,EAAW,OACT,EAAe,MAAM,SAAS,CAAK,EAAG,EAAY,CAAK,EACtD,EAAY,CAAK,CAI1B,EAEM,EAAgB,GAAmD,CACvE,EAAY,EAAkB,CAAS,CAAC,CAC1C,EAEM,EAAQ,EAAA,YAAY,CAAO,EAE3B,GAAA,EAAA,EAAA,SAAA,KACJ,EAAQ,UAAU,OAAS,EAAe,MAAM,SAAW,EAAI,CAAE,aAAc,EAAK,EAAI,IAC1F,EACM,GAAA,EAAA,EAAA,SAAA,KACJ,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,2BAA6B,EAClF,EAEM,GAAA,EAAA,EAAA,MAAA,CAAiB,EAAQ,MAAO,EAAc,CAAE,UAAW,EAAK,CAAC,EAIvE,GAFA,EAAQ,OAAO,iBAAiB,YAAe,EAAS,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,EAE7E,EAAQ,SAAU,CACpB,IAAM,GAAA,EAAA,EAAA,MAAA,CAAoB,EAAQ,aAAgB,EAAa,EAAQ,MAAM,KAAK,CAAC,EAEnF,EAAQ,OAAO,iBAAiB,YAAe,EAAY,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,CACtF,CAEA,IAAM,EAAgB,EAAe,MAC/B,MAAoB,CACxB,EAAY,EAAQ,EAAgB,EAAkB,EAAQ,MAAM,KAAK,CAAC,EAC1E,EAAQ,GACR,EAAM,kBAAkB,QAAQ,CAClC,EAEA,MAAO,CACL,GAAG,EACH,QACA,YACA,cACA,QACA,eAAA,EAAA,EAAA,SAAA,KAA8B,EAAe,MAAM,EAAE,EACrD,iBACA,cACA,YACA,cACA,oBACA,UACF,CACF"}
1
+ {"version":3,"file":"choice-field.cjs","names":[],"sources":["../../src/headless/choice-field.ts"],"sourcesContent":["import { computed, type Readable, signal, watch } from '@vielzeug/ripple';\n\nimport { createDirtyTracker, createField, type FieldHandle, type FieldOptions } from './field-base';\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 selectedValues = signal<string[]>([]);\n const isMultiple = computed(() => Boolean(options.multiple?.value));\n const formValue = computed(() =>\n isMultiple.value ? selectedValues.value.join(',') : (selectedValues.value[0] ?? ''),\n );\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 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 const valueSub = watch(options.value, syncFromProp, { immediate: true });\n\n options.signal.addEventListener('abort', () => valueSub.dispose(), { once: true });\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":"sEA2DA,IAAM,EAAqB,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,EAAqB,GAAmD,CACnF,IAAM,GAAA,EAAA,EAAA,OAAA,CAAkC,CAAC,CAAC,EACpC,GAAA,EAAA,EAAA,SAAA,KAA4B,EAAQ,EAAQ,UAAU,KAAM,EAC5D,GAAA,EAAA,EAAA,SAAA,KACJ,EAAW,MAAQ,EAAe,MAAM,KAAK,GAAG,EAAK,EAAe,MAAM,IAAM,EAClF,EAEM,EAAmB,GAA+B,CACtD,IAAM,EAAa,EAAW,MAAQ,EAAS,EAAO,MAAM,EAAG,CAAC,EAEhE,MAAO,CAAC,GAAG,IAAI,IAAI,EAAW,OAAO,OAAO,CAAC,CAAC,CAChD,EAEM,EAAe,EAAA,mBAAmB,EAKlC,EAAe,GAA2B,CAC9C,EAAe,MAAQ,EAAgB,CAAM,CAC/C,EAEM,EAAa,GAA2B,CAC5C,EAAa,UAAU,EACvB,EAAY,CAAM,CACpB,EACM,MAAoB,EAAU,CAAC,CAAC,EAChC,EAAe,GAAwB,CAC3C,EAAa,UAAU,EACvB,EAAe,MAAQ,EAAe,MAAM,OAAQ,GAAM,IAAM,CAAK,CACvE,EAEM,EAAe,GAAwB,CAC3C,GAAI,EAAW,MAAO,CACf,EAAe,MAAM,SAAS,CAAK,GAAG,EAAU,CAAC,GAAG,EAAe,MAAO,CAAK,CAAC,EAErF,MACF,CAEA,EAAU,CAAC,CAAK,CAAC,CACnB,EAEM,EAAe,GAAwB,CACvC,EAAW,OACT,EAAe,MAAM,SAAS,CAAK,EAAG,EAAY,CAAK,EACtD,EAAY,CAAK,CAI1B,EAEM,EAAgB,GAAmD,CACvE,EAAY,EAAkB,CAAS,CAAC,CAC1C,EAEM,EAAQ,EAAA,YAAY,CAAO,EAE3B,GAAA,EAAA,EAAA,SAAA,KACJ,EAAQ,UAAU,OAAS,EAAe,MAAM,SAAW,EAAI,CAAE,aAAc,EAAK,EAAI,IAC1F,EACM,GAAA,EAAA,EAAA,SAAA,KACJ,EAAS,MAAQ,EAAQ,iBAAiB,OAAS,2BAA6B,EAClF,EAEM,GAAA,EAAA,EAAA,MAAA,CAAiB,EAAQ,MAAO,EAAc,CAAE,UAAW,EAAK,CAAC,EAIvE,GAFA,EAAQ,OAAO,iBAAiB,YAAe,EAAS,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,EAE7E,EAAQ,SAAU,CACpB,IAAM,GAAA,EAAA,EAAA,MAAA,CAAoB,EAAQ,aAAgB,EAAa,EAAQ,MAAM,KAAK,CAAC,EAEnF,EAAQ,OAAO,iBAAiB,YAAe,EAAY,QAAQ,EAAG,CAAE,KAAM,EAAK,CAAC,CACtF,CAEA,IAAM,EAAgB,EAAe,MAC/B,MAAoB,CACxB,EAAY,EAAa,QAAU,EAAgB,EAAkB,EAAQ,MAAM,KAAK,CAAC,EACzF,EAAa,MAAM,EACnB,EAAM,kBAAkB,QAAQ,CAClC,EAEA,MAAO,CACL,GAAG,EACH,QACA,YACA,cACA,QACA,eAAA,EAAA,EAAA,SAAA,KAA8B,EAAe,MAAM,EAAE,EACrD,iBACA,cACA,YACA,cACA,oBACA,UACF,CACF"}
@@ -25,13 +25,8 @@ export type ChoiceFieldHandle = FieldHandle & {
25
25
  formValue: Readable<string>;
26
26
  removeValue: (value: string) => void;
27
27
  /**
28
- * Restores the selection to its native "default": whatever `value` currently holds, if the
29
- * user has never changed the selection yet, or the value snapshotted at creation, if they
30
- * have. Two states, not one — see `createCheckable`'s `reset()` for the identical situation
31
- * with `checked`, and why: `ore-radio-group`/`ore-checkbox-group` reflect the current
32
- * selection back onto the host's `value`/`values` attribute for `:host([value])` styling
33
- * (there's no native form control here to carry it), so `options.value` changes on every
34
- * selection and can't double as "the default to revert to" post-interaction.
28
+ * Restores the selection to its native "default". See `DirtyTracker` for the two-state
29
+ * rationale.
35
30
  */
36
31
  reset: () => void;
37
32
  selectedValue: Readable<string | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"choice-field.d.ts","sourceRoot":"","sources":["../../src/headless/choice-field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAiB,MAAM,kBAAkB,CAAC;AAE1E,OAAO,EAAe,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAIhF,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,2FAA2F;IAC3F,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;;;;;;;;OAQG;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,iBAoG/D,CAAC"}
1
+ {"version":3,"file":"choice-field.d.ts","sourceRoot":"","sources":["../../src/headless/choice-field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAiB,MAAM,kBAAkB,CAAC;AAE1E,OAAO,EAAmC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAIpG,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,2FAA2F;IAC3F,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,iBA+F/D,CAAC"}
@@ -1,50 +1,50 @@
1
- import { createField as e } from "./field-base.js";
2
- import { computed as t, signal as n, watch as r } from "@vielzeug/ripple";
1
+ import { createDirtyTracker as e, createField as t } from "./field-base.js";
2
+ import { computed as n, signal as r, watch as i } from "@vielzeug/ripple";
3
3
  //#region src/headless/choice-field.ts
4
- var i = (e) => e ? Array.isArray(e) ? e.filter(Boolean) : e.split(",").map((e) => e.trim()).filter(Boolean) : [], a = (a) => {
5
- let o = n([]), s = t(() => !!a.multiple?.value), c = t(() => s.value ? o.value.join(",") : o.value[0] ?? ""), l = (e) => {
6
- let t = s.value ? e : e.slice(0, 1);
4
+ var a = (e) => e ? Array.isArray(e) ? e.filter(Boolean) : e.split(",").map((e) => e.trim()).filter(Boolean) : [], o = (o) => {
5
+ let s = r([]), c = n(() => !!o.multiple?.value), l = n(() => c.value ? s.value.join(",") : s.value[0] ?? ""), u = (e) => {
6
+ let t = c.value ? e : e.slice(0, 1);
7
7
  return [...new Set(t.filter(Boolean))];
8
- }, u = !1, d = (e) => {
9
- o.value = l(e);
10
- }, f = (e) => {
11
- u = !0, d(e);
12
- }, p = () => f([]), m = (e) => {
13
- u = !0, o.value = o.value.filter((t) => t !== e);
14
- }, h = (e) => {
15
- if (s.value) {
16
- o.value.includes(e) || f([...o.value, e]);
8
+ }, d = e(), f = (e) => {
9
+ s.value = u(e);
10
+ }, p = (e) => {
11
+ d.markDirty(), f(e);
12
+ }, m = () => p([]), h = (e) => {
13
+ d.markDirty(), s.value = s.value.filter((t) => t !== e);
14
+ }, g = (e) => {
15
+ if (c.value) {
16
+ s.value.includes(e) || p([...s.value, e]);
17
17
  return;
18
18
  }
19
- f([e]);
20
- }, g = (e) => {
21
- s.value && o.value.includes(e) ? m(e) : h(e);
19
+ p([e]);
22
20
  }, _ = (e) => {
23
- d(i(e));
24
- }, v = e(a), y = t(() => a.required?.value && o.value.length === 0 ? { valueMissing: !0 } : null), b = t(() => y.value ? a.requiredMessage?.value || "Please make a selection." : ""), x = r(a.value, _, { immediate: !0 });
25
- if (a.signal.addEventListener("abort", () => x.dispose(), { once: !0 }), a.multiple) {
26
- let e = r(a.multiple, () => _(a.value.value));
27
- a.signal.addEventListener("abort", () => e.dispose(), { once: !0 });
21
+ c.value && s.value.includes(e) ? h(e) : g(e);
22
+ }, v = (e) => {
23
+ f(a(e));
24
+ }, y = t(o), b = n(() => o.required?.value && s.value.length === 0 ? { valueMissing: !0 } : null), x = n(() => b.value ? o.requiredMessage?.value || "Please make a selection." : ""), S = i(o.value, v, { immediate: !0 });
25
+ if (o.signal.addEventListener("abort", () => S.dispose(), { once: !0 }), o.multiple) {
26
+ let e = i(o.multiple, () => v(o.value.value));
27
+ o.signal.addEventListener("abort", () => e.dispose(), { once: !0 });
28
28
  }
29
- let S = o.value, C = () => {
30
- d(u ? S : i(a.value.value)), u = !1, v.triggerValidation("change");
29
+ let C = s.value, w = () => {
30
+ f(d.isDirty ? C : a(o.value.value)), d.clear(), y.triggerValidation("change");
31
31
  };
32
32
  return {
33
- ...v,
34
- clear: p,
35
- formValue: c,
36
- removeValue: m,
37
- reset: C,
38
- selectedValue: t(() => o.value[0]),
39
- selectedValues: o,
40
- selectValue: h,
41
- setValues: f,
42
- toggleValue: g,
43
- validationMessage: b,
44
- validity: y
33
+ ...y,
34
+ clear: m,
35
+ formValue: l,
36
+ removeValue: h,
37
+ reset: w,
38
+ selectedValue: n(() => s.value[0]),
39
+ selectedValues: s,
40
+ selectValue: g,
41
+ setValues: p,
42
+ toggleValue: _,
43
+ validationMessage: x,
44
+ validity: b
45
45
  };
46
46
  };
47
47
  //#endregion
48
- export { a as createChoiceField };
48
+ export { o as createChoiceField };
49
49
 
50
50
  //# sourceMappingURL=choice-field.js.map