@ecoma-io/loom 0.1.1 → 0.2.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 (495) hide show
  1. package/README.md +54 -57
  2. package/dist/blocks/AppHeader/AppHeader.js.map +1 -1
  3. package/dist/blocks/AppHeader/AppHeader.vue.d.ts +5 -6
  4. package/dist/blocks/AppHeader/AppHeader.vue.d.ts.map +1 -1
  5. package/dist/blocks/AppHeader/AppHeader.vue_vue_type_script_setup_true_lang.js +6 -7
  6. package/dist/blocks/AppHeader/AppHeader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  7. package/dist/blocks/DashboardGrid/DashboardGrid.vue.d.ts +1 -1
  8. package/dist/blocks/DashboardGrid/DashboardGrid.vue.d.ts.map +1 -1
  9. package/dist/blocks/DesktopAppShell/DesktopAppShell.js +7 -0
  10. package/dist/blocks/DesktopAppShell/DesktopAppShell.js.map +1 -0
  11. package/dist/blocks/DesktopAppShell/DesktopAppShell.vue.d.ts +102 -0
  12. package/dist/blocks/DesktopAppShell/DesktopAppShell.vue.d.ts.map +1 -0
  13. package/dist/blocks/DesktopAppShell/DesktopAppShell.vue_vue_type_script_setup_true_lang.js +74 -0
  14. package/dist/blocks/DesktopAppShell/DesktopAppShell.vue_vue_type_script_setup_true_lang.js.map +1 -0
  15. package/dist/blocks/ErrorState/ErrorState.js +7 -0
  16. package/dist/blocks/ErrorState/ErrorState.js.map +1 -0
  17. package/dist/blocks/ErrorState/ErrorState.vue.d.ts +44 -0
  18. package/dist/blocks/ErrorState/ErrorState.vue.d.ts.map +1 -0
  19. package/dist/blocks/ErrorState/ErrorState.vue_vue_type_script_setup_true_lang.js +60 -0
  20. package/dist/blocks/ErrorState/ErrorState.vue_vue_type_script_setup_true_lang.js.map +1 -0
  21. package/dist/blocks/FormActions/FormActions.js +7 -0
  22. package/dist/blocks/FormActions/FormActions.js.map +1 -0
  23. package/dist/blocks/FormActions/FormActions.vue.d.ts +51 -0
  24. package/dist/blocks/FormActions/FormActions.vue.d.ts.map +1 -0
  25. package/dist/blocks/FormActions/FormActions.vue_vue_type_script_setup_true_lang.js +21 -0
  26. package/dist/blocks/FormActions/FormActions.vue_vue_type_script_setup_true_lang.js.map +1 -0
  27. package/dist/blocks/FormSection/FormSection.js +7 -0
  28. package/dist/blocks/FormSection/FormSection.js.map +1 -0
  29. package/dist/blocks/FormSection/FormSection.vue.d.ts +85 -0
  30. package/dist/blocks/FormSection/FormSection.vue.d.ts.map +1 -0
  31. package/dist/blocks/FormSection/FormSection.vue_vue_type_script_setup_true_lang.js +62 -0
  32. package/dist/blocks/FormSection/FormSection.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/blocks/LoadingState/LoadingState.js +7 -0
  34. package/dist/blocks/LoadingState/LoadingState.js.map +1 -0
  35. package/dist/blocks/LoadingState/LoadingState.vue.d.ts +22 -0
  36. package/dist/blocks/LoadingState/LoadingState.vue.d.ts.map +1 -0
  37. package/dist/blocks/LoadingState/LoadingState.vue_vue_type_script_setup_true_lang.js +57 -0
  38. package/dist/blocks/LoadingState/LoadingState.vue_vue_type_script_setup_true_lang.js.map +1 -0
  39. package/dist/blocks/MetricCard/MetricCard.js +7 -0
  40. package/dist/blocks/MetricCard/MetricCard.js.map +1 -0
  41. package/dist/blocks/MetricCard/MetricCard.vue.d.ts +59 -0
  42. package/dist/blocks/MetricCard/MetricCard.vue.d.ts.map +1 -0
  43. package/dist/blocks/MetricCard/MetricCard.vue_vue_type_script_setup_true_lang.js +63 -0
  44. package/dist/blocks/MetricCard/MetricCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/blocks/SidebarNav/SidebarNav.js.map +1 -1
  46. package/dist/blocks/SidebarNav/SidebarNav.vue_vue_type_script_setup_true_lang.js.map +1 -1
  47. package/dist/blocks/TitleBar/TitleBar.js.map +1 -1
  48. package/dist/blocks/TitleBar/TitleBar.vue.d.ts +37 -3
  49. package/dist/blocks/TitleBar/TitleBar.vue.d.ts.map +1 -1
  50. package/dist/blocks/TitleBar/TitleBar.vue_vue_type_script_setup_true_lang.js +14 -14
  51. package/dist/blocks/TitleBar/TitleBar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  52. package/dist/blocks/ToastStack/ToastStack.js.map +1 -1
  53. package/dist/blocks/ToastStack/ToastStack.vue.d.ts +20 -0
  54. package/dist/blocks/ToastStack/ToastStack.vue.d.ts.map +1 -1
  55. package/dist/blocks/ToastStack/ToastStack.vue_vue_type_script_setup_true_lang.js +16 -5
  56. package/dist/blocks/ToastStack/ToastStack.vue_vue_type_script_setup_true_lang.js.map +1 -1
  57. package/dist/composition/Center/Center.js +7 -0
  58. package/dist/composition/Center/Center.js.map +1 -0
  59. package/dist/composition/Center/Center.vue.d.ts +40 -0
  60. package/dist/composition/Center/Center.vue.d.ts.map +1 -0
  61. package/dist/composition/Center/Center.vue_vue_type_script_setup_true_lang.js +29 -0
  62. package/dist/composition/Center/Center.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/composition/Frame/Frame.js +7 -0
  64. package/dist/composition/Frame/Frame.js.map +1 -0
  65. package/dist/composition/Frame/Frame.vue.d.ts +35 -0
  66. package/dist/composition/Frame/Frame.vue.d.ts.map +1 -0
  67. package/dist/composition/Frame/Frame.vue_vue_type_script_setup_true_lang.js +29 -0
  68. package/dist/composition/Frame/Frame.vue_vue_type_script_setup_true_lang.js.map +1 -0
  69. package/dist/composition/Grid/Grid.js +7 -0
  70. package/dist/composition/Grid/Grid.js.map +1 -0
  71. package/dist/composition/Grid/Grid.vue.d.ts +41 -0
  72. package/dist/composition/Grid/Grid.vue.d.ts.map +1 -0
  73. package/dist/composition/Grid/Grid.vue_vue_type_script_setup_true_lang.js +27 -0
  74. package/dist/composition/Grid/Grid.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/composition/Inline/Inline.js +7 -0
  76. package/dist/composition/Inline/Inline.js.map +1 -0
  77. package/dist/composition/Inline/Inline.vue.d.ts +46 -0
  78. package/dist/composition/Inline/Inline.vue.d.ts.map +1 -0
  79. package/dist/composition/Inline/Inline.vue_vue_type_script_setup_true_lang.js +35 -0
  80. package/dist/composition/Inline/Inline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  81. package/dist/composition/ScrollReel/ScrollReel.js +7 -0
  82. package/dist/composition/ScrollReel/ScrollReel.js.map +1 -0
  83. package/dist/composition/ScrollReel/ScrollReel.vue.d.ts +42 -0
  84. package/dist/composition/ScrollReel/ScrollReel.vue.d.ts.map +1 -0
  85. package/dist/composition/ScrollReel/ScrollReel.vue_vue_type_script_setup_true_lang.js +79 -0
  86. package/dist/composition/ScrollReel/ScrollReel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  87. package/dist/composition/Sidebar/Sidebar.js +7 -0
  88. package/dist/composition/Sidebar/Sidebar.js.map +1 -0
  89. package/dist/composition/Sidebar/Sidebar.vue.d.ts +52 -0
  90. package/dist/composition/Sidebar/Sidebar.vue.d.ts.map +1 -0
  91. package/dist/composition/Sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +36 -0
  92. package/dist/composition/Sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  93. package/dist/composition/Split/Split.js +7 -0
  94. package/dist/composition/Split/Split.js.map +1 -0
  95. package/dist/composition/Split/Split.vue.d.ts +58 -0
  96. package/dist/composition/Split/Split.vue.d.ts.map +1 -0
  97. package/dist/composition/Split/Split.vue_vue_type_script_setup_true_lang.js +40 -0
  98. package/dist/composition/Split/Split.vue_vue_type_script_setup_true_lang.js.map +1 -0
  99. package/dist/composition/Stack/Stack.js +7 -0
  100. package/dist/composition/Stack/Stack.js.map +1 -0
  101. package/dist/composition/Stack/Stack.vue.d.ts +43 -0
  102. package/dist/composition/Stack/Stack.vue.d.ts.map +1 -0
  103. package/dist/composition/Stack/Stack.vue_vue_type_script_setup_true_lang.js +30 -0
  104. package/dist/composition/Stack/Stack.vue_vue_type_script_setup_true_lang.js.map +1 -0
  105. package/dist/index.d.ts +119 -11
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +83 -5
  108. package/dist/layouts/AppShell/AppShell.js +7 -0
  109. package/dist/layouts/AppShell/AppShell.js.map +1 -0
  110. package/dist/layouts/AppShell/AppShell.vue.d.ts +51 -0
  111. package/dist/layouts/AppShell/AppShell.vue.d.ts.map +1 -0
  112. package/dist/layouts/AppShell/AppShell.vue_vue_type_script_setup_true_lang.js +47 -0
  113. package/dist/layouts/AppShell/AppShell.vue_vue_type_script_setup_true_lang.js.map +1 -0
  114. package/dist/layouts/Centered/Centered.js +7 -0
  115. package/dist/layouts/Centered/Centered.js.map +1 -0
  116. package/dist/layouts/Centered/Centered.vue.d.ts +48 -0
  117. package/dist/layouts/Centered/Centered.vue.d.ts.map +1 -0
  118. package/dist/layouts/Centered/Centered.vue_vue_type_script_setup_true_lang.js +35 -0
  119. package/dist/layouts/Centered/Centered.vue_vue_type_script_setup_true_lang.js.map +1 -0
  120. package/dist/layouts/Dashboard/Dashboard.js +7 -0
  121. package/dist/layouts/Dashboard/Dashboard.js.map +1 -0
  122. package/dist/layouts/Dashboard/Dashboard.vue.d.ts +60 -0
  123. package/dist/layouts/Dashboard/Dashboard.vue.d.ts.map +1 -0
  124. package/dist/layouts/Dashboard/Dashboard.vue_vue_type_script_setup_true_lang.js +64 -0
  125. package/dist/layouts/Dashboard/Dashboard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  126. package/dist/layouts/FormLayout/FormLayout.js +7 -0
  127. package/dist/layouts/FormLayout/FormLayout.js.map +1 -0
  128. package/dist/layouts/FormLayout/FormLayout.vue.d.ts +46 -0
  129. package/dist/layouts/FormLayout/FormLayout.vue.d.ts.map +1 -0
  130. package/dist/layouts/FormLayout/FormLayout.vue_vue_type_script_setup_true_lang.js +28 -0
  131. package/dist/layouts/FormLayout/FormLayout.vue_vue_type_script_setup_true_lang.js.map +1 -0
  132. package/dist/layouts/MasterDetail/MasterDetail.js +7 -0
  133. package/dist/layouts/MasterDetail/MasterDetail.js.map +1 -0
  134. package/dist/layouts/MasterDetail/MasterDetail.vue.d.ts +49 -0
  135. package/dist/layouts/MasterDetail/MasterDetail.vue.d.ts.map +1 -0
  136. package/dist/layouts/MasterDetail/MasterDetail.vue_vue_type_script_setup_true_lang.js +42 -0
  137. package/dist/layouts/MasterDetail/MasterDetail.vue_vue_type_script_setup_true_lang.js.map +1 -0
  138. package/dist/layouts/Reading/Reading.js +7 -0
  139. package/dist/layouts/Reading/Reading.js.map +1 -0
  140. package/dist/layouts/Reading/Reading.vue.d.ts +24 -0
  141. package/dist/layouts/Reading/Reading.vue.d.ts.map +1 -0
  142. package/dist/layouts/Reading/Reading.vue_vue_type_script_setup_true_lang.js +29 -0
  143. package/dist/layouts/Reading/Reading.vue_vue_type_script_setup_true_lang.js.map +1 -0
  144. package/dist/layouts/Settings/Settings.js +7 -0
  145. package/dist/layouts/Settings/Settings.js.map +1 -0
  146. package/dist/layouts/Settings/Settings.vue.d.ts +39 -0
  147. package/dist/layouts/Settings/Settings.vue.d.ts.map +1 -0
  148. package/dist/layouts/Settings/Settings.vue_vue_type_script_setup_true_lang.js +35 -0
  149. package/dist/layouts/Settings/Settings.vue_vue_type_script_setup_true_lang.js.map +1 -0
  150. package/dist/layouts/SplitLayout/SplitLayout.js +7 -0
  151. package/dist/layouts/SplitLayout/SplitLayout.js.map +1 -0
  152. package/dist/layouts/SplitLayout/SplitLayout.vue.d.ts +63 -0
  153. package/dist/layouts/SplitLayout/SplitLayout.vue.d.ts.map +1 -0
  154. package/dist/layouts/SplitLayout/SplitLayout.vue_vue_type_script_setup_true_lang.js +46 -0
  155. package/dist/layouts/SplitLayout/SplitLayout.vue_vue_type_script_setup_true_lang.js.map +1 -0
  156. package/dist/lib/ancestor-disabled.d.ts +35 -0
  157. package/dist/lib/ancestor-disabled.d.ts.map +1 -0
  158. package/dist/lib/ancestor-disabled.js +86 -0
  159. package/dist/lib/ancestor-disabled.js.map +1 -0
  160. package/dist/lib/cn.d.ts.map +1 -1
  161. package/dist/lib/cn.js +46 -1
  162. package/dist/lib/cn.js.map +1 -1
  163. package/dist/lib/count-labels.d.ts +78 -0
  164. package/dist/lib/count-labels.d.ts.map +1 -0
  165. package/dist/lib/count-labels.js +29 -0
  166. package/dist/lib/count-labels.js.map +1 -0
  167. package/dist/lib/date-labels.d.ts +278 -0
  168. package/dist/lib/date-labels.d.ts.map +1 -0
  169. package/dist/lib/date-labels.js +157 -0
  170. package/dist/lib/date-labels.js.map +1 -0
  171. package/dist/lib/field-context.d.ts +178 -0
  172. package/dist/lib/field-context.d.ts.map +1 -0
  173. package/dist/lib/field-context.js +106 -0
  174. package/dist/lib/field-context.js.map +1 -0
  175. package/dist/lib/label-registry.d.ts +75 -0
  176. package/dist/lib/label-registry.d.ts.map +1 -0
  177. package/dist/lib/labels.d.ts +116 -0
  178. package/dist/lib/labels.d.ts.map +1 -0
  179. package/dist/lib/labels.js +84 -0
  180. package/dist/lib/labels.js.map +1 -0
  181. package/dist/lib/theme.d.ts +68 -0
  182. package/dist/lib/theme.d.ts.map +1 -0
  183. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/CalendarDate.js +236 -0
  184. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/CalendarDate.js.map +1 -0
  185. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/calendars/GregorianCalendar.js +117 -0
  186. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/calendars/GregorianCalendar.js.map +1 -0
  187. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/conversion.js +142 -0
  188. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/conversion.js.map +1 -0
  189. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/manipulation.js +280 -0
  190. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/manipulation.js.map +1 -0
  191. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/queries.js +40 -0
  192. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/queries.js.map +1 -0
  193. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/string.js +69 -0
  194. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/string.js.map +1 -0
  195. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/utils.js +8 -0
  196. package/dist/node_modules/.pnpm/@internationalized_date@3.12.3/node_modules/@internationalized/date/dist/private/utils.js.map +1 -0
  197. package/dist/primitives/Accordion/Accordion.js +7 -0
  198. package/dist/primitives/Accordion/Accordion.js.map +1 -0
  199. package/dist/primitives/Accordion/Accordion.vue.d.ts +68 -0
  200. package/dist/primitives/Accordion/Accordion.vue.d.ts.map +1 -0
  201. package/dist/primitives/Accordion/Accordion.vue_vue_type_script_setup_true_lang.js +67 -0
  202. package/dist/primitives/Accordion/Accordion.vue_vue_type_script_setup_true_lang.js.map +1 -0
  203. package/dist/primitives/AlertDialog/AlertDialog.js +7 -0
  204. package/dist/primitives/AlertDialog/AlertDialog.js.map +1 -0
  205. package/dist/primitives/AlertDialog/AlertDialog.vue.d.ts +94 -0
  206. package/dist/primitives/AlertDialog/AlertDialog.vue.d.ts.map +1 -0
  207. package/dist/primitives/AlertDialog/AlertDialog.vue_vue_type_script_setup_true_lang.js +99 -0
  208. package/dist/primitives/AlertDialog/AlertDialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  209. package/dist/primitives/Avatar/Avatar.js +2 -2
  210. package/dist/primitives/Avatar/Avatar.js.map +1 -1
  211. package/dist/primitives/Avatar/Avatar.vue.d.ts +30 -3
  212. package/dist/primitives/Avatar/Avatar.vue.d.ts.map +1 -1
  213. package/dist/primitives/Avatar/Avatar.vue_vue_type_script_setup_true_lang.js +95 -14
  214. package/dist/primitives/Avatar/Avatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  215. package/dist/primitives/AvatarGroup/AvatarGroup.js +7 -0
  216. package/dist/primitives/AvatarGroup/AvatarGroup.js.map +1 -0
  217. package/dist/primitives/AvatarGroup/AvatarGroup.vue.d.ts +104 -0
  218. package/dist/primitives/AvatarGroup/AvatarGroup.vue.d.ts.map +1 -0
  219. package/dist/primitives/AvatarGroup/AvatarGroup.vue_vue_type_script_setup_true_lang.js +139 -0
  220. package/dist/primitives/AvatarGroup/AvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -0
  221. package/dist/primitives/Badge/Badge.js.map +1 -1
  222. package/dist/primitives/Badge/Badge.vue.d.ts +6 -6
  223. package/dist/primitives/Badge/Badge.vue.d.ts.map +1 -1
  224. package/dist/primitives/Badge/Badge.vue_vue_type_script_setup_true_lang.js +1 -1
  225. package/dist/primitives/Badge/Badge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  226. package/dist/primitives/Breadcrumb/Breadcrumb.js +7 -0
  227. package/dist/primitives/Breadcrumb/Breadcrumb.js.map +1 -0
  228. package/dist/primitives/Breadcrumb/Breadcrumb.vue.d.ts +25 -0
  229. package/dist/primitives/Breadcrumb/Breadcrumb.vue.d.ts.map +1 -0
  230. package/dist/primitives/Breadcrumb/Breadcrumb.vue_vue_type_script_setup_true_lang.js +48 -0
  231. package/dist/primitives/Breadcrumb/Breadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -0
  232. package/dist/primitives/Button/Button.js +2 -2
  233. package/dist/primitives/Button/Button.js.map +1 -1
  234. package/dist/primitives/Button/Button.vue.d.ts +31 -8
  235. package/dist/primitives/Button/Button.vue.d.ts.map +1 -1
  236. package/dist/primitives/Button/Button.vue_vue_type_script_setup_true_lang.js +32 -16
  237. package/dist/primitives/Button/Button.vue_vue_type_script_setup_true_lang.js.map +1 -1
  238. package/dist/primitives/Checkbox/Checkbox.js.map +1 -1
  239. package/dist/primitives/Checkbox/Checkbox.vue.d.ts +18 -3
  240. package/dist/primitives/Checkbox/Checkbox.vue.d.ts.map +1 -1
  241. package/dist/primitives/Checkbox/Checkbox.vue_vue_type_script_setup_true_lang.js +46 -7
  242. package/dist/primitives/Checkbox/Checkbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  243. package/dist/primitives/Chip/Chip.js +7 -0
  244. package/dist/primitives/Chip/Chip.js.map +1 -0
  245. package/dist/primitives/Chip/Chip.vue.d.ts +79 -0
  246. package/dist/primitives/Chip/Chip.vue.d.ts.map +1 -0
  247. package/dist/primitives/Chip/Chip.vue_vue_type_script_setup_true_lang.js +139 -0
  248. package/dist/primitives/Chip/Chip.vue_vue_type_script_setup_true_lang.js.map +1 -0
  249. package/dist/primitives/ColorPicker/ColorPicker.js +7 -0
  250. package/dist/primitives/ColorPicker/ColorPicker.js.map +1 -0
  251. package/dist/primitives/ColorPicker/ColorPicker.vue.d.ts +125 -0
  252. package/dist/primitives/ColorPicker/ColorPicker.vue.d.ts.map +1 -0
  253. package/dist/primitives/ColorPicker/ColorPicker.vue_vue_type_script_setup_true_lang.js +301 -0
  254. package/dist/primitives/ColorPicker/ColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -0
  255. package/dist/primitives/Combobox/Combobox.js +7 -0
  256. package/dist/primitives/Combobox/Combobox.js.map +1 -0
  257. package/dist/primitives/Combobox/Combobox.vue.d.ts +205 -0
  258. package/dist/primitives/Combobox/Combobox.vue.d.ts.map +1 -0
  259. package/dist/primitives/Combobox/Combobox.vue_vue_type_script_setup_true_lang.js +345 -0
  260. package/dist/primitives/Combobox/Combobox.vue_vue_type_script_setup_true_lang.js.map +1 -0
  261. package/dist/primitives/ContextMenu/ContextMenu.js +7 -0
  262. package/dist/primitives/ContextMenu/ContextMenu.js.map +1 -0
  263. package/dist/primitives/ContextMenu/ContextMenu.vue.d.ts +55 -0
  264. package/dist/primitives/ContextMenu/ContextMenu.vue.d.ts.map +1 -0
  265. package/dist/primitives/ContextMenu/ContextMenu.vue_vue_type_script_setup_true_lang.js +82 -0
  266. package/dist/primitives/ContextMenu/ContextMenu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  267. package/dist/primitives/DatePicker/DatePicker.js +7 -0
  268. package/dist/primitives/DatePicker/DatePicker.js.map +1 -0
  269. package/dist/primitives/DatePicker/DatePicker.vue.d.ts +102 -0
  270. package/dist/primitives/DatePicker/DatePicker.vue.d.ts.map +1 -0
  271. package/dist/primitives/DatePicker/DatePicker.vue_vue_type_script_setup_true_lang.js +292 -0
  272. package/dist/primitives/DatePicker/DatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -0
  273. package/dist/primitives/DateRangePicker/DateRangePicker.js +7 -0
  274. package/dist/primitives/DateRangePicker/DateRangePicker.js.map +1 -0
  275. package/dist/primitives/DateRangePicker/DateRangePicker.vue.d.ts +173 -0
  276. package/dist/primitives/DateRangePicker/DateRangePicker.vue.d.ts.map +1 -0
  277. package/dist/primitives/DateRangePicker/DateRangePicker.vue_vue_type_script_setup_true_lang.js +412 -0
  278. package/dist/primitives/DateRangePicker/DateRangePicker.vue_vue_type_script_setup_true_lang.js.map +1 -0
  279. package/dist/primitives/DateTimePicker/DateTimePicker.js +7 -0
  280. package/dist/primitives/DateTimePicker/DateTimePicker.js.map +1 -0
  281. package/dist/primitives/DateTimePicker/DateTimePicker.vue.d.ts +113 -0
  282. package/dist/primitives/DateTimePicker/DateTimePicker.vue.d.ts.map +1 -0
  283. package/dist/primitives/DateTimePicker/DateTimePicker.vue_vue_type_script_setup_true_lang.js +358 -0
  284. package/dist/primitives/DateTimePicker/DateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -0
  285. package/dist/primitives/DateTimeRangePicker/DateTimeRangePicker.js +7 -0
  286. package/dist/primitives/DateTimeRangePicker/DateTimeRangePicker.js.map +1 -0
  287. package/dist/primitives/DateTimeRangePicker/DateTimeRangePicker.vue.d.ts +181 -0
  288. package/dist/primitives/DateTimeRangePicker/DateTimeRangePicker.vue.d.ts.map +1 -0
  289. package/dist/primitives/DateTimeRangePicker/DateTimeRangePicker.vue_vue_type_script_setup_true_lang.js +562 -0
  290. package/dist/primitives/DateTimeRangePicker/DateTimeRangePicker.vue_vue_type_script_setup_true_lang.js.map +1 -0
  291. package/dist/primitives/Dialog/Dialog.js +2 -2
  292. package/dist/primitives/Dialog/Dialog.js.map +1 -1
  293. package/dist/primitives/Dialog/Dialog.vue.d.ts +39 -0
  294. package/dist/primitives/Dialog/Dialog.vue.d.ts.map +1 -1
  295. package/dist/primitives/Dialog/Dialog.vue_vue_type_script_setup_true_lang.js +14 -5
  296. package/dist/primitives/Dialog/Dialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  297. package/dist/primitives/Drawer/Drawer.js +7 -0
  298. package/dist/primitives/Drawer/Drawer.js.map +1 -0
  299. package/dist/primitives/Drawer/Drawer.vue.d.ts +110 -0
  300. package/dist/primitives/Drawer/Drawer.vue.d.ts.map +1 -0
  301. package/dist/primitives/Drawer/Drawer.vue_vue_type_script_setup_true_lang.js +223 -0
  302. package/dist/primitives/Drawer/Drawer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  303. package/dist/primitives/DropdownMenu/DropdownMenu.js.map +1 -1
  304. package/dist/primitives/DropdownMenu/DropdownMenu.vue.d.ts.map +1 -1
  305. package/dist/primitives/DropdownMenu/DropdownMenu.vue_vue_type_script_setup_true_lang.js +2 -2
  306. package/dist/primitives/DropdownMenu/DropdownMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  307. package/dist/primitives/Editable/Editable.js +7 -0
  308. package/dist/primitives/Editable/Editable.js.map +1 -0
  309. package/dist/primitives/Editable/Editable.vue.d.ts +200 -0
  310. package/dist/primitives/Editable/Editable.vue.d.ts.map +1 -0
  311. package/dist/primitives/Editable/Editable.vue_vue_type_script_setup_true_lang.js +275 -0
  312. package/dist/primitives/Editable/Editable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  313. package/dist/primitives/Field/Field.js.map +1 -1
  314. package/dist/primitives/Field/Field.vue.d.ts +32 -17
  315. package/dist/primitives/Field/Field.vue.d.ts.map +1 -1
  316. package/dist/primitives/Field/Field.vue_vue_type_script_setup_true_lang.js +51 -18
  317. package/dist/primitives/Field/Field.vue_vue_type_script_setup_true_lang.js.map +1 -1
  318. package/dist/primitives/Fieldset/Fieldset.js +7 -0
  319. package/dist/primitives/Fieldset/Fieldset.js.map +1 -0
  320. package/dist/primitives/Fieldset/Fieldset.vue.d.ts +91 -0
  321. package/dist/primitives/Fieldset/Fieldset.vue.d.ts.map +1 -0
  322. package/dist/primitives/Fieldset/Fieldset.vue_vue_type_script_setup_true_lang.js +138 -0
  323. package/dist/primitives/Fieldset/Fieldset.vue_vue_type_script_setup_true_lang.js.map +1 -0
  324. package/dist/primitives/FileUpload/FileUpload.js +7 -0
  325. package/dist/primitives/FileUpload/FileUpload.js.map +1 -0
  326. package/dist/primitives/FileUpload/FileUpload.vue.d.ts +132 -0
  327. package/dist/primitives/FileUpload/FileUpload.vue.d.ts.map +1 -0
  328. package/dist/primitives/FileUpload/FileUpload.vue_vue_type_script_setup_true_lang.js +359 -0
  329. package/dist/primitives/FileUpload/FileUpload.vue_vue_type_script_setup_true_lang.js.map +1 -0
  330. package/dist/primitives/HoverCard/HoverCard.js +7 -0
  331. package/dist/primitives/HoverCard/HoverCard.js.map +1 -0
  332. package/dist/primitives/HoverCard/HoverCard.vue.d.ts +68 -0
  333. package/dist/primitives/HoverCard/HoverCard.vue.d.ts.map +1 -0
  334. package/dist/primitives/HoverCard/HoverCard.vue_vue_type_script_setup_true_lang.js +62 -0
  335. package/dist/primitives/HoverCard/HoverCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  336. package/dist/primitives/IconButton/IconButton.js +7 -0
  337. package/dist/primitives/IconButton/IconButton.js.map +1 -0
  338. package/dist/primitives/IconButton/IconButton.vue.d.ts +45 -0
  339. package/dist/primitives/IconButton/IconButton.vue.d.ts.map +1 -0
  340. package/dist/primitives/IconButton/IconButton.vue_vue_type_script_setup_true_lang.js +58 -0
  341. package/dist/primitives/IconButton/IconButton.vue_vue_type_script_setup_true_lang.js.map +1 -0
  342. package/dist/primitives/Indicator/Indicator.js +7 -0
  343. package/dist/primitives/Indicator/Indicator.js.map +1 -0
  344. package/dist/primitives/Indicator/Indicator.vue.d.ts +144 -0
  345. package/dist/primitives/Indicator/Indicator.vue.d.ts.map +1 -0
  346. package/dist/primitives/Indicator/Indicator.vue_vue_type_script_setup_true_lang.js +198 -0
  347. package/dist/primitives/Indicator/Indicator.vue_vue_type_script_setup_true_lang.js.map +1 -0
  348. package/dist/primitives/Link/Link.js +7 -0
  349. package/dist/primitives/Link/Link.js.map +1 -0
  350. package/dist/primitives/Link/Link.vue.d.ts +46 -0
  351. package/dist/primitives/Link/Link.vue.d.ts.map +1 -0
  352. package/dist/primitives/Link/Link.vue_vue_type_script_setup_true_lang.js +85 -0
  353. package/dist/primitives/Link/Link.vue_vue_type_script_setup_true_lang.js.map +1 -0
  354. package/dist/primitives/Menubar/Menubar.js.map +1 -1
  355. package/dist/primitives/Menubar/Menubar.vue.d.ts.map +1 -1
  356. package/dist/primitives/Menubar/Menubar.vue_vue_type_script_setup_true_lang.js +1 -1
  357. package/dist/primitives/Menubar/Menubar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  358. package/dist/primitives/NumberField/NumberField.js +2 -2
  359. package/dist/primitives/NumberField/NumberField.js.map +1 -1
  360. package/dist/primitives/NumberField/NumberField.vue.d.ts +86 -25
  361. package/dist/primitives/NumberField/NumberField.vue.d.ts.map +1 -1
  362. package/dist/primitives/NumberField/NumberField.vue_vue_type_script_setup_true_lang.js +83 -39
  363. package/dist/primitives/NumberField/NumberField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  364. package/dist/primitives/OtpInput/OtpInput.js +7 -0
  365. package/dist/primitives/OtpInput/OtpInput.js.map +1 -0
  366. package/dist/primitives/OtpInput/OtpInput.vue.d.ts +110 -0
  367. package/dist/primitives/OtpInput/OtpInput.vue.d.ts.map +1 -0
  368. package/dist/primitives/OtpInput/OtpInput.vue_vue_type_script_setup_true_lang.js +130 -0
  369. package/dist/primitives/OtpInput/OtpInput.vue_vue_type_script_setup_true_lang.js.map +1 -0
  370. package/dist/primitives/Pagination/Pagination.js +7 -0
  371. package/dist/primitives/Pagination/Pagination.js.map +1 -0
  372. package/dist/primitives/Pagination/Pagination.vue.d.ts +143 -0
  373. package/dist/primitives/Pagination/Pagination.vue.d.ts.map +1 -0
  374. package/dist/primitives/Pagination/Pagination.vue_vue_type_script_setup_true_lang.js +231 -0
  375. package/dist/primitives/Pagination/Pagination.vue_vue_type_script_setup_true_lang.js.map +1 -0
  376. package/dist/primitives/Progress/Progress.js +2 -2
  377. package/dist/primitives/Progress/Progress.js.map +1 -1
  378. package/dist/primitives/Progress/Progress.vue.d.ts +81 -15
  379. package/dist/primitives/Progress/Progress.vue.d.ts.map +1 -1
  380. package/dist/primitives/Progress/Progress.vue_vue_type_script_setup_true_lang.js +88 -22
  381. package/dist/primitives/Progress/Progress.vue_vue_type_script_setup_true_lang.js.map +1 -1
  382. package/dist/primitives/RadialProgress/RadialProgress.js +7 -0
  383. package/dist/primitives/RadialProgress/RadialProgress.js.map +1 -0
  384. package/dist/primitives/RadialProgress/RadialProgress.vue.d.ts +96 -0
  385. package/dist/primitives/RadialProgress/RadialProgress.vue.d.ts.map +1 -0
  386. package/dist/primitives/RadialProgress/RadialProgress.vue_vue_type_script_setup_true_lang.js +189 -0
  387. package/dist/primitives/RadialProgress/RadialProgress.vue_vue_type_script_setup_true_lang.js.map +1 -0
  388. package/dist/primitives/RadioGroup/RadioGroup.js.map +1 -1
  389. package/dist/primitives/RadioGroup/RadioGroup.vue.d.ts +7 -7
  390. package/dist/primitives/RadioGroup/RadioGroup.vue.d.ts.map +1 -1
  391. package/dist/primitives/RadioGroup/RadioGroup.vue_vue_type_script_setup_true_lang.js +26 -9
  392. package/dist/primitives/RadioGroup/RadioGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  393. package/dist/primitives/Rating/Rating.js +7 -0
  394. package/dist/primitives/Rating/Rating.js.map +1 -0
  395. package/dist/primitives/Rating/Rating.vue.d.ts +113 -0
  396. package/dist/primitives/Rating/Rating.vue.d.ts.map +1 -0
  397. package/dist/primitives/Rating/Rating.vue_vue_type_script_setup_true_lang.js +282 -0
  398. package/dist/primitives/Rating/Rating.vue_vue_type_script_setup_true_lang.js.map +1 -0
  399. package/dist/primitives/ScrollArea/ScrollArea.js +7 -0
  400. package/dist/primitives/ScrollArea/ScrollArea.js.map +1 -0
  401. package/dist/primitives/ScrollArea/ScrollArea.vue.d.ts +25 -0
  402. package/dist/primitives/ScrollArea/ScrollArea.vue.d.ts.map +1 -0
  403. package/dist/primitives/ScrollArea/ScrollArea.vue_vue_type_script_setup_true_lang.js +45 -0
  404. package/dist/primitives/ScrollArea/ScrollArea.vue_vue_type_script_setup_true_lang.js.map +1 -0
  405. package/dist/primitives/SegmentedControl/SegmentedControl.js.map +1 -1
  406. package/dist/primitives/SegmentedControl/SegmentedControl.vue.d.ts +5 -5
  407. package/dist/primitives/SegmentedControl/SegmentedControl.vue.d.ts.map +1 -1
  408. package/dist/primitives/SegmentedControl/SegmentedControl.vue_vue_type_script_setup_true_lang.js +51 -8
  409. package/dist/primitives/SegmentedControl/SegmentedControl.vue_vue_type_script_setup_true_lang.js.map +1 -1
  410. package/dist/primitives/Select/Select.js.map +1 -1
  411. package/dist/primitives/Select/Select.vue.d.ts +10 -10
  412. package/dist/primitives/Select/Select.vue.d.ts.map +1 -1
  413. package/dist/primitives/Select/Select.vue_vue_type_script_setup_true_lang.js +105 -52
  414. package/dist/primitives/Select/Select.vue_vue_type_script_setup_true_lang.js.map +1 -1
  415. package/dist/primitives/Skeleton/Skeleton.vue.d.ts +1 -1
  416. package/dist/primitives/Slider/Slider.js.map +1 -1
  417. package/dist/primitives/Slider/Slider.vue.d.ts +24 -4
  418. package/dist/primitives/Slider/Slider.vue.d.ts.map +1 -1
  419. package/dist/primitives/Slider/Slider.vue_vue_type_script_setup_true_lang.js +79 -9
  420. package/dist/primitives/Slider/Slider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  421. package/dist/primitives/SpeedDial/SpeedDial.js +7 -0
  422. package/dist/primitives/SpeedDial/SpeedDial.js.map +1 -0
  423. package/dist/primitives/SpeedDial/SpeedDial.vue.d.ts +58 -0
  424. package/dist/primitives/SpeedDial/SpeedDial.vue.d.ts.map +1 -0
  425. package/dist/primitives/SpeedDial/SpeedDial.vue_vue_type_script_setup_true_lang.js +175 -0
  426. package/dist/primitives/SpeedDial/SpeedDial.vue_vue_type_script_setup_true_lang.js.map +1 -0
  427. package/dist/primitives/Stepper/Stepper.js +7 -0
  428. package/dist/primitives/Stepper/Stepper.js.map +1 -0
  429. package/dist/primitives/Stepper/Stepper.vue.d.ts +119 -0
  430. package/dist/primitives/Stepper/Stepper.vue.d.ts.map +1 -0
  431. package/dist/primitives/Stepper/Stepper.vue_vue_type_script_setup_true_lang.js +202 -0
  432. package/dist/primitives/Stepper/Stepper.vue_vue_type_script_setup_true_lang.js.map +1 -0
  433. package/dist/primitives/Switch/Switch.js.map +1 -1
  434. package/dist/primitives/Switch/Switch.vue.d.ts +13 -3
  435. package/dist/primitives/Switch/Switch.vue.d.ts.map +1 -1
  436. package/dist/primitives/Switch/Switch.vue_vue_type_script_setup_true_lang.js +34 -5
  437. package/dist/primitives/Switch/Switch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  438. package/dist/primitives/Tabs/Tabs.js.map +1 -1
  439. package/dist/primitives/Tabs/Tabs.vue.d.ts.map +1 -1
  440. package/dist/primitives/Tabs/Tabs.vue_vue_type_script_setup_true_lang.js +1 -1
  441. package/dist/primitives/Tabs/Tabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  442. package/dist/primitives/TagsInput/TagsInput.js +7 -0
  443. package/dist/primitives/TagsInput/TagsInput.js.map +1 -0
  444. package/dist/primitives/TagsInput/TagsInput.vue.d.ts +156 -0
  445. package/dist/primitives/TagsInput/TagsInput.vue.d.ts.map +1 -0
  446. package/dist/primitives/TagsInput/TagsInput.vue_vue_type_script_setup_true_lang.js +387 -0
  447. package/dist/primitives/TagsInput/TagsInput.vue_vue_type_script_setup_true_lang.js.map +1 -0
  448. package/dist/primitives/TextField/TextField.js +2 -2
  449. package/dist/primitives/TextField/TextField.js.map +1 -1
  450. package/dist/primitives/TextField/TextField.vue.d.ts +119 -10
  451. package/dist/primitives/TextField/TextField.vue.d.ts.map +1 -1
  452. package/dist/primitives/TextField/TextField.vue_vue_type_script_setup_true_lang.js +270 -21
  453. package/dist/primitives/TextField/TextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  454. package/dist/primitives/Textarea/Textarea.js +2 -2
  455. package/dist/primitives/Textarea/Textarea.js.map +1 -1
  456. package/dist/primitives/Textarea/Textarea.vue.d.ts +114 -14
  457. package/dist/primitives/Textarea/Textarea.vue.d.ts.map +1 -1
  458. package/dist/primitives/Textarea/Textarea.vue_vue_type_script_setup_true_lang.js +216 -20
  459. package/dist/primitives/Textarea/Textarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  460. package/dist/primitives/TimePicker/TimePicker.js +7 -0
  461. package/dist/primitives/TimePicker/TimePicker.js.map +1 -0
  462. package/dist/primitives/TimePicker/TimePicker.vue.d.ts +111 -0
  463. package/dist/primitives/TimePicker/TimePicker.vue.d.ts.map +1 -0
  464. package/dist/primitives/TimePicker/TimePicker.vue_vue_type_script_setup_true_lang.js +185 -0
  465. package/dist/primitives/TimePicker/TimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -0
  466. package/dist/primitives/Toast/Toast.js +2 -1
  467. package/dist/primitives/Toast/Toast.js.map +1 -1
  468. package/dist/primitives/Toast/Toast.vue.d.ts +29 -3
  469. package/dist/primitives/Toast/Toast.vue.d.ts.map +1 -1
  470. package/dist/primitives/Toast/Toast.vue_vue_type_script_setup_true_lang.js +17 -7
  471. package/dist/primitives/Toast/Toast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  472. package/dist/primitives/Toast/ToastItem.js +2 -2
  473. package/dist/primitives/Toast/ToastItem.js.map +1 -1
  474. package/dist/primitives/Toast/ToastItem.vue.d.ts +69 -12
  475. package/dist/primitives/Toast/ToastItem.vue.d.ts.map +1 -1
  476. package/dist/primitives/Toast/ToastItem.vue_vue_type_script_setup_true_lang.js +23 -23
  477. package/dist/primitives/Toast/ToastItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  478. package/dist/primitives/Tooltip/Tooltip.vue.d.ts +1 -1
  479. package/dist/primitives/Tooltip/Tooltip.vue.d.ts.map +1 -1
  480. package/dist/primitives/WindowControls/WindowControls.js +2 -2
  481. package/dist/primitives/WindowControls/WindowControls.js.map +1 -1
  482. package/dist/primitives/WindowControls/WindowControls.vue.d.ts +66 -10
  483. package/dist/primitives/WindowControls/WindowControls.vue.d.ts.map +1 -1
  484. package/dist/primitives/WindowControls/WindowControls.vue_vue_type_script_setup_true_lang.js +23 -13
  485. package/dist/primitives/WindowControls/WindowControls.vue_vue_type_script_setup_true_lang.js.map +1 -1
  486. package/dist/styles/global.css +116 -17
  487. package/dist/styles/theme.contrast.test.ts +460 -0
  488. package/dist/styles/theme.css +239 -41
  489. package/dist/theme.js +171 -0
  490. package/dist/theme.js.map +1 -0
  491. package/package.json +14 -4
  492. package/dist/icons/BrandMark.d.ts +0 -18
  493. package/dist/icons/BrandMark.d.ts.map +0 -1
  494. package/dist/icons/BrandMark.js +0 -47
  495. package/dist/icons/BrandMark.js.map +0 -1
