@ecoma-io/loom 0.3.0 → 0.4.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 (398) hide show
  1. package/dist/index.js +38 -6
  2. package/dist/packages/blocks/app-header/src/AppHeader.js.map +1 -1
  3. package/dist/packages/blocks/app-header/src/AppHeader.vue.d.ts.map +1 -1
  4. package/dist/packages/blocks/app-header/src/AppHeader.vue_vue_type_script_setup_true_lang.js +1 -0
  5. package/dist/packages/blocks/app-header/src/AppHeader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/packages/blocks/desktop-app-shell/src/DesktopAppShell.vue.d.ts +3 -3
  7. package/dist/packages/blocks/metric-card/src/MetricCard.js.map +1 -1
  8. package/dist/packages/blocks/metric-card/src/MetricCard.vue.d.ts +22 -4
  9. package/dist/packages/blocks/metric-card/src/MetricCard.vue.d.ts.map +1 -1
  10. package/dist/packages/blocks/metric-card/src/MetricCard.vue_vue_type_script_setup_true_lang.js +38 -15
  11. package/dist/packages/blocks/metric-card/src/MetricCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  12. package/dist/packages/blocks/title-bar/src/TitleBar.vue.d.ts +3 -3
  13. package/dist/packages/blocks/toast-stack/src/ToastStack.vue.d.ts +3 -3
  14. package/dist/packages/composition/scroll-reel/src/ScrollReel.js +2 -2
  15. package/dist/packages/composition/scroll-reel/src/ScrollReel.js.map +1 -1
  16. package/dist/packages/composition/scroll-reel/src/ScrollReel.vue.d.ts +26 -0
  17. package/dist/packages/composition/scroll-reel/src/ScrollReel.vue_vue_type_script_setup_true_lang.js +27 -7
  18. package/dist/packages/composition/scroll-reel/src/ScrollReel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/packages/composition/scroll-reel/src/index.d.ts +2 -1
  20. package/dist/packages/composition/scroll-reel/src/index.js +2 -1
  21. package/dist/packages/core/src/index.d.ts +1 -1
  22. package/dist/packages/core/src/index.d.ts.map +1 -1
  23. package/dist/packages/core/src/index.js +2 -2
  24. package/dist/packages/core/src/motion.d.ts +13 -0
  25. package/dist/packages/core/src/motion.d.ts.map +1 -1
  26. package/dist/packages/core/src/motion.js +20 -13
  27. package/dist/packages/core/src/motion.js.map +1 -1
  28. package/dist/packages/core/src/theme.d.ts +9 -0
  29. package/dist/packages/core/src/theme.d.ts.map +1 -1
  30. package/dist/packages/core/src/theme.js +36 -10
  31. package/dist/packages/core/src/theme.js.map +1 -1
  32. package/dist/packages/labels/src/ancestor-disabled.d.ts +5 -1
  33. package/dist/packages/labels/src/ancestor-disabled.d.ts.map +1 -1
  34. package/dist/packages/labels/src/ancestor-disabled.js +5 -1
  35. package/dist/packages/labels/src/ancestor-disabled.js.map +1 -1
  36. package/dist/packages/labels/src/component-shapes.d.ts +73 -0
  37. package/dist/packages/labels/src/component-shapes.d.ts.map +1 -1
  38. package/dist/packages/labels/src/date-labels.d.ts +8 -6
  39. package/dist/packages/labels/src/date-labels.d.ts.map +1 -1
  40. package/dist/packages/labels/src/date-labels.js +8 -6
  41. package/dist/packages/labels/src/date-labels.js.map +1 -1
  42. package/dist/packages/labels/src/index.d.ts +2 -2
  43. package/dist/packages/labels/src/index.d.ts.map +1 -1
  44. package/dist/packages/labels/src/label-registry.d.ts +7 -1
  45. package/dist/packages/labels/src/label-registry.d.ts.map +1 -1
  46. package/dist/packages/layouts/reading/src/Reading.js.map +1 -1
  47. package/dist/packages/layouts/reading/src/Reading.vue.d.ts +16 -15
  48. package/dist/packages/layouts/reading/src/Reading.vue.d.ts.map +1 -1
  49. package/dist/packages/layouts/reading/src/Reading.vue_vue_type_script_setup_true_lang.js.map +1 -1
  50. package/dist/packages/layouts/reading/src/index.d.ts +1 -0
  51. package/dist/packages/layouts/reading/src/index.d.ts.map +1 -1
  52. package/dist/packages/loom/src/a11y.d.ts +1 -1
  53. package/dist/packages/loom/src/index.d.ts +191 -174
  54. package/dist/packages/loom/src/theme.d.ts +3 -0
  55. package/dist/packages/primitives/alert/src/Alert.js +7 -0
  56. package/dist/packages/primitives/alert/src/Alert.js.map +1 -0
  57. package/dist/packages/primitives/alert/src/Alert.vue.d.ts +73 -0
  58. package/dist/packages/primitives/alert/src/Alert.vue_vue_type_script_setup_true_lang.js +127 -0
  59. package/dist/packages/primitives/alert/src/Alert.vue_vue_type_script_setup_true_lang.js.map +1 -0
  60. package/dist/packages/primitives/alert/src/index.d.ts +4 -0
  61. package/dist/packages/primitives/alert/src/index.js +3 -0
  62. package/dist/packages/primitives/alert-dialog/src/AlertDialog.js.map +1 -1
  63. package/dist/packages/primitives/alert-dialog/src/AlertDialog.vue.d.ts +2 -2
  64. package/dist/packages/primitives/alert-dialog/src/AlertDialog.vue_vue_type_script_setup_true_lang.js +2 -2
  65. package/dist/packages/primitives/alert-dialog/src/AlertDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  66. package/dist/packages/primitives/alert-dialog/src/index.d.ts +1 -1
  67. package/dist/packages/primitives/avatar-group/src/AvatarGroup.vue.d.ts +3 -3
  68. package/dist/packages/primitives/avatar-group/src/index.d.ts +1 -1
  69. package/dist/packages/primitives/badge/src/Badge.js.map +1 -1
  70. package/dist/packages/primitives/badge/src/Badge.vue.d.ts.map +1 -1
  71. package/dist/packages/primitives/badge/src/Badge.vue_vue_type_script_setup_true_lang.js +1 -1
  72. package/dist/packages/primitives/badge/src/Badge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  73. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.js +2 -2
  74. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.js.map +1 -1
  75. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.vue.d.ts +28 -0
  76. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.vue_vue_type_script_setup_true_lang.js +16 -5
  77. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
  78. package/dist/packages/primitives/breadcrumb/src/index.d.ts +2 -1
  79. package/dist/packages/primitives/breadcrumb/src/index.js +2 -1
  80. package/dist/packages/primitives/button/src/Button.js.map +1 -1
  81. package/dist/packages/primitives/button/src/Button.vue.d.ts.map +1 -1
  82. package/dist/packages/primitives/button/src/Button.vue_vue_type_script_setup_true_lang.js.map +1 -1
  83. package/dist/packages/primitives/button/src/index.js +2 -2
  84. package/dist/packages/primitives/card/src/Card.js +7 -0
  85. package/dist/packages/primitives/card/src/Card.js.map +1 -0
  86. package/dist/packages/primitives/card/src/Card.vue.d.ts +72 -0
  87. package/dist/packages/primitives/card/src/Card.vue_vue_type_script_setup_true_lang.js +65 -0
  88. package/dist/packages/primitives/card/src/Card.vue_vue_type_script_setup_true_lang.js.map +1 -0
  89. package/dist/packages/primitives/card/src/index.d.ts +3 -0
  90. package/dist/packages/primitives/card/src/index.d.ts.map +1 -0
  91. package/dist/packages/primitives/card/src/index.js +2 -0
  92. package/dist/packages/primitives/carousel/src/Carousel.js +7 -0
  93. package/dist/packages/primitives/carousel/src/Carousel.js.map +1 -0
  94. package/dist/packages/primitives/carousel/src/Carousel.vue.d.ts +34 -0
  95. package/dist/packages/primitives/carousel/src/Carousel.vue_vue_type_script_setup_true_lang.js +175 -0
  96. package/dist/packages/primitives/carousel/src/Carousel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  97. package/dist/packages/primitives/carousel/src/index.d.ts +3 -0
  98. package/dist/packages/primitives/carousel/src/index.js +3 -0
  99. package/dist/packages/primitives/checkbox/src/Checkbox.js.map +1 -1
  100. package/dist/packages/primitives/checkbox/src/Checkbox.vue.d.ts +1 -1
  101. package/dist/packages/primitives/checkbox/src/Checkbox.vue.d.ts.map +1 -1
  102. package/dist/packages/primitives/checkbox/src/Checkbox.vue_vue_type_script_setup_true_lang.js +3 -3
  103. package/dist/packages/primitives/checkbox/src/Checkbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  104. package/dist/packages/primitives/chip/src/Chip.js.map +1 -1
  105. package/dist/packages/primitives/chip/src/Chip.vue.d.ts.map +1 -1
  106. package/dist/packages/primitives/chip/src/Chip.vue_vue_type_script_setup_true_lang.js +1 -1
  107. package/dist/packages/primitives/chip/src/Chip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  108. package/dist/packages/primitives/collapse/src/Collapse.js +7 -0
  109. package/dist/packages/primitives/collapse/src/Collapse.js.map +1 -0
  110. package/dist/packages/primitives/collapse/src/Collapse.vue.d.ts +56 -0
  111. package/dist/packages/primitives/collapse/src/Collapse.vue.d.ts.map +1 -0
  112. package/dist/packages/primitives/collapse/src/Collapse.vue_vue_type_script_setup_true_lang.js +81 -0
  113. package/dist/packages/primitives/collapse/src/Collapse.vue_vue_type_script_setup_true_lang.js.map +1 -0
  114. package/dist/packages/primitives/collapse/src/index.d.ts +2 -0
  115. package/dist/packages/primitives/collapse/src/index.d.ts.map +1 -0
  116. package/dist/packages/primitives/collapse/src/index.js +2 -0
  117. package/dist/packages/primitives/color-picker/src/ColorPicker.js.map +1 -1
  118. package/dist/packages/primitives/color-picker/src/ColorPicker.vue.d.ts +4 -4
  119. package/dist/packages/primitives/color-picker/src/ColorPicker.vue_vue_type_script_setup_true_lang.js +2 -2
  120. package/dist/packages/primitives/color-picker/src/ColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  121. package/dist/packages/primitives/color-picker/src/index.d.ts +1 -1
  122. package/dist/packages/primitives/combobox/src/Combobox.js.map +1 -1
  123. package/dist/packages/primitives/combobox/src/Combobox.vue.d.ts +2 -2
  124. package/dist/packages/primitives/combobox/src/Combobox.vue_vue_type_script_setup_true_lang.js +2 -2
  125. package/dist/packages/primitives/combobox/src/Combobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  126. package/dist/packages/primitives/combobox/src/index.d.ts +1 -1
  127. package/dist/packages/primitives/context-menu/src/ContextMenu.js.map +1 -1
  128. package/dist/packages/primitives/context-menu/src/ContextMenu.vue.d.ts +12 -0
  129. package/dist/packages/primitives/context-menu/src/ContextMenu.vue.d.ts.map +1 -1
  130. package/dist/packages/primitives/context-menu/src/ContextMenu.vue_vue_type_script_setup_true_lang.js +7 -3
  131. package/dist/packages/primitives/context-menu/src/ContextMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  132. package/dist/packages/primitives/date-picker/src/DatePicker.js.map +1 -1
  133. package/dist/packages/primitives/date-picker/src/DatePicker.vue.d.ts +2 -2
  134. package/dist/packages/primitives/date-picker/src/DatePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  135. package/dist/packages/primitives/date-picker/src/DatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  136. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.js.map +1 -1
  137. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue.d.ts +3 -3
  138. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  139. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  140. package/dist/packages/primitives/date-range-picker/src/index.d.ts +1 -1
  141. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.js.map +1 -1
  142. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue.d.ts +2 -2
  143. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  144. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  145. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.js.map +1 -1
  146. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue.d.ts +3 -3
  147. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  148. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  149. package/dist/packages/primitives/date-time-range-picker/src/index.d.ts +1 -1
  150. package/dist/packages/primitives/dialog/src/Dialog.js.map +1 -1
  151. package/dist/packages/primitives/dialog/src/Dialog.vue.d.ts +12 -4
  152. package/dist/packages/primitives/dialog/src/Dialog.vue_vue_type_script_setup_true_lang.js +1 -1
  153. package/dist/packages/primitives/dialog/src/Dialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  154. package/dist/packages/primitives/dialog/src/index.d.ts +1 -1
  155. package/dist/packages/primitives/drawer/src/Drawer.js.map +1 -1
  156. package/dist/packages/primitives/drawer/src/Drawer.vue.d.ts +2 -2
  157. package/dist/packages/primitives/drawer/src/Drawer.vue_vue_type_script_setup_true_lang.js +2 -2
  158. package/dist/packages/primitives/drawer/src/Drawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  159. package/dist/packages/primitives/drawer/src/index.d.ts +1 -1
  160. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.js.map +1 -1
  161. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.vue.d.ts +26 -0
  162. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.vue.d.ts.map +1 -1
  163. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.vue_vue_type_script_setup_true_lang.js +10 -4
  164. package/dist/packages/primitives/dropdown-menu/src/DropdownMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  165. package/dist/packages/primitives/editable/src/Editable.js.map +1 -1
  166. package/dist/packages/primitives/editable/src/Editable.vue.d.ts +2 -2
  167. package/dist/packages/primitives/editable/src/Editable.vue_vue_type_script_setup_true_lang.js +1 -1
  168. package/dist/packages/primitives/editable/src/Editable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  169. package/dist/packages/primitives/editable/src/index.d.ts +1 -1
  170. package/dist/packages/primitives/file-upload/src/FileUpload.js.map +1 -1
  171. package/dist/packages/primitives/file-upload/src/FileUpload.vue.d.ts +2 -2
  172. package/dist/packages/primitives/file-upload/src/FileUpload.vue_vue_type_script_setup_true_lang.js +1 -1
  173. package/dist/packages/primitives/file-upload/src/FileUpload.vue_vue_type_script_setup_true_lang.js.map +1 -1
  174. package/dist/packages/primitives/file-upload/src/index.d.ts +1 -1
  175. package/dist/packages/primitives/hover-card/src/HoverCard.js.map +1 -1
  176. package/dist/packages/primitives/hover-card/src/HoverCard.vue.d.ts.map +1 -1
  177. package/dist/packages/primitives/hover-card/src/HoverCard.vue_vue_type_script_setup_true_lang.js +1 -1
  178. package/dist/packages/primitives/hover-card/src/HoverCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  179. package/dist/packages/primitives/icon-button/src/IconButton.js.map +1 -1
  180. package/dist/packages/primitives/icon-button/src/IconButton.vue.d.ts +10 -4
  181. package/dist/packages/primitives/icon-button/src/IconButton.vue.d.ts.map +1 -1
  182. package/dist/packages/primitives/icon-button/src/IconButton.vue_vue_type_script_setup_true_lang.js +18 -6
  183. package/dist/packages/primitives/icon-button/src/IconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  184. package/dist/packages/primitives/indicator/src/Indicator.vue.d.ts +2 -2
  185. package/dist/packages/primitives/indicator/src/index.d.ts +1 -1
  186. package/dist/packages/primitives/kbd/src/Kbd.js +7 -0
  187. package/dist/packages/primitives/kbd/src/Kbd.js.map +1 -0
  188. package/dist/packages/primitives/kbd/src/Kbd.vue.d.ts +30 -0
  189. package/dist/packages/primitives/kbd/src/Kbd.vue.d.ts.map +1 -0
  190. package/dist/packages/primitives/kbd/src/Kbd.vue_vue_type_script_setup_true_lang.js +29 -0
  191. package/dist/packages/primitives/kbd/src/Kbd.vue_vue_type_script_setup_true_lang.js.map +1 -0
  192. package/dist/packages/primitives/kbd/src/index.d.ts +3 -0
  193. package/dist/packages/primitives/kbd/src/index.d.ts.map +1 -0
  194. package/dist/packages/primitives/kbd/src/index.js +3 -0
  195. package/dist/packages/primitives/link/src/Link.js.map +1 -1
  196. package/dist/packages/primitives/link/src/Link.vue.d.ts.map +1 -1
  197. package/dist/packages/primitives/link/src/Link.vue_vue_type_script_setup_true_lang.js +3 -3
  198. package/dist/packages/primitives/link/src/Link.vue_vue_type_script_setup_true_lang.js.map +1 -1
  199. package/dist/packages/primitives/list/src/List.js +7 -0
  200. package/dist/packages/primitives/list/src/List.js.map +1 -0
  201. package/dist/packages/primitives/list/src/List.vue.d.ts +22 -0
  202. package/dist/packages/primitives/list/src/List.vue.d.ts.map +1 -0
  203. package/dist/packages/primitives/list/src/List.vue_vue_type_script_setup_true_lang.js +27 -0
  204. package/dist/packages/primitives/list/src/List.vue_vue_type_script_setup_true_lang.js.map +1 -0
  205. package/dist/packages/primitives/list/src/ListItem.js +7 -0
  206. package/dist/packages/primitives/list/src/ListItem.js.map +1 -0
  207. package/dist/packages/primitives/list/src/ListItem.vue.d.ts +75 -0
  208. package/dist/packages/primitives/list/src/ListItem.vue.d.ts.map +1 -0
  209. package/dist/packages/primitives/list/src/ListItem.vue_vue_type_script_setup_true_lang.js +101 -0
  210. package/dist/packages/primitives/list/src/ListItem.vue_vue_type_script_setup_true_lang.js.map +1 -0
  211. package/dist/packages/primitives/list/src/index.d.ts +4 -0
  212. package/dist/packages/primitives/list/src/index.d.ts.map +1 -0
  213. package/dist/packages/primitives/list/src/index.js +3 -0
  214. package/dist/packages/primitives/menubar/src/Menubar.js.map +1 -1
  215. package/dist/packages/primitives/menubar/src/Menubar.vue.d.ts +20 -6
  216. package/dist/packages/primitives/menubar/src/Menubar.vue.d.ts.map +1 -1
  217. package/dist/packages/primitives/menubar/src/Menubar.vue_vue_type_script_setup_true_lang.js +178 -64
  218. package/dist/packages/primitives/menubar/src/Menubar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  219. package/dist/packages/primitives/number-field/src/NumberField.js.map +1 -1
  220. package/dist/packages/primitives/number-field/src/NumberField.vue.d.ts +2 -2
  221. package/dist/packages/primitives/number-field/src/NumberField.vue_vue_type_script_setup_true_lang.js +1 -0
  222. package/dist/packages/primitives/number-field/src/NumberField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  223. package/dist/packages/primitives/number-field/src/index.d.ts +1 -1
  224. package/dist/packages/primitives/otp-input/src/OtpInput.vue.d.ts +2 -2
  225. package/dist/packages/primitives/otp-input/src/index.d.ts +1 -1
  226. package/dist/packages/primitives/pagination/src/Pagination.js.map +1 -1
  227. package/dist/packages/primitives/pagination/src/Pagination.vue.d.ts +3 -3
  228. package/dist/packages/primitives/pagination/src/Pagination.vue_vue_type_script_setup_true_lang.js +1 -1
  229. package/dist/packages/primitives/pagination/src/Pagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  230. package/dist/packages/primitives/pagination/src/index.d.ts +1 -1
  231. package/dist/packages/primitives/popover/src/Popover.js.map +1 -1
  232. package/dist/packages/primitives/popover/src/Popover.vue.d.ts.map +1 -1
  233. package/dist/packages/primitives/popover/src/Popover.vue_vue_type_script_setup_true_lang.js +1 -1
  234. package/dist/packages/primitives/popover/src/Popover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  235. package/dist/packages/primitives/progress/src/Progress.vue.d.ts +2 -2
  236. package/dist/packages/primitives/progress/src/index.d.ts +1 -1
  237. package/dist/packages/primitives/radial-progress/src/RadialProgress.vue.d.ts +2 -2
  238. package/dist/packages/primitives/radial-progress/src/index.d.ts +1 -1
  239. package/dist/packages/primitives/radio-group/src/RadioGroup.js.map +1 -1
  240. package/dist/packages/primitives/radio-group/src/RadioGroup.vue.d.ts.map +1 -1
  241. package/dist/packages/primitives/radio-group/src/RadioGroup.vue_vue_type_script_setup_true_lang.js +1 -1
  242. package/dist/packages/primitives/radio-group/src/RadioGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  243. package/dist/packages/primitives/rating/src/Rating.vue.d.ts +2 -2
  244. package/dist/packages/primitives/rating/src/index.d.ts +1 -1
  245. package/dist/packages/primitives/segmented-control/src/SegmentedControl.js.map +1 -1
  246. package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue.d.ts.map +1 -1
  247. package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue_vue_type_script_setup_true_lang.js +2 -3
  248. package/dist/packages/primitives/segmented-control/src/SegmentedControl.vue_vue_type_script_setup_true_lang.js.map +1 -1
  249. package/dist/packages/primitives/select/src/Select.js.map +1 -1
  250. package/dist/packages/primitives/select/src/Select.vue.d.ts.map +1 -1
  251. package/dist/packages/primitives/select/src/Select.vue_vue_type_script_setup_true_lang.js +1 -1
  252. package/dist/packages/primitives/select/src/Select.vue_vue_type_script_setup_true_lang.js.map +1 -1
  253. package/dist/packages/primitives/slider/src/Slider.js.map +1 -1
  254. package/dist/packages/primitives/slider/src/Slider.vue.d.ts +6 -3
  255. package/dist/packages/primitives/slider/src/Slider.vue.d.ts.map +1 -1
  256. package/dist/packages/primitives/slider/src/Slider.vue_vue_type_script_setup_true_lang.js +15 -10
  257. package/dist/packages/primitives/slider/src/Slider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  258. package/dist/packages/primitives/stepper/src/Stepper.vue.d.ts +2 -2
  259. package/dist/packages/primitives/stepper/src/index.d.ts +1 -1
  260. package/dist/packages/primitives/switch/src/Switch.js.map +1 -1
  261. package/dist/packages/primitives/switch/src/Switch.vue.d.ts +1 -1
  262. package/dist/packages/primitives/switch/src/Switch.vue.d.ts.map +1 -1
  263. package/dist/packages/primitives/switch/src/Switch.vue_vue_type_script_setup_true_lang.js +3 -3
  264. package/dist/packages/primitives/switch/src/Switch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  265. package/dist/packages/primitives/table/src/Table.js +7 -0
  266. package/dist/packages/primitives/table/src/Table.js.map +1 -0
  267. package/dist/packages/primitives/table/src/Table.vue.d.ts +45 -0
  268. package/dist/packages/primitives/table/src/Table.vue_vue_type_script_setup_true_lang.js +72 -0
  269. package/dist/packages/primitives/table/src/Table.vue_vue_type_script_setup_true_lang.js.map +1 -0
  270. package/dist/packages/primitives/table/src/TableCell.js +7 -0
  271. package/dist/packages/primitives/table/src/TableCell.js.map +1 -0
  272. package/dist/packages/primitives/table/src/TableCell.vue.d.ts +19 -0
  273. package/dist/packages/primitives/table/src/TableCell.vue.d.ts.map +1 -0
  274. package/dist/packages/primitives/table/src/TableCell.vue_vue_type_script_setup_true_lang.js +17 -0
  275. package/dist/packages/primitives/table/src/TableCell.vue_vue_type_script_setup_true_lang.js.map +1 -0
  276. package/dist/packages/primitives/table/src/TableHead.js +7 -0
  277. package/dist/packages/primitives/table/src/TableHead.js.map +1 -0
  278. package/dist/packages/primitives/table/src/TableHead.vue.d.ts +56 -0
  279. package/dist/packages/primitives/table/src/TableHead.vue_vue_type_script_setup_true_lang.js +79 -0
  280. package/dist/packages/primitives/table/src/TableHead.vue_vue_type_script_setup_true_lang.js.map +1 -0
  281. package/dist/packages/primitives/table/src/TableRow.js +7 -0
  282. package/dist/packages/primitives/table/src/TableRow.js.map +1 -0
  283. package/dist/packages/primitives/table/src/TableRow.vue.d.ts +39 -0
  284. package/dist/packages/primitives/table/src/TableRow.vue.d.ts.map +1 -0
  285. package/dist/packages/primitives/table/src/TableRow.vue_vue_type_script_setup_true_lang.js +61 -0
  286. package/dist/packages/primitives/table/src/TableRow.vue_vue_type_script_setup_true_lang.js.map +1 -0
  287. package/dist/packages/primitives/table/src/index.d.ts +10 -0
  288. package/dist/packages/primitives/table/src/index.js +8 -0
  289. package/dist/packages/primitives/tabs/src/Tabs.js.map +1 -1
  290. package/dist/packages/primitives/tabs/src/Tabs.vue.d.ts.map +1 -1
  291. package/dist/packages/primitives/tabs/src/Tabs.vue_vue_type_script_setup_true_lang.js +1 -1
  292. package/dist/packages/primitives/tabs/src/Tabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  293. package/dist/packages/primitives/tags-input/src/TagsInput.js.map +1 -1
  294. package/dist/packages/primitives/tags-input/src/TagsInput.vue.d.ts +2 -2
  295. package/dist/packages/primitives/tags-input/src/TagsInput.vue_vue_type_script_setup_true_lang.js +1 -1
  296. package/dist/packages/primitives/tags-input/src/TagsInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  297. package/dist/packages/primitives/tags-input/src/index.d.ts +1 -1
  298. package/dist/packages/primitives/text-field/src/TextField.js.map +1 -1
  299. package/dist/packages/primitives/text-field/src/TextField.vue.d.ts +2 -2
  300. package/dist/packages/primitives/text-field/src/TextField.vue_vue_type_script_setup_true_lang.js +1 -1
  301. package/dist/packages/primitives/text-field/src/TextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  302. package/dist/packages/primitives/text-field/src/index.d.ts +1 -1
  303. package/dist/packages/primitives/text-field/src/index.js +2 -2
  304. package/dist/packages/primitives/textarea/src/Textarea.js.map +1 -1
  305. package/dist/packages/primitives/textarea/src/Textarea.vue.d.ts +2 -2
  306. package/dist/packages/primitives/textarea/src/Textarea.vue_vue_type_script_setup_true_lang.js +1 -1
  307. package/dist/packages/primitives/textarea/src/Textarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  308. package/dist/packages/primitives/textarea/src/index.d.ts +1 -1
  309. package/dist/packages/primitives/textarea/src/index.js +2 -2
  310. package/dist/packages/primitives/time-picker/src/TimePicker.vue.d.ts +2 -2
  311. package/dist/packages/primitives/timeline/src/Timeline.js +7 -0
  312. package/dist/packages/primitives/timeline/src/Timeline.js.map +1 -0
  313. package/dist/packages/primitives/timeline/src/Timeline.vue.d.ts +37 -0
  314. package/dist/packages/primitives/timeline/src/Timeline.vue_vue_type_script_setup_true_lang.js +44 -0
  315. package/dist/packages/primitives/timeline/src/Timeline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  316. package/dist/packages/primitives/timeline/src/TimelineItem.js +7 -0
  317. package/dist/packages/primitives/timeline/src/TimelineItem.js.map +1 -0
  318. package/dist/packages/primitives/timeline/src/TimelineItem.vue.d.ts +50 -0
  319. package/dist/packages/primitives/timeline/src/TimelineItem.vue_vue_type_script_setup_true_lang.js +96 -0
  320. package/dist/packages/primitives/timeline/src/TimelineItem.vue_vue_type_script_setup_true_lang.js.map +1 -0
  321. package/dist/packages/primitives/timeline/src/index.d.ts +5 -0
  322. package/dist/packages/primitives/timeline/src/index.js +5 -0
  323. package/dist/packages/primitives/toast/src/Toast.vue.d.ts +2 -2
  324. package/dist/packages/primitives/toast/src/ToastItem.js.map +1 -1
  325. package/dist/packages/primitives/toast/src/ToastItem.vue.d.ts +2 -2
  326. package/dist/packages/primitives/toast/src/ToastItem.vue_vue_type_script_setup_true_lang.js +2 -2
  327. package/dist/packages/primitives/toast/src/ToastItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  328. package/dist/packages/primitives/toast/src/index.d.ts +1 -1
  329. package/dist/packages/primitives/tooltip/src/Tooltip.js.map +1 -1
  330. package/dist/packages/primitives/tooltip/src/Tooltip.vue.d.ts.map +1 -1
  331. package/dist/packages/primitives/tooltip/src/Tooltip.vue_vue_type_script_setup_true_lang.js +1 -1
  332. package/dist/packages/primitives/tooltip/src/Tooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  333. package/dist/packages/primitives/window-controls/src/WindowControls.vue.d.ts +2 -2
  334. package/dist/packages/primitives/window-controls/src/index.d.ts +1 -1
  335. package/dist/styles/global.css +59 -0
  336. package/dist/styles/theme.css +13 -0
  337. package/dist/theme.js +3 -0
  338. package/package.json +13 -7
  339. package/dist/packages/blocks/desktop-app-shell/src/DesktopAppShell.vue.d.ts.map +0 -1
  340. package/dist/packages/blocks/title-bar/src/TitleBar.vue.d.ts.map +0 -1
  341. package/dist/packages/blocks/toast-stack/src/ToastStack.vue.d.ts.map +0 -1
  342. package/dist/packages/composition/scroll-reel/src/ScrollReel.vue.d.ts.map +0 -1
  343. package/dist/packages/composition/scroll-reel/src/index.d.ts.map +0 -1
  344. package/dist/packages/loom/src/a11y.d.ts.map +0 -1
  345. package/dist/packages/loom/src/index.d.ts.map +0 -1
  346. package/dist/packages/primitives/alert-dialog/src/AlertDialog.vue.d.ts.map +0 -1
  347. package/dist/packages/primitives/alert-dialog/src/index.d.ts.map +0 -1
  348. package/dist/packages/primitives/avatar-group/src/AvatarGroup.vue.d.ts.map +0 -1
  349. package/dist/packages/primitives/avatar-group/src/index.d.ts.map +0 -1
  350. package/dist/packages/primitives/breadcrumb/src/Breadcrumb.vue.d.ts.map +0 -1
  351. package/dist/packages/primitives/breadcrumb/src/index.d.ts.map +0 -1
  352. package/dist/packages/primitives/color-picker/src/ColorPicker.vue.d.ts.map +0 -1
  353. package/dist/packages/primitives/color-picker/src/index.d.ts.map +0 -1
  354. package/dist/packages/primitives/combobox/src/Combobox.vue.d.ts.map +0 -1
  355. package/dist/packages/primitives/combobox/src/index.d.ts.map +0 -1
  356. package/dist/packages/primitives/date-picker/src/DatePicker.vue.d.ts.map +0 -1
  357. package/dist/packages/primitives/date-range-picker/src/DateRangePicker.vue.d.ts.map +0 -1
  358. package/dist/packages/primitives/date-range-picker/src/index.d.ts.map +0 -1
  359. package/dist/packages/primitives/date-time-picker/src/DateTimePicker.vue.d.ts.map +0 -1
  360. package/dist/packages/primitives/date-time-range-picker/src/DateTimeRangePicker.vue.d.ts.map +0 -1
  361. package/dist/packages/primitives/date-time-range-picker/src/index.d.ts.map +0 -1
  362. package/dist/packages/primitives/dialog/src/Dialog.vue.d.ts.map +0 -1
  363. package/dist/packages/primitives/dialog/src/index.d.ts.map +0 -1
  364. package/dist/packages/primitives/drawer/src/Drawer.vue.d.ts.map +0 -1
  365. package/dist/packages/primitives/drawer/src/index.d.ts.map +0 -1
  366. package/dist/packages/primitives/editable/src/Editable.vue.d.ts.map +0 -1
  367. package/dist/packages/primitives/editable/src/index.d.ts.map +0 -1
  368. package/dist/packages/primitives/file-upload/src/FileUpload.vue.d.ts.map +0 -1
  369. package/dist/packages/primitives/file-upload/src/index.d.ts.map +0 -1
  370. package/dist/packages/primitives/indicator/src/Indicator.vue.d.ts.map +0 -1
  371. package/dist/packages/primitives/indicator/src/index.d.ts.map +0 -1
  372. package/dist/packages/primitives/number-field/src/NumberField.vue.d.ts.map +0 -1
  373. package/dist/packages/primitives/number-field/src/index.d.ts.map +0 -1
  374. package/dist/packages/primitives/otp-input/src/OtpInput.vue.d.ts.map +0 -1
  375. package/dist/packages/primitives/otp-input/src/index.d.ts.map +0 -1
  376. package/dist/packages/primitives/pagination/src/Pagination.vue.d.ts.map +0 -1
  377. package/dist/packages/primitives/pagination/src/index.d.ts.map +0 -1
  378. package/dist/packages/primitives/progress/src/Progress.vue.d.ts.map +0 -1
  379. package/dist/packages/primitives/progress/src/index.d.ts.map +0 -1
  380. package/dist/packages/primitives/radial-progress/src/RadialProgress.vue.d.ts.map +0 -1
  381. package/dist/packages/primitives/radial-progress/src/index.d.ts.map +0 -1
  382. package/dist/packages/primitives/rating/src/Rating.vue.d.ts.map +0 -1
  383. package/dist/packages/primitives/rating/src/index.d.ts.map +0 -1
  384. package/dist/packages/primitives/stepper/src/Stepper.vue.d.ts.map +0 -1
  385. package/dist/packages/primitives/stepper/src/index.d.ts.map +0 -1
  386. package/dist/packages/primitives/tags-input/src/TagsInput.vue.d.ts.map +0 -1
  387. package/dist/packages/primitives/tags-input/src/index.d.ts.map +0 -1
  388. package/dist/packages/primitives/text-field/src/TextField.vue.d.ts.map +0 -1
  389. package/dist/packages/primitives/text-field/src/index.d.ts.map +0 -1
  390. package/dist/packages/primitives/textarea/src/Textarea.vue.d.ts.map +0 -1
  391. package/dist/packages/primitives/textarea/src/index.d.ts.map +0 -1
  392. package/dist/packages/primitives/time-picker/src/TimePicker.vue.d.ts.map +0 -1
  393. package/dist/packages/primitives/toast/src/Toast.vue.d.ts.map +0 -1
  394. package/dist/packages/primitives/toast/src/ToastItem.vue.d.ts.map +0 -1
  395. package/dist/packages/primitives/toast/src/index.d.ts.map +0 -1
  396. package/dist/packages/primitives/window-controls/src/WindowControls.vue.d.ts.map +0 -1
  397. package/dist/packages/primitives/window-controls/src/index.d.ts.map +0 -1
  398. package/dist/styles/theme.contrast.test.ts +0 -460
