@ecoma-io/loom 0.3.0 → 0.5.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 (587) hide show
  1. package/README.md +11 -9
  2. package/dist/a11y.js +2 -2
  3. package/dist/index.js +73 -6
  4. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/CalendarDate.js +1 -1
  5. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/CalendarDate.js.map +1 -1
  6. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/calendars/GregorianCalendar.js +1 -1
  7. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/calendars/GregorianCalendar.js.map +1 -1
  8. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/conversion.js +1 -1
  9. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/conversion.js.map +1 -1
  10. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/manipulation.js +1 -1
  11. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/manipulation.js.map +1 -1
  12. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/queries.js +1 -1
  13. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/queries.js.map +1 -1
  14. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/string.js +1 -1
  15. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/string.js.map +1 -1
  16. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/utils.js +1 -1
  17. package/dist/node_modules/.pnpm/{@internationalized_date@3.12.3 → @internationalized_date@3.12.4}/node_modules/@internationalized/date/dist/private/utils.js.map +1 -1
  18. package/dist/packages/blocks/app-header/src/AppHeader.js.map +1 -1
  19. package/dist/packages/blocks/app-header/src/AppHeader.vue.d.ts.map +1 -1
  20. package/dist/packages/blocks/app-header/src/AppHeader.vue_vue_type_script_setup_true_lang.js +1 -0
  21. package/dist/packages/blocks/app-header/src/AppHeader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/packages/blocks/desktop-app-shell/src/DesktopAppShell.vue.d.ts +3 -3
  23. package/dist/packages/blocks/empty-state/src/EmptyState.js.map +1 -1
  24. package/dist/packages/blocks/empty-state/src/EmptyState.vue.d.ts.map +1 -1
  25. package/dist/packages/blocks/empty-state/src/EmptyState.vue_vue_type_script_setup_true_lang.js +5 -6
  26. package/dist/packages/blocks/empty-state/src/EmptyState.vue_vue_type_script_setup_true_lang.js.map +1 -1
  27. package/dist/packages/blocks/error-summary/src/ErrorSummary.js +7 -0
  28. package/dist/packages/blocks/error-summary/src/ErrorSummary.js.map +1 -0
  29. package/dist/packages/blocks/error-summary/src/ErrorSummary.vue.d.ts +31 -0
  30. package/dist/packages/blocks/error-summary/src/ErrorSummary.vue_vue_type_script_setup_true_lang.js +90 -0
  31. package/dist/packages/blocks/error-summary/src/ErrorSummary.vue_vue_type_script_setup_true_lang.js.map +1 -0
  32. package/dist/packages/blocks/error-summary/src/index.d.ts +4 -0
  33. package/dist/packages/blocks/error-summary/src/index.js +3 -0
  34. package/dist/packages/blocks/metric-card/src/MetricCard.js.map +1 -1
  35. package/dist/packages/blocks/metric-card/src/MetricCard.vue.d.ts +22 -4
  36. package/dist/packages/blocks/metric-card/src/MetricCard.vue.d.ts.map +1 -1
  37. package/dist/packages/blocks/metric-card/src/MetricCard.vue_vue_type_script_setup_true_lang.js +38 -15
  38. package/dist/packages/blocks/metric-card/src/MetricCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  39. package/dist/packages/blocks/title-bar/src/TitleBar.vue.d.ts +3 -3
  40. package/dist/packages/blocks/toast-stack/src/ToastStack.vue.d.ts +3 -3
  41. package/dist/packages/composition/scroll-reel/src/ScrollReel.js +2 -2
  42. package/dist/packages/composition/scroll-reel/src/ScrollReel.js.map +1 -1
  43. package/dist/packages/composition/scroll-reel/src/ScrollReel.vue.d.ts +26 -0
  44. package/dist/packages/composition/scroll-reel/src/ScrollReel.vue_vue_type_script_setup_true_lang.js +28 -7
  45. package/dist/packages/composition/scroll-reel/src/ScrollReel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  46. package/dist/packages/composition/scroll-reel/src/index.d.ts +2 -1
  47. package/dist/packages/composition/scroll-reel/src/index.js +2 -1
  48. package/dist/packages/composition/sidebar/src/Sidebar.js.map +1 -1
  49. package/dist/packages/composition/sidebar/src/Sidebar.vue.d.ts +4 -0
  50. package/dist/packages/composition/sidebar/src/Sidebar.vue.d.ts.map +1 -1
  51. package/dist/packages/composition/sidebar/src/Sidebar.vue_vue_type_script_setup_true_lang.js +13 -4
  52. package/dist/packages/composition/sidebar/src/Sidebar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  53. package/dist/packages/composition/split/src/Split.js.map +1 -1
  54. package/dist/packages/composition/split/src/Split.vue.d.ts +11 -12
  55. package/dist/packages/composition/split/src/Split.vue.d.ts.map +1 -1
  56. package/dist/packages/composition/split/src/Split.vue_vue_type_script_setup_true_lang.js +15 -5
  57. package/dist/packages/composition/split/src/Split.vue_vue_type_script_setup_true_lang.js.map +1 -1
  58. package/dist/packages/core/src/a11y-scope.d.ts +71 -5
  59. package/dist/packages/core/src/a11y-scope.d.ts.map +1 -1
  60. package/dist/packages/core/src/a11y-scope.js +126 -6
  61. package/dist/packages/core/src/a11y-scope.js.map +1 -1
  62. package/dist/packages/core/src/index.d.ts +2 -2
  63. package/dist/packages/core/src/index.d.ts.map +1 -1
  64. package/dist/packages/core/src/index.js +3 -3
  65. package/dist/packages/core/src/motion.d.ts +13 -0
  66. package/dist/packages/core/src/motion.d.ts.map +1 -1
  67. package/dist/packages/core/src/motion.js +20 -13
  68. package/dist/packages/core/src/motion.js.map +1 -1
  69. package/dist/packages/core/src/theme.d.ts +9 -0
  70. package/dist/packages/core/src/theme.d.ts.map +1 -1
  71. package/dist/packages/core/src/theme.js +36 -10
  72. package/dist/packages/core/src/theme.js.map +1 -1
  73. package/dist/packages/labels/src/ancestor-disabled.d.ts +5 -1
  74. package/dist/packages/labels/src/ancestor-disabled.d.ts.map +1 -1
  75. package/dist/packages/labels/src/ancestor-disabled.js +5 -1
  76. package/dist/packages/labels/src/ancestor-disabled.js.map +1 -1
  77. package/dist/packages/labels/src/component-shapes.d.ts +236 -1
  78. package/dist/packages/labels/src/component-shapes.d.ts.map +1 -1
  79. package/dist/packages/labels/src/date-labels.d.ts +86 -7
  80. package/dist/packages/labels/src/date-labels.d.ts.map +1 -1
  81. package/dist/packages/labels/src/date-labels.js +58 -9
  82. package/dist/packages/labels/src/date-labels.js.map +1 -1
  83. package/dist/packages/labels/src/field-context.d.ts +2 -2
  84. package/dist/packages/labels/src/field-context.js.map +1 -1
  85. package/dist/packages/labels/src/index.d.ts +3 -3
  86. package/dist/packages/labels/src/index.d.ts.map +1 -1
  87. package/dist/packages/labels/src/index.js +2 -2
  88. package/dist/packages/labels/src/label-registry.d.ts +15 -1
  89. package/dist/packages/labels/src/label-registry.d.ts.map +1 -1
  90. package/dist/packages/layouts/dashboard/src/Dashboard.js.map +1 -1
  91. package/dist/packages/layouts/dashboard/src/Dashboard.vue.d.ts +4 -4
  92. package/dist/packages/layouts/dashboard/src/Dashboard.vue.d.ts.map +1 -1
  93. package/dist/packages/layouts/dashboard/src/Dashboard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  94. package/dist/packages/layouts/reading/src/Reading.js.map +1 -1
  95. package/dist/packages/layouts/reading/src/Reading.vue.d.ts +16 -15
  96. package/dist/packages/layouts/reading/src/Reading.vue.d.ts.map +1 -1
  97. package/dist/packages/layouts/reading/src/Reading.vue_vue_type_script_setup_true_lang.js.map +1 -1
  98. package/dist/packages/layouts/reading/src/index.d.ts +1 -0
  99. package/dist/packages/layouts/reading/src/index.d.ts.map +1 -1
  100. package/dist/packages/layouts/settings/src/Settings.js.map +1 -1
  101. package/dist/packages/layouts/settings/src/Settings.vue.d.ts +3 -3
  102. package/dist/packages/layouts/settings/src/Settings.vue.d.ts.map +1 -1
  103. package/dist/packages/layouts/settings/src/Settings.vue_vue_type_script_setup_true_lang.js.map +1 -1
  104. package/dist/packages/layouts/split-layout/src/SplitLayout.js.map +1 -1
  105. package/dist/packages/layouts/split-layout/src/SplitLayout.vue.d.ts +4 -6
  106. package/dist/packages/layouts/split-layout/src/SplitLayout.vue.d.ts.map +1 -1
  107. package/dist/packages/layouts/split-layout/src/SplitLayout.vue_vue_type_script_setup_true_lang.js +20 -5
  108. package/dist/packages/layouts/split-layout/src/SplitLayout.vue_vue_type_script_setup_true_lang.js.map +1 -1
  109. package/dist/packages/layouts/split-layout/src/index.d.ts +1 -1
  110. package/dist/packages/layouts/split-layout/src/index.d.ts.map +1 -1
  111. package/dist/packages/loom/src/a11y.d.ts +1 -1
  112. package/dist/packages/loom/src/index.d.ts +217 -174
  113. package/dist/packages/loom/src/theme.d.ts +3 -0
  114. package/dist/packages/primitives/alert/src/Alert.js +7 -0
  115. package/dist/packages/primitives/alert/src/Alert.js.map +1 -0
  116. package/dist/packages/primitives/alert/src/Alert.vue.d.ts +91 -0
  117. package/dist/packages/primitives/alert/src/Alert.vue_vue_type_script_setup_true_lang.js +130 -0
  118. package/dist/packages/primitives/alert/src/Alert.vue_vue_type_script_setup_true_lang.js.map +1 -0
  119. package/dist/packages/primitives/alert/src/index.d.ts +4 -0
  120. package/dist/packages/primitives/alert/src/index.js +3 -0
  121. package/dist/packages/primitives/alert-dialog/src/AlertDialog.js.map +1 -1
  122. package/dist/packages/primitives/alert-dialog/src/AlertDialog.vue.d.ts +2 -2
  123. package/dist/packages/primitives/alert-dialog/src/AlertDialog.vue_vue_type_script_setup_true_lang.js +2 -2
  124. package/dist/packages/primitives/alert-dialog/src/AlertDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  125. package/dist/packages/primitives/alert-dialog/src/index.d.ts +1 -1
  126. package/dist/packages/primitives/avatar/src/Avatar.js.map +1 -1
  127. package/dist/packages/primitives/avatar/src/Avatar.vue.d.ts +2 -2
  128. package/dist/packages/primitives/avatar/src/Avatar.vue.d.ts.map +1 -1
  129. package/dist/packages/primitives/avatar/src/Avatar.vue_vue_type_script_setup_true_lang.js +1 -1
  130. package/dist/packages/primitives/avatar/src/Avatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  131. package/dist/packages/primitives/avatar-group/src/AvatarGroup.vue.d.ts +3 -3
  132. package/dist/packages/primitives/avatar-group/src/index.d.ts +1 -1
  133. package/dist/packages/primitives/badge/src/Badge.js.map +1 -1
  134. package/dist/packages/primitives/badge/src/Badge.vue.d.ts.map +1 -1
  135. package/dist/packages/primitives/badge/src/Badge.vue_vue_type_script_setup_true_lang.js +1 -1
  136. package/dist/packages/primitives/badge/src/Badge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  137. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.js +2 -2
  138. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.js.map +1 -1
  139. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.vue.d.ts +28 -0
  140. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.vue_vue_type_script_setup_true_lang.js +16 -5
  141. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
  142. package/dist/packages/primitives/breadcrumb/src/index.d.ts +2 -1
  143. package/dist/packages/primitives/breadcrumb/src/index.js +2 -1
  144. package/dist/packages/primitives/button/src/Button.js.map +1 -1
  145. package/dist/packages/primitives/button/src/Button.vue.d.ts.map +1 -1
  146. package/dist/packages/primitives/button/src/Button.vue_vue_type_script_setup_true_lang.js.map +1 -1
  147. package/dist/packages/primitives/button/src/index.js +2 -2
  148. package/dist/packages/primitives/calendar/src/Calendar.js +7 -0
  149. package/dist/packages/primitives/calendar/src/Calendar.js.map +1 -0
  150. package/dist/packages/primitives/calendar/src/Calendar.vue.d.ts +95 -0
  151. package/dist/packages/primitives/calendar/src/Calendar.vue_vue_type_script_setup_true_lang.js +204 -0
  152. package/dist/packages/primitives/calendar/src/Calendar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  153. package/dist/packages/primitives/calendar/src/index.d.ts +3 -0
  154. package/dist/packages/primitives/calendar/src/index.js +3 -0
  155. package/dist/packages/primitives/card/src/Card.js +7 -0
  156. package/dist/packages/primitives/card/src/Card.js.map +1 -0
  157. package/dist/packages/primitives/card/src/Card.vue.d.ts +72 -0
  158. package/dist/packages/primitives/card/src/Card.vue_vue_type_script_setup_true_lang.js +65 -0
  159. package/dist/packages/primitives/card/src/Card.vue_vue_type_script_setup_true_lang.js.map +1 -0
  160. package/dist/packages/primitives/card/src/index.d.ts +3 -0
  161. package/dist/packages/primitives/card/src/index.d.ts.map +1 -0
  162. package/dist/packages/primitives/card/src/index.js +2 -0
  163. package/dist/packages/primitives/carousel/src/Carousel.js +7 -0
  164. package/dist/packages/primitives/carousel/src/Carousel.js.map +1 -0
  165. package/dist/packages/primitives/carousel/src/Carousel.vue.d.ts +34 -0
  166. package/dist/packages/primitives/carousel/src/Carousel.vue_vue_type_script_setup_true_lang.js +175 -0
  167. package/dist/packages/primitives/carousel/src/Carousel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  168. package/dist/packages/primitives/carousel/src/index.d.ts +3 -0
  169. package/dist/packages/primitives/carousel/src/index.js +3 -0
  170. package/dist/packages/primitives/checkbox/src/Checkbox.js.map +1 -1
  171. package/dist/packages/primitives/checkbox/src/Checkbox.vue.d.ts +1 -1
  172. package/dist/packages/primitives/checkbox/src/Checkbox.vue.d.ts.map +1 -1
  173. package/dist/packages/primitives/checkbox/src/Checkbox.vue_vue_type_script_setup_true_lang.js +3 -3
  174. package/dist/packages/primitives/checkbox/src/Checkbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  175. package/dist/packages/primitives/chip/src/Chip.js.map +1 -1
  176. package/dist/packages/primitives/chip/src/Chip.vue.d.ts.map +1 -1
  177. package/dist/packages/primitives/chip/src/Chip.vue_vue_type_script_setup_true_lang.js +1 -1
  178. package/dist/packages/primitives/chip/src/Chip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  179. package/dist/packages/primitives/collapse/src/Collapse.js +7 -0
  180. package/dist/packages/primitives/collapse/src/Collapse.js.map +1 -0
  181. package/dist/packages/primitives/collapse/src/Collapse.vue.d.ts +56 -0
  182. package/dist/packages/primitives/collapse/src/Collapse.vue.d.ts.map +1 -0
  183. package/dist/packages/primitives/collapse/src/Collapse.vue_vue_type_script_setup_true_lang.js +81 -0
  184. package/dist/packages/primitives/collapse/src/Collapse.vue_vue_type_script_setup_true_lang.js.map +1 -0
  185. package/dist/packages/primitives/collapse/src/index.d.ts +2 -0
  186. package/dist/packages/primitives/collapse/src/index.d.ts.map +1 -0
  187. package/dist/packages/primitives/collapse/src/index.js +2 -0
  188. package/dist/packages/primitives/color-picker/src/ColorPicker.js.map +1 -1
  189. package/dist/packages/primitives/color-picker/src/ColorPicker.vue.d.ts +4 -4
  190. package/dist/packages/primitives/color-picker/src/ColorPicker.vue_vue_type_script_setup_true_lang.js +2 -2
  191. package/dist/packages/primitives/color-picker/src/ColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  192. package/dist/packages/primitives/color-picker/src/index.d.ts +1 -1
  193. package/dist/packages/primitives/combobox/src/Combobox.js.map +1 -1
  194. package/dist/packages/primitives/combobox/src/Combobox.vue.d.ts +3 -3
  195. package/dist/packages/primitives/combobox/src/Combobox.vue_vue_type_script_setup_true_lang.js +2 -2
  196. package/dist/packages/primitives/combobox/src/Combobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  197. package/dist/packages/primitives/combobox/src/index.d.ts +1 -1
  198. package/dist/packages/primitives/command/src/Command.js +7 -0
  199. package/dist/packages/primitives/command/src/Command.js.map +1 -0
  200. package/dist/packages/primitives/command/src/Command.vue.d.ts +77 -0
  201. package/dist/packages/primitives/command/src/Command.vue_vue_type_script_setup_true_lang.js +333 -0
  202. package/dist/packages/primitives/command/src/Command.vue_vue_type_script_setup_true_lang.js.map +1 -0
  203. package/dist/packages/primitives/command/src/index.d.ts +4 -0
  204. package/dist/packages/primitives/command/src/index.js +3 -0
  205. package/dist/packages/primitives/context-menu/src/ContextMenu.js.map +1 -1
  206. package/dist/packages/primitives/context-menu/src/ContextMenu.vue.d.ts +12 -0
  207. package/dist/packages/primitives/context-menu/src/ContextMenu.vue.d.ts.map +1 -1
  208. package/dist/packages/primitives/context-menu/src/ContextMenu.vue_vue_type_script_setup_true_lang.js +7 -3
  209. package/dist/packages/primitives/context-menu/src/ContextMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  210. package/dist/packages/primitives/copy-button/src/CopyButton.js +7 -0
  211. package/dist/packages/primitives/copy-button/src/CopyButton.js.map +1 -0
  212. package/dist/packages/primitives/copy-button/src/CopyButton.vue.d.ts +64 -0
  213. package/dist/packages/primitives/copy-button/src/CopyButton.vue_vue_type_script_setup_true_lang.js +113 -0
  214. package/dist/packages/primitives/copy-button/src/CopyButton.vue_vue_type_script_setup_true_lang.js.map +1 -0
  215. package/dist/packages/primitives/copy-button/src/index.d.ts +4 -0
  216. package/dist/packages/primitives/copy-button/src/index.js +3 -0
  217. package/dist/packages/primitives/data-grid/src/DataGrid.js +7 -0
  218. package/dist/packages/primitives/data-grid/src/DataGrid.js.map +1 -0
  219. package/dist/packages/primitives/data-grid/src/DataGrid.vue.d.ts +128 -0
  220. package/dist/packages/primitives/data-grid/src/DataGrid.vue_vue_type_script_setup_true_lang.js +347 -0
  221. package/dist/packages/primitives/data-grid/src/DataGrid.vue_vue_type_script_setup_true_lang.js.map +1 -0
  222. package/dist/packages/primitives/data-grid/src/index.d.ts +4 -0
  223. package/dist/packages/primitives/data-grid/src/index.js +3 -0
  224. package/dist/packages/primitives/date-picker/src/DatePicker.js.map +1 -1
  225. package/dist/packages/primitives/date-picker/src/DatePicker.vue.d.ts +2 -2
  226. package/dist/packages/primitives/date-picker/src/DatePicker.vue_vue_type_script_setup_true_lang.js +14 -14
  227. package/dist/packages/primitives/date-picker/src/DatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  228. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.js.map +1 -1
  229. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue.d.ts +3 -3
  230. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue_vue_type_script_setup_true_lang.js +22 -19
  231. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  232. package/dist/packages/primitives/date-range-picker/src/index.d.ts +1 -1
  233. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.js.map +1 -1
  234. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue.d.ts +2 -2
  235. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue_vue_type_script_setup_true_lang.js +18 -17
  236. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  237. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.js.map +1 -1
  238. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue.d.ts +3 -3
  239. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue_vue_type_script_setup_true_lang.js +18 -17
  240. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  241. package/dist/packages/primitives/date-time-range-picker/src/index.d.ts +1 -1
  242. package/dist/packages/primitives/dialog/src/Dialog.js.map +1 -1
  243. package/dist/packages/primitives/dialog/src/Dialog.vue.d.ts +27 -11
  244. package/dist/packages/primitives/dialog/src/Dialog.vue_vue_type_script_setup_true_lang.js +6 -4
  245. package/dist/packages/primitives/dialog/src/Dialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  246. package/dist/packages/primitives/dialog/src/index.d.ts +1 -1
  247. package/dist/packages/primitives/drawer/src/Drawer.js.map +1 -1
  248. package/dist/packages/primitives/drawer/src/Drawer.vue.d.ts +12 -4
  249. package/dist/packages/primitives/drawer/src/Drawer.vue_vue_type_script_setup_true_lang.js +13 -5
  250. package/dist/packages/primitives/drawer/src/Drawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  251. package/dist/packages/primitives/drawer/src/index.d.ts +1 -1
  252. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.js.map +1 -1
  253. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.vue.d.ts +26 -0
  254. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.vue.d.ts.map +1 -1
  255. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.vue_vue_type_script_setup_true_lang.js +10 -4
  256. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  257. package/dist/packages/primitives/editable/src/Editable.js.map +1 -1
  258. package/dist/packages/primitives/editable/src/Editable.vue.d.ts +2 -2
  259. package/dist/packages/primitives/editable/src/Editable.vue_vue_type_script_setup_true_lang.js +1 -1
  260. package/dist/packages/primitives/editable/src/Editable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  261. package/dist/packages/primitives/editable/src/index.d.ts +1 -1
  262. package/dist/packages/primitives/file-upload/src/FileUpload.js.map +1 -1
  263. package/dist/packages/primitives/file-upload/src/FileUpload.vue.d.ts +2 -2
  264. package/dist/packages/primitives/file-upload/src/FileUpload.vue_vue_type_script_setup_true_lang.js +1 -1
  265. package/dist/packages/primitives/file-upload/src/FileUpload.vue_vue_type_script_setup_true_lang.js.map +1 -1
  266. package/dist/packages/primitives/file-upload/src/index.d.ts +1 -1
  267. package/dist/packages/primitives/hover-card/src/HoverCard.js.map +1 -1
  268. package/dist/packages/primitives/hover-card/src/HoverCard.vue.d.ts.map +1 -1
  269. package/dist/packages/primitives/hover-card/src/HoverCard.vue_vue_type_script_setup_true_lang.js +1 -1
  270. package/dist/packages/primitives/hover-card/src/HoverCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  271. package/dist/packages/primitives/hover-card/src/index.d.ts.map +1 -1
  272. package/dist/packages/primitives/icon-button/src/IconButton.js.map +1 -1
  273. package/dist/packages/primitives/icon-button/src/IconButton.vue.d.ts +10 -4
  274. package/dist/packages/primitives/icon-button/src/IconButton.vue.d.ts.map +1 -1
  275. package/dist/packages/primitives/icon-button/src/IconButton.vue_vue_type_script_setup_true_lang.js +18 -6
  276. package/dist/packages/primitives/icon-button/src/IconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  277. package/dist/packages/primitives/indicator/src/Indicator.vue.d.ts +2 -2
  278. package/dist/packages/primitives/indicator/src/index.d.ts +1 -1
  279. package/dist/packages/primitives/kbd/src/Kbd.js +7 -0
  280. package/dist/packages/primitives/kbd/src/Kbd.js.map +1 -0
  281. package/dist/packages/primitives/kbd/src/Kbd.vue.d.ts +30 -0
  282. package/dist/packages/primitives/kbd/src/Kbd.vue.d.ts.map +1 -0
  283. package/dist/packages/primitives/kbd/src/Kbd.vue_vue_type_script_setup_true_lang.js +29 -0
  284. package/dist/packages/primitives/kbd/src/Kbd.vue_vue_type_script_setup_true_lang.js.map +1 -0
  285. package/dist/packages/primitives/kbd/src/index.d.ts +3 -0
  286. package/dist/packages/primitives/kbd/src/index.d.ts.map +1 -0
  287. package/dist/packages/primitives/kbd/src/index.js +3 -0
  288. package/dist/packages/primitives/link/src/Link.js.map +1 -1
  289. package/dist/packages/primitives/link/src/Link.vue.d.ts.map +1 -1
  290. package/dist/packages/primitives/link/src/Link.vue_vue_type_script_setup_true_lang.js +3 -3
  291. package/dist/packages/primitives/link/src/Link.vue_vue_type_script_setup_true_lang.js.map +1 -1
  292. package/dist/packages/primitives/list/src/List.js +7 -0
  293. package/dist/packages/primitives/list/src/List.js.map +1 -0
  294. package/dist/packages/primitives/list/src/List.vue.d.ts +22 -0
  295. package/dist/packages/primitives/list/src/List.vue.d.ts.map +1 -0
  296. package/dist/packages/primitives/list/src/List.vue_vue_type_script_setup_true_lang.js +27 -0
  297. package/dist/packages/primitives/list/src/List.vue_vue_type_script_setup_true_lang.js.map +1 -0
  298. package/dist/packages/primitives/list/src/ListItem.js +7 -0
  299. package/dist/packages/primitives/list/src/ListItem.js.map +1 -0
  300. package/dist/packages/primitives/list/src/ListItem.vue.d.ts +75 -0
  301. package/dist/packages/primitives/list/src/ListItem.vue.d.ts.map +1 -0
  302. package/dist/packages/primitives/list/src/ListItem.vue_vue_type_script_setup_true_lang.js +101 -0
  303. package/dist/packages/primitives/list/src/ListItem.vue_vue_type_script_setup_true_lang.js.map +1 -0
  304. package/dist/packages/primitives/list/src/index.d.ts +4 -0
  305. package/dist/packages/primitives/list/src/index.d.ts.map +1 -0
  306. package/dist/packages/primitives/list/src/index.js +3 -0
  307. package/dist/packages/primitives/live-region/src/LiveRegion.js +7 -0
  308. package/dist/packages/primitives/live-region/src/LiveRegion.js.map +1 -0
  309. package/dist/packages/primitives/live-region/src/LiveRegion.vue.d.ts +36 -0
  310. package/dist/packages/primitives/live-region/src/LiveRegion.vue.d.ts.map +1 -0
  311. package/dist/packages/primitives/live-region/src/LiveRegion.vue_vue_type_script_setup_true_lang.js +59 -0
  312. package/dist/packages/primitives/live-region/src/LiveRegion.vue_vue_type_script_setup_true_lang.js.map +1 -0
  313. package/dist/packages/primitives/live-region/src/context.d.ts +40 -0
  314. package/dist/packages/primitives/live-region/src/context.d.ts.map +1 -0
  315. package/dist/packages/primitives/live-region/src/context.js +85 -0
  316. package/dist/packages/primitives/live-region/src/context.js.map +1 -0
  317. package/dist/packages/primitives/live-region/src/index.d.ts +4 -0
  318. package/dist/packages/primitives/live-region/src/index.d.ts.map +1 -0
  319. package/dist/packages/primitives/live-region/src/index.js +3 -0
  320. package/dist/packages/primitives/live-region/src/useAnnounce.d.ts +17 -0
  321. package/dist/packages/primitives/live-region/src/useAnnounce.d.ts.map +1 -0
  322. package/dist/packages/primitives/live-region/src/useAnnounce.js +25 -0
  323. package/dist/packages/primitives/live-region/src/useAnnounce.js.map +1 -0
  324. package/dist/packages/primitives/menubar/src/Menubar.js.map +1 -1
  325. package/dist/packages/primitives/menubar/src/Menubar.vue.d.ts +20 -6
  326. package/dist/packages/primitives/menubar/src/Menubar.vue.d.ts.map +1 -1
  327. package/dist/packages/primitives/menubar/src/Menubar.vue_vue_type_script_setup_true_lang.js +211 -64
  328. package/dist/packages/primitives/menubar/src/Menubar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  329. package/dist/packages/primitives/meter/src/Meter.js +7 -0
  330. package/dist/packages/primitives/meter/src/Meter.js.map +1 -0
  331. package/dist/packages/primitives/meter/src/Meter.vue.d.ts +87 -0
  332. package/dist/packages/primitives/meter/src/Meter.vue_vue_type_script_setup_true_lang.js +236 -0
  333. package/dist/packages/primitives/meter/src/Meter.vue_vue_type_script_setup_true_lang.js.map +1 -0
  334. package/dist/packages/primitives/meter/src/index.d.ts +4 -0
  335. package/dist/packages/primitives/meter/src/index.js +3 -0
  336. package/dist/packages/primitives/navigation-menu/src/NavigationMenu.js +7 -0
  337. package/dist/packages/primitives/navigation-menu/src/NavigationMenu.js.map +1 -0
  338. package/dist/packages/primitives/navigation-menu/src/NavigationMenu.vue.d.ts +69 -0
  339. package/dist/packages/primitives/navigation-menu/src/NavigationMenu.vue.d.ts.map +1 -0
  340. package/dist/packages/primitives/navigation-menu/src/NavigationMenu.vue_vue_type_script_setup_true_lang.js +118 -0
  341. package/dist/packages/primitives/navigation-menu/src/NavigationMenu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  342. package/dist/packages/primitives/navigation-menu/src/index.d.ts +3 -0
  343. package/dist/packages/primitives/navigation-menu/src/index.d.ts.map +1 -0
  344. package/dist/packages/primitives/navigation-menu/src/index.js +2 -0
  345. package/dist/packages/primitives/number-field/src/NumberField.js.map +1 -1
  346. package/dist/packages/primitives/number-field/src/NumberField.vue.d.ts +2 -2
  347. package/dist/packages/primitives/number-field/src/NumberField.vue_vue_type_script_setup_true_lang.js +1 -0
  348. package/dist/packages/primitives/number-field/src/NumberField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  349. package/dist/packages/primitives/number-field/src/index.d.ts +1 -1
  350. package/dist/packages/primitives/otp-input/src/OtpInput.vue.d.ts +2 -2
  351. package/dist/packages/primitives/otp-input/src/index.d.ts +1 -1
  352. package/dist/packages/primitives/pagination/src/Pagination.js.map +1 -1
  353. package/dist/packages/primitives/pagination/src/Pagination.vue.d.ts +3 -3
  354. package/dist/packages/primitives/pagination/src/Pagination.vue_vue_type_script_setup_true_lang.js +1 -1
  355. package/dist/packages/primitives/pagination/src/Pagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  356. package/dist/packages/primitives/pagination/src/index.d.ts +1 -1
  357. package/dist/packages/primitives/popover/src/Popover.js.map +1 -1
  358. package/dist/packages/primitives/popover/src/Popover.vue.d.ts.map +1 -1
  359. package/dist/packages/primitives/popover/src/Popover.vue_vue_type_script_setup_true_lang.js +1 -1
  360. package/dist/packages/primitives/popover/src/Popover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  361. package/dist/packages/primitives/progress/src/Progress.vue.d.ts +2 -2
  362. package/dist/packages/primitives/progress/src/index.d.ts +1 -1
  363. package/dist/packages/primitives/radial-progress/src/RadialProgress.vue.d.ts +2 -2
  364. package/dist/packages/primitives/radial-progress/src/index.d.ts +1 -1
  365. package/dist/packages/primitives/radio-group/src/RadioGroup.js.map +1 -1
  366. package/dist/packages/primitives/radio-group/src/RadioGroup.vue.d.ts.map +1 -1
  367. package/dist/packages/primitives/radio-group/src/RadioGroup.vue_vue_type_script_setup_true_lang.js +1 -1
  368. package/dist/packages/primitives/radio-group/src/RadioGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  369. package/dist/packages/primitives/rating/src/Rating.vue.d.ts +2 -2
  370. package/dist/packages/primitives/rating/src/index.d.ts +1 -1
  371. package/dist/packages/primitives/segmented-control/src/SegmentedControl.js.map +1 -1
  372. package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue.d.ts.map +1 -1
  373. package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue_vue_type_script_setup_true_lang.js +2 -3
  374. package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue_vue_type_script_setup_true_lang.js.map +1 -1
  375. package/dist/packages/primitives/select/src/Select.js.map +1 -1
  376. package/dist/packages/primitives/select/src/Select.vue.d.ts.map +1 -1
  377. package/dist/packages/primitives/select/src/Select.vue_vue_type_script_setup_true_lang.js +1 -1
  378. package/dist/packages/primitives/select/src/Select.vue_vue_type_script_setup_true_lang.js.map +1 -1
  379. package/dist/packages/primitives/skip-link/src/SkipLink.js +7 -0
  380. package/dist/packages/primitives/skip-link/src/SkipLink.js.map +1 -0
  381. package/dist/packages/primitives/skip-link/src/SkipLink.vue.d.ts +62 -0
  382. package/dist/packages/primitives/skip-link/src/SkipLink.vue_vue_type_script_setup_true_lang.js +64 -0
  383. package/dist/packages/primitives/skip-link/src/SkipLink.vue_vue_type_script_setup_true_lang.js.map +1 -0
  384. package/dist/packages/primitives/skip-link/src/index.d.ts +3 -0
  385. package/dist/packages/primitives/skip-link/src/index.js +3 -0
  386. package/dist/packages/primitives/slider/src/Slider.js.map +1 -1
  387. package/dist/packages/primitives/slider/src/Slider.vue.d.ts +6 -3
  388. package/dist/packages/primitives/slider/src/Slider.vue.d.ts.map +1 -1
  389. package/dist/packages/primitives/slider/src/Slider.vue_vue_type_script_setup_true_lang.js +15 -10
  390. package/dist/packages/primitives/slider/src/Slider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  391. package/dist/packages/primitives/stepper/src/Stepper.js.map +1 -1
  392. package/dist/packages/primitives/stepper/src/Stepper.vue.d.ts +2 -2
  393. package/dist/packages/primitives/stepper/src/Stepper.vue_vue_type_script_setup_true_lang.js +7 -5
  394. package/dist/packages/primitives/stepper/src/Stepper.vue_vue_type_script_setup_true_lang.js.map +1 -1
  395. package/dist/packages/primitives/stepper/src/index.d.ts +1 -1
  396. package/dist/packages/primitives/switch/src/Switch.js.map +1 -1
  397. package/dist/packages/primitives/switch/src/Switch.vue.d.ts +1 -1
  398. package/dist/packages/primitives/switch/src/Switch.vue.d.ts.map +1 -1
  399. package/dist/packages/primitives/switch/src/Switch.vue_vue_type_script_setup_true_lang.js +3 -3
  400. package/dist/packages/primitives/switch/src/Switch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  401. package/dist/packages/primitives/table/src/Table.js +7 -0
  402. package/dist/packages/primitives/table/src/Table.js.map +1 -0
  403. package/dist/packages/primitives/table/src/Table.vue.d.ts +45 -0
  404. package/dist/packages/primitives/table/src/Table.vue_vue_type_script_setup_true_lang.js +72 -0
  405. package/dist/packages/primitives/table/src/Table.vue_vue_type_script_setup_true_lang.js.map +1 -0
  406. package/dist/packages/primitives/table/src/TableCell.js +7 -0
  407. package/dist/packages/primitives/table/src/TableCell.js.map +1 -0
  408. package/dist/packages/primitives/table/src/TableCell.vue.d.ts +19 -0
  409. package/dist/packages/primitives/table/src/TableCell.vue.d.ts.map +1 -0
  410. package/dist/packages/primitives/table/src/TableCell.vue_vue_type_script_setup_true_lang.js +17 -0
  411. package/dist/packages/primitives/table/src/TableCell.vue_vue_type_script_setup_true_lang.js.map +1 -0
  412. package/dist/packages/primitives/table/src/TableHead.js +7 -0
  413. package/dist/packages/primitives/table/src/TableHead.js.map +1 -0
  414. package/dist/packages/primitives/table/src/TableHead.vue.d.ts +56 -0
  415. package/dist/packages/primitives/table/src/TableHead.vue_vue_type_script_setup_true_lang.js +79 -0
  416. package/dist/packages/primitives/table/src/TableHead.vue_vue_type_script_setup_true_lang.js.map +1 -0
  417. package/dist/packages/primitives/table/src/TableRow.js +7 -0
  418. package/dist/packages/primitives/table/src/TableRow.js.map +1 -0
  419. package/dist/packages/primitives/table/src/TableRow.vue.d.ts +39 -0
  420. package/dist/packages/primitives/table/src/TableRow.vue.d.ts.map +1 -0
  421. package/dist/packages/primitives/table/src/TableRow.vue_vue_type_script_setup_true_lang.js +61 -0
  422. package/dist/packages/primitives/table/src/TableRow.vue_vue_type_script_setup_true_lang.js.map +1 -0
  423. package/dist/packages/primitives/table/src/index.d.ts +10 -0
  424. package/dist/packages/primitives/table/src/index.js +8 -0
  425. package/dist/packages/primitives/tabs/src/Tabs.js.map +1 -1
  426. package/dist/packages/primitives/tabs/src/Tabs.vue.d.ts.map +1 -1
  427. package/dist/packages/primitives/tabs/src/Tabs.vue_vue_type_script_setup_true_lang.js +1 -1
  428. package/dist/packages/primitives/tabs/src/Tabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  429. package/dist/packages/primitives/tags-input/src/TagsInput.js.map +1 -1
  430. package/dist/packages/primitives/tags-input/src/TagsInput.vue.d.ts +2 -2
  431. package/dist/packages/primitives/tags-input/src/TagsInput.vue_vue_type_script_setup_true_lang.js +1 -1
  432. package/dist/packages/primitives/tags-input/src/TagsInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  433. package/dist/packages/primitives/tags-input/src/index.d.ts +1 -1
  434. package/dist/packages/primitives/text-field/src/TextField.js.map +1 -1
  435. package/dist/packages/primitives/text-field/src/TextField.vue.d.ts +2 -2
  436. package/dist/packages/primitives/text-field/src/TextField.vue_vue_type_script_setup_true_lang.js +1 -1
  437. package/dist/packages/primitives/text-field/src/TextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  438. package/dist/packages/primitives/text-field/src/index.d.ts +1 -1
  439. package/dist/packages/primitives/text-field/src/index.js +2 -2
  440. package/dist/packages/primitives/textarea/src/Textarea.js.map +1 -1
  441. package/dist/packages/primitives/textarea/src/Textarea.vue.d.ts +2 -2
  442. package/dist/packages/primitives/textarea/src/Textarea.vue_vue_type_script_setup_true_lang.js +1 -1
  443. package/dist/packages/primitives/textarea/src/Textarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  444. package/dist/packages/primitives/textarea/src/index.d.ts +1 -1
  445. package/dist/packages/primitives/textarea/src/index.js +2 -2
  446. package/dist/packages/primitives/time-picker/src/TimePicker.js.map +1 -1
  447. package/dist/packages/primitives/time-picker/src/TimePicker.vue.d.ts +2 -2
  448. package/dist/packages/primitives/time-picker/src/TimePicker.vue_vue_type_script_setup_true_lang.js +10 -8
  449. package/dist/packages/primitives/time-picker/src/TimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  450. package/dist/packages/primitives/timeline/src/Timeline.js +7 -0
  451. package/dist/packages/primitives/timeline/src/Timeline.js.map +1 -0
  452. package/dist/packages/primitives/timeline/src/Timeline.vue.d.ts +37 -0
  453. package/dist/packages/primitives/timeline/src/Timeline.vue_vue_type_script_setup_true_lang.js +44 -0
  454. package/dist/packages/primitives/timeline/src/Timeline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  455. package/dist/packages/primitives/timeline/src/TimelineItem.js +7 -0
  456. package/dist/packages/primitives/timeline/src/TimelineItem.js.map +1 -0
  457. package/dist/packages/primitives/timeline/src/TimelineItem.vue.d.ts +50 -0
  458. package/dist/packages/primitives/timeline/src/TimelineItem.vue_vue_type_script_setup_true_lang.js +96 -0
  459. package/dist/packages/primitives/timeline/src/TimelineItem.vue_vue_type_script_setup_true_lang.js.map +1 -0
  460. package/dist/packages/primitives/timeline/src/index.d.ts +5 -0
  461. package/dist/packages/primitives/timeline/src/index.js +5 -0
  462. package/dist/packages/primitives/toast/src/Toast.vue.d.ts +2 -2
  463. package/dist/packages/primitives/toast/src/ToastItem.js.map +1 -1
  464. package/dist/packages/primitives/toast/src/ToastItem.vue.d.ts +2 -2
  465. package/dist/packages/primitives/toast/src/ToastItem.vue_vue_type_script_setup_true_lang.js +5 -3
  466. package/dist/packages/primitives/toast/src/ToastItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  467. package/dist/packages/primitives/toast/src/index.d.ts +1 -1
  468. package/dist/packages/primitives/toggle-group/src/ToggleGroup.js +7 -0
  469. package/dist/packages/primitives/toggle-group/src/ToggleGroup.js.map +1 -0
  470. package/dist/packages/primitives/toggle-group/src/ToggleGroup.vue.d.ts +53 -0
  471. package/dist/packages/primitives/toggle-group/src/ToggleGroup.vue.d.ts.map +1 -0
  472. package/dist/packages/primitives/toggle-group/src/ToggleGroup.vue_vue_type_script_setup_true_lang.js +111 -0
  473. package/dist/packages/primitives/toggle-group/src/ToggleGroup.vue_vue_type_script_setup_true_lang.js.map +1 -0
  474. package/dist/packages/primitives/toggle-group/src/index.d.ts +3 -0
  475. package/dist/packages/primitives/toggle-group/src/index.d.ts.map +1 -0
  476. package/dist/packages/primitives/toggle-group/src/index.js +2 -0
  477. package/dist/packages/primitives/toolbar/src/Toolbar.js +7 -0
  478. package/dist/packages/primitives/toolbar/src/Toolbar.js.map +1 -0
  479. package/dist/packages/primitives/toolbar/src/Toolbar.vue.d.ts +31 -0
  480. package/dist/packages/primitives/toolbar/src/Toolbar.vue.d.ts.map +1 -0
  481. package/dist/packages/primitives/toolbar/src/Toolbar.vue_vue_type_script_setup_true_lang.js +149 -0
  482. package/dist/packages/primitives/toolbar/src/Toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  483. package/dist/packages/primitives/toolbar/src/ToolbarSeparator.js +7 -0
  484. package/dist/packages/primitives/toolbar/src/ToolbarSeparator.js.map +1 -0
  485. package/dist/packages/primitives/toolbar/src/ToolbarSeparator.vue.d.ts +24 -0
  486. package/dist/packages/primitives/toolbar/src/ToolbarSeparator.vue.d.ts.map +1 -0
  487. package/dist/packages/primitives/toolbar/src/ToolbarSeparator.vue_vue_type_script_setup_true_lang.js +40 -0
  488. package/dist/packages/primitives/toolbar/src/ToolbarSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -0
  489. package/dist/packages/primitives/toolbar/src/index.d.ts +4 -0
  490. package/dist/packages/primitives/toolbar/src/index.d.ts.map +1 -0
  491. package/dist/packages/primitives/toolbar/src/index.js +3 -0
  492. package/dist/packages/primitives/tooltip/src/Tooltip.js.map +1 -1
  493. package/dist/packages/primitives/tooltip/src/Tooltip.vue.d.ts.map +1 -1
  494. package/dist/packages/primitives/tooltip/src/Tooltip.vue_vue_type_script_setup_true_lang.js +1 -1
  495. package/dist/packages/primitives/tooltip/src/Tooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  496. package/dist/packages/primitives/tree-view/src/TreeView.js +7 -0
  497. package/dist/packages/primitives/tree-view/src/TreeView.js.map +1 -0
  498. package/dist/packages/primitives/tree-view/src/TreeView.vue.d.ts +99 -0
  499. package/dist/packages/primitives/tree-view/src/TreeView.vue_vue_type_script_setup_true_lang.js +310 -0
  500. package/dist/packages/primitives/tree-view/src/TreeView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  501. package/dist/packages/primitives/tree-view/src/TreeViewNode.js +7 -0
  502. package/dist/packages/primitives/tree-view/src/TreeViewNode.js.map +1 -0
  503. package/dist/packages/primitives/tree-view/src/TreeViewNode.vue.d.ts +15 -0
  504. package/dist/packages/primitives/tree-view/src/TreeViewNode.vue.d.ts.map +1 -0
  505. package/dist/packages/primitives/tree-view/src/TreeViewNode.vue_vue_type_script_setup_true_lang.js +103 -0
  506. package/dist/packages/primitives/tree-view/src/TreeViewNode.vue_vue_type_script_setup_true_lang.js.map +1 -0
  507. package/dist/packages/primitives/tree-view/src/context.d.ts +39 -0
  508. package/dist/packages/primitives/tree-view/src/context.d.ts.map +1 -0
  509. package/dist/packages/primitives/tree-view/src/context.js +6 -0
  510. package/dist/packages/primitives/tree-view/src/context.js.map +1 -0
  511. package/dist/packages/primitives/tree-view/src/index.d.ts +4 -0
  512. package/dist/packages/primitives/tree-view/src/index.js +3 -0
  513. package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.js +7 -0
  514. package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.js.map +1 -0
  515. package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.vue.d.ts +21 -0
  516. package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.vue.d.ts.map +1 -0
  517. package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.vue_vue_type_script_setup_true_lang.js +27 -0
  518. package/dist/packages/primitives/visually-hidden/src/VisuallyHidden.vue_vue_type_script_setup_true_lang.js.map +1 -0
  519. package/dist/packages/primitives/visually-hidden/src/index.d.ts +3 -0
  520. package/dist/packages/primitives/visually-hidden/src/index.d.ts.map +1 -0
  521. package/dist/packages/primitives/visually-hidden/src/index.js +2 -0
  522. package/dist/packages/primitives/window-controls/src/WindowControls.vue.d.ts +2 -2
  523. package/dist/packages/primitives/window-controls/src/index.d.ts +1 -1
  524. package/dist/styles/global.css +59 -0
  525. package/dist/styles/theme.css +13 -0
  526. package/dist/theme.js +3 -0
  527. package/package.json +37 -30
  528. package/dist/packages/blocks/desktop-app-shell/src/DesktopAppShell.vue.d.ts.map +0 -1
  529. package/dist/packages/blocks/title-bar/src/TitleBar.vue.d.ts.map +0 -1
  530. package/dist/packages/blocks/toast-stack/src/ToastStack.vue.d.ts.map +0 -1
  531. package/dist/packages/composition/scroll-reel/src/ScrollReel.vue.d.ts.map +0 -1
  532. package/dist/packages/composition/scroll-reel/src/index.d.ts.map +0 -1
  533. package/dist/packages/loom/src/a11y.d.ts.map +0 -1
  534. package/dist/packages/loom/src/index.d.ts.map +0 -1
  535. package/dist/packages/primitives/alert-dialog/src/AlertDialog.vue.d.ts.map +0 -1
  536. package/dist/packages/primitives/alert-dialog/src/index.d.ts.map +0 -1
  537. package/dist/packages/primitives/avatar-group/src/AvatarGroup.vue.d.ts.map +0 -1
  538. package/dist/packages/primitives/avatar-group/src/index.d.ts.map +0 -1
  539. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.vue.d.ts.map +0 -1
  540. package/dist/packages/primitives/breadcrumb/src/index.d.ts.map +0 -1
  541. package/dist/packages/primitives/color-picker/src/ColorPicker.vue.d.ts.map +0 -1
  542. package/dist/packages/primitives/color-picker/src/index.d.ts.map +0 -1
  543. package/dist/packages/primitives/combobox/src/Combobox.vue.d.ts.map +0 -1
  544. package/dist/packages/primitives/combobox/src/index.d.ts.map +0 -1
  545. package/dist/packages/primitives/date-picker/src/DatePicker.vue.d.ts.map +0 -1
  546. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue.d.ts.map +0 -1
  547. package/dist/packages/primitives/date-range-picker/src/index.d.ts.map +0 -1
  548. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue.d.ts.map +0 -1
  549. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue.d.ts.map +0 -1
  550. package/dist/packages/primitives/date-time-range-picker/src/index.d.ts.map +0 -1
  551. package/dist/packages/primitives/dialog/src/Dialog.vue.d.ts.map +0 -1
  552. package/dist/packages/primitives/dialog/src/index.d.ts.map +0 -1
  553. package/dist/packages/primitives/drawer/src/Drawer.vue.d.ts.map +0 -1
  554. package/dist/packages/primitives/drawer/src/index.d.ts.map +0 -1
  555. package/dist/packages/primitives/editable/src/Editable.vue.d.ts.map +0 -1
  556. package/dist/packages/primitives/editable/src/index.d.ts.map +0 -1
  557. package/dist/packages/primitives/file-upload/src/FileUpload.vue.d.ts.map +0 -1
  558. package/dist/packages/primitives/file-upload/src/index.d.ts.map +0 -1
  559. package/dist/packages/primitives/indicator/src/Indicator.vue.d.ts.map +0 -1
  560. package/dist/packages/primitives/indicator/src/index.d.ts.map +0 -1
  561. package/dist/packages/primitives/number-field/src/NumberField.vue.d.ts.map +0 -1
  562. package/dist/packages/primitives/number-field/src/index.d.ts.map +0 -1
  563. package/dist/packages/primitives/otp-input/src/OtpInput.vue.d.ts.map +0 -1
  564. package/dist/packages/primitives/otp-input/src/index.d.ts.map +0 -1
  565. package/dist/packages/primitives/pagination/src/Pagination.vue.d.ts.map +0 -1
  566. package/dist/packages/primitives/pagination/src/index.d.ts.map +0 -1
  567. package/dist/packages/primitives/progress/src/Progress.vue.d.ts.map +0 -1
  568. package/dist/packages/primitives/progress/src/index.d.ts.map +0 -1
  569. package/dist/packages/primitives/radial-progress/src/RadialProgress.vue.d.ts.map +0 -1
  570. package/dist/packages/primitives/radial-progress/src/index.d.ts.map +0 -1
  571. package/dist/packages/primitives/rating/src/Rating.vue.d.ts.map +0 -1
  572. package/dist/packages/primitives/rating/src/index.d.ts.map +0 -1
  573. package/dist/packages/primitives/stepper/src/Stepper.vue.d.ts.map +0 -1
  574. package/dist/packages/primitives/stepper/src/index.d.ts.map +0 -1
  575. package/dist/packages/primitives/tags-input/src/TagsInput.vue.d.ts.map +0 -1
  576. package/dist/packages/primitives/tags-input/src/index.d.ts.map +0 -1
  577. package/dist/packages/primitives/text-field/src/TextField.vue.d.ts.map +0 -1
  578. package/dist/packages/primitives/text-field/src/index.d.ts.map +0 -1
  579. package/dist/packages/primitives/textarea/src/Textarea.vue.d.ts.map +0 -1
  580. package/dist/packages/primitives/textarea/src/index.d.ts.map +0 -1
  581. package/dist/packages/primitives/time-picker/src/TimePicker.vue.d.ts.map +0 -1
  582. package/dist/packages/primitives/toast/src/Toast.vue.d.ts.map +0 -1
  583. package/dist/packages/primitives/toast/src/ToastItem.vue.d.ts.map +0 -1
  584. package/dist/packages/primitives/toast/src/index.d.ts.map +0 -1
  585. package/dist/packages/primitives/window-controls/src/WindowControls.vue.d.ts.map +0 -1
  586. package/dist/packages/primitives/window-controls/src/index.d.ts.map +0 -1
  587. package/dist/styles/theme.contrast.test.ts +0 -460