@@ -5,34 +5,47 @@
5
5
  * (TextField, Textarea, a select, …). Setting `error` swaps InlineError in
6
6
  * for `hint`, because a row shows one message at a time, never both stacked.
7
7
  *
8
- * `for` does two jobs. It is what associates the `<label>` with the control
9
- * (WCAG 1.3.1 / 4.1.2) `label` alone, with no `for`, falls back to a plain
10
- * `<span>` rather than emit a `<label>` with nothing to point at. And it is
11
- * what the hint/error line derives its own id from (`${for}-description`),
12
- * which is the missing half of the accessibility contract: Field cannot
13
- * reach into an arbitrary slotted control to set `aria-describedby` on it,
14
- * so it publishes a predictable id instead and leaves that one attribute to
15
- * the consumer — the same way `invalid` already has to be set on the
16
- * control directly. TextField and Textarea both forward an `aria-describedby`
17
- * passed to them straight onto their native element, so wiring the two
18
- * together is exactly:
8
+ * **A Loom control in the slot wires itself.** Field publishes the row's id,
9
+ * the id of whichever message is showing, and `required` / `invalid` /
10
+ * `disabled` / `readonly` / `name`; every Loom form control picks those up
11
+ * through `useFieldControl()`. So the three attributes this component used to
12
+ * ask a caller to copy by hand `id`, `aria-describedby`, `invalid` — are now
13
+ * the default, and forgetting one is no longer a silent defect:
19
14
  *