@@ -1 +1 @@
1
- {"version":3,"file":"Textarea.js","names":[],"sources":["../../../../../packages/primitives/textarea/src/Textarea.vue"],"sourcesContent":["<script lang=\"ts\">\nimport {\n COUNT_LABELS,\n warningWindow,\n type CountBand,\n type TextareaLabels,\n} from \"@ecoma-io/loom-labels\";\n\nexport type TextareaResize = \"none\" | \"vertical\";\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 * rather than a transcription of it.\n */\nexport const TEXTAREA_LABELS: TextareaLabels = COUNT_LABELS;\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from \"vue\";\nimport { cn } 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\";\n\n/**\n * Textarea — multi-line text entry, around a native `<textarea>`. The\n * accessible name is a first-class prop pair (`aria-label`/`aria-labelledby`,\n * camelized by Vue) bound onto the textarea explicitly, the same contract as\n * TextField.\n *\n * The border, focus ring and invalid state live on the wrapper so optional\n * `#leading`/`#trailing` adornments sit *inside* the field and share its focus\n * bloom, instead of every caller re-inventing an icon-inside-input layout.\n * The slot names and positions match TextField's, and the vertical alignment\n * differs — `items-start` for multi-line, where the text begins, versus\n * `items-center` for single-line — because a five-row box with a centered icon\n * mid-field reads as a mistake rather than a choice.\n *\n * Wrapping a rendered structure means the fallthrough `class` must land on the\n * wrapper (so a caller's `w-64`/`flex-1` sizes the whole field) while the rest\n * of the attrs (`id`, `name`, `data-testid`, `aria-describedby`) belong on the\n * real `<textarea>` — the split-attrs convention shared with TextField.\n *\n * That same wrapper is why the character counter is a prop here rather than a\n * `CountedField` beside this one. A counter rendered *below* the field would\n * have to move the fallthrough `class` off the box a caller sizes with it, and\n * a textarea has no free column to hang one in — the value fills the box and\n * scrolls, and the bottom-right corner belongs to the native resize grabber.\n * The counter sits below the border, where `resize` moves the box's bottom\n * edge and the counter travels with it rather than floating over the text.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself through\n * `useFieldControl()` — the row's id, the id of its hint or error line, and\n * `required` / `invalid` / `disabled` / `readonly` / `name` — so\n * `<Field label=\"Bio\" hint=\"…\"><Textarea /></Field>` needs no attributes at the\n * call site. Every prop below still wins over the row when it is set, in both\n * directions, which is why the four booleans default to `undefined` rather\n * than `false`.\n *\n * `readonly` and `disabled` are different states, and they are told apart on\n * three channels rather than one. A read-only field is a value on show: the\n * subtle fill, the text at full strength, the input hairline, still a Tab stop\n * and still submitted. A disabled one is unavailable: the deeper neutral fill,\n * the muted text, and the hairline receding to the plain border — a difference\n * in shape as well as in colour, which is what survives a reader who cannot\n * resolve the two fills apart.\n */\nconst props = withDefaults(\n defineProps<{\n /** The current value. Pair with `@update:modelValue` for `v-model`. */\n modelValue?: string;\n /** Text shown in the empty field. */\n placeholder?: string;\n /** Disables the field and blocks pointer and keyboard input. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Shows the value without allowing an edit, while staying focusable and still submitted. Unset defers to a wrapping Field. */\n readonly?: boolean | undefined;\n /** Error state: paints the destructive border/ring and sets aria-invalid. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /** Marks the field mandatory to assistive tech (`aria-required`). Unset defers to a wrapping Field. */\n required?: boolean | undefined;\n /** The field name for a native form post or `FormData`. Unset defers to a wrapping Field. */\n name?: string;\n /** Initial height in text rows. */\n rows?: number;\n /** Whether a person can drag-resize the field; `none` locks it to `rows`. The counter sits below the box either way, so a drag never moves it onto the value. */\n resize?: TextareaResize;\n /** The accessible name, when no visible element already labels the field. */\n ariaLabel?: string;\n /** The id of the visible element that labels the field (e.g. a Field wrapper's label). */\n ariaLabelledby?: string;\n /**\n * The character limit the counter reports against, and the point past\n * which it reads as over-limit.\n *\n * Advisory, exactly as `required` is: it does **not** set the native\n * `maxlength`, which silently truncates a paste and tells the reader\n * nothing. Pass `maxlength` yourself as a plain attribute where you want\n * the browser to enforce it too.\n */\n maxLength?: number;\n /**\n * Forces the counter on or off. Left unset it follows `maxLength` — a\n * limit the reader is never shown is one they can only discover by\n * exceeding it — so set it `true` for a running total with no limit, and\n * `false` to hold a limit the field reports nowhere.\n */\n showCount?: boolean | undefined;\n /**\n * Names for everything this control says out loud, as any subset of\n * `TextareaLabels` — the rest stay as the host's `provideLoomLabels`\n * vocabulary left them, and then as Loom's English.\n *\n * This is the per-instance correction, not the place to localise an\n * application: the case it exists for is a field counting something other\n * than characters, where \"80 words left\" is the true sentence.\n */\n labels?: LabelOverrides<TextareaLabels>;\n }>(),\n {\n // Not `false`, for any of the four — see TextField.vue, which carries the\n // full reasoning. `false` is a caller's decision that must beat the row;\n // absent is a caller saying nothing, and only `undefined` survives to mean\n // that past Vue's absent-Boolean-casts-to-false rule.\n disabled: undefined,\n readonly: undefined,\n invalid: undefined,\n required: undefined,\n rows: 3,\n resize: \"vertical\",\n // `undefined` for the same Boolean-cast reason, and here it is the whole\n // feature: `showCount ?? (maxLength !== undefined)` would be a dead branch\n // if an absent prop arrived as `false`.\n showCount: undefined,\n },\n);\n\nconst emit = defineEmits<{ \"update:modelValue\": [value: string] }>();\n\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: textareaAttrs } = useSplitAttrs();\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(\"textarea\", TEXTAREA_LABELS, () => props.labels);\n\nconst countId = useId();\n\n/**\n * What is actually in the box, which is not always `modelValue` — TextField's\n * reasoning, and the same defect waiting on this side of it.\n *\n * Used without `v-model` this component is uncontrolled, so `modelValue` stays\n * `undefined` while the reader types and the counter still has to count. And\n * binding `:value=\"currentValue\"` rather than `:value=\"modelValue\"` is\n * load-bearing rather than cosmetic: Vue's `value` patch coerces\n * `null`/`undefined` to `\"\"`, so any re-render of an uncontrolled field wipes\n * it — a defect nothing had met only because nothing in this component\n * re-rendered on a keystroke until the counter did.\n */\nconst typed = ref(props.modelValue ?? \"\");\nconst currentValue = computed(() => props.modelValue ?? typed.value);\n\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n typed.value = value;\n emit(\"update:modelValue\", value);\n}\n\n/**\n * Length in UTF-16 code units, which is what the platform's own `maxlength`\n * counts. `[...value].length` would count code points and disagree with the\n * browser on any astral character, so a caller who pairs this with a native\n * `maxlength` would watch the counter and the enforcement drift apart.\n */\nconst count = computed(() => currentValue.value.length);\nconst showsCount = computed(() => props.showCount ?? props.maxLength !== undefined);\n\nconst band = computed<CountBand>(() => {\n const max = props.maxLength;\n if (max === undefined) return \"under\";\n const remaining = max - count.value;\n if (remaining < 0) return \"over\";\n return remaining <= warningWindow(max) ? \"near\" : \"under\";\n});\n\nconst countText = computed(() => {\n const max = props.maxLength;\n if (max === undefined) return text.value.count({ count: count.value });\n const over = count.value - max;\n return over > 0\n ? text.value.countOverMax({ count: count.value, max, over })\n : text.value.countOfMax({ count: count.value, max });\n});\n\n/**\n * What the live region says, and it changes **only when the band changes** —\n * TextField's decision, taken once for the pair rather than re-litigated here.\n *\n * A region holding the counter itself would speak on every keystroke, burying\n * the field's own echo of the character just typed under a number nobody asked\n * for. What a reader needs is the two facts they cannot see coming — the limit\n * is close, and the limit is passed — so this fires once at each crossing and\n * goes quiet again on the way back down. The numbers are therefore true at the\n * moment they are spoken and not after; the visible counter carries the live\n * figure, and it is wired into `aria-describedby`, so the exact count is one\n * re-read away whenever the reader wants it rather than pushed at them.\n */\nconst announcement = ref(\"\");\nwatch(band, (now) => {\n const max = props.maxLength;\n if (max === undefined || now === \"under\") {\n announcement.value = \"\";\n return;\n }\n announcement.value =\n now === \"over\"\n ? text.value.limitExceeded({ over: count.value - max, max })\n : text.value.approachingLimit({ remaining: max - count.value, max });\n});\n\n// `id` and `aria-describedby` arrive as fallthrough attrs rather than props, so\n// they are handed in as this caller's own values: a caller who sets either\n// still beats the row, and the row's description is merged into theirs. The\n// counter joins that list rather than replacing anything — it reads after the\n// caller's own description and before the row's message, which is the same\n// most-specific-first order `useFieldControl` applies one layer out.\n//\n// `field.attrs` then spreads **after** `textareaAttrs`, and that position is\n// the contract — see TextField.vue, which carries the full reasoning. The\n// `:aria-invalid` binding that used to sit below went with it: an individual\n// attribute written after a `v-bind` wins, so keeping one would overwrite the\n// resolved value with the raw prop.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [attrs[\"aria-describedby\"] as string | undefined, showsCount.value ? countId : \"\"]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n name: props.name,\n disabled: props.disabled,\n readonly: props.readonly,\n invalid: props.invalid,\n required: props.required,\n}));\n</script>\n\n<template>\n <!-- Layout, and nothing else: no fallthrough attribute lands here and nothing\n is painted on it. `inline-flex` is what makes it shrink-wrap the field\n frame — a block wrapper would be as wide as the column, and the counter\n would right-align to that column rather than to the field's own edge. -->\n <div class=\"inline-flex max-w-full flex-col align-top\">\n <!-- The field frame: border, focus, state. Like TextField, the border lives\n here so optional #leading/#trailing adornments sit inside the field and\n share its focus bloom, instead of every caller re-inventing an\n icon-inside-input layout. -->\n <div\n :data-disabled=\"field.disabled || undefined\"\n :data-readonly=\"field.readonly || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n 'group flex items-start gap-2 rounded-md border border-input bg-background text-foreground px-3 py-2 text-sm',\n 'transition-[color,background-color,border-color,box-shadow] duration-fast ease-out',\n // Rim-lit at rest, the weave blooms on focus (Signature law): the field\n // catches light instead of casting a shadow, and focus draws it tight.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // The three resting appearances, and each is three changes rather than\n // one. Read-only and disabled used to share `bg-muted` and part only on\n // the text colour, which is a distinction carried by hue alone — the\n // one thing a reader with a colour deficiency, or a low-quality\n // display, or forced-colors, does not get. The fill separates them and\n // the border weight separates them again, so the progression\n // available → on show → unavailable is legible on three channels.\n //\n // Read-only is a value **on show**: the fill lifts off `background` to\n // `subtle` so the row reads as settled rather than editable, and the\n // text stays full strength — 13.46:1 over that fill — because being\n // read is the entire purpose of the state. The border does not move,\n // because nothing about the control's reach has: it is still a Tab stop\n // and still submitted. The native `readonly` attribute below carries\n // the same fact to assistive tech, so nothing here rests on colour.\n field.readonly && 'bg-subtle',\n // Disabled is **unavailable**: the fill drains one step further to\n // `muted`, the text follows it down to `muted-foreground` (4.68:1 over\n // it, still AA), and the rim slackens from `input` to the lighter\n // `border`.\n //\n // Drained rather than faded, and `opacity` is never the mechanism. The\n // element a dim would sit on is the box, and the box is wrapped around\n // the one thing a reader still needs: measured on the built site,\n // `opacity-50` put a disabled field's value at 3.08:1 and its\n // placeholder — already the muted colour before the fade — at 2.07:1.\n // The text colour is declared here rather than on the `<textarea>` because\n // the textarea sets no resting colour of its own and inherits this one,\n // which also carries it to the adornments in the same box.\n field.disabled && 'cursor-not-allowed border-border bg-muted text-muted-foreground',\n // The same appearance, reached from the other direction: a\n // `<fieldset disabled>` above this field disables the `<textarea>` inside\n // it natively and never touches `field.disabled`, so the rule above\n // cannot fire and the field rendered byte-identical to an editable one.\n //\n // `in-[fieldset:disabled]:` is a *read* of the attribute that did the\n // disabling, so the fill and the native inertness cannot disagree — the\n // fieldset element is still the only place the fact is written. It is\n // preferred to `has-[:disabled]:` (a wrapper reading its own contents)\n // because that proxy is wrong wherever a control holds a part that\n // disables itself.\n 'in-[fieldset:disabled]:cursor-not-allowed in-[fieldset:disabled]:bg-muted in-[fieldset:disabled]:text-muted-foreground',\n // Gated where the two rules below are ordered, and for the same reason.\n // Tailwind emits the ancestor half of `in-*` inside `:where()`, so this\n // carries no more specificity than `border-destructive` and would win\n // on emission order alone — an unavailable field that is also in error\n // would quietly lose its destructive rim.\n !field.invalid && 'in-[fieldset:disabled]:border-border',\n // Last of the three rules that name a border colour, and the order is\n // load-bearing: `cn()` resolves a border colour by whichever class it\n // saw last, so a field that is both in error and unavailable would lose\n // its destructive rim to the disabled one if this sat above it.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n attrs.class as string,\n )\n \"\n >\n <span\n v-if=\"$slots.leading\"\n class=\"inline-flex shrink-0 text-muted-foreground transition-colors duration-fast ease-out group-focus-within:text-foreground\"\n >\n <slot name=\"leading\" />\n </span>\n <textarea\n v-bind=\"{ ...textareaAttrs, ...field.attrs }\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"ariaLabelledby\"\n :value=\"currentValue\"\n :placeholder=\"placeholder\"\n :disabled=\"field.disabled\"\n :readonly=\"field.readonly\"\n :rows=\"rows\"\n :class=\"\n cn(\n 'min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed',\n resize === 'none' ? 'resize-none' : 'resize-y',\n )\n \"\n @input=\"onInput\"\n />\n <span\n v-if=\"$slots.trailing\"\n class=\"inline-flex shrink-0 items-center gap-1 text-muted-foreground transition-colors duration-fast ease-out group-focus-within:text-foreground\"\n >\n <slot name=\"trailing\" />\n </span>\n </div>\n <!-- Below the box, never inside it. A textarea's value fills its box and\n scrolls, so there is no free column to hang a readout in, and the\n bottom-right corner belongs to the native resize grabber — an inset\n counter would sit on top of the one affordance that says the field can\n be dragged. Here it travels with the bottom edge instead, so `resize`\n moves it rather than moving the box out from under it.\n\n `tabular` because the number changes under itself on every keystroke,\n and proportional digits would shuffle the whole readout sideways as\n they do. -->\n <span\n v-if=\"showsCount\"\n :id=\"countId\"\n :data-over-limit=\"band === 'over' || undefined\"\n :class=\"\n cn(\n 'tabular mt-1 select-none self-end text-small',\n band === 'under' && 'text-muted-foreground',\n // Colour, and never only colour: `countOverMax` says the limit is\n // passed in words as well, and `approachingLimit` announces the near\n // band once. The digits themselves are the third reading — 21/20 is\n // over the limit whatever it is painted.\n band === 'near' && 'text-warning',\n band === 'over' && 'text-destructive-text',\n )\n \"\n >\n {{ countText }}\n </span>\n <!-- In the DOM from the first render, empty. A live region mounted at the\n same moment as its text is a region assistive tech was not yet\n watching, so the first warning — the one that matters most — is the one\n it announces least reliably. -->\n <span v-if=\"showsCount && maxLength !== undefined\" role=\"status\" class=\"sr-only\">\n {{ announcement }}\n </span>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"Textarea.js","names":[],"sources":["../../../../../packages/primitives/textarea/src/Textarea.vue"],"sourcesContent":["<script lang=\"ts\">\nimport {\n COUNT_LABELS,\n warningWindow,\n type CountBand,\n type TextareaLabels,\n} from \"@ecoma-io/loom-labels\";\n\nexport type TextareaResize = \"none\" | \"vertical\";\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 * rather than a transcription of it.\n */\nexport const TEXTAREA_LABELS: TextareaLabels = COUNT_LABELS;\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from \"vue\";\nimport { cn } 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\";\n\n/**\n * Textarea — multi-line text entry, around a native `<textarea>`. The\n * accessible name is a first-class prop pair (`aria-label`/`aria-labelledby`,\n * camelized by Vue) bound onto the textarea explicitly, the same contract as\n * TextField.\n *\n * The border, focus ring and invalid state live on the wrapper so optional\n * `#leading`/`#trailing` adornments sit *inside* the field and share its focus\n * bloom, instead of every caller re-inventing an icon-inside-input layout.\n * The slot names and positions match TextField's, and the vertical alignment\n * differs — `items-start` for multi-line, where the text begins, versus\n * `items-center` for single-line — because a five-row box with a centered icon\n * mid-field reads as a mistake rather than a choice.\n *\n * Wrapping a rendered structure means the fallthrough `class` must land on the\n * wrapper (so a caller's `w-64`/`flex-1` sizes the whole field) while the rest\n * of the attrs (`id`, `name`, `data-testid`, `aria-describedby`) belong on the\n * real `<textarea>` — the split-attrs convention shared with TextField.\n *\n * That same wrapper is why the character counter is a prop here rather than a\n * `CountedField` beside this one. A counter rendered *below* the field would\n * have to move the fallthrough `class` off the box a caller sizes with it, and\n * a textarea has no free column to hang one in — the value fills the box and\n * scrolls, and the bottom-right corner belongs to the native resize grabber.\n * The counter sits below the border, where `resize` moves the box's bottom\n * edge and the counter travels with it rather than floating over the text.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself through\n * `useFieldControl()` — the row's id, the id of its hint or error line, and\n * `required` / `invalid` / `disabled` / `readonly` / `name` — so\n * `<Field label=\"Bio\" hint=\"…\"><Textarea /></Field>` needs no attributes at the\n * call site. Every prop below still wins over the row when it is set, in both\n * directions, which is why the four booleans default to `undefined` rather\n * than `false`.\n *\n * `readonly` and `disabled` are different states, and they are told apart on\n * three channels rather than one. A read-only field is a value on show: the\n * subtle fill, the text at full strength, the input hairline, still a Tab stop\n * and still submitted. A disabled one is unavailable: the deeper neutral fill,\n * the muted text, and the hairline receding to the plain border — a difference\n * in shape as well as in colour, which is what survives a reader who cannot\n * resolve the two fills apart.\n */\nconst props = withDefaults(\n defineProps<{\n /** The current value. Pair with `@update:modelValue` for `v-model`. */\n modelValue?: string;\n /** Text shown in the empty field. */\n placeholder?: string;\n /** Disables the field and blocks pointer and keyboard input. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Shows the value without allowing an edit, while staying focusable and still submitted. Unset defers to a wrapping Field. */\n readonly?: boolean | undefined;\n /** Error state: paints the destructive border/ring and sets aria-invalid. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /** Marks the field mandatory to assistive tech (`aria-required`). Unset defers to a wrapping Field. */\n required?: boolean | undefined;\n /** The field name for a native form post or `FormData`. Unset defers to a wrapping Field. */\n name?: string;\n /** Initial height in text rows. */\n rows?: number;\n /** Whether a person can drag-resize the field; `none` locks it to `rows`. The counter sits below the box either way, so a drag never moves it onto the value. */\n resize?: TextareaResize;\n /** The accessible name, when no visible element already labels the field. */\n ariaLabel?: string;\n /** The id of the visible element that labels the field (e.g. a Field wrapper's label). */\n ariaLabelledby?: string;\n /**\n * The character limit the counter reports against, and the point past\n * which it reads as over-limit.\n *\n * Advisory, exactly as `required` is: it does **not** set the native\n * `maxlength`, which silently truncates a paste and tells the reader\n * nothing. Pass `maxlength` yourself as a plain attribute where you want\n * the browser to enforce it too.\n */\n maxLength?: number;\n /**\n * Forces the counter on or off. Left unset it follows `maxLength` — a\n * limit the reader is never shown is one they can only discover by\n * exceeding it — so set it `true` for a running total with no limit, and\n * `false` to hold a limit the field reports nowhere.\n */\n showCount?: boolean | undefined;\n /**\n * Names for everything this control says out loud, as any subset of\n * `TextareaLabels` — the rest stay as the host's `provideLoomLabels`\n * vocabulary left them, and then as Loom's English.\n *\n * This is the per-instance correction, not the place to localise an\n * application: the case it exists for is a field counting something other\n * than characters, where \"80 words left\" is the true sentence.\n */\n labels?: LabelOverrides<TextareaLabels>;\n }>(),\n {\n // Not `false`, for any of the four — see TextField.vue, which carries the\n // full reasoning. `false` is a caller's decision that must beat the row;\n // absent is a caller saying nothing, and only `undefined` survives to mean\n // that past Vue's absent-Boolean-casts-to-false rule.\n disabled: undefined,\n readonly: undefined,\n invalid: undefined,\n required: undefined,\n rows: 3,\n resize: \"vertical\",\n // `undefined` for the same Boolean-cast reason, and here it is the whole\n // feature: `showCount ?? (maxLength !== undefined)` would be a dead branch\n // if an absent prop arrived as `false`.\n showCount: undefined,\n },\n);\n\nconst emit = defineEmits<{ \"update:modelValue\": [value: string] }>();\n\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: textareaAttrs } = useSplitAttrs();\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(\"textarea\", TEXTAREA_LABELS, () => props.labels);\n\nconst countId = useId();\n\n/**\n * What is actually in the box, which is not always `modelValue` — TextField's\n * reasoning, and the same defect waiting on this side of it.\n *\n * Used without `v-model` this component is uncontrolled, so `modelValue` stays\n * `undefined` while the reader types and the counter still has to count. And\n * binding `:value=\"currentValue\"` rather than `:value=\"modelValue\"` is\n * load-bearing rather than cosmetic: Vue's `value` patch coerces\n * `null`/`undefined` to `\"\"`, so any re-render of an uncontrolled field wipes\n * it — a defect nothing had met only because nothing in this component\n * re-rendered on a keystroke until the counter did.\n */\nconst typed = ref(props.modelValue ?? \"\");\nconst currentValue = computed(() => props.modelValue ?? typed.value);\n\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n typed.value = value;\n emit(\"update:modelValue\", value);\n}\n\n/**\n * Length in UTF-16 code units, which is what the platform's own `maxlength`\n * counts. `[...value].length` would count code points and disagree with the\n * browser on any astral character, so a caller who pairs this with a native\n * `maxlength` would watch the counter and the enforcement drift apart.\n */\nconst count = computed(() => currentValue.value.length);\nconst showsCount = computed(() => props.showCount ?? props.maxLength !== undefined);\n\nconst band = computed<CountBand>(() => {\n const max = props.maxLength;\n if (max === undefined) return \"under\";\n const remaining = max - count.value;\n if (remaining < 0) return \"over\";\n return remaining <= warningWindow(max) ? \"near\" : \"under\";\n});\n\nconst countText = computed(() => {\n const max = props.maxLength;\n if (max === undefined) return text.value.count({ count: count.value });\n const over = count.value - max;\n return over > 0\n ? text.value.countOverMax({ count: count.value, max, over })\n : text.value.countOfMax({ count: count.value, max });\n});\n\n/**\n * What the live region says, and it changes **only when the band changes** —\n * TextField's decision, taken once for the pair rather than re-litigated here.\n *\n * A region holding the counter itself would speak on every keystroke, burying\n * the field's own echo of the character just typed under a number nobody asked\n * for. What a reader needs is the two facts they cannot see coming — the limit\n * is close, and the limit is passed — so this fires once at each crossing and\n * goes quiet again on the way back down. The numbers are therefore true at the\n * moment they are spoken and not after; the visible counter carries the live\n * figure, and it is wired into `aria-describedby`, so the exact count is one\n * re-read away whenever the reader wants it rather than pushed at them.\n */\nconst announcement = ref(\"\");\nwatch(band, (now) => {\n const max = props.maxLength;\n if (max === undefined || now === \"under\") {\n announcement.value = \"\";\n return;\n }\n announcement.value =\n now === \"over\"\n ? text.value.limitExceeded({ over: count.value - max, max })\n : text.value.approachingLimit({ remaining: max - count.value, max });\n});\n\n// `id` and `aria-describedby` arrive as fallthrough attrs rather than props, so\n// they are handed in as this caller's own values: a caller who sets either\n// still beats the row, and the row's description is merged into theirs. The\n// counter joins that list rather than replacing anything — it reads after the\n// caller's own description and before the row's message, which is the same\n// most-specific-first order `useFieldControl` applies one layer out.\n//\n// `field.attrs` then spreads **after** `textareaAttrs`, and that position is\n// the contract — see TextField.vue, which carries the full reasoning. The\n// `:aria-invalid` binding that used to sit below went with it: an individual\n// attribute written after a `v-bind` wins, so keeping one would overwrite the\n// resolved value with the raw prop.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [attrs[\"aria-describedby\"] as string | undefined, showsCount.value ? countId : \"\"]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n name: props.name,\n disabled: props.disabled,\n readonly: props.readonly,\n invalid: props.invalid,\n required: props.required,\n}));\n</script>\n\n<template>\n <!-- Layout, and nothing else: no fallthrough attribute lands here and nothing\n is painted on it. `inline-flex` is what makes it shrink-wrap the field\n frame — a block wrapper would be as wide as the column, and the counter\n would right-align to that column rather than to the field's own edge. -->\n <div class=\"inline-flex max-w-full flex-col align-top\">\n <!-- The field frame: border, focus, state. Like TextField, the border lives\n here so optional #leading/#trailing adornments sit inside the field and\n share its focus bloom, instead of every caller re-inventing an\n icon-inside-input layout. -->\n <div\n :data-disabled=\"field.disabled || undefined\"\n :data-readonly=\"field.readonly || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n 'group flex items-start gap-2 rounded-md border border-input bg-background text-foreground px-3 py-2 text-sm',\n 'transition-[color,background-color,border-color,box-shadow] duration-fast ease-out',\n // Rim-lit at rest, the weave blooms on focus (Signature law): the field\n // catches light instead of casting a shadow, and focus draws it tight.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // The three resting appearances, and each is three changes rather than\n // one. Read-only and disabled used to share `bg-muted` and part only on\n // the text colour, which is a distinction carried by hue alone — the\n // one thing a reader with a colour deficiency, or a low-quality\n // display, or forced-colors, does not get. The fill separates them and\n // the border weight separates them again, so the progression\n // available → on show → unavailable is legible on three channels.\n //\n // Read-only is a value **on show**: the fill lifts off `background` to\n // `subtle` so the row reads as settled rather than editable, and the\n // text stays full strength — 13.46:1 over that fill — because being\n // read is the entire purpose of the state. The border does not move,\n // because nothing about the control's reach has: it is still a Tab stop\n // and still submitted. The native `readonly` attribute below carries\n // the same fact to assistive tech, so nothing here rests on colour.\n field.readonly && 'bg-subtle',\n // Disabled is **unavailable**: the fill drains one step further to\n // `muted`, the text follows it down to `muted-foreground` (4.68:1 over\n // it, still AA), and the rim slackens from `input` to the lighter\n // `border`.\n //\n // Drained rather than faded, and `opacity` is never the mechanism. The\n // element a dim would sit on is the box, and the box is wrapped around\n // the one thing a reader still needs: measured on the built site,\n // `opacity-50` put a disabled field's value at 3.08:1 and its\n // placeholder — already the muted colour before the fade — at 2.07:1.\n // The text colour is declared here rather than on the `<textarea>` because\n // the textarea sets no resting colour of its own and inherits this one,\n // which also carries it to the adornments in the same box.\n field.disabled && 'cursor-not-allowed border-border bg-muted text-muted-foreground',\n // The same appearance, reached from the other direction: a\n // `<fieldset disabled>` above this field disables the `<textarea>` inside\n // it natively and never touches `field.disabled`, so the rule above\n // cannot fire and the field rendered byte-identical to an editable one.\n //\n // `in-[fieldset:disabled]:` is a *read* of the attribute that did the\n // disabling, so the fill and the native inertness cannot disagree — the\n // fieldset element is still the only place the fact is written. It is\n // preferred to `has-[:disabled]:` (a wrapper reading its own contents)\n // because that proxy is wrong wherever a control holds a part that\n // disables itself.\n 'in-[fieldset:disabled]:cursor-not-allowed in-[fieldset:disabled]:bg-muted in-[fieldset:disabled]:text-muted-foreground',\n // Gated where the two rules below are ordered, and for the same reason.\n // Tailwind emits the ancestor half of `in-*` inside `:where()`, so this\n // carries no more specificity than `border-destructive` and would win\n // on emission order alone — an unavailable field that is also in error\n // would quietly lose its destructive rim.\n !field.invalid && 'in-[fieldset:disabled]:border-border',\n // Last of the three rules that name a border colour, and the order is\n // load-bearing: `cn()` resolves a border colour by whichever class it\n // saw last, so a field that is both in error and unavailable would lose\n // its destructive rim to the disabled one if this sat above it.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n attrs.class as string,\n )\n \"\n >\n <span\n v-if=\"$slots.leading\"\n class=\"inline-flex shrink-0 text-muted-foreground transition-colors duration-fast ease-out group-focus-within:text-foreground\"\n >\n <slot name=\"leading\" />\n </span>\n <textarea\n v-bind=\"{ ...textareaAttrs, ...field.attrs }\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"ariaLabelledby\"\n :value=\"currentValue\"\n :placeholder=\"placeholder\"\n :disabled=\"field.disabled\"\n :readonly=\"field.readonly\"\n :rows=\"rows\"\n :class=\"\n cn(\n 'min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed',\n resize === 'none' ? 'resize-none' : 'resize-y',\n )\n \"\n @input=\"onInput\"\n />\n <span\n v-if=\"$slots.trailing\"\n class=\"inline-flex shrink-0 items-center gap-1 text-muted-foreground transition-colors duration-fast ease-out group-focus-within:text-foreground\"\n >\n <slot name=\"trailing\" />\n </span>\n </div>\n <!-- Below the box, never inside it. A textarea's value fills its box and\n scrolls, so there is no free column to hang a readout in, and the\n bottom-right corner belongs to the native resize grabber — an inset\n counter would sit on top of the one affordance that says the field can\n be dragged. Here it travels with the bottom edge instead, so `resize`\n moves it rather than moving the box out from under it.\n\n `tabular` because the number changes under itself on every keystroke,\n and proportional digits would shuffle the whole readout sideways as\n they do. -->\n <span\n v-if=\"showsCount\"\n :id=\"countId\"\n :data-over-limit=\"band === 'over' || undefined\"\n :class=\"\n cn(\n 'tabular mt-1 select-none self-end text-small',\n band === 'under' && 'text-muted-foreground',\n // Colour, and never only colour: `countOverMax` says the limit is\n // passed in words as well, and `approachingLimit` announces the near\n // band once. The digits themselves are the third reading — 21/20 is\n // over the limit whatever it is painted.\n band === 'near' && 'text-warning-text',\n band === 'over' && 'text-destructive-text',\n )\n \"\n >\n {{ countText }}\n </span>\n <!-- In the DOM from the first render, empty. A live region mounted at the\n same moment as its text is a region assistive tech was not yet\n watching, so the first warning — the one that matters most — is the one\n it announces least reliably. -->\n <span v-if=\"showsCount && maxLength !== undefined\" role=\"status\" class=\"sr-only\">\n {{ announcement }}\n </span>\n </div>\n</template>\n"],"mappings":""}
@@ -1,5 +1,5 @@
1
- import { type LabelOverrides } from "@ecoma-io/loom-labels";
2
- import { type TextareaLabels } from "@ecoma-io/loom-labels";
1
+ import { type LabelOverrides } from "../../../labels/src/index";
2
+ import { type TextareaLabels } from "../../../labels/src/index";
3
3
  export type TextareaResize = "none" | "vertical";