@@ -0,0 +1,118 @@
1
+ import { cn } from "../../../core/src/cn.js";
2
+ import { optional } from "../../../core/src/props.js";
3
+ import { listStaggerDelay } from "../../../core/src/motion.js";
4
+ import "../../../core/src/index.js";
5
+ import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeProps, normalizeClass, normalizeStyle, openBlock, renderList, toDisplayString, unref, withCtx } from "vue";
6
+ import { NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuRoot, NavigationMenuTrigger, NavigationMenuViewport } from "reka-ui";
7
+ //#region packages/primitives/navigation-menu/src/NavigationMenu.vue?vue&type=script&setup=true&lang.ts
8
+ var _hoisted_1 = ["aria-label", "dir"];
9
+ var _hoisted_2 = [
10
+ "href",
11
+ "aria-current",
12
+ "aria-disabled",
13
+ "tabindex"
14
+ ];
15
+ var _hoisted_3 = { class: "flex flex-col gap-1 p-1" };
16
+ var _hoisted_4 = [
17
+ "href",
18
+ "aria-current",
19
+ "aria-disabled",
20
+ "tabindex"
21
+ ];
22
+ var _hoisted_5 = { class: "font-medium" };
23
+ var _hoisted_6 = {
24
+ key: 0,
25
+ class: "text-xs text-muted-foreground"
26
+ };
27
+ var NavigationMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
28
+ __name: "NavigationMenu",
29
+ props: {
30
+ items: {},
31
+ orientation: { default: "horizontal" },
32
+ dir: {},
33
+ modelValue: { default: void 0 },
34
+ ariaLabel: { default: "Navigation" }
35
+ },
36
+ emits: ["update:modelValue"],
37
+ setup(__props) {
38
+ return (_ctx, _cache) => {
39
+ return openBlock(), createElementBlock("nav", {
40
+ "aria-label": __props.ariaLabel,
41
+ dir: __props.dir
42
+ }, [createVNode(unref(NavigationMenuRoot), mergeProps({ orientation: __props.orientation }, unref(optional)({
43
+ modelValue: __props.modelValue,
44
+ dir: __props.dir
45
+ }), {
46
+ class: "relative",
47
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.$emit("update:modelValue", $event))
48
+ }), {
49
+ default: withCtx(() => [
50
+ createVNode(unref(NavigationMenuList), { class: normalizeClass(unref(cn)("flex items-center gap-0.5", __props.orientation === "vertical" && "flex-col items-stretch")) }, {
51
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
52
+ return openBlock(), createBlock(unref(NavigationMenuItem), {
53
+ key: item.value,
54
+ value: item.value
55
+ }, {
56
+ default: withCtx(() => [item.children && item.children.length > 0 ? (openBlock(), createBlock(unref(NavigationMenuTrigger), {
57
+ key: 0,
58
+ disabled: item.disabled ?? false,
59
+ "aria-haspopup": "menu",
60
+ class: normalizeClass(unref(cn)("min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out", "hover:bg-subtle hover:text-foreground", "focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo", "data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground", "data-[state=open]:bg-subtle data-[state=open]:text-foreground"))
61
+ }, {
62
+ default: withCtx(() => [createTextVNode(toDisplayString(item.label), 1)]),
63
+ _: 2
64
+ }, 1032, ["disabled", "class"])) : (openBlock(), createBlock(unref(NavigationMenuLink), mergeProps({
65
+ key: 1,
66
+ ref_for: true
67
+ }, unref(optional)({
68
+ asChild: true,
69
+ active: item.active
70
+ }), { class: unref(cn)("min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out", "hover:bg-subtle hover:text-foreground", "focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo", item.active && "bg-primary-muted text-primary-text", item.disabled && "pointer-events-none text-muted-foreground") }), {
71
+ default: withCtx(() => [createElementVNode("a", {
72
+ href: item.href,
73
+ "aria-current": item.active ? "page" : void 0,
74
+ "aria-disabled": item.disabled || void 0,
75
+ tabindex: item.disabled ? -1 : void 0
76
+ }, toDisplayString(item.label), 9, _hoisted_2)]),
77
+ _: 2
78
+ }, 1040, ["class"])), item.children && item.children.length > 0 ? (openBlock(), createBlock(unref(NavigationMenuContent), {
79
+ key: 2,
80
+ class: normalizeClass(unref(cn)("z-overlay min-w-[16rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-none", "data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out")),
81
+ style: { "transform-origin": "var(--reka-popper-transform-origin)" }
82
+ }, {
83
+ default: withCtx(() => [createElementVNode("div", _hoisted_3, [(openBlock(true), createElementBlock(Fragment, null, renderList(item.children, (link, i) => {
84
+ return openBlock(), createElementBlock("a", {
85
+ key: link.href,
86
+ href: link.href,
87
+ "aria-current": link.active ? "page" : void 0,
88
+ "aria-disabled": link.disabled || void 0,
89
+ tabindex: link.disabled ? -1 : void 0,
90
+ style: normalizeStyle({ animationDelay: unref(listStaggerDelay)(i) }),
91
+ class: normalizeClass(unref(cn)("block rounded-sm px-2 py-1.5 text-sm text-foreground no-underline outline-none", "transition-colors duration-fast ease-out", "hover:bg-subtle", "focus-visible:bg-subtle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo", link.active && "bg-primary-muted text-primary-text", link.disabled && "pointer-events-none text-muted-foreground", "animate-fade-rise"))
92
+ }, [createElementVNode("div", _hoisted_5, toDisplayString(link.label), 1), link.description ? (openBlock(), createElementBlock("div", _hoisted_6, toDisplayString(link.description), 1)) : createCommentVNode("", true)], 14, _hoisted_4);
93
+ }), 128))])]),
94
+ _: 2
95
+ }, 1032, ["class"])) : createCommentVNode("", true)]),
96
+ _: 2
97
+ }, 1032, ["value"]);
98
+ }), 128))]),
99
+ _: 1
100
+ }, 8, ["class"]),
101
+ __props.orientation === "horizontal" ? (openBlock(), createBlock(unref(NavigationMenuIndicator), {
102
+ key: 0,
103
+ class: normalizeClass(unref(cn)("top-full z-10 h-0.5 bg-primary", "data-[state=hidden]:animate-fade-fall data-[state=visible]:animate-fade-rise"))
104
+ }, null, 8, ["class"])) : createCommentVNode("", true),
105
+ createVNode(unref(NavigationMenuViewport), {
106
+ class: normalizeClass(unref(cn)("data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out")),
107
+ style: { "transform-origin": "var(--reka-navigation-menu-viewport-transform-origin)" }
108
+ }, null, 8, ["class"])
109
+ ]),
110
+ _: 1
111
+ }, 16, ["orientation"])], 8, _hoisted_1);
112
+ };
113
+ }
114
+ });
115
+ //#endregion
116
+ export { NavigationMenu_vue_vue_type_script_setup_true_lang_default as default };
117
+
118
+ //# sourceMappingURL=NavigationMenu.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavigationMenu.vue_vue_type_script_setup_true_lang.js","names":["$emit"],"sources":["../../../../../packages/primitives/navigation-menu/src/NavigationMenu.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * A navigation link shown inside a dropdown panel. The list is data rather than\n * markup because the primitive stays free of routing logic — the host provides\n * `href` values and decides what navigation means.\n */\nexport interface NavigationMenuLink {\n /** Visible label. */\n label: string;\n /** Link target. */\n href: string;\n /** Whether this link is the current page. */\n active?: boolean;\n /** Optional description shown below the label. */\n description?: string;\n /** Disabled state. */\n disabled?: boolean;\n}\n\n/**\n * One top-level navigation entry. An item with `children` opens a dropdown\n * panel; an item without them is a plain link.\n */\nexport interface NavigationMenuItem {\n /** Visible label for the trigger button. */\n label: string;\n /** Unique key identifying this item. */\n value: string;\n /** The link target. When set, this item is a simple link rather than a dropdown trigger. */\n href?: string;\n /** Whether this item is currently active (shows aria-current). */\n active?: boolean;\n /** Nested navigation items shown in a dropdown panel. */\n children?: NavigationMenuLink[];\n /** Disabled state. */\n disabled?: boolean;\n}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n NavigationMenuRoot,\n NavigationMenuList,\n NavigationMenuItem as RekaNavigationMenuItem,\n NavigationMenuTrigger,\n NavigationMenuContent,\n NavigationMenuLink as RekaNavigationMenuLink,\n NavigationMenuIndicator,\n NavigationMenuViewport,\n} from \"reka-ui\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { listStaggerDelay } from \"@ecoma-io/loom-core\";\nimport { optional } from \"@ecoma-io/loom-core\";\n\n/**\n * NavigationMenu — the WAI-ARIA navigation menu pattern: a landmark containing\n * triggers that open dropdown panels of navigation links.\n *\n * Unlike `DropdownMenu` (button → command list) or `Menubar` (horizontal strip\n * of command menus), this component is a site-navigation landmark. Its panels\n * contain links, not actions, and the whole structure is wrapped in a `<nav>`\n * landmark.\n *\n * Items are data rather than markup. A top-level item with `children` opens a\n * dropdown panel; one without renders as a direct link. The keyboard model\n * follows the APG Navigation Menu pattern: roving tabindex across triggers,\n * Left/Right arrows move between triggers, ArrowDown opens, Escape closes.\n */\nwithDefaults(\n defineProps<{\n /** The top-level navigation items, in order. */\n items: NavigationMenuItem[];\n /** Layout direction of the trigger bar. */\n orientation?: \"horizontal\" | \"vertical\";\n /** Reading direction. Mirrors arrow keys when right-to-left. */\n dir?: \"ltr\" | \"rtl\";\n /** Drive the open trigger from the host; omit it and the menu owns its own state. */\n modelValue?: string | undefined;\n /** Accessible label for the nav landmark. */\n ariaLabel?: string;\n }>(),\n // `modelValue: undefined` is load-bearing — it keeps the uncontrolled third\n // state reachable past Vue's absent-Boolean casting. `optional()` in the\n // template is the other half.\n { modelValue: undefined, orientation: \"horizontal\", ariaLabel: \"Navigation\" },\n);\n\ndefineEmits<{ \"update:modelValue\": [value: string] }>();\n</script>\n\n<template>\n <nav :aria-label=\"ariaLabel\" :dir=\"dir\">\n <NavigationMenuRoot\n :orientation=\"orientation\"\n v-bind=\"optional({ modelValue, dir })\"\n class=\"relative\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <NavigationMenuList\n :class=\"\n cn('flex items-center gap-0.5', orientation === 'vertical' && 'flex-col items-stretch')\n \"\n >\n <RekaNavigationMenuItem v-for=\"item in items\" :key=\"item.value\" :value=\"item.value\">\n <!-- Items with children get a trigger button that opens a panel. -->\n <NavigationMenuTrigger\n v-if=\"item.children && item.children.length > 0\"\n :disabled=\"item.disabled ?? false\"\n aria-haspopup=\"menu\"\n :class=\"\n cn(\n 'min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out',\n 'hover:bg-subtle hover:text-foreground',\n 'focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n 'data-[disabled]:pointer-events-none data-[disabled]:text-muted-foreground',\n 'data-[state=open]:bg-subtle data-[state=open]:text-foreground',\n )\n \"\n >\n {{ item.label }}\n </NavigationMenuTrigger>\n\n <!-- Simple link items wrapped in NavigationMenuLink for roving focus. -->\n <RekaNavigationMenuLink\n v-else\n v-bind=\"optional({ asChild: true, active: item.active })\"\n :class=\"\n cn(\n 'min-h-6 rounded-sm px-2 py-1 text-sm text-muted-foreground transition-colors duration-fast ease-out',\n 'hover:bg-subtle hover:text-foreground',\n 'focus-visible:bg-subtle focus-visible:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n item.active && 'bg-primary-muted text-primary-text',\n item.disabled && 'pointer-events-none text-muted-foreground',\n )\n \"\n >\n <a\n :href=\"item.href\"\n :aria-current=\"item.active ? 'page' : undefined\"\n :aria-disabled=\"item.disabled || undefined\"\n :tabindex=\"item.disabled ? -1 : undefined\"\n >\n {{ item.label }}\n </a>\n </RekaNavigationMenuLink>\n\n <NavigationMenuContent\n v-if=\"item.children && item.children.length > 0\"\n :class=\"\n cn(\n 'z-overlay min-w-[16rem] rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-none',\n 'data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out',\n )\n \"\n style=\"transform-origin: var(--reka-popper-transform-origin)\"\n >\n <div class=\"flex flex-col gap-1 p-1\">\n <a\n v-for=\"(link, i) in item.children\"\n :key=\"link.href\"\n :href=\"link.href\"\n :aria-current=\"link.active ? 'page' : undefined\"\n :aria-disabled=\"link.disabled || undefined\"\n :tabindex=\"link.disabled ? -1 : undefined\"\n :style=\"{ animationDelay: listStaggerDelay(i) }\"\n :class=\"\n cn(\n 'block rounded-sm px-2 py-1.5 text-sm text-foreground no-underline outline-none',\n 'transition-colors duration-fast ease-out',\n 'hover:bg-subtle',\n 'focus-visible:bg-subtle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:shadow-halo',\n link.active && 'bg-primary-muted text-primary-text',\n link.disabled && 'pointer-events-none text-muted-foreground',\n 'animate-fade-rise',\n )\n \"\n >\n <div class=\"font-medium\">{{ link.label }}</div>\n <div v-if=\"link.description\" class=\"text-xs text-muted-foreground\">\n {{ link.description }}\n </div>\n </a>\n </div>\n </NavigationMenuContent>\n </RekaNavigationMenuItem>\n </NavigationMenuList>\n\n <!-- Animated indicator bar (horizontal mode only). -->\n <NavigationMenuIndicator\n v-if=\"orientation === 'horizontal'\"\n :class=\"\n cn(\n 'top-full z-10 h-0.5 bg-primary',\n 'data-[state=hidden]:animate-fade-fall data-[state=visible]:animate-fade-rise',\n )\n \"\n />\n\n <NavigationMenuViewport\n :class=\"cn('data-[state=open]:animate-scale-in data-[state=closed]:animate-scale-out')\"\n style=\"transform-origin: var(--reka-navigation-menu-viewport-transform-origin)\"\n />\n </NavigationMenuRoot>\n </nav>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FE,OAAA,UAAA,GAAA,mBAgHM,OAAA;IAhHA,cAAY,QAAA;IAAY,KAAK,QAAA;GACjC,GAAA,CAAA,YA8GqB,MAAA,kBAAA,GA9GrB,WA8GqB,EA7GlB,aAAa,QAAA,YAAW,GACjB,MAAA,QAAA,CAAQ,CAAA;IAAA,YAAG,QAAA;IAAU,KAAE,QAAA;GAAG,CAAA,GAAA;IAClC,OAAM;IACL,uBAAkB,OAAA,OAAA,OAAA,MAAA,WAAEA,KAAAA,MAAK,qBAAsB,MAAM;;IAEtD,SAAA,cAuFqB;KAvFrB,YAuFqB,MAAA,kBAAA,GAAA,EAtFlB,OAAK,eAAa,MAAA,EAAA,CAAE,CAAA,6BAA8B,QAAA,gBAAW,cAAA,wBAAA,CAAA,EAAA,GAAA;MAItC,SAAA,cAAqB,EAA7C,UAAA,IAAA,GAAA,mBAiFyB,UAAA,MAAA,WAjFc,QAAA,QAAR,SAAI;OAAnC,OAAA,UAAA,GAAA,YAiFyB,MAAA,kBAAA,GAAA;QAjFsB,KAAK,KAAK;QAAQ,OAAO,KAAK;;QAE3E,SAAA,cAewB,CAdhB,KAAK,YAAY,KAAK,SAAS,SAAM,KAD7C,UAAA,GAAA,YAewB,MAAA,qBAAA,GAAA;;SAbrB,UAAU,KAAK,YAAQ;SACxB,iBAAc;SACb,OAAK,eAAiB,MAAA,EAAA,CAAE,CAAA,uGAAA,yCAAA,qKAAA,6EAAA,+DAAA,CAAA;;SAUzB,SAAA,cAAgB,CAAb,gBAAA,gBAAA,KAAK,KAAK,GAAA,CAAA,CAAA,CAAA;;QAIf,GAAA,MAAA,CAAA,YAAA,OAAA,CAAA,MAAA,UAAA,GAAA,YAqByB,MAAA,kBAAA,GArBzB,WAqByB;;;QAnBf,GAAA,MAAA,QAAA,CAAQ,CAAA;SAAA,SAAA;SAAA,QAA0B,KAAK;QAAM,CAAA,GAAA,EACpD,OAAsB,MAAA,EAAA,CAAE,CAAA,uGAAA,yCAAA,qKAAuX,KAAK,UAAM,sCAA0D,KAAK,YAAQ,2CAAA,EAAA,CAAA,GAAA;SAUle,SAAA,cAOI,CAPJ,mBAOI,KAAA;UAND,MAAM,KAAK;UACX,gBAAc,KAAK,SAAM,SAAY,KAAA;UACrC,iBAAe,KAAK,YAAY,KAAA;UAChC,UAAU,KAAK,WAAQ,KAAQ,KAAA;SAE7B,GAAA,gBAAA,KAAK,KAAK,GAAA,GAAA,UAAA,CAAA,CAAA;;QAKT,GAAA,MAAA,CAAA,OAAA,CAAA,IAAA,KAAK,YAAY,KAAK,SAAS,SAAM,KAD7C,UAAA,GAAA,YAqCwB,MAAA,qBAAA,GAAA;;SAnCrB,OAAK,eAAiB,MAAA,EAAA,CAAE,CAAA,yHAAA,0EAAA,CAAA;SAMzB,OAAA,EAAA,oBAAA,sCAAA;;SAEA,SAAA,cA0BM,CA1BN,mBA0BM,OA1BN,YA0BM,EAzBJ,UAAA,IAAA,GAAA,mBAwBI,UAAA,MAAA,WAvBkB,KAAK,WAAjB,MAAM,MAAC;UADjB,OAAA,UAAA,GAAA,mBAwBI,KAAA;WAtBD,KAAK,KAAK;WACV,MAAM,KAAK;WACX,gBAAc,KAAK,SAAM,SAAY,KAAA;WACrC,iBAAe,KAAK,YAAY,KAAA;WAChC,UAAU,KAAK,WAAQ,KAAQ,KAAA;WAC/B,OAAK,eAAA,EAAA,gBAAoB,MAAA,gBAAA,CAAgB,CAAC,CAAC,EAAA,CAAA;WAC3C,OAAK,eAAqB,MAAA,EAAA,CAAE,CAAA,kFAAA,4CAAA,mBAAA,uIAA8X,KAAK,UAAM,sCAA8D,KAAK,YAAQ,6CAAA,mBAAA,CAAA;UAYjf,GAAA,CAAA,mBAA+C,OAA/C,YAA+C,gBAAnB,KAAK,KAAK,GAAA,CAAA,GAC3B,KAAK,eAAhB,UAAA,GAAA,mBAEM,OAFN,YAEM,gBADD,KAAK,WAAW,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,IAAA,UAAA;;;;;;;;;KAUvB,QAAA,gBAAW,gBADnB,UAAA,GAAA,YAQE,MAAA,uBAAA,GAAA;;MANC,OAAK,eAAa,MAAA,EAAA,CAAE,CAAA,kCAAA,8EAAA,CAAA;;KAQvB,YAGE,MAAA,sBAAA,GAAA;MAFC,OAAK,eAAE,MAAA,EAAA,CAAE,CAAA,0EAAA,CAAA;MACV,OAAA,EAAA,oBAAA,wDAAA"}
@@ -0,0 +1,3 @@
1
+ export { default } from "./NavigationMenu.vue";
2
+ export type { NavigationMenuItem, NavigationMenuLink } from "./NavigationMenu.vue";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../packages/primitives/navigation-menu/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import NavigationMenu_default from "./NavigationMenu.js";
2
+ export { NavigationMenu_default as default };
@@ -1 +1 @@
1
- {"version":3,"file":"NumberField.js","names":[],"sources":["../../../../../packages/primitives/number-field/src/NumberField.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { NumberFieldLabels } from \"@ecoma-io/loom-labels\";\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 NUMBER_FIELD_LABELS: NumberFieldLabels = {\n increment: \"Increase value\",\n decrement: \"Decrease value\",\n roleDescription: \"Number field\",\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onUnmounted, ref, useTemplateRef, watch } from \"vue\";\nimport {\n NumberFieldRoot,\n NumberFieldInput,\n NumberFieldIncrement,\n NumberFieldDecrement,\n} from \"reka-ui\";\nimport { ChevronUp, ChevronDown } from \"@lucide/vue\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useAncestorDisabled } from \"@ecoma-io/loom-labels\";\nimport { useFieldControl } from \"@ecoma-io/loom-labels\";\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\nimport { optional } from \"@ecoma-io/loom-core\";\n\n/**\n * NumberField — a constrained number input you can also *scrub*: drag\n * horizontally anywhere on the field and the value runs with the pointer, the\n * gesture professional editors trained everyone to expect on a numeric row.\n *\n * Built on Reka UI's NumberField, which supplies the spinbutton semantics and\n * the typed-value parsing. Two things are added here because Reka has no\n * equivalent: the scrub-drag gesture, and a Shift multiplier on the arrow\n * keys.\n *\n * The event contract is what makes it usable behind an undo stack.\n * `update:modelValue` is transient — it fires on every drag tick, every arrow\n * tick and every applied edit, so a host can paint a live preview. `commit`\n * fires once per gesture boundary — drag release, Enter, or focus leaving the\n * field. One long drag is therefore one undo checkpoint rather than one per\n * pixel, and a gesture that ends where it started produces none at all.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself: the row's id, the id of\n * its hint or error line, `required`, `invalid`, `disabled`, `readonly` and the\n * name the value is submitted under all arrive through `useFieldControl()`, so\n * `<Field label=\"Rotation\" error=\"…\"><NumberField … /></Field>` needs no\n * attributes at the call site. Every prop below still wins over the row when it\n * is set, in both directions — which is why the three booleans default to\n * `undefined` rather than `false`.\n *\n * `readonly` and `disabled` are different states here for the same reason they\n * are on a text input, and a number is the case that makes it obvious: a rate,\n * a computed total or a locked coordinate is a value on show. It stays a Tab\n * stop, stays in the form's submitted data, and takes a lifted fill with the\n * number at full strength, one step short of the disabled state's drained fill,\n * muted number and slackened rim — and every path that would change it is\n * closed, the stepper included, because a control offering a gesture that does\n * nothing is worse than one that offers none.\n */\nconst props = withDefaults(\n defineProps<{\n /** The current value. Transient edits are reported before it changes. */\n modelValue?: number;\n /** Lower bound. Every path — typing, arrows, scrub — is clamped to it. */\n min?: number;\n /** Upper bound, clamped the same way. It wins over step alignment. */\n max?: number;\n /** The granularity of one arrow tick or one scrub step. Shift multiplies it by ten. */\n step?: number;\n /** A presentational suffix such as `px` or `deg`. It is never part of the value. */\n unit?: string;\n /** Unavailable: dims the field and refuses every edit path. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Shows the value without allowing an edit — typing, arrows, stepper and scrub all closed — while staying focusable and still submitted. Unset defers to a wrapping Field. */\n readonly?: boolean | undefined;\n /** Error state: paints the destructive border and ring, and sets `aria-invalid`. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /**\n * Names for everything this control says out loud, as any subset of\n * `NumberFieldLabels` — 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: the case it exists for is `roleDescription`, where a field\n * holding a rotation or a price is more useful named as that than as a\n * number field.\n */\n labels?: LabelOverrides<NumberFieldLabels>;\n }>(),\n {\n step: 1,\n // Not `false`, for any of the three. `false` is a caller saying \"this field\n // is fine / enabled / editable even though its row is not\"; absent is a\n // caller saying nothing, and only `undefined` survives to mean that past\n // Vue's absent-Boolean-casts-to-false rule — see TextField.vue, which\n // carries the full reasoning.\n disabled: undefined,\n readonly: undefined,\n invalid: undefined,\n },\n);\n\nconst emit = defineEmits<{\n /** Transient: every drag tick, arrow tick, or applied edit. Not a checkpoint. */\n \"update:modelValue\": [value: number];\n /** Committed: once per gesture boundary — drag release, Enter, or focus leaving. */\n commit: [value: number];\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(\"numberField\", NUMBER_FIELD_LABELS, () => props.labels);\n\n// The rendered root is a non-focusable `role=\"group\"` wrapper, so fallthrough\n// attributes that describe the *control* — `aria-labelledby`, `data-testid` —\n// belong on the spinbutton inside it, not on the group. `class` is the one\n// exception and goes the other way: a caller's sizing or layout class has to\n// land on the element that participates in the parent's layout, merged through\n// `cn()` so a caller's `w-24` actually beats the root's own `w-full` instead of\n// losing to whichever utility Tailwind happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\n\n/**\n * The scrub is the reason this control cannot be left to the platform.\n *\n * `<fieldset disabled>` makes the inner `<input>` and the two stepper buttons\n * inert on its own, which covers the keyboard and the two clicks — but the\n * drag-to-change gesture is a `pointerdown` on the **root**, and a `<div>` is\n * not a form control, so nothing native stops it. `onPointerDown` bails on\n * `field.disabled`, and that is exactly the value a disabled fieldset does not\n * reach. Without this, a field inside a disabled group looked unavailable,\n * refused the keyboard, and still changed its number under a drag.\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<{ $el?: Element }>(\"root\");\nconst groupDisabled = useAncestorDisabled(() => root.value?.$el);\n\n// `id` and `aria-describedby` reach this control as fallthrough attrs rather\n// than props, so they are read off `attrs` and handed in as this caller's own\n// values — a caller who sets either still beats the row, and the row's\n// description is merged into theirs rather than replacing it. `name` and\n// `required` are absent from this object because there is no prop to oppose\n// the row with; the row's answer stands unopposed, which is what is wanted.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: attrs[\"aria-describedby\"] as string | undefined,\n // The fieldset beats the prop in both directions, exactly as it does for the\n // `<input>` inside this very control.\n disabled: groupDisabled.value ? true : props.disabled,\n readonly: props.readonly,\n invalid: props.invalid,\n}));\n\n// `name` is the one resolved key that does not travel with the bag, and here\n// it is a defect rather than a nicety: the bag lands on the spinbutton, and\n// what the spinbutton holds is the *formatted* number — `1,234` for 1234. A\n// `name` on it would post the grouping separators along with the digits. Reka\n// submits the model value itself through a hidden input it renders from the\n// root's own `name`, so that is where the row's name goes. Everything else in\n// the bag — the id, the merged description, `aria-required`, `aria-invalid` —\n// belongs on the `role=\"spinbutton\"` node, which is that input.\nconst inputFieldAttrs = computed(() => {\n // Removed from a copy rather than listed positively, so a key added to the\n // resolved bag later reaches this input without anyone remembering to come\n // back here.\n const aria: Record<string, unknown> = { ...field.attrs };\n delete aria.name;\n return aria;\n});\n\n// `optional()` drops the absent bounds and an absent name rather than\n// forwarding `undefined`, which Reka would read as \"a bound that is undefined\"\n// rather than \"no bound\" — and, for the name, would have it render a hidden\n// form input for a field nothing is submitting.\nconst rootProps = computed(() =>\n optional({\n modelValue: lastValue.value,\n min: props.min,\n max: props.max,\n name: field.name,\n }),\n);\n\nfunction clampValue(value: number): number {\n const step = props.step || 1;\n const stepped = Math.round(value / step) * step;\n const withMin = props.min !== undefined ? Math.max(props.min, stepped) : stepped;\n return props.max !== undefined ? Math.min(props.max, withMin) : withMin;\n}\n\n// The single source of truth for \"the last value we know about\", updated the\n// moment it changes — never re-derived from `props.modelValue` at commit time,\n// because that prop only reflects a new value after the host's v-model\n// round-trip re-renders us, which is a tick later than the blur or keydown\n// being handled.\n//\n// It is also what the Reka root binds as its own `model-value`, rather than\n// `props.modelValue`. Given a defined model value Reka runs fully controlled:\n// its increment reads back through that prop, so a host that correctly\n// withholds transient echoes until commit would freeze every arrow tick at\n// start + 1 and freeze the readout at the gesture's start value. Feeding it\n// `lastValue` keeps the ticks accumulating and the readout live inside a\n// gesture, while the host still sees nothing but transient emits until the\n// boundary.\nconst lastValue = ref(props.modelValue);\n\n// The value the host last acknowledged. `commit` only fires when `lastValue`\n// differs from it, so overlapping boundaries (Enter then blur, drag release\n// then blur) and no-edit focus passes (Tab in, Tab out) produce no extra\n// checkpoints.\nconst lastCommittedValue = ref(props.modelValue);\n\n// True while an uncommitted edit is in flight. An external prop change\n// re-baselines `lastCommittedValue` only when nothing is pending — otherwise\n// the host echoing our own transient emits back down would make the pending\n// edit look already committed and swallow its `commit`.\nlet editPending = false;\n\nwatch(\n () => props.modelValue,\n (value) => {\n lastValue.value = value;\n if (!editPending) lastCommittedValue.value = value;\n },\n);\n\nfunction onUpdate(value: number | undefined) {\n // Reka clears its model to `undefined` when the text in the field does not\n // parse — an emptied field, most reachably. The contract here is a number,\n // so the field falls back to its own floor instead of forwarding a hole,\n // which is also what Reka itself does when a stepper is pressed on\n // unparseable text.\n const next = value ?? props.min ?? 0;\n editPending = true;\n lastValue.value = next;\n emit(\"update:modelValue\", next);\n}\n\nfunction commitLastValue() {\n // The boundary ends the edit session even when the value ended up back where\n // it started — a drag that returned to its origin still ends the gesture.\n editPending = false;\n if (lastValue.value === lastCommittedValue.value) return;\n lastCommittedValue.value = lastValue.value;\n emit(\"commit\", lastValue.value ?? props.min ?? 0);\n}\n\n// Shift = ×10. Reka's own arrow handling is bound on the input and ignores\n// `shiftKey`, so this intercepts in the capture phase — strictly before the\n// event reaches the input — and fully replaces the default ×1 tick for this\n// one case. Letting both run would double-step.\nfunction onKeydownCapture(event: KeyboardEvent) {\n // Read-only closes this path as firmly as disabled does. Reka's own arrow\n // handling already bails on `readonly`, and this override replaces it — so\n // without the guard Shift+Arrow would be the one way to edit a field that\n // says it cannot be edited.\n if (field.disabled || field.readonly) return;\n if (!event.shiftKey || (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\")) return;\n event.preventDefault();\n event.stopPropagation();\n const tick = (props.step || 1) * 10;\n const current = lastValue.value ?? props.min ?? 0;\n onUpdate(clampValue(event.key === \"ArrowUp\" ? current + tick : current - tick));\n}\n\n// Bubble phase, not capture: by the time this fires, the input's own Enter\n// handling has already applied the typed text, because bubbling visits the\n// target before its ancestors.\nfunction onKeydownCommit(event: KeyboardEvent) {\n if (event.key !== \"Enter\") return;\n commitLastValue();\n}\n\n// A held arrow key is one gesture, exactly like a pointer drag: every keydown,\n// including the operating system's auto-repeats, only ticks the transient\n// value, and the keyup ends the gesture and commits the summed delta once. A\n// single tap is a keydown plus a keyup, so it still commits once.\nfunction onKeyupCommit(event: KeyboardEvent) {\n if (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\") return;\n commitLastValue();\n}\n\n// `focusout` bubbles where `blur` does not, so this reliably runs after the\n// input's own blur handling — but it only commits once focus has actually left\n// the field, not when it shifts internally to the hover-revealed stepper.\nfunction onFocusOut(event: FocusEvent) {\n const root = event.currentTarget as HTMLElement;\n if (event.relatedTarget instanceof Node && root.contains(event.relatedTarget)) return;\n commitLastValue();\n}\n\n// Horizontal scrub. A plain click must still place the caret for typing, so\n// the gesture does not take over until the pointer has moved past a small\n// threshold.\nconst DRAG_THRESHOLD_PX = 3;\nconst PIXELS_PER_STEP = 4;\n\n// Set while a drag's window listeners are attached, so unmounting mid-drag\n// detaches them instead of leaking handlers onto `window`.\nlet removeDragListeners: (() => void) | undefined;\nonUnmounted(() => removeDragListeners?.());\n\nfunction onPointerDown(event: PointerEvent) {\n // A read-only field still places the caret and still selects text, which is\n // the whole point of it — it simply never starts a scrub.\n if (field.disabled || field.readonly || event.button !== 0) return;\n const startX = event.clientX;\n const startValue = lastValue.value ?? props.min ?? 0;\n const step = props.step || 1;\n let dragging = false;\n\n function onPointerMove(moveEvent: PointerEvent) {\n const deltaX = moveEvent.clientX - startX;\n if (!dragging) {\n if (Math.abs(deltaX) < DRAG_THRESHOLD_PX) return;\n dragging = true;\n window.getSelection()?.removeAllRanges();\n }\n moveEvent.preventDefault();\n const deltaSteps = Math.round(deltaX / PIXELS_PER_STEP);\n const next = clampValue(startValue + deltaSteps * step);\n if (next !== lastValue.value) onUpdate(next);\n }\n\n function onPointerUp() {\n removeDragListeners?.();\n if (dragging) commitLastValue();\n }\n\n // Bail-out (Escape mid-scrub, or a browser-issued pointercancel): the\n // gesture is discarded. Nothing commits, and one last transient update\n // carrying the start value lets the host restore the pre-drag preview.\n function abortDrag() {\n removeDragListeners?.();\n if (!dragging) return;\n if (lastValue.value !== startValue) onUpdate(startValue);\n editPending = false;\n }\n\n function onDragKeydown(keyEvent: KeyboardEvent) {\n if (keyEvent.key !== \"Escape\" || !dragging) return;\n keyEvent.preventDefault();\n keyEvent.stopPropagation();\n abortDrag();\n }\n\n window.addEventListener(\"pointermove\", onPointerMove);\n window.addEventListener(\"pointerup\", onPointerUp);\n window.addEventListener(\"pointercancel\", abortDrag);\n window.addEventListener(\"keydown\", onDragKeydown, true);\n removeDragListeners = () => {\n window.removeEventListener(\"pointermove\", onPointerMove);\n window.removeEventListener(\"pointerup\", onPointerUp);\n window.removeEventListener(\"pointercancel\", abortDrag);\n window.removeEventListener(\"keydown\", onDragKeydown, true);\n removeDragListeners = undefined;\n };\n}\n</script>\n\n<template>\n <NumberFieldRoot\n ref=\"root\"\n v-bind=\"rootProps\"\n :step=\"step\"\n :disabled=\"field.disabled\"\n :readonly=\"field.readonly\"\n :aria-disabled=\"field.disabled || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n 'group relative inline-flex h-9 w-full items-center rounded-md border border-input bg-background',\n 'transition-[color,background-color,box-shadow] duration-fast ease-out',\n // Rim-lit at rest; the ring blooms on focus.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // Three resting appearances, three channels — the same treatment\n // TextField carries at length. Read-only and disabled used to share one\n // fill and part only on the text colour, which is a distinction made in\n // hue alone; the fill and the border weight now part them twice more.\n //\n // Read-only keeps its focus ring and its full-strength number: a\n // read-only value is on show, not an unavailable control. The fill lifts\n // to `subtle`, the rim stays `input` because the field's reach has not\n // changed, and Reka's native `readonly` on the input carries the state\n // to assistive tech, so nothing here rests on colour.\n field.readonly && 'bg-subtle',\n // Unavailable is a *colour* and a *weight*, never an opacity.\n // `opacity-50` on this box faded the value inside it:\n // `--color-foreground` measures 14.09:1 on the resting fill and 2.99:1\n // once composited at half alpha, and the unit suffix beside it — muted,\n // and smaller — fell to 2.02:1. The fill drains a step past read-only\n // and the rim slackens to `border`; the input below takes the matching\n // text colour, which has to be declared there rather than inherited from\n // here because the input names `text-foreground` itself.\n field.disabled && 'border-border bg-muted',\n // Last of the rules that name a border colour: `cn()` resolves one by\n // whichever class it saw last, so a field that is both in error and\n // unavailable would otherwise lose its destructive rim to the disabled\n // one.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n 'data-[disabled]:cursor-not-allowed',\n attrs.class as string,\n )\n \"\n @update:model-value=\"onUpdate\"\n @keydown.capture=\"onKeydownCapture\"\n @keydown=\"onKeydownCommit\"\n @keyup=\"onKeyupCommit\"\n @focusout=\"onFocusOut\"\n @pointerdown=\"onPointerDown\"\n >\n <!-- `aria-roledescription` is written before the `v-bind` rather than after\n it, which is the opposite of the field bag below and deliberate: it is\n replacing Reka's own literal, not a caller's, so a caller who passes\n the attribute directly should still beat it. Reka's is merged inside\n its render function and Vue applies everything from here afterwards,\n so both orders defeat Reka and only this one leaves the caller a way\n past Loom. -->\n <NumberFieldInput\n :aria-roledescription=\"text.roleDescription\"\n v-bind=\"{ ...inputAttrs, ...inputFieldAttrs }\"\n :class=\"\n cn(\n 'tabular h-full w-full flex-1 rounded-md bg-transparent px-3 text-sm text-foreground outline-none',\n unit ? 'pr-9' : 'pr-3',\n // 4.68:1 against the drained fill the root takes, and two steps back\n // from the black an editable value keeps — the state stays plain to\n // see and the number stays plain to read. `cn()` is what makes the\n // pair work: it drops the `text-foreground` above rather than leaving\n // two colours for the stylesheet to order.\n field.disabled && 'cursor-not-allowed text-muted-foreground',\n // The scrub cursor is a promise the field makes about the gesture it\n // takes. Read-only takes no gesture, so it must not make the promise\n // — the caret cursor is the honest one for a value you may select\n // and copy but not change.\n !field.disabled && !field.readonly && 'cursor-ew-resize',\n )\n \"\n />\n <!-- The unit is presentation, so it is hidden from assistive technology\n and it clears out on hover to make room for the stepper. -->\n <span\n v-if=\"unit\"\n aria-hidden=\"true\"\n class=\"pointer-events-none absolute right-2 text-xs text-muted-foreground transition-opacity duration-fast group-hover:opacity-0\"\n >\n {{ unit }}\n </span>\n <!-- The stepper is hidden until hover: a column of these is read as a\n column of numbers, and a permanent pair of chevrons on every row turns\n that into a column of controls.\n\n It is not rendered at all on a read-only field. Reka's own\n `handleChangingValue` bails on `readonly` *after* focusing the input,\n so a rendered stepper would be two named buttons that a pointer or a\n screen reader can still reach and press, and that answer by moving\n focus and changing nothing. Absent is the honest state. -->\n <div\n v-if=\"!field.readonly\"\n class=\"absolute right-1 flex flex-col opacity-0 transition-opacity duration-fast group-hover:opacity-100\"\n >\n <!-- Both `:aria-label`s replace one Reka writes in English inside its own\n render function — \"Increase\" and \"Decrease\". They reach the DOM node\n as fallthrough attributes, which Vue merges last, so each is a\n replacement rather than a second name. Removing one does not fall\n back to nothing; it falls back to Reka's English.\n\n The two keep `data-[disabled]:opacity-50` where the box around them\n gave it up, and the difference is that each holds a chevron and\n nothing else. Fading a glyph costs a reader nothing; their names are\n in `aria-label`, not in dimmed text. -->\n <NumberFieldIncrement\n :aria-label=\"text.increment\"\n class=\"flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground [transition:transform_var(--duration-fast)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-out),color_var(--duration-fast)_var(--ease-out)] hover:bg-subtle hover:text-foreground active:scale-press focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50\"\n >\n <!-- Stroke lives on the 24 grid and scales with size, so the inherited\n 1.5 would render 0.75 device pixels at this 12px box and all but\n disappear. 2.5 restores the optical weight. -->\n <ChevronUp class=\"h-3 w-3\" :stroke-width=\"2.5\" />\n </NumberFieldIncrement>\n <NumberFieldDecrement\n :aria-label=\"text.decrement\"\n class=\"flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground [transition:transform_var(--duration-fast)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-out),color_var(--duration-fast)_var(--ease-out)] hover:bg-subtle hover:text-foreground active:scale-press focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50\"\n >\n <ChevronDown class=\"h-3 w-3\" :stroke-width=\"2.5\" />\n </NumberFieldDecrement>\n </div>\n </NumberFieldRoot>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"NumberField.js","names":[],"sources":["../../../../../packages/primitives/number-field/src/NumberField.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { NumberFieldLabels } from \"@ecoma-io/loom-labels\";\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 NUMBER_FIELD_LABELS: NumberFieldLabels = {\n increment: \"Increase value\",\n decrement: \"Decrease value\",\n roleDescription: \"Number field\",\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onUnmounted, ref, useTemplateRef, watch } from \"vue\";\nimport {\n NumberFieldRoot,\n NumberFieldInput,\n NumberFieldIncrement,\n NumberFieldDecrement,\n} from \"reka-ui\";\nimport { ChevronUp, ChevronDown } from \"@lucide/vue\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useAncestorDisabled } from \"@ecoma-io/loom-labels\";\nimport { useFieldControl } from \"@ecoma-io/loom-labels\";\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\nimport { optional } from \"@ecoma-io/loom-core\";\n\n/**\n * NumberField — a constrained number input you can also *scrub*: drag\n * horizontally anywhere on the field and the value runs with the pointer, the\n * gesture professional editors trained everyone to expect on a numeric row.\n *\n * Built on Reka UI's NumberField, which supplies the spinbutton semantics and\n * the typed-value parsing. Two things are added here because Reka has no\n * equivalent: the scrub-drag gesture, and a Shift multiplier on the arrow\n * keys.\n *\n * The event contract is what makes it usable behind an undo stack.\n * `update:modelValue` is transient — it fires on every drag tick, every arrow\n * tick and every applied edit, so a host can paint a live preview. `commit`\n * fires once per gesture boundary — drag release, Enter, or focus leaving the\n * field. One long drag is therefore one undo checkpoint rather than one per\n * pixel, and a gesture that ends where it started produces none at all.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself: the row's id, the id of\n * its hint or error line, `required`, `invalid`, `disabled`, `readonly` and the\n * name the value is submitted under all arrive through `useFieldControl()`, so\n * `<Field label=\"Rotation\" error=\"…\"><NumberField … /></Field>` needs no\n * attributes at the call site. Every prop below still wins over the row when it\n * is set, in both directions — which is why the three booleans default to\n * `undefined` rather than `false`.\n *\n * `readonly` and `disabled` are different states here for the same reason they\n * are on a text input, and a number is the case that makes it obvious: a rate,\n * a computed total or a locked coordinate is a value on show. It stays a Tab\n * stop, stays in the form's submitted data, and takes a lifted fill with the\n * number at full strength, one step short of the disabled state's drained fill,\n * muted number and slackened rim — and every path that would change it is\n * closed, the stepper included, because a control offering a gesture that does\n * nothing is worse than one that offers none.\n */\nconst props = withDefaults(\n defineProps<{\n /** The current value. Transient edits are reported before it changes. */\n modelValue?: number;\n /** Lower bound. Every path — typing, arrows, scrub — is clamped to it. */\n min?: number;\n /** Upper bound, clamped the same way. It wins over step alignment. */\n max?: number;\n /** The granularity of one arrow tick or one scrub step. Shift multiplies it by ten. */\n step?: number;\n /** A presentational suffix such as `px` or `deg`. It is never part of the value. */\n unit?: string;\n /** Unavailable: dims the field and refuses every edit path. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Shows the value without allowing an edit — typing, arrows, stepper and scrub all closed — while staying focusable and still submitted. Unset defers to a wrapping Field. */\n readonly?: boolean | undefined;\n /** Error state: paints the destructive border and ring, and sets `aria-invalid`. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /**\n * Names for everything this control says out loud, as any subset of\n * `NumberFieldLabels` — 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: the case it exists for is `roleDescription`, where a field\n * holding a rotation or a price is more useful named as that than as a\n * number field.\n */\n labels?: LabelOverrides<NumberFieldLabels>;\n }>(),\n {\n step: 1,\n // Not `false`, for any of the three. `false` is a caller saying \"this field\n // is fine / enabled / editable even though its row is not\"; absent is a\n // caller saying nothing, and only `undefined` survives to mean that past\n // Vue's absent-Boolean-casts-to-false rule — see TextField.vue, which\n // carries the full reasoning.\n disabled: undefined,\n readonly: undefined,\n invalid: undefined,\n },\n);\n\nconst emit = defineEmits<{\n /** Transient: every drag tick, arrow tick, or applied edit. Not a checkpoint. */\n \"update:modelValue\": [value: number];\n /** Committed: once per gesture boundary — drag release, Enter, or focus leaving. */\n commit: [value: number];\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(\"numberField\", NUMBER_FIELD_LABELS, () => props.labels);\n\n// The rendered root is a non-focusable `role=\"group\"` wrapper, so fallthrough\n// attributes that describe the *control* — `aria-labelledby`, `data-testid` —\n// belong on the spinbutton inside it, not on the group. `class` is the one\n// exception and goes the other way: a caller's sizing or layout class has to\n// land on the element that participates in the parent's layout, merged through\n// `cn()` so a caller's `w-24` actually beats the root's own `w-full` instead of\n// losing to whichever utility Tailwind happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\n\n/**\n * The scrub is the reason this control cannot be left to the platform.\n *\n * `<fieldset disabled>` makes the inner `<input>` and the two stepper buttons\n * inert on its own, which covers the keyboard and the two clicks — but the\n * drag-to-change gesture is a `pointerdown` on the **root**, and a `<div>` is\n * not a form control, so nothing native stops it. `onPointerDown` bails on\n * `field.disabled`, and that is exactly the value a disabled fieldset does not\n * reach. Without this, a field inside a disabled group looked unavailable,\n * refused the keyboard, and still changed its number under a drag.\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<{ $el?: Element }>(\"root\");\nconst groupDisabled = useAncestorDisabled(() => root.value?.$el);\n\n// `id` and `aria-describedby` reach this control as fallthrough attrs rather\n// than props, so they are read off `attrs` and handed in as this caller's own\n// values — a caller who sets either still beats the row, and the row's\n// description is merged into theirs rather than replacing it. `name` and\n// `required` are absent from this object because there is no prop to oppose\n// the row with; the row's answer stands unopposed, which is what is wanted.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: attrs[\"aria-describedby\"] as string | undefined,\n // The fieldset beats the prop in both directions, exactly as it does for the\n // `<input>` inside this very control.\n disabled: groupDisabled.value ? true : props.disabled,\n readonly: props.readonly,\n invalid: props.invalid,\n}));\n\n// `name` is the one resolved key that does not travel with the bag, and here\n// it is a defect rather than a nicety: the bag lands on the spinbutton, and\n// what the spinbutton holds is the *formatted* number — `1,234` for 1234. A\n// `name` on it would post the grouping separators along with the digits. Reka\n// submits the model value itself through a hidden input it renders from the\n// root's own `name`, so that is where the row's name goes. Everything else in\n// the bag — the id, the merged description, `aria-required`, `aria-invalid` —\n// belongs on the `role=\"spinbutton\"` node, which is that input.\nconst inputFieldAttrs = computed(() => {\n // Removed from a copy rather than listed positively, so a key added to the\n // resolved bag later reaches this input without anyone remembering to come\n // back here.\n const aria: Record<string, unknown> = { ...field.attrs };\n delete aria.name;\n return aria;\n});\n\n// `optional()` drops the absent bounds and an absent name rather than\n// forwarding `undefined`, which Reka would read as \"a bound that is undefined\"\n// rather than \"no bound\" — and, for the name, would have it render a hidden\n// form input for a field nothing is submitting.\nconst rootProps = computed(() =>\n optional({\n modelValue: lastValue.value,\n min: props.min,\n max: props.max,\n name: field.name,\n }),\n);\n\nfunction clampValue(value: number): number {\n const step = props.step || 1;\n const stepped = Math.round(value / step) * step;\n const withMin = props.min !== undefined ? Math.max(props.min, stepped) : stepped;\n return props.max !== undefined ? Math.min(props.max, withMin) : withMin;\n}\n\n// The single source of truth for \"the last value we know about\", updated the\n// moment it changes — never re-derived from `props.modelValue` at commit time,\n// because that prop only reflects a new value after the host's v-model\n// round-trip re-renders us, which is a tick later than the blur or keydown\n// being handled.\n//\n// It is also what the Reka root binds as its own `model-value`, rather than\n// `props.modelValue`. Given a defined model value Reka runs fully controlled:\n// its increment reads back through that prop, so a host that correctly\n// withholds transient echoes until commit would freeze every arrow tick at\n// start + 1 and freeze the readout at the gesture's start value. Feeding it\n// `lastValue` keeps the ticks accumulating and the readout live inside a\n// gesture, while the host still sees nothing but transient emits until the\n// boundary.\nconst lastValue = ref(props.modelValue);\n\n// The value the host last acknowledged. `commit` only fires when `lastValue`\n// differs from it, so overlapping boundaries (Enter then blur, drag release\n// then blur) and no-edit focus passes (Tab in, Tab out) produce no extra\n// checkpoints.\nconst lastCommittedValue = ref(props.modelValue);\n\n// True while an uncommitted edit is in flight. An external prop change\n// re-baselines `lastCommittedValue` only when nothing is pending — otherwise\n// the host echoing our own transient emits back down would make the pending\n// edit look already committed and swallow its `commit`.\nlet editPending = false;\n\nwatch(\n () => props.modelValue,\n (value) => {\n lastValue.value = value;\n if (!editPending) lastCommittedValue.value = value;\n },\n);\n\nfunction onUpdate(value: number | undefined) {\n // Reka clears its model to `undefined` when the text in the field does not\n // parse — an emptied field, most reachably. The contract here is a number,\n // so the field falls back to its own floor instead of forwarding a hole,\n // which is also what Reka itself does when a stepper is pressed on\n // unparseable text.\n const next = value ?? props.min ?? 0;\n editPending = true;\n lastValue.value = next;\n emit(\"update:modelValue\", next);\n}\n\nfunction commitLastValue() {\n // The boundary ends the edit session even when the value ended up back where\n // it started — a drag that returned to its origin still ends the gesture.\n editPending = false;\n if (lastValue.value === lastCommittedValue.value) return;\n lastCommittedValue.value = lastValue.value;\n emit(\"commit\", lastValue.value ?? props.min ?? 0);\n}\n\n// Shift = ×10. Reka's own arrow handling is bound on the input and ignores\n// `shiftKey`, so this intercepts in the capture phase — strictly before the\n// event reaches the input — and fully replaces the default ×1 tick for this\n// one case. Letting both run would double-step.\nfunction onKeydownCapture(event: KeyboardEvent) {\n // Read-only closes this path as firmly as disabled does. Reka's own arrow\n // handling already bails on `readonly`, and this override replaces it — so\n // without the guard Shift+Arrow would be the one way to edit a field that\n // says it cannot be edited.\n if (field.disabled || field.readonly) return;\n if (!event.shiftKey || (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\")) return;\n event.preventDefault();\n event.stopPropagation();\n const tick = (props.step || 1) * 10;\n const current = lastValue.value ?? props.min ?? 0;\n onUpdate(clampValue(event.key === \"ArrowUp\" ? current + tick : current - tick));\n}\n\n// Bubble phase, not capture: by the time this fires, the input's own Enter\n// handling has already applied the typed text, because bubbling visits the\n// target before its ancestors.\nfunction onKeydownCommit(event: KeyboardEvent) {\n if (event.key !== \"Enter\") return;\n commitLastValue();\n}\n\n// A held arrow key is one gesture, exactly like a pointer drag: every keydown,\n// including the operating system's auto-repeats, only ticks the transient\n// value, and the keyup ends the gesture and commits the summed delta once. A\n// single tap is a keydown plus a keyup, so it still commits once.\nfunction onKeyupCommit(event: KeyboardEvent) {\n if (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\") return;\n commitLastValue();\n}\n\n// `focusout` bubbles where `blur` does not, so this reliably runs after the\n// input's own blur handling — but it only commits once focus has actually left\n// the field, not when it shifts internally to the hover-revealed stepper.\nfunction onFocusOut(event: FocusEvent) {\n const root = event.currentTarget as HTMLElement;\n if (event.relatedTarget instanceof Node && root.contains(event.relatedTarget)) return;\n commitLastValue();\n}\n\n// Horizontal scrub. A plain click must still place the caret for typing, so\n// the gesture does not take over until the pointer has moved past a small\n// threshold.\nconst DRAG_THRESHOLD_PX = 3;\nconst PIXELS_PER_STEP = 4;\n\n// Set while a drag's window listeners are attached, so unmounting mid-drag\n// detaches them instead of leaking handlers onto `window`.\nlet removeDragListeners: (() => void) | undefined;\nonUnmounted(() => removeDragListeners?.());\n\nfunction onPointerDown(event: PointerEvent) {\n // A read-only field still places the caret and still selects text, which is\n // the whole point of it — it simply never starts a scrub.\n if (field.disabled || field.readonly || event.button !== 0) return;\n // Disarm before arming: a second concurrent pointerdown used to overwrite\n // `removeDragListeners` instead of running it, so the first pointer's\n // release committed this half-finished scrub and its move handler stayed on\n // the window ticking values with no pointer anywhere near the field.\n removeDragListeners?.();\n const startX = event.clientX;\n const startValue = lastValue.value ?? props.min ?? 0;\n const step = props.step || 1;\n let dragging = false;\n\n function onPointerMove(moveEvent: PointerEvent) {\n const deltaX = moveEvent.clientX - startX;\n if (!dragging) {\n if (Math.abs(deltaX) < DRAG_THRESHOLD_PX) return;\n dragging = true;\n window.getSelection()?.removeAllRanges();\n }\n moveEvent.preventDefault();\n const deltaSteps = Math.round(deltaX / PIXELS_PER_STEP);\n const next = clampValue(startValue + deltaSteps * step);\n if (next !== lastValue.value) onUpdate(next);\n }\n\n function onPointerUp() {\n removeDragListeners?.();\n if (dragging) commitLastValue();\n }\n\n // Bail-out (Escape mid-scrub, or a browser-issued pointercancel): the\n // gesture is discarded. Nothing commits, and one last transient update\n // carrying the start value lets the host restore the pre-drag preview.\n function abortDrag() {\n removeDragListeners?.();\n if (!dragging) return;\n if (lastValue.value !== startValue) onUpdate(startValue);\n editPending = false;\n }\n\n function onDragKeydown(keyEvent: KeyboardEvent) {\n if (keyEvent.key !== \"Escape\" || !dragging) return;\n keyEvent.preventDefault();\n keyEvent.stopPropagation();\n abortDrag();\n }\n\n window.addEventListener(\"pointermove\", onPointerMove);\n window.addEventListener(\"pointerup\", onPointerUp);\n window.addEventListener(\"pointercancel\", abortDrag);\n window.addEventListener(\"keydown\", onDragKeydown, true);\n removeDragListeners = () => {\n window.removeEventListener(\"pointermove\", onPointerMove);\n window.removeEventListener(\"pointerup\", onPointerUp);\n window.removeEventListener(\"pointercancel\", abortDrag);\n window.removeEventListener(\"keydown\", onDragKeydown, true);\n removeDragListeners = undefined;\n };\n}\n</script>\n\n<template>\n <NumberFieldRoot\n ref=\"root\"\n v-bind=\"rootProps\"\n :step=\"step\"\n :disabled=\"field.disabled\"\n :readonly=\"field.readonly\"\n :aria-disabled=\"field.disabled || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n 'group relative inline-flex h-9 w-full items-center rounded-md border border-input bg-background',\n 'transition-[color,background-color,box-shadow] duration-fast ease-out',\n // Rim-lit at rest; the ring blooms on focus.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // Three resting appearances, three channels — the same treatment\n // TextField carries at length. Read-only and disabled used to share one\n // fill and part only on the text colour, which is a distinction made in\n // hue alone; the fill and the border weight now part them twice more.\n //\n // Read-only keeps its focus ring and its full-strength number: a\n // read-only value is on show, not an unavailable control. The fill lifts\n // to `subtle`, the rim stays `input` because the field's reach has not\n // changed, and Reka's native `readonly` on the input carries the state\n // to assistive tech, so nothing here rests on colour.\n field.readonly && 'bg-subtle',\n // Unavailable is a *colour* and a *weight*, never an opacity.\n // `opacity-50` on this box faded the value inside it:\n // `--color-foreground` measures 14.09:1 on the resting fill and 2.99:1\n // once composited at half alpha, and the unit suffix beside it — muted,\n // and smaller — fell to 2.02:1. The fill drains a step past read-only\n // and the rim slackens to `border`; the input below takes the matching\n // text colour, which has to be declared there rather than inherited from\n // here because the input names `text-foreground` itself.\n field.disabled && 'border-border bg-muted',\n // Last of the rules that name a border colour: `cn()` resolves one by\n // whichever class it saw last, so a field that is both in error and\n // unavailable would otherwise lose its destructive rim to the disabled\n // one.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n 'data-[disabled]:cursor-not-allowed',\n attrs.class as string,\n )\n \"\n @update:model-value=\"onUpdate\"\n @keydown.capture=\"onKeydownCapture\"\n @keydown=\"onKeydownCommit\"\n @keyup=\"onKeyupCommit\"\n @focusout=\"onFocusOut\"\n @pointerdown=\"onPointerDown\"\n >\n <!-- `aria-roledescription` is written before the `v-bind` rather than after\n it, which is the opposite of the field bag below and deliberate: it is\n replacing Reka's own literal, not a caller's, so a caller who passes\n the attribute directly should still beat it. Reka's is merged inside\n its render function and Vue applies everything from here afterwards,\n so both orders defeat Reka and only this one leaves the caller a way\n past Loom. -->\n <NumberFieldInput\n :aria-roledescription=\"text.roleDescription\"\n v-bind=\"{ ...inputAttrs, ...inputFieldAttrs }\"\n :class=\"\n cn(\n 'tabular h-full w-full flex-1 rounded-md bg-transparent px-3 text-sm text-foreground outline-none',\n unit ? 'pr-9' : 'pr-3',\n // 4.68:1 against the drained fill the root takes, and two steps back\n // from the black an editable value keeps — the state stays plain to\n // see and the number stays plain to read. `cn()` is what makes the\n // pair work: it drops the `text-foreground` above rather than leaving\n // two colours for the stylesheet to order.\n field.disabled && 'cursor-not-allowed text-muted-foreground',\n // The scrub cursor is a promise the field makes about the gesture it\n // takes. Read-only takes no gesture, so it must not make the promise\n // — the caret cursor is the honest one for a value you may select\n // and copy but not change.\n !field.disabled && !field.readonly && 'cursor-ew-resize',\n )\n \"\n />\n <!-- The unit is presentation, so it is hidden from assistive technology\n and it clears out on hover to make room for the stepper. -->\n <span\n v-if=\"unit\"\n aria-hidden=\"true\"\n class=\"pointer-events-none absolute right-2 text-xs text-muted-foreground transition-opacity duration-fast group-hover:opacity-0\"\n >\n {{ unit }}\n </span>\n <!-- The stepper is hidden until hover: a column of these is read as a\n column of numbers, and a permanent pair of chevrons on every row turns\n that into a column of controls.\n\n It is not rendered at all on a read-only field. Reka's own\n `handleChangingValue` bails on `readonly` *after* focusing the input,\n so a rendered stepper would be two named buttons that a pointer or a\n screen reader can still reach and press, and that answer by moving\n focus and changing nothing. Absent is the honest state. -->\n <div\n v-if=\"!field.readonly\"\n class=\"absolute right-1 flex flex-col opacity-0 transition-opacity duration-fast group-hover:opacity-100\"\n >\n <!-- Both `:aria-label`s replace one Reka writes in English inside its own\n render function — \"Increase\" and \"Decrease\". They reach the DOM node\n as fallthrough attributes, which Vue merges last, so each is a\n replacement rather than a second name. Removing one does not fall\n back to nothing; it falls back to Reka's English.\n\n The two keep `data-[disabled]:opacity-50` where the box around them\n gave it up, and the difference is that each holds a chevron and\n nothing else. Fading a glyph costs a reader nothing; their names are\n in `aria-label`, not in dimmed text. -->\n <NumberFieldIncrement\n :aria-label=\"text.increment\"\n class=\"flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground [transition:transform_var(--duration-fast)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-out),color_var(--duration-fast)_var(--ease-out)] hover:bg-subtle hover:text-foreground active:scale-press focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50\"\n >\n <!-- Stroke lives on the 24 grid and scales with size, so the inherited\n 1.5 would render 0.75 device pixels at this 12px box and all but\n disappear. 2.5 restores the optical weight. -->\n <ChevronUp class=\"h-3 w-3\" :stroke-width=\"2.5\" />\n </NumberFieldIncrement>\n <NumberFieldDecrement\n :aria-label=\"text.decrement\"\n class=\"flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground [transition:transform_var(--duration-fast)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-out),color_var(--duration-fast)_var(--ease-out)] hover:bg-subtle hover:text-foreground active:scale-press focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50\"\n >\n <ChevronDown class=\"h-3 w-3\" :stroke-width=\"2.5\" />\n </NumberFieldDecrement>\n </div>\n </NumberFieldRoot>\n</template>\n"],"mappings":""}
@@ -1,5 +1,5 @@
1
- import { type LabelOverrides } from "@ecoma-io/loom-labels";
2
- import type { NumberFieldLabels } from "@ecoma-io/loom-labels";
1
+ import { type LabelOverrides } from "../../../labels/src/index";
2
+ import type { NumberFieldLabels } from "../../../labels/src/index";
3
3
  /**
4
4
 
5
5
  /**
@@ -146,6 +146,7 @@ var NumberField_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
146
146
  onUnmounted(() => removeDragListeners?.());
147
147
  function onPointerDown(event) {
148
148
  if (field.disabled || field.readonly || event.button !== 0) return;
149
+ removeDragListeners?.();
149
150
  const startX = event.clientX;
150
151
  const startValue = lastValue.value ?? props.min ?? 0;
151
152
  const step = props.step || 1;
@@ -1 +1 @@
1
- {"version":3,"file":"NumberField.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../../../packages/primitives/number-field/src/NumberField.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { NumberFieldLabels } from \"@ecoma-io/loom-labels\";\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 NUMBER_FIELD_LABELS: NumberFieldLabels = {\n increment: \"Increase value\",\n decrement: \"Decrease value\",\n roleDescription: \"Number field\",\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onUnmounted, ref, useTemplateRef, watch } from \"vue\";\nimport {\n NumberFieldRoot,\n NumberFieldInput,\n NumberFieldIncrement,\n NumberFieldDecrement,\n} from \"reka-ui\";\nimport { ChevronUp, ChevronDown } from \"@lucide/vue\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useAncestorDisabled } from \"@ecoma-io/loom-labels\";\nimport { useFieldControl } from \"@ecoma-io/loom-labels\";\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\nimport { optional } from \"@ecoma-io/loom-core\";\n\n/**\n * NumberField — a constrained number input you can also *scrub*: drag\n * horizontally anywhere on the field and the value runs with the pointer, the\n * gesture professional editors trained everyone to expect on a numeric row.\n *\n * Built on Reka UI's NumberField, which supplies the spinbutton semantics and\n * the typed-value parsing. Two things are added here because Reka has no\n * equivalent: the scrub-drag gesture, and a Shift multiplier on the arrow\n * keys.\n *\n * The event contract is what makes it usable behind an undo stack.\n * `update:modelValue` is transient — it fires on every drag tick, every arrow\n * tick and every applied edit, so a host can paint a live preview. `commit`\n * fires once per gesture boundary — drag release, Enter, or focus leaving the\n * field. One long drag is therefore one undo checkpoint rather than one per\n * pixel, and a gesture that ends where it started produces none at all.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself: the row's id, the id of\n * its hint or error line, `required`, `invalid`, `disabled`, `readonly` and the\n * name the value is submitted under all arrive through `useFieldControl()`, so\n * `<Field label=\"Rotation\" error=\"…\"><NumberField … /></Field>` needs no\n * attributes at the call site. Every prop below still wins over the row when it\n * is set, in both directions — which is why the three booleans default to\n * `undefined` rather than `false`.\n *\n * `readonly` and `disabled` are different states here for the same reason they\n * are on a text input, and a number is the case that makes it obvious: a rate,\n * a computed total or a locked coordinate is a value on show. It stays a Tab\n * stop, stays in the form's submitted data, and takes a lifted fill with the\n * number at full strength, one step short of the disabled state's drained fill,\n * muted number and slackened rim — and every path that would change it is\n * closed, the stepper included, because a control offering a gesture that does\n * nothing is worse than one that offers none.\n */\nconst props = withDefaults(\n defineProps<{\n /** The current value. Transient edits are reported before it changes. */\n modelValue?: number;\n /** Lower bound. Every path — typing, arrows, scrub — is clamped to it. */\n min?: number;\n /** Upper bound, clamped the same way. It wins over step alignment. */\n max?: number;\n /** The granularity of one arrow tick or one scrub step. Shift multiplies it by ten. */\n step?: number;\n /** A presentational suffix such as `px` or `deg`. It is never part of the value. */\n unit?: string;\n /** Unavailable: dims the field and refuses every edit path. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Shows the value without allowing an edit — typing, arrows, stepper and scrub all closed — while staying focusable and still submitted. Unset defers to a wrapping Field. */\n readonly?: boolean | undefined;\n /** Error state: paints the destructive border and ring, and sets `aria-invalid`. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /**\n * Names for everything this control says out loud, as any subset of\n * `NumberFieldLabels` — 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: the case it exists for is `roleDescription`, where a field\n * holding a rotation or a price is more useful named as that than as a\n * number field.\n */\n labels?: LabelOverrides<NumberFieldLabels>;\n }>(),\n {\n step: 1,\n // Not `false`, for any of the three. `false` is a caller saying \"this field\n // is fine / enabled / editable even though its row is not\"; absent is a\n // caller saying nothing, and only `undefined` survives to mean that past\n // Vue's absent-Boolean-casts-to-false rule — see TextField.vue, which\n // carries the full reasoning.\n disabled: undefined,\n readonly: undefined,\n invalid: undefined,\n },\n);\n\nconst emit = defineEmits<{\n /** Transient: every drag tick, arrow tick, or applied edit. Not a checkpoint. */\n \"update:modelValue\": [value: number];\n /** Committed: once per gesture boundary — drag release, Enter, or focus leaving. */\n commit: [value: number];\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(\"numberField\", NUMBER_FIELD_LABELS, () => props.labels);\n\n// The rendered root is a non-focusable `role=\"group\"` wrapper, so fallthrough\n// attributes that describe the *control* — `aria-labelledby`, `data-testid` —\n// belong on the spinbutton inside it, not on the group. `class` is the one\n// exception and goes the other way: a caller's sizing or layout class has to\n// land on the element that participates in the parent's layout, merged through\n// `cn()` so a caller's `w-24` actually beats the root's own `w-full` instead of\n// losing to whichever utility Tailwind happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\n\n/**\n * The scrub is the reason this control cannot be left to the platform.\n *\n * `<fieldset disabled>` makes the inner `<input>` and the two stepper buttons\n * inert on its own, which covers the keyboard and the two clicks — but the\n * drag-to-change gesture is a `pointerdown` on the **root**, and a `<div>` is\n * not a form control, so nothing native stops it. `onPointerDown` bails on\n * `field.disabled`, and that is exactly the value a disabled fieldset does not\n * reach. Without this, a field inside a disabled group looked unavailable,\n * refused the keyboard, and still changed its number under a drag.\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<{ $el?: Element }>(\"root\");\nconst groupDisabled = useAncestorDisabled(() => root.value?.$el);\n\n// `id` and `aria-describedby` reach this control as fallthrough attrs rather\n// than props, so they are read off `attrs` and handed in as this caller's own\n// values — a caller who sets either still beats the row, and the row's\n// description is merged into theirs rather than replacing it. `name` and\n// `required` are absent from this object because there is no prop to oppose\n// the row with; the row's answer stands unopposed, which is what is wanted.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: attrs[\"aria-describedby\"] as string | undefined,\n // The fieldset beats the prop in both directions, exactly as it does for the\n // `<input>` inside this very control.\n disabled: groupDisabled.value ? true : props.disabled,\n readonly: props.readonly,\n invalid: props.invalid,\n}));\n\n// `name` is the one resolved key that does not travel with the bag, and here\n// it is a defect rather than a nicety: the bag lands on the spinbutton, and\n// what the spinbutton holds is the *formatted* number — `1,234` for 1234. A\n// `name` on it would post the grouping separators along with the digits. Reka\n// submits the model value itself through a hidden input it renders from the\n// root's own `name`, so that is where the row's name goes. Everything else in\n// the bag — the id, the merged description, `aria-required`, `aria-invalid` —\n// belongs on the `role=\"spinbutton\"` node, which is that input.\nconst inputFieldAttrs = computed(() => {\n // Removed from a copy rather than listed positively, so a key added to the\n // resolved bag later reaches this input without anyone remembering to come\n // back here.\n const aria: Record<string, unknown> = { ...field.attrs };\n delete aria.name;\n return aria;\n});\n\n// `optional()` drops the absent bounds and an absent name rather than\n// forwarding `undefined`, which Reka would read as \"a bound that is undefined\"\n// rather than \"no bound\" — and, for the name, would have it render a hidden\n// form input for a field nothing is submitting.\nconst rootProps = computed(() =>\n optional({\n modelValue: lastValue.value,\n min: props.min,\n max: props.max,\n name: field.name,\n }),\n);\n\nfunction clampValue(value: number): number {\n const step = props.step || 1;\n const stepped = Math.round(value / step) * step;\n const withMin = props.min !== undefined ? Math.max(props.min, stepped) : stepped;\n return props.max !== undefined ? Math.min(props.max, withMin) : withMin;\n}\n\n// The single source of truth for \"the last value we know about\", updated the\n// moment it changes — never re-derived from `props.modelValue` at commit time,\n// because that prop only reflects a new value after the host's v-model\n// round-trip re-renders us, which is a tick later than the blur or keydown\n// being handled.\n//\n// It is also what the Reka root binds as its own `model-value`, rather than\n// `props.modelValue`. Given a defined model value Reka runs fully controlled:\n// its increment reads back through that prop, so a host that correctly\n// withholds transient echoes until commit would freeze every arrow tick at\n// start + 1 and freeze the readout at the gesture's start value. Feeding it\n// `lastValue` keeps the ticks accumulating and the readout live inside a\n// gesture, while the host still sees nothing but transient emits until the\n// boundary.\nconst lastValue = ref(props.modelValue);\n\n// The value the host last acknowledged. `commit` only fires when `lastValue`\n// differs from it, so overlapping boundaries (Enter then blur, drag release\n// then blur) and no-edit focus passes (Tab in, Tab out) produce no extra\n// checkpoints.\nconst lastCommittedValue = ref(props.modelValue);\n\n// True while an uncommitted edit is in flight. An external prop change\n// re-baselines `lastCommittedValue` only when nothing is pending — otherwise\n// the host echoing our own transient emits back down would make the pending\n// edit look already committed and swallow its `commit`.\nlet editPending = false;\n\nwatch(\n () => props.modelValue,\n (value) => {\n lastValue.value = value;\n if (!editPending) lastCommittedValue.value = value;\n },\n);\n\nfunction onUpdate(value: number | undefined) {\n // Reka clears its model to `undefined` when the text in the field does not\n // parse — an emptied field, most reachably. The contract here is a number,\n // so the field falls back to its own floor instead of forwarding a hole,\n // which is also what Reka itself does when a stepper is pressed on\n // unparseable text.\n const next = value ?? props.min ?? 0;\n editPending = true;\n lastValue.value = next;\n emit(\"update:modelValue\", next);\n}\n\nfunction commitLastValue() {\n // The boundary ends the edit session even when the value ended up back where\n // it started — a drag that returned to its origin still ends the gesture.\n editPending = false;\n if (lastValue.value === lastCommittedValue.value) return;\n lastCommittedValue.value = lastValue.value;\n emit(\"commit\", lastValue.value ?? props.min ?? 0);\n}\n\n// Shift = ×10. Reka's own arrow handling is bound on the input and ignores\n// `shiftKey`, so this intercepts in the capture phase — strictly before the\n// event reaches the input — and fully replaces the default ×1 tick for this\n// one case. Letting both run would double-step.\nfunction onKeydownCapture(event: KeyboardEvent) {\n // Read-only closes this path as firmly as disabled does. Reka's own arrow\n // handling already bails on `readonly`, and this override replaces it — so\n // without the guard Shift+Arrow would be the one way to edit a field that\n // says it cannot be edited.\n if (field.disabled || field.readonly) return;\n if (!event.shiftKey || (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\")) return;\n event.preventDefault();\n event.stopPropagation();\n const tick = (props.step || 1) * 10;\n const current = lastValue.value ?? props.min ?? 0;\n onUpdate(clampValue(event.key === \"ArrowUp\" ? current + tick : current - tick));\n}\n\n// Bubble phase, not capture: by the time this fires, the input's own Enter\n// handling has already applied the typed text, because bubbling visits the\n// target before its ancestors.\nfunction onKeydownCommit(event: KeyboardEvent) {\n if (event.key !== \"Enter\") return;\n commitLastValue();\n}\n\n// A held arrow key is one gesture, exactly like a pointer drag: every keydown,\n// including the operating system's auto-repeats, only ticks the transient\n// value, and the keyup ends the gesture and commits the summed delta once. A\n// single tap is a keydown plus a keyup, so it still commits once.\nfunction onKeyupCommit(event: KeyboardEvent) {\n if (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\") return;\n commitLastValue();\n}\n\n// `focusout` bubbles where `blur` does not, so this reliably runs after the\n// input's own blur handling — but it only commits once focus has actually left\n// the field, not when it shifts internally to the hover-revealed stepper.\nfunction onFocusOut(event: FocusEvent) {\n const root = event.currentTarget as HTMLElement;\n if (event.relatedTarget instanceof Node && root.contains(event.relatedTarget)) return;\n commitLastValue();\n}\n\n// Horizontal scrub. A plain click must still place the caret for typing, so\n// the gesture does not take over until the pointer has moved past a small\n// threshold.\nconst DRAG_THRESHOLD_PX = 3;\nconst PIXELS_PER_STEP = 4;\n\n// Set while a drag's window listeners are attached, so unmounting mid-drag\n// detaches them instead of leaking handlers onto `window`.\nlet removeDragListeners: (() => void) | undefined;\nonUnmounted(() => removeDragListeners?.());\n\nfunction onPointerDown(event: PointerEvent) {\n // A read-only field still places the caret and still selects text, which is\n // the whole point of it — it simply never starts a scrub.\n if (field.disabled || field.readonly || event.button !== 0) return;\n const startX = event.clientX;\n const startValue = lastValue.value ?? props.min ?? 0;\n const step = props.step || 1;\n let dragging = false;\n\n function onPointerMove(moveEvent: PointerEvent) {\n const deltaX = moveEvent.clientX - startX;\n if (!dragging) {\n if (Math.abs(deltaX) < DRAG_THRESHOLD_PX) return;\n dragging = true;\n window.getSelection()?.removeAllRanges();\n }\n moveEvent.preventDefault();\n const deltaSteps = Math.round(deltaX / PIXELS_PER_STEP);\n const next = clampValue(startValue + deltaSteps * step);\n if (next !== lastValue.value) onUpdate(next);\n }\n\n function onPointerUp() {\n removeDragListeners?.();\n if (dragging) commitLastValue();\n }\n\n // Bail-out (Escape mid-scrub, or a browser-issued pointercancel): the\n // gesture is discarded. Nothing commits, and one last transient update\n // carrying the start value lets the host restore the pre-drag preview.\n function abortDrag() {\n removeDragListeners?.();\n if (!dragging) return;\n if (lastValue.value !== startValue) onUpdate(startValue);\n editPending = false;\n }\n\n function onDragKeydown(keyEvent: KeyboardEvent) {\n if (keyEvent.key !== \"Escape\" || !dragging) return;\n keyEvent.preventDefault();\n keyEvent.stopPropagation();\n abortDrag();\n }\n\n window.addEventListener(\"pointermove\", onPointerMove);\n window.addEventListener(\"pointerup\", onPointerUp);\n window.addEventListener(\"pointercancel\", abortDrag);\n window.addEventListener(\"keydown\", onDragKeydown, true);\n removeDragListeners = () => {\n window.removeEventListener(\"pointermove\", onPointerMove);\n window.removeEventListener(\"pointerup\", onPointerUp);\n window.removeEventListener(\"pointercancel\", abortDrag);\n window.removeEventListener(\"keydown\", onDragKeydown, true);\n removeDragListeners = undefined;\n };\n}\n</script>\n\n<template>\n <NumberFieldRoot\n ref=\"root\"\n v-bind=\"rootProps\"\n :step=\"step\"\n :disabled=\"field.disabled\"\n :readonly=\"field.readonly\"\n :aria-disabled=\"field.disabled || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n 'group relative inline-flex h-9 w-full items-center rounded-md border border-input bg-background',\n 'transition-[color,background-color,box-shadow] duration-fast ease-out',\n // Rim-lit at rest; the ring blooms on focus.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // Three resting appearances, three channels — the same treatment\n // TextField carries at length. Read-only and disabled used to share one\n // fill and part only on the text colour, which is a distinction made in\n // hue alone; the fill and the border weight now part them twice more.\n //\n // Read-only keeps its focus ring and its full-strength number: a\n // read-only value is on show, not an unavailable control. The fill lifts\n // to `subtle`, the rim stays `input` because the field's reach has not\n // changed, and Reka's native `readonly` on the input carries the state\n // to assistive tech, so nothing here rests on colour.\n field.readonly && 'bg-subtle',\n // Unavailable is a *colour* and a *weight*, never an opacity.\n // `opacity-50` on this box faded the value inside it:\n // `--color-foreground` measures 14.09:1 on the resting fill and 2.99:1\n // once composited at half alpha, and the unit suffix beside it — muted,\n // and smaller — fell to 2.02:1. The fill drains a step past read-only\n // and the rim slackens to `border`; the input below takes the matching\n // text colour, which has to be declared there rather than inherited from\n // here because the input names `text-foreground` itself.\n field.disabled && 'border-border bg-muted',\n // Last of the rules that name a border colour: `cn()` resolves one by\n // whichever class it saw last, so a field that is both in error and\n // unavailable would otherwise lose its destructive rim to the disabled\n // one.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n 'data-[disabled]:cursor-not-allowed',\n attrs.class as string,\n )\n \"\n @update:model-value=\"onUpdate\"\n @keydown.capture=\"onKeydownCapture\"\n @keydown=\"onKeydownCommit\"\n @keyup=\"onKeyupCommit\"\n @focusout=\"onFocusOut\"\n @pointerdown=\"onPointerDown\"\n >\n <!-- `aria-roledescription` is written before the `v-bind` rather than after\n it, which is the opposite of the field bag below and deliberate: it is\n replacing Reka's own literal, not a caller's, so a caller who passes\n the attribute directly should still beat it. Reka's is merged inside\n its render function and Vue applies everything from here afterwards,\n so both orders defeat Reka and only this one leaves the caller a way\n past Loom. -->\n <NumberFieldInput\n :aria-roledescription=\"text.roleDescription\"\n v-bind=\"{ ...inputAttrs, ...inputFieldAttrs }\"\n :class=\"\n cn(\n 'tabular h-full w-full flex-1 rounded-md bg-transparent px-3 text-sm text-foreground outline-none',\n unit ? 'pr-9' : 'pr-3',\n // 4.68:1 against the drained fill the root takes, and two steps back\n // from the black an editable value keeps — the state stays plain to\n // see and the number stays plain to read. `cn()` is what makes the\n // pair work: it drops the `text-foreground` above rather than leaving\n // two colours for the stylesheet to order.\n field.disabled && 'cursor-not-allowed text-muted-foreground',\n // The scrub cursor is a promise the field makes about the gesture it\n // takes. Read-only takes no gesture, so it must not make the promise\n // — the caret cursor is the honest one for a value you may select\n // and copy but not change.\n !field.disabled && !field.readonly && 'cursor-ew-resize',\n )\n \"\n />\n <!-- The unit is presentation, so it is hidden from assistive technology\n and it clears out on hover to make room for the stepper. -->\n <span\n v-if=\"unit\"\n aria-hidden=\"true\"\n class=\"pointer-events-none absolute right-2 text-xs text-muted-foreground transition-opacity duration-fast group-hover:opacity-0\"\n >\n {{ unit }}\n </span>\n <!-- The stepper is hidden until hover: a column of these is read as a\n column of numbers, and a permanent pair of chevrons on every row turns\n that into a column of controls.\n\n It is not rendered at all on a read-only field. Reka's own\n `handleChangingValue` bails on `readonly` *after* focusing the input,\n so a rendered stepper would be two named buttons that a pointer or a\n screen reader can still reach and press, and that answer by moving\n focus and changing nothing. Absent is the honest state. -->\n <div\n v-if=\"!field.readonly\"\n class=\"absolute right-1 flex flex-col opacity-0 transition-opacity duration-fast group-hover:opacity-100\"\n >\n <!-- Both `:aria-label`s replace one Reka writes in English inside its own\n render function — \"Increase\" and \"Decrease\". They reach the DOM node\n as fallthrough attributes, which Vue merges last, so each is a\n replacement rather than a second name. Removing one does not fall\n back to nothing; it falls back to Reka's English.\n\n The two keep `data-[disabled]:opacity-50` where the box around them\n gave it up, and the difference is that each holds a chevron and\n nothing else. Fading a glyph costs a reader nothing; their names are\n in `aria-label`, not in dimmed text. -->\n <NumberFieldIncrement\n :aria-label=\"text.increment\"\n class=\"flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground [transition:transform_var(--duration-fast)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-out),color_var(--duration-fast)_var(--ease-out)] hover:bg-subtle hover:text-foreground active:scale-press focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50\"\n >\n <!-- Stroke lives on the 24 grid and scales with size, so the inherited\n 1.5 would render 0.75 device pixels at this 12px box and all but\n disappear. 2.5 restores the optical weight. -->\n <ChevronUp class=\"h-3 w-3\" :stroke-width=\"2.5\" />\n </NumberFieldIncrement>\n <NumberFieldDecrement\n :aria-label=\"text.decrement\"\n class=\"flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground [transition:transform_var(--duration-fast)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-out),color_var(--duration-fast)_var(--ease-out)] hover:bg-subtle hover:text-foreground active:scale-press focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50\"\n >\n <ChevronDown class=\"h-3 w-3\" :stroke-width=\"2.5\" />\n </NumberFieldDecrement>\n </div>\n </NumberFieldRoot>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"NumberField.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../../../packages/primitives/number-field/src/NumberField.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { NumberFieldLabels } from \"@ecoma-io/loom-labels\";\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 NUMBER_FIELD_LABELS: NumberFieldLabels = {\n increment: \"Increase value\",\n decrement: \"Decrease value\",\n roleDescription: \"Number field\",\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onUnmounted, ref, useTemplateRef, watch } from \"vue\";\nimport {\n NumberFieldRoot,\n NumberFieldInput,\n NumberFieldIncrement,\n NumberFieldDecrement,\n} from \"reka-ui\";\nimport { ChevronUp, ChevronDown } from \"@lucide/vue\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useAncestorDisabled } from \"@ecoma-io/loom-labels\";\nimport { useFieldControl } from \"@ecoma-io/loom-labels\";\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\nimport { optional } from \"@ecoma-io/loom-core\";\n\n/**\n * NumberField — a constrained number input you can also *scrub*: drag\n * horizontally anywhere on the field and the value runs with the pointer, the\n * gesture professional editors trained everyone to expect on a numeric row.\n *\n * Built on Reka UI's NumberField, which supplies the spinbutton semantics and\n * the typed-value parsing. Two things are added here because Reka has no\n * equivalent: the scrub-drag gesture, and a Shift multiplier on the arrow\n * keys.\n *\n * The event contract is what makes it usable behind an undo stack.\n * `update:modelValue` is transient — it fires on every drag tick, every arrow\n * tick and every applied edit, so a host can paint a live preview. `commit`\n * fires once per gesture boundary — drag release, Enter, or focus leaving the\n * field. One long drag is therefore one undo checkpoint rather than one per\n * pixel, and a gesture that ends where it started produces none at all.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself: the row's id, the id of\n * its hint or error line, `required`, `invalid`, `disabled`, `readonly` and the\n * name the value is submitted under all arrive through `useFieldControl()`, so\n * `<Field label=\"Rotation\" error=\"…\"><NumberField … /></Field>` needs no\n * attributes at the call site. Every prop below still wins over the row when it\n * is set, in both directions — which is why the three booleans default to\n * `undefined` rather than `false`.\n *\n * `readonly` and `disabled` are different states here for the same reason they\n * are on a text input, and a number is the case that makes it obvious: a rate,\n * a computed total or a locked coordinate is a value on show. It stays a Tab\n * stop, stays in the form's submitted data, and takes a lifted fill with the\n * number at full strength, one step short of the disabled state's drained fill,\n * muted number and slackened rim — and every path that would change it is\n * closed, the stepper included, because a control offering a gesture that does\n * nothing is worse than one that offers none.\n */\nconst props = withDefaults(\n defineProps<{\n /** The current value. Transient edits are reported before it changes. */\n modelValue?: number;\n /** Lower bound. Every path — typing, arrows, scrub — is clamped to it. */\n min?: number;\n /** Upper bound, clamped the same way. It wins over step alignment. */\n max?: number;\n /** The granularity of one arrow tick or one scrub step. Shift multiplies it by ten. */\n step?: number;\n /** A presentational suffix such as `px` or `deg`. It is never part of the value. */\n unit?: string;\n /** Unavailable: dims the field and refuses every edit path. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Shows the value without allowing an edit — typing, arrows, stepper and scrub all closed — while staying focusable and still submitted. Unset defers to a wrapping Field. */\n readonly?: boolean | undefined;\n /** Error state: paints the destructive border and ring, and sets `aria-invalid`. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /**\n * Names for everything this control says out loud, as any subset of\n * `NumberFieldLabels` — 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: the case it exists for is `roleDescription`, where a field\n * holding a rotation or a price is more useful named as that than as a\n * number field.\n */\n labels?: LabelOverrides<NumberFieldLabels>;\n }>(),\n {\n step: 1,\n // Not `false`, for any of the three. `false` is a caller saying \"this field\n // is fine / enabled / editable even though its row is not\"; absent is a\n // caller saying nothing, and only `undefined` survives to mean that past\n // Vue's absent-Boolean-casts-to-false rule — see TextField.vue, which\n // carries the full reasoning.\n disabled: undefined,\n readonly: undefined,\n invalid: undefined,\n },\n);\n\nconst emit = defineEmits<{\n /** Transient: every drag tick, arrow tick, or applied edit. Not a checkpoint. */\n \"update:modelValue\": [value: number];\n /** Committed: once per gesture boundary — drag release, Enter, or focus leaving. */\n commit: [value: number];\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(\"numberField\", NUMBER_FIELD_LABELS, () => props.labels);\n\n// The rendered root is a non-focusable `role=\"group\"` wrapper, so fallthrough\n// attributes that describe the *control* — `aria-labelledby`, `data-testid` —\n// belong on the spinbutton inside it, not on the group. `class` is the one\n// exception and goes the other way: a caller's sizing or layout class has to\n// land on the element that participates in the parent's layout, merged through\n// `cn()` so a caller's `w-24` actually beats the root's own `w-full` instead of\n// losing to whichever utility Tailwind happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\n\n/**\n * The scrub is the reason this control cannot be left to the platform.\n *\n * `<fieldset disabled>` makes the inner `<input>` and the two stepper buttons\n * inert on its own, which covers the keyboard and the two clicks — but the\n * drag-to-change gesture is a `pointerdown` on the **root**, and a `<div>` is\n * not a form control, so nothing native stops it. `onPointerDown` bails on\n * `field.disabled`, and that is exactly the value a disabled fieldset does not\n * reach. Without this, a field inside a disabled group looked unavailable,\n * refused the keyboard, and still changed its number under a drag.\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<{ $el?: Element }>(\"root\");\nconst groupDisabled = useAncestorDisabled(() => root.value?.$el);\n\n// `id` and `aria-describedby` reach this control as fallthrough attrs rather\n// than props, so they are read off `attrs` and handed in as this caller's own\n// values — a caller who sets either still beats the row, and the row's\n// description is merged into theirs rather than replacing it. `name` and\n// `required` are absent from this object because there is no prop to oppose\n// the row with; the row's answer stands unopposed, which is what is wanted.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: attrs[\"aria-describedby\"] as string | undefined,\n // The fieldset beats the prop in both directions, exactly as it does for the\n // `<input>` inside this very control.\n disabled: groupDisabled.value ? true : props.disabled,\n readonly: props.readonly,\n invalid: props.invalid,\n}));\n\n// `name` is the one resolved key that does not travel with the bag, and here\n// it is a defect rather than a nicety: the bag lands on the spinbutton, and\n// what the spinbutton holds is the *formatted* number — `1,234` for 1234. A\n// `name` on it would post the grouping separators along with the digits. Reka\n// submits the model value itself through a hidden input it renders from the\n// root's own `name`, so that is where the row's name goes. Everything else in\n// the bag — the id, the merged description, `aria-required`, `aria-invalid` —\n// belongs on the `role=\"spinbutton\"` node, which is that input.\nconst inputFieldAttrs = computed(() => {\n // Removed from a copy rather than listed positively, so a key added to the\n // resolved bag later reaches this input without anyone remembering to come\n // back here.\n const aria: Record<string, unknown> = { ...field.attrs };\n delete aria.name;\n return aria;\n});\n\n// `optional()` drops the absent bounds and an absent name rather than\n// forwarding `undefined`, which Reka would read as \"a bound that is undefined\"\n// rather than \"no bound\" — and, for the name, would have it render a hidden\n// form input for a field nothing is submitting.\nconst rootProps = computed(() =>\n optional({\n modelValue: lastValue.value,\n min: props.min,\n max: props.max,\n name: field.name,\n }),\n);\n\nfunction clampValue(value: number): number {\n const step = props.step || 1;\n const stepped = Math.round(value / step) * step;\n const withMin = props.min !== undefined ? Math.max(props.min, stepped) : stepped;\n return props.max !== undefined ? Math.min(props.max, withMin) : withMin;\n}\n\n// The single source of truth for \"the last value we know about\", updated the\n// moment it changes — never re-derived from `props.modelValue` at commit time,\n// because that prop only reflects a new value after the host's v-model\n// round-trip re-renders us, which is a tick later than the blur or keydown\n// being handled.\n//\n// It is also what the Reka root binds as its own `model-value`, rather than\n// `props.modelValue`. Given a defined model value Reka runs fully controlled:\n// its increment reads back through that prop, so a host that correctly\n// withholds transient echoes until commit would freeze every arrow tick at\n// start + 1 and freeze the readout at the gesture's start value. Feeding it\n// `lastValue` keeps the ticks accumulating and the readout live inside a\n// gesture, while the host still sees nothing but transient emits until the\n// boundary.\nconst lastValue = ref(props.modelValue);\n\n// The value the host last acknowledged. `commit` only fires when `lastValue`\n// differs from it, so overlapping boundaries (Enter then blur, drag release\n// then blur) and no-edit focus passes (Tab in, Tab out) produce no extra\n// checkpoints.\nconst lastCommittedValue = ref(props.modelValue);\n\n// True while an uncommitted edit is in flight. An external prop change\n// re-baselines `lastCommittedValue` only when nothing is pending — otherwise\n// the host echoing our own transient emits back down would make the pending\n// edit look already committed and swallow its `commit`.\nlet editPending = false;\n\nwatch(\n () => props.modelValue,\n (value) => {\n lastValue.value = value;\n if (!editPending) lastCommittedValue.value = value;\n },\n);\n\nfunction onUpdate(value: number | undefined) {\n // Reka clears its model to `undefined` when the text in the field does not\n // parse — an emptied field, most reachably. The contract here is a number,\n // so the field falls back to its own floor instead of forwarding a hole,\n // which is also what Reka itself does when a stepper is pressed on\n // unparseable text.\n const next = value ?? props.min ?? 0;\n editPending = true;\n lastValue.value = next;\n emit(\"update:modelValue\", next);\n}\n\nfunction commitLastValue() {\n // The boundary ends the edit session even when the value ended up back where\n // it started — a drag that returned to its origin still ends the gesture.\n editPending = false;\n if (lastValue.value === lastCommittedValue.value) return;\n lastCommittedValue.value = lastValue.value;\n emit(\"commit\", lastValue.value ?? props.min ?? 0);\n}\n\n// Shift = ×10. Reka's own arrow handling is bound on the input and ignores\n// `shiftKey`, so this intercepts in the capture phase — strictly before the\n// event reaches the input — and fully replaces the default ×1 tick for this\n// one case. Letting both run would double-step.\nfunction onKeydownCapture(event: KeyboardEvent) {\n // Read-only closes this path as firmly as disabled does. Reka's own arrow\n // handling already bails on `readonly`, and this override replaces it — so\n // without the guard Shift+Arrow would be the one way to edit a field that\n // says it cannot be edited.\n if (field.disabled || field.readonly) return;\n if (!event.shiftKey || (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\")) return;\n event.preventDefault();\n event.stopPropagation();\n const tick = (props.step || 1) * 10;\n const current = lastValue.value ?? props.min ?? 0;\n onUpdate(clampValue(event.key === \"ArrowUp\" ? current + tick : current - tick));\n}\n\n// Bubble phase, not capture: by the time this fires, the input's own Enter\n// handling has already applied the typed text, because bubbling visits the\n// target before its ancestors.\nfunction onKeydownCommit(event: KeyboardEvent) {\n if (event.key !== \"Enter\") return;\n commitLastValue();\n}\n\n// A held arrow key is one gesture, exactly like a pointer drag: every keydown,\n// including the operating system's auto-repeats, only ticks the transient\n// value, and the keyup ends the gesture and commits the summed delta once. A\n// single tap is a keydown plus a keyup, so it still commits once.\nfunction onKeyupCommit(event: KeyboardEvent) {\n if (event.key !== \"ArrowUp\" && event.key !== \"ArrowDown\") return;\n commitLastValue();\n}\n\n// `focusout` bubbles where `blur` does not, so this reliably runs after the\n// input's own blur handling — but it only commits once focus has actually left\n// the field, not when it shifts internally to the hover-revealed stepper.\nfunction onFocusOut(event: FocusEvent) {\n const root = event.currentTarget as HTMLElement;\n if (event.relatedTarget instanceof Node && root.contains(event.relatedTarget)) return;\n commitLastValue();\n}\n\n// Horizontal scrub. A plain click must still place the caret for typing, so\n// the gesture does not take over until the pointer has moved past a small\n// threshold.\nconst DRAG_THRESHOLD_PX = 3;\nconst PIXELS_PER_STEP = 4;\n\n// Set while a drag's window listeners are attached, so unmounting mid-drag\n// detaches them instead of leaking handlers onto `window`.\nlet removeDragListeners: (() => void) | undefined;\nonUnmounted(() => removeDragListeners?.());\n\nfunction onPointerDown(event: PointerEvent) {\n // A read-only field still places the caret and still selects text, which is\n // the whole point of it — it simply never starts a scrub.\n if (field.disabled || field.readonly || event.button !== 0) return;\n // Disarm before arming: a second concurrent pointerdown used to overwrite\n // `removeDragListeners` instead of running it, so the first pointer's\n // release committed this half-finished scrub and its move handler stayed on\n // the window ticking values with no pointer anywhere near the field.\n removeDragListeners?.();\n const startX = event.clientX;\n const startValue = lastValue.value ?? props.min ?? 0;\n const step = props.step || 1;\n let dragging = false;\n\n function onPointerMove(moveEvent: PointerEvent) {\n const deltaX = moveEvent.clientX - startX;\n if (!dragging) {\n if (Math.abs(deltaX) < DRAG_THRESHOLD_PX) return;\n dragging = true;\n window.getSelection()?.removeAllRanges();\n }\n moveEvent.preventDefault();\n const deltaSteps = Math.round(deltaX / PIXELS_PER_STEP);\n const next = clampValue(startValue + deltaSteps * step);\n if (next !== lastValue.value) onUpdate(next);\n }\n\n function onPointerUp() {\n removeDragListeners?.();\n if (dragging) commitLastValue();\n }\n\n // Bail-out (Escape mid-scrub, or a browser-issued pointercancel): the\n // gesture is discarded. Nothing commits, and one last transient update\n // carrying the start value lets the host restore the pre-drag preview.\n function abortDrag() {\n removeDragListeners?.();\n if (!dragging) return;\n if (lastValue.value !== startValue) onUpdate(startValue);\n editPending = false;\n }\n\n function onDragKeydown(keyEvent: KeyboardEvent) {\n if (keyEvent.key !== \"Escape\" || !dragging) return;\n keyEvent.preventDefault();\n keyEvent.stopPropagation();\n abortDrag();\n }\n\n window.addEventListener(\"pointermove\", onPointerMove);\n window.addEventListener(\"pointerup\", onPointerUp);\n window.addEventListener(\"pointercancel\", abortDrag);\n window.addEventListener(\"keydown\", onDragKeydown, true);\n removeDragListeners = () => {\n window.removeEventListener(\"pointermove\", onPointerMove);\n window.removeEventListener(\"pointerup\", onPointerUp);\n window.removeEventListener(\"pointercancel\", abortDrag);\n window.removeEventListener(\"keydown\", onDragKeydown, true);\n removeDragListeners = undefined;\n };\n}\n</script>\n\n<template>\n <NumberFieldRoot\n ref=\"root\"\n v-bind=\"rootProps\"\n :step=\"step\"\n :disabled=\"field.disabled\"\n :readonly=\"field.readonly\"\n :aria-disabled=\"field.disabled || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n 'group relative inline-flex h-9 w-full items-center rounded-md border border-input bg-background',\n 'transition-[color,background-color,box-shadow] duration-fast ease-out',\n // Rim-lit at rest; the ring blooms on focus.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // Three resting appearances, three channels — the same treatment\n // TextField carries at length. Read-only and disabled used to share one\n // fill and part only on the text colour, which is a distinction made in\n // hue alone; the fill and the border weight now part them twice more.\n //\n // Read-only keeps its focus ring and its full-strength number: a\n // read-only value is on show, not an unavailable control. The fill lifts\n // to `subtle`, the rim stays `input` because the field's reach has not\n // changed, and Reka's native `readonly` on the input carries the state\n // to assistive tech, so nothing here rests on colour.\n field.readonly && 'bg-subtle',\n // Unavailable is a *colour* and a *weight*, never an opacity.\n // `opacity-50` on this box faded the value inside it:\n // `--color-foreground` measures 14.09:1 on the resting fill and 2.99:1\n // once composited at half alpha, and the unit suffix beside it — muted,\n // and smaller — fell to 2.02:1. The fill drains a step past read-only\n // and the rim slackens to `border`; the input below takes the matching\n // text colour, which has to be declared there rather than inherited from\n // here because the input names `text-foreground` itself.\n field.disabled && 'border-border bg-muted',\n // Last of the rules that name a border colour: `cn()` resolves one by\n // whichever class it saw last, so a field that is both in error and\n // unavailable would otherwise lose its destructive rim to the disabled\n // one.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n 'data-[disabled]:cursor-not-allowed',\n attrs.class as string,\n )\n \"\n @update:model-value=\"onUpdate\"\n @keydown.capture=\"onKeydownCapture\"\n @keydown=\"onKeydownCommit\"\n @keyup=\"onKeyupCommit\"\n @focusout=\"onFocusOut\"\n @pointerdown=\"onPointerDown\"\n >\n <!-- `aria-roledescription` is written before the `v-bind` rather than after\n it, which is the opposite of the field bag below and deliberate: it is\n replacing Reka's own literal, not a caller's, so a caller who passes\n the attribute directly should still beat it. Reka's is merged inside\n its render function and Vue applies everything from here afterwards,\n so both orders defeat Reka and only this one leaves the caller a way\n past Loom. -->\n <NumberFieldInput\n :aria-roledescription=\"text.roleDescription\"\n v-bind=\"{ ...inputAttrs, ...inputFieldAttrs }\"\n :class=\"\n cn(\n 'tabular h-full w-full flex-1 rounded-md bg-transparent px-3 text-sm text-foreground outline-none',\n unit ? 'pr-9' : 'pr-3',\n // 4.68:1 against the drained fill the root takes, and two steps back\n // from the black an editable value keeps — the state stays plain to\n // see and the number stays plain to read. `cn()` is what makes the\n // pair work: it drops the `text-foreground` above rather than leaving\n // two colours for the stylesheet to order.\n field.disabled && 'cursor-not-allowed text-muted-foreground',\n // The scrub cursor is a promise the field makes about the gesture it\n // takes. Read-only takes no gesture, so it must not make the promise\n // — the caret cursor is the honest one for a value you may select\n // and copy but not change.\n !field.disabled && !field.readonly && 'cursor-ew-resize',\n )\n \"\n />\n <!-- The unit is presentation, so it is hidden from assistive technology\n and it clears out on hover to make room for the stepper. -->\n <span\n v-if=\"unit\"\n aria-hidden=\"true\"\n class=\"pointer-events-none absolute right-2 text-xs text-muted-foreground transition-opacity duration-fast group-hover:opacity-0\"\n >\n {{ unit }}\n </span>\n <!-- The stepper is hidden until hover: a column of these is read as a\n column of numbers, and a permanent pair of chevrons on every row turns\n that into a column of controls.\n\n It is not rendered at all on a read-only field. Reka's own\n `handleChangingValue` bails on `readonly` *after* focusing the input,\n so a rendered stepper would be two named buttons that a pointer or a\n screen reader can still reach and press, and that answer by moving\n focus and changing nothing. Absent is the honest state. -->\n <div\n v-if=\"!field.readonly\"\n class=\"absolute right-1 flex flex-col opacity-0 transition-opacity duration-fast group-hover:opacity-100\"\n >\n <!-- Both `:aria-label`s replace one Reka writes in English inside its own\n render function — \"Increase\" and \"Decrease\". They reach the DOM node\n as fallthrough attributes, which Vue merges last, so each is a\n replacement rather than a second name. Removing one does not fall\n back to nothing; it falls back to Reka's English.\n\n The two keep `data-[disabled]:opacity-50` where the box around them\n gave it up, and the difference is that each holds a chevron and\n nothing else. Fading a glyph costs a reader nothing; their names are\n in `aria-label`, not in dimmed text. -->\n <NumberFieldIncrement\n :aria-label=\"text.increment\"\n class=\"flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground [transition:transform_var(--duration-fast)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-out),color_var(--duration-fast)_var(--ease-out)] hover:bg-subtle hover:text-foreground active:scale-press focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50\"\n >\n <!-- Stroke lives on the 24 grid and scales with size, so the inherited\n 1.5 would render 0.75 device pixels at this 12px box and all but\n disappear. 2.5 restores the optical weight. -->\n <ChevronUp class=\"h-3 w-3\" :stroke-width=\"2.5\" />\n </NumberFieldIncrement>\n <NumberFieldDecrement\n :aria-label=\"text.decrement\"\n class=\"flex min-h-6 min-w-6 items-center justify-center rounded-sm text-muted-foreground [transition:transform_var(--duration-fast)_var(--ease-spring),background-color_var(--duration-fast)_var(--ease-out),color_var(--duration-fast)_var(--ease-out)] hover:bg-subtle hover:text-foreground active:scale-press focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring data-[disabled]:pointer-events-none data-[disabled]:opacity-50\"\n >\n <ChevronDown class=\"h-3 w-3\" :stroke-width=\"2.5\" />\n </NumberFieldDecrement>\n </div>\n </NumberFieldRoot>\n</template>\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  export { default } from "./NumberField.vue";
2
2
  export { NUMBER_FIELD_LABELS } from "./NumberField.vue";
3
- export type { NumberFieldLabels } from "@ecoma-io/loom-labels";
3
+ export type { NumberFieldLabels } from "../../../labels/src/index";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
- import { type LabelOverrides } from "@ecoma-io/loom-labels";
2
- import type { OtpInputLabels, OtpInputType } from "@ecoma-io/loom-labels";
1
+ import { type LabelOverrides } from "../../../labels/src/index";
2
+ import type { OtpInputLabels, OtpInputType } from "../../../labels/src/index";
3
3
  /**
4
4
  * Loom's English, co-located with the component so it tree-shakes with it, and
5
5
  * exported so a host can build a partial vocabulary against the real thing.
@@ -1,4 +1,4 @@
1
1
  export { default } from "./OtpInput.vue";
2
2
  export { OTP_INPUT_LABELS } from "./OtpInput.vue";
3
- export type { OtpInputLabels, OtpInputType } from "@ecoma-io/loom-labels";
3
+ export type { OtpInputLabels, OtpInputType } from "../../../labels/src/index";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Pagination.js","names":[],"sources":["../../../../../packages/primitives/pagination/src/Pagination.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { PaginationLabels } from \"@ecoma-io/loom-labels\";\nimport { cva } from \"class-variance-authority\";\n\n/**\n\n/**\n * Loom's English, co-located with the component so it tree-shakes with it: a\n * consumer who never imports Pagination ships none of these bytes.\n *\n * Exported so a host can build a partial vocabulary against the real thing\n * rather than a transcription of it.\n */\nexport const PAGINATION_LABELS: PaginationLabels = {\n nav: \"Pagination\",\n first: \"First page\",\n previous: \"Previous page\",\n next: \"Next page\",\n last: \"Last page\",\n page: ({ page }) => `Page ${String(page)}`,\n position: ({ page, pageCount }) => `Page ${String(page)} of ${String(pageCount)}`,\n ellipsis: \"More pages\",\n};\n\n/**\n * How much of the paging apparatus is rendered. All three drive the same\n * model and carry the same semantics — they differ only in how much room the\n * navigation is worth on the surface it sits on.\n *\n * • `full` — the numbered row with ellipses, plus first/prev/next/last.\n * For a table where jumping to a known page is a real task.\n * • `compact` — prev, \"Page 3 of 12\", next. The position stays legible\n * without spending a whole row on it.\n * • `simple` — prev and next alone, for a feed nobody navigates by index.\n */\nexport type PaginationVariant = \"full\" | \"compact\" | \"simple\";\n\nexport const paginationVariants = cva(\"inline-flex items-center\", {\n // `satisfies` keeps the two halves honest: the union above is the name a\n // consumer imports and the documentation prints, the map below is the\n // classes. Adding a member to one and not the other stops compiling.\n variants: {\n variant: {\n // The numbered row is a run of same-size squares and reads as one strip\n // at gap-1; the compact form is a sentence between two controls and\n // needs the wider gap to stop reading as a third button.\n full: \"gap-1\",\n compact: \"gap-2\",\n simple: \"gap-1\",\n } satisfies Record<PaginationVariant, string>,\n },\n defaultVariants: { variant: \"full\" },\n});\n</script>\n\n<script setup lang=\"ts\">\nimport { nextTick } from \"vue\";\nimport {\n PaginationRoot,\n PaginationList,\n PaginationListItem,\n PaginationFirst,\n PaginationPrev,\n PaginationNext,\n PaginationLast,\n PaginationEllipsis,\n} from \"reka-ui\";\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from \"@lucide/vue\";\nimport { buttonVariants } from \"@ecoma-io/loom-button\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { optional } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\n\n/**\n * Pagination — moving through a paged result set, one page at a time or by\n * jumping to a numbered one.\n *\n * It renders **buttons, never links**. Loom cannot know whether a consumer's\n * pages are addressable URLs, and an `<a>` with no `href` is not a link at\n * all — it is a div that lies to a screen reader and drops out of the tab\n * order. A product whose pages are real, crawlable URLs wants its own\n * `<a href>` row instead of this component; nothing here can be made to\n * produce one.\n *\n * Reach for a different control when the list is not paged: an infinite feed\n * wants a \"load more\" button, and a long list a reader scans rather than\n * indexes wants a virtualised scroller.\n *\n * Built on Reka UI's Pagination, which owns the arithmetic — the page count,\n * the sliding window of numbers around the current page, and where the\n * ellipses fall. That window is fiddly enough at the boundaries that\n * recomputing it here would only be a second, subtly different answer.\n */\nconst props = withDefaults(\n defineProps<{\n /**\n * The current page, 1-based. Leave it unset to let the component keep its\n * own page and simply report changes.\n */\n page?: number;\n /** How many items exist in total, across every page. */\n total: number;\n /** How many items one page holds. With `total`, this is what fixes the page count. */\n itemsPerPage?: number;\n /** How much of the control renders: the numbered row, a position readout, or arrows alone. */\n variant?: PaginationVariant;\n /** How many numbers sit either side of the current page, in the `full` variant. */\n siblingCount?: number;\n /** Pins page 1 and the last page into the numbered row, with ellipses standing in for the gap. */\n showEdges?: boolean;\n /** Unavailable: every control dims and refuses to move the page. */\n disabled?: boolean;\n /**\n * Names for everything this control says out loud, as any subset of\n * `PaginationLabels` — 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: the case it exists for is the two paginations above and\n * below one table, which must not share `labels.nav`.\n */\n labels?: LabelOverrides<PaginationLabels>;\n }>(),\n {\n itemsPerPage: 10,\n variant: \"full\",\n siblingCount: 1,\n // Reka defaults this off, which drops the ellipses entirely and lets the\n // numbered row float free of both ends. The anchored form is what people\n // mean by a full pagination, so Loom turns it on.\n showEdges: true,\n disabled: false,\n },\n);\n\nconst emit = defineEmits<{\n /** The 1-based page the reader asked for. A controlled host decides whether to honour it. */\n \"update:page\": [page: number];\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(\"pagination\", PAGINATION_LABELS, () => props.labels);\n\n// The root renders the `<nav>`, so every fallthrough attribute belongs on it —\n// there is no second node they could sensibly describe. `class` is pulled out\n// of that spread and merged through `cn()` instead: spreading it alongside the\n// nav's own `:class` only concatenates the two lists, so a caller's `gap-2`\n// would win or lose depending on which utility Tailwind happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: navAttrs } = useSplitAttrs();\n\n// The edge controls keep the dim, and they are the one place in this component\n// where it is still the right answer: their whole content is a chevron marked\n// `aria-hidden`, so there is no text for the alpha to take down with it and the\n// name a screen reader announces is an `aria-label` no paint can reach. WCAG\n// exempts an inactive control from 1.4.11 besides. Prev and First sit disabled\n// on page 1 for as long as a reader is on it, which is the other half of the\n// argument: a faded chevron is the conventional reading of \"no further this\n// way\", where a drained plate would draw the eye to the control that cannot be\n// used.\nconst edgeClass = cn(\n buttonVariants({ variant: \"ghost\", size: \"icon-sm\" }),\n // The edges assert `w-8`, a fixed 32px square, but they sit in a flex row\n // with `flex-shrink: 1` and no min-width, so a row narrower than its content\n // (a phone-width column) collapses them to the 16px chevron — a touch target\n // below WCAG 2.5.8's 24px floor. The page numbers already floor themselves\n // with `min-w-8`; the edges must refuse to shrink instead, leaving the row to\n // overflow rather than ever compressing a control's own target.\n \"shrink-0 disabled:cursor-not-allowed disabled:opacity-50\",\n);\n\n// The page buttons borrow Button's own variants rather than restating its\n// press language, so the row cannot drift from every other pressable thing in\n// the library. `min-w-8` with tabular figures is what keeps the row from\n// reflowing as the numbers under it grow a digit.\nfunction pageClass(selected: boolean): string {\n return cn(\n buttonVariants({ variant: selected ? \"primary\" : \"ghost\", size: \"sm\" }),\n \"min-w-8 px-2 tabular\",\n // No `opacity` here, unlike the edges above, because this button's content\n // *is* text — the page number, the one thing a reader needs from a pager\n // that is briefly out of service. `disabled:opacity-50` composited the\n // ghost variant's `--color-muted-foreground` down to 2.05:1 and the current\n // page's white-on-primary label down to 2.48:1; halving the alpha more than\n // halves the contrast. Unavailability is painted in measured colours\n // instead.\n //\n // The two branches drain to different wells on purpose, which is Chip's\n // rule applied to a row of numbers: an unselected page falls to the neutral\n // well at 4.67:1, and the current page keeps the primary wash it already wears\n // at 6.84:1. Which page a reader is on is information, and a disabled pager\n // that forgets it has answered the question by refusing to answer it.\n selected\n ? \"disabled:cursor-not-allowed disabled:bg-primary-muted disabled:text-primary-text disabled:shadow-none\"\n : \"disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\",\n );\n}\n\n/** Which edge control stands in for which, when the one that was pressed goes away. */\nconst MIRROR: Record<string, string | undefined> = {\n first: \"last\",\n prev: \"next\",\n next: \"prev\",\n last: \"first\",\n};\n\n// Reka disables Prev/First at page 1 and Next/Last at the last page, and that\n// is the honest state: a control that looks unavailable and still fires is\n// worse than one that is not there at all. The cost is real, though — a\n// browser blurs a button the instant it becomes disabled, so a reader holding\n// Enter on Next arrives at the last page with focus dumped on `<body>` and\n// their place in the document gone.\n//\n// So the control that fired the change hands focus on: to the current page\n// button where the variant has one — which announces \"Page 12, current page\",\n// the very thing the reader was trying to find out — and to its mirror control\n// otherwise, which is the nearest thing still able to move the page.\n//\n// Only for a keyboard activation. `detail === 0` is what separates a click\n// synthesised by Enter or Space from a pointer click, and a pointer user has\n// no focus position to lose; moving focus for them would raise a ring nobody\n// asked for.\nlet pressed: HTMLButtonElement | null = null;\n\nfunction rememberKeyboardPress(event: MouseEvent): void {\n const target = event.target as HTMLElement | null;\n pressed = event.detail === 0 ? (target?.closest<HTMLButtonElement>(\"button\") ?? null) : null;\n}\n\nfunction handOffFocus(): void {\n const origin = pressed;\n pressed = null;\n if (!origin?.disabled) return;\n\n // Only rescue focus that was actually lost. A browser leaves `<body>`\n // focused after disabling the button; jsdom leaves the button itself. Any\n // other active element means the reader has already moved on, and stealing\n // focus back from them would be the worse bug.\n const active = document.activeElement;\n if (active !== origin && active !== document.body) return;\n\n const nav = origin.closest(\"nav\");\n if (!nav) return;\n\n const mirror = MIRROR[origin.getAttribute(\"data-edge\") ?? \"\"];\n const target =\n nav.querySelector<HTMLButtonElement>('[aria-current=\"page\"]:not([disabled])') ??\n (mirror\n ? nav.querySelector<HTMLButtonElement>(`[data-edge=\"${mirror}\"]:not([disabled])`)\n : null);\n target?.focus();\n}\n\nfunction onPageChange(value: number): void {\n emit(\"update:page\", value);\n // After the re-render rather than before it: the button that was pressed\n // only becomes disabled once the new page has been applied.\n void nextTick(handOffFocus);\n}\n</script>\n\n<template>\n <PaginationRoot\n v-slot=\"{ page: current, pageCount }\"\n v-bind=\"{ ...navAttrs, ...optional({ page }) }\"\n :total=\"total\"\n :items-per-page=\"itemsPerPage\"\n :sibling-count=\"siblingCount\"\n :show-edges=\"showEdges\"\n :disabled=\"disabled\"\n :aria-label=\"text.nav\"\n :class=\"cn(paginationVariants({ variant }), attrs.class as string)\"\n @click=\"rememberKeyboardPress\"\n @update:page=\"onPageChange\"\n >\n <!-- Every `:aria-label` from here down replaces one Reka writes in English\n inside its own render function. It reaches the DOM node as a\n fallthrough attribute, which Vue merges last, so it is a replacement\n rather than a second name. Removing one does not fall back to nothing;\n it falls back to Reka's English, which is the failure this whole seam\n exists to close and which no test of Loom's own strings would catch. -->\n <PaginationFirst\n v-if=\"variant === 'full'\"\n data-edge=\"first\"\n :aria-label=\"text.first\"\n :class=\"edgeClass\"\n >\n <ChevronsLeft class=\"h-4 w-4\" aria-hidden=\"true\" />\n </PaginationFirst>\n\n <PaginationPrev data-edge=\"prev\" :aria-label=\"text.previous\" :class=\"edgeClass\">\n <ChevronLeft class=\"h-4 w-4\" aria-hidden=\"true\" />\n </PaginationPrev>\n\n <PaginationList v-if=\"variant === 'full'\" v-slot=\"{ items }\" class=\"flex items-center gap-1\">\n <!-- Keyed by page number, never by index. The row is furniture: a page\n that stays inside the window across a change keeps its own DOM node,\n so nothing remounts, nothing re-animates, and the numbers do not\n shuffle under a reader who has just pressed Next. -->\n <template\n v-for=\"(item, index) in items\"\n :key=\"item.type === 'page' ? `page-${item.value}` : `ellipsis-${index}`\"\n >\n <PaginationListItem\n v-if=\"item.type === 'page'\"\n :value=\"item.value\"\n :aria-label=\"text.page({ page: item.value })\"\n :class=\"pageClass(item.value === current)\"\n />\n <!-- The ellipsis is not a control, so it renders as a span rather than\n Reka's default div-with-a-glyph inside a row of buttons: nothing to\n focus, nothing that looks pressable, and the glyph itself hidden\n from assistive technology because \"horizontal ellipsis\" is not what\n it means. The words are what it means. -->\n <PaginationEllipsis\n v-else\n as=\"span\"\n class=\"inline-flex h-8 min-w-8 select-none items-center justify-center text-sm text-muted-foreground\"\n >\n <span aria-hidden=\"true\">…</span>\n <span class=\"sr-only\">{{ text.ellipsis }}</span>\n </PaginationEllipsis>\n </template>\n </PaginationList>\n\n <!-- The position, for the two variants that have no numbered row to read it\n off. `role=\"status\"` because pressing Next in those variants otherwise\n changes nothing a screen reader would notice — focus stays put and no\n `aria-current` moves. The `full` variant deliberately has no live\n region: its number row already carries the state, and announcing it\n again on top of the focus hand-off below would say everything twice. -->\n <!-- One string rather than three nodes with the numbers between them: a\n language that puts the count before the word, or drops the preposition\n entirely, cannot reorder markup this component emits. `tabular` moves\n from the two number spans to the whole readout, which is the only real\n cost — the \"of\" between them is now tabular too, and the row of digits\n it was steadying is not rendered in either of these variants. -->\n <span\n v-if=\"variant !== 'full'\"\n role=\"status\"\n :class=\"variant === 'compact' ? 'px-1 text-sm tabular text-muted-foreground' : 'sr-only'\"\n >\n {{ text.position({ page: current, pageCount }) }}\n </span>\n\n <PaginationNext data-edge=\"next\" :aria-label=\"text.next\" :class=\"edgeClass\">\n <ChevronRight class=\"h-4 w-4\" aria-hidden=\"true\" />\n </PaginationNext>\n\n <PaginationLast\n v-if=\"variant === 'full'\"\n data-edge=\"last\"\n :aria-label=\"text.last\"\n :class=\"edgeClass\"\n >\n <ChevronsRight class=\"h-4 w-4\" aria-hidden=\"true\" />\n </PaginationLast>\n </PaginationRoot>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"Pagination.js","names":[],"sources":["../../../../../packages/primitives/pagination/src/Pagination.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { PaginationLabels } from \"@ecoma-io/loom-labels\";\nimport { cva } from \"class-variance-authority\";\n\n/**\n\n/**\n * Loom's English, co-located with the component so it tree-shakes with it: a\n * consumer who never imports Pagination ships none of these bytes.\n *\n * Exported so a host can build a partial vocabulary against the real thing\n * rather than a transcription of it.\n */\nexport const PAGINATION_LABELS: PaginationLabels = {\n nav: \"Pagination\",\n first: \"First page\",\n previous: \"Previous page\",\n next: \"Next page\",\n last: \"Last page\",\n page: ({ page }) => `Page ${String(page)}`,\n position: ({ page, pageCount }) => `Page ${String(page)} of ${String(pageCount)}`,\n ellipsis: \"More pages\",\n};\n\n/**\n * How much of the paging apparatus is rendered. All three drive the same\n * model and carry the same semantics — they differ only in how much room the\n * navigation is worth on the surface it sits on.\n *\n * • `full` — the numbered row with ellipses, plus first/prev/next/last.\n * For a table where jumping to a known page is a real task.\n * • `compact` — prev, \"Page 3 of 12\", next. The position stays legible\n * without spending a whole row on it.\n * • `simple` — prev and next alone, for a feed nobody navigates by index.\n */\nexport type PaginationVariant = \"full\" | \"compact\" | \"simple\";\n\nexport const paginationVariants = cva(\"inline-flex items-center\", {\n // `satisfies` keeps the two halves honest: the union above is the name a\n // consumer imports and the documentation prints, the map below is the\n // classes. Adding a member to one and not the other stops compiling.\n variants: {\n variant: {\n // The numbered row is a run of same-size squares and reads as one strip\n // at gap-1; the compact form is a sentence between two controls and\n // needs the wider gap to stop reading as a third button.\n full: \"gap-1\",\n compact: \"gap-2\",\n simple: \"gap-1\",\n } satisfies Record<PaginationVariant, string>,\n },\n defaultVariants: { variant: \"full\" },\n});\n</script>\n\n<script setup lang=\"ts\">\nimport { nextTick } from \"vue\";\nimport {\n PaginationRoot,\n PaginationList,\n PaginationListItem,\n PaginationFirst,\n PaginationPrev,\n PaginationNext,\n PaginationLast,\n PaginationEllipsis,\n} from \"reka-ui\";\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from \"@lucide/vue\";\nimport { buttonVariants } from \"@ecoma-io/loom-button\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { optional } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\n\n/**\n * Pagination — moving through a paged result set, one page at a time or by\n * jumping to a numbered one.\n *\n * It renders **buttons, never links**. Loom cannot know whether a consumer's\n * pages are addressable URLs, and an `<a>` with no `href` is not a link at\n * all — it is a div that lies to a screen reader and drops out of the tab\n * order. A product whose pages are real, crawlable URLs wants its own\n * `<a href>` row instead of this component; nothing here can be made to\n * produce one.\n *\n * Reach for a different control when the list is not paged: an infinite feed\n * wants a \"load more\" button, and a long list a reader scans rather than\n * indexes wants a virtualised scroller.\n *\n * Built on Reka UI's Pagination, which owns the arithmetic — the page count,\n * the sliding window of numbers around the current page, and where the\n * ellipses fall. That window is fiddly enough at the boundaries that\n * recomputing it here would only be a second, subtly different answer.\n */\nconst props = withDefaults(\n defineProps<{\n /**\n * The current page, 1-based. Leave it unset to let the component keep its\n * own page and simply report changes.\n */\n page?: number;\n /** How many items exist in total, across every page. */\n total: number;\n /** How many items one page holds. With `total`, this is what fixes the page count. */\n itemsPerPage?: number;\n /** How much of the control renders: the numbered row, a position readout, or arrows alone. */\n variant?: PaginationVariant;\n /** How many numbers sit either side of the current page, in the `full` variant. */\n siblingCount?: number;\n /** Pins page 1 and the last page into the numbered row, with ellipses standing in for the gap. */\n showEdges?: boolean;\n /** Unavailable: every control dims and refuses to move the page. */\n disabled?: boolean;\n /**\n * Names for everything this control says out loud, as any subset of\n * `PaginationLabels` — 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: the case it exists for is the two paginations above and\n * below one table, which must not share `labels.nav`.\n */\n labels?: LabelOverrides<PaginationLabels>;\n }>(),\n {\n itemsPerPage: 10,\n variant: \"full\",\n siblingCount: 1,\n // Reka defaults this off, which drops the ellipses entirely and lets the\n // numbered row float free of both ends. The anchored form is what people\n // mean by a full pagination, so Loom turns it on.\n showEdges: true,\n disabled: false,\n },\n);\n\nconst emit = defineEmits<{\n /** The 1-based page the reader asked for. A controlled host decides whether to honour it. */\n \"update:page\": [page: number];\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(\"pagination\", PAGINATION_LABELS, () => props.labels);\n\n// The root renders the `<nav>`, so every fallthrough attribute belongs on it —\n// there is no second node they could sensibly describe. `class` is pulled out\n// of that spread and merged through `cn()` instead: spreading it alongside the\n// nav's own `:class` only concatenates the two lists, so a caller's `gap-2`\n// would win or lose depending on which utility Tailwind happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: navAttrs } = useSplitAttrs();\n\n// The edge controls used to keep the dim on the theory that a chevron has no\n// text for an alpha to take down — but the library-wide rule settled since is\n// drained, not dimmed, full stop: the same well every unavailable control\n// wears, so a disabled pager reads as one treatment rather than two. WCAG\n// exempts an inactive control from 1.4.11 either way; what decides it here is\n// that a drained plate is how \"no further this way\" looks everywhere else in\n// Loom.\nconst edgeClass = cn(\n buttonVariants({ variant: \"ghost\", size: \"icon-sm\" }),\n // The edges assert `w-8`, a fixed 32px square, but they sit in a flex row\n // with `flex-shrink: 1` and no min-width, so a row narrower than its content\n // (a phone-width column) collapses them to the 16px chevron — a touch target\n // below WCAG 2.5.8's 24px floor. The page numbers already floor themselves\n // with `min-w-8`; the edges must refuse to shrink instead, leaving the row to\n // overflow rather than ever compressing a control's own target.\n \"shrink-0 disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground disabled:shadow-none\",\n);\n\n// The page buttons borrow Button's own variants rather than restating its\n// press language, so the row cannot drift from every other pressable thing in\n// the library. `min-w-8` with tabular figures is what keeps the row from\n// reflowing as the numbers under it grow a digit.\nfunction pageClass(selected: boolean): string {\n return cn(\n buttonVariants({ variant: selected ? \"primary\" : \"ghost\", size: \"sm\" }),\n \"min-w-8 px-2 tabular\",\n // No `opacity` here, unlike the edges above, because this button's content\n // *is* text — the page number, the one thing a reader needs from a pager\n // that is briefly out of service. `disabled:opacity-50` composited the\n // ghost variant's `--color-muted-foreground` down to 2.05:1 and the current\n // page's white-on-primary label down to 2.48:1; halving the alpha more than\n // halves the contrast. Unavailability is painted in measured colours\n // instead.\n //\n // The two branches drain to different wells on purpose, which is Chip's\n // rule applied to a row of numbers: an unselected page falls to the neutral\n // well at 4.67:1, and the current page keeps the primary wash it already wears\n // at 6.84:1. Which page a reader is on is information, and a disabled pager\n // that forgets it has answered the question by refusing to answer it.\n selected\n ? \"disabled:cursor-not-allowed disabled:bg-primary-muted disabled:text-primary-text disabled:shadow-none\"\n : \"disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground\",\n );\n}\n\n/** Which edge control stands in for which, when the one that was pressed goes away. */\nconst MIRROR: Record<string, string | undefined> = {\n first: \"last\",\n prev: \"next\",\n next: \"prev\",\n last: \"first\",\n};\n\n// Reka disables Prev/First at page 1 and Next/Last at the last page, and that\n// is the honest state: a control that looks unavailable and still fires is\n// worse than one that is not there at all. The cost is real, though — a\n// browser blurs a button the instant it becomes disabled, so a reader holding\n// Enter on Next arrives at the last page with focus dumped on `<body>` and\n// their place in the document gone.\n//\n// So the control that fired the change hands focus on: to the current page\n// button where the variant has one — which announces \"Page 12, current page\",\n// the very thing the reader was trying to find out — and to its mirror control\n// otherwise, which is the nearest thing still able to move the page.\n//\n// Only for a keyboard activation. `detail === 0` is what separates a click\n// synthesised by Enter or Space from a pointer click, and a pointer user has\n// no focus position to lose; moving focus for them would raise a ring nobody\n// asked for.\nlet pressed: HTMLButtonElement | null = null;\n\nfunction rememberKeyboardPress(event: MouseEvent): void {\n const target = event.target as HTMLElement | null;\n pressed = event.detail === 0 ? (target?.closest<HTMLButtonElement>(\"button\") ?? null) : null;\n}\n\nfunction handOffFocus(): void {\n const origin = pressed;\n pressed = null;\n if (!origin?.disabled) return;\n\n // Only rescue focus that was actually lost. A browser leaves `<body>`\n // focused after disabling the button; jsdom leaves the button itself. Any\n // other active element means the reader has already moved on, and stealing\n // focus back from them would be the worse bug.\n const active = document.activeElement;\n if (active !== origin && active !== document.body) return;\n\n const nav = origin.closest(\"nav\");\n if (!nav) return;\n\n const mirror = MIRROR[origin.getAttribute(\"data-edge\") ?? \"\"];\n const target =\n nav.querySelector<HTMLButtonElement>('[aria-current=\"page\"]:not([disabled])') ??\n (mirror\n ? nav.querySelector<HTMLButtonElement>(`[data-edge=\"${mirror}\"]:not([disabled])`)\n : null);\n target?.focus();\n}\n\nfunction onPageChange(value: number): void {\n emit(\"update:page\", value);\n // After the re-render rather than before it: the button that was pressed\n // only becomes disabled once the new page has been applied.\n void nextTick(handOffFocus);\n}\n</script>\n\n<template>\n <PaginationRoot\n v-slot=\"{ page: current, pageCount }\"\n v-bind=\"{ ...navAttrs, ...optional({ page }) }\"\n :total=\"total\"\n :items-per-page=\"itemsPerPage\"\n :sibling-count=\"siblingCount\"\n :show-edges=\"showEdges\"\n :disabled=\"disabled\"\n :aria-label=\"text.nav\"\n :class=\"cn(paginationVariants({ variant }), attrs.class as string)\"\n @click=\"rememberKeyboardPress\"\n @update:page=\"onPageChange\"\n >\n <!-- Every `:aria-label` from here down replaces one Reka writes in English\n inside its own render function. It reaches the DOM node as a\n fallthrough attribute, which Vue merges last, so it is a replacement\n rather than a second name. Removing one does not fall back to nothing;\n it falls back to Reka's English, which is the failure this whole seam\n exists to close and which no test of Loom's own strings would catch. -->\n <PaginationFirst\n v-if=\"variant === 'full'\"\n data-edge=\"first\"\n :aria-label=\"text.first\"\n :class=\"edgeClass\"\n >\n <ChevronsLeft class=\"h-4 w-4\" aria-hidden=\"true\" />\n </PaginationFirst>\n\n <PaginationPrev data-edge=\"prev\" :aria-label=\"text.previous\" :class=\"edgeClass\">\n <ChevronLeft class=\"h-4 w-4\" aria-hidden=\"true\" />\n </PaginationPrev>\n\n <PaginationList v-if=\"variant === 'full'\" v-slot=\"{ items }\" class=\"flex items-center gap-1\">\n <!-- Keyed by page number, never by index. The row is furniture: a page\n that stays inside the window across a change keeps its own DOM node,\n so nothing remounts, nothing re-animates, and the numbers do not\n shuffle under a reader who has just pressed Next. -->\n <template\n v-for=\"(item, index) in items\"\n :key=\"item.type === 'page' ? `page-${item.value}` : `ellipsis-${index}`\"\n >\n <PaginationListItem\n v-if=\"item.type === 'page'\"\n :value=\"item.value\"\n :aria-label=\"text.page({ page: item.value })\"\n :class=\"pageClass(item.value === current)\"\n />\n <!-- The ellipsis is not a control, so it renders as a span rather than\n Reka's default div-with-a-glyph inside a row of buttons: nothing to\n focus, nothing that looks pressable, and the glyph itself hidden\n from assistive technology because \"horizontal ellipsis\" is not what\n it means. The words are what it means. -->\n <PaginationEllipsis\n v-else\n as=\"span\"\n class=\"inline-flex h-8 min-w-8 select-none items-center justify-center text-sm text-muted-foreground\"\n >\n <span aria-hidden=\"true\">…</span>\n <span class=\"sr-only\">{{ text.ellipsis }}</span>\n </PaginationEllipsis>\n </template>\n </PaginationList>\n\n <!-- The position, for the two variants that have no numbered row to read it\n off. `role=\"status\"` because pressing Next in those variants otherwise\n changes nothing a screen reader would notice — focus stays put and no\n `aria-current` moves. The `full` variant deliberately has no live\n region: its number row already carries the state, and announcing it\n again on top of the focus hand-off below would say everything twice. -->\n <!-- One string rather than three nodes with the numbers between them: a\n language that puts the count before the word, or drops the preposition\n entirely, cannot reorder markup this component emits. `tabular` moves\n from the two number spans to the whole readout, which is the only real\n cost — the \"of\" between them is now tabular too, and the row of digits\n it was steadying is not rendered in either of these variants. -->\n <span\n v-if=\"variant !== 'full'\"\n role=\"status\"\n :class=\"variant === 'compact' ? 'px-1 text-sm tabular text-muted-foreground' : 'sr-only'\"\n >\n {{ text.position({ page: current, pageCount }) }}\n </span>\n\n <PaginationNext data-edge=\"next\" :aria-label=\"text.next\" :class=\"edgeClass\">\n <ChevronRight class=\"h-4 w-4\" aria-hidden=\"true\" />\n </PaginationNext>\n\n <PaginationLast\n v-if=\"variant === 'full'\"\n data-edge=\"last\"\n :aria-label=\"text.last\"\n :class=\"edgeClass\"\n >\n <ChevronsRight class=\"h-4 w-4\" aria-hidden=\"true\" />\n </PaginationLast>\n </PaginationRoot>\n</template>\n"],"mappings":""}
@@ -1,5 +1,5 @@
1
- import { type LabelOverrides } from "@ecoma-io/loom-labels";
2
- import type { PaginationLabels } from "@ecoma-io/loom-labels";
1
+ import { type LabelOverrides } from "../../../labels/src/index";
2
+ import type { PaginationLabels } from "../../../labels/src/index";
3
3
  /**
4
4
 
5
5
  /**
@@ -23,7 +23,7 @@ export declare const PAGINATION_LABELS: PaginationLabels;
23
23
  */
24
24
  export type PaginationVariant = "full" | "compact" | "simple";
25
25
  export declare const paginationVariants: (props?: ({
26
- variant?: "full" | "compact" | "simple" | null | undefined;
26
+ variant?: "compact" | "full" | "simple" | null | undefined;
27
27
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
28
28
  declare const _default: typeof __VLS_export;
29
29
  export default _default;