20
15
  * ```vue
21
- * <Field label="Email" for="email" error="Invalid address">
22
- * <TextField id="email" aria-describedby="email-description" invalid />
16
+ * <Field label="Email" error="Invalid address" required>
17
+ * <TextField />
23
18
  * </Field>
24
19
  * ```
20
+ *
21
+ * A prop set on the control always wins, in both directions: `undefined` is
22
+ * the only value that yields, `false` is a decision. One control inside a row
23
+ * that is showing an error can still declare itself individually fine.
24
+ *
25
+ * **`for` is not a legacy path.** `provide`/`inject` only reaches a component
26
+ * that injects, so a bare `<input>`, a product's own control or a third-party
27
+ * widget in the slot cannot be wired this way — and that is the case `for` has
28
+ * always answered. Pass it and the label points at your id, the message line
29
+ * publishes `${for}-description` for your own `aria-describedby`, and a Loom
30
+ * control adopts that id rather than a generated one, so an existing call site
31
+ * keeps every id it published.
25
32
  */
26
33
  type __VLS_Props = {
27
- /** The visible label text. Renders as a real `<label>` only when `for` is also set. */
34
+ /** The visible label text. Renders as a real `<label>` once there is a control for it to name. */
28
35
  label?: string;
29
36
  /** Supporting text shown below the control. Hidden while `error` is set. */
30
37
  hint?: string;
31
- /** The error message. Renders through InlineError and replaces `hint`. */
38
+ /** The error message. Renders through InlineError, replaces `hint`, and marks a Loom control inside invalid. */
32
39
  error?: string;
33
- /** Marks the field mandatory and appends a destructive asterisk to the label. */
40
+ /** Marks the row mandatory: a destructive asterisk on the label, and `aria-required` on a Loom control inside. */
34
41
  required?: boolean;
35
- /** The id of the control in the default slot drives the label's `for` and the hint/error id. */
42
+ /** Disables a Loom control inside. Leave unset to let the control decide for itself. */
43
+ disabled?: boolean | undefined;
44
+ /** Renders a Loom control inside read-only: still focusable, still submitted, not editable. */
45
+ readonly?: boolean | undefined;
46
+ /** The name a Loom control inside submits under, for a native form post or `FormData`. */
47
+ name?: string;
48
+ /** The id of the control in the default slot. Omit it and Field generates one for a Loom control. */
36
49
  for?: string;
37
50
  };
38
51
  declare var __VLS_1: {};
@@ -40,6 +53,8 @@ type __VLS_Slots = {} & {
40
53
  default?: (props: typeof __VLS_1) => any;
41
54
  };
42
55
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
56
+ disabled: boolean | undefined;
57
+ readonly: boolean | undefined;
43
58
  required: boolean;