4
4
  /**
5
5
  * Loom's English, co-located with the component so it tree-shakes with it, and
@@ -239,7 +239,7 @@ var Textarea_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineC
239
239
  key: 0,
240
240
  id: unref(countId),
241
241
  "data-over-limit": band.value === "over" || void 0,
242
- class: normalizeClass(unref(cn)("tabular mt-1 select-none self-end text-small", band.value === "under" && "text-muted-foreground", band.value === "near" && "text-warning", band.value === "over" && "text-destructive-text"))
242
+ class: normalizeClass(unref(cn)("tabular mt-1 select-none self-end text-small", band.value === "under" && "text-muted-foreground", band.value === "near" && "text-warning-text", band.value === "over" && "text-destructive-text"))
243
243
  }, toDisplayString(countText.value), 11, _hoisted_6)) : createCommentVNode("", true),
244
244
  showsCount.value && __props.maxLength !== void 0 ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(announcement.value), 1)) : createCommentVNode("", true)
245
245
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"Textarea.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../../../packages/primitives/textarea/src/Textarea.vue"],"sourcesContent":["<script lang=\"ts\">\nimport {\n COUNT_LABELS,\n warningWindow,\n type CountBand,\n type TextareaLabels,\n} from \"@ecoma-io/loom-labels\";\n\nexport type TextareaResize = \"none\" | \"vertical\";\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 * rather than a transcription of it.\n */\nexport const TEXTAREA_LABELS: TextareaLabels = COUNT_LABELS;\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from \"vue\";\nimport { cn } 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\";\n\n/**\n * Textarea — multi-line text entry, around a native `<textarea>`. The\n * accessible name is a first-class prop pair (`aria-label`/`aria-labelledby`,\n * camelized by Vue) bound onto the textarea explicitly, the same contract as\n * TextField.\n *\n * The border, focus ring and invalid state live on the wrapper so optional\n * `#leading`/`#trailing` adornments sit *inside* the field and share its focus\n * bloom, instead of every caller re-inventing an icon-inside-input layout.\n * The slot names and positions match TextField's, and the vertical alignment\n * differs — `items-start` for multi-line, where the text begins, versus\n * `items-center` for single-line — because a five-row box with a centered icon\n * mid-field reads as a mistake rather than a choice.\n *\n * Wrapping a rendered structure means the fallthrough `class` must land on the\n * wrapper (so a caller's `w-64`/`flex-1` sizes the whole field) while the rest\n * of the attrs (`id`, `name`, `data-testid`, `aria-describedby`) belong on the\n * real `<textarea>` — the split-attrs convention shared with TextField.\n *\n * That same wrapper is why the character counter is a prop here rather than a\n * `CountedField` beside this one. A counter rendered *below* the field would\n * have to move the fallthrough `class` off the box a caller sizes with it, and\n * a textarea has no free column to hang one in — the value fills the box and\n * scrolls, and the bottom-right corner belongs to the native resize grabber.\n * The counter sits below the border, where `resize` moves the box's bottom\n * edge and the counter travels with it rather than floating over the text.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself through\n * `useFieldControl()` — the row's id, the id of its hint or error line, and\n * `required` / `invalid` / `disabled` / `readonly` / `name` — so\n * `<Field label=\"Bio\" hint=\"…\"><Textarea /></Field>` needs no attributes at the\n * call site. Every prop below still wins over the row when it is set, in both\n * directions, which is why the four booleans default to `undefined` rather\n * than `false`.\n *\n * `readonly` and `disabled` are different states, and they are told apart on\n * three channels rather than one. A read-only field is a value on show: the\n * subtle fill, the text at full strength, the input hairline, still a Tab stop\n * and still submitted. A disabled one is unavailable: the deeper neutral fill,\n * the muted text, and the hairline receding to the plain border — a difference\n * in shape as well as in colour, which is what survives a reader who cannot\n * resolve the two fills apart.\n */\nconst props = withDefaults(\n defineProps<{\n /** The current value. Pair with `@update:modelValue` for `v-model`. */\n modelValue?: string;\n /** Text shown in the empty field. */\n placeholder?: string;\n /** Disables the field and blocks pointer and keyboard input. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Shows the value without allowing an edit, while staying focusable and still submitted. Unset defers to a wrapping Field. */\n readonly?: boolean | undefined;\n /** Error state: paints the destructive border/ring and sets aria-invalid. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /** Marks the field mandatory to assistive tech (`aria-required`). Unset defers to a wrapping Field. */\n required?: boolean | undefined;\n /** The field name for a native form post or `FormData`. Unset defers to a wrapping Field. */\n name?: string;\n /** Initial height in text rows. */\n rows?: number;\n /** Whether a person can drag-resize the field; `none` locks it to `rows`. The counter sits below the box either way, so a drag never moves it onto the value. */\n resize?: TextareaResize;\n /** The accessible name, when no visible element already labels the field. */\n ariaLabel?: string;\n /** The id of the visible element that labels the field (e.g. a Field wrapper's label). */\n ariaLabelledby?: string;\n /**\n * The character limit the counter reports against, and the point past\n * which it reads as over-limit.\n *\n * Advisory, exactly as `required` is: it does **not** set the native\n * `maxlength`, which silently truncates a paste and tells the reader\n * nothing. Pass `maxlength` yourself as a plain attribute where you want\n * the browser to enforce it too.\n */\n maxLength?: number;\n /**\n * Forces the counter on or off. Left unset it follows `maxLength` — a\n * limit the reader is never shown is one they can only discover by\n * exceeding it — so set it `true` for a running total with no limit, and\n * `false` to hold a limit the field reports nowhere.\n */\n showCount?: boolean | undefined;\n /**\n * Names for everything this control says out loud, as any subset of\n * `TextareaLabels` — the rest stay as the host's `provideLoomLabels`\n * vocabulary left them, and then as Loom's English.\n *\n * This is the per-instance correction, not the place to localise an\n * application: the case it exists for is a field counting something other\n * than characters, where \"80 words left\" is the true sentence.\n */\n labels?: LabelOverrides<TextareaLabels>;\n }>(),\n {\n // Not `false`, for any of the four — see TextField.vue, which carries the\n // full reasoning. `false` is a caller's decision that must beat the row;\n // absent is a caller saying nothing, and only `undefined` survives to mean\n // that past Vue's absent-Boolean-casts-to-false rule.\n disabled: undefined,\n readonly: undefined,\n invalid: undefined,\n required: undefined,\n rows: 3,\n resize: \"vertical\",\n // `undefined` for the same Boolean-cast reason, and here it is the whole\n // feature: `showCount ?? (maxLength !== undefined)` would be a dead branch\n // if an absent prop arrived as `false`.\n showCount: undefined,\n },\n);\n\nconst emit = defineEmits<{ \"update:modelValue\": [value: string] }>();\n\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: textareaAttrs } = useSplitAttrs();\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(\"textarea\", TEXTAREA_LABELS, () => props.labels);\n\nconst countId = useId();\n\n/**\n * What is actually in the box, which is not always `modelValue` — TextField's\n * reasoning, and the same defect waiting on this side of it.\n *\n * Used without `v-model` this component is uncontrolled, so `modelValue` stays\n * `undefined` while the reader types and the counter still has to count. And\n * binding `:value=\"currentValue\"` rather than `:value=\"modelValue\"` is\n * load-bearing rather than cosmetic: Vue's `value` patch coerces\n * `null`/`undefined` to `\"\"`, so any re-render of an uncontrolled field wipes\n * it — a defect nothing had met only because nothing in this component\n * re-rendered on a keystroke until the counter did.\n */\nconst typed = ref(props.modelValue ?? \"\");\nconst currentValue = computed(() => props.modelValue ?? typed.value);\n\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n typed.value = value;\n emit(\"update:modelValue\", value);\n}\n\n/**\n * Length in UTF-16 code units, which is what the platform's own `maxlength`\n * counts. `[...value].length` would count code points and disagree with the\n * browser on any astral character, so a caller who pairs this with a native\n * `maxlength` would watch the counter and the enforcement drift apart.\n */\nconst count = computed(() => currentValue.value.length);\nconst showsCount = computed(() => props.showCount ?? props.maxLength !== undefined);\n\nconst band = computed<CountBand>(() => {\n const max = props.maxLength;\n if (max === undefined) return \"under\";\n const remaining = max - count.value;\n if (remaining < 0) return \"over\";\n return remaining <= warningWindow(max) ? \"near\" : \"under\";\n});\n\nconst countText = computed(() => {\n const max = props.maxLength;\n if (max === undefined) return text.value.count({ count: count.value });\n const over = count.value - max;\n return over > 0\n ? text.value.countOverMax({ count: count.value, max, over })\n : text.value.countOfMax({ count: count.value, max });\n});\n\n/**\n * What the live region says, and it changes **only when the band changes** —\n * TextField's decision, taken once for the pair rather than re-litigated here.\n *\n * A region holding the counter itself would speak on every keystroke, burying\n * the field's own echo of the character just typed under a number nobody asked\n * for. What a reader needs is the two facts they cannot see coming — the limit\n * is close, and the limit is passed — so this fires once at each crossing and\n * goes quiet again on the way back down. The numbers are therefore true at the\n * moment they are spoken and not after; the visible counter carries the live\n * figure, and it is wired into `aria-describedby`, so the exact count is one\n * re-read away whenever the reader wants it rather than pushed at them.\n */\nconst announcement = ref(\"\");\nwatch(band, (now) => {\n const max = props.maxLength;\n if (max === undefined || now === \"under\") {\n announcement.value = \"\";\n return;\n }\n announcement.value =\n now === \"over\"\n ? text.value.limitExceeded({ over: count.value - max, max })\n : text.value.approachingLimit({ remaining: max - count.value, max });\n});\n\n// `id` and `aria-describedby` arrive as fallthrough attrs rather than props, so\n// they are handed in as this caller's own values: a caller who sets either\n// still beats the row, and the row's description is merged into theirs. The\n// counter joins that list rather than replacing anything — it reads after the\n// caller's own description and before the row's message, which is the same\n// most-specific-first order `useFieldControl` applies one layer out.\n//\n// `field.attrs` then spreads **after** `textareaAttrs`, and that position is\n// the contract — see TextField.vue, which carries the full reasoning. The\n// `:aria-invalid` binding that used to sit below went with it: an individual\n// attribute written after a `v-bind` wins, so keeping one would overwrite the\n// resolved value with the raw prop.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [attrs[\"aria-describedby\"] as string | undefined, showsCount.value ? countId : \"\"]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n name: props.name,\n disabled: props.disabled,\n readonly: props.readonly,\n invalid: props.invalid,\n required: props.required,\n}));\n</script>\n\n<template>\n <!-- Layout, and nothing else: no fallthrough attribute lands here and nothing\n is painted on it. `inline-flex` is what makes it shrink-wrap the field\n frame — a block wrapper would be as wide as the column, and the counter\n would right-align to that column rather than to the field's own edge. -->\n <div class=\"inline-flex max-w-full flex-col align-top\">\n <!-- The field frame: border, focus, state. Like TextField, the border lives\n here so optional #leading/#trailing adornments sit inside the field and\n share its focus bloom, instead of every caller re-inventing an\n icon-inside-input layout. -->\n <div\n :data-disabled=\"field.disabled || undefined\"\n :data-readonly=\"field.readonly || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n 'group flex items-start gap-2 rounded-md border border-input bg-background text-foreground px-3 py-2 text-sm',\n 'transition-[color,background-color,border-color,box-shadow] duration-fast ease-out',\n // Rim-lit at rest, the weave blooms on focus (Signature law): the field\n // catches light instead of casting a shadow, and focus draws it tight.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // The three resting appearances, and each is three changes rather than\n // one. Read-only and disabled used to share `bg-muted` and part only on\n // the text colour, which is a distinction carried by hue alone — the\n // one thing a reader with a colour deficiency, or a low-quality\n // display, or forced-colors, does not get. The fill separates them and\n // the border weight separates them again, so the progression\n // available → on show → unavailable is legible on three channels.\n //\n // Read-only is a value **on show**: the fill lifts off `background` to\n // `subtle` so the row reads as settled rather than editable, and the\n // text stays full strength — 13.46:1 over that fill — because being\n // read is the entire purpose of the state. The border does not move,\n // because nothing about the control's reach has: it is still a Tab stop\n // and still submitted. The native `readonly` attribute below carries\n // the same fact to assistive tech, so nothing here rests on colour.\n field.readonly && 'bg-subtle',\n // Disabled is **unavailable**: the fill drains one step further to\n // `muted`, the text follows it down to `muted-foreground` (4.68:1 over\n // it, still AA), and the rim slackens from `input` to the lighter\n // `border`.\n //\n // Drained rather than faded, and `opacity` is never the mechanism. The\n // element a dim would sit on is the box, and the box is wrapped around\n // the one thing a reader still needs: measured on the built site,\n // `opacity-50` put a disabled field's value at 3.08:1 and its\n // placeholder — already the muted colour before the fade — at 2.07:1.\n // The text colour is declared here rather than on the `<textarea>` because\n // the textarea sets no resting colour of its own and inherits this one,\n // which also carries it to the adornments in the same box.\n field.disabled && 'cursor-not-allowed border-border bg-muted text-muted-foreground',\n // The same appearance, reached from the other direction: a\n // `<fieldset disabled>` above this field disables the `<textarea>` inside\n // it natively and never touches `field.disabled`, so the rule above\n // cannot fire and the field rendered byte-identical to an editable one.\n //\n // `in-[fieldset:disabled]:` is a *read* of the attribute that did the\n // disabling, so the fill and the native inertness cannot disagree — the\n // fieldset element is still the only place the fact is written. It is\n // preferred to `has-[:disabled]:` (a wrapper reading its own contents)\n // because that proxy is wrong wherever a control holds a part that\n // disables itself.\n 'in-[fieldset:disabled]:cursor-not-allowed in-[fieldset:disabled]:bg-muted in-[fieldset:disabled]:text-muted-foreground',\n // Gated where the two rules below are ordered, and for the same reason.\n // Tailwind emits the ancestor half of `in-*` inside `:where()`, so this\n // carries no more specificity than `border-destructive` and would win\n // on emission order alone — an unavailable field that is also in error\n // would quietly lose its destructive rim.\n !field.invalid && 'in-[fieldset:disabled]:border-border',\n // Last of the three rules that name a border colour, and the order is\n // load-bearing: `cn()` resolves a border colour by whichever class it\n // saw last, so a field that is both in error and unavailable would lose\n // its destructive rim to the disabled one if this sat above it.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n attrs.class as string,\n )\n \"\n >\n <span\n v-if=\"$slots.leading\"\n class=\"inline-flex shrink-0 text-muted-foreground transition-colors duration-fast ease-out group-focus-within:text-foreground\"\n >\n <slot name=\"leading\" />\n </span>\n <textarea\n v-bind=\"{ ...textareaAttrs, ...field.attrs }\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"ariaLabelledby\"\n :value=\"currentValue\"\n :placeholder=\"placeholder\"\n :disabled=\"field.disabled\"\n :readonly=\"field.readonly\"\n :rows=\"rows\"\n :class=\"\n cn(\n 'min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed',\n resize === 'none' ? 'resize-none' : 'resize-y',\n )\n \"\n @input=\"onInput\"\n />\n <span\n v-if=\"$slots.trailing\"\n class=\"inline-flex shrink-0 items-center gap-1 text-muted-foreground transition-colors duration-fast ease-out group-focus-within:text-foreground\"\n >\n <slot name=\"trailing\" />\n </span>\n </div>\n <!-- Below the box, never inside it. A textarea's value fills its box and\n scrolls, so there is no free column to hang a readout in, and the\n bottom-right corner belongs to the native resize grabber — an inset\n counter would sit on top of the one affordance that says the field can\n be dragged. Here it travels with the bottom edge instead, so `resize`\n moves it rather than moving the box out from under it.\n\n `tabular` because the number changes under itself on every keystroke,\n and proportional digits would shuffle the whole readout sideways as\n they do. -->\n <span\n v-if=\"showsCount\"\n :id=\"countId\"\n :data-over-limit=\"band === 'over' || undefined\"\n :class=\"\n cn(\n 'tabular mt-1 select-none self-end text-small',\n band === 'under' && 'text-muted-foreground',\n // Colour, and never only colour: `countOverMax` says the limit is\n // passed in words as well, and `approachingLimit` announces the near\n // band once. The digits themselves are the third reading — 21/20 is\n // over the limit whatever it is painted.\n band === 'near' && 'text-warning',\n band === 'over' && 'text-destructive-text',\n )\n \"\n >\n {{ countText }}\n </span>\n <!-- In the DOM from the first render, empty. A live region mounted at the\n same moment as its text is a region assistive tech was not yet\n watching, so the first warning — the one that matters most — is the one\n it announces least reliably. -->\n <span v-if=\"showsCount && maxLength !== undefined\" role=\"status\" class=\"sr-only\">\n {{ announcement }}\n </span>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,IAAa,kBAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqD/C,MAAM,QAAQ;EAsEd,MAAM,OAAO;EAGb,MAAM,EAAE,OAAO,MAAM,kBAAkB,cAAc;EAKrD,MAAM,OAAO,UAAU,YAAY,uBAAuB,MAAM,MAAM;EAEtE,MAAM,UAAU,MAAM;;;;;;;;;;;;;EActB,MAAM,QAAQ,IAAI,MAAM,cAAc,EAAE;EACxC,MAAM,eAAe,eAAe,MAAM,cAAc,MAAM,KAAK;EAEnE,SAAS,QAAQ,OAAoB;GACnC,MAAM,QAAS,MAAM,OAA+B;GACpD,MAAM,QAAQ;GACd,KAAK,qBAAqB,KAAK;EACjC;;;;;;;EAQA,MAAM,QAAQ,eAAe,aAAa,MAAM,MAAM;EACtD,MAAM,aAAa,eAAe,MAAM,aAAa,MAAM,cAAc,KAAA,CAAS;EAElF,MAAM,OAAO,eAA0B;GACrC,MAAM,MAAM,MAAM;GAClB,IAAI,QAAQ,KAAA,GAAW,OAAO;GAC9B,MAAM,YAAY,MAAM,MAAM;GAC9B,IAAI,YAAY,GAAG,OAAO;GAC1B,OAAO,aAAa,cAAc,GAAG,IAAI,SAAS;EACpD,CAAC;EAED,MAAM,YAAY,eAAe;GAC/B,MAAM,MAAM,MAAM;GAClB,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAK,MAAM,MAAM,EAAE,OAAO,MAAM,MAAM,CAAC;GACrE,MAAM,OAAO,MAAM,QAAQ;GAC3B,OAAO,OAAO,IACV,KAAK,MAAM,aAAa;IAAE,OAAO,MAAM;IAAO;IAAK;GAAK,CAAC,IACzD,KAAK,MAAM,WAAW;IAAE,OAAO,MAAM;IAAO;GAAI,CAAC;EACvD,CAAC;;;;;;;;;;;;;;EAeD,MAAM,eAAe,IAAI,EAAE;EAC3B,MAAM,OAAO,QAAQ;GACnB,MAAM,MAAM,MAAM;GAClB,IAAI,QAAQ,KAAA,KAAa,QAAQ,SAAS;IACxC,aAAa,QAAQ;IACrB;GACF;GACA,aAAa,QACX,QAAQ,SACJ,KAAK,MAAM,cAAc;IAAE,MAAM,MAAM,QAAQ;IAAK;GAAI,CAAC,IACzD,KAAK,MAAM,iBAAiB;IAAE,WAAW,MAAM,MAAM;IAAO;GAAI,CAAC;EACzE,CAAC;EAcD,MAAM,QAAQ,uBAAuB;GACnC,IAAI,MAAM;GACV,aAAa,CAAC,MAAM,qBAA2C,WAAW,QAAQ,UAAU,EAAE,CAAA,CAC3F,QAAQ,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC,CAAA,CACpE,KAAK,GAAG;GACX,MAAM,MAAM;GACZ,UAAU,MAAM;GAChB,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,UAAU,MAAM;EAClB,EAAE;;GAQA,OAAA,UAAA,GAAA,mBA4IM,OA5IN,YA4IM;IAvIJ,mBAkGM,OAAA;KAjGH,iBAAe,MAAA,KAAA,CAAK,CAAC,YAAY,KAAA;KACjC,iBAAe,MAAA,KAAA,CAAK,CAAC,YAAY,KAAA;KACjC,gBAAc,MAAA,KAAA,CAAK,CAAC,WAAW,KAAA;KAC/B,OAAK,eAAW,MAAA,EAAA,CAAE,CAAA,+GAAA,sFAAA,kFAAue,CAAA,MAAA,KAAA,CAAK,CAAC,WAAO,4BAAipC,MAAA,KAAA,CAAK,CAAC,YAAQ,aAAs6B,MAAA,KAAA,CAAK,CAAC,YAAQ,mEAAA,0HAAi1C,CAAA,MAAA,KAAA,CAAK,CAAC,WAAO,wCAAsX,MAAA,KAAA,CAAK,CAAC,WAAO,uDAAqE,MAAA,KAAA,CAAK,CAAC,KAAA,CAAA;;KAkE93IA,KAAAA,OAAO,WADf,UAAA,GAAA,mBAKO,QALP,YAKO,CADL,WAAuB,KAAA,QAAA,SAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;KAEzB,mBAgBE,YAhBF,WAgBE;MAAA,GAfa,MAAA,aAAA;MAAa,GAAK,MAAA,KAAA,CAAK,CAAC;KAAK,GAAA;MACzC,cAAY,QAAA;MACZ,mBAAiB,QAAA;MACjB,OAAO,aAAA;MACP,aAAa,QAAA;MACb,UAAU,MAAA,KAAA,CAAK,CAAC;MAChB,UAAU,MAAA,KAAA,CAAK,CAAC;MAChB,MAAM,QAAA;MACN,OAAkB,MAAA,EAAA,CAAE,CAAA,4GAAsI,QAAA,WAAM,SAAA,gBAAA,UAAA;MAMzJ;;KAGFA,KAAAA,OAAO,YADf,UAAA,GAAA,mBAKO,QALP,YAKO,CADL,WAAwB,KAAA,QAAA,UAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;IAcpB,WAAA,SADR,UAAA,GAAA,mBAkBO,QAAA;;KAhBJ,IAAI,MAAA,OAAA;KACJ,mBAAiB,KAAA,UAAI,UAAe,KAAA;KACpC,OAAK,eAAW,MAAA,EAAA,CAAE,CAAA,gDAAsE,KAAA,UAAI,WAAA,yBAAmV,KAAA,UAAI,UAAA,gBAAyC,KAAA,UAAI,UAAA,uBAAA,CAAA;IAa9d,GAAA,gBAAA,UAAA,KAAS,GAAA,IAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;IAMF,WAAA,SAAc,QAAA,cAAc,KAAA,KAAxC,UAAA,GAAA,mBAEO,QAFP,YAEO,gBADF,aAAA,KAAY,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA"}
1
+ {"version":3,"file":"Textarea.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../../../packages/primitives/textarea/src/Textarea.vue"],"sourcesContent":["<script lang=\"ts\">\nimport {\n COUNT_LABELS,\n warningWindow,\n type CountBand,\n type TextareaLabels,\n} from \"@ecoma-io/loom-labels\";\n\nexport type TextareaResize = \"none\" | \"vertical\";\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 * rather than a transcription of it.\n */\nexport const TEXTAREA_LABELS: TextareaLabels = COUNT_LABELS;\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, useId, watch } from \"vue\";\nimport { cn } 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\";\n\n/**\n * Textarea — multi-line text entry, around a native `<textarea>`. The\n * accessible name is a first-class prop pair (`aria-label`/`aria-labelledby`,\n * camelized by Vue) bound onto the textarea explicitly, the same contract as\n * TextField.\n *\n * The border, focus ring and invalid state live on the wrapper so optional\n * `#leading`/`#trailing` adornments sit *inside* the field and share its focus\n * bloom, instead of every caller re-inventing an icon-inside-input layout.\n * The slot names and positions match TextField's, and the vertical alignment\n * differs — `items-start` for multi-line, where the text begins, versus\n * `items-center` for single-line — because a five-row box with a centered icon\n * mid-field reads as a mistake rather than a choice.\n *\n * Wrapping a rendered structure means the fallthrough `class` must land on the\n * wrapper (so a caller's `w-64`/`flex-1` sizes the whole field) while the rest\n * of the attrs (`id`, `name`, `data-testid`, `aria-describedby`) belong on the\n * real `<textarea>` — the split-attrs convention shared with TextField.\n *\n * That same wrapper is why the character counter is a prop here rather than a\n * `CountedField` beside this one. A counter rendered *below* the field would\n * have to move the fallthrough `class` off the box a caller sizes with it, and\n * a textarea has no free column to hang one in — the value fills the box and\n * scrolls, and the bottom-right corner belongs to the native resize grabber.\n * The counter sits below the border, where `resize` moves the box's bottom\n * edge and the counter travels with it rather than floating over the text.\n *\n * Inside a [Field](../Field/Field.vue) it wires itself through\n * `useFieldControl()` — the row's id, the id of its hint or error line, and\n * `required` / `invalid` / `disabled` / `readonly` / `name` — so\n * `<Field label=\"Bio\" hint=\"…\"><Textarea /></Field>` needs no attributes at the\n * call site. Every prop below still wins over the row when it is set, in both\n * directions, which is why the four booleans default to `undefined` rather\n * than `false`.\n *\n * `readonly` and `disabled` are different states, and they are told apart on\n * three channels rather than one. A read-only field is a value on show: the\n * subtle fill, the text at full strength, the input hairline, still a Tab stop\n * and still submitted. A disabled one is unavailable: the deeper neutral fill,\n * the muted text, and the hairline receding to the plain border — a difference\n * in shape as well as in colour, which is what survives a reader who cannot\n * resolve the two fills apart.\n */\nconst props = withDefaults(\n defineProps<{\n /** The current value. Pair with `@update:modelValue` for `v-model`. */\n modelValue?: string;\n /** Text shown in the empty field. */\n placeholder?: string;\n /** Disables the field and blocks pointer and keyboard input. Unset defers to a wrapping Field. */\n disabled?: boolean | undefined;\n /** Shows the value without allowing an edit, while staying focusable and still submitted. Unset defers to a wrapping Field. */\n readonly?: boolean | undefined;\n /** Error state: paints the destructive border/ring and sets aria-invalid. Unset defers to a wrapping Field's `error`. */\n invalid?: boolean | undefined;\n /** Marks the field mandatory to assistive tech (`aria-required`). Unset defers to a wrapping Field. */\n required?: boolean | undefined;\n /** The field name for a native form post or `FormData`. Unset defers to a wrapping Field. */\n name?: string;\n /** Initial height in text rows. */\n rows?: number;\n /** Whether a person can drag-resize the field; `none` locks it to `rows`. The counter sits below the box either way, so a drag never moves it onto the value. */\n resize?: TextareaResize;\n /** The accessible name, when no visible element already labels the field. */\n ariaLabel?: string;\n /** The id of the visible element that labels the field (e.g. a Field wrapper's label). */\n ariaLabelledby?: string;\n /**\n * The character limit the counter reports against, and the point past\n * which it reads as over-limit.\n *\n * Advisory, exactly as `required` is: it does **not** set the native\n * `maxlength`, which silently truncates a paste and tells the reader\n * nothing. Pass `maxlength` yourself as a plain attribute where you want\n * the browser to enforce it too.\n */\n maxLength?: number;\n /**\n * Forces the counter on or off. Left unset it follows `maxLength` — a\n * limit the reader is never shown is one they can only discover by\n * exceeding it — so set it `true` for a running total with no limit, and\n * `false` to hold a limit the field reports nowhere.\n */\n showCount?: boolean | undefined;\n /**\n * Names for everything this control says out loud, as any subset of\n * `TextareaLabels` — the rest stay as the host's `provideLoomLabels`\n * vocabulary left them, and then as Loom's English.\n *\n * This is the per-instance correction, not the place to localise an\n * application: the case it exists for is a field counting something other\n * than characters, where \"80 words left\" is the true sentence.\n */\n labels?: LabelOverrides<TextareaLabels>;\n }>(),\n {\n // Not `false`, for any of the four — see TextField.vue, which carries the\n // full reasoning. `false` is a caller's decision that must beat the row;\n // absent is a caller saying nothing, and only `undefined` survives to mean\n // that past Vue's absent-Boolean-casts-to-false rule.\n disabled: undefined,\n readonly: undefined,\n invalid: undefined,\n required: undefined,\n rows: 3,\n resize: \"vertical\",\n // `undefined` for the same Boolean-cast reason, and here it is the whole\n // feature: `showCount ?? (maxLength !== undefined)` would be a dead branch\n // if an absent prop arrived as `false`.\n showCount: undefined,\n },\n);\n\nconst emit = defineEmits<{ \"update:modelValue\": [value: string] }>();\n\ndefineOptions({ inheritAttrs: false });\nconst { attrs, rest: textareaAttrs } = useSplitAttrs();\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(\"textarea\", TEXTAREA_LABELS, () => props.labels);\n\nconst countId = useId();\n\n/**\n * What is actually in the box, which is not always `modelValue` — TextField's\n * reasoning, and the same defect waiting on this side of it.\n *\n * Used without `v-model` this component is uncontrolled, so `modelValue` stays\n * `undefined` while the reader types and the counter still has to count. And\n * binding `:value=\"currentValue\"` rather than `:value=\"modelValue\"` is\n * load-bearing rather than cosmetic: Vue's `value` patch coerces\n * `null`/`undefined` to `\"\"`, so any re-render of an uncontrolled field wipes\n * it — a defect nothing had met only because nothing in this component\n * re-rendered on a keystroke until the counter did.\n */\nconst typed = ref(props.modelValue ?? \"\");\nconst currentValue = computed(() => props.modelValue ?? typed.value);\n\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLTextAreaElement).value;\n typed.value = value;\n emit(\"update:modelValue\", value);\n}\n\n/**\n * Length in UTF-16 code units, which is what the platform's own `maxlength`\n * counts. `[...value].length` would count code points and disagree with the\n * browser on any astral character, so a caller who pairs this with a native\n * `maxlength` would watch the counter and the enforcement drift apart.\n */\nconst count = computed(() => currentValue.value.length);\nconst showsCount = computed(() => props.showCount ?? props.maxLength !== undefined);\n\nconst band = computed<CountBand>(() => {\n const max = props.maxLength;\n if (max === undefined) return \"under\";\n const remaining = max - count.value;\n if (remaining < 0) return \"over\";\n return remaining <= warningWindow(max) ? \"near\" : \"under\";\n});\n\nconst countText = computed(() => {\n const max = props.maxLength;\n if (max === undefined) return text.value.count({ count: count.value });\n const over = count.value - max;\n return over > 0\n ? text.value.countOverMax({ count: count.value, max, over })\n : text.value.countOfMax({ count: count.value, max });\n});\n\n/**\n * What the live region says, and it changes **only when the band changes** —\n * TextField's decision, taken once for the pair rather than re-litigated here.\n *\n * A region holding the counter itself would speak on every keystroke, burying\n * the field's own echo of the character just typed under a number nobody asked\n * for. What a reader needs is the two facts they cannot see coming — the limit\n * is close, and the limit is passed — so this fires once at each crossing and\n * goes quiet again on the way back down. The numbers are therefore true at the\n * moment they are spoken and not after; the visible counter carries the live\n * figure, and it is wired into `aria-describedby`, so the exact count is one\n * re-read away whenever the reader wants it rather than pushed at them.\n */\nconst announcement = ref(\"\");\nwatch(band, (now) => {\n const max = props.maxLength;\n if (max === undefined || now === \"under\") {\n announcement.value = \"\";\n return;\n }\n announcement.value =\n now === \"over\"\n ? text.value.limitExceeded({ over: count.value - max, max })\n : text.value.approachingLimit({ remaining: max - count.value, max });\n});\n\n// `id` and `aria-describedby` arrive as fallthrough attrs rather than props, so\n// they are handed in as this caller's own values: a caller who sets either\n// still beats the row, and the row's description is merged into theirs. The\n// counter joins that list rather than replacing anything — it reads after the\n// caller's own description and before the row's message, which is the same\n// most-specific-first order `useFieldControl` applies one layer out.\n//\n// `field.attrs` then spreads **after** `textareaAttrs`, and that position is\n// the contract — see TextField.vue, which carries the full reasoning. The\n// `:aria-invalid` binding that used to sit below went with it: an individual\n// attribute written after a `v-bind` wins, so keeping one would overwrite the\n// resolved value with the raw prop.\nconst field = useFieldControl(() => ({\n id: attrs.id as string | undefined,\n describedBy: [attrs[\"aria-describedby\"] as string | undefined, showsCount.value ? countId : \"\"]\n .filter((id): id is string => typeof id === \"string\" && id.length > 0)\n .join(\" \"),\n name: props.name,\n disabled: props.disabled,\n readonly: props.readonly,\n invalid: props.invalid,\n required: props.required,\n}));\n</script>\n\n<template>\n <!-- Layout, and nothing else: no fallthrough attribute lands here and nothing\n is painted on it. `inline-flex` is what makes it shrink-wrap the field\n frame — a block wrapper would be as wide as the column, and the counter\n would right-align to that column rather than to the field's own edge. -->\n <div class=\"inline-flex max-w-full flex-col align-top\">\n <!-- The field frame: border, focus, state. Like TextField, the border lives\n here so optional #leading/#trailing adornments sit inside the field and\n share its focus bloom, instead of every caller re-inventing an\n icon-inside-input layout. -->\n <div\n :data-disabled=\"field.disabled || undefined\"\n :data-readonly=\"field.readonly || undefined\"\n :data-invalid=\"field.invalid || undefined\"\n :class=\"\n cn(\n 'group flex items-start gap-2 rounded-md border border-input bg-background text-foreground px-3 py-2 text-sm',\n 'transition-[color,background-color,border-color,box-shadow] duration-fast ease-out',\n // Rim-lit at rest, the weave blooms on focus (Signature law): the field\n // catches light instead of casting a shadow, and focus draws it tight.\n 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring',\n !field.invalid && 'focus-within:shadow-halo',\n // The three resting appearances, and each is three changes rather than\n // one. Read-only and disabled used to share `bg-muted` and part only on\n // the text colour, which is a distinction carried by hue alone — the\n // one thing a reader with a colour deficiency, or a low-quality\n // display, or forced-colors, does not get. The fill separates them and\n // the border weight separates them again, so the progression\n // available → on show → unavailable is legible on three channels.\n //\n // Read-only is a value **on show**: the fill lifts off `background` to\n // `subtle` so the row reads as settled rather than editable, and the\n // text stays full strength — 13.46:1 over that fill — because being\n // read is the entire purpose of the state. The border does not move,\n // because nothing about the control's reach has: it is still a Tab stop\n // and still submitted. The native `readonly` attribute below carries\n // the same fact to assistive tech, so nothing here rests on colour.\n field.readonly && 'bg-subtle',\n // Disabled is **unavailable**: the fill drains one step further to\n // `muted`, the text follows it down to `muted-foreground` (4.68:1 over\n // it, still AA), and the rim slackens from `input` to the lighter\n // `border`.\n //\n // Drained rather than faded, and `opacity` is never the mechanism. The\n // element a dim would sit on is the box, and the box is wrapped around\n // the one thing a reader still needs: measured on the built site,\n // `opacity-50` put a disabled field's value at 3.08:1 and its\n // placeholder — already the muted colour before the fade — at 2.07:1.\n // The text colour is declared here rather than on the `<textarea>` because\n // the textarea sets no resting colour of its own and inherits this one,\n // which also carries it to the adornments in the same box.\n field.disabled && 'cursor-not-allowed border-border bg-muted text-muted-foreground',\n // The same appearance, reached from the other direction: a\n // `<fieldset disabled>` above this field disables the `<textarea>` inside\n // it natively and never touches `field.disabled`, so the rule above\n // cannot fire and the field rendered byte-identical to an editable one.\n //\n // `in-[fieldset:disabled]:` is a *read* of the attribute that did the\n // disabling, so the fill and the native inertness cannot disagree — the\n // fieldset element is still the only place the fact is written. It is\n // preferred to `has-[:disabled]:` (a wrapper reading its own contents)\n // because that proxy is wrong wherever a control holds a part that\n // disables itself.\n 'in-[fieldset:disabled]:cursor-not-allowed in-[fieldset:disabled]:bg-muted in-[fieldset:disabled]:text-muted-foreground',\n // Gated where the two rules below are ordered, and for the same reason.\n // Tailwind emits the ancestor half of `in-*` inside `:where()`, so this\n // carries no more specificity than `border-destructive` and would win\n // on emission order alone — an unavailable field that is also in error\n // would quietly lose its destructive rim.\n !field.invalid && 'in-[fieldset:disabled]:border-border',\n // Last of the three rules that name a border colour, and the order is\n // load-bearing: `cn()` resolves a border colour by whichever class it\n // saw last, so a field that is both in error and unavailable would lose\n // its destructive rim to the disabled one if this sat above it.\n field.invalid && 'border-destructive focus-within:outline-destructive',\n attrs.class as string,\n )\n \"\n >\n <span\n v-if=\"$slots.leading\"\n class=\"inline-flex shrink-0 text-muted-foreground transition-colors duration-fast ease-out group-focus-within:text-foreground\"\n >\n <slot name=\"leading\" />\n </span>\n <textarea\n v-bind=\"{ ...textareaAttrs, ...field.attrs }\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"ariaLabelledby\"\n :value=\"currentValue\"\n :placeholder=\"placeholder\"\n :disabled=\"field.disabled\"\n :readonly=\"field.readonly\"\n :rows=\"rows\"\n :class=\"\n cn(\n 'min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed',\n resize === 'none' ? 'resize-none' : 'resize-y',\n )\n \"\n @input=\"onInput\"\n />\n <span\n v-if=\"$slots.trailing\"\n class=\"inline-flex shrink-0 items-center gap-1 text-muted-foreground transition-colors duration-fast ease-out group-focus-within:text-foreground\"\n >\n <slot name=\"trailing\" />\n </span>\n </div>\n <!-- Below the box, never inside it. A textarea's value fills its box and\n scrolls, so there is no free column to hang a readout in, and the\n bottom-right corner belongs to the native resize grabber — an inset\n counter would sit on top of the one affordance that says the field can\n be dragged. Here it travels with the bottom edge instead, so `resize`\n moves it rather than moving the box out from under it.\n\n `tabular` because the number changes under itself on every keystroke,\n and proportional digits would shuffle the whole readout sideways as\n they do. -->\n <span\n v-if=\"showsCount\"\n :id=\"countId\"\n :data-over-limit=\"band === 'over' || undefined\"\n :class=\"\n cn(\n 'tabular mt-1 select-none self-end text-small',\n band === 'under' && 'text-muted-foreground',\n // Colour, and never only colour: `countOverMax` says the limit is\n // passed in words as well, and `approachingLimit` announces the near\n // band once. The digits themselves are the third reading — 21/20 is\n // over the limit whatever it is painted.\n band === 'near' && 'text-warning-text',\n band === 'over' && 'text-destructive-text',\n )\n \"\n >\n {{ countText }}\n </span>\n <!-- In the DOM from the first render, empty. A live region mounted at the\n same moment as its text is a region assistive tech was not yet\n watching, so the first warning — the one that matters most — is the one\n it announces least reliably. -->\n <span v-if=\"showsCount && maxLength !== undefined\" role=\"status\" class=\"sr-only\">\n {{ announcement }}\n </span>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,IAAa,kBAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqD/C,MAAM,QAAQ;EAsEd,MAAM,OAAO;EAGb,MAAM,EAAE,OAAO,MAAM,kBAAkB,cAAc;EAKrD,MAAM,OAAO,UAAU,YAAY,uBAAuB,MAAM,MAAM;EAEtE,MAAM,UAAU,MAAM;;;;;;;;;;;;;EActB,MAAM,QAAQ,IAAI,MAAM,cAAc,EAAE;EACxC,MAAM,eAAe,eAAe,MAAM,cAAc,MAAM,KAAK;EAEnE,SAAS,QAAQ,OAAoB;GACnC,MAAM,QAAS,MAAM,OAA+B;GACpD,MAAM,QAAQ;GACd,KAAK,qBAAqB,KAAK;EACjC;;;;;;;EAQA,MAAM,QAAQ,eAAe,aAAa,MAAM,MAAM;EACtD,MAAM,aAAa,eAAe,MAAM,aAAa,MAAM,cAAc,KAAA,CAAS;EAElF,MAAM,OAAO,eAA0B;GACrC,MAAM,MAAM,MAAM;GAClB,IAAI,QAAQ,KAAA,GAAW,OAAO;GAC9B,MAAM,YAAY,MAAM,MAAM;GAC9B,IAAI,YAAY,GAAG,OAAO;GAC1B,OAAO,aAAa,cAAc,GAAG,IAAI,SAAS;EACpD,CAAC;EAED,MAAM,YAAY,eAAe;GAC/B,MAAM,MAAM,MAAM;GAClB,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAK,MAAM,MAAM,EAAE,OAAO,MAAM,MAAM,CAAC;GACrE,MAAM,OAAO,MAAM,QAAQ;GAC3B,OAAO,OAAO,IACV,KAAK,MAAM,aAAa;IAAE,OAAO,MAAM;IAAO;IAAK;GAAK,CAAC,IACzD,KAAK,MAAM,WAAW;IAAE,OAAO,MAAM;IAAO;GAAI,CAAC;EACvD,CAAC;;;;;;;;;;;;;;EAeD,MAAM,eAAe,IAAI,EAAE;EAC3B,MAAM,OAAO,QAAQ;GACnB,MAAM,MAAM,MAAM;GAClB,IAAI,QAAQ,KAAA,KAAa,QAAQ,SAAS;IACxC,aAAa,QAAQ;IACrB;GACF;GACA,aAAa,QACX,QAAQ,SACJ,KAAK,MAAM,cAAc;IAAE,MAAM,MAAM,QAAQ;IAAK;GAAI,CAAC,IACzD,KAAK,MAAM,iBAAiB;IAAE,WAAW,MAAM,MAAM;IAAO;GAAI,CAAC;EACzE,CAAC;EAcD,MAAM,QAAQ,uBAAuB;GACnC,IAAI,MAAM;GACV,aAAa,CAAC,MAAM,qBAA2C,WAAW,QAAQ,UAAU,EAAE,CAAA,CAC3F,QAAQ,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC,CAAA,CACpE,KAAK,GAAG;GACX,MAAM,MAAM;GACZ,UAAU,MAAM;GAChB,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,UAAU,MAAM;EAClB,EAAE;;GAQA,OAAA,UAAA,GAAA,mBA4IM,OA5IN,YA4IM;IAvIJ,mBAkGM,OAAA;KAjGH,iBAAe,MAAA,KAAA,CAAK,CAAC,YAAY,KAAA;KACjC,iBAAe,MAAA,KAAA,CAAK,CAAC,YAAY,KAAA;KACjC,gBAAc,MAAA,KAAA,CAAK,CAAC,WAAW,KAAA;KAC/B,OAAK,eAAW,MAAA,EAAA,CAAE,CAAA,+GAAA,sFAAA,kFAAue,CAAA,MAAA,KAAA,CAAK,CAAC,WAAO,4BAAipC,MAAA,KAAA,CAAK,CAAC,YAAQ,aAAs6B,MAAA,KAAA,CAAK,CAAC,YAAQ,mEAAA,0HAAi1C,CAAA,MAAA,KAAA,CAAK,CAAC,WAAO,wCAAsX,MAAA,KAAA,CAAK,CAAC,WAAO,uDAAqE,MAAA,KAAA,CAAK,CAAC,KAAA,CAAA;;KAkE93IA,KAAAA,OAAO,WADf,UAAA,GAAA,mBAKO,QALP,YAKO,CADL,WAAuB,KAAA,QAAA,SAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;KAEzB,mBAgBE,YAhBF,WAgBE;MAAA,GAfa,MAAA,aAAA;MAAa,GAAK,MAAA,KAAA,CAAK,CAAC;KAAK,GAAA;MACzC,cAAY,QAAA;MACZ,mBAAiB,QAAA;MACjB,OAAO,aAAA;MACP,aAAa,QAAA;MACb,UAAU,MAAA,KAAA,CAAK,CAAC;MAChB,UAAU,MAAA,KAAA,CAAK,CAAC;MAChB,MAAM,QAAA;MACN,OAAkB,MAAA,EAAA,CAAE,CAAA,4GAAsI,QAAA,WAAM,SAAA,gBAAA,UAAA;MAMzJ;;KAGFA,KAAAA,OAAO,YADf,UAAA,GAAA,mBAKO,QALP,YAKO,CADL,WAAwB,KAAA,QAAA,UAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;IAcpB,WAAA,SADR,UAAA,GAAA,mBAkBO,QAAA;;KAhBJ,IAAI,MAAA,OAAA;KACJ,mBAAiB,KAAA,UAAI,UAAe,KAAA;KACpC,OAAK,eAAW,MAAA,EAAA,CAAE,CAAA,gDAAsE,KAAA,UAAI,WAAA,yBAAmV,KAAA,UAAI,UAAA,qBAA8C,KAAA,UAAI,UAAA,uBAAA,CAAA;IAane,GAAA,gBAAA,UAAA,KAAS,GAAA,IAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;IAMF,WAAA,SAAc,QAAA,cAAc,KAAA,KAAxC,UAAA,GAAA,mBAEO,QAFP,YAEO,gBADF,aAAA,KAAY,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA"}
@@ -1,5 +1,5 @@
1
1
  export { default } from "./Textarea.vue";
