@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
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.js","names":[],"sources":["../../../../../packages/primitives/combobox/src/Combobox.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { ComboboxLabels } 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, and\n * exported so a host can build a partial vocabulary against the real thing.\n */\nexport const COMBOBOX_LABELS: ComboboxLabels = {\n trigger: \"Show options\",\n empty: \"No results\",\n count: ({ count }) => `${String(count)} selected`,\n overflow: ({ hidden }) => `+${String(hidden)} more`,\n};\n\n/**\n * One row of the list. `value` is what the model carries; `label` is what a\n * reader sees, what the filter matches, and what the input shows once the row\n * is chosen.\n *\n * Identical to `SelectOption` today, and deliberately not the same\n * declaration: the two surfaces are independent, and the first field either\n * one grows — a secondary line here, a group key there — is a field the other\n * should not inherit by accident. A shared alias would make that change look\n * free when it is not.\n */\nexport interface ComboboxOption {\n /** The value bound to `v-model` when this row is chosen. Unique within the list. */\n value: string;\n /** The text shown on the row, matched by the filter, and put in the input once chosen. */\n label: string;\n /** Present but unchoosable — visible, skipped by the keyboard, unclickable. */\n disabled?: boolean;\n}\n\n/**\n * The control's height scale. It matches the text input and Select scales\n * exactly, so a form row mixing a Combobox beside either never comes out\n * uneven — which is why reaching past `size` for an `h-*` class is always the\n * wrong fix.\n */\nexport type ComboboxSize = \"sm\" | \"md\" | \"lg\";\n\nexport const comboboxVariants = cva(\n [\n \"flex w-full items-center gap-2 rounded-md border border-input bg-background text-foreground\",\n \"transition-[color,background-color,border-color,box-shadow] duration-fast ease-out\",\n // The focusable node is the input *inside* this box, so the ring is drawn\n // with `focus-within` rather than `focus-visible` — the same choice\n // TextField makes for the same reason, and what keeps the two controls\n // lighting up identically in a form row.\n \"focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring\",\n ],\n {\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 size: {\n sm: \"h-8 px-2.5 text-xs\",\n md: \"h-9 px-3 text-sm\",\n lg: \"h-11 px-4 text-base\",\n } satisfies Record<ComboboxSize, string>,\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * The same three numbers as a floor rather than as a fixed height, for the\n * multi-select box only.\n *\n * A box holding tokens is sized by its content, which is TagsInput's call and\n * the reason its field is `min-h-9`. What keeps that from turning into an\n * unbounded control here is `visibleValues`: the token row never wraps, so in\n * practice the box resolves to exactly the height above and a multi-select\n * still lines up with the single-select beside it. The `h-auto` that goes with\n * it in the template is what releases the fixed height `comboboxVariants`\n * already set — Tailwind's merge resolves the two, `min-h-*` does not.\n */\nconst multiSelectHeights = {\n sm: \"min-h-8\",\n md: \"min-h-9\",\n lg: \"min-h-11\",\n} satisfies Record<ComboboxSize, string>;\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from \"vue\";\nimport {\n ComboboxRoot,\n ComboboxAnchor,\n ComboboxInput,\n ComboboxTrigger,\n ComboboxPortal,\n ComboboxContent,\n ComboboxViewport,\n ComboboxItem,\n ComboboxItemIndicator,\n ComboboxEmpty,\n} from \"reka-ui\";\nimport { Check, ChevronDown } from \"@lucide/vue\";\nimport Chip from \"@ecoma-io/loom-chip\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { listStaggerDelay } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\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 * Combobox — a text input that narrows a list as you type, for the list too\n * long to scan. Countries, currencies, repositories, people: anywhere a reader\n * knows roughly what they are looking for and would rather type three letters\n * than scroll past two hundred rows.\n *\n * Its neighbour is `Select`, and the boundary is the length of the list rather\n * than the shape of the control. Three to fifteen rows a reader can read\n * through belong in a Select — a search box over eight options is a keystroke\n * charged for nothing. Two to five options that should all be visible at once\n * belong in a segmented control. Past roughly fifteen, searching beats\n * scrolling and this is the control that offers it.\n *\n * Built on Reka UI's Combobox, which supplies the editable-combobox semantics:\n * the `role=\"combobox\"` input wired to the listbox by `aria-expanded` and\n * `aria-controls`, `aria-autocomplete=\"list\"`, and the active row tracked by\n * `aria-activedescendant` while real focus stays in the input. That last part\n * is the one genuine behavioural difference from Select, which moves focus\n * onto the row itself — a combobox cannot, because the reader is still typing.\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` and the name the\n * chosen value is submitted under all arrive through `useFieldControl()`, so\n * `<Field label=\"Country\" error=\"…\"><Combobox … /></Field>` needs no attributes\n * at the call site. Both booleans below still win over the row when they are\n * set, in both directions — which is why they default to `undefined` rather\n * than `false`.\n *\n * A row's `readonly` is deliberately ignored. The text in the box is not the\n * value — it is the chosen option's label, rewritten on every choice — so a\n * `readonly` input here would freeze a caption rather than protect a value,\n * while the list underneath still opened on click. A choice nobody may change\n * is a disabled Combobox, or the label rendered as text.\n *\n * `multiple` turns it into a many-of-many picker. The chosen values render as\n * tokens in the box — the same `Chip` a\n * [TagsInput](../TagsInput/TagsInput.vue) commits its values to, so a selection\n * looks the same wherever a form shows one — and the input goes back to being\n * only a search box. The tokens are **inert**, deliberately: the list is where\n * a choice is made and where it is unmade, so the control stays the single Tab\n * stop it is in single-select, and a value hidden behind the overflow count is\n * reachable by exactly the same gesture as a visible one. That is the one place\n * this diverges from TagsInput, whose tokens carry their own remove control\n * because it has no list to send a reader back to.\n */\nconst props = withDefaults(\n defineProps<{\n /**\n * The chosen option's `value`, or every chosen value when `multiple`.\n *\n * The union is one prop rather than a generic component, and the invariant\n * is the mode: single-select emits and accepts a `string` and never an\n * array, multi-select an array and never a bare string. A generic would\n * express that in the type system and cost more than it buys — it would\n * print a conditional type in the generated API table below, and it would\n * break `InstanceType<typeof Combobox>`, which is how a consumer types a\n * template ref.\n */\n modelValue?: string | string[] | undefined;\n /** The rows, in the order they are shown. */\n options: ComboboxOption[];\n /** Lets several rows be chosen at once: chosen rows toggle instead of closing the list, and the box shows the selection as tokens. */\n multiple?: boolean;\n /**\n * How many tokens the box shows before the rest collapse into a count.\n *\n * The box must not grow with the selection — it sits in a form row beside\n * controls on a fixed height scale — so past this many the tail becomes one\n * `labels.overflow` summary: five chosen shows three and \"+2 more\", fifty\n * shows three and \"+47 more\". Raise it for a wide box; the row never wraps,\n * so a number the box has no room for is a number that truncates.\n */\n visibleValues?: number;\n /** Shown in the empty input, before anything is typed or chosen. */\n placeholder?: string;\n /** Shown as a row of its own when the filter matches nothing. Defaults to `labels.empty`, so say what was searched for rather than restating the default in a second language. */\n emptyMessage?: string | undefined;\n /** Unavailable: dims the control, refuses to open and refuses to take text. Unset defers to a wrapping Field. */\n disabled?: 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 /** Control height. It shares the text input scale so mixed form rows stay aligned. */\n size?: ComboboxSize;\n /**\n * Filter `options` against the typed text. Turn it off when the host is\n * producing the list itself — from a server, in response to\n * `update:query` — so a result that does not literally contain what was\n * typed is not then hidden a second time by a filter running underneath it.\n */\n filter?: boolean;\n /**\n * Names for the two strings this control supplies itself, as any subset of\n * `ComboboxLabels` — 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. `emptyMessage` is the shorter way to reach `empty` for one\n * box; this is what reaches `trigger`.\n */\n labels?: LabelOverrides<ComboboxLabels>;\n }>(),\n {\n // `modelValue: undefined` is load-bearing rather than redundant: paired\n // with `optional()` below it is what keeps the uncontrolled third state\n // reachable, instead of pinning the control to a value nobody chose.\n modelValue: undefined,\n multiple: false,\n // Three, and the number is a judgement rather than a measurement: two\n // tokens plus a count reads as a control that shows almost nothing, and\n // four leaves a `md` box no room to type in at the default width. It is a\n // prop because the right answer is the box's width, which only the caller\n // knows.\n visibleValues: 3,\n // No English default here any more. The wording moved into `COMBOBOX_LABELS`\n // so a host can set it once for the whole application rather than on every\n // box, and an absent prop is what lets the resolved label through.\n emptyMessage: undefined,\n // Not `false`, for either. `false` is a caller saying \"this control is fine\n // / enabled even though its row is not\"; absent is a caller saying nothing,\n // and only `undefined` survives to mean that past Vue's\n // absent-Boolean-casts-to-false rule — see TextField.vue, which carries the\n // full reasoning.\n disabled: undefined,\n invalid: undefined,\n size: \"md\",\n filter: true,\n },\n);\n\nconst emit = defineEmits<{\n /** The chosen option's `value` — or the whole chosen list, when `multiple`. Single-select fires once per choice and the list closes with it; multi-select fires once per toggle and the list stays open. */\n \"update:modelValue\": [value: string | string[]];\n /** The text now in the input — as the reader types, and again when a choice puts its label there. */\n \"update:query\": [query: string];\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(\"combobox\", COMBOBOX_LABELS, () => props.labels);\n\nconst query = ref(\"\");\n\n/**\n * The stagger is the list's *reveal*, and re-filtering is not a reveal. This\n * is true from the moment the list opens until the first keystroke, after\n * which every row arrives immediately.\n *\n * Two things go wrong without it. A delay recomputed per keystroke makes the\n * list feel like it is rebuilding under the reader's fingers — the opposite of\n * what a filter is for. And raising `animation-delay` on a row that survived\n * the keystroke rewinds its entrance: the row is still mounted, so the\n * animation is not restarted, it is re-timed, and a row that had finished\n * fading in drops back to half-opacity. Going the other way — stagger to `0ms`\n * — only ever fast-forwards, which is why this flag falls and does not rise\n * again until the content has unmounted and remounted with the next open.\n */\nconst revealing = ref(true);\n\nconst countId = useId();\n\n/**\n * The multi-select's own copy of the selection, and the reason it keeps one:\n * the tokens have to render whether or not the host bound `v-model`.\n *\n * Left uncontrolled, `modelValue` stays `undefined` while Reka manages the\n * choice internally — which is the third state `optional()` exists to preserve\n * — so a trigger reading the prop would sit empty while the list showed three\n * checked rows. This mirrors rather than drives: it is never fed back into\n * Reka, so nothing about the uncontrolled control changes.\n *\n * Seeded from the prop rather than sanitised, for TagsInput's reason: a list\n * the host already holds is the host's to hold.\n */\nconst selected = ref<string[]>(Array.isArray(props.modelValue) ? [...props.modelValue] : []);\nwatch(\n () => props.modelValue,\n (next) => {\n if (Array.isArray(next)) selected.value = [...next];\n },\n { deep: true },\n);\n\n/**\n * The chosen values as the box shows them, and the fallback is the point: a\n * host driving `options` from a search hands back only the rows matching the\n * current query, so a value chosen from an earlier one has no option to look\n * its label up in. Falling back to the value keeps the token on screen — a\n * chosen value that vanished from the trigger while it was still in the model\n * would be the control lying about what it holds.\n */\nconst tokens = computed(() =>\n selected.value.map((value) => ({\n value,\n label: props.options.find((option) => option.value === value)?.label ?? value,\n })),\n);\n\nconst visibleTokens = computed(() => tokens.value.slice(0, Math.max(0, props.visibleValues)));\nconst hiddenTokens = computed(() => tokens.value.length - visibleTokens.value.length);\n\n/**\n * What the input shows once a row is chosen. Without this Reka falls back to\n * the model value's own `toString()`, which would leave `en` in the box where\n * the reader picked \"English\".\n *\n * A multi-select shows nothing: the tokens are the selection, and the input\n * beside them is a search box. Reka calls this on close rather than on choose,\n * so the query the reader typed survives every toggle and one search can pick\n * several rows — which is the whole reason the list stays open.\n */\nfunction displayValue(value: unknown): string {\n if (props.multiple) return \"\";\n return props.options.find((option) => option.value === value)?.label ?? \"\";\n}\n\n/**\n * Reka hands back the whole selection on every toggle, never a delta, so the\n * emitted array is always the complete list — the same contract TagsInput's\n * `update:modelValue` carries, and the reason a host can assign it straight\n * back.\n */\nfunction onChoice(value: unknown): void {\n if (!props.multiple) {\n emit(\"update:modelValue\", String(value));\n return;\n }\n const values = Array.isArray(value) ? value.map(String) : [];\n selected.value = values;\n emit(\"update:modelValue\", values);\n}\n\nfunction onQuery(text: string): void {\n // Reka rewrites the input's text on its own account — once as the component\n // mounts, and again when a choice puts the chosen label back in the box —\n // and each of those arrives here as an update. Reporting a query that did\n // not change would have a host running the same search twice on mount, so\n // only a real change is passed on.\n if (text === query.value) return;\n query.value = text;\n revealing.value = false;\n emit(\"update:query\", text);\n}\n\n// The root renders no DOM node of its own, so fallthrough attributes would be\n// dropped on the floor. They go to the input, which is the element they\n// describe and the only one a name can land on — the chevron trigger already\n// carries Reka's own `aria-label`, and naming the control means naming the\n// `role=\"combobox\"` node.\n//\n// `class` is pulled out of that spread and merged through `cn()` onto the\n// anchor instead, because the anchor is the box a caller means when they pass\n// `w-64`. Spreading it alongside the anchor's own `:class` would only\n// concatenate the two lists — Vue's merge is generic, not Tailwind-aware — so\n// a caller's width would win or lose depending on which utility Tailwind\n// happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\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`,\n// `readonly` and `required` are absent from this object because there is no\n// prop to oppose the row with; the row's answer stands unopposed, which is\n// what is wanted.\n//\n// The multi-select's count joins that list rather than replacing anything: it\n// is one more thing this control says about itself, and it reads after the\n// caller's own description and before the row's message — the same\n// most-specific-first order `useFieldControl` applies one layer out. Wired\n// through `aria-describedby` rather than announced, which is TagsInput's call\n// for the same fact: it is heard on focus and on every return to the box, which\n// is when the question is asked, and a live region would speak the whole total\n// over the top of the `aria-selected` change the row itself already announces.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [attrs[\"aria-describedby\"] as string | undefined, props.multiple ? countId : \"\"]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n disabled: props.disabled,\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 input, and what\n// the input holds is the chosen option's *label*. A `name` on it would post\n// \"Viet Nam\" where the model carries `vn`. Reka submits the value itself\n// through a hidden control it renders from the root's own `name`, so that is\n// where the row's name goes. Everything else in the bag — the id, the merged\n// description, `aria-required`, `aria-invalid` — belongs on the\n// `role=\"combobox\"` node, which is the 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</script>\n\n<template>\n <!-- `open-on-click` and not `open-on-focus`: clicking the box is a reader\n asking to see the list, while focus arrives by Tab as often as by\n intent, and a list that unfolds over the next field every time someone\n tabs through a form is an obstruction. Typing opens it either way. -->\n <ComboboxRoot\n v-bind=\"optional({ modelValue, name: field.name })\"\n :multiple=\"multiple\"\n :disabled=\"field.disabled\"\n :ignore-filter=\"!filter\"\n open-on-click\n @update:model-value=\"onChoice\"\n @update:open=\"revealing = true\"\n >\n <ComboboxAnchor\n :data-disabled=\"field.disabled || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n comboboxVariants({ size }),\n // A box holding tokens is sized by them — see `multiSelectHeights`.\n // `h-auto` is what releases the fixed height the variant just set;\n // Tailwind's merge resolves two `h-*` utilities and leaves a `min-h-*`\n // beside either of them alone.\n multiple && ['h-auto', multiSelectHeights[size], 'gap-1.5'],\n !field.invalid && 'focus-within:shadow-halo',\n field.invalid && 'border-destructive focus-within:outline-destructive',\n // The box holds the chosen value, so it is drained rather than\n // dimmed: `opacity-50` composited the input's text down to 3.06:1,\n // and what a reader needs from an unavailable Combobox is precisely\n // what it is set to. The neutral well says unavailable on its own —\n // the input's own `disabled:text-muted-foreground` below carries the\n // text to 4.67:1 over it.\n //\n // The hairline recedes with the fill, and that second channel is the\n // point: unavailable is a difference in *shape* as well as in colour,\n // so it survives a reader who cannot resolve `muted` from\n // `background`. This control has no read-only state to be told apart\n // from — see the docblock — so it takes the unavailable row alone.\n field.disabled && 'cursor-not-allowed border-border bg-muted text-muted-foreground',\n attrs.class as string,\n )\n \"\n >\n <!-- The selection, as the tokens a TagsInput commits its values to, so a\n chosen set looks the same wherever a form shows one. Inert on\n purpose: the list is where a value is chosen and where it is\n unchosen, which keeps the whole control one Tab stop and keeps a\n value behind the overflow count reachable by the same gesture as a\n visible one. -->\n <template v-if=\"multiple\">\n <Chip\n v-for=\"token in visibleTokens\"\n :key=\"token.value\"\n size=\"sm\"\n :disabled=\"field.disabled\"\n class=\"min-w-0 animate-fade-rise\"\n >\n {{ token.label }}\n </Chip>\n <!-- Not a token: it stands for values rather than being one, and a\n fourth pill saying \"+47 more\" would read as a forty-eighth\n choice. -->\n <span v-if=\"hiddenTokens > 0\" class=\"tabular shrink-0 text-small text-muted-foreground\">\n {{ text.overflow({ hidden: hiddenTokens }) }}\n </span>\n </template>\n\n <ComboboxInput\n v-bind=\"{ ...inputAttrs, ...inputFieldAttrs }\"\n :model-value=\"query\"\n :display-value=\"displayValue\"\n :placeholder=\"multiple && tokens.length > 0 ? undefined : placeholder\"\n class=\"min-w-6 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:text-muted-foreground\"\n @update:model-value=\"onQuery\"\n />\n <!-- Reka takes this button out of the tab order itself, so the whole\n control is one Tab stop: the input. It also names it, in English —\n `aria-label=\"Show popup\"`, written inside its own render function\n with no prop to reach it by — and the binding below replaces that\n rather than competing with it, because Vue merges a fallthrough\n attribute onto the root vnode after the render function produced it.\n Removing it does not fall back to nothing; it falls back to Reka's\n English, which no test of Loom's own strings would catch. -->\n <ComboboxTrigger\n :aria-label=\"text.trigger\"\n class=\"group min-h-6 min-w-6 shrink-0 text-muted-foreground disabled:cursor-not-allowed\"\n >\n <ChevronDown\n class=\"h-4 w-4 transition-transform duration-fast ease-out group-data-[state=open]:rotate-180\"\n />\n </ComboboxTrigger>\n\n <!-- The size of the selection, and the only thing that carries it to a\n reader who cannot see the tokens: the visible row shows the first few\n and a count for the rest, and neither is announced. Reka publishes\n nothing of the sort — the listbox's `aria-multiselectable` says the\n control takes several values, never how many it holds. -->\n <span v-if=\"multiple\" :id=\"countId\" class=\"sr-only\">\n {{ text.count({ count: tokens.length }) }}\n </span>\n </ComboboxAnchor>\n\n <ComboboxPortal>\n <ComboboxContent\n position=\"popper\"\n :side-offset=\"6\"\n :class=\"\n cn(\n 'z-50 max-h-72 min-w-[var(--reka-combobox-trigger-width)] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md',\n // Scoped to the open state, never unconditional. Reka keeps the\n // closed content mounted while it waits for an animationend that a\n // mount-only animation never fires again — an invisible overlay\n // eating clicks. Scoping it makes the closed element compute\n // `animation-name: none`, which is the immediate-unmount branch.\n 'data-[state=open]:animate-fade-rise',\n )\n \"\n >\n <ComboboxViewport class=\"p-1\">\n <!-- A filter that matches nothing has to say so. An empty popover\n reads as a broken one, and a screen reader is told nothing at\n all — so the message is a row inside the listbox rather than a\n caption beside it. `role=\"option\"` because a bare div is not a\n legitimate child of `role=\"listbox\"` and is skipped; disabled\n because it is a statement, not a choice. -->\n <ComboboxEmpty\n role=\"option\"\n aria-disabled=\"true\"\n class=\"animate-fade-rise px-3 py-1.5 text-sm text-muted-foreground\"\n >\n {{ emptyMessage ?? text.empty }}\n </ComboboxEmpty>\n\n <!-- Keyed by value rather than by index: a row that survives a\n keystroke keeps its DOM node, so its entrance animation is not\n re-run and the list settles instead of flashing. Keyed by index,\n every row below a filtered-out one would be reused for different\n content while the tail remounted — an animation playing on the\n wrong rows.\n\n `text-value` pins the filter to the label. Left off, Reka falls\n back to whatever the row happens to render, which is the label\n today and the label plus a badge, a hint or a count the first\n time a row grows one. -->\n <ComboboxItem\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n :value=\"option.value\"\n :text-value=\"option.label\"\n :disabled=\"option.disabled ?? false\"\n :style=\"{ animationDelay: revealing ? listStaggerDelay(index) : '0ms' }\"\n :class=\"\n cn(\n 'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-3 py-1.5 pr-8 text-sm text-foreground outline-none',\n 'transition-colors duration-fast ease-out',\n 'animate-fade-rise',\n 'data-[highlighted]:bg-subtle',\n 'data-[state=checked]:bg-primary-muted data-[state=checked]:text-primary-text',\n // Inert, but still readable. `data-[disabled]:opacity-50` took\n // the label to 3.13:1 on the popover; the colour on the span\n // below leaves it at 5.76:1 and still visibly lighter than the\n // rows around it.\n 'data-[disabled]:pointer-events-none',\n )\n \"\n >\n <!-- The mute lives on this span rather than on the row, and it has\n to. `data-[disabled]:` sorts *before* `data-[state=checked]:`\n in Tailwind's variant order, so a disabled row that is also the\n chosen one would take `text-primary-text` from the container — and a\n container colour is only inherited. Declared here it wins\n whatever the row resolved to. -->\n <span :class=\"option.disabled && 'text-muted-foreground'\">{{ option.label }}</span>\n <ComboboxItemIndicator class=\"absolute right-2 inline-flex items-center\">\n <Check class=\"h-4 w-4\" />\n </ComboboxItemIndicator>\n </ComboboxItem>\n </ComboboxViewport>\n </ComboboxContent>\n </ComboboxPortal>\n </ComboboxRoot>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"Combobox.js","names":[],"sources":["../../../../../packages/primitives/combobox/src/Combobox.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { ComboboxLabels } 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, and\n * exported so a host can build a partial vocabulary against the real thing.\n */\nexport const COMBOBOX_LABELS: ComboboxLabels = {\n trigger: \"Show options\",\n empty: \"No results\",\n count: ({ count }) => `${String(count)} selected`,\n overflow: ({ hidden }) => `+${String(hidden)} more`,\n};\n\n/**\n * One row of the list. `value` is what the model carries; `label` is what a\n * reader sees, what the filter matches, and what the input shows once the row\n * is chosen.\n *\n * Identical to `SelectOption` today, and deliberately not the same\n * declaration: the two surfaces are independent, and the first field either\n * one grows — a secondary line here, a group key there — is a field the other\n * should not inherit by accident. A shared alias would make that change look\n * free when it is not.\n */\nexport interface ComboboxOption {\n /** The value bound to `v-model` when this row is chosen. Unique within the list. */\n value: string;\n /** The text shown on the row, matched by the filter, and put in the input once chosen. */\n label: string;\n /** Present but unchoosable — visible, skipped by the keyboard, unclickable. */\n disabled?: boolean;\n}\n\n/**\n * The control's height scale. It matches the text input and Select scales\n * exactly, so a form row mixing a Combobox beside either never comes out\n * uneven — which is why reaching past `size` for an `h-*` class is always the\n * wrong fix.\n */\nexport type ComboboxSize = \"sm\" | \"md\" | \"lg\";\n\nexport const comboboxVariants = cva(\n [\n \"flex w-full items-center gap-2 rounded-md border border-input bg-background text-foreground\",\n \"transition-[color,background-color,border-color,box-shadow] duration-fast ease-out\",\n // The focusable node is the input *inside* this box, so the ring is drawn\n // with `focus-within` rather than `focus-visible` — the same choice\n // TextField makes for the same reason, and what keeps the two controls\n // lighting up identically in a form row.\n \"focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring\",\n ],\n {\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 size: {\n sm: \"h-8 px-2.5 text-xs\",\n md: \"h-9 px-3 text-sm\",\n lg: \"h-11 px-4 text-base\",\n } satisfies Record<ComboboxSize, string>,\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * The same three numbers as a floor rather than as a fixed height, for the\n * multi-select box only.\n *\n * A box holding tokens is sized by its content, which is TagsInput's call and\n * the reason its field is `min-h-9`. What keeps that from turning into an\n * unbounded control here is `visibleValues`: the token row never wraps, so in\n * practice the box resolves to exactly the height above and a multi-select\n * still lines up with the single-select beside it. The `h-auto` that goes with\n * it in the template is what releases the fixed height `comboboxVariants`\n * already set — Tailwind's merge resolves the two, `min-h-*` does not.\n */\nconst multiSelectHeights = {\n sm: \"min-h-8\",\n md: \"min-h-9\",\n lg: \"min-h-11\",\n} satisfies Record<ComboboxSize, string>;\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from \"vue\";\nimport {\n ComboboxRoot,\n ComboboxAnchor,\n ComboboxInput,\n ComboboxTrigger,\n ComboboxPortal,\n ComboboxContent,\n ComboboxViewport,\n ComboboxItem,\n ComboboxItemIndicator,\n ComboboxEmpty,\n} from \"reka-ui\";\nimport { Check, ChevronDown } from \"@lucide/vue\";\nimport Chip from \"@ecoma-io/loom-chip\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { listStaggerDelay } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\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 * Combobox — a text input that narrows a list as you type, for the list too\n * long to scan. Countries, currencies, repositories, people: anywhere a reader\n * knows roughly what they are looking for and would rather type three letters\n * than scroll past two hundred rows.\n *\n * Its neighbour is `Select`, and the boundary is the length of the list rather\n * than the shape of the control. Three to fifteen rows a reader can read\n * through belong in a Select — a search box over eight options is a keystroke\n * charged for nothing. Two to five options that should all be visible at once\n * belong in a segmented control. Past roughly fifteen, searching beats\n * scrolling and this is the control that offers it.\n *\n * Built on Reka UI's Combobox, which supplies the editable-combobox semantics:\n * the `role=\"combobox\"` input wired to the listbox by `aria-expanded` and\n * `aria-controls`, `aria-autocomplete=\"list\"`, and the active row tracked by\n * `aria-activedescendant` while real focus stays in the input. That last part\n * is the one genuine behavioural difference from Select, which moves focus\n * onto the row itself — a combobox cannot, because the reader is still typing.\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` and the name the\n * chosen value is submitted under all arrive through `useFieldControl()`, so\n * `<Field label=\"Country\" error=\"…\"><Combobox … /></Field>` needs no attributes\n * at the call site. Both booleans below still win over the row when they are\n * set, in both directions — which is why they default to `undefined` rather\n * than `false`.\n *\n * A row's `readonly` is deliberately ignored. The text in the box is not the\n * value — it is the chosen option's label, rewritten on every choice — so a\n * `readonly` input here would freeze a caption rather than protect a value,\n * while the list underneath still opened on click. A choice nobody may change\n * is a disabled Combobox, or the label rendered as text.\n *\n * `multiple` turns it into a many-of-many picker. The chosen values render as\n * tokens in the box — the same `Chip` a\n * [TagsInput](../TagsInput/TagsInput.vue) commits its values to, so a selection\n * looks the same wherever a form shows one — and the input goes back to being\n * only a search box. The tokens are **inert**, deliberately: the list is where\n * a choice is made and where it is unmade, so the control stays the single Tab\n * stop it is in single-select, and a value hidden behind the overflow count is\n * reachable by exactly the same gesture as a visible one. That is the one place\n * this diverges from TagsInput, whose tokens carry their own remove control\n * because it has no list to send a reader back to.\n */\nconst props = withDefaults(\n defineProps<{\n /**\n * The chosen option's `value`, or every chosen value when `multiple`.\n *\n * The union is one prop rather than a generic component, and the invariant\n * is the mode: single-select emits and accepts a `string` and never an\n * array, multi-select an array and never a bare string. A generic would\n * express that in the type system and cost more than it buys — it would\n * print a conditional type in the generated API table below, and it would\n * break `InstanceType<typeof Combobox>`, which is how a consumer types a\n * template ref.\n */\n modelValue?: string | string[] | undefined;\n /** The rows, in the order they are shown. */\n options: ComboboxOption[];\n /** Lets several rows be chosen at once: chosen rows toggle instead of closing the list, and the box shows the selection as tokens. */\n multiple?: boolean;\n /**\n * How many tokens the box shows before the rest collapse into a count.\n *\n * The box must not grow with the selection — it sits in a form row beside\n * controls on a fixed height scale — so past this many the tail becomes one\n * `labels.overflow` summary: five chosen shows three and \"+2 more\", fifty\n * shows three and \"+47 more\". Raise it for a wide box; the row never wraps,\n * so a number the box has no room for is a number that truncates.\n */\n visibleValues?: number;\n /** Shown in the empty input, before anything is typed or chosen. */\n placeholder?: string;\n /** Shown as a row of its own when the filter matches nothing. Defaults to `labels.empty`, so say what was searched for rather than restating the default in a second language. */\n emptyMessage?: string | undefined;\n /** Unavailable: dims the control, refuses to open and refuses to take text. Unset defers to a wrapping Field. */\n disabled?: 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 /** Control height. It shares the text input scale so mixed form rows stay aligned. */\n size?: ComboboxSize;\n /**\n * Filter `options` against the typed text. Turn it off when the host is\n * producing the list itself — from a server, in response to\n * `update:query` — so a result that does not literally contain what was\n * typed is not then hidden a second time by a filter running underneath it.\n */\n filter?: boolean;\n /**\n * Names for the two strings this control supplies itself, as any subset of\n * `ComboboxLabels` — 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. `emptyMessage` is the shorter way to reach `empty` for one\n * box; this is what reaches `trigger`.\n */\n labels?: LabelOverrides<ComboboxLabels>;\n }>(),\n {\n // `modelValue: undefined` is load-bearing rather than redundant: paired\n // with `optional()` below it is what keeps the uncontrolled third state\n // reachable, instead of pinning the control to a value nobody chose.\n modelValue: undefined,\n multiple: false,\n // Three, and the number is a judgement rather than a measurement: two\n // tokens plus a count reads as a control that shows almost nothing, and\n // four leaves a `md` box no room to type in at the default width. It is a\n // prop because the right answer is the box's width, which only the caller\n // knows.\n visibleValues: 3,\n // No English default here any more. The wording moved into `COMBOBOX_LABELS`\n // so a host can set it once for the whole application rather than on every\n // box, and an absent prop is what lets the resolved label through.\n emptyMessage: undefined,\n // Not `false`, for either. `false` is a caller saying \"this control is fine\n // / enabled even though its row is not\"; absent is a caller saying nothing,\n // and only `undefined` survives to mean that past Vue's\n // absent-Boolean-casts-to-false rule — see TextField.vue, which carries the\n // full reasoning.\n disabled: undefined,\n invalid: undefined,\n size: \"md\",\n filter: true,\n },\n);\n\nconst emit = defineEmits<{\n /** The chosen option's `value` — or the whole chosen list, when `multiple`. Single-select fires once per choice and the list closes with it; multi-select fires once per toggle and the list stays open. */\n \"update:modelValue\": [value: string | string[]];\n /** The text now in the input — as the reader types, and again when a choice puts its label there. */\n \"update:query\": [query: string];\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(\"combobox\", COMBOBOX_LABELS, () => props.labels);\n\nconst query = ref(\"\");\n\n/**\n * The stagger is the list's *reveal*, and re-filtering is not a reveal. This\n * is true from the moment the list opens until the first keystroke, after\n * which every row arrives immediately.\n *\n * Two things go wrong without it. A delay recomputed per keystroke makes the\n * list feel like it is rebuilding under the reader's fingers — the opposite of\n * what a filter is for. And raising `animation-delay` on a row that survived\n * the keystroke rewinds its entrance: the row is still mounted, so the\n * animation is not restarted, it is re-timed, and a row that had finished\n * fading in drops back to half-opacity. Going the other way — stagger to `0ms`\n * — only ever fast-forwards, which is why this flag falls and does not rise\n * again until the content has unmounted and remounted with the next open.\n */\nconst revealing = ref(true);\n\nconst countId = useId();\n\n/**\n * The multi-select's own copy of the selection, and the reason it keeps one:\n * the tokens have to render whether or not the host bound `v-model`.\n *\n * Left uncontrolled, `modelValue` stays `undefined` while Reka manages the\n * choice internally — which is the third state `optional()` exists to preserve\n * — so a trigger reading the prop would sit empty while the list showed three\n * checked rows. This mirrors rather than drives: it is never fed back into\n * Reka, so nothing about the uncontrolled control changes.\n *\n * Seeded from the prop rather than sanitised, for TagsInput's reason: a list\n * the host already holds is the host's to hold.\n */\nconst selected = ref<string[]>(Array.isArray(props.modelValue) ? [...props.modelValue] : []);\nwatch(\n () => props.modelValue,\n (next) => {\n if (Array.isArray(next)) selected.value = [...next];\n },\n // Shallow, deliberately: the contract is whole-list replacement — Reka hands\n // back the complete selection on every toggle and a host echoes it straight\n // back down — so in-place mutation is not a case this mirror owes anyone.\n);\n\n/**\n * The chosen values as the box shows them, and the fallback is the point: a\n * host driving `options` from a search hands back only the rows matching the\n * current query, so a value chosen from an earlier one has no option to look\n * its label up in. Falling back to the value keeps the token on screen — a\n * chosen value that vanished from the trigger while it was still in the model\n * would be the control lying about what it holds.\n */\nconst tokens = computed(() =>\n selected.value.map((value) => ({\n value,\n label: props.options.find((option) => option.value === value)?.label ?? value,\n })),\n);\n\nconst visibleTokens = computed(() => tokens.value.slice(0, Math.max(0, props.visibleValues)));\nconst hiddenTokens = computed(() => tokens.value.length - visibleTokens.value.length);\n\n/**\n * What the input shows once a row is chosen. Without this Reka falls back to\n * the model value's own `toString()`, which would leave `en` in the box where\n * the reader picked \"English\".\n *\n * A multi-select shows nothing: the tokens are the selection, and the input\n * beside them is a search box. Reka calls this on close rather than on choose,\n * so the query the reader typed survives every toggle and one search can pick\n * several rows — which is the whole reason the list stays open.\n */\nfunction displayValue(value: unknown): string {\n if (props.multiple) return \"\";\n return props.options.find((option) => option.value === value)?.label ?? \"\";\n}\n\n/**\n * Reka hands back the whole selection on every toggle, never a delta, so the\n * emitted array is always the complete list — the same contract TagsInput's\n * `update:modelValue` carries, and the reason a host can assign it straight\n * back.\n */\nfunction onChoice(value: unknown): void {\n if (!props.multiple) {\n emit(\"update:modelValue\", String(value));\n return;\n }\n const values = Array.isArray(value) ? value.map(String) : [];\n selected.value = values;\n emit(\"update:modelValue\", values);\n}\n\nfunction onQuery(text: string): void {\n // Reka rewrites the input's text on its own account — once as the component\n // mounts, and again when a choice puts the chosen label back in the box —\n // and each of those arrives here as an update. Reporting a query that did\n // not change would have a host running the same search twice on mount, so\n // only a real change is passed on.\n if (text === query.value) return;\n query.value = text;\n revealing.value = false;\n emit(\"update:query\", text);\n}\n\n// The root renders no DOM node of its own, so fallthrough attributes would be\n// dropped on the floor. They go to the input, which is the element they\n// describe and the only one a name can land on — the chevron trigger already\n// carries Reka's own `aria-label`, and naming the control means naming the\n// `role=\"combobox\"` node.\n//\n// `class` is pulled out of that spread and merged through `cn()` onto the\n// anchor instead, because the anchor is the box a caller means when they pass\n// `w-64`. Spreading it alongside the anchor's own `:class` would only\n// concatenate the two lists — Vue's merge is generic, not Tailwind-aware — so\n// a caller's width would win or lose depending on which utility Tailwind\n// happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\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`,\n// `readonly` and `required` are absent from this object because there is no\n// prop to oppose the row with; the row's answer stands unopposed, which is\n// what is wanted.\n//\n// The multi-select's count joins that list rather than replacing anything: it\n// is one more thing this control says about itself, and it reads after the\n// caller's own description and before the row's message — the same\n// most-specific-first order `useFieldControl` applies one layer out. Wired\n// through `aria-describedby` rather than announced, which is TagsInput's call\n// for the same fact: it is heard on focus and on every return to the box, which\n// is when the question is asked, and a live region would speak the whole total\n// over the top of the `aria-selected` change the row itself already announces.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [attrs[\"aria-describedby\"] as string | undefined, props.multiple ? countId : \"\"]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n disabled: props.disabled,\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 input, and what\n// the input holds is the chosen option's *label*. A `name` on it would post\n// \"Viet Nam\" where the model carries `vn`. Reka submits the value itself\n// through a hidden control it renders from the root's own `name`, so that is\n// where the row's name goes. Everything else in the bag — the id, the merged\n// description, `aria-required`, `aria-invalid` — belongs on the\n// `role=\"combobox\"` node, which is the 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</script>\n\n<template>\n <!-- `open-on-click` and not `open-on-focus`: clicking the box is a reader\n asking to see the list, while focus arrives by Tab as often as by\n intent, and a list that unfolds over the next field every time someone\n tabs through a form is an obstruction. Typing opens it either way. -->\n <ComboboxRoot\n v-bind=\"optional({ modelValue, name: field.name })\"\n :multiple=\"multiple\"\n :disabled=\"field.disabled\"\n :ignore-filter=\"!filter\"\n open-on-click\n @update:model-value=\"onChoice\"\n @update:open=\"revealing = true\"\n >\n <ComboboxAnchor\n :data-disabled=\"field.disabled || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n comboboxVariants({ size }),\n // A box holding tokens is sized by them — see `multiSelectHeights`.\n // `h-auto` is what releases the fixed height the variant just set;\n // Tailwind's merge resolves two `h-*` utilities and leaves a `min-h-*`\n // beside either of them alone.\n multiple && ['h-auto', multiSelectHeights[size], 'gap-1.5'],\n !field.invalid && 'focus-within:shadow-halo',\n field.invalid && 'border-destructive focus-within:outline-destructive',\n // The box holds the chosen value, so it is drained rather than\n // dimmed: `opacity-50` composited the input's text down to 3.06:1,\n // and what a reader needs from an unavailable Combobox is precisely\n // what it is set to. The neutral well says unavailable on its own —\n // the input's own `disabled:text-muted-foreground` below carries the\n // text to 4.67:1 over it.\n //\n // The hairline recedes with the fill, and that second channel is the\n // point: unavailable is a difference in *shape* as well as in colour,\n // so it survives a reader who cannot resolve `muted` from\n // `background`. This control has no read-only state to be told apart\n // from — see the docblock — so it takes the unavailable row alone.\n field.disabled && 'cursor-not-allowed border-border bg-muted text-muted-foreground',\n attrs.class as string,\n )\n \"\n >\n <!-- The selection, as the tokens a TagsInput commits its values to, so a\n chosen set looks the same wherever a form shows one. Inert on\n purpose: the list is where a value is chosen and where it is\n unchosen, which keeps the whole control one Tab stop and keeps a\n value behind the overflow count reachable by the same gesture as a\n visible one. -->\n <template v-if=\"multiple\">\n <Chip\n v-for=\"token in visibleTokens\"\n :key=\"token.value\"\n size=\"sm\"\n :disabled=\"field.disabled\"\n class=\"min-w-0 animate-fade-rise\"\n >\n {{ token.label }}\n </Chip>\n <!-- Not a token: it stands for values rather than being one, and a\n fourth pill saying \"+47 more\" would read as a forty-eighth\n choice. -->\n <span v-if=\"hiddenTokens > 0\" class=\"tabular shrink-0 text-small text-muted-foreground\">\n {{ text.overflow({ hidden: hiddenTokens }) }}\n </span>\n </template>\n\n <ComboboxInput\n v-bind=\"{ ...inputAttrs, ...inputFieldAttrs }\"\n :model-value=\"query\"\n :display-value=\"displayValue\"\n :placeholder=\"multiple && tokens.length > 0 ? undefined : placeholder\"\n class=\"min-w-6 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:text-muted-foreground\"\n @update:model-value=\"onQuery\"\n />\n <!-- Reka takes this button out of the tab order itself, so the whole\n control is one Tab stop: the input. It also names it, in English —\n `aria-label=\"Show popup\"`, written inside its own render function\n with no prop to reach it by — and the binding below replaces that\n rather than competing with it, because Vue merges a fallthrough\n attribute onto the root vnode after the render function produced it.\n Removing it does not fall back to nothing; it falls back to Reka's\n English, which no test of Loom's own strings would catch. -->\n <ComboboxTrigger\n :aria-label=\"text.trigger\"\n class=\"group min-h-6 min-w-6 shrink-0 text-muted-foreground disabled:cursor-not-allowed\"\n >\n <ChevronDown\n class=\"h-4 w-4 transition-transform duration-fast ease-out group-data-[state=open]:rotate-180\"\n />\n </ComboboxTrigger>\n\n <!-- The size of the selection, and the only thing that carries it to a\n reader who cannot see the tokens: the visible row shows the first few\n and a count for the rest, and neither is announced. Reka publishes\n nothing of the sort — the listbox's `aria-multiselectable` says the\n control takes several values, never how many it holds. -->\n <span v-if=\"multiple\" :id=\"countId\" class=\"sr-only\">\n {{ text.count({ count: tokens.length }) }}\n </span>\n </ComboboxAnchor>\n\n <ComboboxPortal>\n <ComboboxContent\n position=\"popper\"\n :side-offset=\"6\"\n :class=\"\n cn(\n 'z-overlay max-h-72 min-w-[var(--reka-combobox-trigger-width)] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md',\n // Each half of the motion is scoped to its own state, never\n // unconditional. Closing swaps the computed animation-name to\n // fade-fall's, which is what Reka's Presence reads: it holds the\n // panel mounted until that exit's animationend and only then takes\n // it away. Unconditional, a finished entrance animation sat on the\n // closed element and stranded it invisible over the page, eating\n // clicks.\n 'data-[state=open]:animate-fade-rise data-[state=closed]:animate-fade-fall',\n )\n \"\n >\n <ComboboxViewport class=\"p-1\">\n <!-- A filter that matches nothing has to say so. An empty popover\n reads as a broken one, and a screen reader is told nothing at\n all — so the message is a row inside the listbox rather than a\n caption beside it. `role=\"option\"` because a bare div is not a\n legitimate child of `role=\"listbox\"` and is skipped; disabled\n because it is a statement, not a choice. -->\n <ComboboxEmpty\n role=\"option\"\n aria-disabled=\"true\"\n class=\"animate-fade-rise px-3 py-1.5 text-sm text-muted-foreground\"\n >\n {{ emptyMessage ?? text.empty }}\n </ComboboxEmpty>\n\n <!-- Keyed by value rather than by index: a row that survives a\n keystroke keeps its DOM node, so its entrance animation is not\n re-run and the list settles instead of flashing. Keyed by index,\n every row below a filtered-out one would be reused for different\n content while the tail remounted — an animation playing on the\n wrong rows.\n\n `text-value` pins the filter to the label. Left off, Reka falls\n back to whatever the row happens to render, which is the label\n today and the label plus a badge, a hint or a count the first\n time a row grows one. -->\n <ComboboxItem\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n :value=\"option.value\"\n :text-value=\"option.label\"\n :disabled=\"option.disabled ?? false\"\n :style=\"{ animationDelay: revealing ? listStaggerDelay(index) : '0ms' }\"\n :class=\"\n cn(\n 'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-3 py-1.5 pr-8 text-sm text-foreground outline-none',\n 'transition-colors duration-fast ease-out',\n 'animate-fade-rise',\n 'data-[highlighted]:bg-subtle',\n 'data-[state=checked]:bg-primary-muted data-[state=checked]:text-primary-text',\n // Inert, but still readable. `data-[disabled]:opacity-50` took\n // the label to 3.13:1 on the popover; the colour on the span\n // below leaves it at 5.76:1 and still visibly lighter than the\n // rows around it.\n 'data-[disabled]:pointer-events-none',\n )\n \"\n >\n <!-- The mute lives on this span rather than on the row, and it has\n to. `data-[disabled]:` sorts *before* `data-[state=checked]:`\n in Tailwind's variant order, so a disabled row that is also the\n chosen one would take `text-primary-text` from the container — and a\n container colour is only inherited. Declared here it wins\n whatever the row resolved to. -->\n <span :class=\"option.disabled && 'text-muted-foreground'\">{{ option.label }}</span>\n <ComboboxItemIndicator class=\"absolute right-2 inline-flex items-center\">\n <Check class=\"h-4 w-4\" />\n </ComboboxItemIndicator>\n </ComboboxItem>\n </ComboboxViewport>\n </ComboboxContent>\n </ComboboxPortal>\n </ComboboxRoot>\n</template>\n"],"mappings":""}
@@ -1,5 +1,5 @@
1
- import { type LabelOverrides } from "@ecoma-io/loom-labels";
2
- import type { ComboboxLabels } from "@ecoma-io/loom-labels";
1
+ import { type LabelOverrides } from "../../../labels/src/index";
2
+ import type { ComboboxLabels } from "../../../labels/src/index";
3
3
  /**
4
4
 
5
5
  /**
@@ -159,7 +159,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
159
159
  invalid: boolean | undefined;
160
160
  modelValue: string | string[] | undefined;
161
161
  multiple: boolean;
162
- visibleValues: number;
163
162
  emptyMessage: string | undefined;
163
+ visibleValues: number;
164
164
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
165
165
  //# sourceMappingURL=Combobox.vue.d.ts.map
@@ -125,7 +125,7 @@ var Combobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineC
125
125
  const selected = ref(Array.isArray(props.modelValue) ? [...props.modelValue] : []);
126
126
  watch(() => props.modelValue, (next) => {
127
127
  if (Array.isArray(next)) selected.value = [...next];
128
- }, { deep: true });
128
+ });
129
129
  /**
130
130
  * The chosen values as the box shows them, and the fallback is the point: a
131
131
  * host driving `options` from a search hands back only the rows matching the
@@ -252,7 +252,7 @@ var Combobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineC
252
252
  default: withCtx(() => [createVNode(unref(ComboboxContent), {
253
253
  position: "popper",
254
254
  "side-offset": 6,
255
- class: normalizeClass(unref(cn)("z-50 max-h-72 min-w-[var(--reka-combobox-trigger-width)] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md", "data-[state=open]:animate-fade-rise"))
255
+ class: normalizeClass(unref(cn)("z-overlay max-h-72 min-w-[var(--reka-combobox-trigger-width)] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md", "data-[state=open]:animate-fade-rise data-[state=closed]:animate-fade-fall"))
256
256
  }, {
257
257
  default: withCtx(() => [createVNode(unref(ComboboxViewport), { class: "p-1" }, {
258
258
  default: withCtx(() => [createVNode(unref(ComboboxEmpty), {
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../../../packages/primitives/combobox/src/Combobox.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { ComboboxLabels } 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, and\n * exported so a host can build a partial vocabulary against the real thing.\n */\nexport const COMBOBOX_LABELS: ComboboxLabels = {\n trigger: \"Show options\",\n empty: \"No results\",\n count: ({ count }) => `${String(count)} selected`,\n overflow: ({ hidden }) => `+${String(hidden)} more`,\n};\n\n/**\n * One row of the list. `value` is what the model carries; `label` is what a\n * reader sees, what the filter matches, and what the input shows once the row\n * is chosen.\n *\n * Identical to `SelectOption` today, and deliberately not the same\n * declaration: the two surfaces are independent, and the first field either\n * one grows — a secondary line here, a group key there — is a field the other\n * should not inherit by accident. A shared alias would make that change look\n * free when it is not.\n */\nexport interface ComboboxOption {\n /** The value bound to `v-model` when this row is chosen. Unique within the list. */\n value: string;\n /** The text shown on the row, matched by the filter, and put in the input once chosen. */\n label: string;\n /** Present but unchoosable — visible, skipped by the keyboard, unclickable. */\n disabled?: boolean;\n}\n\n/**\n * The control's height scale. It matches the text input and Select scales\n * exactly, so a form row mixing a Combobox beside either never comes out\n * uneven — which is why reaching past `size` for an `h-*` class is always the\n * wrong fix.\n */\nexport type ComboboxSize = \"sm\" | \"md\" | \"lg\";\n\nexport const comboboxVariants = cva(\n [\n \"flex w-full items-center gap-2 rounded-md border border-input bg-background text-foreground\",\n \"transition-[color,background-color,border-color,box-shadow] duration-fast ease-out\",\n // The focusable node is the input *inside* this box, so the ring is drawn\n // with `focus-within` rather than `focus-visible` — the same choice\n // TextField makes for the same reason, and what keeps the two controls\n // lighting up identically in a form row.\n \"focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring\",\n ],\n {\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 size: {\n sm: \"h-8 px-2.5 text-xs\",\n md: \"h-9 px-3 text-sm\",\n lg: \"h-11 px-4 text-base\",\n } satisfies Record<ComboboxSize, string>,\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * The same three numbers as a floor rather than as a fixed height, for the\n * multi-select box only.\n *\n * A box holding tokens is sized by its content, which is TagsInput's call and\n * the reason its field is `min-h-9`. What keeps that from turning into an\n * unbounded control here is `visibleValues`: the token row never wraps, so in\n * practice the box resolves to exactly the height above and a multi-select\n * still lines up with the single-select beside it. The `h-auto` that goes with\n * it in the template is what releases the fixed height `comboboxVariants`\n * already set — Tailwind's merge resolves the two, `min-h-*` does not.\n */\nconst multiSelectHeights = {\n sm: \"min-h-8\",\n md: \"min-h-9\",\n lg: \"min-h-11\",\n} satisfies Record<ComboboxSize, string>;\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from \"vue\";\nimport {\n ComboboxRoot,\n ComboboxAnchor,\n ComboboxInput,\n ComboboxTrigger,\n ComboboxPortal,\n ComboboxContent,\n ComboboxViewport,\n ComboboxItem,\n ComboboxItemIndicator,\n ComboboxEmpty,\n} from \"reka-ui\";\nimport { Check, ChevronDown } from \"@lucide/vue\";\nimport Chip from \"@ecoma-io/loom-chip\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { listStaggerDelay } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\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 * Combobox — a text input that narrows a list as you type, for the list too\n * long to scan. Countries, currencies, repositories, people: anywhere a reader\n * knows roughly what they are looking for and would rather type three letters\n * than scroll past two hundred rows.\n *\n * Its neighbour is `Select`, and the boundary is the length of the list rather\n * than the shape of the control. Three to fifteen rows a reader can read\n * through belong in a Select — a search box over eight options is a keystroke\n * charged for nothing. Two to five options that should all be visible at once\n * belong in a segmented control. Past roughly fifteen, searching beats\n * scrolling and this is the control that offers it.\n *\n * Built on Reka UI's Combobox, which supplies the editable-combobox semantics:\n * the `role=\"combobox\"` input wired to the listbox by `aria-expanded` and\n * `aria-controls`, `aria-autocomplete=\"list\"`, and the active row tracked by\n * `aria-activedescendant` while real focus stays in the input. That last part\n * is the one genuine behavioural difference from Select, which moves focus\n * onto the row itself — a combobox cannot, because the reader is still typing.\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` and the name the\n * chosen value is submitted under all arrive through `useFieldControl()`, so\n * `<Field label=\"Country\" error=\"…\"><Combobox … /></Field>` needs no attributes\n * at the call site. Both booleans below still win over the row when they are\n * set, in both directions — which is why they default to `undefined` rather\n * than `false`.\n *\n * A row's `readonly` is deliberately ignored. The text in the box is not the\n * value — it is the chosen option's label, rewritten on every choice — so a\n * `readonly` input here would freeze a caption rather than protect a value,\n * while the list underneath still opened on click. A choice nobody may change\n * is a disabled Combobox, or the label rendered as text.\n *\n * `multiple` turns it into a many-of-many picker. The chosen values render as\n * tokens in the box — the same `Chip` a\n * [TagsInput](../TagsInput/TagsInput.vue) commits its values to, so a selection\n * looks the same wherever a form shows one — and the input goes back to being\n * only a search box. The tokens are **inert**, deliberately: the list is where\n * a choice is made and where it is unmade, so the control stays the single Tab\n * stop it is in single-select, and a value hidden behind the overflow count is\n * reachable by exactly the same gesture as a visible one. That is the one place\n * this diverges from TagsInput, whose tokens carry their own remove control\n * because it has no list to send a reader back to.\n */\nconst props = withDefaults(\n defineProps<{\n /**\n * The chosen option's `value`, or every chosen value when `multiple`.\n *\n * The union is one prop rather than a generic component, and the invariant\n * is the mode: single-select emits and accepts a `string` and never an\n * array, multi-select an array and never a bare string. A generic would\n * express that in the type system and cost more than it buys — it would\n * print a conditional type in the generated API table below, and it would\n * break `InstanceType<typeof Combobox>`, which is how a consumer types a\n * template ref.\n */\n modelValue?: string | string[] | undefined;\n /** The rows, in the order they are shown. */\n options: ComboboxOption[];\n /** Lets several rows be chosen at once: chosen rows toggle instead of closing the list, and the box shows the selection as tokens. */\n multiple?: boolean;\n /**\n * How many tokens the box shows before the rest collapse into a count.\n *\n * The box must not grow with the selection — it sits in a form row beside\n * controls on a fixed height scale — so past this many the tail becomes one\n * `labels.overflow` summary: five chosen shows three and \"+2 more\", fifty\n * shows three and \"+47 more\". Raise it for a wide box; the row never wraps,\n * so a number the box has no room for is a number that truncates.\n */\n visibleValues?: number;\n /** Shown in the empty input, before anything is typed or chosen. */\n placeholder?: string;\n /** Shown as a row of its own when the filter matches nothing. Defaults to `labels.empty`, so say what was searched for rather than restating the default in a second language. */\n emptyMessage?: string | undefined;\n /** Unavailable: dims the control, refuses to open and refuses to take text. Unset defers to a wrapping Field. */\n disabled?: 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 /** Control height. It shares the text input scale so mixed form rows stay aligned. */\n size?: ComboboxSize;\n /**\n * Filter `options` against the typed text. Turn it off when the host is\n * producing the list itself — from a server, in response to\n * `update:query` — so a result that does not literally contain what was\n * typed is not then hidden a second time by a filter running underneath it.\n */\n filter?: boolean;\n /**\n * Names for the two strings this control supplies itself, as any subset of\n * `ComboboxLabels` — 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. `emptyMessage` is the shorter way to reach `empty` for one\n * box; this is what reaches `trigger`.\n */\n labels?: LabelOverrides<ComboboxLabels>;\n }>(),\n {\n // `modelValue: undefined` is load-bearing rather than redundant: paired\n // with `optional()` below it is what keeps the uncontrolled third state\n // reachable, instead of pinning the control to a value nobody chose.\n modelValue: undefined,\n multiple: false,\n // Three, and the number is a judgement rather than a measurement: two\n // tokens plus a count reads as a control that shows almost nothing, and\n // four leaves a `md` box no room to type in at the default width. It is a\n // prop because the right answer is the box's width, which only the caller\n // knows.\n visibleValues: 3,\n // No English default here any more. The wording moved into `COMBOBOX_LABELS`\n // so a host can set it once for the whole application rather than on every\n // box, and an absent prop is what lets the resolved label through.\n emptyMessage: undefined,\n // Not `false`, for either. `false` is a caller saying \"this control is fine\n // / enabled even though its row is not\"; absent is a caller saying nothing,\n // and only `undefined` survives to mean that past Vue's\n // absent-Boolean-casts-to-false rule — see TextField.vue, which carries the\n // full reasoning.\n disabled: undefined,\n invalid: undefined,\n size: \"md\",\n filter: true,\n },\n);\n\nconst emit = defineEmits<{\n /** The chosen option's `value` — or the whole chosen list, when `multiple`. Single-select fires once per choice and the list closes with it; multi-select fires once per toggle and the list stays open. */\n \"update:modelValue\": [value: string | string[]];\n /** The text now in the input — as the reader types, and again when a choice puts its label there. */\n \"update:query\": [query: string];\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(\"combobox\", COMBOBOX_LABELS, () => props.labels);\n\nconst query = ref(\"\");\n\n/**\n * The stagger is the list's *reveal*, and re-filtering is not a reveal. This\n * is true from the moment the list opens until the first keystroke, after\n * which every row arrives immediately.\n *\n * Two things go wrong without it. A delay recomputed per keystroke makes the\n * list feel like it is rebuilding under the reader's fingers — the opposite of\n * what a filter is for. And raising `animation-delay` on a row that survived\n * the keystroke rewinds its entrance: the row is still mounted, so the\n * animation is not restarted, it is re-timed, and a row that had finished\n * fading in drops back to half-opacity. Going the other way — stagger to `0ms`\n * — only ever fast-forwards, which is why this flag falls and does not rise\n * again until the content has unmounted and remounted with the next open.\n */\nconst revealing = ref(true);\n\nconst countId = useId();\n\n/**\n * The multi-select's own copy of the selection, and the reason it keeps one:\n * the tokens have to render whether or not the host bound `v-model`.\n *\n * Left uncontrolled, `modelValue` stays `undefined` while Reka manages the\n * choice internally — which is the third state `optional()` exists to preserve\n * — so a trigger reading the prop would sit empty while the list showed three\n * checked rows. This mirrors rather than drives: it is never fed back into\n * Reka, so nothing about the uncontrolled control changes.\n *\n * Seeded from the prop rather than sanitised, for TagsInput's reason: a list\n * the host already holds is the host's to hold.\n */\nconst selected = ref<string[]>(Array.isArray(props.modelValue) ? [...props.modelValue] : []);\nwatch(\n () => props.modelValue,\n (next) => {\n if (Array.isArray(next)) selected.value = [...next];\n },\n { deep: true },\n);\n\n/**\n * The chosen values as the box shows them, and the fallback is the point: a\n * host driving `options` from a search hands back only the rows matching the\n * current query, so a value chosen from an earlier one has no option to look\n * its label up in. Falling back to the value keeps the token on screen — a\n * chosen value that vanished from the trigger while it was still in the model\n * would be the control lying about what it holds.\n */\nconst tokens = computed(() =>\n selected.value.map((value) => ({\n value,\n label: props.options.find((option) => option.value === value)?.label ?? value,\n })),\n);\n\nconst visibleTokens = computed(() => tokens.value.slice(0, Math.max(0, props.visibleValues)));\nconst hiddenTokens = computed(() => tokens.value.length - visibleTokens.value.length);\n\n/**\n * What the input shows once a row is chosen. Without this Reka falls back to\n * the model value's own `toString()`, which would leave `en` in the box where\n * the reader picked \"English\".\n *\n * A multi-select shows nothing: the tokens are the selection, and the input\n * beside them is a search box. Reka calls this on close rather than on choose,\n * so the query the reader typed survives every toggle and one search can pick\n * several rows — which is the whole reason the list stays open.\n */\nfunction displayValue(value: unknown): string {\n if (props.multiple) return \"\";\n return props.options.find((option) => option.value === value)?.label ?? \"\";\n}\n\n/**\n * Reka hands back the whole selection on every toggle, never a delta, so the\n * emitted array is always the complete list — the same contract TagsInput's\n * `update:modelValue` carries, and the reason a host can assign it straight\n * back.\n */\nfunction onChoice(value: unknown): void {\n if (!props.multiple) {\n emit(\"update:modelValue\", String(value));\n return;\n }\n const values = Array.isArray(value) ? value.map(String) : [];\n selected.value = values;\n emit(\"update:modelValue\", values);\n}\n\nfunction onQuery(text: string): void {\n // Reka rewrites the input's text on its own account — once as the component\n // mounts, and again when a choice puts the chosen label back in the box —\n // and each of those arrives here as an update. Reporting a query that did\n // not change would have a host running the same search twice on mount, so\n // only a real change is passed on.\n if (text === query.value) return;\n query.value = text;\n revealing.value = false;\n emit(\"update:query\", text);\n}\n\n// The root renders no DOM node of its own, so fallthrough attributes would be\n// dropped on the floor. They go to the input, which is the element they\n// describe and the only one a name can land on — the chevron trigger already\n// carries Reka's own `aria-label`, and naming the control means naming the\n// `role=\"combobox\"` node.\n//\n// `class` is pulled out of that spread and merged through `cn()` onto the\n// anchor instead, because the anchor is the box a caller means when they pass\n// `w-64`. Spreading it alongside the anchor's own `:class` would only\n// concatenate the two lists — Vue's merge is generic, not Tailwind-aware — so\n// a caller's width would win or lose depending on which utility Tailwind\n// happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\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`,\n// `readonly` and `required` are absent from this object because there is no\n// prop to oppose the row with; the row's answer stands unopposed, which is\n// what is wanted.\n//\n// The multi-select's count joins that list rather than replacing anything: it\n// is one more thing this control says about itself, and it reads after the\n// caller's own description and before the row's message — the same\n// most-specific-first order `useFieldControl` applies one layer out. Wired\n// through `aria-describedby` rather than announced, which is TagsInput's call\n// for the same fact: it is heard on focus and on every return to the box, which\n// is when the question is asked, and a live region would speak the whole total\n// over the top of the `aria-selected` change the row itself already announces.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [attrs[\"aria-describedby\"] as string | undefined, props.multiple ? countId : \"\"]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n disabled: props.disabled,\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 input, and what\n// the input holds is the chosen option's *label*. A `name` on it would post\n// \"Viet Nam\" where the model carries `vn`. Reka submits the value itself\n// through a hidden control it renders from the root's own `name`, so that is\n// where the row's name goes. Everything else in the bag — the id, the merged\n// description, `aria-required`, `aria-invalid` — belongs on the\n// `role=\"combobox\"` node, which is the 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</script>\n\n<template>\n <!-- `open-on-click` and not `open-on-focus`: clicking the box is a reader\n asking to see the list, while focus arrives by Tab as often as by\n intent, and a list that unfolds over the next field every time someone\n tabs through a form is an obstruction. Typing opens it either way. -->\n <ComboboxRoot\n v-bind=\"optional({ modelValue, name: field.name })\"\n :multiple=\"multiple\"\n :disabled=\"field.disabled\"\n :ignore-filter=\"!filter\"\n open-on-click\n @update:model-value=\"onChoice\"\n @update:open=\"revealing = true\"\n >\n <ComboboxAnchor\n :data-disabled=\"field.disabled || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n comboboxVariants({ size }),\n // A box holding tokens is sized by them — see `multiSelectHeights`.\n // `h-auto` is what releases the fixed height the variant just set;\n // Tailwind's merge resolves two `h-*` utilities and leaves a `min-h-*`\n // beside either of them alone.\n multiple && ['h-auto', multiSelectHeights[size], 'gap-1.5'],\n !field.invalid && 'focus-within:shadow-halo',\n field.invalid && 'border-destructive focus-within:outline-destructive',\n // The box holds the chosen value, so it is drained rather than\n // dimmed: `opacity-50` composited the input's text down to 3.06:1,\n // and what a reader needs from an unavailable Combobox is precisely\n // what it is set to. The neutral well says unavailable on its own —\n // the input's own `disabled:text-muted-foreground` below carries the\n // text to 4.67:1 over it.\n //\n // The hairline recedes with the fill, and that second channel is the\n // point: unavailable is a difference in *shape* as well as in colour,\n // so it survives a reader who cannot resolve `muted` from\n // `background`. This control has no read-only state to be told apart\n // from — see the docblock — so it takes the unavailable row alone.\n field.disabled && 'cursor-not-allowed border-border bg-muted text-muted-foreground',\n attrs.class as string,\n )\n \"\n >\n <!-- The selection, as the tokens a TagsInput commits its values to, so a\n chosen set looks the same wherever a form shows one. Inert on\n purpose: the list is where a value is chosen and where it is\n unchosen, which keeps the whole control one Tab stop and keeps a\n value behind the overflow count reachable by the same gesture as a\n visible one. -->\n <template v-if=\"multiple\">\n <Chip\n v-for=\"token in visibleTokens\"\n :key=\"token.value\"\n size=\"sm\"\n :disabled=\"field.disabled\"\n class=\"min-w-0 animate-fade-rise\"\n >\n {{ token.label }}\n </Chip>\n <!-- Not a token: it stands for values rather than being one, and a\n fourth pill saying \"+47 more\" would read as a forty-eighth\n choice. -->\n <span v-if=\"hiddenTokens > 0\" class=\"tabular shrink-0 text-small text-muted-foreground\">\n {{ text.overflow({ hidden: hiddenTokens }) }}\n </span>\n </template>\n\n <ComboboxInput\n v-bind=\"{ ...inputAttrs, ...inputFieldAttrs }\"\n :model-value=\"query\"\n :display-value=\"displayValue\"\n :placeholder=\"multiple && tokens.length > 0 ? undefined : placeholder\"\n class=\"min-w-6 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:text-muted-foreground\"\n @update:model-value=\"onQuery\"\n />\n <!-- Reka takes this button out of the tab order itself, so the whole\n control is one Tab stop: the input. It also names it, in English —\n `aria-label=\"Show popup\"`, written inside its own render function\n with no prop to reach it by — and the binding below replaces that\n rather than competing with it, because Vue merges a fallthrough\n attribute onto the root vnode after the render function produced it.\n Removing it does not fall back to nothing; it falls back to Reka's\n English, which no test of Loom's own strings would catch. -->\n <ComboboxTrigger\n :aria-label=\"text.trigger\"\n class=\"group min-h-6 min-w-6 shrink-0 text-muted-foreground disabled:cursor-not-allowed\"\n >\n <ChevronDown\n class=\"h-4 w-4 transition-transform duration-fast ease-out group-data-[state=open]:rotate-180\"\n />\n </ComboboxTrigger>\n\n <!-- The size of the selection, and the only thing that carries it to a\n reader who cannot see the tokens: the visible row shows the first few\n and a count for the rest, and neither is announced. Reka publishes\n nothing of the sort — the listbox's `aria-multiselectable` says the\n control takes several values, never how many it holds. -->\n <span v-if=\"multiple\" :id=\"countId\" class=\"sr-only\">\n {{ text.count({ count: tokens.length }) }}\n </span>\n </ComboboxAnchor>\n\n <ComboboxPortal>\n <ComboboxContent\n position=\"popper\"\n :side-offset=\"6\"\n :class=\"\n cn(\n 'z-50 max-h-72 min-w-[var(--reka-combobox-trigger-width)] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md',\n // Scoped to the open state, never unconditional. Reka keeps the\n // closed content mounted while it waits for an animationend that a\n // mount-only animation never fires again — an invisible overlay\n // eating clicks. Scoping it makes the closed element compute\n // `animation-name: none`, which is the immediate-unmount branch.\n 'data-[state=open]:animate-fade-rise',\n )\n \"\n >\n <ComboboxViewport class=\"p-1\">\n <!-- A filter that matches nothing has to say so. An empty popover\n reads as a broken one, and a screen reader is told nothing at\n all — so the message is a row inside the listbox rather than a\n caption beside it. `role=\"option\"` because a bare div is not a\n legitimate child of `role=\"listbox\"` and is skipped; disabled\n because it is a statement, not a choice. -->\n <ComboboxEmpty\n role=\"option\"\n aria-disabled=\"true\"\n class=\"animate-fade-rise px-3 py-1.5 text-sm text-muted-foreground\"\n >\n {{ emptyMessage ?? text.empty }}\n </ComboboxEmpty>\n\n <!-- Keyed by value rather than by index: a row that survives a\n keystroke keeps its DOM node, so its entrance animation is not\n re-run and the list settles instead of flashing. Keyed by index,\n every row below a filtered-out one would be reused for different\n content while the tail remounted — an animation playing on the\n wrong rows.\n\n `text-value` pins the filter to the label. Left off, Reka falls\n back to whatever the row happens to render, which is the label\n today and the label plus a badge, a hint or a count the first\n time a row grows one. -->\n <ComboboxItem\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n :value=\"option.value\"\n :text-value=\"option.label\"\n :disabled=\"option.disabled ?? false\"\n :style=\"{ animationDelay: revealing ? listStaggerDelay(index) : '0ms' }\"\n :class=\"\n cn(\n 'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-3 py-1.5 pr-8 text-sm text-foreground outline-none',\n 'transition-colors duration-fast ease-out',\n 'animate-fade-rise',\n 'data-[highlighted]:bg-subtle',\n 'data-[state=checked]:bg-primary-muted data-[state=checked]:text-primary-text',\n // Inert, but still readable. `data-[disabled]:opacity-50` took\n // the label to 3.13:1 on the popover; the colour on the span\n // below leaves it at 5.76:1 and still visibly lighter than the\n // rows around it.\n 'data-[disabled]:pointer-events-none',\n )\n \"\n >\n <!-- The mute lives on this span rather than on the row, and it has\n to. `data-[disabled]:` sorts *before* `data-[state=checked]:`\n in Tailwind's variant order, so a disabled row that is also the\n chosen one would take `text-primary-text` from the container — and a\n container colour is only inherited. Declared here it wins\n whatever the row resolved to. -->\n <span :class=\"option.disabled && 'text-muted-foreground'\">{{ option.label }}</span>\n <ComboboxItemIndicator class=\"absolute right-2 inline-flex items-center\">\n <Check class=\"h-4 w-4\" />\n </ComboboxItemIndicator>\n </ComboboxItem>\n </ComboboxViewport>\n </ComboboxContent>\n </ComboboxPortal>\n </ComboboxRoot>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"Combobox.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../../../packages/primitives/combobox/src/Combobox.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { ComboboxLabels } 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, and\n * exported so a host can build a partial vocabulary against the real thing.\n */\nexport const COMBOBOX_LABELS: ComboboxLabels = {\n trigger: \"Show options\",\n empty: \"No results\",\n count: ({ count }) => `${String(count)} selected`,\n overflow: ({ hidden }) => `+${String(hidden)} more`,\n};\n\n/**\n * One row of the list. `value` is what the model carries; `label` is what a\n * reader sees, what the filter matches, and what the input shows once the row\n * is chosen.\n *\n * Identical to `SelectOption` today, and deliberately not the same\n * declaration: the two surfaces are independent, and the first field either\n * one grows — a secondary line here, a group key there — is a field the other\n * should not inherit by accident. A shared alias would make that change look\n * free when it is not.\n */\nexport interface ComboboxOption {\n /** The value bound to `v-model` when this row is chosen. Unique within the list. */\n value: string;\n /** The text shown on the row, matched by the filter, and put in the input once chosen. */\n label: string;\n /** Present but unchoosable — visible, skipped by the keyboard, unclickable. */\n disabled?: boolean;\n}\n\n/**\n * The control's height scale. It matches the text input and Select scales\n * exactly, so a form row mixing a Combobox beside either never comes out\n * uneven — which is why reaching past `size` for an `h-*` class is always the\n * wrong fix.\n */\nexport type ComboboxSize = \"sm\" | \"md\" | \"lg\";\n\nexport const comboboxVariants = cva(\n [\n \"flex w-full items-center gap-2 rounded-md border border-input bg-background text-foreground\",\n \"transition-[color,background-color,border-color,box-shadow] duration-fast ease-out\",\n // The focusable node is the input *inside* this box, so the ring is drawn\n // with `focus-within` rather than `focus-visible` — the same choice\n // TextField makes for the same reason, and what keeps the two controls\n // lighting up identically in a form row.\n \"focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring\",\n ],\n {\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 size: {\n sm: \"h-8 px-2.5 text-xs\",\n md: \"h-9 px-3 text-sm\",\n lg: \"h-11 px-4 text-base\",\n } satisfies Record<ComboboxSize, string>,\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * The same three numbers as a floor rather than as a fixed height, for the\n * multi-select box only.\n *\n * A box holding tokens is sized by its content, which is TagsInput's call and\n * the reason its field is `min-h-9`. What keeps that from turning into an\n * unbounded control here is `visibleValues`: the token row never wraps, so in\n * practice the box resolves to exactly the height above and a multi-select\n * still lines up with the single-select beside it. The `h-auto` that goes with\n * it in the template is what releases the fixed height `comboboxVariants`\n * already set — Tailwind's merge resolves the two, `min-h-*` does not.\n */\nconst multiSelectHeights = {\n sm: \"min-h-8\",\n md: \"min-h-9\",\n lg: \"min-h-11\",\n} satisfies Record<ComboboxSize, string>;\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from \"vue\";\nimport {\n ComboboxRoot,\n ComboboxAnchor,\n ComboboxInput,\n ComboboxTrigger,\n ComboboxPortal,\n ComboboxContent,\n ComboboxViewport,\n ComboboxItem,\n ComboboxItemIndicator,\n ComboboxEmpty,\n} from \"reka-ui\";\nimport { Check, ChevronDown } from \"@lucide/vue\";\nimport Chip from \"@ecoma-io/loom-chip\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { listStaggerDelay } from \"@ecoma-io/loom-core\";\nimport { useSplitAttrs } from \"@ecoma-io/loom-core\";\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 * Combobox — a text input that narrows a list as you type, for the list too\n * long to scan. Countries, currencies, repositories, people: anywhere a reader\n * knows roughly what they are looking for and would rather type three letters\n * than scroll past two hundred rows.\n *\n * Its neighbour is `Select`, and the boundary is the length of the list rather\n * than the shape of the control. Three to fifteen rows a reader can read\n * through belong in a Select — a search box over eight options is a keystroke\n * charged for nothing. Two to five options that should all be visible at once\n * belong in a segmented control. Past roughly fifteen, searching beats\n * scrolling and this is the control that offers it.\n *\n * Built on Reka UI's Combobox, which supplies the editable-combobox semantics:\n * the `role=\"combobox\"` input wired to the listbox by `aria-expanded` and\n * `aria-controls`, `aria-autocomplete=\"list\"`, and the active row tracked by\n * `aria-activedescendant` while real focus stays in the input. That last part\n * is the one genuine behavioural difference from Select, which moves focus\n * onto the row itself — a combobox cannot, because the reader is still typing.\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` and the name the\n * chosen value is submitted under all arrive through `useFieldControl()`, so\n * `<Field label=\"Country\" error=\"…\"><Combobox … /></Field>` needs no attributes\n * at the call site. Both booleans below still win over the row when they are\n * set, in both directions — which is why they default to `undefined` rather\n * than `false`.\n *\n * A row's `readonly` is deliberately ignored. The text in the box is not the\n * value — it is the chosen option's label, rewritten on every choice — so a\n * `readonly` input here would freeze a caption rather than protect a value,\n * while the list underneath still opened on click. A choice nobody may change\n * is a disabled Combobox, or the label rendered as text.\n *\n * `multiple` turns it into a many-of-many picker. The chosen values render as\n * tokens in the box — the same `Chip` a\n * [TagsInput](../TagsInput/TagsInput.vue) commits its values to, so a selection\n * looks the same wherever a form shows one — and the input goes back to being\n * only a search box. The tokens are **inert**, deliberately: the list is where\n * a choice is made and where it is unmade, so the control stays the single Tab\n * stop it is in single-select, and a value hidden behind the overflow count is\n * reachable by exactly the same gesture as a visible one. That is the one place\n * this diverges from TagsInput, whose tokens carry their own remove control\n * because it has no list to send a reader back to.\n */\nconst props = withDefaults(\n defineProps<{\n /**\n * The chosen option's `value`, or every chosen value when `multiple`.\n *\n * The union is one prop rather than a generic component, and the invariant\n * is the mode: single-select emits and accepts a `string` and never an\n * array, multi-select an array and never a bare string. A generic would\n * express that in the type system and cost more than it buys — it would\n * print a conditional type in the generated API table below, and it would\n * break `InstanceType<typeof Combobox>`, which is how a consumer types a\n * template ref.\n */\n modelValue?: string | string[] | undefined;\n /** The rows, in the order they are shown. */\n options: ComboboxOption[];\n /** Lets several rows be chosen at once: chosen rows toggle instead of closing the list, and the box shows the selection as tokens. */\n multiple?: boolean;\n /**\n * How many tokens the box shows before the rest collapse into a count.\n *\n * The box must not grow with the selection — it sits in a form row beside\n * controls on a fixed height scale — so past this many the tail becomes one\n * `labels.overflow` summary: five chosen shows three and \"+2 more\", fifty\n * shows three and \"+47 more\". Raise it for a wide box; the row never wraps,\n * so a number the box has no room for is a number that truncates.\n */\n visibleValues?: number;\n /** Shown in the empty input, before anything is typed or chosen. */\n placeholder?: string;\n /** Shown as a row of its own when the filter matches nothing. Defaults to `labels.empty`, so say what was searched for rather than restating the default in a second language. */\n emptyMessage?: string | undefined;\n /** Unavailable: dims the control, refuses to open and refuses to take text. Unset defers to a wrapping Field. */\n disabled?: 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 /** Control height. It shares the text input scale so mixed form rows stay aligned. */\n size?: ComboboxSize;\n /**\n * Filter `options` against the typed text. Turn it off when the host is\n * producing the list itself — from a server, in response to\n * `update:query` — so a result that does not literally contain what was\n * typed is not then hidden a second time by a filter running underneath it.\n */\n filter?: boolean;\n /**\n * Names for the two strings this control supplies itself, as any subset of\n * `ComboboxLabels` — 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. `emptyMessage` is the shorter way to reach `empty` for one\n * box; this is what reaches `trigger`.\n */\n labels?: LabelOverrides<ComboboxLabels>;\n }>(),\n {\n // `modelValue: undefined` is load-bearing rather than redundant: paired\n // with `optional()` below it is what keeps the uncontrolled third state\n // reachable, instead of pinning the control to a value nobody chose.\n modelValue: undefined,\n multiple: false,\n // Three, and the number is a judgement rather than a measurement: two\n // tokens plus a count reads as a control that shows almost nothing, and\n // four leaves a `md` box no room to type in at the default width. It is a\n // prop because the right answer is the box's width, which only the caller\n // knows.\n visibleValues: 3,\n // No English default here any more. The wording moved into `COMBOBOX_LABELS`\n // so a host can set it once for the whole application rather than on every\n // box, and an absent prop is what lets the resolved label through.\n emptyMessage: undefined,\n // Not `false`, for either. `false` is a caller saying \"this control is fine\n // / enabled even though its row is not\"; absent is a caller saying nothing,\n // and only `undefined` survives to mean that past Vue's\n // absent-Boolean-casts-to-false rule — see TextField.vue, which carries the\n // full reasoning.\n disabled: undefined,\n invalid: undefined,\n size: \"md\",\n filter: true,\n },\n);\n\nconst emit = defineEmits<{\n /** The chosen option's `value` — or the whole chosen list, when `multiple`. Single-select fires once per choice and the list closes with it; multi-select fires once per toggle and the list stays open. */\n \"update:modelValue\": [value: string | string[]];\n /** The text now in the input — as the reader types, and again when a choice puts its label there. */\n \"update:query\": [query: string];\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(\"combobox\", COMBOBOX_LABELS, () => props.labels);\n\nconst query = ref(\"\");\n\n/**\n * The stagger is the list's *reveal*, and re-filtering is not a reveal. This\n * is true from the moment the list opens until the first keystroke, after\n * which every row arrives immediately.\n *\n * Two things go wrong without it. A delay recomputed per keystroke makes the\n * list feel like it is rebuilding under the reader's fingers — the opposite of\n * what a filter is for. And raising `animation-delay` on a row that survived\n * the keystroke rewinds its entrance: the row is still mounted, so the\n * animation is not restarted, it is re-timed, and a row that had finished\n * fading in drops back to half-opacity. Going the other way — stagger to `0ms`\n * — only ever fast-forwards, which is why this flag falls and does not rise\n * again until the content has unmounted and remounted with the next open.\n */\nconst revealing = ref(true);\n\nconst countId = useId();\n\n/**\n * The multi-select's own copy of the selection, and the reason it keeps one:\n * the tokens have to render whether or not the host bound `v-model`.\n *\n * Left uncontrolled, `modelValue` stays `undefined` while Reka manages the\n * choice internally — which is the third state `optional()` exists to preserve\n * — so a trigger reading the prop would sit empty while the list showed three\n * checked rows. This mirrors rather than drives: it is never fed back into\n * Reka, so nothing about the uncontrolled control changes.\n *\n * Seeded from the prop rather than sanitised, for TagsInput's reason: a list\n * the host already holds is the host's to hold.\n */\nconst selected = ref<string[]>(Array.isArray(props.modelValue) ? [...props.modelValue] : []);\nwatch(\n () => props.modelValue,\n (next) => {\n if (Array.isArray(next)) selected.value = [...next];\n },\n // Shallow, deliberately: the contract is whole-list replacement — Reka hands\n // back the complete selection on every toggle and a host echoes it straight\n // back down — so in-place mutation is not a case this mirror owes anyone.\n);\n\n/**\n * The chosen values as the box shows them, and the fallback is the point: a\n * host driving `options` from a search hands back only the rows matching the\n * current query, so a value chosen from an earlier one has no option to look\n * its label up in. Falling back to the value keeps the token on screen — a\n * chosen value that vanished from the trigger while it was still in the model\n * would be the control lying about what it holds.\n */\nconst tokens = computed(() =>\n selected.value.map((value) => ({\n value,\n label: props.options.find((option) => option.value === value)?.label ?? value,\n })),\n);\n\nconst visibleTokens = computed(() => tokens.value.slice(0, Math.max(0, props.visibleValues)));\nconst hiddenTokens = computed(() => tokens.value.length - visibleTokens.value.length);\n\n/**\n * What the input shows once a row is chosen. Without this Reka falls back to\n * the model value's own `toString()`, which would leave `en` in the box where\n * the reader picked \"English\".\n *\n * A multi-select shows nothing: the tokens are the selection, and the input\n * beside them is a search box. Reka calls this on close rather than on choose,\n * so the query the reader typed survives every toggle and one search can pick\n * several rows — which is the whole reason the list stays open.\n */\nfunction displayValue(value: unknown): string {\n if (props.multiple) return \"\";\n return props.options.find((option) => option.value === value)?.label ?? \"\";\n}\n\n/**\n * Reka hands back the whole selection on every toggle, never a delta, so the\n * emitted array is always the complete list — the same contract TagsInput's\n * `update:modelValue` carries, and the reason a host can assign it straight\n * back.\n */\nfunction onChoice(value: unknown): void {\n if (!props.multiple) {\n emit(\"update:modelValue\", String(value));\n return;\n }\n const values = Array.isArray(value) ? value.map(String) : [];\n selected.value = values;\n emit(\"update:modelValue\", values);\n}\n\nfunction onQuery(text: string): void {\n // Reka rewrites the input's text on its own account — once as the component\n // mounts, and again when a choice puts the chosen label back in the box —\n // and each of those arrives here as an update. Reporting a query that did\n // not change would have a host running the same search twice on mount, so\n // only a real change is passed on.\n if (text === query.value) return;\n query.value = text;\n revealing.value = false;\n emit(\"update:query\", text);\n}\n\n// The root renders no DOM node of its own, so fallthrough attributes would be\n// dropped on the floor. They go to the input, which is the element they\n// describe and the only one a name can land on — the chevron trigger already\n// carries Reka's own `aria-label`, and naming the control means naming the\n// `role=\"combobox\"` node.\n//\n// `class` is pulled out of that spread and merged through `cn()` onto the\n// anchor instead, because the anchor is the box a caller means when they pass\n// `w-64`. Spreading it alongside the anchor's own `:class` would only\n// concatenate the two lists — Vue's merge is generic, not Tailwind-aware — so\n// a caller's width would win or lose depending on which utility Tailwind\n// happened to emit last.\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: inputAttrs } = useSplitAttrs();\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`,\n// `readonly` and `required` are absent from this object because there is no\n// prop to oppose the row with; the row's answer stands unopposed, which is\n// what is wanted.\n//\n// The multi-select's count joins that list rather than replacing anything: it\n// is one more thing this control says about itself, and it reads after the\n// caller's own description and before the row's message — the same\n// most-specific-first order `useFieldControl` applies one layer out. Wired\n// through `aria-describedby` rather than announced, which is TagsInput's call\n// for the same fact: it is heard on focus and on every return to the box, which\n// is when the question is asked, and a live region would speak the whole total\n// over the top of the `aria-selected` change the row itself already announces.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [attrs[\"aria-describedby\"] as string | undefined, props.multiple ? countId : \"\"]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n disabled: props.disabled,\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 input, and what\n// the input holds is the chosen option's *label*. A `name` on it would post\n// \"Viet Nam\" where the model carries `vn`. Reka submits the value itself\n// through a hidden control it renders from the root's own `name`, so that is\n// where the row's name goes. Everything else in the bag — the id, the merged\n// description, `aria-required`, `aria-invalid` — belongs on the\n// `role=\"combobox\"` node, which is the 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</script>\n\n<template>\n <!-- `open-on-click` and not `open-on-focus`: clicking the box is a reader\n asking to see the list, while focus arrives by Tab as often as by\n intent, and a list that unfolds over the next field every time someone\n tabs through a form is an obstruction. Typing opens it either way. -->\n <ComboboxRoot\n v-bind=\"optional({ modelValue, name: field.name })\"\n :multiple=\"multiple\"\n :disabled=\"field.disabled\"\n :ignore-filter=\"!filter\"\n open-on-click\n @update:model-value=\"onChoice\"\n @update:open=\"revealing = true\"\n >\n <ComboboxAnchor\n :data-disabled=\"field.disabled || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n comboboxVariants({ size }),\n // A box holding tokens is sized by them — see `multiSelectHeights`.\n // `h-auto` is what releases the fixed height the variant just set;\n // Tailwind's merge resolves two `h-*` utilities and leaves a `min-h-*`\n // beside either of them alone.\n multiple && ['h-auto', multiSelectHeights[size], 'gap-1.5'],\n !field.invalid && 'focus-within:shadow-halo',\n field.invalid && 'border-destructive focus-within:outline-destructive',\n // The box holds the chosen value, so it is drained rather than\n // dimmed: `opacity-50` composited the input's text down to 3.06:1,\n // and what a reader needs from an unavailable Combobox is precisely\n // what it is set to. The neutral well says unavailable on its own —\n // the input's own `disabled:text-muted-foreground` below carries the\n // text to 4.67:1 over it.\n //\n // The hairline recedes with the fill, and that second channel is the\n // point: unavailable is a difference in *shape* as well as in colour,\n // so it survives a reader who cannot resolve `muted` from\n // `background`. This control has no read-only state to be told apart\n // from — see the docblock — so it takes the unavailable row alone.\n field.disabled && 'cursor-not-allowed border-border bg-muted text-muted-foreground',\n attrs.class as string,\n )\n \"\n >\n <!-- The selection, as the tokens a TagsInput commits its values to, so a\n chosen set looks the same wherever a form shows one. Inert on\n purpose: the list is where a value is chosen and where it is\n unchosen, which keeps the whole control one Tab stop and keeps a\n value behind the overflow count reachable by the same gesture as a\n visible one. -->\n <template v-if=\"multiple\">\n <Chip\n v-for=\"token in visibleTokens\"\n :key=\"token.value\"\n size=\"sm\"\n :disabled=\"field.disabled\"\n class=\"min-w-0 animate-fade-rise\"\n >\n {{ token.label }}\n </Chip>\n <!-- Not a token: it stands for values rather than being one, and a\n fourth pill saying \"+47 more\" would read as a forty-eighth\n choice. -->\n <span v-if=\"hiddenTokens > 0\" class=\"tabular shrink-0 text-small text-muted-foreground\">\n {{ text.overflow({ hidden: hiddenTokens }) }}\n </span>\n </template>\n\n <ComboboxInput\n v-bind=\"{ ...inputAttrs, ...inputFieldAttrs }\"\n :model-value=\"query\"\n :display-value=\"displayValue\"\n :placeholder=\"multiple && tokens.length > 0 ? undefined : placeholder\"\n class=\"min-w-6 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:text-muted-foreground\"\n @update:model-value=\"onQuery\"\n />\n <!-- Reka takes this button out of the tab order itself, so the whole\n control is one Tab stop: the input. It also names it, in English —\n `aria-label=\"Show popup\"`, written inside its own render function\n with no prop to reach it by — and the binding below replaces that\n rather than competing with it, because Vue merges a fallthrough\n attribute onto the root vnode after the render function produced it.\n Removing it does not fall back to nothing; it falls back to Reka's\n English, which no test of Loom's own strings would catch. -->\n <ComboboxTrigger\n :aria-label=\"text.trigger\"\n class=\"group min-h-6 min-w-6 shrink-0 text-muted-foreground disabled:cursor-not-allowed\"\n >\n <ChevronDown\n class=\"h-4 w-4 transition-transform duration-fast ease-out group-data-[state=open]:rotate-180\"\n />\n </ComboboxTrigger>\n\n <!-- The size of the selection, and the only thing that carries it to a\n reader who cannot see the tokens: the visible row shows the first few\n and a count for the rest, and neither is announced. Reka publishes\n nothing of the sort — the listbox's `aria-multiselectable` says the\n control takes several values, never how many it holds. -->\n <span v-if=\"multiple\" :id=\"countId\" class=\"sr-only\">\n {{ text.count({ count: tokens.length }) }}\n </span>\n </ComboboxAnchor>\n\n <ComboboxPortal>\n <ComboboxContent\n position=\"popper\"\n :side-offset=\"6\"\n :class=\"\n cn(\n 'z-overlay max-h-72 min-w-[var(--reka-combobox-trigger-width)] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md',\n // Each half of the motion is scoped to its own state, never\n // unconditional. Closing swaps the computed animation-name to\n // fade-fall's, which is what Reka's Presence reads: it holds the\n // panel mounted until that exit's animationend and only then takes\n // it away. Unconditional, a finished entrance animation sat on the\n // closed element and stranded it invisible over the page, eating\n // clicks.\n 'data-[state=open]:animate-fade-rise data-[state=closed]:animate-fade-fall',\n )\n \"\n >\n <ComboboxViewport class=\"p-1\">\n <!-- A filter that matches nothing has to say so. An empty popover\n reads as a broken one, and a screen reader is told nothing at\n all — so the message is a row inside the listbox rather than a\n caption beside it. `role=\"option\"` because a bare div is not a\n legitimate child of `role=\"listbox\"` and is skipped; disabled\n because it is a statement, not a choice. -->\n <ComboboxEmpty\n role=\"option\"\n aria-disabled=\"true\"\n class=\"animate-fade-rise px-3 py-1.5 text-sm text-muted-foreground\"\n >\n {{ emptyMessage ?? text.empty }}\n </ComboboxEmpty>\n\n <!-- Keyed by value rather than by index: a row that survives a\n keystroke keeps its DOM node, so its entrance animation is not\n re-run and the list settles instead of flashing. Keyed by index,\n every row below a filtered-out one would be reused for different\n content while the tail remounted — an animation playing on the\n wrong rows.\n\n `text-value` pins the filter to the label. Left off, Reka falls\n back to whatever the row happens to render, which is the label\n today and the label plus a badge, a hint or a count the first\n time a row grows one. -->\n <ComboboxItem\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n :value=\"option.value\"\n :text-value=\"option.label\"\n :disabled=\"option.disabled ?? false\"\n :style=\"{ animationDelay: revealing ? listStaggerDelay(index) : '0ms' }\"\n :class=\"\n cn(\n 'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-3 py-1.5 pr-8 text-sm text-foreground outline-none',\n 'transition-colors duration-fast ease-out',\n 'animate-fade-rise',\n 'data-[highlighted]:bg-subtle',\n 'data-[state=checked]:bg-primary-muted data-[state=checked]:text-primary-text',\n // Inert, but still readable. `data-[disabled]:opacity-50` took\n // the label to 3.13:1 on the popover; the colour on the span\n // below leaves it at 5.76:1 and still visibly lighter than the\n // rows around it.\n 'data-[disabled]:pointer-events-none',\n )\n \"\n >\n <!-- The mute lives on this span rather than on the row, and it has\n to. `data-[disabled]:` sorts *before* `data-[state=checked]:`\n in Tailwind's variant order, so a disabled row that is also the\n chosen one would take `text-primary-text` from the container — and a\n container colour is only inherited. Declared here it wins\n whatever the row resolved to. -->\n <span :class=\"option.disabled && 'text-muted-foreground'\">{{ option.label }}</span>\n <ComboboxItemIndicator class=\"absolute right-2 inline-flex items-center\">\n <Check class=\"h-4 w-4\" />\n </ComboboxItemIndicator>\n </ComboboxItem>\n </ComboboxViewport>\n </ComboboxContent>\n </ComboboxPortal>\n </ComboboxRoot>\n</template>\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  export { default, comboboxVariants, COMBOBOX_LABELS } from "./Combobox.vue";
2
2
  export type { ComboboxOption, ComboboxSize } from "./Combobox.vue";
3
- export type { ComboboxLabels } from "@ecoma-io/loom-labels";
3
+ export type { ComboboxLabels } from "../../../labels/src/index";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ import Command_vue_vue_type_script_setup_true_lang_default, { COMMAND_LABELS } from "./Command.vue_vue_type_script_setup_true_lang.js";
2
+ //#region packages/primitives/command/src/Command.vue
3
+ var Command_default = Command_vue_vue_type_script_setup_true_lang_default;
4
+ //#endregion
5
+ export { COMMAND_LABELS, Command_default as default };
6
+
7
+ //# sourceMappingURL=Command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Command.js","names":[],"sources":["../../../../../packages/primitives/command/src/Command.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { CommandLabels, LabelOverrides } from \"@ecoma-io/loom-labels\";\n/**\n * One searchable item in the command list. `value` is what the host receives on\n * select; `label` is what the reader sees and what the filter matches against.\n *\n * Deliberately not `ComboboxOption` or `DropdownMenuEntry`: those shapes carry\n * `modelValue` and `separator` fields respectively, and either would be\n * dead weight here — a command is an action, not a value or a menu row.\n */\nexport interface CommandItem {\n /** Unique key. */\n value: string;\n /** Visible label. */\n label: string;\n /** Optional description shown below the label. */\n description?: string;\n /** Group heading this item belongs to. */\n group?: string;\n /** Disabled state. */\n disabled?: boolean;\n}\n\n/**\n * A named section heading above a run of items. Only rendered when the group\n * contains at least one item that survived the current filter.\n */\nexport interface CommandGroup {\n /** Group heading text. */\n heading: string;\n}\n\n/**\n * Loom's English, co-located with the component so it tree-shakes with it.\n * Every string a Command actually says is the chrome around the host's items,\n * so the whole slot is here and none of it hides in the template.\n */\nexport const COMMAND_LABELS: CommandLabels = {\n placeholder: \"Type to search…\",\n emptyMessage: \"No results found.\",\n searchLabel: \"Search\",\n resultSingular: \"{count} result available\",\n resultPlural: \"{count} results available\",\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref, useId, watch } from \"vue\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { useLabels } from \"@ecoma-io/loom-labels\";\n\n/**\n * Command — a keyboard-driven command search, the universal \"Cmd+K\" pattern.\n *\n * A text input that narrows a grouped list as you type, for executing actions\n * rather than selecting values. Its neighbour is `Combobox`, which selects a\n * value for a form field; this control runs an action and has no `modelValue`.\n *\n * The two are one family in the same way a Combobox and a Select are: the same\n * surface vocabulary, the same tokens, the same keyboard model. What differs is\n * intent — \"open settings\" is not a value you could submit in a form — and that\n * intent shapes the API: `onSelect` rather than `v-model`.\n *\n * Built from native ARIA roles rather than Reka UI, because Reka ships no\n * Command primitive. The semantics follow the WAI-ARIA combobox pattern with\n * `aria-activedescendant` navigation: focus stays in the input while the\n * highlighted item moves, which is the only model that keeps typing fluid.\n *\n * `aria-expanded` is deliberately absent. The input is a fixed searchbox, not\n * a popover trigger, so it has no collapsed state to announce: the listbox is\n * visible on mount and returns on any interaction after Escape closes it.\n */\nconst props = withDefaults(\n defineProps<{\n /** The items to search through. */\n items: CommandItem[];\n /** Optional group definitions for section headers. */\n groups?: CommandGroup[];\n /** Controlled search query. */\n query?: string | undefined;\n /** Whether the command menu is open. */\n open?: boolean | undefined;\n /** Names for the strings this control supplies itself. */\n labels?: LabelOverrides<CommandLabels> | undefined;\n /** Accessible label by reference for the search input. */\n ariaLabelledby?: string;\n }>(),\n {\n query: undefined,\n open: undefined,\n labels: undefined,\n },\n);\n\nconst emit = defineEmits<{\n /** Fired when an item is selected. */\n select: [value: string];\n /** Fired when the query changes. */\n \"update:query\": [value: string];\n /** Fired when open state changes. */\n \"update:open\": [value: boolean];\n}>();\n\nconst resolvedLabels = useLabels(\"command\", COMMAND_LABELS, () => props.labels);\n\nconst inputId = useId();\nconst listboxId = useId();\nconst liveRegionId = useId();\n\n/**\n * The controlled/uncontrolled query. When `query` is bound from outside, the\n * prop wins; otherwise the component owns its own state.\n */\nconst internalQuery = ref(\"\");\nconst queryValue = computed(() => props.query ?? internalQuery.value);\n\nfunction setQuery(value: string): void {\n internalQuery.value = value;\n emit(\"update:query\", value);\n}\n\n/**\n * The controlled/uncontrolled open state. `true` when the listbox is visible.\n */\nconst internalOpen = ref(true);\nconst isOpen = computed(() => props.open ?? internalOpen.value);\n\nfunction setOpen(value: boolean): void {\n if (props.open !== undefined) {\n emit(\"update:open\", value);\n } else {\n internalOpen.value = value;\n }\n}\n\n/**\n * Bring the listbox back after Escape closed it. Focus never leaves the\n * search input, so without this a closed Command is a dead end — the only\n * escape, before this fix, was unmounting the component. Controlled mode is\n * untouched: the emit the host chose stays with the host, and this rewrites\n * only the fallback state a host never bound.\n */\nfunction restoreOpen(): void {\n if (props.open === undefined) internalOpen.value = true;\n}\n\n/**\n * The index of the highlighted item within the filtered list. Reset to `0`\n * whenever the query changes so the first match is always ready to select.\n */\nconst highlightedIndex = ref(0);\n\nconst listboxRef = ref<HTMLElement | null>(null);\n\n// Arrow-key focus never leaves the input (aria-activedescendant), so the\n// scrollable listbox must be tab-reachable itself — an overflow container no\n// keyboard can reach is the scrollable-region-focusable violation the harness\n// axe gate exists to catch. Scrolling the highlighted option into view is the\n// other half: an active item moved off-screen is navigation the user cannot\n// see. jsdom has no scrollIntoView, hence the optional call.\nwatch([highlightedIndex, isOpen], async () => {\n if (!isOpen.value) return;\n await nextTick();\n listboxRef.value?.querySelector(\"[data-active]\")?.scrollIntoView?.({ block: \"nearest\" });\n});\n\n/**\n * Items filtered by the current query. Case-insensitive substring match\n * across `label`, `description` and `value`.\n */\nconst filteredItems = computed(() => {\n const needle = queryValue.value.trim().toLowerCase();\n if (!needle) return props.items;\n return props.items.filter((item) => {\n const haystack = [item.label, item.description ?? \"\", item.value].join(\" \").toLowerCase();\n return haystack.includes(needle);\n });\n});\n\n/**\n * Items grouped for rendering. Each entry is either a group heading or a flat\n * item. Groups with no matching items are omitted entirely.\n */\ninterface GroupedEntry {\n kind: \"heading\";\n heading: string;\n groupId: string;\n}\n\ninterface GroupedItem {\n kind: \"item\";\n item: CommandItem;\n globalIndex: number;\n}\n\ntype RenderEntry = GroupedEntry | GroupedItem;\n\nconst renderEntries = computed<RenderEntry[]>(() => {\n if (filteredItems.value.length === 0) return [];\n\n const groupDefs = props.groups;\n if (!groupDefs || groupDefs.length === 0) {\n return filteredItems.value.map((item, i) => ({ kind: \"item\", item, globalIndex: i }));\n }\n\n const ungrouped: CommandItem[] = [];\n const grouped = new Map<string, CommandItem[]>();\n\n for (const item of filteredItems.value) {\n if (item.group) {\n const list = grouped.get(item.group) ?? [];\n list.push(item);\n grouped.set(item.group, list);\n } else {\n ungrouped.push(item);\n }\n }\n\n const entries: RenderEntry[] = [];\n let globalIndex = 0;\n\n for (const def of groupDefs) {\n const items = grouped.get(def.heading);\n if (!items || items.length === 0) continue;\n entries.push({\n kind: \"heading\",\n heading: def.heading,\n groupId: `${inputId}-group-${def.heading}`,\n });\n for (const item of items) {\n entries.push({ kind: \"item\", item, globalIndex });\n globalIndex++;\n }\n }\n\n for (const item of ungrouped) {\n entries.push({ kind: \"item\", item, globalIndex });\n globalIndex++;\n }\n\n return entries;\n});\n\n/**\n * The item list for the non-grouped case, used by keyboard navigation. Derived\n * from `renderEntries` so the two are always in sync.\n */\nconst flatItems = computed(() =>\n renderEntries.value.filter((e): e is GroupedItem => e.kind === \"item\"),\n);\n\n/**\n * Keep the highlight within bounds. A filter that removes items can strand the\n * index past the end.\n */\nwatch(flatItems, (items) => {\n if (highlightedIndex.value >= items.length) {\n highlightedIndex.value = Math.max(0, items.length - 1);\n }\n});\n\n/**\n * The documented reset: the highlight returns to the first match whenever the\n * query changes — whether the query came from typing or from a parent updating\n * the controlled `query` prop. Typing reaches this watcher through `setQuery`,\n * which is why onInput no longer resets the highlight itself.\n */\nwatch(queryValue, () => {\n highlightedIndex.value = 0;\n});\n\n/**\n * The total number of results, announced to assistive technology through a live\n * region. Changes only when the filtered set changes, not on every keystroke.\n */\nconst resultCount = computed(() => filteredItems.value.length);\nconst announcement = ref(\"\");\nwatch(resultCount, (count) => {\n const template =\n count === 1 ? resolvedLabels.value.resultSingular : resolvedLabels.value.resultPlural;\n announcement.value = template.replace(\"{count}\", String(count));\n});\n\n/**\n * The currently highlighted item, or `undefined` when the list is empty.\n */\nconst highlightedItem = computed(() => flatItems.value[highlightedIndex.value]?.item);\n\nfunction highlightUp(): void {\n if (flatItems.value.length === 0) return;\n highlightedIndex.value =\n highlightedIndex.value <= 0 ? flatItems.value.length - 1 : highlightedIndex.value - 1;\n}\n\nfunction highlightDown(): void {\n if (flatItems.value.length === 0) return;\n highlightedIndex.value =\n highlightedIndex.value >= flatItems.value.length - 1 ? 0 : highlightedIndex.value + 1;\n}\n\nfunction selectHighlighted(): void {\n const item = highlightedItem.value;\n if (!item || item.disabled) return;\n emit(\"select\", item.value);\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n // A closed Command returns on any interaction except the two that leave it —\n // Escape (the key that closed it, still under the same finger) and Tab\n // (which moves focus elsewhere). Keydown fires before the input event, so\n // typing also lands on an open list.\n if (!isOpen.value && event.key !== \"Escape\" && event.key !== \"Tab\") restoreOpen();\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n highlightDown();\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n highlightUp();\n break;\n }\n case \"Enter\": {\n event.preventDefault();\n selectHighlighted();\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n if (queryValue.value.length > 0) {\n setQuery(\"\");\n } else {\n setOpen(false);\n }\n break;\n }\n case \"Home\": {\n event.preventDefault();\n if (flatItems.value.length > 0) highlightedIndex.value = 0;\n break;\n }\n case \"End\": {\n event.preventDefault();\n if (flatItems.value.length > 0) highlightedIndex.value = flatItems.value.length - 1;\n break;\n }\n }\n}\n\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLInputElement).value;\n setQuery(value);\n}\n\nfunction selectItem(item: CommandItem): void {\n if (item.disabled) return;\n emit(\"select\", item.value);\n}\n\nfunction itemId(index: number): string {\n return `${inputId}-option-${String(index)}`;\n}\n</script>\n\n<template>\n <div\n :data-state=\"isOpen ? 'open' : 'closed'\"\n :class=\"\n cn(\n 'rounded-md border border-border bg-popover text-popover-foreground shadow-md overflow-hidden',\n 'outline-none',\n )\n \"\n >\n <div class=\"flex items-center border-b border-border\">\n <input\n :id=\"inputId\"\n type=\"text\"\n role=\"searchbox\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n aria-autocomplete=\"list\"\n :aria-controls=\"listboxId\"\n :aria-activedescendant=\"highlightedItem ? itemId(highlightedIndex) : undefined\"\n :aria-labelledby=\"ariaLabelledby\"\n :aria-label=\"ariaLabelledby ? undefined : resolvedLabels.searchLabel\"\n :placeholder=\"resolvedLabels.placeholder\"\n :value=\"queryValue\"\n class=\"w-full px-3 py-2 text-sm bg-transparent text-foreground border-none outline-none placeholder:text-muted-foreground\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @focus=\"restoreOpen\"\n @click=\"restoreOpen\"\n />\n </div>\n <div\n v-if=\"isOpen\"\n :id=\"listboxId\"\n ref=\"listboxRef\"\n role=\"listbox\"\n tabindex=\"0\"\n :aria-labelledby=\"inputId\"\n class=\"max-h-[300px] overflow-y-auto p-1\"\n >\n <template\n v-for=\"entry in renderEntries\"\n :key=\"entry.kind === 'heading' ? entry.groupId : entry.item.value\"\n >\n <div\n v-if=\"entry.kind === 'heading'\"\n :id=\"entry.groupId\"\n role=\"presentation\"\n class=\"px-2 py-1.5 text-micro font-medium uppercase tracking-wide text-muted-foreground\"\n >\n {{ entry.heading }}\n </div>\n <!-- eslint-disable vuejs-accessibility/click-events-have-key-events, vuejs-accessibility/interactive-supports-focus, vuejs-accessibility/mouse-events-have-key-events -->\n <div\n v-else\n :id=\"itemId(entry.globalIndex)\"\n role=\"option\"\n :aria-selected=\"highlightedIndex === entry.globalIndex\"\n :aria-disabled=\"entry.item.disabled || undefined\"\n :data-active=\"highlightedIndex === entry.globalIndex || undefined\"\n class=\"flex flex-col gap-0.5 rounded-sm px-2 py-1.5 text-sm cursor-pointer outline-none transition-colors duration-fast ease-out data-[active]:bg-primary-muted data-[active]:text-primary-text\"\n @mouseenter=\"highlightedIndex = entry.globalIndex\"\n @click=\"selectItem(entry.item)\"\n >\n <span>{{ entry.item.label }}</span>\n <span v-if=\"entry.item.description\" class=\"text-xs text-muted-foreground\">\n {{ entry.item.description }}\n </span>\n </div>\n </template>\n\n <div\n v-if=\"filteredItems.length === 0\"\n role=\"option\"\n aria-selected=\"false\"\n aria-disabled=\"true\"\n class=\"px-2 py-6 text-center text-sm text-muted-foreground\"\n >\n {{ resolvedLabels.emptyMessage }}\n </div>\n </div>\n\n <span :id=\"liveRegionId\" role=\"status\" aria-live=\"polite\" class=\"sr-only\">\n {{ announcement }}\n </span>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,77 @@
1
+ import type { CommandLabels, LabelOverrides } from "../../../labels/src/index";
2
+ /**
3
+ * One searchable item in the command list. `value` is what the host receives on
4
+ * select; `label` is what the reader sees and what the filter matches against.
5
+ *
6
+ * Deliberately not `ComboboxOption` or `DropdownMenuEntry`: those shapes carry
7
+ * `modelValue` and `separator` fields respectively, and either would be
8
+ * dead weight here — a command is an action, not a value or a menu row.
9
+ */
10
+ export interface CommandItem {
11
+ /** Unique key. */
12
+ value: string;
13
+ /** Visible label. */
14
+ label: string;
15
+ /** Optional description shown below the label. */
16
+ description?: string;
17
+ /** Group heading this item belongs to. */
18
+ group?: string;
19
+ /** Disabled state. */
20
+ disabled?: boolean;
21
+ }
22
+ /**
23
+ * A named section heading above a run of items. Only rendered when the group
24
+ * contains at least one item that survived the current filter.
25
+ */
26
+ export interface CommandGroup {
27
+ /** Group heading text. */
28
+ heading: string;
29
+ }
30
+ /**
31
+ * Loom's English, co-located with the component so it tree-shakes with it.
32
+ * Every string a Command actually says is the chrome around the host's items,
33
+ * so the whole slot is here and none of it hides in the template.
34
+ */
35
+ export declare const COMMAND_LABELS: CommandLabels;
36
+ declare const _default: typeof __VLS_export;
37
+ export default _default;
38
+ declare const __VLS_export: import("vue").DefineComponent<{
39
+ /** The items to search through. */
40
+ items: CommandItem[];
41
+ /** Optional group definitions for section headers. */
42
+ groups?: CommandGroup[];
43
+ /** Controlled search query. */
44
+ query?: string | undefined;
45
+ /** Whether the command menu is open. */
46
+ open?: boolean | undefined;
47
+ /** Names for the strings this control supplies itself. */
48
+ labels?: LabelOverrides<CommandLabels> | undefined;
49
+ /** Accessible label by reference for the search input. */
50
+ ariaLabelledby?: string;
51
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
52
+ select: (value: string) => any;
53
+ "update:open": (value: boolean) => any;
54
+ "update:query": (value: string) => any;
55
+ }, string, import("vue").PublicProps, Readonly<{
56
+ /** The items to search through. */
57
+ items: CommandItem[];
58
+ /** Optional group definitions for section headers. */
59
+ groups?: CommandGroup[];
60
+ /** Controlled search query. */
61
+ query?: string | undefined;
62
+ /** Whether the command menu is open. */
63
+ open?: boolean | undefined;
64
+ /** Names for the strings this control supplies itself. */
65
+ labels?: LabelOverrides<CommandLabels> | undefined;
66
+ /** Accessible label by reference for the search input. */
67
+ ariaLabelledby?: string;
68
+ }> & Readonly<{
69
+ onSelect?: (value: string) => any;
70
+ "onUpdate:open"?: (value: boolean) => any;
71
+ "onUpdate:query"?: (value: string) => any;
72
+ }>, {
73
+ open: boolean | undefined;
74
+ labels: LabelOverrides<CommandLabels> | undefined;
75
+ query: string | undefined;
76
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
77
+ //# sourceMappingURL=Command.vue.d.ts.map