44
59
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
45
60
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1 +1 @@
1
- {"version":3,"file":"Field.vue.d.ts","sourceRoot":"","sources":["../../../src/primitives/Field/Field.vue"],"names":[],"mappings":"AA8FA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,KAAK,WAAW,GAAG;IACf,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kGAAkG;IAClG,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AA0FJ,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAK/C,QAAA,MAAM,UAAU;cApGD,OAAO;6EAuGpB,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"Field.vue.d.ts","sourceRoot":"","sources":["../../../src/primitives/Field/Field.vue"],"names":[],"mappings":"AAsLA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,KAAK,WAAW,GAAG;IACf,kGAAkG;IAClG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gHAAgH;IAChH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kHAAkH;IAClH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,0FAA0F;IAC1F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qGAAqG;IACrG,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AA2KJ,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAK/C,QAAA,MAAM,UAAU;cAzLD,OAAO,GAAG,SAAS;cAEnB,OAAO,GAAG,SAAS;cAJnB,OAAO;6EA8LpB,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -1,8 +1,9 @@
1
+ import { provideFieldContext, useOuterFieldContext } from "../../lib/field-context.js";
1
2
  import InlineError_default from "../InlineError/InlineError.js";
2
- import { computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, defineComponent, openBlock, renderSlot, toDisplayString } from "vue";
3
+ import { computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, defineComponent, openBlock, renderSlot, toDisplayString, useId, useSlots } from "vue";
3
4
  //#region src/primitives/Field/Field.vue?vue&type=script&setup=true&lang.ts
4
5
  var _hoisted_1 = { class: "flex flex-col gap-1.5" };
5
- var _hoisted_2 = ["for"];
6
+ var _hoisted_2 = ["id", "for"];
6
7
  var _hoisted_3 = {
7
8
  key: 0,
8
9
  class: "text-destructive"
@@ -23,23 +24,30 @@ var _hoisted_6 = ["id"];
23
24
  * (TextField, Textarea, a select, …). Setting `error` swaps InlineError in
24
25
  * for `hint`, because a row shows one message at a time, never both stacked.
25
26
  *
26
- * `for` does two jobs. It is what associates the `<label>` with the control
27
- * (WCAG 1.3.1 / 4.1.2) `label` alone, with no `for`, falls back to a plain
28
- * `<span>` rather than emit a `<label>` with nothing to point at. And it is
29
- * what the hint/error line derives its own id from (`${for}-description`),
30
- * which is the missing half of the accessibility contract: Field cannot
31
- * reach into an arbitrary slotted control to set `aria-describedby` on it,
32
- * so it publishes a predictable id instead and leaves that one attribute to
33
- * the consumer — the same way `invalid` already has to be set on the
34
- * control directly. TextField and Textarea both forward an `aria-describedby`
35
- * passed to them straight onto their native element, so wiring the two
36
- * together is exactly:
27
+ * **A Loom control in the slot wires itself.** Field publishes the row's id,
28
+ * the id of whichever message is showing, and `required` / `invalid` /
29
+ * `disabled` / `readonly` / `name`; every Loom form control picks those up
30
+ * through `useFieldControl()`. So the three attributes this component used to
31
+ * ask a caller to copy by hand `id`, `aria-describedby`, `invalid` — are now
32
+ * the default, and forgetting one is no longer a silent defect:
37
33
  *
38
34
  * ```vue
39
- * <Field label="Email" for="email" error="Invalid address">
40
- * <TextField id="email" aria-describedby="email-description" invalid />
35
+ * <Field label="Email" error="Invalid address" required>
36
+ * <TextField />
41
37
  * </Field>
42
38
  * ```
39
+ *
40
+ * A prop set on the control always wins, in both directions: `undefined` is
41
+ * the only value that yields, `false` is a decision. One control inside a row
42
+ * that is showing an error can still declare itself individually fine.
43
+ *
44
+ * **`for` is not a legacy path.** `provide`/`inject` only reaches a component
45
+ * that injects, so a bare `<input>`, a product's own control or a third-party
46
+ * widget in the slot cannot be wired this way — and that is the case `for` has
47
+ * always answered. Pass it and the label points at your id, the message line
48
+ * publishes `${for}-description` for your own `aria-describedby`, and a Loom
49
+ * control adopts that id rather than a generated one, so an existing call site
50
+ * keeps every id it published.
43
51
  */
44
52
  var Field_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
45
53
  __name: "Field",
@@ -51,16 +59,41 @@ var Field_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComp
51
59
  type: Boolean,
52
60
  default: false
53
61
  },
62
+ disabled: {
63
+ type: Boolean,
64
+ default: void 0
65
+ },
66
+ readonly: {
67
+ type: Boolean,
68
+ default: void 0
69
+ },
70
+ name: {},
54
71
  for: {}
55
72
  },