2
2
  export { TEXTAREA_LABELS } from "./Textarea.vue";
3
3
  export type { TextareaResize } from "./Textarea.vue";
4
- export type { TextareaLabels } from "@ecoma-io/loom-labels";
4
+ export type { TextareaLabels } from "../../../labels/src/index";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,3 @@
1
- import "./Textarea.vue_vue_type_script_setup_true_lang.js";
1
+ import { TEXTAREA_LABELS } from "./Textarea.vue_vue_type_script_setup_true_lang.js";
2
2
  import Textarea_default from "./Textarea.js";
3
- export { Textarea_default as default };
3
+ export { TEXTAREA_LABELS, Textarea_default as default };
@@ -1,5 +1,5 @@
1
- import { type LabelOverrides } from "@ecoma-io/loom-labels";
2
- import type { TimeSegmentLabels } from "@ecoma-io/loom-labels";
1
+ import { type LabelOverrides } from "../../../labels/src/index";
2
+ import type { TimeSegmentLabels } from "../../../labels/src/index";
3
3
  /**
4
4
  * Everything this control publishes to assistive technology: the four names
5
5
  * Reka UI would otherwise give the segments in English of its own accord.
@@ -0,0 +1,7 @@
1
+ import Timeline_vue_vue_type_script_setup_true_lang_default, { TIMELINE_LABELS } from "./Timeline.vue_vue_type_script_setup_true_lang.js";
2
+ //#region packages/primitives/timeline/src/Timeline.vue
3
+ var Timeline_default = Timeline_vue_vue_type_script_setup_true_lang_default;
4
+ //#endregion
5
+ export { TIMELINE_LABELS, Timeline_default as default };
6
+
7
+ //# sourceMappingURL=Timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.js","names":[],"sources":["../../../../../packages/primitives/timeline/src/Timeline.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { TimelineLabels } from \"@ecoma-io/loom-labels\";\n\n/**\n * Timeline — a chronological spine: markers down a line, each carrying a\n * moment (title, description, timestamp) and where it sits relative to now.\n *\n * **Vertical is the only axis.** A horizontal timeline is a Stepper wearing\n * different clothes: ordered stages a reader moves through, which Stepper\n * already models with a state machine, reachability rules and keyboard\n * traversal. A timeline's moments are *records* the reader scans, and the\n * scan runs top-to-bottom at every viewport this library targets. If a\n * design reaches for horizontal, it is asking for Stepper.\n *\n * Statuses are explicit — the host owns the truth about what has happened —\n * and every item states its status in words beside the marker, because a\n * dot's colour alone is not a state.\n */\nexport const TIMELINE_LABELS: TimelineLabels = {\n region: \"Timeline\",\n complete: \"Completed\",\n current: \"Current\",\n upcoming: \"Upcoming\",\n};\n\n/** Where a moment sits relative to now. The host owns the truth. */\nexport type TimelineStatus = \"complete\" | \"current\" | \"upcoming\";\n</script>\n\n<script setup lang=\"ts\">\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\n\ndefineProps<{\n /** Names the timeline when surrounding prose does not already say it. */\n labels?: LabelOverrides<TimelineLabels>;\n}>();\n\n// `text`, not `labels`: one of three sources, resolved key by key.\nconst text = useLabels(\"timeline\", TIMELINE_LABELS);\n</script>\n\n<template>\n <!-- An ordered list, because a timeline IS ordered. Items draw their own\n connector stub; the last one's is retired here so the line stops where\n the story does. -->\n <!-- A div carrying explicit list roles rather than a styled ol: the docs\n site's own unlayered `ol` rules beat every utility here (the failure\n AvatarGroup recorded first), and explicit roles also survive engines\n that drop list semantics once `list-style: none` lands. -->\n <div\n role=\"list\"\n :aria-label=\"text.region\"\n class=\"m-0 p-0 [&>*:last-child_.loom-timeline-line]:hidden\"\n >\n <!-- @slot The entries: one `TimelineItem` per moment, in order. -->\n <slot />\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,37 @@
1
+ import { type LabelOverrides } from "../../../labels/src/index";
2
+ import type { TimelineLabels } from "../../../labels/src/index";
3
+ /**
4
+ * Timeline — a chronological spine: markers down a line, each carrying a
5
+ * moment (title, description, timestamp) and where it sits relative to now.
6
+ *
7
+ * **Vertical is the only axis.** A horizontal timeline is a Stepper wearing
8
+ * different clothes: ordered stages a reader moves through, which Stepper
9
+ * already models with a state machine, reachability rules and keyboard
10
+ * traversal. A timeline's moments are *records* the reader scans, and the
11
+ * scan runs top-to-bottom at every viewport this library targets. If a
12
+ * design reaches for horizontal, it is asking for Stepper.
13
+ *
14
+ * Statuses are explicit — the host owns the truth about what has happened —
15
+ * and every item states its status in words beside the marker, because a
16
+ * dot's colour alone is not a state.
17
+ */
18
+ export declare const TIMELINE_LABELS: TimelineLabels;
19
+ /** Where a moment sits relative to now. The host owns the truth. */
20
+ export type TimelineStatus = "complete" | "current" | "upcoming";
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
23
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
24
+ /** Names the timeline when surrounding prose does not already say it. */
25
+ labels?: LabelOverrides<TimelineLabels>;
26
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{
27
+ /** Names the timeline when surrounding prose does not already say it. */
28
+ labels?: LabelOverrides<TimelineLabels>;
29
+ }> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
30
+ default?: (props: {}) => any;
31
+ }>;
32
+ type __VLS_WithSlots<T, S> = T & {
33
+ new (): {
34
+ $slots: S;
35
+ };
36
+ };
37
+ //# sourceMappingURL=Timeline.vue.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { useLabels } from "../../../labels/src/labels.js";
2
+ import "../../../labels/src/index.js";
3
+ import { createElementBlock, defineComponent, openBlock, renderSlot, unref } from "vue";
4
+ //#region packages/primitives/timeline/src/Timeline.vue?vue&type=script&setup=true&lang.ts
5
+ var _hoisted_1 = ["aria-label"];
6
+ /**
7
+ * Timeline — a chronological spine: markers down a line, each carrying a
8
+ * moment (title, description, timestamp) and where it sits relative to now.
9
+ *
10
+ * **Vertical is the only axis.** A horizontal timeline is a Stepper wearing
11
+ * different clothes: ordered stages a reader moves through, which Stepper
12
+ * already models with a state machine, reachability rules and keyboard
13
+ * traversal. A timeline's moments are *records* the reader scans, and the
14
+ * scan runs top-to-bottom at every viewport this library targets. If a
15
+ * design reaches for horizontal, it is asking for Stepper.
16
+ *
17
+ * Statuses are explicit — the host owns the truth about what has happened —
18
+ * and every item states its status in words beside the marker, because a
19
+ * dot's colour alone is not a state.
20
+ */
21
+ var TIMELINE_LABELS = {
22
+ region: "Timeline",
23
+ complete: "Completed",
24
+ current: "Current",
25
+ upcoming: "Upcoming"
26
+ };
27
+ var Timeline_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
28
+ __name: "Timeline",
29
+ props: { labels: {} },
30
+ setup(__props) {
31
+ const text = useLabels("timeline", TIMELINE_LABELS);
32
+ return (_ctx, _cache) => {
33
+ return openBlock(), createElementBlock("div", {
34
+ role: "list",
35
+ "aria-label": unref(text).region,
36
+ class: "m-0 p-0 [&>*:last-child_.loom-timeline-line]:hidden"
37
+ }, [renderSlot(_ctx.$slots, "default")], 8, _hoisted_1);
38
+ };
39
+ }
40
+ });
41
+ //#endregion
42
+ export { TIMELINE_LABELS, Timeline_vue_vue_type_script_setup_true_lang_default as default };
43
+
44
+ //# sourceMappingURL=Timeline.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Timeline.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../../../packages/primitives/timeline/src/Timeline.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { TimelineLabels } from \"@ecoma-io/loom-labels\";\n\n/**\n * Timeline — a chronological spine: markers down a line, each carrying a\n * moment (title, description, timestamp) and where it sits relative to now.\n *\n * **Vertical is the only axis.** A horizontal timeline is a Stepper wearing\n * different clothes: ordered stages a reader moves through, which Stepper\n * already models with a state machine, reachability rules and keyboard\n * traversal. A timeline's moments are *records* the reader scans, and the\n * scan runs top-to-bottom at every viewport this library targets. If a\n * design reaches for horizontal, it is asking for Stepper.\n *\n * Statuses are explicit — the host owns the truth about what has happened —\n * and every item states its status in words beside the marker, because a\n * dot's colour alone is not a state.\n */\nexport const TIMELINE_LABELS: TimelineLabels = {\n region: \"Timeline\",\n complete: \"Completed\",\n current: \"Current\",\n upcoming: \"Upcoming\",\n};\n\n/** Where a moment sits relative to now. The host owns the truth. */\nexport type TimelineStatus = \"complete\" | \"current\" | \"upcoming\";\n</script>\n\n<script setup lang=\"ts\">\nimport { useLabels, type LabelOverrides } from \"@ecoma-io/loom-labels\";\n\ndefineProps<{\n /** Names the timeline when surrounding prose does not already say it. */\n labels?: LabelOverrides<TimelineLabels>;\n}>();\n\n// `text`, not `labels`: one of three sources, resolved key by key.\nconst text = useLabels(\"timeline\", TIMELINE_LABELS);\n</script>\n\n<template>\n <!-- An ordered list, because a timeline IS ordered. Items draw their own\n connector stub; the last one's is retired here so the line stops where\n the story does. -->\n <!-- A div carrying explicit list roles rather than a styled ol: the docs\n site's own unlayered `ol` rules beat every utility here (the failure\n AvatarGroup recorded first), and explicit roles also survive engines\n that drop list semantics once `list-style: none` lands. -->\n <div\n role=\"list\"\n :aria-label=\"text.region\"\n class=\"m-0 p-0 [&>*:last-child_.loom-timeline-line]:hidden\"\n >\n <!-- @slot The entries: one `TimelineItem` per moment, in order. -->\n <slot />\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkBA,IAAa,kBAAkC;CAC7C,QAAQ;CACR,UAAU;CACV,SAAS;CACT,UAAU;AACZ;;;;;EAeA,MAAM,OAAO,UAAU,YAAY,eAAe;;GAWhD,OAAA,UAAA,GAAA,mBAOM,OAAA;IANJ,MAAK;IACJ,cAAY,MAAA,IAAA,CAAI,CAAC;IAClB,OAAM;GAGN,GAAA,CAAA,WAAQ,KAAA,QAAA,SAAA,CAAA,GAAA,GAAA,UAAA"}
@@ -0,0 +1,7 @@
1
+ import TimelineItem_vue_vue_type_script_setup_true_lang_default, { timelineMarkerVariants } from "./TimelineItem.vue_vue_type_script_setup_true_lang.js";
2
+ //#region packages/primitives/timeline/src/TimelineItem.vue
3
+ var TimelineItem_default = TimelineItem_vue_vue_type_script_setup_true_lang_default;
4
+ //#endregion
5
+ export { TimelineItem_default as default, timelineMarkerVariants };
6
+
7
+ //# sourceMappingURL=TimelineItem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimelineItem.js","names":[],"sources":["../../../../../packages/primitives/timeline/src/TimelineItem.vue"],"sourcesContent":["<script lang=\"ts\">\nimport { cva } from \"class-variance-authority\";\n\n/**\n * The marker, per status. Complete fills with success and takes the check;\n * current fills with primary and wears a halo — emphasis without motion, so\n * reduced-motion readers lose nothing; upcoming is a hollow dot, present but\n * not yet lived.\n */\nexport const timelineMarkerVariants = cva(\n \"relative z-raised flex h-6 w-6 shrink-0 items-center justify-center rounded-full border\",\n {\n variants: {\n status: {\n complete: \"border-success bg-success text-success-foreground\",\n current: \"border-primary bg-primary-muted text-primary-text ring-4 ring-primary-muted\",\n upcoming: \"border-border bg-background text-muted-foreground\",\n },\n },\n defaultVariants: { status: \"upcoming\" as TimelineStatus },\n },\n);\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { Check } from \"@lucide/vue\";\nimport { useLabels, type LabelOverrides, type TimelineLabels } from \"@ecoma-io/loom-labels\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { TIMELINE_LABELS, type TimelineStatus } from \"./Timeline.vue\";\n\nconst props = withDefaults(\n defineProps<{\n /** The moment's heading. */\n title: string;\n /** A line under the title. */\n description?: string;\n /** When it happened (or will). Rendered inside a real `<time>`. */\n timestamp?: string;\n /** Machine-readable timestamp for `<time datetime>`. */\n datetime?: string;\n /** Where the moment sits relative to now. The host owns the truth. */\n status?: TimelineStatus;\n /** Names what the item says on its own account, as any subset of `TimelineLabels`. */\n labels?: LabelOverrides<TimelineLabels>;\n }>(),\n { status: \"upcoming\" },\n);\n\n// `text`, not `labels`: one of three sources, resolved key by key.\nconst text = useLabels(\"timeline\", TIMELINE_LABELS, () => props.labels);\n\nconst STATUS_WORD = {\n complete: \"complete\",\n current: \"current\",\n upcoming: \"upcoming\",\n} as const;\nconst statusWord = computed(() => {\n const bag = text.value;\n return bag[STATUS_WORD[props.status]];\n});\n</script>\n\n<template>\n <!-- pl-8 clears the marker column; the connector stub is drawn by this\n item along the left edge of that gutter and retired by the wrapper for\n the last entry, so the line stops where the story does. -->\n <div\n role=\"listitem\"\n class=\"relative pb-8 pl-8 last:pb-0\"\n :aria-current=\"status === 'current' ? 'step' : undefined\"\n >\n <!-- The spine sits under the marker column: left-3 is exactly the\n h-6 marker's centre, so the line runs through every dot and retires\n with the final entry. -->\n <span\n aria-hidden=\"true\"\n class=\"loom-timeline-line absolute top-6 bottom-0 left-3 w-px -translate-x-1/2 bg-border\"\n />\n\n <span\n aria-hidden=\"true\"\n :class=\"cn(timelineMarkerVariants({ status }), 'absolute left-0 top-0')\"\n >\n <Check v-if=\"status === 'complete'\" class=\"h-3.5 w-3.5\" />\n </span>\n\n <div>\n <div class=\"flex flex-wrap items-baseline gap-x-2\">\n <p class=\"text-sm font-medium\">{{ title }}</p>\n <time\n v-if=\"timestamp || datetime\"\n :datetime=\"datetime\"\n class=\"text-small tabular text-muted-foreground\"\n >{{ timestamp ?? datetime }}</time\n >\n <span class=\"sr-only\">{{ statusWord }}</span>\n </div>\n <p v-if=\"description\" class=\"mt-1 text-small text-muted-foreground\">{{ description }}</p>\n <!-- @slot Anything beyond title/description/timestamp — a Badge, a link out. -->\n <div v-if=\"$slots.default\" class=\"mt-2\">\n <slot />\n </div>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,50 @@
1
+ import { type LabelOverrides, type TimelineLabels } from "../../../labels/src/index";
2
+ import { type TimelineStatus } from "./Timeline.vue";
3
+ /**
4
+ * The marker, per status. Complete fills with success and takes the check;
5
+ * current fills with primary and wears a halo — emphasis without motion, so
6
+ * reduced-motion readers lose nothing; upcoming is a hollow dot, present but
7
+ * not yet lived.
8
+ */
9
+ export declare const timelineMarkerVariants: (props?: ({
10
+ status?: "complete" | "current" | "upcoming" | null | undefined;
11
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
14
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
15
+ /** The moment's heading. */
16
+ title: string;
17
+ /** A line under the title. */
18
+ description?: string;
19
+ /** When it happened (or will). Rendered inside a real `<time>`. */
20
+ timestamp?: string;
21
+ /** Machine-readable timestamp for `<time datetime>`. */
22
+ datetime?: string;
23
+ /** Where the moment sits relative to now. The host owns the truth. */
24
+ status?: TimelineStatus;
25
+ /** Names what the item says on its own account, as any subset of `TimelineLabels`. */
26
+ labels?: LabelOverrides<TimelineLabels>;
27
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{
28
+ /** The moment's heading. */
29
+ title: string;
30
+ /** A line under the title. */
31
+ description?: string;
32
+ /** When it happened (or will). Rendered inside a real `<time>`. */
33
+ timestamp?: string;
34
+ /** Machine-readable timestamp for `<time datetime>`. */
35
+ datetime?: string;
36
+ /** Where the moment sits relative to now. The host owns the truth. */
37
+ status?: TimelineStatus;
38
+ /** Names what the item says on its own account, as any subset of `TimelineLabels`. */
39
+ labels?: LabelOverrides<TimelineLabels>;
40
+ }> & Readonly<{}>, {
41
+ status: TimelineStatus;
42
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
43
+ default?: (props: {}) => any;
44
+ }>;
45
+ type __VLS_WithSlots<T, S> = T & {
46
+ new (): {
47
+ $slots: S;
48
+ };
49
+ };
50
+ //# sourceMappingURL=TimelineItem.vue.d.ts.map
@@ -0,0 +1,96 @@
1
+ import { cn } from "../../../core/src/cn.js";
2
+ import "../../../core/src/index.js";
3
+ import { useLabels } from "../../../labels/src/labels.js";
4
+ import "../../../labels/src/index.js";
5
+ import { TIMELINE_LABELS } from "./Timeline.vue_vue_type_script_setup_true_lang.js";
6
+ import "./Timeline.js";
7
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, renderSlot, toDisplayString, unref } from "vue";
8
+ import { Check } from "@lucide/vue";
9
+ import { cva } from "class-variance-authority";
10
+ //#region packages/primitives/timeline/src/TimelineItem.vue?vue&type=script&setup=true&lang.ts
11
+ var _hoisted_1 = ["aria-current"];
12
+ var _hoisted_2 = { class: "flex flex-wrap items-baseline gap-x-2" };
13
+ var _hoisted_3 = { class: "text-sm font-medium" };
14
+ var _hoisted_4 = ["datetime"];
15
+ var _hoisted_5 = { class: "sr-only" };
16
+ var _hoisted_6 = {
17
+ key: 0,
18
+ class: "mt-1 text-small text-muted-foreground"
19
+ };
20
+ var _hoisted_7 = {
21
+ key: 1,
22
+ class: "mt-2"
23
+ };
24
+ /**
25
+ * The marker, per status. Complete fills with success and takes the check;
26
+ * current fills with primary and wears a halo — emphasis without motion, so
27
+ * reduced-motion readers lose nothing; upcoming is a hollow dot, present but
28
+ * not yet lived.
29
+ */
30
+ var timelineMarkerVariants = cva("relative z-raised flex h-6 w-6 shrink-0 items-center justify-center rounded-full border", {
31
+ variants: { status: {
32
+ complete: "border-success bg-success text-success-foreground",
33
+ current: "border-primary bg-primary-muted text-primary-text ring-4 ring-primary-muted",
34
+ upcoming: "border-border bg-background text-muted-foreground"
35
+ } },
36
+ defaultVariants: { status: "upcoming" }
37
+ });
38
+ var TimelineItem_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
39
+ __name: "TimelineItem",
40
+ props: {
41
+ title: {},
42
+ description: {},
43
+ timestamp: {},
44
+ datetime: {},
45
+ status: { default: "upcoming" },
46
+ labels: {}
47
+ },
48
+ setup(__props) {
49
+ const props = __props;
50
+ const text = useLabels("timeline", TIMELINE_LABELS, () => props.labels);
51
+ const STATUS_WORD = {
52
+ complete: "complete",
53
+ current: "current",
54
+ upcoming: "upcoming"
55
+ };
56
+ const statusWord = computed(() => {
57
+ return text.value[STATUS_WORD[props.status]];
58
+ });
59
+ return (_ctx, _cache) => {
60
+ return openBlock(), createElementBlock("div", {
61
+ role: "listitem",
62
+ class: "relative pb-8 pl-8 last:pb-0",
63
+ "aria-current": __props.status === "current" ? "step" : void 0
64
+ }, [
65
+ _cache[0] || (_cache[0] = createElementVNode("span", {
66
+ "aria-hidden": "true",
67
+ class: "loom-timeline-line absolute top-6 bottom-0 left-3 w-px -translate-x-1/2 bg-border"
68
+ }, null, -1)),
69
+ createElementVNode("span", {
70
+ "aria-hidden": "true",
71
+ class: normalizeClass(unref(cn)(unref(timelineMarkerVariants)({ status: __props.status }), "absolute left-0 top-0"))
72
+ }, [__props.status === "complete" ? (openBlock(), createBlock(unref(Check), {
73
+ key: 0,
74
+ class: "h-3.5 w-3.5"
75
+ })) : createCommentVNode("", true)], 2),
76
+ createElementVNode("div", null, [
77
+ createElementVNode("div", _hoisted_2, [
78
+ createElementVNode("p", _hoisted_3, toDisplayString(__props.title), 1),
79
+ __props.timestamp || __props.datetime ? (openBlock(), createElementBlock("time", {
80
+ key: 0,
81
+ datetime: __props.datetime,
82
+ class: "text-small tabular text-muted-foreground"
83
+ }, toDisplayString(__props.timestamp ?? __props.datetime), 9, _hoisted_4)) : createCommentVNode("", true),
84
+ createElementVNode("span", _hoisted_5, toDisplayString(statusWord.value), 1)
85
+ ]),
86
+ __props.description ? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString(__props.description), 1)) : createCommentVNode("", true),
87
+ _ctx.$slots.default ? (openBlock(), createElementBlock("div", _hoisted_7, [renderSlot(_ctx.$slots, "default")])) : createCommentVNode("", true)
88
+ ])
89
+ ], 8, _hoisted_1);
90
+ };
91
+ }
92
+ });
93
+ //#endregion
94
+ export { TimelineItem_vue_vue_type_script_setup_true_lang_default as default, timelineMarkerVariants };
95
+
96
+ //# sourceMappingURL=TimelineItem.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimelineItem.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../../../packages/primitives/timeline/src/TimelineItem.vue"],"sourcesContent":["<script lang=\"ts\">\nimport { cva } from \"class-variance-authority\";\n\n/**\n * The marker, per status. Complete fills with success and takes the check;\n * current fills with primary and wears a halo — emphasis without motion, so\n * reduced-motion readers lose nothing; upcoming is a hollow dot, present but\n * not yet lived.\n */\nexport const timelineMarkerVariants = cva(\n \"relative z-raised flex h-6 w-6 shrink-0 items-center justify-center rounded-full border\",\n {\n variants: {\n status: {\n complete: \"border-success bg-success text-success-foreground\",\n current: \"border-primary bg-primary-muted text-primary-text ring-4 ring-primary-muted\",\n upcoming: \"border-border bg-background text-muted-foreground\",\n },\n },\n defaultVariants: { status: \"upcoming\" as TimelineStatus },\n },\n);\n</script>\n\n<script setup lang=\"ts\">\nimport { computed } from \"vue\";\nimport { Check } from \"@lucide/vue\";\nimport { useLabels, type LabelOverrides, type TimelineLabels } from \"@ecoma-io/loom-labels\";\nimport { cn } from \"@ecoma-io/loom-core\";\nimport { TIMELINE_LABELS, type TimelineStatus } from \"./Timeline.vue\";\n\nconst props = withDefaults(\n defineProps<{\n /** The moment's heading. */\n title: string;\n /** A line under the title. */\n description?: string;\n /** When it happened (or will). Rendered inside a real `<time>`. */\n timestamp?: string;\n /** Machine-readable timestamp for `<time datetime>`. */\n datetime?: string;\n /** Where the moment sits relative to now. The host owns the truth. */\n status?: TimelineStatus;\n /** Names what the item says on its own account, as any subset of `TimelineLabels`. */\n labels?: LabelOverrides<TimelineLabels>;\n }>(),\n { status: \"upcoming\" },\n);\n\n// `text`, not `labels`: one of three sources, resolved key by key.\nconst text = useLabels(\"timeline\", TIMELINE_LABELS, () => props.labels);\n\nconst STATUS_WORD = {\n complete: \"complete\",\n current: \"current\",\n upcoming: \"upcoming\",\n} as const;\nconst statusWord = computed(() => {\n const bag = text.value;\n return bag[STATUS_WORD[props.status]];\n});\n</script>\n\n<template>\n <!-- pl-8 clears the marker column; the connector stub is drawn by this\n item along the left edge of that gutter and retired by the wrapper for\n the last entry, so the line stops where the story does. -->\n <div\n role=\"listitem\"\n class=\"relative pb-8 pl-8 last:pb-0\"\n :aria-current=\"status === 'current' ? 'step' : undefined\"\n >\n <!-- The spine sits under the marker column: left-3 is exactly the\n h-6 marker's centre, so the line runs through every dot and retires\n with the final entry. -->\n <span\n aria-hidden=\"true\"\n class=\"loom-timeline-line absolute top-6 bottom-0 left-3 w-px -translate-x-1/2 bg-border\"\n />\n\n <span\n aria-hidden=\"true\"\n :class=\"cn(timelineMarkerVariants({ status }), 'absolute left-0 top-0')\"\n >\n <Check v-if=\"status === 'complete'\" class=\"h-3.5 w-3.5\" />\n </span>\n\n <div>\n <div class=\"flex flex-wrap items-baseline gap-x-2\">\n <p class=\"text-sm font-medium\">{{ title }}</p>\n <time\n v-if=\"timestamp || datetime\"\n :datetime=\"datetime\"\n class=\"text-small tabular text-muted-foreground\"\n >{{ timestamp ?? datetime }}</time\n >\n <span class=\"sr-only\">{{ statusWord }}</span>\n </div>\n <p v-if=\"description\" class=\"mt-1 text-small text-muted-foreground\">{{ description }}</p>\n <!-- @slot Anything beyond title/description/timestamp — a Badge, a link out. -->\n <div v-if=\"$slots.default\" class=\"mt-2\">\n <slot />\n </div>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,IAAa,yBAAyB,IACpC,2FACA;CACE,UAAU,EACR,QAAQ;EACN,UAAU;EACV,SAAS;EACT,UAAU;CACZ,EACF;CACA,iBAAiB,EAAE,QAAQ,WAA6B;AAC1D,CACF;;;;;;;;;;;;EAUA,MAAM,QAAQ;EAmBd,MAAM,OAAO,UAAU,YAAY,uBAAuB,MAAM,MAAM;EAEtE,MAAM,cAAc;GAClB,UAAU;GACV,SAAS;GACT,UAAU;EACZ;EACA,MAAM,aAAa,eAAe;GAEhC,OADY,KAAK,MACN,YAAY,MAAM;EAC/B,CAAC;;GAOC,OAAA,UAAA,GAAA,mBAqCM,OAAA;IApCJ,MAAK;IACL,OAAM;IACL,gBAAc,QAAA,WAAM,YAAA,SAA0B,KAAA;;IAK/C,OAAA,OAAA,OAAA,KAAA,mBAGE,QAAA;KAFA,eAAY;KACZ,OAAM;;IAGR,mBAKO,QAAA;KAJL,eAAY;KACX,OAAK,eAAE,MAAA,EAAA,CAAE,CAAC,MAAA,sBAAA,CAAsB,CAAA,EAAA,QAAG,QAAA,OAAM,CAAA,GAAA,uBAAA,CAAA;IAE7B,GAAA,CAAA,QAAA,WAAM,cAAnB,UAAA,GAAA,YAA0D,MAAA,KAAA,GAAA;;KAAtB,OAAM;;IAG5C,mBAgBM,OAAA,MAAA;KAfJ,mBASM,OATN,YASM;MARJ,mBAA8C,KAA9C,YAA8C,gBAAZ,QAAA,KAAK,GAAA,CAAA;MAE/B,QAAA,aAAa,QAAA,YADrB,UAAA,GAAA,mBAKC,QAAA;;OAHE,UAAU,QAAA;OACX,OAAM;MACF,GAAA,gBAAA,QAAA,aAAa,QAAA,QAAQ,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;MAE3B,mBAA6C,QAA7C,YAA6C,gBAApB,WAAA,KAAU,GAAA,CAAA;;KAE5B,QAAA,eAAT,UAAA,GAAA,mBAAyF,KAAzF,YAAyF,gBAAlB,QAAA,WAAW,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;KAEvEA,KAAAA,OAAO,WAAlB,UAAA,GAAA,mBAEM,OAFN,YAEM,CADJ,WAAQ,KAAA,QAAA,SAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA"}
@@ -0,0 +1,5 @@
1
+ export { default, TIMELINE_LABELS } from "./Timeline.vue";
2
+ export { default as TimelineItem, timelineMarkerVariants } from "./TimelineItem.vue";
3
+ export type { TimelineLabels } from "../../../labels/src/index";
4
+ export type { TimelineStatus } from "./Timeline.vue";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ import { TIMELINE_LABELS } from "./Timeline.vue_vue_type_script_setup_true_lang.js";
2
+ import Timeline_default from "./Timeline.js";
3
+ import "./TimelineItem.vue_vue_type_script_setup_true_lang.js";
4
+ import TimelineItem_default from "./TimelineItem.js";
5
+ export { TIMELINE_LABELS, TimelineItem_default as TimelineItem, Timeline_default as default };
@@ -1,6 +1,6 @@
1
- import { type LabelOverrides, type ToastLabels } from "@ecoma-io/loom-labels";
1
+ import { type LabelOverrides, type ToastLabels } from "../../../labels/src/index";
2
2
  export type ToastVariant = "info" | "success" | "warning" | "destructive" | "accent";
3
- export type { ToastLabels } from "@ecoma-io/loom-labels";
3
+ export type { ToastLabels } from "../../../labels/src/index";
4
4
  export { TOAST_LABELS } from "./ToastItem.vue";
5
5
  declare const _default: typeof __VLS_export;
6
6
  export default _default;