56
73
  setup(__props) {
57
74
  const props = __props;
58
- const descriptionId = computed(() => props.for ? `${props.for}-description` : void 0);
75
+ const generatedId = useId();
76
+ const hasControl = useSlots().default !== void 0;
77
+ const controlId = computed(() => props.for ?? (hasControl ? generatedId : void 0));
78
+ const labelId = computed(() => controlId.value ? `${controlId.value}-label` : void 0);
79
+ const descriptionId = computed(() => controlId.value ? `${controlId.value}-description` : void 0);
80
+ const group = useOuterFieldContext();
81
+ provideFieldContext({
82
+ controlId: () => controlId.value,
83
+ labelledBy: () => props.label ? labelId.value : void 0,
84
+ describedBy: () => props.error ?? props.hint ? descriptionId.value : void 0,
85
+ name: () => props.name,
86
+ required: () => props.required || void 0,
87
+ invalid: () => props.error ? true : void 0,
88
+ disabled: () => props.disabled,
89
+ readonly: () => props.readonly ?? group?.readonly.value
90
+ });
59
91
  return (_ctx, _cache) => {
60
92
  return openBlock(), createElementBlock("div", _hoisted_1, [
61
- __props.label && props.for ? (openBlock(), createElementBlock("label", {
93
+ __props.label && controlId.value ? (openBlock(), createElementBlock("label", {
62
94
  key: 0,
63
- for: props.for,
95
+ id: labelId.value,
96
+ for: controlId.value,
64
97
  class: "text-sm font-medium text-foreground"
65
98
  }, [createTextVNode(toDisplayString(__props.label) + " ", 1), __props.required ? (openBlock(), createElementBlock("span", _hoisted_3, "*")) : createCommentVNode("", true)], 8, _hoisted_2)) : __props.label ? (openBlock(), createElementBlock("span", _hoisted_4, [createTextVNode(toDisplayString(__props.label) + " ", 1), __props.required ? (openBlock(), createElementBlock("span", _hoisted_5, "*")) : createCommentVNode("", true)])) : createCommentVNode("", true),
66
99
  renderSlot(_ctx.$slots, "default"),
@@ -1 +1 @@
1
- {"version":3,"file":"Field.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/primitives/Field/Field.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/* eslint vuejs-accessibility/label-has-for: [\"error\", { \"required\": \"id\" }] */\n/*\n * The workspace default for this rule requires BOTH a `for`/id pairing AND\n * the control physically nested inside the `<label>` (`required: {every:\n * [\"nesting\",\"id\"]}`). Field labels a control passed through the default\n * slot — never nested inside the `<label>` — so \"nesting\" can never be\n * satisfied by any slot-based composition, regardless of how the label is\n * written. The `for`/id pairing below is itself a fully valid, standard\n * accessible-name association (WCAG 1.3.1 / 4.1.2); this override scopes the\n * rule to that single valid strategy for this file only.\n */\nimport { computed } from \"vue\";\nimport InlineError from \"../InlineError/InlineError.vue\";\n\n/**\n * Field — the form-row wrapper: label + control + one hint-or-error line, so\n * a consumer stops hand-assembling that layout at every call site. Pure\n * composition, not a Reka component — the default slot is the control\n * (TextField, Textarea, a select, …). Setting `error` swaps InlineError in\n * for `hint`, because a row shows one message at a time, never both stacked.\n *\n * `for` does two jobs. It is what associates the `<label>` with the control\n * (WCAG 1.3.1 / 4.1.2) — `label` alone, with no `for`, falls back to a plain\n * `<span>` rather than emit a `<label>` with nothing to point at. And it is\n * what the hint/error line derives its own id from (`${for}-description`),\n * which is the missing half of the accessibility contract: Field cannot\n * reach into an arbitrary slotted control to set `aria-describedby` on it,\n * so it publishes a predictable id instead and leaves that one attribute to\n * the consumer — the same way `invalid` already has to be set on the\n * control directly. TextField and Textarea both forward an `aria-describedby`\n * passed to them straight onto their native element, so wiring the two\n * together is exactly:\n *\n * ```vue\n * <Field label=\"Email\" for=\"email\" error=\"Invalid address\">\n * <TextField id=\"email\" aria-describedby=\"email-description\" invalid />\n * </Field>\n * ```\n */\nconst props = withDefaults(\n defineProps<{\n /** The visible label text. Renders as a real `<label>` only when `for` is also set. */\n label?: string;\n /** Supporting text shown below the control. Hidden while `error` is set. */\n hint?: string;\n /** The error message. Renders through InlineError and replaces `hint`. */\n error?: string;\n /** Marks the field mandatory and appends a destructive asterisk to the label. */\n required?: boolean;\n /** The id of the control in the default slot — drives the label's `for` and the hint/error id. */\n for?: string;\n }>(),\n { required: false },\n);\n\n// The hint and the error are mutually exclusive, so one id covers whichever\n// is showing. Undefined without `for`: an id with no control to point at it\n// would be a landmark for no one.\nconst descriptionId = computed(() => (props.for ? `${props.for}-description` : undefined));\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label v-if=\"label && props.for\" :for=\"props.for\" class=\"text-sm font-medium text-foreground\">\n {{ label }} <span v-if=\"required\" class=\"text-destructive\">*</span>\n </label>\n <span v-else-if=\"label\" class=\"text-sm font-medium text-foreground\">\n {{ label }} <span v-if=\"required\" class=\"text-destructive\">*</span>\n </span>\n <slot />\n <p v-if=\"hint && !error\" :id=\"descriptionId\" class=\"text-xs text-muted-foreground\">\n {{ hint }}\n </p>\n <InlineError v-if=\"error\" :id=\"descriptionId\" :message=\"error\" />\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwCA,MAAM,QAAQ;EAmBd,MAAM,gBAAgB,eAAgB,MAAM,MAAM,GAAG,MAAM,IAAI,gBAAgB,KAAA,CAAU;;GAIvF,OAAA,UAAA,GAAA,mBAYM,OAZN,YAYM;IAXS,QAAA,SAAS,MAAM,OAA5B,UAAA,GAAA,mBAEQ,SAAA;;KAF0B,KAAK,MAAM;KAAK,OAAM;IACnD,GAAA,CAAA,gBAAA,gBAAA,QAAA,KAAK,IAAG,KAAC,CAAA,GAAY,QAAA,YAAZ,UAAA,GAAA,mBAAuD,QAAvD,YAA+C,GAAC,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,GAAA,UAAA,KAE7C,QAAA,SAAjB,UAAA,GAAA,mBAEO,QAFP,YAEO,CADF,gBAAA,gBAAA,QAAA,KAAK,IAAG,KAAC,CAAA,GAAY,QAAA,YAAZ,UAAA,GAAA,mBAAuD,QAAvD,YAA+C,GAAC,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAE9D,WAAQ,KAAA,QAAA,SAAA;IACC,QAAA,QAAI,CAAK,QAAA,SAAlB,UAAA,GAAA,mBAEI,KAAA;;KAFsB,IAAI,cAAA;KAAe,OAAM;IAC9C,GAAA,gBAAA,QAAA,IAAI,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;IAEU,QAAA,SAAnB,UAAA,GAAA,YAAiE,qBAAA;;KAAtC,IAAI,cAAA;KAAgB,SAAS,QAAA"}
1
+ {"version":3,"file":"Field.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/primitives/Field/Field.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/* eslint vuejs-accessibility/label-has-for: [\"error\", { \"required\": \"id\" }] */\n/*\n * The workspace default for this rule requires BOTH a `for`/id pairing AND\n * the control physically nested inside the `<label>` (`required: {every:\n * [\"nesting\",\"id\"]}`). Field labels a control passed through the default\n * slot — never nested inside the `<label>` — so \"nesting\" can never be\n * satisfied by any slot-based composition, regardless of how the label is\n * written. The `for`/id pairing below is itself a fully valid, standard\n * accessible-name association (WCAG 1.3.1 / 4.1.2); this override scopes the\n * rule to that single valid strategy for this file only.\n */\nimport { computed, useId, useSlots } from \"vue\";\nimport InlineError from \"../InlineError/InlineError.vue\";\nimport { provideFieldContext, useOuterFieldContext } from \"../../lib/field-context\";\n\n/**\n * Field — the form-row wrapper: label + control + one hint-or-error line, so\n * a consumer stops hand-assembling that layout at every call site. Pure\n * composition, not a Reka component — the default slot is the control\n * (TextField, Textarea, a select, …). Setting `error` swaps InlineError in\n * for `hint`, because a row shows one message at a time, never both stacked.\n *\n * **A Loom control in the slot wires itself.** Field publishes the row's id,\n * the id of whichever message is showing, and `required` / `invalid` /\n * `disabled` / `readonly` / `name`; every Loom form control picks those up\n * through `useFieldControl()`. So the three attributes this component used to\n * ask a caller to copy by hand — `id`, `aria-describedby`, `invalid` — are now\n * the default, and forgetting one is no longer a silent defect:\n *\n * ```vue\n * <Field label=\"Email\" error=\"Invalid address\" required>\n * <TextField />\n * </Field>\n * ```\n *\n * A prop set on the control always wins, in both directions: `undefined` is\n * the only value that yields, `false` is a decision. One control inside a row\n * that is showing an error can still declare itself individually fine.\n *\n * **`for` is not a legacy path.** `provide`/`inject` only reaches a component\n * that injects, so a bare `<input>`, a product's own control or a third-party\n * widget in the slot cannot be wired this way — and that is the case `for` has\n * always answered. Pass it and the label points at your id, the message line\n * publishes `${for}-description` for your own `aria-describedby`, and a Loom\n * control adopts that id rather than a generated one, so an existing call site\n * keeps every id it published.\n */\nconst props = withDefaults(\n defineProps<{\n /** The visible label text. Renders as a real `<label>` once there is a control for it to name. */\n label?: string;\n /** Supporting text shown below the control. Hidden while `error` is set. */\n hint?: string;\n /** The error message. Renders through InlineError, replaces `hint`, and marks a Loom control inside invalid. */\n error?: string;\n /** Marks the row mandatory: a destructive asterisk on the label, and `aria-required` on a Loom control inside. */\n required?: boolean;\n /** Disables a Loom control inside. Leave unset to let the control decide for itself. */\n disabled?: boolean | undefined;\n /** Renders a Loom control inside read-only: still focusable, still submitted, not editable. */\n readonly?: boolean | undefined;\n /** The name a Loom control inside submits under, for a native form post or `FormData`. */\n name?: string;\n /** The id of the control in the default slot. Omit it and Field generates one for a Loom control. */\n for?: string;\n }>(),\n {\n // `disabled` and `readonly` carry an explicit `undefined` because absent\n // has to stay tellable from `false` all the way down: a Field saying\n // nothing must not overrule a control that disabled itself, and Vue casts\n // an absent Boolean prop with no declared default to `false`. `required`\n // stays a plain boolean — Field is the only thing that claims a row is\n // mandatory, so there is no lower opinion for a `false` to beat.\n required: false,\n disabled: undefined,\n readonly: undefined,\n },\n);\n\n// Called unconditionally, even when `for` makes it unused: `useId()` draws\n// from a per-app counter walked in setup order, and a conditional call makes\n// that sequence depend on props — every id after it shifts, and the server's\n// walk stops matching the client's.\nconst generatedId = useId();\n\n// Whether the call site wrote children at all is a fact about the template,\n// not about state, so it is read once rather than computed — `slots` is not a\n// reactive source and a `computed` over it would cache and go stale. It also\n// has to be answerable during *this* component's render: the server renders a\n// component's own output before any of its slot children, so anything Field\n// learned from the mounted controls would arrive after the label and the hint\n// were already written on the server and before them on the client, which is a\n// hydration mismatch by construction.\nconst hasControl = useSlots().default !== undefined;\n\n// The row's control id is what you passed, or a generated one when there is a\n// control in the slot to give it to. Nothing in the slot means nothing to name\n// and nothing to describe — a `<label for>` pointing at no control names\n// nobody, and an id nothing references is a landmark for no one.\nconst controlId = computed(() => props.for ?? (hasControl ? generatedId : undefined));\n\n// The label's own id, for group controls that name themselves with\n// `aria-labelledby` rather than by `label for`. Follows the same `-label`\n// convention as the description line's `-description`, and it exists for the\n// same reason: a group element inside the slot cannot be pointed at by `<label\n// for>` (that element is not a labelable element), so it needs a second way to\n// learn the label's id.\nconst labelId = computed(() => (controlId.value ? `${controlId.value}-label` : undefined));\n\n// The hint and the error are mutually exclusive, so one id covers whichever is\n// showing.\nconst descriptionId = computed(() =>\n controlId.value ? `${controlId.value}-description` : undefined,\n);\n\n// A Fieldset above answers exactly one key this Field also answers, and its\n// `readonly` is a claim about the whole group that a row does not get to\n// forget. Everything else a Fieldset knows either stops there deliberately\n// (`required`, `invalid`, the group's own description) or travels natively\n// (`disabled`, through the real `<fieldset>`) — see Fieldset.vue. Merging it\n// here rather than inside the context keeps the one overlapping key visible.\nconst group = useOuterFieldContext();\n\nprovideFieldContext({\n controlId: () => controlId.value,\n // Group controls name themselves with `aria-labelledby`, not `label for`, so\n // they need the label's own id. Only when the label is actually rendered: an\n // IDREF that resolves to nothing is worse than no attribute at all.\n labelledBy: () => (props.label ? labelId.value : undefined),\n // Only while a message is actually rendered. An IDREF resolving to nothing\n // is a worse answer than no attribute — the same reasoning Fieldset already\n // carries for its own `aria-describedby`.\n describedBy: () => ((props.error ?? props.hint) ? descriptionId.value : undefined),\n name: () => props.name,\n required: () => props.required || undefined,\n // The row's message and the control's `aria-invalid` are two halves of one\n // state; two independent switches for it is how they come to disagree.\n invalid: () => (props.error ? true : undefined),\n disabled: () => props.disabled,\n readonly: () => props.readonly ?? group?.readonly.value,\n});\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label\n v-if=\"label && controlId\"\n :id=\"labelId\"\n :for=\"controlId\"\n class=\"text-sm font-medium text-foreground\"\n >\n {{ label }} <span v-if=\"required\" class=\"text-destructive\">*</span>\n </label>\n <span v-else-if=\"label\" class=\"text-sm font-medium text-foreground\">\n {{ label }} <span v-if=\"required\" class=\"text-destructive\">*</span>\n </span>\n <slot />\n <p v-if=\"hint && !error\" :id=\"descriptionId\" class=\"text-xs text-muted-foreground\">\n {{ hint }}\n </p>\n <InlineError v-if=\"error\" :id=\"descriptionId\" :message=\"error\" />\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgDA,MAAM,QAAQ;EAoCd,MAAM,cAAc,MAAM;EAU1B,MAAM,aAAa,SAAS,CAAC,CAAC,YAAY,KAAA;EAM1C,MAAM,YAAY,eAAe,MAAM,QAAQ,aAAa,cAAc,KAAA,EAAU;EAQpF,MAAM,UAAU,eAAgB,UAAU,QAAQ,GAAG,UAAU,MAAM,UAAU,KAAA,CAAU;EAIzF,MAAM,gBAAgB,eACpB,UAAU,QAAQ,GAAG,UAAU,MAAM,gBAAgB,KAAA,CACvD;EAQA,MAAM,QAAQ,qBAAqB;EAEnC,oBAAoB;GAClB,iBAAiB,UAAU;GAI3B,kBAAmB,MAAM,QAAQ,QAAQ,QAAQ,KAAA;GAIjD,mBAAqB,MAAM,SAAS,MAAM,OAAQ,cAAc,QAAQ,KAAA;GACxE,YAAY,MAAM;GAClB,gBAAgB,MAAM,YAAY,KAAA;GAGlC,eAAgB,MAAM,QAAQ,OAAO,KAAA;GACrC,gBAAgB,MAAM;GACtB,gBAAgB,MAAM,YAAY,OAAO,SAAS;EACpD,CAAC;;GAIC,OAAA,UAAA,GAAA,mBAiBM,OAjBN,YAiBM;IAfI,QAAA,SAAS,UAAA,SADjB,UAAA,GAAA,mBAOQ,SAAA;;KALL,IAAI,QAAA;KACJ,KAAK,UAAA;KACN,OAAM;IAEH,GAAA,CAAA,gBAAA,gBAAA,QAAA,KAAK,IAAG,KAAC,CAAA,GAAY,QAAA,YAAZ,UAAA,GAAA,mBAAuD,QAAvD,YAA+C,GAAC,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,GAAA,UAAA,KAE7C,QAAA,SAAjB,UAAA,GAAA,mBAEO,QAFP,YAEO,CADF,gBAAA,gBAAA,QAAA,KAAK,IAAG,KAAC,CAAA,GAAY,QAAA,YAAZ,UAAA,GAAA,mBAAuD,QAAvD,YAA+C,GAAC,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAE9D,WAAQ,KAAA,QAAA,SAAA;IACC,QAAA,QAAI,CAAK,QAAA,SAAlB,UAAA,GAAA,mBAEI,KAAA;;KAFsB,IAAI,cAAA;KAAe,OAAM;IAC9C,GAAA,gBAAA,QAAA,IAAI,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;IAEU,QAAA,SAAnB,UAAA,GAAA,YAAiE,qBAAA;;KAAtC,IAAI,cAAA;KAAgB,SAAS,QAAA"}
@@ -0,0 +1,7 @@
1
+ import Fieldset_vue_vue_type_script_setup_true_lang_default from "./Fieldset.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/primitives/Fieldset/Fieldset.vue
3
+ var Fieldset_default = Fieldset_vue_vue_type_script_setup_true_lang_default;
4
+ //#endregion
5
+ export { Fieldset_default as default };
6
+
7
+ //# sourceMappingURL=Fieldset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Fieldset.js","names":[],"sources":["../../../src/primitives/Fieldset/Fieldset.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport InlineError from \"../InlineError/InlineError.vue\";\nimport { cn } from \"../../lib/cn\";\nimport { useSplitAttrs } from \"../../lib/attrs\";\nimport { provideFieldContext } from \"../../lib/field-context\";\n\n/**\n * Fieldset — several related controls under one name and one shared\n * hint-or-error line: an address block, a set of notification switches, a\n * date range. Where Field is a single row (label, control, message), Fieldset\n * is the group those rows sit in, and the two nest — a Fieldset of Fields is\n * the shape it was drawn for. A lone control does not need a group; that is a\n * Field.\n *\n * It renders a real `<fieldset>` and `<legend>`, and that choice is most of\n * the component. A `<div role=\"group\" aria-labelledby>` names the group just\n * as well and gives up `<fieldset disabled>`, which disables every form\n * control inside it natively — including controls that arrived through the\n * default slot and that Fieldset holds no reference to. Nothing adds that\n * back from a `div`: a wrapper cannot pass a `disabled` prop to a control it\n * never rendered.\n *\n * The hint-or-error line answers exactly as Field's does, for the reason\n * Field.vue sets out — a wrapper cannot reach into a slotted control to set\n * `aria-describedby`, so it publishes a predictable `${id}-description` and\n * leaves that one attribute to the caller. One half differs: Fieldset owns a\n * real element, so it points the group's own `aria-describedby` at that id\n * itself. Wire a control inside to the same id only when you want the message\n * announced a second time on that control.\n *\n * ```vue\n * <Fieldset legend=\"Shipping address\" id=\"shipping\" hint=\"Where the order goes\" required>\n * <Field label=\"Street\"><TextField /></Field>\n * <Field label=\"City\"><TextField /></Field>\n * </Fieldset>\n * ```\n *\n * **The group hands down exactly one thing: `readonly`.** Everything else it\n * knows either travels natively or is a claim about the group that is not\n * automatically a claim about each control in it, and the reasoning is worth\n * stating because the next person will ask:\n *\n * - `disabled` travels through the real `<fieldset>`, and a second mechanism\n * alongside it would be actively harmful rather than merely redundant. One\n * fact carried two ways lets the two disagree: a control written\n * `:disabled=\"false\"` beats a context by the precedence rule, while the\n * native attribute disables it anyway — leaving a control that renders\n * enabled and silently refuses input. The element also reaches strictly\n * further, which is the argument for rendering a `<fieldset>` at all.\n * - `required` and `invalid` stop here. \"This group is mandatory\" is not\n * \"every control in it is mandatory\" — an address block needs a street and\n * not an apartment number — and a group-level error does not make each\n * control in it individually wrong. `aria-required` or `aria-invalid` on a\n * control that is fine is a false statement to the only audience that hears\n * it.\n * - The description stops here too, because unlike Field this component owns a\n * real element and already points its own `aria-describedby` at the message.\n * - `controlId` would collide with the id on the `<fieldset>` itself, and one\n * shared `name` is right for a set of radios and wrong for everything else.\n *\n * `readonly` is left because nothing else can carry it: there is no native\n * `<fieldset readonly>`, and a group shown in view mode is a real state.\n */\nconst props = withDefaults(\n defineProps<{\n /** The group's name, rendered as a real `<legend>` — the accessible name of the group. */\n legend?: string;\n /** Supporting text shown below the group. Hidden while `error` is set. */\n hint?: string;\n /** The group's error message. Renders through InlineError and replaces `hint`. */\n error?: string;\n /** Marks the group mandatory and appends a destructive asterisk to the legend. */\n required?: boolean;\n /** Disables every control inside, slotted ones included, and dims the group. */\n disabled?: boolean;\n /** Renders every Loom control inside read-only: still focusable, still submitted, not editable. */\n readonly?: boolean | undefined;\n /** Set on the `<fieldset>`, and what the hint/error id is derived from (`${id}-description`). */\n id?: string;\n }>(),\n {\n required: false,\n disabled: false,\n // Explicitly `undefined`, not `false`: a Fieldset that says nothing about\n // `readonly` must not overrule a Field or a control that set it, and Vue\n // casts an absent Boolean prop with no declared default to `false`.\n readonly: undefined,\n },\n);\n\nprovideFieldContext({\n controlId: () => undefined,\n describedBy: () => undefined,\n name: () => undefined,\n required: () => undefined,\n invalid: () => undefined,\n disabled: () => undefined,\n readonly: () => props.readonly,\n});\n\n// The hint and the error are mutually exclusive, so one id covers whichever\n// is showing. Undefined without `id`, exactly as in Field: an id nothing can\n// be pointed at is a landmark for no one. Undefined with no message either —\n// unlike Field, this id is also spent on the group's own `aria-describedby`,\n// and an IDREF resolving to nothing is a worse answer than no attribute.\nconst descriptionId = computed(() =>\n props.id && (props.hint ?? props.error) ? `${props.id}-description` : undefined,\n);\n\n// One rendered node, so this split is only about `class`. Vue's own\n// fallthrough merge concatenates the two lists rather than resolving them, so\n// a caller's `min-w-full` would beat — or lose to — the `min-w-0` below on\n// Tailwind's emission order instead of on being the later declaration. That\n// one utility is load-bearing (see the root class), so the merge has to be\n// Tailwind-aware.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: fieldsetAttrs } = useSplitAttrs();\n</script>\n\n<template>\n <fieldset\n v-bind=\"fieldsetAttrs\"\n :id=\"id\"\n :disabled=\"disabled\"\n :aria-describedby=\"descriptionId\"\n :class=\"\n cn(\n 'flex flex-col gap-4',\n // Not defensive. Every UA stylesheet gives `<fieldset>` a\n // `min-inline-size: min-content` that no `width` can beat, so a group\n // holding a text input refuses to shrink below that input's intrinsic\n // width and blows out the column of any consumer who drops one into a\n // form grid. Overriding the minimum itself is the only fix.\n 'min-w-0',\n // `group`, and no `opacity`. Dimming the group was the worst instance\n // of that mistake in the library, because opacity *composites* and\n // therefore multiplies: this element's 50% ran over a Switch's or a\n // Select's own 50%, so a control inside a disabled group rendered at\n // 25% effective alpha and its label measured 1.65:1 — and the group's\n // own hint, one dim rather than two, still came out at 2.05:1. Neither\n // was reachable by fixing the controls, because neither was the\n // controls' doing.\n //\n // Removing it here is what un-multiplies them: a nested control now\n // computes only its own drained treatment (Select and OtpInput land\n // their labels on 4.67:1 that way), and nothing this element does\n // reduces it further. The group still reads as unavailable — the\n // legend below mutes, and every control in it wears its own disabled\n // state — and the native `disabled` attribute carries the fact to\n // assistive tech, so none of it rests on colour alone.\n //\n // And no fill either, which is the other half of the same answer. Every\n // control in the library drains to `bg-muted` when it is unavailable,\n // so a `bg-muted` on the group would be that exact colour running\n // edge-to-edge behind them: the fills would meet at 1:1 and a row of\n // disabled controls would read as one grey slab with no controls in it.\n // The group is a grouping, not a surface — it has no fill available and\n // no `readonly` appearance either, because a read-only group is a group\n // of read-only controls, each of which now says so on its own.\n 'disabled:cursor-not-allowed',\n 'group',\n attrs.class as string,\n )\n \"\n >\n <!-- Deliberately borderless, like Field: with no border to interrupt, the\n legend's notch rendering never arises and the group is spaced rather\n than boxed. The margin is not a stand-in for the container's `gap`\n either — a rendered legend sits outside the anonymous box that holds\n a fieldset's content, so it is never a flex item and no `gap` reaches\n it. -->\n <!-- The legend is where \"this whole group is unavailable\" is now painted:\n it is the group's accessible name and the only text the group owns\n rather than borrows. `group-disabled:` reads the real `:disabled` on\n the `<fieldset>` above, so there is no second copy of the state to fall\n out of step with the attribute. 14.09:1 available, 5.25:1 not — a drop\n a reader can see, at a colour they can still read. -->\n <legend\n v-if=\"legend\"\n class=\"mb-2 text-sm font-medium text-foreground group-disabled:text-muted-foreground\"\n >\n {{ legend }} <span v-if=\"required\" class=\"text-destructive\">*</span>\n </legend>\n <!-- @slot The controls in the group — Fields, most often. -->\n <slot />\n <p v-if=\"hint && !error\" :id=\"descriptionId\" class=\"text-xs text-muted-foreground\">\n {{ hint }}\n </p>\n <InlineError v-if=\"error\" :id=\"descriptionId\" :message=\"error\" />\n </fieldset>\n</template>\n"],"mappings":""}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Fieldset — several related controls under one name and one shared
3
+ * hint-or-error line: an address block, a set of notification switches, a
4
+ * date range. Where Field is a single row (label, control, message), Fieldset
5
+ * is the group those rows sit in, and the two nest — a Fieldset of Fields is
6
+ * the shape it was drawn for. A lone control does not need a group; that is a
7
+ * Field.
8
+ *
9
+ * It renders a real `<fieldset>` and `<legend>`, and that choice is most of
10
+ * the component. A `<div role="group" aria-labelledby>` names the group just
11
+ * as well and gives up `<fieldset disabled>`, which disables every form
12
+ * control inside it natively — including controls that arrived through the
13
+ * default slot and that Fieldset holds no reference to. Nothing adds that
14
+ * back from a `div`: a wrapper cannot pass a `disabled` prop to a control it
15
+ * never rendered.
16
+ *
17
+ * The hint-or-error line answers exactly as Field's does, for the reason
18
+ * Field.vue sets out — a wrapper cannot reach into a slotted control to set
19
+ * `aria-describedby`, so it publishes a predictable `${id}-description` and
20
+ * leaves that one attribute to the caller. One half differs: Fieldset owns a
21
+ * real element, so it points the group's own `aria-describedby` at that id
22
+ * itself. Wire a control inside to the same id only when you want the message
23
+ * announced a second time on that control.
24
+ *
25
+ * ```vue
26
+ * <Fieldset legend="Shipping address" id="shipping" hint="Where the order goes" required>
27
+ * <Field label="Street"><TextField /></Field>
28
+ * <Field label="City"><TextField /></Field>
29
+ * </Fieldset>
30
+ * ```
31
+ *
32
+ * **The group hands down exactly one thing: `readonly`.** Everything else it
33
+ * knows either travels natively or is a claim about the group that is not
34
+ * automatically a claim about each control in it, and the reasoning is worth
35
+ * stating because the next person will ask:
36
+ *
37
+ * - `disabled` travels through the real `<fieldset>`, and a second mechanism
38
+ * alongside it would be actively harmful rather than merely redundant. One
39
+ * fact carried two ways lets the two disagree: a control written
40
+ * `:disabled="false"` beats a context by the precedence rule, while the
41
+ * native attribute disables it anyway — leaving a control that renders
42
+ * enabled and silently refuses input. The element also reaches strictly
43
+ * further, which is the argument for rendering a `<fieldset>` at all.
44
+ * - `required` and `invalid` stop here. "This group is mandatory" is not
45
+ * "every control in it is mandatory" — an address block needs a street and
46
+ * not an apartment number — and a group-level error does not make each
47
+ * control in it individually wrong. `aria-required` or `aria-invalid` on a
48
+ * control that is fine is a false statement to the only audience that hears
49
+ * it.
50
+ * - The description stops here too, because unlike Field this component owns a
51
+ * real element and already points its own `aria-describedby` at the message.
52
+ * - `controlId` would collide with the id on the `<fieldset>` itself, and one
53
+ * shared `name` is right for a set of radios and wrong for everything else.
54
+ *
55
+ * `readonly` is left because nothing else can carry it: there is no native
56
+ * `<fieldset readonly>`, and a group shown in view mode is a real state.
57
+ */
58
+ type __VLS_Props = {
59
+ /** The group's name, rendered as a real `<legend>` — the accessible name of the group. */
60
+ legend?: string;
61
+ /** Supporting text shown below the group. Hidden while `error` is set. */
62
+ hint?: string;
63
+ /** The group's error message. Renders through InlineError and replaces `hint`. */
64
+ error?: string;
65
+ /** Marks the group mandatory and appends a destructive asterisk to the legend. */
66
+ required?: boolean;
67
+ /** Disables every control inside, slotted ones included, and dims the group. */
68
+ disabled?: boolean;
69
+ /** Renders every Loom control inside read-only: still focusable, still submitted, not editable. */
70
+ readonly?: boolean | undefined;
71
+ /** Set on the `<fieldset>`, and what the hint/error id is derived from (`${id}-description`). */
72
+ id?: string;
73
+ };
74
+ declare var __VLS_1: {};
75
+ type __VLS_Slots = {} & {
76
+ default?: (props: typeof __VLS_1) => any;
77
+ };
78
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
79
+ disabled: boolean;
80
+ readonly: boolean | undefined;
81
+ required: boolean;
82
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
83
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
84
+ declare const _default: typeof __VLS_export;
85
+ export default _default;
86
+ type __VLS_WithSlots<T, S> = T & {
87
+ new (): {
88
+ $slots: S;
89
+ };
90
+ };
91
+ //# sourceMappingURL=Fieldset.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Fieldset.vue.d.ts","sourceRoot":"","sources":["../../../src/primitives/Fieldset/Fieldset.vue"],"names":[],"mappings":"AAyMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,KAAK,WAAW,GAAG;IACf,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,iGAAiG;IACjG,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AA2JJ,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AAK/C,QAAA,MAAM,UAAU;cAvKD,OAAO;cAEP,OAAO,GAAG,SAAS;cAJnB,OAAO;6EA4KpB,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AAWzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -0,0 +1,138 @@
1
+ import { cn } from "../../lib/cn.js";
2
+ import { useSplitAttrs } from "../../lib/attrs.js";
3
+ import { provideFieldContext } from "../../lib/field-context.js";
4
+ import InlineError_default from "../InlineError/InlineError.js";
5
+ import { computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, defineComponent, mergeProps, openBlock, renderSlot, toDisplayString, unref } from "vue";
6
+ //#region src/primitives/Fieldset/Fieldset.vue?vue&type=script&setup=true&lang.ts
7
+ var _hoisted_1 = [
8
+ "id",
9
+ "disabled",
10
+ "aria-describedby"
11
+ ];
12
+ var _hoisted_2 = {
13
+ key: 0,
14
+ class: "mb-2 text-sm font-medium text-foreground group-disabled:text-muted-foreground"
15
+ };
16
+ var _hoisted_3 = {
17
+ key: 0,
18
+ class: "text-destructive"
19
+ };
20
+ var _hoisted_4 = ["id"];
21
+ /**
22
+ * Fieldset — several related controls under one name and one shared
23
+ * hint-or-error line: an address block, a set of notification switches, a
24
+ * date range. Where Field is a single row (label, control, message), Fieldset
25
+ * is the group those rows sit in, and the two nest — a Fieldset of Fields is
26
+ * the shape it was drawn for. A lone control does not need a group; that is a
27
+ * Field.
28
+ *
29
+ * It renders a real `<fieldset>` and `<legend>`, and that choice is most of
30
+ * the component. A `<div role="group" aria-labelledby>` names the group just
31
+ * as well and gives up `<fieldset disabled>`, which disables every form
32
+ * control inside it natively — including controls that arrived through the
33
+ * default slot and that Fieldset holds no reference to. Nothing adds that
34
+ * back from a `div`: a wrapper cannot pass a `disabled` prop to a control it
35
+ * never rendered.
36
+ *
37
+ * The hint-or-error line answers exactly as Field's does, for the reason
38
+ * Field.vue sets out — a wrapper cannot reach into a slotted control to set
39
+ * `aria-describedby`, so it publishes a predictable `${id}-description` and
40
+ * leaves that one attribute to the caller. One half differs: Fieldset owns a
41
+ * real element, so it points the group's own `aria-describedby` at that id
42
+ * itself. Wire a control inside to the same id only when you want the message
43
+ * announced a second time on that control.
44
+ *
45
+ * ```vue
46
+ * <Fieldset legend="Shipping address" id="shipping" hint="Where the order goes" required>
47
+ * <Field label="Street"><TextField /></Field>
48
+ * <Field label="City"><TextField /></Field>
49
+ * </Fieldset>
50
+ * ```
51
+ *
52
+ * **The group hands down exactly one thing: `readonly`.** Everything else it
53
+ * knows either travels natively or is a claim about the group that is not
54
+ * automatically a claim about each control in it, and the reasoning is worth
55
+ * stating because the next person will ask:
56
+ *
57
+ * - `disabled` travels through the real `<fieldset>`, and a second mechanism
58
+ * alongside it would be actively harmful rather than merely redundant. One
59
+ * fact carried two ways lets the two disagree: a control written
60
+ * `:disabled="false"` beats a context by the precedence rule, while the
61
+ * native attribute disables it anyway — leaving a control that renders
62
+ * enabled and silently refuses input. The element also reaches strictly
63
+ * further, which is the argument for rendering a `<fieldset>` at all.
64
+ * - `required` and `invalid` stop here. "This group is mandatory" is not
65
+ * "every control in it is mandatory" — an address block needs a street and
66
+ * not an apartment number — and a group-level error does not make each
67
+ * control in it individually wrong. `aria-required` or `aria-invalid` on a
68
+ * control that is fine is a false statement to the only audience that hears
69
+ * it.
70
+ * - The description stops here too, because unlike Field this component owns a
71
+ * real element and already points its own `aria-describedby` at the message.
72
+ * - `controlId` would collide with the id on the `<fieldset>` itself, and one
73
+ * shared `name` is right for a set of radios and wrong for everything else.
74
+ *
75
+ * `readonly` is left because nothing else can carry it: there is no native
76
+ * `<fieldset readonly>`, and a group shown in view mode is a real state.
77
+ */
78
+ var Fieldset_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
79
+ inheritAttrs: false,
80
+ __name: "Fieldset",
81
+ props: {
82
+ legend: {},
83
+ hint: {},
84
+ error: {},
85
+ required: {
86
+ type: Boolean,
87
+ default: false
88
+ },
89
+ disabled: {
90
+ type: Boolean,
91
+ default: false
92
+ },
93
+ readonly: {
94
+ type: Boolean,
95
+ default: void 0
96
+ },
97
+ id: {}
98
+ },
99
+ setup(__props) {
100
+ const props = __props;
101
+ provideFieldContext({
102
+ controlId: () => void 0,
103
+ describedBy: () => void 0,
104
+ name: () => void 0,
105
+ required: () => void 0,
106
+ invalid: () => void 0,
107
+ disabled: () => void 0,
108
+ readonly: () => props.readonly
109
+ });
110
+ const descriptionId = computed(() => props.id && (props.hint ?? props.error) ? `${props.id}-description` : void 0);
111
+ const { attrs, rest: fieldsetAttrs } = useSplitAttrs();
112
+ return (_ctx, _cache) => {
113
+ return openBlock(), createElementBlock("fieldset", mergeProps(unref(fieldsetAttrs), {
114
+ id: __props.id,
115
+ disabled: __props.disabled,
116
+ "aria-describedby": descriptionId.value,
117
+ class: unref(cn)("flex flex-col gap-4", "min-w-0", "disabled:cursor-not-allowed", "group", unref(attrs).class)
118
+ }), [
119
+ __props.legend ? (openBlock(), createElementBlock("legend", _hoisted_2, [createTextVNode(toDisplayString(__props.legend) + " ", 1), __props.required ? (openBlock(), createElementBlock("span", _hoisted_3, "*")) : createCommentVNode("", true)])) : createCommentVNode("", true),
120
+ renderSlot(_ctx.$slots, "default"),
121
+ __props.hint && !__props.error ? (openBlock(), createElementBlock("p", {
122
+ key: 1,
123
+ id: descriptionId.value,
124
+ class: "text-xs text-muted-foreground"
125
+ }, toDisplayString(__props.hint), 9, _hoisted_4)) : createCommentVNode("", true),
126
+ __props.error ? (openBlock(), createBlock(InlineError_default, {
127
+ key: 2,
128
+ id: descriptionId.value,
129
+ message: __props.error
130
+ }, null, 8, ["id", "message"])) : createCommentVNode("", true)
131
+ ], 16, _hoisted_1);
132
+ };
133
+ }
134
+ });
135
+ //#endregion
136
+ export { Fieldset_vue_vue_type_script_setup_true_lang_default as default };
137
+
138
+ //# sourceMappingURL=Fieldset.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Fieldset.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/primitives/Fieldset/Fieldset.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport InlineError from \"../InlineError/InlineError.vue\";\nimport { cn } from \"../../lib/cn\";\nimport { useSplitAttrs } from \"../../lib/attrs\";\nimport { provideFieldContext } from \"../../lib/field-context\";\n\n/**\n * Fieldset — several related controls under one name and one shared\n * hint-or-error line: an address block, a set of notification switches, a\n * date range. Where Field is a single row (label, control, message), Fieldset\n * is the group those rows sit in, and the two nest — a Fieldset of Fields is\n * the shape it was drawn for. A lone control does not need a group; that is a\n * Field.\n *\n * It renders a real `<fieldset>` and `<legend>`, and that choice is most of\n * the component. A `<div role=\"group\" aria-labelledby>` names the group just\n * as well and gives up `<fieldset disabled>`, which disables every form\n * control inside it natively — including controls that arrived through the\n * default slot and that Fieldset holds no reference to. Nothing adds that\n * back from a `div`: a wrapper cannot pass a `disabled` prop to a control it\n * never rendered.\n *\n * The hint-or-error line answers exactly as Field's does, for the reason\n * Field.vue sets out — a wrapper cannot reach into a slotted control to set\n * `aria-describedby`, so it publishes a predictable `${id}-description` and\n * leaves that one attribute to the caller. One half differs: Fieldset owns a\n * real element, so it points the group's own `aria-describedby` at that id\n * itself. Wire a control inside to the same id only when you want the message\n * announced a second time on that control.\n *\n * ```vue\n * <Fieldset legend=\"Shipping address\" id=\"shipping\" hint=\"Where the order goes\" required>\n * <Field label=\"Street\"><TextField /></Field>\n * <Field label=\"City\"><TextField /></Field>\n * </Fieldset>\n * ```\n *\n * **The group hands down exactly one thing: `readonly`.** Everything else it\n * knows either travels natively or is a claim about the group that is not\n * automatically a claim about each control in it, and the reasoning is worth\n * stating because the next person will ask:\n *\n * - `disabled` travels through the real `<fieldset>`, and a second mechanism\n * alongside it would be actively harmful rather than merely redundant. One\n * fact carried two ways lets the two disagree: a control written\n * `:disabled=\"false\"` beats a context by the precedence rule, while the\n * native attribute disables it anyway — leaving a control that renders\n * enabled and silently refuses input. The element also reaches strictly\n * further, which is the argument for rendering a `<fieldset>` at all.\n * - `required` and `invalid` stop here. \"This group is mandatory\" is not\n * \"every control in it is mandatory\" — an address block needs a street and\n * not an apartment number — and a group-level error does not make each\n * control in it individually wrong. `aria-required` or `aria-invalid` on a\n * control that is fine is a false statement to the only audience that hears\n * it.\n * - The description stops here too, because unlike Field this component owns a\n * real element and already points its own `aria-describedby` at the message.\n * - `controlId` would collide with the id on the `<fieldset>` itself, and one\n * shared `name` is right for a set of radios and wrong for everything else.\n *\n * `readonly` is left because nothing else can carry it: there is no native\n * `<fieldset readonly>`, and a group shown in view mode is a real state.\n */\nconst props = withDefaults(\n defineProps<{\n /** The group's name, rendered as a real `<legend>` — the accessible name of the group. */\n legend?: string;\n /** Supporting text shown below the group. Hidden while `error` is set. */\n hint?: string;\n /** The group's error message. Renders through InlineError and replaces `hint`. */\n error?: string;\n /** Marks the group mandatory and appends a destructive asterisk to the legend. */\n required?: boolean;\n /** Disables every control inside, slotted ones included, and dims the group. */\n disabled?: boolean;\n /** Renders every Loom control inside read-only: still focusable, still submitted, not editable. */\n readonly?: boolean | undefined;\n /** Set on the `<fieldset>`, and what the hint/error id is derived from (`${id}-description`). */\n id?: string;\n }>(),\n {\n required: false,\n disabled: false,\n // Explicitly `undefined`, not `false`: a Fieldset that says nothing about\n // `readonly` must not overrule a Field or a control that set it, and Vue\n // casts an absent Boolean prop with no declared default to `false`.\n readonly: undefined,\n },\n);\n\nprovideFieldContext({\n controlId: () => undefined,\n describedBy: () => undefined,\n name: () => undefined,\n required: () => undefined,\n invalid: () => undefined,\n disabled: () => undefined,\n readonly: () => props.readonly,\n});\n\n// The hint and the error are mutually exclusive, so one id covers whichever\n// is showing. Undefined without `id`, exactly as in Field: an id nothing can\n// be pointed at is a landmark for no one. Undefined with no message either —\n// unlike Field, this id is also spent on the group's own `aria-describedby`,\n// and an IDREF resolving to nothing is a worse answer than no attribute.\nconst descriptionId = computed(() =>\n props.id && (props.hint ?? props.error) ? `${props.id}-description` : undefined,\n);\n\n// One rendered node, so this split is only about `class`. Vue's own\n// fallthrough merge concatenates the two lists rather than resolving them, so\n// a caller's `min-w-full` would beat — or lose to — the `min-w-0` below on\n// Tailwind's emission order instead of on being the later declaration. That\n// one utility is load-bearing (see the root class), so the merge has to be\n// Tailwind-aware.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: fieldsetAttrs } = useSplitAttrs();\n</script>\n\n<template>\n <fieldset\n v-bind=\"fieldsetAttrs\"\n :id=\"id\"\n :disabled=\"disabled\"\n :aria-describedby=\"descriptionId\"\n :class=\"\n cn(\n 'flex flex-col gap-4',\n // Not defensive. Every UA stylesheet gives `<fieldset>` a\n // `min-inline-size: min-content` that no `width` can beat, so a group\n // holding a text input refuses to shrink below that input's intrinsic\n // width and blows out the column of any consumer who drops one into a\n // form grid. Overriding the minimum itself is the only fix.\n 'min-w-0',\n // `group`, and no `opacity`. Dimming the group was the worst instance\n // of that mistake in the library, because opacity *composites* and\n // therefore multiplies: this element's 50% ran over a Switch's or a\n // Select's own 50%, so a control inside a disabled group rendered at\n // 25% effective alpha and its label measured 1.65:1 — and the group's\n // own hint, one dim rather than two, still came out at 2.05:1. Neither\n // was reachable by fixing the controls, because neither was the\n // controls' doing.\n //\n // Removing it here is what un-multiplies them: a nested control now\n // computes only its own drained treatment (Select and OtpInput land\n // their labels on 4.67:1 that way), and nothing this element does\n // reduces it further. The group still reads as unavailable — the\n // legend below mutes, and every control in it wears its own disabled\n // state — and the native `disabled` attribute carries the fact to\n // assistive tech, so none of it rests on colour alone.\n //\n // And no fill either, which is the other half of the same answer. Every\n // control in the library drains to `bg-muted` when it is unavailable,\n // so a `bg-muted` on the group would be that exact colour running\n // edge-to-edge behind them: the fills would meet at 1:1 and a row of\n // disabled controls would read as one grey slab with no controls in it.\n // The group is a grouping, not a surface — it has no fill available and\n // no `readonly` appearance either, because a read-only group is a group\n // of read-only controls, each of which now says so on its own.\n 'disabled:cursor-not-allowed',\n 'group',\n attrs.class as string,\n )\n \"\n >\n <!-- Deliberately borderless, like Field: with no border to interrupt, the\n legend's notch rendering never arises and the group is spaced rather\n than boxed. The margin is not a stand-in for the container's `gap`\n either — a rendered legend sits outside the anonymous box that holds\n a fieldset's content, so it is never a flex item and no `gap` reaches\n it. -->\n <!-- The legend is where \"this whole group is unavailable\" is now painted:\n it is the group's accessible name and the only text the group owns\n rather than borrows. `group-disabled:` reads the real `:disabled` on\n the `<fieldset>` above, so there is no second copy of the state to fall\n out of step with the attribute. 14.09:1 available, 5.25:1 not — a drop\n a reader can see, at a colour they can still read. -->\n <legend\n v-if=\"legend\"\n class=\"mb-2 text-sm font-medium text-foreground group-disabled:text-muted-foreground\"\n >\n {{ legend }} <span v-if=\"required\" class=\"text-destructive\">*</span>\n </legend>\n <!-- @slot The controls in the group — Fields, most often. -->\n <slot />\n <p v-if=\"hint && !error\" :id=\"descriptionId\" class=\"text-xs text-muted-foreground\">\n {{ hint }}\n </p>\n <InlineError v-if=\"error\" :id=\"descriptionId\" :message=\"error\" />\n </fieldset>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgEA,MAAM,QAAQ;EA2Bd,oBAAoB;GAClB,iBAAiB,KAAA;GACjB,mBAAmB,KAAA;GACnB,YAAY,KAAA;GACZ,gBAAgB,KAAA;GAChB,eAAe,KAAA;GACf,gBAAgB,KAAA;GAChB,gBAAgB,MAAM;EACxB,CAAC;EAOD,MAAM,gBAAgB,eACpB,MAAM,OAAO,MAAM,QAAQ,MAAM,SAAS,GAAG,MAAM,GAAG,gBAAgB,KAAA,CACxE;EASA,MAAM,EAAE,OAAO,MAAM,kBAAkB,cAAc;;GAInD,OAAA,UAAA,GAAA,mBAqEW,YArEX,WACU,MAoEC,aAAA,GApEY;IACpB,IAAI,QAAA;IACJ,UAAU,QAAA;IACV,oBAAkB,cAAA;IAClB,OAAc,MAAA,EAAA,CAAE,CAAA,uBAAA,WAAA,+BAAA,SAAqrE,MAAA,KAAA,CAAK,CAAC,KAAA;;IAqDpsE,QAAA,UADR,UAAA,GAAA,mBAKS,UALT,YAKS,CADJ,gBAAA,gBAAA,QAAA,MAAM,IAAG,KAAC,CAAA,GAAY,QAAA,YAAZ,UAAA,GAAA,mBAAuD,QAAvD,YAA+C,GAAC,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAG/D,WAAQ,KAAA,QAAA,SAAA;IACC,QAAA,QAAI,CAAK,QAAA,SAAlB,UAAA,GAAA,mBAEI,KAAA;;KAFsB,IAAI,cAAA;KAAe,OAAM;IAC9C,GAAA,gBAAA,QAAA,IAAI,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;IAEU,QAAA,SAAnB,UAAA,GAAA,YAAiE,qBAAA;;KAAtC,IAAI,cAAA;KAAgB,SAAS,QAAA"}
@@ -0,0 +1,7 @@
1
+ import FileUpload_vue_vue_type_script_setup_true_lang_default, { FILE_UPLOAD_LABELS } from "./FileUpload.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/primitives/FileUpload/FileUpload.vue
3
+ var FileUpload_default = FileUpload_vue_vue_type_script_setup_true_lang_default;
4
+ //#endregion
5
+ export { FILE_UPLOAD_LABELS, FileUpload_default as default };
6
+
7
+ //# sourceMappingURL=FileUpload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileUpload.js","names":[],"sources":["../../../src/primitives/FileUpload/FileUpload.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { LabelOf } from \"../../lib/labels\";\n\n/** Why a file the reader offered was not added to the list. */\nexport type FileUploadRejectReason = \"too-large\" | \"type\" | \"duplicate\" | \"too-many\";\n\n/** One refused file, paired with the rule that refused it. */\nexport interface FileUploadRejection {\n /** The file as the browser handed it over — untouched, so a host can report on it. */\n file: File;\n /** Which rule turned it away. */\n reason: FileUploadRejectReason;\n}\n\nconst BYTE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\"] as const;\n\n/**\n * A byte count as a person reads it.\n *\n * Binary steps with the short decimal names, which is what every operating\n * system file dialog beside this control shows — a reader comparing \"1.4 MB\"\n * here against \"1.4 MB\" in their own file browser should see the same number,\n * and the pedantically-correct \"1.5 MB\" (or \"1.4 MiB\") only reads as a bug.\n * One decimal below ten and none above it, so the size column stays the same\n * width down the list.\n *\n * Deliberately not exported: it is a formatter this control needs, not a\n * public utility, and the moment a host imports it from here the wording of a\n * file size becomes part of this component's API.\n */\nfunction formatBytes(bytes: number): string {\n let value = Math.max(bytes, 0);\n let step = 0;\n while (value >= 1024 && step < BYTE_UNITS.length - 1) {\n value /= 1024;\n step += 1;\n }\n const rounded = step > 0 && value < 10 ? Math.round(value * 10) / 10 : Math.round(value);\n return `${String(rounded)} ${BYTE_UNITS[step] ?? \"B\"}`;\n}\n\n/**\n * Everything this control puts in front of a reader that is not a file name —\n * and it is the most prose in the library, which is why every one of these\n * takes raw values and returns a finished string.\n *\n * **Every size arrives as a byte count, never as `\"5 MB\"`.** The unit ladder,\n * the decimal separator and the space before the unit are all language\n * decisions: `formatBytes` above writes `1,5 MB` nowhere and `%42` never, and\n * a host handed the number reaches `Intl.NumberFormat` for both. Handing over\n * a string Loom had already formatted would make Loom's English the only\n * arithmetic anyone could do.\n *\n * **`rejected` takes the whole list, not one refusal at a time.** Loom joined\n * four sentences with a space before this, and a joiner is a fragment — the\n * character between two sentences is a property of the script, and a language\n * that would rather say \"3 files were refused\" than list them cannot get there\n * from a per-file message. One function over the list can say either.\n *\n * **`remove` and `rejected` are handed the `File` itself**, not its name, for\n * the same reason: a host that would rather name the row by index, or truncate\n * a long name from the middle, has the whole object to work from.\n */\nexport interface FileUploadLabels {\n /** The zone's own line of copy, which is also the file input's accessible name. The `label` prop overrides it for one zone. */\n readonly zone: LabelOf<{ multiple: boolean }>;\n /** The smaller line under it, stating the size limit. The `hint` prop overrides it for one zone. */\n readonly hint: LabelOf<{ maxSize: number }>;\n /** The size shown against one chosen file. */\n readonly size: LabelOf<{ bytes: number }>;\n /** The button that takes one file back out of the list. */\n readonly remove: LabelOf<{ file: File }>;\n /** Everything refused by one interaction, as one message. `maxSize` is `undefined` when no limit is set. */\n readonly rejected: LabelOf<{\n rejections: readonly FileUploadRejection[];\n maxSize: number | undefined;\n }>;\n}\n\n/**\n * Every refusal names the file, because the reader is looking at a list of\n * several and \"that file is too large\" identifies none of them.\n */\nfunction describeRejection(rejection: FileUploadRejection, limit: string): string {\n const name = rejection.file.name;\n switch (rejection.reason) {\n case \"too-large\":\n return `${name} is larger than ${limit}.`;\n case \"type\":\n return `${name} is not an accepted file type.`;\n case \"duplicate\":\n return `${name} has already been chosen.`;\n case \"too-many\":\n return `${name} was not added — only one file can be chosen.`;\n }\n}\n\n/**\n * Loom's English, co-located with the component so it tree-shakes with it, and\n * exported so a host can build a partial vocabulary against the real thing.\n */\nexport const FILE_UPLOAD_LABELS: FileUploadLabels = {\n zone: ({ multiple }) =>\n multiple ? \"Choose files or drag them here\" : \"Choose a file or drag it here\",\n hint: ({ maxSize }) => `Up to ${formatBytes(maxSize)}`,\n size: ({ bytes }) => formatBytes(bytes),\n remove: ({ file }) => `Remove ${file.name}`,\n rejected: ({ rejections, maxSize }) => {\n const limit = maxSize === undefined ? \"the size limit\" : formatBytes(maxSize);\n return rejections.map((rejection) => describeRejection(rejection, limit)).join(\" \");\n },\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, useTemplateRef } from \"vue\";\nimport { CloudUpload, File as FileIcon, TriangleAlert, X } from \"@lucide/vue\";\nimport { cn } from \"../../lib/cn\";\nimport { listStaggerDelay } from \"../../lib/motion\";\nimport { useSplitAttrs } from \"../../lib/attrs\";\nimport { useAncestorDisabled } from \"../../lib/ancestor-disabled\";\nimport { useFieldControl } from \"../../lib/field-context\";\nimport { useLabels, type LabelOverrides } from \"../../lib/labels\";\n\n/**\n * FileUpload — choosing files, and nothing else. A drop zone that is also a\n * real file dialog, plus the list of what has been chosen so far, with each\n * row removable.\n *\n * **It chooses; it does not upload.** There is no progress bar here and no\n * request: the host reads `modelValue`, sends it wherever it goes, and renders\n * `Progress` (or `Spinner`, when there is no percentage) for that half. Two\n * controls rather than one, because the upload's states — retrying, partial,\n * failed on the server — belong to whatever is doing the uploading, and a\n * control that owned both would have to guess at all of them.\n *\n * **Drag and drop is a shortcut, never the path.** The zone is a `<label>`\n * wrapped around a real `<input type=\"file\">`, visually hidden by the clip\n * technique rather than by `display: none` or `hidden` — both of which take\n * the input out of the tab order and leave a control only a pointer can reach.\n * Tab lands on the input, Space or Enter opens the system file dialog, and the\n * zone draws the focus ring on its behalf because a 1px input cannot show one.\n * A `div` with a click handler in place of that input is the standard defect\n * in this control, and it is invisible to everyone testing with a mouse.\n *\n * Files arriving while `multiple` is set are appended to the list rather than\n * replacing it: a reader who drops two files and then a third means three, and\n * the native input's replace-everything behaviour is the surprise. Without\n * `multiple` the newest choice replaces the old one, exactly as the input does.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself through\n * `useFieldControl()`: the row's id lands on the input (and on the `<label>`'s\n * `for`, so the zone keeps activating it), and the row's `name`, `required`,\n * `invalid` and the id of its hint or error line arrive with it. The row's\n * description is **merged** with this control's own refusal message rather than\n * replacing it — a file turned away for being too large has to keep saying so.\n *\n * There is no `readonly`, and a row's is ignored rather than approximated.\n * `readonly` is inert on `<input type=\"file\">` in every browser, for the reason\n * it is inert on a checkbox: there is no text to protect, only a dialog to\n * open. A zone that may be read but not added to is a disabled zone above a\n * list — and the list, which is where the value actually is, stays readable\n * either way.\n */\nconst props = withDefaults(\n defineProps<{\n /** The chosen files. Leave it unbound to let the control keep its own list. */\n modelValue?: File[] | undefined;\n /** What the file dialog offers and what a drop is checked against — the native `accept` syntax: `.csv`, `image/png`, `image/*`, comma-separated. */\n accept?: string;\n /** Allows more than one file, and appends each new choice to the list instead of replacing it. */\n multiple?: boolean;\n /** Largest file accepted, in bytes. A file over it is refused and named in the message. */\n maxSize?: number;\n /** Unavailable: dims the zone, refuses the dialog, the drop and every remove button. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Error state: paints the destructive border and ring, and sets `aria-invalid` on the input. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /** The zone's own line of copy, which is also the input's accessible name. Defaults to `labels.zone`, whose wording matches `multiple`. */\n label?: string;\n /** The smaller line under it. Defaults to `labels.hint` against the `maxSize` limit, when there is one. */\n hint?: string;\n /**\n * Everything this control says that is not a file name, as any subset of\n * `FileUploadLabels` — the rest stay as the host's `provideLoomLabels`\n * vocabulary left them, and then as Loom's English.\n *\n * This is the per-instance correction, not the place to localise an\n * application. `label` and `hint` are the shorter way to reach the two\n * keys of the same name for one zone; this is what reaches the refusal\n * message, the remove buttons and the size column.\n */\n labels?: LabelOverrides<FileUploadLabels>;\n }>(),\n {\n // Not `[]`. An empty array is a concrete value, so it would say \"the host\n // owns this list\" — and a host that binds nothing would then watch every\n // file it chose vanish on the next render. `undefined` is what leaves the\n // control managing its own, the same distinction `optional()` exists to\n // preserve for the Reka-backed primitives.\n modelValue: undefined,\n multiple: false,\n // Not `false`, for either. `false` is a caller saying \"this zone is fine /\n // enabled even though its row is not\"; absent is a caller saying nothing,\n // and only `undefined` survives to mean that past Vue's\n // absent-Boolean-casts-to-false rule — see TextField.vue, which carries the\n // full reasoning.\n disabled: undefined,\n invalid: undefined,\n },\n);\n\nconst emit = defineEmits<{\n /** The full list after the change — files added, or one removed. Never a partial list. */\n \"update:modelValue\": [files: File[]];\n /** Every file refused by this one interaction, each with the rule that refused it. Fires once per interaction, not once per file. */\n reject: [rejections: FileUploadRejection[]];\n}>();\n\n// `text`, not `labels`: the prop of that name is one of the three sources this\n// resolves, and a template reading the raw prop would be reading the overrides\n// rather than the answer.\nconst text = useLabels(\"fileUpload\", FILE_UPLOAD_LABELS, () => props.labels);\n\n// `class` sizes the whole control, so it lands on the root; everything else a\n// caller passes — `name`, `id`, `data-testid`, `aria-describedby` — describes\n// the real `<input type=\"file\">` and lands there. Splitting them is the only\n// way to route both correctly: the root is not a form control and the input is\n// not the box a caller lays out.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\n\n// The label's `for` needs the input's id, and a caller passing their own id —\n// or a Field row publishing one — must not end up with a label pointing at\n// nothing. `inputId` is resolved from `field` at the foot of this block.\nconst generatedId = useId();\nconst messageId = useId();\n\nconst ownFiles = ref<File[]>([]);\nconst files = computed(() => props.modelValue ?? ownFiles.value);\n\nconst rejections = ref<FileUploadRejection[]>([]);\n\n/**\n * A stable key per file, held against the File object itself.\n *\n * Keying the rows by index instead re-keys every row after the one removed, so\n * removing the first of five replays the entrance animation on the other four\n * — the list appears to reload because one row left it. A WeakMap keys by\n * identity, holds nothing alive, and survives the list being reordered.\n */\nconst rowIds = new WeakMap<File, number>();\nlet nextRowId = 0;\nfunction rowId(file: File): number {\n let id = rowIds.get(file);\n if (id === undefined) {\n id = nextRowId++;\n rowIds.set(file, id);\n }\n return id;\n}\n\n/** Name, size and mtime — what the file system itself calls the same file. */\nfunction signature(file: File): string {\n return `${file.name}:${String(file.size)}:${String(file.lastModified)}`;\n}\n\n/**\n * The same test the native input applies to `accept`, applied again to a drop\n * — the browser enforces it in the file dialog and not at all on a drag, so a\n * zone that only sets the attribute accepts anything dropped on it.\n *\n * `file.type` is the browser's guess from the extension and it is empty for\n * anything it does not recognise, which is why an `accept` written as MIME\n * types alone turns away files it meant to take. Naming both — `.csv,text/csv`\n * — is the form that works, and it is what the documentation shows.\n */\nfunction accepts(file: File): boolean {\n const accept = props.accept?.trim();\n if (accept === undefined || accept.length === 0) return true;\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return accept.split(\",\").some((raw) => {\n const pattern = raw.trim().toLowerCase();\n if (pattern.length === 0) return false;\n if (pattern.startsWith(\".\")) return name.endsWith(pattern);\n if (pattern.endsWith(\"/*\")) return type.startsWith(pattern.slice(0, -1));\n return type === pattern;\n });\n}\n\nfunction commit(next: File[]): void {\n if (props.modelValue === undefined) ownFiles.value = next;\n emit(\"update:modelValue\", next);\n}\n\n/** Runs the offered files past every rule, keeping what passes and reporting the rest. */\nfunction add(incoming: File[]): void {\n if (field.disabled) return;\n\n const current = files.value;\n const kept = props.multiple ? [...current] : [];\n const seen = new Set(kept.map(signature));\n const refused: FileUploadRejection[] = [];\n const limit = props.maxSize;\n\n for (const file of incoming) {\n if (!props.multiple && kept.length > 0) {\n refused.push({ file, reason: \"too-many\" });\n } else if (limit !== undefined && file.size > limit) {\n refused.push({ file, reason: \"too-large\" });\n } else if (!accepts(file)) {\n refused.push({ file, reason: \"type\" });\n } else if (seen.has(signature(file))) {\n refused.push({ file, reason: \"duplicate\" });\n } else {\n seen.add(signature(file));\n kept.push(file);\n }\n }\n\n rejections.value = refused;\n if (refused.length > 0) emit(\"reject\", refused);\n\n const unchanged = kept.length === current.length && kept.every((file, i) => file === current[i]);\n if (!unchanged) commit(kept);\n}\n\nfunction remove(index: number): void {\n if (field.disabled) return;\n // The message named files from the last selection. Once the reader edits the\n // list themselves it no longer describes anything on screen, and an error\n // sitting under a list somebody is actively fixing reads as an error about\n // that list.\n rejections.value = [];\n commit(files.value.filter((_, i) => i !== index));\n}\n\nfunction onSelect(event: Event): void {\n const input = event.target as HTMLInputElement;\n add(Array.from(input.files ?? []));\n // Cleared so choosing the same file a second time still fires `change`. The\n // input compares the new selection against its own value and stays silent\n // when they match — which, after a file has been removed from the list, is a\n // control that refuses to take back the file the reader just took out.\n input.value = \"\";\n}\n\n/**\n * Enters minus leaves, rather than a boolean.\n *\n * `dragleave` fires on the zone every time the pointer crosses onto one of its\n * own children, so an enter/leave pair drops the highlight and picks it up\n * again as the reader drags across the icon and the copy — the zone flickers\n * under the cursor at the exact moment it is meant to be saying \"let go here\".\n * A child's `dragenter` always lands before the parent's `dragleave`, so the\n * count never reaches zero until the pointer has genuinely left. `relatedTarget`\n * answers the same question in one line, but it is not reliably populated for a\n * drag that began outside the document — which is every drag this zone sees.\n */\nconst dragDepth = ref(0);\nconst dragging = computed(() => dragDepth.value > 0 && !field.disabled);\n\nfunction onDragEnter(): void {\n dragDepth.value += 1;\n}\n\nfunction onDragLeave(): void {\n dragDepth.value = Math.max(dragDepth.value - 1, 0);\n}\n\nfunction onDragOver(event: DragEvent): void {\n // Without this the browser keeps its default handling, which for a file is\n // \"navigate to it\" — the drop never reaches the zone and the page is gone.\n // Setting dropEffect is what makes the cursor say copy rather than move.\n event.preventDefault();\n if (event.dataTransfer) event.dataTransfer.dropEffect = field.disabled ? \"none\" : \"copy\";\n}\n\nfunction onDrop(event: DragEvent): void {\n event.preventDefault();\n // Reset rather than decrement: the drop consumes the drag, and no matching\n // `dragleave` follows it.\n dragDepth.value = 0;\n add(Array.from(event.dataTransfer?.files ?? []));\n}\n\n// Nothing here listens on `window` or `document`. A page-wide drag handler\n// would be the usual way to stop a file dropped *beside* the zone from\n// navigating the browser away, but a library primitive that installs one\n// silently governs every other drop target on the host's page. That decision\n// is the host's, so the listeners a component cannot tear down are the ones it\n// never registers.\n\n// The two content props sit *above* the resolved label rather than replacing\n// it: `label` is the wording for one zone — \"Drop last month's export here\" —\n// and the label is what every other zone says. Read through `text.` here and\n// not resolved above, so a language switch after mount repaints the copy.\nconst zoneLabel = computed(() => props.label ?? text.value.zone({ multiple: props.multiple }));\n\nconst zoneHint = computed(() => {\n if (props.hint !== undefined) return props.hint;\n // The limit is handed over as a byte count, never as `formatBytes` output: a\n // host that writes `1,5 Mo` or Eastern Arabic digits cannot get there from a\n // string this component already decided the shape of.\n return props.maxSize === undefined ? undefined : text.value.hint({ maxSize: props.maxSize });\n});\n\n// One call over the whole list rather than one per refusal joined with a\n// space. The joiner was the fragment: which character separates two sentences\n// is a property of the script, and a language that would rather count the\n// refused files than list them has no way to say so from a per-file message.\nconst message = computed(() =>\n rejections.value.length === 0\n ? \"\"\n : text.value.rejected({ rejections: rejections.value, maxSize: props.maxSize }),\n);\n\n/**\n * The drop is the reason this control cannot be left to the platform.\n *\n * `<fieldset disabled>` makes the nested `<input type=\"file\">` inert, which\n * closes the click and the keyboard, and it makes each file's remove button\n * inert too. It does nothing at all to the `drop` handler, because that sits on\n * a `<label>` — not a form control, and never disabled by a fieldset. `add()`\n * bails on `field.disabled`, which is exactly the value a disabled fieldset\n * does not reach, so a zone inside a disabled group accepted a dragged file\n * while refusing every other way in.\n *\n * Read off the DOM rather than taken from the Field context, which publishes no\n * `disabled` on purpose — see `../../lib/ancestor-disabled.ts`.\n */\nconst root = useTemplateRef<HTMLElement>(\"root\");\nconst groupDisabled = useAncestorDisabled(() => root.value);\n\n// The Field wiring sits here, below `message`, because it reads it: this is the\n// one control in the family that already had something of its own to say about\n// itself, so its `describedBy` is a *merge* and not a hand-over. Its refusal\n// message reads first — a file turned away for being too large is the most\n// specific thing there is to say — then whatever the caller pointed at, then\n// the row's hint or error, which `useFieldControl` appends. Replacing rather\n// than merging at either end is how a rejection stops being announced.\n//\n// The condition is `message` itself, not `rejections`, so the id can never\n// point at a `<p>` the template has not rendered.\n//\n// `name` and `required` are absent from the object because this component has\n// no prop for either: the row's answer stands unopposed, and its `name` reaches\n// the real `<input type=\"file\">`, which is what a `<form>` posts. `readonly` is\n// absent for the reason the docblock gives.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [message.value.length > 0 ? messageId : undefined, attrs[\"aria-describedby\"]]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n // The fieldset beats the prop in both directions, exactly as it does for the\n // `<input type=\"file\">` inside this very control.\n disabled: groupDisabled.value ? true : props.disabled,\n invalid: props.invalid,\n}));\n\n// `field.id` is the caller's own id, then the row's; the generated one is the\n// floor beneath both, because the `<label for>` and the input have to agree on\n// an id whether or not anybody supplied one.\nconst inputId = computed(() => field.id ?? generatedId);\n</script>\n\n<template>\n <div ref=\"root\" :class=\"cn('flex w-full flex-col', attrs.class as string)\">\n <!-- The drag handlers sit on a <label>, which the rule reads as a static\n element — and it is usually right to: a box with a drop handler and\n nothing else is a control no keyboard can reach. Here the keyboard\n path is the real <input type=\"file\"> nested inside, which the label\n both names and activates, so the drop adds a pointer shortcut to a\n control that already works without one. -->\n <!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -->\n <label\n :for=\"inputId\"\n :data-dragging=\"dragging || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :data-disabled=\"field.disabled || undefined\"\n :class=\"\n cn(\n // `group` so the two lines of copy below can name their own disabled\n // colour off this element's state. A colour set here would only be\n // *inherited* by them, and both declare a colour of their own — an\n // inherited value loses to any declaration, however weak.\n 'group flex flex-col items-center justify-center gap-2 rounded-md border border-dashed border-input bg-background px-6 py-8 text-center',\n // Border and fill are the drag feedback, so they carry the transition\n // — fast, because a zone that lights up after the pointer has already\n // crossed it is answering a question the reader stopped asking.\n 'transition-[color,background-color,border-color,box-shadow] duration-fast ease-out',\n // Focus lives on the clipped input, which is 1px and can show a reader\n // nothing. The zone wears the ring on its behalf, the same\n // focus-within arrangement TextField uses for its own inner input.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // The dash going solid is what keeps the drag state off colour alone.\n 'data-[dragging]:border-solid data-[dragging]:border-primary data-[dragging]:bg-primary-muted',\n // Drained, not dimmed. The zone is a large blank region whose whole\n // purpose is the two lines of text in the middle of it, and\n // `opacity-50` took the headline to 3.06:1 and the hint below it to\n // 2.05:1 — the reader least able to guess what a drop zone wants is\n // the one it stopped telling. The fill falls to the neutral well\n // instead, which is where every other unavailable surface in the\n // library goes, and both lines land at 4.67:1 over it. The dash stays,\n // because it is what marks this out as a drop target even while\n // nothing may be dropped on it; only its colour slackens, from\n // `input` to the lighter `border`, so the state is told in a weight\n // as well as in two fills rather than in hue alone.\n field.disabled\n ? 'cursor-not-allowed border-border bg-muted'\n : 'cursor-pointer hover:bg-subtle',\n // Last of the rules naming a border colour, and the order is\n // load-bearing: `cn()` resolves one by whichever class it saw last,\n // so a zone both refusing a file and unavailable would lose its\n // destructive rim to the disabled one if this sat above it.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n )\n \"\n @dragenter=\"onDragEnter\"\n @dragleave=\"onDragLeave\"\n @dragover=\"onDragOver\"\n @drop=\"onDrop\"\n >\n <!-- `field.attrs` spreads **after** the fallthrough attrs, and that\n position is the contract (see TextField.vue): the bag has already\n resolved the caller's own id and merged their description with this\n zone's refusal message and the row's hint, and it drops every key\n that resolved to nothing, so it can only add. The `:aria-invalid` and\n `:aria-describedby` bindings that used to sit here went with it — an\n individual attribute written after a `v-bind` wins, so either one\n kept would overwrite the resolved value with the raw prop.\n\n `:id` is the deliberate exception, and not that failure: `inputId` is\n the bag's own id with the generated one beneath it, so it can only\n ever restate what the bag holds or supply the id the `<label for>`\n needs when the bag holds none. -->\n <input\n v-bind=\"{ ...inputAttrs, ...field.attrs }\"\n :id=\"inputId\"\n type=\"file\"\n :accept=\"accept\"\n :multiple=\"multiple\"\n :disabled=\"field.disabled\"\n class=\"sr-only\"\n @change=\"onSelect\"\n />\n <!-- The medallion, as EmptyState draws it: a 20px glyph alone in a large\n blank region has nothing holding it and reads as a stray mark. -->\n <span\n aria-hidden=\"true\"\n class=\"grid h-10 w-10 place-items-center rounded-full border border-border bg-subtle text-muted-foreground\"\n >\n <CloudUpload class=\"h-5 w-5\" />\n </span>\n <!-- The disabled colour is declared here rather than inherited from the\n zone, for the reason the `group` on it records: this span sets\n `text-foreground` itself, and an inherited value never beats a\n declared one. The hint below needs no such rule — it is already on\n the muted colour, which holds 4.67:1 over the drained fill. -->\n <span class=\"text-sm text-foreground group-data-[disabled]:text-muted-foreground\">{{\n zoneLabel\n }}</span>\n <span v-if=\"zoneHint\" class=\"text-small text-muted-foreground\">{{ zoneHint }}</span>\n </label>\n\n <!-- The region is in the DOM from the first render, empty. A live region\n mounted at the same moment as its text is a region assistive tech was\n not yet watching, so the first refusal — the one that matters most —\n is the one it announces least reliably. -->\n <div aria-live=\"polite\" aria-atomic=\"true\">\n <p\n v-if=\"message.length > 0\"\n :id=\"messageId\"\n class=\"mt-2 flex animate-fade-rise items-start gap-2 text-small text-destructive\"\n >\n <TriangleAlert class=\"mt-0.5 h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n <span>{{ message }}</span>\n </p>\n </div>\n\n <ul v-if=\"files.length > 0\" class=\"mt-3 flex flex-col gap-1.5\">\n <li\n v-for=\"(file, index) in files\"\n :key=\"rowId(file)\"\n class=\"flex animate-fade-rise items-center gap-2 rounded-md border border-border bg-card px-3 py-2\"\n :style=\"{ animationDelay: listStaggerDelay(index) }\"\n >\n <FileIcon class=\"h-4 w-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span class=\"min-w-0 flex-1 truncate text-sm text-foreground\">{{ file.name }}</span>\n <span class=\"shrink-0 tabular text-small text-muted-foreground\">\n {{ text.size({ bytes: file.size }) }}\n </span>\n <!-- Named with the file it removes. Seven buttons all called \"Remove\"\n are seven identical announcements and one guess about which row a\n reader is on. -->\n <button\n type=\"button\"\n :aria-label=\"text.remove({ file })\"\n :disabled=\"field.disabled\"\n class=\"inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors duration-fast ease-out hover:bg-subtle hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo disabled:cursor-not-allowed disabled:opacity-50\"\n @click=\"remove(index)\"\n >\n <X class=\"h-4 w-4\" />\n </button>\n </li>\n </ul>\n </div>\n</template>\n"],"mappings":""}