bitboss-ui 2.1.135 → 3.0.0-alpha.4

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 (1369) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -24
  3. package/bin/bitboss-ui-mcp.mjs +662 -0
  4. package/bin/bitboss-ui.mjs +710 -0
  5. package/bin/bitboss-ui.node.test.mjs +113 -0
  6. package/dist/ai/BbAccordion.md +528 -113
  7. package/dist/ai/BbAlert.md +380 -197
  8. package/dist/ai/BbAsterisk.md +46 -0
  9. package/dist/ai/BbAvatar.md +458 -178
  10. package/dist/ai/BbBadge.md +697 -225
  11. package/dist/ai/BbBadgeButton.md +54 -0
  12. package/dist/ai/BbBaseButton.md +491 -0
  13. package/dist/ai/BbBaseCheckbox.md +49 -0
  14. package/dist/ai/BbBaseCheckboxGroup.md +64 -0
  15. package/dist/ai/BbBaseCheckboxIcon.md +176 -0
  16. package/dist/ai/BbBaseColorInput.md +64 -0
  17. package/dist/ai/BbBaseColorPalette.md +39 -0
  18. package/dist/ai/BbBaseDatePicker.md +42 -0
  19. package/dist/ai/BbBaseDatePickerInput.md +73 -0
  20. package/dist/ai/BbBaseInputContainer.md +48 -0
  21. package/dist/ai/BbBaseNumberInput.md +71 -0
  22. package/dist/ai/BbBaseRadio.md +46 -0
  23. package/dist/ai/BbBaseRadioGroup.md +64 -0
  24. package/dist/ai/BbBaseRadioIcon.md +191 -0
  25. package/dist/ai/BbBaseRating.md +51 -0
  26. package/dist/ai/BbBaseSelect.md +88 -0
  27. package/dist/ai/BbBaseSlider.md +56 -0
  28. package/dist/ai/BbBaseSwitch.md +49 -0
  29. package/dist/ai/BbBaseSwitchGroup.md +64 -0
  30. package/dist/ai/BbBaseSwitchIcon.md +186 -0
  31. package/dist/ai/BbBaseTag.md +68 -0
  32. package/dist/ai/BbBaseTextInput.md +75 -0
  33. package/dist/ai/BbBaseTextarea.md +62 -0
  34. package/dist/ai/BbBaseTimePickerInput.md +62 -0
  35. package/dist/ai/BbBreadcrumbs.md +553 -503
  36. package/dist/ai/BbButton.md +841 -636
  37. package/dist/ai/BbCheckbox.md +585 -254
  38. package/dist/ai/BbCheckboxGroup.md +573 -361
  39. package/dist/ai/BbCollapsible.md +425 -103
  40. package/dist/ai/BbColorInput.md +536 -323
  41. package/dist/ai/BbColorPalette.md +569 -330
  42. package/dist/ai/BbConfirm.md +659 -136
  43. package/dist/ai/BbConfirmPortal.md +28 -0
  44. package/dist/ai/BbDatePicker.md +244 -0
  45. package/dist/ai/BbDatePickerInput.md +720 -388
  46. package/dist/ai/BbDialog.md +778 -99
  47. package/dist/ai/BbDropdown.md +1072 -763
  48. package/dist/ai/BbDropdownButton.md +525 -614
  49. package/dist/ai/BbDropdownGroup.md +34 -0
  50. package/dist/ai/BbDropzone.md +1206 -436
  51. package/dist/ai/BbForm.md +239 -0
  52. package/dist/ai/BbIcon.md +382 -212
  53. package/dist/ai/BbIndicator.md +283 -0
  54. package/dist/ai/BbNumberInput.md +717 -355
  55. package/dist/ai/BbOffCanvas.md +897 -498
  56. package/dist/ai/BbPagination.md +489 -535
  57. package/dist/ai/BbPopover.md +591 -546
  58. package/dist/ai/BbProgress.md +462 -69
  59. package/dist/ai/BbRadio.md +352 -229
  60. package/dist/ai/BbRadioGroup.md +437 -353
  61. package/dist/ai/BbRating.md +372 -225
  62. package/dist/ai/BbSelect.md +1399 -536
  63. package/dist/ai/BbSelectPopover.md +1255 -1578
  64. package/dist/ai/BbSlider.md +500 -256
  65. package/dist/ai/BbSmoothHeight.md +485 -139
  66. package/dist/ai/BbSpinner.md +718 -131
  67. package/dist/ai/BbSwitch.md +573 -119
  68. package/dist/ai/BbSwitchGroup.md +521 -214
  69. package/dist/ai/BbTable.md +1996 -2365
  70. package/dist/ai/BbTabs.md +1147 -0
  71. package/dist/ai/BbTabsList.md +29 -0
  72. package/dist/ai/BbTabsPanes.md +27 -0
  73. package/dist/ai/BbTabsRoot.md +57 -0
  74. package/dist/ai/BbTag.md +416 -292
  75. package/dist/ai/BbTextInput.md +878 -333
  76. package/dist/ai/BbTextarea.md +850 -255
  77. package/dist/ai/BbTimePicker.md +231 -0
  78. package/dist/ai/BbTimePickerInput.md +200 -0
  79. package/dist/ai/BbToast.md +669 -196
  80. package/dist/ai/BbToastPortal.md +28 -0
  81. package/dist/ai/BbTooltip.md +453 -346
  82. package/dist/ai/BbTree.md +993 -234
  83. package/dist/ai/ChipsBox.md +27 -199
  84. package/dist/ai/ClearableButton.md +21 -55
  85. package/dist/ai/CommaBox.md +25 -200
  86. package/dist/ai/CommonInputWrapper.md +42 -0
  87. package/dist/ai/CommonPopover.md +30 -510
  88. package/dist/ai/ErrorIcon.md +19 -56
  89. package/dist/ai/FlatListBox.md +35 -379
  90. package/dist/ai/GroupedListBox.md +37 -535
  91. package/dist/ai/GuidesComponent.md +21 -0
  92. package/dist/ai/ListBox.md +38 -231
  93. package/dist/ai/OptionsContainer.md +34 -253
  94. package/dist/ai/WarningIcon.md +21 -0
  95. package/dist/ai/components.json +15897 -0
  96. package/dist/ai/composables/useBbConfig.md +80 -0
  97. package/dist/ai/composables/useBbFormContext.md +110 -0
  98. package/dist/ai/composables/useBbTableContext.md +167 -0
  99. package/dist/ai/composables/useBbTabsContext.md +118 -0
  100. package/dist/ai/composables/useBroadcastChannelInstance.md +97 -0
  101. package/dist/ai/composables/useConfirm.md +313 -0
  102. package/dist/ai/composables/useCountdown.md +86 -0
  103. package/dist/ai/composables/useId.md +60 -0
  104. package/dist/ai/composables/useMobile.md +85 -0
  105. package/dist/ai/composables/useToast.md +238 -0
  106. package/dist/ai/guides/agent-contract.md +165 -0
  107. package/dist/ai/guides/ai-router.md +113 -0
  108. package/dist/ai/guides/app-layout.md +318 -0
  109. package/dist/ai/guides/coherence-playbook.md +136 -0
  110. package/dist/ai/guides/component-picker.md +236 -0
  111. package/dist/ai/guides/design-language.md +478 -0
  112. package/dist/ai/guides/design-tokens.md +450 -0
  113. package/dist/ai/guides/fetch-items-playbook.md +161 -0
  114. package/dist/ai/guides/floating-bottom-panel.md +130 -0
  115. package/dist/ai/guides/icons-policy.md +257 -0
  116. package/dist/ai/guides/inertia-helpers.md +1443 -0
  117. package/dist/ai/guides/inline-edit-playbook.md +267 -0
  118. package/dist/ai/guides/installation-and-plugin-setup.md +440 -0
  119. package/dist/ai/guides/migration/components/bb-alert.md +37 -0
  120. package/dist/ai/guides/migration/components/bb-avatar.md +24 -0
  121. package/dist/ai/guides/migration/components/bb-badge.md +110 -0
  122. package/dist/ai/guides/migration/components/bb-breadcrumbs.md +26 -0
  123. package/dist/ai/guides/migration/components/bb-button.md +55 -0
  124. package/dist/ai/guides/migration/components/bb-checkbox-group.md +54 -0
  125. package/dist/ai/guides/migration/components/bb-checkbox.md +45 -0
  126. package/dist/ai/guides/migration/components/bb-chip.md +35 -0
  127. package/dist/ai/guides/migration/components/bb-collapsible.md +26 -0
  128. package/dist/ai/guides/migration/components/bb-color-input.md +23 -0
  129. package/dist/ai/guides/migration/components/bb-color-palette.md +31 -0
  130. package/dist/ai/guides/migration/components/bb-date-picker-input.md +84 -0
  131. package/dist/ai/guides/migration/components/bb-dialog.md +46 -0
  132. package/dist/ai/guides/migration/components/bb-dropdown-button.md +31 -0
  133. package/dist/ai/guides/migration/components/bb-dropdown.md +36 -0
  134. package/dist/ai/guides/migration/components/bb-dropzone.md +27 -0
  135. package/dist/ai/guides/migration/components/bb-icon.md +27 -0
  136. package/dist/ai/guides/migration/components/bb-offcanvas.md +63 -0
  137. package/dist/ai/guides/migration/components/bb-pagination.md +24 -0
  138. package/dist/ai/guides/migration/components/bb-popover.md +52 -0
  139. package/dist/ai/guides/migration/components/bb-rating.md +24 -0
  140. package/dist/ai/guides/migration/components/bb-select-popover.md +59 -0
  141. package/dist/ai/guides/migration/components/bb-select.md +69 -0
  142. package/dist/ai/guides/migration/components/bb-slider.md +25 -0
  143. package/dist/ai/guides/migration/components/bb-smooth-height.md +45 -0
  144. package/dist/ai/guides/migration/components/bb-spinner.md +23 -0
  145. package/dist/ai/guides/migration/components/bb-table.md +66 -0
  146. package/dist/ai/guides/migration/components/bb-tabs.md +47 -0
  147. package/dist/ai/guides/migration/components/bb-tag.md +29 -0
  148. package/dist/ai/guides/migration/components/bb-text-input.md +37 -0
  149. package/dist/ai/guides/migration/components/bb-toast.md +47 -0
  150. package/dist/ai/guides/migration/components/bb-tooltip.md +34 -0
  151. package/dist/ai/guides/migration/components/bb-tree.md +77 -0
  152. package/dist/ai/guides/migration/components/use-confirm.md +46 -0
  153. package/dist/ai/guides/migration/v2-to-v3.md +649 -0
  154. package/dist/ai/guides/options-items-playbook.md +222 -0
  155. package/dist/ai/guides/page-shell.md +113 -0
  156. package/dist/ai/guides/validated-forms.md +177 -0
  157. package/dist/ai/index.md +158 -32
  158. package/dist/ai/recipes/inertia/approvals-inbox.md +1635 -0
  159. package/dist/ai/recipes/inertia/inline-edit-workspace.md +1765 -0
  160. package/dist/ai/recipes/inertia/layout-scaffold.md +385 -0
  161. package/dist/ai/recipes/inertia/onboarding.md +1791 -0
  162. package/dist/ai/recipes/inertia/ownership-atlas.md +1327 -0
  163. package/dist/ai/recipes/inertia/record-form.md +1489 -0
  164. package/dist/ai/recipes/inertia/records-workspace.md +1416 -0
  165. package/dist/ai/recipes/inertia/upload-center.md +2003 -0
  166. package/dist/ai/recipes/inertia/wizard-form.md +1500 -0
  167. package/dist/ai/recipes/nuxt/approvals-inbox.md +1578 -0
  168. package/dist/ai/recipes/nuxt/inline-edit-workspace.md +1703 -0
  169. package/dist/ai/recipes/nuxt/layout-scaffold.md +385 -0
  170. package/dist/ai/recipes/nuxt/onboarding.md +1724 -0
  171. package/dist/ai/recipes/nuxt/record-form.md +1502 -0
  172. package/dist/ai/recipes/nuxt/records-workspace.md +1329 -0
  173. package/dist/ai/recipes/nuxt/upload-center.md +1886 -0
  174. package/dist/ai/recipes/nuxt/wizard-form.md +1509 -0
  175. package/dist/ai/recipes/vue/approvals-inbox.md +1496 -0
  176. package/dist/ai/recipes/vue/inline-edit-workspace.md +1594 -0
  177. package/dist/ai/recipes/vue/layout-scaffold.md +385 -0
  178. package/dist/ai/recipes/vue/onboarding.md +1679 -0
  179. package/dist/ai/recipes/vue/record-form.md +1586 -0
  180. package/dist/ai/recipes/vue/records-workspace.md +1245 -0
  181. package/dist/ai/recipes/vue/upload-center.md +1845 -0
  182. package/dist/ai/recipes/vue/wizard-form.md +1441 -0
  183. package/dist/ai/source/BbAccordion.md +123 -0
  184. package/dist/ai/source/BbAlert.md +401 -0
  185. package/dist/ai/source/BbAsterisk.md +32 -0
  186. package/dist/ai/source/BbAvatar.md +258 -0
  187. package/dist/ai/source/BbBadge.md +560 -0
  188. package/dist/ai/source/BbBadgeButton.md +490 -0
  189. package/dist/ai/source/BbBaseButton.md +682 -0
  190. package/dist/ai/source/BbBaseCheckbox.md +461 -0
  191. package/dist/ai/source/BbBaseCheckboxGroup.md +554 -0
  192. package/dist/ai/source/BbBaseCheckboxIcon.md +201 -0
  193. package/dist/ai/source/BbBaseColorInput.md +416 -0
  194. package/dist/ai/source/BbBaseColorPalette.md +934 -0
  195. package/dist/ai/source/BbBaseDatePicker.md +1001 -0
  196. package/dist/ai/source/BbBaseDatePickerInput.md +1785 -0
  197. package/dist/ai/source/BbBaseInputContainer.md +1210 -0
  198. package/dist/ai/source/BbBaseNumberInput.md +617 -0
  199. package/dist/ai/source/BbBaseRadio.md +377 -0
  200. package/dist/ai/source/BbBaseRadioGroup.md +533 -0
  201. package/dist/ai/source/BbBaseRadioIcon.md +153 -0
  202. package/dist/ai/source/BbBaseRating.md +573 -0
  203. package/dist/ai/source/BbBaseSelect.md +898 -0
  204. package/dist/ai/source/BbBaseSlider.md +1085 -0
  205. package/dist/ai/source/BbBaseSwitch.md +246 -0
  206. package/dist/ai/source/BbBaseSwitchGroup.md +302 -0
  207. package/dist/ai/source/BbBaseSwitchIcon.md +206 -0
  208. package/dist/ai/source/BbBaseTag.md +694 -0
  209. package/dist/ai/source/BbBaseTextInput.md +511 -0
  210. package/dist/ai/source/BbBaseTextarea.md +480 -0
  211. package/dist/ai/source/BbBaseTimePickerInput.md +1031 -0
  212. package/dist/ai/source/BbBreadcrumbs.md +815 -0
  213. package/dist/ai/source/BbButton.md +1006 -0
  214. package/dist/ai/source/BbCheckbox.md +326 -0
  215. package/dist/ai/source/BbCheckboxGroup.md +419 -0
  216. package/dist/ai/source/BbCollapsible.md +137 -0
  217. package/dist/ai/source/BbColorInput.md +322 -0
  218. package/dist/ai/source/BbColorPalette.md +371 -0
  219. package/dist/ai/source/BbConfirm.md +193 -0
  220. package/dist/ai/source/BbConfirmPortal.md +74 -0
  221. package/dist/ai/source/BbDatePicker.md +729 -0
  222. package/dist/ai/source/BbDatePickerInput.md +525 -0
  223. package/dist/ai/source/BbDialog.md +818 -0
  224. package/dist/ai/source/BbDropdown.md +2387 -0
  225. package/dist/ai/source/BbDropdownButton.md +315 -0
  226. package/dist/ai/source/BbDropdownGroup.md +1259 -0
  227. package/dist/ai/source/BbDropzone.md +864 -0
  228. package/dist/ai/source/BbForm.md +100 -0
  229. package/dist/ai/source/BbIcon.md +214 -0
  230. package/dist/ai/source/BbIndicator.md +298 -0
  231. package/dist/ai/source/BbNumberInput.md +374 -0
  232. package/dist/ai/source/BbOffCanvas.md +1266 -0
  233. package/dist/ai/source/BbPagination.md +835 -0
  234. package/dist/ai/source/BbPopover.md +860 -0
  235. package/dist/ai/source/BbProgress.md +140 -0
  236. package/dist/ai/source/BbRadio.md +303 -0
  237. package/dist/ai/source/BbRadioGroup.md +420 -0
  238. package/dist/ai/source/BbRating.md +369 -0
  239. package/dist/ai/source/BbSelect.md +688 -0
  240. package/dist/ai/source/BbSelectPopover.md +2234 -0
  241. package/dist/ai/source/BbSlider.md +378 -0
  242. package/dist/ai/source/BbSmoothHeight.md +265 -0
  243. package/dist/ai/source/BbSpinner.md +127 -0
  244. package/dist/ai/source/BbSwitch.md +175 -0
  245. package/dist/ai/source/BbSwitchGroup.md +240 -0
  246. package/dist/ai/source/BbTable.md +3433 -0
  247. package/dist/ai/source/BbTabs.md +403 -0
  248. package/dist/ai/source/BbTabsList.md +578 -0
  249. package/dist/ai/source/BbTabsPanes.md +368 -0
  250. package/dist/ai/source/BbTabsRoot.md +372 -0
  251. package/dist/ai/source/BbTag.md +390 -0
  252. package/dist/ai/source/BbTextInput.md +352 -0
  253. package/dist/ai/source/BbTextarea.md +337 -0
  254. package/dist/ai/source/BbTimePicker.md +595 -0
  255. package/dist/ai/source/BbTimePickerInput.md +376 -0
  256. package/dist/ai/source/BbToast.md +525 -0
  257. package/dist/ai/source/BbToastPortal.md +482 -0
  258. package/dist/ai/source/BbTooltip.md +522 -0
  259. package/dist/ai/source/BbTree.md +323 -0
  260. package/dist/ai/source/ChipsBox.md +201 -0
  261. package/dist/ai/source/ClearableButton.md +70 -0
  262. package/dist/ai/source/CommaBox.md +202 -0
  263. package/dist/ai/source/CommonInputWrapper.md +554 -0
  264. package/dist/ai/source/CommonPopover.md +616 -0
  265. package/dist/ai/source/ErrorIcon.md +43 -0
  266. package/dist/ai/source/FlatListBox.md +396 -0
  267. package/dist/ai/source/GroupedListBox.md +540 -0
  268. package/dist/ai/source/GuidesComponent.md +383 -0
  269. package/dist/ai/source/ListBox.md +238 -0
  270. package/dist/ai/source/OptionsContainer.md +191 -0
  271. package/dist/ai/source/WarningIcon.md +47 -0
  272. package/dist/alert-variants.d.ts +19 -0
  273. package/dist/assets/css/reset.css_inline.js +4 -0
  274. package/dist/assets/svgs/alert.svg_raw.js +4 -0
  275. package/dist/assets/svgs/calendar.svg_raw.js +4 -0
  276. package/dist/assets/svgs/check_stroke.svg_raw.js +4 -0
  277. package/dist/assets/svgs/chevron_down.svg_raw.js +4 -0
  278. package/dist/assets/svgs/chevron_right.svg_raw.js +4 -0
  279. package/dist/assets/svgs/clear.svg_raw.js +4 -0
  280. package/dist/assets/svgs/clock.svg_raw.js +4 -0
  281. package/dist/assets/svgs/error.svg_raw.js +4 -0
  282. package/dist/assets/svgs/search.svg_raw.js +4 -0
  283. package/dist/assets/svgs/sort.svg_raw.js +4 -0
  284. package/dist/assets/svgs/spinner.svg_raw.js +4 -0
  285. package/dist/assets/svgs/user.svg_raw.js +4 -0
  286. package/dist/badge-variants.d.ts +19 -0
  287. package/dist/button-variants.d.ts +21 -0
  288. package/dist/components/BbAccordion/BbAccordion.vue.d.ts +9 -3
  289. package/dist/components/BbAccordion/BbAccordion.vue.js +6 -0
  290. package/dist/components/BbAccordion/BbAccordion.vue_vue_type_script_setup_true_lang.js +66 -0
  291. package/dist/components/BbAccordion/types.d.ts +12 -8
  292. package/dist/components/BbAlert/BbAlert.vue.d.ts +12 -3
  293. package/dist/components/BbAlert/BbAlert.vue.js +6 -0
  294. package/dist/components/BbAlert/BbAlert.vue_vue_type_script_setup_true_lang.js +65 -0
  295. package/dist/components/BbAlert/types.d.ts +56 -12
  296. package/dist/components/BbAsterisk/BbAsterisk.vue.d.ts +3 -0
  297. package/dist/components/BbAsterisk/BbAsterisk.vue.js +6 -0
  298. package/dist/components/BbAsterisk/BbAsterisk.vue_vue_type_script_setup_true_lang.js +13 -0
  299. package/dist/components/BbAvatar/BbAvatar.vue.d.ts +5 -0
  300. package/dist/components/BbAvatar/BbAvatar.vue.js +6 -0
  301. package/dist/components/BbAvatar/BbAvatar.vue_vue_type_script_setup_true_lang.js +78 -0
  302. package/dist/components/BbAvatar/types.d.ts +6 -5
  303. package/dist/components/BbBadge/BadgeAppendIcon.vue.d.ts +3 -0
  304. package/dist/components/BbBadge/BadgeAppendIcon.vue.js +5 -0
  305. package/dist/components/BbBadge/BadgeAppendIcon.vue_vue_type_script_setup_true_lang.js +22 -0
  306. package/dist/components/BbBadge/BadgeBodyContent.vue.d.ts +15 -0
  307. package/dist/components/BbBadge/BadgeBodyContent.vue.js +5 -0
  308. package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +34 -0
  309. package/dist/components/BbBadge/BbBadge.vue.d.ts +8 -1
  310. package/dist/components/BbBadge/BbBadge.vue.js +6 -0
  311. package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +83 -0
  312. package/dist/components/BbBadge/BbBadgeButton.vue.d.ts +12 -0
  313. package/dist/components/BbBadge/BbBadgeButton.vue.js +5 -0
  314. package/dist/components/BbBadge/BbBadgeButton.vue_vue_type_script_setup_true_lang.js +54 -0
  315. package/dist/components/BbBadge/badgeButtonContext.d.ts +27 -0
  316. package/dist/components/BbBadge/badgeButtonContext.js +5 -0
  317. package/dist/components/BbBadge/types.d.ts +61 -16
  318. package/dist/components/BbBaseButton/BbBaseButton.vue.d.ts +13 -0
  319. package/dist/components/BbBaseButton/BbBaseButton.vue.js +6 -0
  320. package/dist/components/BbBaseButton/BbBaseButton.vue_vue_type_script_setup_true_lang.js +171 -0
  321. package/dist/components/BbBaseButton/RouterComponent.vue.js +5 -0
  322. package/dist/components/BbBaseButton/RouterComponent.vue_vue_type_script_setup_true_lang.js +29 -0
  323. package/dist/components/BbBaseButton/types.d.ts +191 -0
  324. package/dist/components/BbBaseButton/types.js +33 -0
  325. package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue.d.ts +40 -0
  326. package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue.js +6 -0
  327. package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue_vue_type_script_setup_true_lang.js +145 -0
  328. package/dist/components/BbBaseCheckbox/types.d.ts +224 -0
  329. package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue.d.ts +19 -0
  330. package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue.js +6 -0
  331. package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue_vue_type_script_setup_true_lang.js +190 -0
  332. package/dist/components/BbBaseCheckboxGroup/types.d.ts +257 -0
  333. package/dist/components/BbBaseCheckboxIcon/BbBaseCheckboxIcon.vue.d.ts +4 -0
  334. package/dist/components/BbBaseCheckboxIcon/BbBaseCheckboxIcon.vue.js +6 -0
  335. package/dist/components/BbBaseCheckboxIcon/BbBaseCheckboxIcon.vue_vue_type_script_setup_true_lang.js +43 -0
  336. package/dist/components/BbBaseCheckboxIcon/types.d.ts +47 -0
  337. package/dist/components/BbBaseColorInput/BbBaseColorInput.vue.d.ts +42 -0
  338. package/dist/components/BbBaseColorInput/BbBaseColorInput.vue.js +6 -0
  339. package/dist/components/BbBaseColorInput/BbBaseColorInput.vue_vue_type_script_setup_true_lang.js +178 -0
  340. package/dist/components/BbBaseColorInput/types.d.ts +210 -0
  341. package/dist/components/BbBaseColorPalette/BbBaseColorPalette.vue.d.ts +13 -0
  342. package/dist/components/BbBaseColorPalette/BbBaseColorPalette.vue.js +6 -0
  343. package/dist/components/BbBaseColorPalette/BbBaseColorPalette.vue_vue_type_script_setup_true_lang.js +395 -0
  344. package/dist/components/BbBaseColorPalette/types.d.ts +40 -0
  345. package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue.js +6 -0
  346. package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue_vue_type_script_setup_true_lang.js +300 -0
  347. package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue.d.ts +44 -0
  348. package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue.js +5 -0
  349. package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +310 -0
  350. package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue.d.ts +5 -0
  351. package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue.js +5 -0
  352. package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue_vue_type_script_setup_true_lang.js +63 -0
  353. package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue.d.ts +5 -0
  354. package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue.js +5 -0
  355. package/dist/components/BbBaseDatePicker/BbBaseDatePickerYearSelector.vue_vue_type_script_setup_true_lang.js +75 -0
  356. package/dist/components/BbBaseDatePicker/typeahead.d.ts +112 -0
  357. package/dist/components/BbBaseDatePicker/typeahead.js +37 -0
  358. package/dist/components/BbBaseDatePicker/types.d.ts +97 -0
  359. package/dist/components/BbBaseDatePicker/useDatePickerContext.d.ts +58 -0
  360. package/dist/components/BbBaseDatePicker/useDatePickerContext.js +11 -0
  361. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue.d.ts +30 -0
  362. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue.js +6 -0
  363. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +716 -0
  364. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue.d.ts +30 -0
  365. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue.js +6 -0
  366. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerTimeSelector.vue_vue_type_script_setup_true_lang.js +204 -0
  367. package/dist/components/BbBaseDatePickerInput/types.d.ts +311 -0
  368. package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue.d.ts +17 -0
  369. package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue.js +6 -0
  370. package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue_vue_type_script_setup_true_lang.js +191 -0
  371. package/dist/components/BbBaseInputContainer/types.d.ts +197 -0
  372. package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue.d.ts +52 -0
  373. package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue.js +6 -0
  374. package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue_vue_type_script_setup_true_lang.js +215 -0
  375. package/dist/components/BbBaseNumberInput/types.d.ts +211 -0
  376. package/dist/components/BbBaseRadio/BbBaseRadio.vue.d.ts +33 -0
  377. package/dist/components/BbBaseRadio/BbBaseRadio.vue.js +6 -0
  378. package/dist/components/BbBaseRadio/BbBaseRadio.vue_vue_type_script_setup_true_lang.js +114 -0
  379. package/dist/components/BbBaseRadio/types.d.ts +179 -0
  380. package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue.d.ts +19 -0
  381. package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue.js +6 -0
  382. package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue_vue_type_script_setup_true_lang.js +177 -0
  383. package/dist/components/BbBaseRadioGroup/types.d.ts +223 -0
  384. package/dist/components/BbBaseRadioIcon/BbBaseRadioIcon.vue.d.ts +4 -0
  385. package/dist/components/BbBaseRadioIcon/BbBaseRadioIcon.vue.js +6 -0
  386. package/dist/components/BbBaseRadioIcon/BbBaseRadioIcon.vue_vue_type_script_setup_true_lang.js +28 -0
  387. package/dist/components/BbBaseRadioIcon/types.d.ts +41 -0
  388. package/dist/components/BbBaseRating/BbBaseRating.vue.d.ts +38 -0
  389. package/dist/components/BbBaseRating/BbBaseRating.vue.js +6 -0
  390. package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +184 -0
  391. package/dist/components/BbBaseRating/types.d.ts +168 -0
  392. package/dist/components/BbBaseSelect/BbBaseSelect.vue.d.ts +19 -0
  393. package/dist/components/BbBaseSelect/BbBaseSelect.vue.js +6 -0
  394. package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +307 -0
  395. package/dist/components/BbBaseSelect/types.d.ts +297 -0
  396. package/dist/components/BbBaseSlider/BbBaseSlider.vue.d.ts +35 -0
  397. package/dist/components/BbBaseSlider/BbBaseSlider.vue.js +6 -0
  398. package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +324 -0
  399. package/dist/components/BbBaseSlider/types.d.ts +200 -0
  400. package/dist/components/BbBaseSwitch/BbBaseSwitch.vue.d.ts +35 -0
  401. package/dist/components/BbBaseSwitch/BbBaseSwitch.vue.js +6 -0
  402. package/dist/components/BbBaseSwitch/BbBaseSwitch.vue_vue_type_script_setup_true_lang.js +146 -0
  403. package/dist/components/BbBaseSwitch/types.d.ts +10 -0
  404. package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue.d.ts +19 -0
  405. package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue.js +6 -0
  406. package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue_vue_type_script_setup_true_lang.js +190 -0
  407. package/dist/components/BbBaseSwitchGroup/types.d.ts +8 -0
  408. package/dist/components/BbBaseSwitchIcon/BbBaseSwitchIcon.vue.d.ts +4 -0
  409. package/dist/components/BbBaseSwitchIcon/BbBaseSwitchIcon.vue.js +6 -0
  410. package/dist/components/BbBaseSwitchIcon/BbBaseSwitchIcon.vue_vue_type_script_setup_true_lang.js +30 -0
  411. package/dist/components/BbBaseSwitchIcon/types.d.ts +48 -0
  412. package/dist/components/BbBaseTag/BbBaseTag.vue.d.ts +50 -0
  413. package/dist/components/BbBaseTag/BbBaseTag.vue.js +6 -0
  414. package/dist/components/BbBaseTag/BbBaseTag.vue_vue_type_script_setup_true_lang.js +266 -0
  415. package/dist/components/BbBaseTag/types.d.ts +160 -0
  416. package/dist/components/BbBaseTextInput/BbBaseTextInput.vue.d.ts +45 -0
  417. package/dist/components/BbBaseTextInput/BbBaseTextInput.vue.js +6 -0
  418. package/dist/components/BbBaseTextInput/BbBaseTextInput.vue_vue_type_script_setup_true_lang.js +200 -0
  419. package/dist/components/BbBaseTextInput/types.d.ts +209 -0
  420. package/dist/components/BbBaseTextarea/BbBaseTextarea.vue.d.ts +39 -0
  421. package/dist/components/BbBaseTextarea/BbBaseTextarea.vue.js +6 -0
  422. package/dist/components/BbBaseTextarea/BbBaseTextarea.vue_vue_type_script_setup_true_lang.js +175 -0
  423. package/dist/components/BbBaseTextarea/types.d.ts +170 -0
  424. package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue.d.ts +27 -0
  425. package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue.js +6 -0
  426. package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +522 -0
  427. package/dist/components/BbBaseTimePickerInput/types.d.ts +151 -0
  428. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue.d.ts +0 -1
  429. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue.js +6 -0
  430. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +224 -0
  431. package/dist/components/BbBreadcrumbs/estimateBreadcrumbItemWidth.js +47 -0
  432. package/dist/components/BbBreadcrumbs/types.d.ts +37 -22
  433. package/dist/components/BbButton/BbButton.vue.d.ts +6 -5
  434. package/dist/components/BbButton/BbButton.vue.js +6 -0
  435. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +181 -0
  436. package/dist/components/BbButton/types.d.ts +48 -47
  437. package/dist/components/BbCheckbox/BbCheckbox.vue.d.ts +4 -3
  438. package/dist/components/BbCheckbox/BbCheckbox.vue.js +6 -0
  439. package/dist/components/BbCheckbox/BbCheckbox.vue_vue_type_script_setup_true_lang.js +146 -0
  440. package/dist/components/BbCheckbox/types.d.ts +31 -10
  441. package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue.d.ts +2 -13
  442. package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue.js +6 -0
  443. package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +190 -0
  444. package/dist/components/BbCheckboxGroup/types.d.ts +51 -23
  445. package/dist/components/BbCollapsible/BbCollapsible.vue.d.ts +2 -1
  446. package/dist/components/BbCollapsible/BbCollapsible.vue.js +6 -0
  447. package/dist/components/BbCollapsible/BbCollapsible.vue_vue_type_script_setup_true_lang.js +31 -0
  448. package/dist/components/BbCollapsible/types.d.ts +13 -8
  449. package/dist/components/BbColorInput/BbColorInput.vue.d.ts +10 -1
  450. package/dist/components/BbColorInput/BbColorInput.vue.js +6 -0
  451. package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +177 -0
  452. package/dist/components/BbColorInput/types.d.ts +44 -11
  453. package/dist/components/BbColorPalette/BbColorPalette.vue.d.ts +5 -8
  454. package/dist/components/BbColorPalette/BbColorPalette.vue.js +5 -0
  455. package/dist/components/BbColorPalette/BbColorPalette.vue_vue_type_script_setup_true_lang.js +126 -0
  456. package/dist/components/BbColorPalette/types.d.ts +71 -52
  457. package/dist/components/BbConfirm/BbConfirm.vue.js +6 -0
  458. package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +138 -0
  459. package/dist/components/BbConfirm/BbConfirmPortal.vue.d.ts +5 -6
  460. package/dist/components/BbConfirm/BbConfirmPortal.vue.js +5 -0
  461. package/dist/components/BbConfirm/BbConfirmPortal.vue_vue_type_script_setup_true_lang.js +19 -0
  462. package/dist/components/BbDatePicker/BbDatePicker.vue.d.ts +37 -0
  463. package/dist/components/BbDatePicker/BbDatePicker.vue.js +6 -0
  464. package/dist/components/BbDatePicker/BbDatePicker.vue_vue_type_script_setup_true_lang.js +319 -0
  465. package/dist/components/BbDatePicker/types.d.ts +159 -0
  466. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.d.ts +3 -1
  467. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.js +6 -0
  468. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +179 -0
  469. package/dist/components/BbDatePickerInput/types.d.ts +134 -16
  470. package/dist/components/BbDialog/BbDialog.vue.d.ts +14 -6
  471. package/dist/components/BbDialog/BbDialog.vue.js +6 -0
  472. package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +214 -0
  473. package/dist/components/BbDialog/types.d.ts +146 -1
  474. package/dist/components/BbDialog/useDialogStack.d.ts +5 -0
  475. package/dist/components/BbDialog/useDialogStack.js +20 -0
  476. package/dist/components/BbDropdown/AdaptiveDropdown.vue.d.ts +24 -0
  477. package/dist/components/BbDropdown/AdaptiveDropdown.vue.js +6 -0
  478. package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +116 -0
  479. package/dist/components/BbDropdown/BbDropdown.vue.d.ts +14 -7
  480. package/dist/components/BbDropdown/BbDropdown.vue.js +6 -0
  481. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +561 -0
  482. package/dist/components/BbDropdown/BbDropdownGroup.vue.d.ts +44 -0
  483. package/dist/components/BbDropdown/BbDropdownGroup.vue.js +5 -0
  484. package/dist/components/BbDropdown/BbDropdownGroup.vue_vue_type_script_setup_true_lang.js +36 -0
  485. package/dist/components/BbDropdown/BbDropdownList.vue.d.ts +31 -0
  486. package/dist/components/BbDropdown/BbDropdownList.vue.js +6 -0
  487. package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +470 -0
  488. package/dist/components/BbDropdown/DropdownPipelineResolver.vue.d.ts +16 -0
  489. package/dist/components/BbDropdown/DropdownPipelineResolver.vue.js +5 -0
  490. package/dist/components/BbDropdown/DropdownPipelineResolver.vue_vue_type_script_setup_true_lang.js +168 -0
  491. package/dist/components/BbDropdown/normalizeGroups.d.ts +18 -0
  492. package/dist/components/BbDropdown/normalizeGroups.js +46 -0
  493. package/dist/components/BbDropdown/types.d.ts +468 -46
  494. package/dist/components/BbDropdown/types.js +4 -0
  495. package/dist/components/BbDropdown/useDropdownContext.d.ts +89 -0
  496. package/dist/components/BbDropdown/useDropdownContext.js +9 -0
  497. package/dist/components/BbDropdown/useDropdownGroupRegistry.d.ts +108 -0
  498. package/dist/components/BbDropdown/useDropdownGroupRegistry.js +5 -0
  499. package/dist/components/BbDropdownButton/BbDropdownButton.vue.d.ts +3 -3
  500. package/dist/components/BbDropdownButton/BbDropdownButton.vue.js +6 -0
  501. package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +119 -0
  502. package/dist/components/BbDropdownButton/types.d.ts +47 -52
  503. package/dist/components/BbDropzone/BbDropzone.vue.d.ts +5 -4
  504. package/dist/components/BbDropzone/BbDropzone.vue.js +6 -0
  505. package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +308 -0
  506. package/dist/components/BbDropzone/types.d.ts +77 -8
  507. package/dist/components/BbIcon/BbIcon.vue.d.ts +3 -2
  508. package/dist/components/BbIcon/BbIcon.vue.js +6 -0
  509. package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +65 -0
  510. package/dist/components/BbIcon/types.d.ts +6 -19
  511. package/dist/components/BbIndicator/BbIndicator.vue.d.ts +14 -0
  512. package/dist/components/BbIndicator/BbIndicator.vue.js +6 -0
  513. package/dist/components/BbIndicator/BbIndicator.vue_vue_type_script_setup_true_lang.js +56 -0
  514. package/dist/components/BbIndicator/types.d.ts +49 -0
  515. package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +12 -6
  516. package/dist/components/BbNumberInput/BbNumberInput.vue.js +6 -0
  517. package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +194 -0
  518. package/dist/components/BbNumberInput/types.d.ts +40 -4
  519. package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +16 -10
  520. package/dist/components/BbOffCanvas/BbOffCanvas.vue.js +6 -0
  521. package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +284 -0
  522. package/dist/components/BbOffCanvas/types.d.ts +71 -39
  523. package/dist/components/BbOffCanvas/useOffCanvasStack.d.ts +25 -0
  524. package/dist/components/BbOffCanvas/useOffCanvasStack.js +68 -0
  525. package/dist/components/BbPagination/BbPagination.vue.d.ts +9 -4
  526. package/dist/components/BbPagination/BbPagination.vue.js +6 -0
  527. package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +244 -0
  528. package/dist/components/BbPagination/paginationFit.d.ts +38 -0
  529. package/dist/components/BbPagination/paginationFit.js +13 -0
  530. package/dist/components/BbPagination/types.d.ts +51 -16
  531. package/dist/components/BbPopover/BbPopover.vue.d.ts +16 -15
  532. package/dist/components/BbPopover/BbPopover.vue.js +6 -0
  533. package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +309 -0
  534. package/dist/components/BbPopover/escapeStack.d.ts +6 -0
  535. package/dist/components/BbPopover/escapeStack.js +10 -0
  536. package/dist/components/BbPopover/types.d.ts +90 -31
  537. package/dist/components/BbProgress/BbProgress.vue.d.ts +1 -0
  538. package/dist/components/BbProgress/BbProgress.vue.js +6 -0
  539. package/dist/components/BbProgress/BbProgress.vue_vue_type_script_setup_true_lang.js +36 -0
  540. package/dist/components/BbProgress/types.d.ts +15 -1
  541. package/dist/components/BbRadio/BbRadio.vue.d.ts +5 -3
  542. package/dist/components/BbRadio/BbRadio.vue.js +6 -0
  543. package/dist/components/BbRadio/BbRadio.vue_vue_type_script_setup_true_lang.js +133 -0
  544. package/dist/components/BbRadio/types.d.ts +32 -9
  545. package/dist/components/BbRadioGroup/BbRadioGroup.vue.d.ts +4 -14
  546. package/dist/components/BbRadioGroup/BbRadioGroup.vue.js +6 -0
  547. package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +186 -0
  548. package/dist/components/BbRadioGroup/types.d.ts +48 -22
  549. package/dist/components/BbRating/BbRating.vue.d.ts +4 -1
  550. package/dist/components/BbRating/BbRating.vue.js +6 -0
  551. package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +150 -0
  552. package/dist/components/BbRating/types.d.ts +44 -10
  553. package/dist/components/BbSelect/BbSelect.vue.d.ts +2 -11
  554. package/dist/components/BbSelect/BbSelect.vue.js +6 -0
  555. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +241 -0
  556. package/dist/components/BbSelect/types.d.ts +112 -59
  557. package/dist/components/BbSelectPopover/BbSelectPopover.vue.d.ts +1 -10
  558. package/dist/components/BbSelectPopover/BbSelectPopover.vue.js +6 -0
  559. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +639 -0
  560. package/dist/components/BbSelectPopover/types.d.ts +197 -105
  561. package/dist/components/BbSlider/BbSlider.vue.d.ts +3 -6
  562. package/dist/components/BbSlider/BbSlider.vue.js +6 -0
  563. package/dist/components/BbSlider/BbSlider.vue_vue_type_script_setup_true_lang.js +166 -0
  564. package/dist/components/BbSlider/types.d.ts +44 -13
  565. package/dist/components/BbSmoothHeight/BbSmoothHeight.vue.d.ts +3 -1
  566. package/dist/components/BbSmoothHeight/BbSmoothHeight.vue.js +6 -0
  567. package/dist/components/BbSmoothHeight/BbSmoothHeight.vue_vue_type_script_setup_true_lang.js +79 -0
  568. package/dist/components/BbSmoothHeight/types.d.ts +17 -2
  569. package/dist/components/BbSpinner/BbSpinner.vue.d.ts +1 -1
  570. package/dist/components/BbSpinner/BbSpinner.vue.js +6 -0
  571. package/dist/components/BbSpinner/BbSpinner.vue_vue_type_script_setup_true_lang.js +30 -0
  572. package/dist/components/BbSpinner/types.d.ts +10 -3
  573. package/dist/components/BbSwitch/BbSwitch.vue.d.ts +5 -3
  574. package/dist/components/BbSwitch/BbSwitch.vue.js +6 -0
  575. package/dist/components/BbSwitch/BbSwitch.vue_vue_type_script_setup_true_lang.js +142 -0
  576. package/dist/components/BbSwitch/types.d.ts +3 -1
  577. package/dist/components/BbSwitchGroup/BbSwitchGroup.vue.d.ts +4 -14
  578. package/dist/components/BbSwitchGroup/BbSwitchGroup.vue.js +6 -0
  579. package/dist/components/BbSwitchGroup/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +190 -0
  580. package/dist/components/BbSwitchGroup/types.d.ts +3 -22
  581. package/dist/components/BbTable/BbTable.vue.d.ts +58 -31
  582. package/dist/components/BbTable/BbTable.vue.js +6 -0
  583. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +900 -0
  584. package/dist/components/BbTable/types.d.ts +213 -37
  585. package/dist/components/BbTable/utils.d.ts +9 -1
  586. package/dist/components/BbTable/utils.js +53 -0
  587. package/dist/components/BbTabs/BbTabs.vue.d.ts +20 -0
  588. package/dist/components/BbTabs/BbTabs.vue.js +6 -0
  589. package/dist/components/BbTabs/BbTabs.vue_vue_type_script_setup_true_lang.js +64 -0
  590. package/dist/components/BbTabs/BbTabsList.vue.d.ts +19 -0
  591. package/dist/components/BbTabs/BbTabsList.vue.js +6 -0
  592. package/dist/components/BbTabs/BbTabsList.vue_vue_type_script_setup_true_lang.js +123 -0
  593. package/dist/components/BbTabs/BbTabsPanes.vue.d.ts +16 -0
  594. package/dist/components/BbTabs/BbTabsPanes.vue.js +6 -0
  595. package/dist/components/BbTabs/BbTabsPanes.vue_vue_type_script_setup_true_lang.js +35 -0
  596. package/dist/components/BbTabs/BbTabsRoot.vue.d.ts +20 -0
  597. package/dist/components/BbTabs/BbTabsRoot.vue.js +5 -0
  598. package/dist/components/BbTabs/BbTabsRoot.vue_vue_type_script_setup_true_lang.js +55 -0
  599. package/dist/components/BbTabs/types.d.ts +264 -0
  600. package/dist/components/BbTag/BbTag.vue.d.ts +19 -6
  601. package/dist/components/BbTag/BbTag.vue.js +6 -0
  602. package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +187 -0
  603. package/dist/components/BbTag/types.d.ts +55 -17
  604. package/dist/components/BbTextInput/BbTextInput.vue.d.ts +10 -1
  605. package/dist/components/BbTextInput/BbTextInput.vue.js +6 -0
  606. package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +195 -0
  607. package/dist/components/BbTextInput/types.d.ts +31 -4
  608. package/dist/components/BbTextarea/BbTextarea.vue.d.ts +10 -1
  609. package/dist/components/BbTextarea/BbTextarea.vue.js +6 -0
  610. package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +178 -0
  611. package/dist/components/BbTextarea/types.d.ts +33 -2
  612. package/dist/components/BbTimePicker/BbTimePicker.vue.d.ts +33 -0
  613. package/dist/components/BbTimePicker/BbTimePicker.vue.js +6 -0
  614. package/dist/components/BbTimePicker/BbTimePicker.vue_vue_type_script_setup_true_lang.js +244 -0
  615. package/dist/components/BbTimePicker/types.d.ts +133 -0
  616. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue.d.ts +25 -0
  617. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue.js +6 -0
  618. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +157 -0
  619. package/dist/components/BbTimePickerInput/types.d.ts +174 -0
  620. package/dist/components/BbToast/BbToast.vue.d.ts +2 -3
  621. package/dist/components/BbToast/BbToast.vue.js +6 -0
  622. package/dist/components/BbToast/BbToast.vue_vue_type_script_setup_true_lang.js +39 -0
  623. package/dist/components/BbToast/BbToastMessage.vue.d.ts +17 -8
  624. package/dist/components/BbToast/BbToastMessage.vue.js +5 -0
  625. package/dist/components/BbToast/BbToastMessage.vue_vue_type_script_setup_true_lang.js +170 -0
  626. package/dist/components/BbToast/BbToastPortal.vue.d.ts +30 -0
  627. package/dist/components/BbToast/BbToastPortal.vue.js +5 -0
  628. package/dist/components/BbToast/BbToastPortal.vue_vue_type_script_setup_true_lang.js +19 -0
  629. package/dist/components/BbToast/BbToastRegion.vue.d.ts +8 -0
  630. package/dist/components/BbToast/BbToastRegion.vue.js +5 -0
  631. package/dist/components/BbToast/BbToastRegion.vue_vue_type_script_setup_true_lang.js +100 -0
  632. package/dist/components/BbToast/types.d.ts +69 -5
  633. package/dist/components/BbTooltip/BbTooltip.vue.d.ts +10 -3
  634. package/dist/components/BbTooltip/BbTooltip.vue.js +6 -0
  635. package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +160 -0
  636. package/dist/components/BbTooltip/types.d.ts +69 -11
  637. package/dist/components/BbTree/BbTree.vue.d.ts +28 -13
  638. package/dist/components/BbTree/BbTree.vue.js +6 -0
  639. package/dist/components/BbTree/BbTree.vue_vue_type_script_setup_true_lang.js +55 -0
  640. package/dist/components/BbTree/BbTreeLevel.vue.d.ts +19 -0
  641. package/dist/components/BbTree/BbTreeLevel.vue.js +5 -0
  642. package/dist/components/BbTree/BbTreeLevel.vue_vue_type_script_setup_true_lang.js +94 -0
  643. package/dist/components/BbTree/context.d.ts +17 -0
  644. package/dist/components/BbTree/context.js +4 -0
  645. package/dist/components/BbTree/types.d.ts +94 -41
  646. package/dist/components/ChipsBox/ChipsBox.vue.d.ts +2 -1
  647. package/dist/components/ChipsBox/ChipsBox.vue.js +5 -0
  648. package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +98 -0
  649. package/dist/components/ChipsBox/types.d.ts +12 -5
  650. package/dist/components/ClearableButton/ClearableButton.vue.d.ts +2 -1
  651. package/dist/components/ClearableButton/ClearableButton.vue.js +6 -0
  652. package/dist/components/ClearableButton/ClearableButton.vue_vue_type_script_setup_true_lang.js +19 -0
  653. package/dist/components/ClearableButton/types.d.ts +7 -1
  654. package/dist/components/CloseIcon/CloseButton.vue.d.ts +5 -0
  655. package/dist/components/CloseIcon/CloseButton.vue.js +7 -0
  656. package/dist/components/CloseIcon/CloseButton.vue_vue_type_script_setup_true_lang.js +23 -0
  657. package/dist/components/CloseIcon/types.d.ts +2 -0
  658. package/dist/components/CommaBox/CommaBox.vue.d.ts +2 -1
  659. package/dist/components/CommaBox/CommaBox.vue.js +6 -0
  660. package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +81 -0
  661. package/dist/components/CommaBox/types.d.ts +6 -7
  662. package/dist/components/CommonInputWrapper/CommonInputWrapper.vue.d.ts +19 -0
  663. package/dist/components/CommonInputWrapper/CommonInputWrapper.vue.js +6 -0
  664. package/dist/components/CommonInputWrapper/CommonInputWrapper.vue_vue_type_script_setup_true_lang.js +107 -0
  665. package/dist/components/CommonInputWrapper/types.d.ts +96 -0
  666. package/dist/components/CommonPopover/CommonPopover.vue.d.ts +9 -3
  667. package/dist/components/CommonPopover/CommonPopover.vue.js +6 -0
  668. package/dist/components/CommonPopover/CommonPopover.vue_vue_type_script_setup_true_lang.js +160 -0
  669. package/dist/components/CommonPopover/types.d.ts +65 -16
  670. package/dist/components/ErrorIcon/ErrorIcon.vue.d.ts +2 -0
  671. package/dist/components/ErrorIcon/ErrorIcon.vue.js +6 -0
  672. package/dist/components/ErrorIcon/ErrorIcon.vue_vue_type_script_setup_true_lang.js +16 -0
  673. package/dist/components/FlatListBox/FlatListBox.vue.d.ts +2 -5
  674. package/dist/components/FlatListBox/FlatListBox.vue.js +5 -0
  675. package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +167 -0
  676. package/dist/components/FlatListBox/types.d.ts +1 -1
  677. package/dist/components/GroupedListBox/GroupedListBox.vue.d.ts +2 -5
  678. package/dist/components/GroupedListBox/GroupedListBox.vue.js +5 -0
  679. package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +218 -0
  680. package/dist/components/ListBox/ListBox.vue.d.ts +3 -6
  681. package/dist/components/ListBox/ListBox.vue.js +5 -0
  682. package/dist/components/ListBox/ListBox.vue_vue_type_script_setup_true_lang.js +112 -0
  683. package/dist/components/ListBox/types.d.ts +1 -1
  684. package/dist/components/OptionsContainer/OptionsContainer.vue.d.ts +1 -0
  685. package/dist/components/OptionsContainer/OptionsContainer.vue.js +5 -0
  686. package/dist/components/OptionsContainer/OptionsContainer.vue_vue_type_script_setup_true_lang.js +52 -0
  687. package/dist/components/OptionsContainer/types.d.ts +14 -24
  688. package/dist/components/Transitions/ScaleFade.vue.d.ts +2 -2
  689. package/dist/components/Transitions/ScaleFade.vue.js +5 -0
  690. package/dist/components/Transitions/ScaleFade.vue_vue_type_script_setup_true_lang.js +47 -0
  691. package/dist/components/Transitions/Slide.vue.d.ts +15 -0
  692. package/dist/components/Transitions/Slide.vue.js +7 -0
  693. package/dist/components/Transitions/Slide.vue_vue_type_script_setup_true_lang.js +28 -0
  694. package/dist/components/WarningIcon/WarningIcon.vue.d.ts +5 -0
  695. package/dist/components/WarningIcon/WarningIcon.vue.js +6 -0
  696. package/dist/components/WarningIcon/WarningIcon.vue_vue_type_script_setup_true_lang.js +16 -0
  697. package/dist/components/WarningIcon/types.d.ts +3 -0
  698. package/dist/composables/__testResets.d.ts +1 -0
  699. package/dist/composables/useActiveInactiveState.d.ts +4 -0
  700. package/dist/composables/useActiveInactiveState.js +21 -0
  701. package/dist/composables/useBaseOptions.d.ts +3 -3
  702. package/dist/composables/useBaseOptions.js +31 -0
  703. package/dist/composables/useBbConfig.d.ts +8 -37
  704. package/dist/composables/useBbConfig.js +5 -0
  705. package/dist/composables/useBbDropdownContext.d.ts +89 -0
  706. package/dist/composables/useBbDropdownContext.js +46 -0
  707. package/dist/composables/useBbFormContext.d.ts +90 -0
  708. package/dist/composables/useBbFormContext.js +69 -0
  709. package/dist/composables/useBbTableContext.d.ts +195 -0
  710. package/dist/composables/useBbTableContext.js +208 -0
  711. package/dist/composables/useBbTabsContext.d.ts +111 -0
  712. package/dist/composables/useBbTabsContext.js +225 -0
  713. package/dist/composables/useBreakpoint.d.ts +13 -0
  714. package/dist/composables/useBreakpoint.js +39 -0
  715. package/dist/composables/useBroadcastChannelInstance.d.ts +1 -3
  716. package/dist/composables/useBroadcastChannelInstance.js +39 -0
  717. package/dist/composables/useCancelableFunction.js +37 -0
  718. package/dist/composables/useClosingInert.d.ts +28 -0
  719. package/dist/composables/useClosingInert.js +23 -0
  720. package/dist/composables/useCoherence.js +16 -0
  721. package/dist/composables/useConfirm.d.ts +278 -68
  722. package/dist/composables/useConfirm.js +130 -0
  723. package/dist/composables/useCountdown.js +42 -0
  724. package/dist/composables/useHashedWatcher.d.ts +17 -0
  725. package/dist/composables/useHashedWatcher.js +14 -0
  726. package/dist/composables/useId.d.ts +3 -2
  727. package/dist/composables/useIndexById.js +16 -0
  728. package/dist/composables/useInertiaPage.d.ts +10 -0
  729. package/dist/composables/useInertiaPage.js +8 -0
  730. package/dist/composables/useItemText.js +17 -0
  731. package/dist/composables/useItemValue.js +9 -0
  732. package/dist/composables/useItemsGetter.d.ts +1 -1
  733. package/dist/composables/useItemsGetter.js +30 -0
  734. package/dist/composables/useListboxFocus.js +66 -0
  735. package/dist/composables/useLocale.d.ts +1 -1
  736. package/dist/composables/useLocale.js +11 -0
  737. package/dist/composables/useLogger.d.ts +1 -1
  738. package/dist/composables/useLogger.js +10 -0
  739. package/dist/composables/useMinimalQueue.d.ts +1 -1
  740. package/dist/composables/useMobile.js +7 -0
  741. package/dist/composables/useOnFalsy.d.ts +2 -0
  742. package/dist/composables/useOnFalsy.js +9 -0
  743. package/dist/composables/useOnTruthy.d.ts +7 -0
  744. package/dist/composables/useOnTruthy.js +9 -0
  745. package/dist/composables/usePrefill.d.ts +2 -1
  746. package/dist/composables/usePrefill.js +24 -0
  747. package/dist/composables/useQuery.d.ts +15 -3
  748. package/dist/composables/useQuery.js +42 -0
  749. package/dist/composables/useRoute.js +11 -0
  750. package/dist/composables/useRouter.js +8 -0
  751. package/dist/composables/useSafeArea.d.ts +51 -0
  752. package/dist/composables/useSafeArea.js +16 -0
  753. package/dist/composables/useSegmentedFields.d.ts +104 -0
  754. package/dist/composables/useSegmentedFields.js +159 -0
  755. package/dist/composables/useTableWidthContext.d.ts +5 -0
  756. package/dist/composables/useTableWidthContext.js +42 -0
  757. package/dist/composables/useToast.d.ts +40 -70
  758. package/dist/composables/useToast.js +58 -0
  759. package/dist/composables/useTopLayer.d.ts +9 -0
  760. package/dist/composables/useTopLayer.js +7 -0
  761. package/dist/composables/useUntil.js +13 -0
  762. package/dist/composables/useViewportWidth.d.ts +17 -0
  763. package/dist/composables/useViewportWidth.js +6 -0
  764. package/dist/confirm-variants.d.ts +17 -0
  765. package/dist/deprecation/ai-deprecations.json.d.ts +784 -0
  766. package/dist/deprecation/ai-deprecations.json.js +4 -0
  767. package/dist/deprecation/removedProps.d.ts +3 -0
  768. package/dist/deprecation/removedProps.js +13 -0
  769. package/dist/directives/bbColor.d.ts +65 -0
  770. package/dist/directives/bbColor.js +24 -0
  771. package/dist/directives/bbDate.d.ts +66 -0
  772. package/dist/directives/bbDate.js +24 -0
  773. package/dist/directives/bbDropdown.d.ts +15 -11
  774. package/dist/directives/bbDropdown.js +23 -0
  775. package/dist/directives/bbTime.d.ts +64 -0
  776. package/dist/directives/bbTime.js +24 -0
  777. package/dist/directives/bbTooltip.d.ts +16 -11
  778. package/dist/directives/bbTooltip.js +24 -0
  779. package/dist/directives/createPopoverDirective.js +54 -0
  780. package/dist/dropdown-item-variants.d.ts +17 -0
  781. package/dist/i18n/index.d.ts +51 -3
  782. package/dist/i18n/index.js +27 -0
  783. package/dist/i18n/locales/cs.json +115 -0
  784. package/dist/i18n/locales/da.json +115 -0
  785. package/dist/i18n/locales/de.json +115 -0
  786. package/dist/i18n/locales/el.json +115 -0
  787. package/dist/i18n/locales/en.json +115 -0
  788. package/dist/i18n/locales/es.json +115 -0
  789. package/dist/i18n/locales/fi.json +115 -0
  790. package/dist/i18n/locales/fr.json +115 -0
  791. package/dist/i18n/locales/hu.json +115 -0
  792. package/dist/i18n/locales/it.json +115 -0
  793. package/dist/i18n/locales/ja.json +115 -0
  794. package/dist/i18n/locales/ko.json +115 -0
  795. package/dist/i18n/locales/nb.json +115 -0
  796. package/dist/i18n/locales/nl.json +115 -0
  797. package/dist/i18n/locales/pl.json +115 -0
  798. package/dist/i18n/locales/pt.json +115 -0
  799. package/dist/i18n/locales/ro.json +115 -0
  800. package/dist/i18n/locales/ru.json +115 -0
  801. package/dist/i18n/locales/sv.json +115 -0
  802. package/dist/i18n/locales/tr.json +115 -0
  803. package/dist/i18n/locales/uk.json +115 -0
  804. package/dist/i18n/locales/zh-cn.json +115 -0
  805. package/dist/icons-whitelist.d.ts +8 -0
  806. package/dist/index.d.ts +96 -79
  807. package/dist/index.js +76 -82
  808. package/dist/indicator-variants.d.ts +20 -0
  809. package/dist/llms-full.txt +79703 -0
  810. package/dist/llms-medium.txt +1514 -0
  811. package/dist/llms.txt +105 -0
  812. package/dist/locale-blueprint.json +141 -0
  813. package/dist/locale-registry.d.ts +37 -0
  814. package/dist/nuxt-module.d.ts +3 -0
  815. package/dist/nuxt.js +19 -0
  816. package/dist/plugin.d.ts +4 -0
  817. package/dist/plugin.js +22 -0
  818. package/dist/reset.css +1 -0
  819. package/dist/reset.d.ts +0 -0
  820. package/dist/runtime/nuxt-plugin.d.ts +2 -0
  821. package/dist/runtime/nuxt-plugin.js +24 -0
  822. package/dist/styles.css +2 -0
  823. package/dist/styles.d.ts +0 -0
  824. package/dist/toast-variants.d.ts +20 -0
  825. package/dist/tooltip-variants.d.ts +17 -0
  826. package/dist/types/ActionButton.d.ts +32 -0
  827. package/dist/types/AlertVariant.d.ts +16 -0
  828. package/dist/types/BadgeVariant.d.ts +16 -0
  829. package/dist/types/ButtonVariant.d.ts +17 -0
  830. package/dist/types/Classes.d.ts +1 -1
  831. package/dist/types/ColumnName.d.ts +1 -1
  832. package/dist/types/CommonProps.d.ts +68 -277
  833. package/dist/types/Config.d.ts +84 -0
  834. package/dist/types/ConfirmVariant.d.ts +15 -0
  835. package/dist/types/DropdownItemVariant.d.ts +18 -0
  836. package/dist/types/Icon.d.ts +5 -7
  837. package/dist/types/IndicatorVariant.d.ts +16 -0
  838. package/dist/types/InputDirection.d.ts +15 -0
  839. package/dist/types/ItemAccessor.d.ts +21 -0
  840. package/dist/types/Placement.d.ts +6 -0
  841. package/dist/types/ResponsiveSize.d.ts +20 -0
  842. package/dist/types/SizeScale.d.ts +8 -0
  843. package/dist/types/ToastVariant.d.ts +17 -0
  844. package/dist/types/TooltipVariant.d.ts +18 -0
  845. package/dist/utilities/functions/bytesToUnit.d.ts +2 -0
  846. package/dist/utilities/functions/bytesToUnit.js +18 -0
  847. package/dist/utilities/functions/clampPrecision.js +10 -0
  848. package/dist/utilities/functions/convertDateValue.d.ts +23 -10
  849. package/dist/utilities/functions/convertDateValue.js +12 -0
  850. package/dist/utilities/functions/deburr.js +5 -0
  851. package/dist/utilities/functions/empty.js +5 -0
  852. package/dist/utilities/functions/flattenTree.d.ts +1 -1
  853. package/dist/utilities/functions/hash.js +5 -0
  854. package/dist/utilities/functions/isDateDisabled.d.ts +1 -0
  855. package/dist/utilities/functions/isDateDisabled.js +20 -0
  856. package/dist/utilities/functions/isEqual.js +5 -0
  857. package/dist/utilities/functions/isNotEmpty.js +5 -0
  858. package/dist/utilities/functions/matchAnyKey.d.ts +1 -1
  859. package/dist/utilities/functions/matchAnyKey.js +15 -0
  860. package/dist/utilities/functions/parseSize.d.ts +1 -1
  861. package/dist/utilities/functions/parseSize.js +15 -0
  862. package/dist/utilities/functions/percentage.js +11 -0
  863. package/dist/utilities/functions/pipe.d.ts +1 -1
  864. package/dist/utilities/functions/reject.d.ts +25 -0
  865. package/dist/utilities/functions/reject.js +28 -0
  866. package/dist/utilities/functions/resolveEnumProp.d.ts +19 -0
  867. package/dist/utilities/functions/resolveEnumProp.js +5 -0
  868. package/dist/utilities/functions/slotKey.d.ts +13 -0
  869. package/dist/utilities/functions/slotKey.js +4 -0
  870. package/dist/utilities/functions/sort.d.ts +2 -2
  871. package/dist/utilities/functions/sort.js +19 -0
  872. package/dist/utilities/functions/throttle.js +5 -0
  873. package/dist/utilities/functions/waitFor.d.ts +9 -2
  874. package/dist/utilities/functions/waitFor.js +14 -0
  875. package/dist/utilities/functions/warnRemovedAttrs.d.ts +11 -0
  876. package/dist/utilities/functions/warnRemovedAttrs.js +20 -0
  877. package/dist/utilities/functions/when.js +5 -0
  878. package/dist/utilities/misc/iconsInjectionKey.d.ts +15 -0
  879. package/dist/utilities/misc/iconsInjectionKey.js +4 -0
  880. package/dist/utilities/misc/transitions.d.ts +5 -0
  881. package/dist/utilities/misc/transitions.js +8 -0
  882. package/dist/utils/injectLibraryStyles.d.ts +6 -0
  883. package/dist/utils/injectLibraryStyles.js +38 -0
  884. package/dist/validated/BbCheckbox.vue.d.ts +29 -0
  885. package/dist/validated/BbCheckbox.vue.js +5 -0
  886. package/dist/validated/BbCheckbox.vue_vue_type_script_setup_true_lang.js +75 -0
  887. package/dist/validated/BbCheckboxGroup.vue.d.ts +34 -0
  888. package/dist/validated/BbCheckboxGroup.vue.js +5 -0
  889. package/dist/validated/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +82 -0
  890. package/dist/validated/BbColorInput.vue.d.ts +36 -0
  891. package/dist/validated/BbColorInput.vue.js +5 -0
  892. package/dist/validated/BbColorInput.vue_vue_type_script_setup_true_lang.js +71 -0
  893. package/dist/validated/BbDatePickerInput.vue.d.ts +41 -0
  894. package/dist/validated/BbDatePickerInput.vue.js +5 -0
  895. package/dist/validated/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +90 -0
  896. package/dist/validated/BbDropzone.vue.d.ts +24 -0
  897. package/dist/validated/BbDropzone.vue.js +5 -0
  898. package/dist/validated/BbDropzone.vue_vue_type_script_setup_true_lang.js +45 -0
  899. package/dist/validated/BbForm.vue.d.ts +28 -0
  900. package/dist/validated/BbForm.vue.js +5 -0
  901. package/dist/validated/BbForm.vue_vue_type_script_setup_true_lang.js +42 -0
  902. package/dist/validated/BbNumberInput.vue.d.ts +36 -0
  903. package/dist/validated/BbNumberInput.vue.js +5 -0
  904. package/dist/validated/BbNumberInput.vue_vue_type_script_setup_true_lang.js +72 -0
  905. package/dist/validated/BbRadioGroup.vue.d.ts +34 -0
  906. package/dist/validated/BbRadioGroup.vue.js +5 -0
  907. package/dist/validated/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +78 -0
  908. package/dist/validated/BbRating.vue.d.ts +30 -0
  909. package/dist/validated/BbRating.vue.js +5 -0
  910. package/dist/validated/BbRating.vue_vue_type_script_setup_true_lang.js +63 -0
  911. package/dist/validated/BbSelect.vue.d.ts +46 -0
  912. package/dist/validated/BbSelect.vue.js +5 -0
  913. package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +109 -0
  914. package/dist/validated/BbSlider.vue.d.ts +36 -0
  915. package/dist/validated/BbSlider.vue.js +5 -0
  916. package/dist/validated/BbSlider.vue_vue_type_script_setup_true_lang.js +69 -0
  917. package/dist/validated/BbSwitch.vue.d.ts +29 -0
  918. package/dist/validated/BbSwitch.vue.js +5 -0
  919. package/dist/validated/BbSwitch.vue_vue_type_script_setup_true_lang.js +75 -0
  920. package/dist/validated/BbSwitchGroup.vue.d.ts +34 -0
  921. package/dist/validated/BbSwitchGroup.vue.js +5 -0
  922. package/dist/validated/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +82 -0
  923. package/dist/validated/BbTag.vue.d.ts +36 -0
  924. package/dist/validated/BbTag.vue.js +5 -0
  925. package/dist/validated/BbTag.vue_vue_type_script_setup_true_lang.js +72 -0
  926. package/dist/validated/BbTextInput.vue.d.ts +36 -0
  927. package/dist/validated/BbTextInput.vue.js +5 -0
  928. package/dist/validated/BbTextInput.vue_vue_type_script_setup_true_lang.js +72 -0
  929. package/dist/validated/BbTextarea.vue.d.ts +36 -0
  930. package/dist/validated/BbTextarea.vue.js +5 -0
  931. package/dist/validated/BbTextarea.vue_vue_type_script_setup_true_lang.js +70 -0
  932. package/dist/validated/BbTimePickerInput.vue.d.ts +37 -0
  933. package/dist/validated/BbTimePickerInput.vue.js +5 -0
  934. package/dist/validated/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +81 -0
  935. package/dist/validated/index.d.ts +29 -0
  936. package/dist/validated/useValidatedField.d.ts +93 -0
  937. package/dist/validated/useValidatedField.js +51 -0
  938. package/dist/validated.js +21 -0
  939. package/dist/vite-plugin.d.ts +261 -0
  940. package/dist/vite.js +603 -0
  941. package/llms.txt +107 -0
  942. package/package.json +181 -60
  943. package/scripts/lib/__fixtures__/validated-manifest.json +65 -0
  944. package/scripts/lib/ensure-manifest.mjs +39 -0
  945. package/scripts/lib/eslint-plugin-autofix.node.test.mjs +109 -0
  946. package/scripts/lib/eslint-plugin-recommended.node.test.mjs +87 -0
  947. package/scripts/lib/eslint-plugin-slots.node.test.mjs +271 -0
  948. package/scripts/lib/eslint-plugin.mjs +702 -0
  949. package/scripts/lib/eslint-plugin.node.test.mjs +193 -0
  950. package/scripts/lib/html-attributes.mjs +191 -0
  951. package/scripts/lib/mcp-config.mjs +126 -0
  952. package/scripts/lib/migrate-fix.mjs +115 -0
  953. package/scripts/lib/validate-bb-markup.mjs +488 -0
  954. package/dist/ai/BaseButton.md +0 -449
  955. package/dist/ai/BaseCheckbox.md +0 -512
  956. package/dist/ai/BaseCheckboxGroup.md +0 -597
  957. package/dist/ai/BaseColorInput.md +0 -461
  958. package/dist/ai/BaseDatePicker.md +0 -739
  959. package/dist/ai/BaseDatePickerInput.md +0 -1517
  960. package/dist/ai/BaseDialog.md +0 -610
  961. package/dist/ai/BaseInputContainer.md +0 -570
  962. package/dist/ai/BaseNumberInput.md +0 -509
  963. package/dist/ai/BaseRadio.md +0 -405
  964. package/dist/ai/BaseRadioGroup.md +0 -535
  965. package/dist/ai/BaseRating.md +0 -489
  966. package/dist/ai/BaseSelect.md +0 -1742
  967. package/dist/ai/BaseSlider.md +0 -871
  968. package/dist/ai/BaseSwitch.md +0 -322
  969. package/dist/ai/BaseSwitchGroup.md +0 -298
  970. package/dist/ai/BaseTag.md +0 -623
  971. package/dist/ai/BaseTextInput.md +0 -392
  972. package/dist/ai/BaseTextarea.md +0 -398
  973. package/dist/ai/BbChip.md +0 -147
  974. package/dist/ai/BbIntersection.md +0 -120
  975. package/dist/ai/BbRatio.md +0 -61
  976. package/dist/ai/BbRows.md +0 -307
  977. package/dist/ai/BbTab.md +0 -950
  978. package/dist/ai/CommonInputInnerContainer.md +0 -414
  979. package/dist/ai/CommonInputOuterContainer.md +0 -55
  980. package/dist/components/BaseButton/BaseButton.vue.d.ts +0 -14
  981. package/dist/components/BaseButton/types.d.ts +0 -159
  982. package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +0 -39
  983. package/dist/components/BaseCheckbox/types.d.ts +0 -213
  984. package/dist/components/BaseCheckboxGroup/BaseCheckboxGroup.vue.d.ts +0 -30
  985. package/dist/components/BaseCheckboxGroup/types.d.ts +0 -282
  986. package/dist/components/BaseColorInput/BaseColorInput.vue.d.ts +0 -35
  987. package/dist/components/BaseColorInput/ColorPalette.vue.d.ts +0 -13
  988. package/dist/components/BaseColorInput/types.d.ts +0 -233
  989. package/dist/components/BaseDatePicker/BaseDatePickerHeader.vue.d.ts +0 -15
  990. package/dist/components/BaseDatePicker/BaseDatePickerInputDaySelector.vue.d.ts +0 -58
  991. package/dist/components/BaseDatePicker/BaseDatePickerMonthSelector.vue.d.ts +0 -12
  992. package/dist/components/BaseDatePicker/BaseDatePickerYearSelector.vue.d.ts +0 -12
  993. package/dist/components/BaseDatePicker/types.d.ts +0 -100
  994. package/dist/components/BaseDatePickerInput/BaseDatePickerInput.vue.d.ts +0 -26
  995. package/dist/components/BaseDatePickerInput/types.d.ts +0 -206
  996. package/dist/components/BaseDialog/BaseDialog.vue.d.ts +0 -37
  997. package/dist/components/BaseDialog/types.d.ts +0 -180
  998. package/dist/components/BaseInputContainer/BaseInputContainer.vue.d.ts +0 -18
  999. package/dist/components/BaseInputContainer/types.d.ts +0 -126
  1000. package/dist/components/BaseNumberInput/BaseNumberInput.vue.d.ts +0 -46
  1001. package/dist/components/BaseNumberInput/types.d.ts +0 -191
  1002. package/dist/components/BaseRadio/BaseRadio.vue.d.ts +0 -31
  1003. package/dist/components/BaseRadio/types.d.ts +0 -173
  1004. package/dist/components/BaseRadioGroup/BaseRadioGroup.vue.d.ts +0 -30
  1005. package/dist/components/BaseRadioGroup/types.d.ts +0 -240
  1006. package/dist/components/BaseRating/BaseRating.vue.d.ts +0 -35
  1007. package/dist/components/BaseRating/types.d.ts +0 -144
  1008. package/dist/components/BaseSelect/BaseSelect.vue.d.ts +0 -28
  1009. package/dist/components/BaseSelect/types.d.ts +0 -470
  1010. package/dist/components/BaseSlider/BaseSlider.vue.d.ts +0 -38
  1011. package/dist/components/BaseSlider/types.d.ts +0 -201
  1012. package/dist/components/BaseSwitch/BaseSwitch.vue.d.ts +0 -34
  1013. package/dist/components/BaseSwitch/types.d.ts +0 -25
  1014. package/dist/components/BaseSwitchGroup/BaseSwitchGroup.vue.d.ts +0 -30
  1015. package/dist/components/BaseSwitchGroup/types.d.ts +0 -8
  1016. package/dist/components/BaseTag/BaseTag.vue.d.ts +0 -37
  1017. package/dist/components/BaseTag/types.d.ts +0 -136
  1018. package/dist/components/BaseTextInput/BaseTextInput.vue.d.ts +0 -33
  1019. package/dist/components/BaseTextInput/types.d.ts +0 -166
  1020. package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +0 -31
  1021. package/dist/components/BaseTextarea/types.d.ts +0 -151
  1022. package/dist/components/BbChip/BbChip.vue.d.ts +0 -17
  1023. package/dist/components/BbChip/types.d.ts +0 -23
  1024. package/dist/components/BbIntersection/BbIntersection.vue.d.ts +0 -23
  1025. package/dist/components/BbIntersection/types.d.ts +0 -36
  1026. package/dist/components/BbRatio/BbRatio.vue.d.ts +0 -14
  1027. package/dist/components/BbRatio/types.d.ts +0 -15
  1028. package/dist/components/BbRows/BbRows.vue.d.ts +0 -62
  1029. package/dist/components/BbRows/types.d.ts +0 -21
  1030. package/dist/components/BbTab/BbTab.vue.d.ts +0 -27
  1031. package/dist/components/BbTab/types.d.ts +0 -186
  1032. package/dist/components/CommonInputInnerContainer/CommonInputInnerContainer.vue.d.ts +0 -15
  1033. package/dist/components/CommonInputInnerContainer/types.d.ts +0 -47
  1034. package/dist/components/CommonInputOuterContainer/CommonInputOuterContainer.vue.d.ts +0 -11
  1035. package/dist/components/CommonInputOuterContainer/types.d.ts +0 -16
  1036. package/dist/composables/useModalsState.d.ts +0 -5
  1037. package/dist/composables/useQueue.d.ts +0 -58
  1038. package/dist/composables/useWizard.d.ts +0 -85
  1039. package/dist/i18n/en.d.ts +0 -67
  1040. package/dist/i18n/it.d.ts +0 -67
  1041. package/dist/index.css +0 -2
  1042. package/dist/index100.js +0 -6
  1043. package/dist/index101.js +0 -300
  1044. package/dist/index102.js +0 -16
  1045. package/dist/index103.js +0 -16
  1046. package/dist/index104.js +0 -27
  1047. package/dist/index109.js +0 -19
  1048. package/dist/index11.js +0 -42
  1049. package/dist/index110.js +0 -12
  1050. package/dist/index111.js +0 -19
  1051. package/dist/index112.js +0 -25
  1052. package/dist/index114.js +0 -7
  1053. package/dist/index115.js +0 -241
  1054. package/dist/index116.js +0 -5
  1055. package/dist/index117.js +0 -42
  1056. package/dist/index118.js +0 -5
  1057. package/dist/index119.js +0 -53
  1058. package/dist/index120.js +0 -5
  1059. package/dist/index121.js +0 -32
  1060. package/dist/index122.js +0 -5
  1061. package/dist/index124.js +0 -52
  1062. package/dist/index125.js +0 -5
  1063. package/dist/index126.js +0 -223
  1064. package/dist/index128.js +0 -6
  1065. package/dist/index129.js +0 -162
  1066. package/dist/index13.js +0 -92
  1067. package/dist/index130.js +0 -25
  1068. package/dist/index131.js +0 -51
  1069. package/dist/index132.js +0 -5
  1070. package/dist/index133.js +0 -607
  1071. package/dist/index135.js +0 -6
  1072. package/dist/index136.js +0 -14
  1073. package/dist/index137.js +0 -153
  1074. package/dist/index139.js +0 -6
  1075. package/dist/index14.js +0 -306
  1076. package/dist/index140.js +0 -224
  1077. package/dist/index141.js +0 -5
  1078. package/dist/index143.js +0 -128
  1079. package/dist/index145.js +0 -6
  1080. package/dist/index146.js +0 -8
  1081. package/dist/index147.js +0 -182
  1082. package/dist/index149.js +0 -6
  1083. package/dist/index15.js +0 -10
  1084. package/dist/index151.js +0 -4
  1085. package/dist/index152.js +0 -95
  1086. package/dist/index154.js +0 -6
  1087. package/dist/index155.js +0 -168
  1088. package/dist/index157.js +0 -6
  1089. package/dist/index158.js +0 -145
  1090. package/dist/index16.js +0 -59
  1091. package/dist/index160.js +0 -6
  1092. package/dist/index161.js +0 -53
  1093. package/dist/index163.js +0 -6
  1094. package/dist/index164.js +0 -16
  1095. package/dist/index166.js +0 -35
  1096. package/dist/index168.js +0 -6
  1097. package/dist/index169.js +0 -89
  1098. package/dist/index171.js +0 -6
  1099. package/dist/index172.js +0 -82
  1100. package/dist/index174.js +0 -6
  1101. package/dist/index175.js +0 -38
  1102. package/dist/index176.js +0 -461
  1103. package/dist/index177.js +0 -28
  1104. package/dist/index178.js +0 -66
  1105. package/dist/index18.js +0 -35
  1106. package/dist/index180.js +0 -162
  1107. package/dist/index181.js +0 -5
  1108. package/dist/index182.js +0 -217
  1109. package/dist/index183.js +0 -5
  1110. package/dist/index184.js +0 -112
  1111. package/dist/index185.js +0 -5
  1112. package/dist/index186.js +0 -459
  1113. package/dist/index188.js +0 -6
  1114. package/dist/index19.js +0 -4
  1115. package/dist/index192.js +0 -268
  1116. package/dist/index194.js +0 -6
  1117. package/dist/index195.js +0 -124
  1118. package/dist/index197.js +0 -6
  1119. package/dist/index198.js +0 -174
  1120. package/dist/index2.js +0 -20
  1121. package/dist/index20.js +0 -9
  1122. package/dist/index200.js +0 -6
  1123. package/dist/index201.js +0 -224
  1124. package/dist/index203.js +0 -6
  1125. package/dist/index204.js +0 -135
  1126. package/dist/index206.js +0 -6
  1127. package/dist/index207.js +0 -5
  1128. package/dist/index208.js +0 -170
  1129. package/dist/index21.js +0 -35
  1130. package/dist/index210.js +0 -6
  1131. package/dist/index211.js +0 -37
  1132. package/dist/index213.js +0 -6
  1133. package/dist/index214.js +0 -63
  1134. package/dist/index216.js +0 -6
  1135. package/dist/index217.js +0 -74
  1136. package/dist/index219.js +0 -6
  1137. package/dist/index22.js +0 -6
  1138. package/dist/index220.js +0 -65
  1139. package/dist/index222.js +0 -6
  1140. package/dist/index223.js +0 -67
  1141. package/dist/index225.js +0 -6
  1142. package/dist/index226.js +0 -41
  1143. package/dist/index228.js +0 -6
  1144. package/dist/index229.js +0 -10
  1145. package/dist/index23.js +0 -7
  1146. package/dist/index230.js +0 -22
  1147. package/dist/index232.js +0 -8
  1148. package/dist/index233.js +0 -297
  1149. package/dist/index235.js +0 -6
  1150. package/dist/index236.js +0 -47
  1151. package/dist/index237.js +0 -165
  1152. package/dist/index239.js +0 -6
  1153. package/dist/index24.js +0 -9
  1154. package/dist/index241.js +0 -159
  1155. package/dist/index243.js +0 -6
  1156. package/dist/index244.js +0 -134
  1157. package/dist/index246.js +0 -6
  1158. package/dist/index247.js +0 -167
  1159. package/dist/index249.js +0 -6
  1160. package/dist/index25.js +0 -24
  1161. package/dist/index250.js +0 -147
  1162. package/dist/index252.js +0 -6
  1163. package/dist/index253.js +0 -35
  1164. package/dist/index255.js +0 -6
  1165. package/dist/index256.js +0 -65
  1166. package/dist/index258.js +0 -6
  1167. package/dist/index259.js +0 -19
  1168. package/dist/index260.js +0 -5
  1169. package/dist/index261.js +0 -160
  1170. package/dist/index263.js +0 -6
  1171. package/dist/index264.js +0 -60
  1172. package/dist/index266.js +0 -6
  1173. package/dist/index267.js +0 -134
  1174. package/dist/index269.js +0 -6
  1175. package/dist/index27.js +0 -4
  1176. package/dist/index271.js +0 -206
  1177. package/dist/index273.js +0 -6
  1178. package/dist/index274.js +0 -85
  1179. package/dist/index275.js +0 -5
  1180. package/dist/index276.js +0 -55
  1181. package/dist/index277.js +0 -5
  1182. package/dist/index278.js +0 -164
  1183. package/dist/index28.js +0 -8
  1184. package/dist/index280.js +0 -6
  1185. package/dist/index281.js +0 -145
  1186. package/dist/index283.js +0 -6
  1187. package/dist/index284.js +0 -11
  1188. package/dist/index285.js +0 -200
  1189. package/dist/index287.js +0 -6
  1190. package/dist/index288.js +0 -241
  1191. package/dist/index29.js +0 -7
  1192. package/dist/index290.js +0 -6
  1193. package/dist/index291.js +0 -21
  1194. package/dist/index293.js +0 -6
  1195. package/dist/index294.js +0 -125
  1196. package/dist/index296.js +0 -6
  1197. package/dist/index297.js +0 -161
  1198. package/dist/index299.js +0 -6
  1199. package/dist/index30.js +0 -118
  1200. package/dist/index300.js +0 -127
  1201. package/dist/index302.js +0 -6
  1202. package/dist/index303.js +0 -20
  1203. package/dist/index305.js +0 -6
  1204. package/dist/index306.js +0 -116
  1205. package/dist/index307.js +0 -5
  1206. package/dist/index308.js +0 -507
  1207. package/dist/index31.js +0 -10
  1208. package/dist/index310.js +0 -6
  1209. package/dist/index311.js +0 -196
  1210. package/dist/index313.js +0 -6
  1211. package/dist/index314.js +0 -142
  1212. package/dist/index316.js +0 -6
  1213. package/dist/index317.js +0 -134
  1214. package/dist/index319.js +0 -6
  1215. package/dist/index32.js +0 -28
  1216. package/dist/index320.js +0 -162
  1217. package/dist/index322.js +0 -6
  1218. package/dist/index323.js +0 -319
  1219. package/dist/index325.js +0 -6
  1220. package/dist/index326.js +0 -40
  1221. package/dist/index327.js +0 -43
  1222. package/dist/index328.js +0 -665
  1223. package/dist/index33.js +0 -5
  1224. package/dist/index330.js +0 -6
  1225. package/dist/index331.js +0 -155
  1226. package/dist/index333.js +0 -6
  1227. package/dist/index334.js +0 -142
  1228. package/dist/index336.js +0 -6
  1229. package/dist/index337.js +0 -155
  1230. package/dist/index339.js +0 -6
  1231. package/dist/index34.js +0 -112
  1232. package/dist/index340.js +0 -58
  1233. package/dist/index341.js +0 -5
  1234. package/dist/index342.js +0 -20
  1235. package/dist/index344.js +0 -6
  1236. package/dist/index345.js +0 -142
  1237. package/dist/index347.js +0 -6
  1238. package/dist/index349.js +0 -109
  1239. package/dist/index351.js +0 -6
  1240. package/dist/index352.js +0 -55
  1241. package/dist/index353.js +0 -24
  1242. package/dist/index354.js +0 -23
  1243. package/dist/index355.js +0 -839
  1244. package/dist/index356.js +0 -474
  1245. package/dist/index357.js +0 -52
  1246. package/dist/index358.js +0 -7
  1247. package/dist/index36.js +0 -6
  1248. package/dist/index38.js +0 -63
  1249. package/dist/index39.js +0 -63
  1250. package/dist/index40.js +0 -9
  1251. package/dist/index42.js +0 -8
  1252. package/dist/index43.js +0 -179
  1253. package/dist/index44.js +0 -21
  1254. package/dist/index46.js +0 -385
  1255. package/dist/index48.js +0 -6
  1256. package/dist/index49.js +0 -33
  1257. package/dist/index5.js +0 -34
  1258. package/dist/index50.js +0 -12
  1259. package/dist/index51.js +0 -8
  1260. package/dist/index52.js +0 -106
  1261. package/dist/index53.js +0 -330
  1262. package/dist/index54.js +0 -97
  1263. package/dist/index55.js +0 -328
  1264. package/dist/index56.js +0 -28
  1265. package/dist/index57.js +0 -102
  1266. package/dist/index58.js +0 -157
  1267. package/dist/index6.js +0 -38
  1268. package/dist/index60.js +0 -6
  1269. package/dist/index61.js +0 -131
  1270. package/dist/index62.js +0 -5
  1271. package/dist/index65.js +0 -35
  1272. package/dist/index66.js +0 -26
  1273. package/dist/index68.js +0 -6
  1274. package/dist/index69.js +0 -54
  1275. package/dist/index7.js +0 -58
  1276. package/dist/index71.js +0 -6
  1277. package/dist/index72.js +0 -14
  1278. package/dist/index74.js +0 -6
  1279. package/dist/index75.js +0 -159
  1280. package/dist/index77.js +0 -6
  1281. package/dist/index78.js +0 -4
  1282. package/dist/index79.js +0 -4
  1283. package/dist/index80.js +0 -54
  1284. package/dist/index81.js +0 -5
  1285. package/dist/index82.js +0 -5
  1286. package/dist/index83.js +0 -141
  1287. package/dist/index85.js +0 -6
  1288. package/dist/index86.js +0 -65
  1289. package/dist/index88.js +0 -6
  1290. package/dist/index89.js +0 -30
  1291. package/dist/index9.js +0 -11
  1292. package/dist/index90.js +0 -19
  1293. package/dist/index91.js +0 -17
  1294. package/dist/index92.js +0 -9
  1295. package/dist/index93.js +0 -34
  1296. package/dist/index95.js +0 -16
  1297. package/dist/index96.js +0 -10
  1298. package/dist/index97.js +0 -18
  1299. package/dist/index98.js +0 -189
  1300. package/dist/types/VoidHtmlElements.d.ts +0 -1
  1301. package/dist/utilities/functions/add.d.ts +0 -1
  1302. package/dist/utilities/functions/booleanishToBoolean.d.ts +0 -6
  1303. package/dist/utilities/functions/capitalize.d.ts +0 -4
  1304. package/dist/utilities/functions/cloneDeep.d.ts +0 -1
  1305. package/dist/utilities/functions/debounce.d.ts +0 -1
  1306. package/dist/utilities/functions/drop.d.ts +0 -1
  1307. package/dist/utilities/functions/exceedsPrecision.d.ts +0 -1
  1308. package/dist/utilities/functions/findRight.d.ts +0 -4
  1309. package/dist/utilities/functions/findRightIndex.d.ts +0 -4
  1310. package/dist/utilities/functions/flow.d.ts +0 -1
  1311. package/dist/utilities/functions/getHueFromRGB.d.ts +0 -9
  1312. package/dist/utilities/functions/groupBy.d.ts +0 -6
  1313. package/dist/utilities/functions/gt.d.ts +0 -7
  1314. package/dist/utilities/functions/hexToRGB.d.ts +0 -7
  1315. package/dist/utilities/functions/hslToHex.d.ts +0 -1
  1316. package/dist/utilities/functions/includes.d.ts +0 -1
  1317. package/dist/utilities/functions/initial.d.ts +0 -1
  1318. package/dist/utilities/functions/isCssColor.d.ts +0 -5
  1319. package/dist/utilities/functions/lt.d.ts +0 -7
  1320. package/dist/utilities/functions/makeUrl.d.ts +0 -1
  1321. package/dist/utilities/functions/mapKeys.d.ts +0 -5
  1322. package/dist/utilities/functions/mapValues.d.ts +0 -1
  1323. package/dist/utilities/functions/matchesPropertyInsensitive.d.ts +0 -1
  1324. package/dist/utilities/functions/max.d.ts +0 -2
  1325. package/dist/utilities/functions/mean.d.ts +0 -4
  1326. package/dist/utilities/functions/merge.d.ts +0 -11
  1327. package/dist/utilities/functions/millisecondsTo.d.ts +0 -1
  1328. package/dist/utilities/functions/negate.d.ts +0 -4
  1329. package/dist/utilities/functions/normalizeItemGroups.d.ts +0 -13
  1330. package/dist/utilities/functions/omit.d.ts +0 -2
  1331. package/dist/utilities/functions/overEvery.d.ts +0 -4
  1332. package/dist/utilities/functions/pick.d.ts +0 -1
  1333. package/dist/utilities/functions/replace.d.ts +0 -1
  1334. package/dist/utilities/functions/replaceInTree.d.ts +0 -4
  1335. package/dist/utilities/functions/retry.d.ts +0 -5
  1336. package/dist/utilities/functions/roundToTheNearest.d.ts +0 -8
  1337. package/dist/utilities/functions/set.d.ts +0 -15
  1338. package/dist/utilities/functions/stub.d.ts +0 -6
  1339. package/dist/utilities/functions/transposePercent.d.ts +0 -9
  1340. package/dist/utilities/functions/tryTo.d.ts +0 -2
  1341. package/dist/utilities/functions/uniqBy.d.ts +0 -1
  1342. /package/dist/{index64.js → _virtual/_plugin-vue_export-helper.js} +0 -0
  1343. /package/dist/components/{BaseButton → BbBaseButton}/RouterComponent.vue.d.ts +0 -0
  1344. /package/dist/components/{BaseDatePicker/BaseDatePicker.vue.d.ts → BbBaseDatePicker/BbBaseDatePicker.vue.d.ts} +0 -0
  1345. /package/dist/{index165.js → composables/useArray.js} +0 -0
  1346. /package/dist/{index17.js → composables/useFrameworkDetection.js} +0 -0
  1347. /package/dist/{index12.js → composables/useId.js} +0 -0
  1348. /package/dist/{index107.js → utilities/functions/chunk.js} +0 -0
  1349. /package/dist/{index8.js → utilities/functions/clamp.js} +0 -0
  1350. /package/dist/{index150.js → utilities/functions/curry.js} +0 -0
  1351. /package/dist/{index45.js → utilities/functions/extractDomContainer.js} +0 -0
  1352. /package/dist/{index270.js → utilities/functions/findInTree.js} +0 -0
  1353. /package/dist/{index348.js → utilities/functions/flattenTree.js} +0 -0
  1354. /package/dist/{index41.js → utilities/functions/get.js} +0 -0
  1355. /package/dist/{index189.js → utilities/functions/head.js} +0 -0
  1356. /package/dist/{index108.js → utilities/functions/identity.js} +0 -0
  1357. /package/dist/{index179.js → utilities/functions/indexBy.js} +0 -0
  1358. /package/dist/{index37.js → utilities/functions/isHex.js} +0 -0
  1359. /package/dist/{index94.js → utilities/functions/isNil.js} +0 -0
  1360. /package/dist/{index26.js → utilities/functions/isNotNil.js} +0 -0
  1361. /package/dist/{index105.js → utilities/functions/last.js} +0 -0
  1362. /package/dist/{index142.js → utilities/functions/noop.js} +0 -0
  1363. /package/dist/{index231.js → utilities/functions/parseWidthString.js} +0 -0
  1364. /package/dist/{index240.js → utilities/functions/pickBy.js} +0 -0
  1365. /package/dist/{index123.js → utilities/functions/range.js} +0 -0
  1366. /package/dist/{index190.js → utilities/functions/snapToStep.js} +0 -0
  1367. /package/dist/{index191.js → utilities/functions/transpose.js} +0 -0
  1368. /package/dist/{index106.js → utilities/functions/validateDateFormat.js} +0 -0
  1369. /package/dist/{index10.js → utilities/functions/wait.js} +0 -0
@@ -1,2515 +1,2146 @@
1
1
  # BbTable
2
2
 
3
- ## Template & Script
3
+ ## Agent Contract
4
+
5
+ - Safety level: **Consumer API**
6
+ - Group: **Data Display**
7
+ - Description: Displays tabular data, configurable columns and data mapper.
8
+ - Usage rule: Prefer this component for consumer-facing implementations.
9
+ - Implementation source (only when the contract below is not enough): [source/BbTable.md](./source/BbTable.md)
10
+
11
+ ## Usage & Guidelines
12
+
13
+ ### When to use
14
+
15
+ Reach for `BbTable` to present **rows of records with columns** — an invoices
16
+ listing, a members admin, an audit log. It owns extraction, formatting, loading
17
+ skeletons, empty states, selection, sorting indicators, row expansion and
18
+ keyboard navigation, so your markup stays a column definition plus a few slots.
19
+
20
+ - For a **single-select or multi-select list of options** in a form, use
21
+ `BbSelect` / `BbCheckboxGroup` / `BbRadioGroup` — not a selectable table.
22
+ - For **hierarchical, expandable nodes** (a file tree, nested categories), use
23
+ `BbTree`. Flat rows with one level of detail stay in `BbTable` (`#expand`).
24
+ - `BbTable` is items-driven: read the Options/Items and Fetch playbooks. Pass
25
+ domain objects straight to `items` and set `item-value` for stable row
26
+ identity — never pre-map rows into `{ label, value }` shapes.
27
+
28
+ The table **reports** state (sort, selection, page, expansion, highlight)
29
+ through v-models; it never reorders, slices, or fetches on its own. You apply
30
+ that state to your data source (client-side or via `dependencies`-driven
31
+ refetch). This keeps it equally at home in a client-only screen and a
32
+ server-paginated one.
33
+
34
+ **`BbTable` ships no client-side virtualization or windowing** — every row you
35
+ hand it in `items` mounts to the DOM, always. A few hundred rows is fine for a
36
+ client-only screen. Past roughly 1,000 rows, don't ship the whole dataset to
37
+ the browser: page it — feed `BbTable` the current slice as `items` and pass
38
+ `page` / `per-page` / `total-items` (see § Pagination) so the server, not the
39
+ DOM, holds the rest.
40
+
41
+ ### Columns: the full grammar
42
+
43
+ A column is `{ key, label }` plus optional presentation — everything a cell
44
+ needs is declared here, not in markup:
45
+
46
+ - **`key`** extracts the cell from the item; dot paths reach nested fields
47
+ (`'customer.address.city'`). Keys autocomplete from the item type (up to 3
48
+ levels deep), but any string is accepted — that is how virtual columns work.
49
+ - **`formatter(content, key, item)`** transforms the extracted value. It runs
50
+ even when the value is nullish, so a virtual column whose `key` matches no
51
+ field can still compute from `item`; set `formatOnNull: false` when a
52
+ formatter must only see real values.
53
+ - **`placeholder`** fills the cell when the pipeline still ends nullish.
54
+ - **`align`** (`'left' | 'center' | 'right'`) per column; the table-level
55
+ `align` prop sets the default for the whole table (`left`).
56
+ - **`width`** freezes the column: numbers and bare numeric strings are px
57
+ (`130`, `'130'`); anything carrying a unit passes through (`'12rem'`,
58
+ `'20%'`). Declaring any `width` switches the table to `table-layout: fixed`
59
+ (widths are only authoritative there); unfrozen columns then share the
60
+ leftover space proportionally to their content.
61
+ - **`skeleton`** picks the loading placeholder shape (see Loading below).
62
+ - **`sortable` / `sorted`** — see Sorting.
63
+ - **`thClass` / `tdClass` / `rowClass`** style the header cell, the data cells,
64
+ and the whole `<tr>`. `tdClass` and `rowClass` may be functions
65
+ `(content, key, item) => Classes` for state-dependent styling. Table-level
66
+ `thClass` / `tdClass` / `rowClass` / `headerRowClass` apply everywhere; a
67
+ row's classes accumulate the table `rowClass` plus every column's.
68
+ - **`snap`** maps a nested table's column onto its parent's tracks — see
69
+ Nested tables.
70
+
71
+ **Columns, formatters and a per-row class**
4
72
 
5
73
  ```vue
6
74
  <template>
7
- <component
8
- :is="containerTag"
9
- :id="id"
10
- ref="containerRef"
11
- :aria-busy="loading"
12
- :aria-rowcount="hasProvidedAccessibilityData ? props.totalItems : undefined"
13
- :class="containerClass"
14
- :data-bb-table-id="tableId"
15
- :inert="disabled"
16
- :style="containerStyle"
75
+ <div
76
+ class="max-w-2xl overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
17
77
  >
18
- <legend v-if="selectable" class="sr-only">{{ legend }}</legend>
19
- <table ref="tableRef">
20
- <caption
21
- v-if="caption"
22
- :class="{
23
- 'bb-table-caption': true,
24
- 'sr-only': !displayCaption,
25
- }"
26
- >
27
- {{
28
- caption
29
- }}
30
- </caption>
31
- <colgroup>
32
- <col
33
- v-for="track in columnTracks"
34
- :key="track.key"
35
- :style="{ width: track.width }"
36
- />
37
- </colgroup>
38
- <thead>
39
- <!-- @vue-ignore -->
40
- <slot name="thead">
41
- <tr
42
- :aria-rowindex="hasProvidedAccessibilityData ? 1 : undefined"
43
- class="bb-table-header-row"
44
- >
45
- <th
46
- v-if="selectable"
47
- class="bb-table-header bb-table-header--select"
48
- :class="thClass"
49
- scope="col"
50
- >
51
- <!-- @vue-ignore -->
52
- <slot
53
- :multiple="multiple"
54
- :name="'header:select'"
55
- :select-all="!!selectAll"
56
- :text="selectText || t('table.selectText').value"
57
- >
58
- <span class="bb-table-header__content">
59
- <label
60
- v-if="allowSelectAll && multiple"
61
- class="bb-table-check__label"
62
- :class="{
63
- 'bb-table-check__label--disabled': disabled,
64
- }"
65
- >
66
- <BaseCheckbox
67
- :key="indeterminate.toString()"
68
- v-model="selectAll"
69
- class="bb-table-check"
70
- :disabled="disabled"
71
- :indeterminate="indeterminate"
72
- :name="`select_all_${name || ''}`"
73
- :readonly="readonly"
74
- />
75
- <span class="bb-table-check__label-text sr-only"
76
- >{{ selectAllLabel || t('table.selectAllLabel').value }}
77
- </span>
78
- </label>
79
- <span
80
- v-else-if="!multiple"
81
- class="bb-table-radio__label-text sr-only"
82
- >{{ selectText || t('table.selectText').value }}</span
83
- >
84
- </span>
85
- </slot>
86
- </th>
87
- <th
88
- v-for="(header, index) in mappedHeaders"
89
- :key="header.key"
90
- :aria-sort="header.sortDirection"
91
- class="bb-table-header"
92
- :class="[
93
- header.classes,
94
- {
95
- 'bb-table-header--edge-start': index === 0,
96
- 'bb-table-header--edge-end':
97
- index === mappedHeaders.length - 1,
98
- },
99
- ]"
100
- scope="col"
101
- >
102
- <!-- @vue-ignore -->
103
- <slot
104
- :classes="header.classes"
105
- :items="internalItems"
106
- :label="header.label"
107
- :name="header.slotName"
108
- ><span class="bb-table-header__content">{{
109
- header.label
110
- }}</span></slot
111
- >
112
- </th>
113
- <th
114
- v-if="actions"
115
- class="bb-table-header bb-table-header--actions"
116
- :class="thClass"
117
- scope="col"
118
- >
119
- <!-- @vue-ignore -->
120
- <slot name="header:actions" :text="actionsText">
121
- <span class="bb-table-header__content sr-only">
122
- {{ actionsText }}
123
- </span>
124
- </slot>
125
- </th>
126
- </tr>
127
- </slot>
128
- </thead>
129
- <tbody>
130
- <slot v-if="loading" :items="internalItems" name="loading">
131
- <template v-for="(_, rowIndex) in skeletonLength" :key="rowIndex">
132
- <tr
133
- :aria-rowindex="
134
- hasProvidedAccessibilityData ? rowIndex + 2 : undefined
135
- "
136
- class="bb-table-skeleton__row"
137
- >
138
- <td
139
- v-if="selectable"
140
- class="bb-table-skeleton__cell bb-table-skeleton__cell--select"
141
- >
142
- <div class="bb-table-skeleton__placeholder" />
143
- </td>
144
- <td
145
- v-for="(col, colIndex) in props.columns"
146
- :key="col.key"
147
- :class="[
148
- 'bb-table-skeleton__cell',
149
- `bb-table-skeleton__cell--${col.skeleton ?? 'text'}`,
150
- ]"
151
- >
152
- <!-- text: 2 lines with dynamic widths -->
153
- <template v-if="!col.skeleton || col.skeleton === 'text'">
154
- <div class="bb-table-skeleton__cell-content">
155
- <div
156
- class="bb-table-skeleton__line"
157
- :style="{
158
- width: `${skeletonLineWidths[(rowIndex + colIndex) % 4]}%`,
159
- }"
160
- />
161
- <div
162
- class="bb-table-skeleton__line"
163
- :style="{
164
- width: `${skeletonLineWidths[(rowIndex + colIndex + 2) % 4]}%`,
165
- }"
166
- />
167
- </div>
168
- </template>
169
- <!-- avatar: circle only -->
170
- <div
171
- v-else-if="col.skeleton === 'avatar'"
172
- class="bb-table-skeleton__avatar"
173
- />
174
- <!-- avatar-text: avatar + 2 lines -->
175
- <div
176
- v-else-if="col.skeleton === 'avatar-text'"
177
- class="bb-table-skeleton__avatar-text"
178
- >
179
- <div class="bb-table-skeleton__avatar" />
180
- <div class="bb-table-skeleton__cell-content">
181
- <div
182
- class="bb-table-skeleton__line"
183
- :style="{
184
- width: `${skeletonLineWidths[(rowIndex + colIndex) % 4]}%`,
185
- }"
186
- />
187
- <div
188
- class="bb-table-skeleton__line"
189
- :style="{
190
- width: `${skeletonLineWidths[(rowIndex + colIndex + 2) % 4]}%`,
191
- }"
192
- />
193
- </div>
194
- </div>
195
- <!-- image: rounded rect -->
196
- <div
197
- v-else-if="col.skeleton === 'image'"
198
- class="bb-table-skeleton__image"
199
- />
200
- <!-- badge: small pill -->
201
- <div
202
- v-else-if="col.skeleton === 'badge'"
203
- class="bb-table-skeleton__badge"
204
- />
205
- </td>
206
- <td
207
- v-if="actions"
208
- class="bb-table-skeleton__cell bb-table-skeleton__cell--actions"
209
- >
210
- <div class="bb-table-skeleton__placeholder" />
211
- </td>
212
- </tr>
213
- </template>
214
- <tr
215
- :aria-rowindex="
216
- hasProvidedAccessibilityData ? skeletonLength + 2 : undefined
217
- "
218
- class="bb-table-loading__row bb-table-loading__row--sr-only"
219
- >
220
- <td
221
- :colspan="replacementContentSpan"
222
- class="bb-table-loading__cell"
223
- aria-live="polite"
224
- role="status"
225
- >
226
- <span class="bb-table-loading__text sr-only">{{
227
- loadingText
228
- }}</span>
229
- </td>
230
- </tr>
231
- </slot>
232
- <slot v-else-if="!options.length" :name="'no-data'">
233
- <tr
234
- :aria-rowindex="hasProvidedAccessibilityData ? 2 : undefined"
235
- class="bb-table-no-data__row"
236
- >
237
- <td
238
- class="bb-table-no-data__cell"
239
- :colspan="replacementContentSpan"
240
- >
241
- <span class="bb-table-no-data__text">{{
242
- noDataText || t('table.noDataText').value
243
- }}</span>
244
- </td>
245
- </tr>
246
- </slot>
247
- <slot v-else name="tbody">
248
- <template v-for="item in mappedItems" :key="item.valueHash">
249
- <tr
250
- :aria-rowindex="
251
- hasProvidedAccessibilityData && item.rowIndex
252
- ? item.rowIndex
253
- : undefined
254
- "
255
- class="bb-table-data__row"
256
- @click="item.onRowClick"
257
- @contextmenu="item.onRowContextMenu"
258
- @dblclick="item.onRowDblClick"
259
- >
260
- <td
261
- v-if="selectable"
262
- class="bb-table-data__cell bb-table-data__cell--select"
263
- :class="tdClass"
264
- >
265
- <!-- @vue-ignore -->
266
- <slot
267
- :checked="isSelected(item)"
268
- :disabled="!!item.disabled"
269
- :input-name="randomName"
270
- :item="item.item"
271
- :name="'select'"
272
- :readonly="!!readonly"
273
- :value="item.value"
274
- >
275
- <!-- We use temp names for these inputs because we don't submit them -->
276
- <label
277
- v-if="multiple"
278
- class="bb-table-check__label"
279
- :class="{
280
- 'bb-table-check__label--disabled': item.disabled,
281
- }"
282
- >
283
- <BaseCheckbox
284
- :checked="isSelected(item)"
285
- class="bb-table-check"
286
- :disabled="item.disabled"
287
- :readonly="readonly"
288
- @change="() => onInputChange(item)"
289
- />
290
- <span class="bb-table-check__label-text sr-only">{{
291
- item.accessibleLabel
292
- }}</span>
293
- </label>
294
-
295
- <label
296
- v-else
297
- class="bb-table-radio__label"
298
- :class="{
299
- 'bb-table-radio__label--disabled': item.disabled,
300
- }"
301
- >
302
- <BaseRadio
303
- :checked="item.selected"
304
- class="bb-table-radio"
305
- :disabled="item.disabled"
306
- :name="randomName"
307
- :readonly="readonly"
308
- :value="item.value"
309
- @change="() => onInputChange(item)"
310
- />
311
- <span class="bb-table-radio__label-text sr-only">{{
312
- item.accessibleLabel
313
- }}</span>
314
- </label>
315
- </slot>
316
- </td>
317
- <td
318
- v-for="(col, colIndex) in item.cols"
319
- :key="col.key"
320
- class="bb-table-data__cell"
321
- :class="[
322
- col.classes,
323
- {
324
- 'bb-table-data__cell--edge-start': colIndex === 0,
325
- 'bb-table-data__cell--edge-end':
326
- colIndex === item.cols.length - 1,
327
- },
328
- ]"
329
- >
330
- <!-- @vue-ignore -->
331
- <slot
332
- :classes="col.classes"
333
- :content="col.content"
334
- :expanded="item.expanded"
335
- :item="item.item"
336
- :name="col.slotName"
337
- :expand-props="getExpandControllerProps(item)"
338
- :toggle-expanded="() => toggleExpandedFor(item.valueHash)"
339
- :value="item.value"
340
- >{{ col.content }}</slot
341
- >
342
- </td>
343
- <td
344
- v-if="actions"
345
- class="bb-table__cell bb-table__cell--actions"
346
- :class="tdClass"
347
- >
348
- <!-- @vue-ignore -->
349
- <slot
350
- :expanded="item.expanded"
351
- :item="item.item"
352
- :name="'actions'"
353
- :expand-props="getExpandControllerProps(item)"
354
- :toggle-expanded="() => toggleExpandedFor(item.valueHash)"
355
- :value="item.value"
356
- ></slot>
357
- </td>
358
- </tr>
359
- <tr
360
- v-if="item.expanded"
361
- :aria-rowindex="
362
- hasProvidedAccessibilityData && item.rowIndex
363
- ? item.rowIndex + 1
364
- : undefined
365
- "
366
- :id="`expanded_${item.valueHash}`"
367
- class="bb-table-expand__row"
368
- >
369
- <td
370
- :colspan="replacementContentSpan"
371
- class="bb-table-expand__cell"
372
- >
373
- <!-- @vue-ignore -->
374
- <slot
375
- :expanded="item.expanded"
376
- :item="item.item"
377
- :name="'expand'"
378
- :expand-props="getExpandControllerProps(item)"
379
- :toggle-expanded="() => toggleExpandedFor(item.valueHash)"
380
- :value="item.value"
381
- ></slot>
382
- </td>
383
- </tr>
384
- </template>
385
- </slot>
386
- </tbody>
387
- <tfoot>
388
- <!-- @vue-ignore -->
389
- <slot name="tfoot"></slot>
390
- </tfoot>
391
- </table>
392
- <input v-for="input in hiddenInputs" :key="input.value" v-bind="input" />
393
- </component>
78
+ <BbTable
79
+ caption="Recent invoices"
80
+ :columns="columns"
81
+ compact
82
+ item-value="id"
83
+ :items="invoices"
84
+ :row-class="rowClass"
85
+ />
86
+ </div>
394
87
  </template>
395
-
396
- <script
397
- setup
398
- lang="ts"
399
- generic="Item = any, T extends ItemKey<Item> = string & {}"
400
- >
401
- import {
402
- computed,
403
- nextTick,
404
- onBeforeUnmount,
405
- onMounted,
406
- reactive,
407
- ref,
408
- toRef,
409
- watch,
410
- } from 'vue';
411
- import { clamp } from '@/utilities/functions/clamp';
412
- import { hash } from '@/utilities/functions/hash';
413
- import { isNil } from '@/utilities/functions/isNil';
414
- import { when } from '@/utilities/functions/when';
415
- import { useId } from '@/composables/useId';
416
- import { useItemValue } from '@/composables/useItemValue';
417
- import { useItemsGetter } from '@/composables/useItemsGetter';
418
- import { usePrefill } from '@/composables/usePrefill';
419
- import { useIndexById } from '@/composables/useIndexById';
420
- import { useBaseOptions } from '@/composables/useBaseOptions';
421
- import { useHashedWatcher } from '@/composables/useHashedWatcher';
422
- import { useTableWidthContext } from '@/composables/useTableWidthContext';
423
- import { useLocale } from '@/composables/useLocale';
424
- import BaseCheckbox from '../BaseCheckbox/BaseCheckbox.vue';
425
- import BaseRadio from '../BaseRadio/BaseRadio.vue';
426
- import type { Option as BaseOption } from '@/types/Option';
427
- import type { Classes } from '@/types/Classes';
428
- import type { ColumnName } from '@/types/ColumnName';
429
- import type { PrefixWith } from '@/types/PrefixWith';
430
- import type {
431
- BbTableColumn,
432
- BbTableEvents,
433
- BbTableProps,
434
- BbTableSlots,
435
- ExpandControllerProps,
436
- ItemKey,
437
- MappedCell,
438
- MappedHeader,
439
- MappedItem,
440
- } from './types';
441
- import {
442
- applyFixedTableColumns,
443
- columnKeyToSlotName,
444
- mergeCellClasses,
445
- mergeHeaderClasses,
446
- } from './utils';
447
- import { useLogger } from '@/composables/useLogger';
448
- import { isEmpty } from '@/utilities/functions/empty';
449
-
450
- const { getItemValue } = useItemValue();
451
-
452
- /** Temporary name for selection inputs; hidden inputs handle real submission. */
453
- const randomName = useId().id.value;
454
-
455
- type InternalColumn<K extends string> = Omit<BbTableColumn<Item>, 'key'> & {
456
- key: K;
457
- };
458
-
459
- type InternalProps<K extends string> = Omit<BbTableProps<Item>, 'columns'> & {
460
- columns: InternalColumn<K>[];
88
+ <script setup lang="ts">
89
+ import { BbTable } from 'bitboss-ui';
90
+ import type { BbTableColumn } from 'bitboss-ui';
91
+
92
+ type Invoice = {
93
+ id: string;
94
+ number: string;
95
+ client: string;
96
+ issuedAt: string;
97
+ note: string | null;
98
+ amount: number;
99
+ status: 'paid' | 'pending' | 'overdue';
461
100
  };
462
101
 
463
- const props = withDefaults(defineProps<InternalProps<T>>(), {
464
- align: 'left',
465
- actionsText: 'Azioni',
466
- allowSelectAll: true,
467
- columns: () => [],
468
- dependencies: () => [],
469
- depsDebounceTime: 0,
470
- fixedColumns: () => [],
471
- items: () => [],
472
- max: Infinity,
473
- multiple: true,
474
- modelValue: () => [],
475
- unselectedItems: () => [],
102
+ const euro = new Intl.NumberFormat('en-IE', {
103
+ style: 'currency',
104
+ currency: 'EUR',
476
105
  });
477
106
 
478
- const emit = defineEmits<BbTableEvents>();
479
-
480
- type InferredBodySlots = ColumnName<T>;
481
- type InferredHeaderSlots = PrefixWith<ColumnName<T>, 'header:'>;
482
-
483
- defineSlots<
484
- BbTableSlots<Item> & {
485
- [K in InferredBodySlots]?: (props: {
486
- classes?: Classes;
487
- content?: any;
488
- expanded?: boolean;
489
- expandProps?: ExpandControllerProps;
490
- toggleExpanded?: () => void;
491
- item: Item;
492
- value?: any;
493
- }) => any;
494
- } & {
495
- [K in InferredHeaderSlots]?: (props: {
496
- classes?: Classes;
497
- content?: any;
498
- items?: Item[];
499
- label?: string;
500
- }) => any;
501
- }
502
- >();
503
-
504
- if (props.multiple && !Array.isArray(props.modelValue)) {
505
- const logger = useLogger();
506
- logger.throw(
507
- 'BbTable is set for multiple selection but modelValue is not an array.'
508
- );
509
- }
510
-
511
- const { t } = useLocale();
512
-
513
- const tableRef = ref<HTMLTableElement>();
514
- const containerRef = ref<HTMLElement>();
515
-
516
- const {
517
- id: tableId,
518
- parentId,
519
- parentNode,
520
- cssVars,
521
- parentVar,
522
- setTracks,
523
- } = useTableWidthContext({
524
- id: () => props.id,
525
- container: () => containerRef.value,
526
- inheritFrom: () =>
527
- typeof props.inheritColumnWidths === 'string'
528
- ? props.inheritColumnWidths
529
- : undefined,
530
- });
107
+ // Columns declare extraction (`key`), formatting (`formatter`), alignment and a
108
+ // fixed `width`. `placeholder` fills in when a cell resolves to nullish.
109
+ const columns: Array<BbTableColumn<Invoice>> = [
110
+ { key: 'number', label: 'Invoice', width: 110 },
111
+ { key: 'client', label: 'Client' },
112
+ {
113
+ key: 'issuedAt',
114
+ label: 'Issued',
115
+ width: 100,
116
+ formatter: (value: string) => new Date(value).toLocaleDateString('en-GB'),
117
+ },
118
+ { key: 'note', label: 'Note', placeholder: '—' },
119
+ {
120
+ key: 'amount',
121
+ label: 'Amount',
122
+ align: 'right',
123
+ width: 110,
124
+ formatter: euro.format,
125
+ },
126
+ ];
531
127
 
532
- /** Any column pinned to a parent track range — a per-column inherit signal. */
533
- const hasSnap = computed(() => props.columns.some((c) => c.snap != null));
128
+ const invoices: Invoice[] = [
129
+ {
130
+ id: 'inv-1041',
131
+ number: 'INV-1041',
132
+ client: 'Northwind Traders',
133
+ issuedAt: '2026-06-02',
134
+ note: 'Retainer',
135
+ amount: 4200,
136
+ status: 'paid',
137
+ },
138
+ {
139
+ id: 'inv-1042',
140
+ number: 'INV-1042',
141
+ client: 'Globex',
142
+ issuedAt: '2026-06-11',
143
+ note: null,
144
+ amount: 980.5,
145
+ status: 'pending',
146
+ },
147
+ {
148
+ id: 'inv-1043',
149
+ number: 'INV-1043',
150
+ client: 'Initech',
151
+ issuedAt: '2026-05-19',
152
+ note: 'Overage',
153
+ amount: 15600,
154
+ status: 'overdue',
155
+ },
156
+ ];
157
+
158
+ // `row-class` is applied to each `<tr>`; use it to flag rows by state. Soft amber
159
+ // wash matches the design-language attention accent — public API, no internals.
160
+ const rowClass = (invoice: Invoice) =>
161
+ invoice.status === 'overdue'
162
+ ? 'bg-[color:color-mix(in_oklab,#d97706_8%,var(--bb-panel))]'
163
+ : '';
164
+ </script>
165
+ ```
534
166
 
535
- // Opt-in: inherit when explicitly enabled (`true`, or an ancestor id string),
536
- // or implicitly when any column carries a `snap` — a snap only resolves against
537
- // a parent's tracks, so asking for one is asking to inherit. An absent prop with
538
- // no snaps does not inherit, so a plain nested table stays independent. An
539
- // explicit `false` is authoritative and overrides the implicit snap signal.
540
- const inheritColumnWidths = computed(
541
- () => !!props.inheritColumnWidths || hasSnap.value
542
- );
167
+ A virtual column has a `key` that matches nothing and derives its content in
168
+ the formatter — use it for computed cells (totals, ratios) instead of
169
+ pre-mapping your items:
543
170
 
544
- /**
545
- * Assigned column widths (frozen, snapped, or inherited) are only authoritative
546
- * under `table-layout: fixed`; in auto layout they behave like a minimum. So a
547
- * table that drives widths — explicitly `fixed`, inheriting from a parent, or
548
- * with a column carrying a `width` or `snap` — uses fixed layout even when
549
- * `fixed` was not set. (`snap` is a width definition: the parent track range.)
550
- */
551
- const fixedLayout = computed(
552
- () =>
553
- props.fixed ||
554
- hasSnap.value ||
555
- props.columns.some((column) => column.width != null) ||
556
- (inheritColumnWidths.value && !!parentId.value)
557
- );
171
+ **A virtual total column computed from the row**
558
172
 
559
- /**
560
- * A nested table inheriting its parent's widths must size itself to the sum of
561
- * those columns rather than stretching to fill its container — otherwise fixed
562
- * layout would distribute the slack and the columns would no longer match the
563
- * parent. Tables with the same column count already sum to the full width, so
564
- * this is a no-op for them and only narrows tables with fewer columns.
565
- */
566
- /** True for a nested table that inherits its column widths from a parent. */
567
- const isInheriting = computed(
568
- () => inheritColumnWidths.value && !!parentId.value
569
- );
173
+ ```vue
174
+ <BbTable
175
+ caption="Order lines"
176
+ :columns="[
177
+ { key: 'sku', label: 'SKU' },
178
+ { key: 'qty', label: 'Qty', align: 'right' },
179
+ { key: 'unitPrice', label: 'Unit €', align: 'right' },
180
+ {
181
+ key: 'total',
182
+ label: 'Total €',
183
+ align: 'right',
184
+ formatter: (_, __, item) =>
185
+ (item.qty * item.unitPrice).toLocaleString('en-IE'),
186
+ },
187
+ ]"
188
+ :items="[
189
+ { sku: 'CHAIR-OAK', qty: 4, unitPrice: 189 },
190
+ { sku: 'DESK-140', qty: 2, unitPrice: 420 },
191
+ { sku: 'LAMP-LED', qty: 6, unitPrice: 39 },
192
+ ]"
193
+ />
194
+ ```
570
195
 
571
- const sizeToColumns = isInheriting;
196
+ **`actions` and `select` are not columns.** Both are rendered by the table
197
+ itself, from the `actions` and `selectable` props. Putting
198
+ `{ key: 'actions' }` in `columns` adds an ordinary data column that reads
199
+ `item.actions`, so you get a stray empty cell _and_ the `#actions` slot never
200
+ renders — which looks like a broken slot and is really a bad column list. Dev
201
+ builds warn.
202
+
203
+ ### Custom cells and headers
204
+
205
+ Override any column's body with a `#<key>` slot and its header with a
206
+ `#header:<key>` slot. The slot name is the key with every run of non-word
207
+ characters turned into `_` (dashes are kept as-is), **lowercased**:
208
+ `address.city` → `#address_city`, `first_name` → `#first_name`, `issued-at` →
209
+ `#issued-at`, but also `issuedAt` → `#issuedat` — prefer snake_case keys so
210
+ slot names read naturally. Keep the column in `columns`
211
+ even when you fully slot it: label, alignment and width still come from there.
212
+
213
+ Body cell scope: `content` (the formatted value), `item` (raw row), `value`
214
+ (resolved via `item-value`), `classes`, plus the row's interaction surface —
215
+ `selected` / `toggleSelected`, `highlighted` / `toggleHighlighted`,
216
+ `expanded` / `toggleExpanded` / `expandProps`, and `sortOrder`. The flags are
217
+ one-way state; the `toggle*` callbacks are the write path.
218
+
219
+ Header scope: `label`, `items` (the full list — handy for counts), `sortable`,
220
+ `sortOrder`, `toggleSort`, `classes`.
221
+
222
+ Below, the status badges carry brand glyphs shipped in the app's `iconDir`,
223
+ referenced as `local:*` names on the badge's `prepend:icon`. Per the icons
224
+ policy the dynamic status maps to **full-name literals** (a lookup object) —
225
+ never a template-composed `` `local:${status}` ``. Colours use the registered
226
+ `soft-*` badge variants (design-language palette); the companion carries their
227
+ CSS in a non-scoped style block.
228
+
229
+ **Status badges and an annotated header**
572
230
 
573
- /** Numbers and bare numeric strings (e.g. `400`, `'400'`) default to px; values
574
- * that already carry a unit/keyword (`'50%'`, `'10rem'`, `'auto'`) pass through. */
575
- const toLength = (width: number | string) => {
576
- if (typeof width === 'number') return `${width}px`;
577
- const trimmed = width.trim();
578
- return /^-?\d*\.?\d+$/.test(trimmed) ? `${trimmed}px` : trimmed;
231
+ ```vue
232
+ <template>
233
+ <div
234
+ class="max-w-2xl overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
235
+ >
236
+ <BbTable
237
+ caption="Invoice status"
238
+ :columns="columns"
239
+ compact
240
+ item-value="id"
241
+ :items="invoices"
242
+ >
243
+ <!-- Cell slot: name = the column key (dots/non-word chars become `_`,
244
+ dashes are kept).
245
+ The glyphs are brand assets from the app's `iconDir`, referenced as
246
+ `local:*` names — mapped to full literals, never template-composed. -->
247
+ <template #status="{ content }">
248
+ <BbBadge
249
+ :prepend:icon="glyphs[content as Status]"
250
+ size="sm"
251
+ :variant="variants[content as Status]"
252
+ >{{ labels[content as Status] }}</BbBadge
253
+ >
254
+ </template>
255
+
256
+ <!-- Header slot: `header:` + the column key. `label` comes from the scope. -->
257
+ <template #header:amount="{ label }">
258
+ <span class="inline-flex items-center gap-1">
259
+ {{ label }}
260
+ <span class="text-xs font-normal text-[color:var(--bb-text-muted)]"
261
+ >EUR</span
262
+ >
263
+ </span>
264
+ </template>
265
+ </BbTable>
266
+ </div>
267
+ </template>
268
+ <script setup lang="ts">
269
+ import { BbBadge, BbTable } from 'bitboss-ui';
270
+ import type { BadgeVariantType, BbTableColumn, IconType } from 'bitboss-ui';
271
+
272
+ type Status = 'paid' | 'pending' | 'overdue';
273
+ type Invoice = {
274
+ id: string;
275
+ number: string;
276
+ client: string;
277
+ status: Status;
278
+ amount: string;
579
279
  };
580
280
 
581
- /** Number of data columns the parent renders (numeric track keys). */
582
- const parentDataColumns = computed(() => {
583
- const tracks = parentNode.value?.tracks;
584
- return tracks
585
- ? Object.keys(tracks).filter((key) => /^\d+$/.test(key)).length
586
- : 0;
587
- });
588
-
589
- /**
590
- * The parent's rendered columns in order — `select` (if any), data columns
591
- * `0…N-1`, then `actions` (if any) — form a gridline coordinate, one cell per
592
- * column. Cell `c` is the range `[c, c + 1]`. So a no-select parent starts its
593
- * data at cell 0, while a selectable parent starts it at cell 1.
594
- */
595
- const parentColumnKeys = computed(() => {
596
- const tracks = parentNode.value?.tracks ?? {};
597
- const keys: string[] = [];
598
- if ('select' in tracks) keys.push('select');
599
- for (let k = 0; k < parentDataColumns.value; k++) keys.push(String(k));
600
- if ('actions' in tracks) keys.push('actions');
601
- return keys;
602
- });
603
-
604
- /** Gridline cells where the parent's data region begins/ends and the table ends. */
605
- const firstDataCell = computed(() =>
606
- 'select' in (parentNode.value?.tracks ?? {}) ? 1 : 0
607
- );
608
- const dataEndCell = computed(
609
- () => firstDataCell.value + parentDataColumns.value
610
- );
611
- const tableEndCell = computed(() => parentColumnKeys.value.length);
612
-
613
- /** Parent track custom property for the integer cell `[cell, cell + 1]`. */
614
- const cellTrackVar = (cell: number) => {
615
- const key = parentColumnKeys.value[cell];
616
- return key ? parentVar(key, '0px') : '0px';
281
+ const columns: Array<BbTableColumn<Invoice>> = [
282
+ { key: 'number', label: 'Invoice', width: 110 },
283
+ { key: 'client', label: 'Client' },
284
+ { key: 'status', label: 'Status', width: 120 },
285
+ { key: 'amount', label: 'Amount', align: 'right', width: 110 },
286
+ ];
287
+
288
+ const labels: Record<Status, string> = {
289
+ paid: 'Paid',
290
+ pending: 'Pending',
291
+ overdue: 'Overdue',
617
292
  };
618
293
 
619
- /** CSS length summing the parent tracks across the gridline range `[a, b]`. */
620
- const gridRangeWidth = (a: number, b: number): string | null => {
621
- if (!parentId.value || b - a <= 1e-6) return null;
622
- const parts: string[] = [];
623
- for (let cell = Math.floor(a); cell < Math.ceil(b - 1e-6); cell++) {
624
- const fraction = Math.min(b, cell + 1) - Math.max(a, cell);
625
- if (fraction <= 1e-6) continue;
626
- const track = cellTrackVar(cell);
627
- parts.push(
628
- fraction >= 1 - 1e-6 ? track : `${Number(fraction.toFixed(4))} * ${track}`
629
- );
630
- }
631
- if (!parts.length) return null;
632
- // A bare `var()` can stand alone, but a single fractional term still needs a
633
- // `calc()` wrapper for the multiplication to be valid CSS.
634
- if (parts.length === 1 && !parts[0].includes('*')) return parts[0];
635
- return `calc(${parts.join(' + ')})`;
294
+ // Status → full-name literals (icons policy: no `local:${status}` templates).
295
+ const glyphs: Record<Status, IconType> = {
296
+ paid: 'local:check_circle',
297
+ pending: 'local:clock',
298
+ overdue: 'local:alert_circle',
636
299
  };
637
300
 
638
- /**
639
- * Resolved parent snap interval for each data column.
640
- * - A number snaps to that start point; the column then runs up to the next
641
- * column's snap point (or one whole track if the next is unset). So a single
642
- * snap that skips ahead widens this column to cover the gap — `snap: 4` on the
643
- * second column makes the first effectively `[start, 4]`.
644
- * - A `[start, end]` pair is taken verbatim (`end` of `-1` runs to the end of
645
- * the data region).
646
- * - Omitted, the column chains from the previous column's end.
647
- */
648
- const columnIntervals = computed(() => {
649
- const cols = props.columns;
650
- // Explicit start points; a later column's snap sets the previous column's end.
651
- const starts = cols.map((column) =>
652
- typeof column.snap === 'number'
653
- ? column.snap
654
- : Array.isArray(column.snap)
655
- ? column.snap[0]
656
- : null
657
- );
658
- let cursor = firstDataCell.value;
659
- return cols.map((column, i) => {
660
- const start = starts[i] ?? cursor;
661
- let end: number;
662
- if (Array.isArray(column.snap)) {
663
- end = column.snap[1] === -1 ? dataEndCell.value : column.snap[1];
664
- } else {
665
- const next = starts[i + 1];
666
- end = next != null && next > start ? next : Math.floor(start) + 1;
667
- }
668
- cursor = end;
669
- return { start, end };
670
- });
671
- });
672
-
673
- /**
674
- * A column whose interval begins at or beyond the parent's last data cell has no
675
- * matching parent column to inherit from — the table has more columns than its
676
- * parent. Such columns are sized to their own measured content and scroll within
677
- * the expand row instead of inheriting a track.
678
- */
679
- const isOverflowColumn = (interval: { start: number }) =>
680
- isInheriting.value && interval.start >= dataEndCell.value;
681
-
682
- /** True when any column overflows the parent (more columns than the parent). */
683
- const overflowsParent = computed(() =>
684
- columnIntervals.value.some(isOverflowColumn)
685
- );
686
-
687
- /**
688
- * A "flex" column shares a root table's leftover space (min-content floor)
689
- * proportionally via the probe. Only root tables flex this way; an inheriting
690
- * table instead snaps its in-range columns to the parent, content-sizes its
691
- * overflow columns, and lets a single fill column take up any slack.
692
- */
693
- const isFlexColumn = (index: number) =>
694
- props.columns[index]?.width == null && !isInheriting.value;
695
-
696
- /**
697
- * The last column of an inheriting table whose width is left undefined — neither
698
- * a `width` nor a `snap` (a `snap` *is* a width definition: the track range it
699
- * spans). That column absorbs the leftover space so the table fills its parent
700
- * when a frozen/snapped column narrower than the parent's would otherwise leave a
701
- * gap. `-1` for a root table, or when every column defines its own width.
702
- */
703
- const fillColumnIndex = computed(() => {
704
- if (!isInheriting.value) return -1;
705
- for (let i = props.columns.length - 1; i >= 0; i--) {
706
- const column = props.columns[i];
707
- if (column.width == null && column.snap == null) return i;
708
- }
709
- return -1;
710
- });
711
-
712
- // Parent columns left of the first column (e.g. a select this table doesn't
713
- // render, or columns skipped by the first snap) and those right of the last are
714
- // folded into the edge columns as the structural offset, keeping the table
715
- // aligned to the parent. `offsetStartValue` becomes the first column's padding.
716
- const offsetStartValue = computed(() => {
717
- const first = columnIntervals.value[0];
718
- // No parent to align to (a root table) means no offset at all — without this
719
- // the structural reclaim below would still fold a negative `-ownSelect` in.
720
- if (!inheritColumnWidths.value || !parentId.value || !first) return null;
721
- // The fill runs from the parent's left edge to this table's first data
722
- // column — the parent's select cell, if any, plus any skipped data columns.
723
- const head = gridRangeWidth(0, first.start);
724
- // This table's own select column sits flush at the left edge and takes its
725
- // measured width, so reclaim it from the fill (mirror of the actions side):
726
- // the first data column then lines the *next* column up flush with the
727
- // parent. When the parent has a select cell it is part of `head`, so only the
728
- // surplus over it is reclaimed; when the parent has none the whole select
729
- // width comes out of the first column, which shrinks to make room for it.
730
- const ownSelect = props.selectable ? structuralFit.select : null;
731
- if (!ownSelect) return head;
732
- return `calc(${head ?? '0px'} - ${ownSelect})`;
733
- });
734
- const offsetEndValue = computed(() => {
735
- const intervals = columnIntervals.value;
736
- const last = intervals[intervals.length - 1];
737
- // No parent to align to (a root table) means no offset at all — without this
738
- // the structural reclaim below would still fold a negative `-ownActions` in.
739
- if (!inheritColumnWidths.value || !parentId.value || !last) return null;
740
- // With more columns than the parent, the table runs past the parent's right
741
- // edge: there is no trailing parent region to fold, and the last column is a
742
- // content-sized overflow column, so there is no end fill.
743
- if (overflowsParent.value) return null;
744
- // When the last column is the (auto) fill column it already absorbs the whole
745
- // trailing region via fixed layout, so folding the offset in too would double
746
- // it — and skew its right-aligned content inward.
747
- if (fillColumnIndex.value === props.columns.length - 1) return null;
748
- // The fill runs from the last data column's end all the way to the parent's
749
- // right edge — any unmapped data columns plus the parent's actions cell, if
750
- // it has one.
751
- const tail = gridRangeWidth(last.end, tableEndCell.value);
752
- // This table's own actions column sits flush against that right edge and
753
- // takes its measured content width, so reclaim it from the fill: the table
754
- // then ends exactly at the parent's edge instead of overflowing by the
755
- // actions width. When the parent has an actions cell it is part of `tail`, so
756
- // only the surplus over it is effectively reclaimed; when the parent has none
757
- // the whole actions width comes out of the data fill. The result may be
758
- // negative — the last data column then shrinks to make room for the actions.
759
- const ownActions = props.actions ? structuralFit.actions : null;
760
- if (!ownActions) return tail;
761
- return `calc(${tail ?? '0px'} - ${ownActions})`;
762
- });
763
- const offsetStartActive = computed(() => offsetStartValue.value !== null);
764
- const offsetEndActive = computed(() => offsetEndValue.value !== null);
765
-
766
- /**
767
- * Measured intrinsic content widths of the structural columns. In a fixed table
768
- * these pin `select`/`actions` to their content so the remaining space is shared
769
- * among the data columns; `null` falls back to the regular auto behavior.
770
- */
771
- const structuralFit = reactive<{
772
- select: string | null;
773
- actions: string | null;
774
- }>({ select: null, actions: null });
775
-
776
- /**
777
- * Rendered columns in DOM order, each with the track key it publishes/inherits
778
- * and the width to apply via `<colgroup>`. A frozen column width wins; otherwise
779
- * an unfrozen column syncs to the parent's matching track (or `auto` at root).
780
- * The first/last column additionally absorbs the structural edge offset.
781
- */
782
- const columnTracks = computed(() => {
783
- const sync = (key: string) =>
784
- inheritColumnWidths.value ? parentVar(key) : 'auto';
785
- // In fixed layout `auto` columns share width equally, so pin the structural
786
- // columns to their measured content width and let the data columns share.
787
- const structural = (key: 'select' | 'actions') =>
788
- fixedLayout.value && structuralFit[key]
789
- ? (structuralFit[key] as string)
790
- : sync(key);
791
- const tracks: { key: string; width: string }[] = [];
792
-
793
- if (props.selectable) {
794
- tracks.push({ key: 'select', width: structural('select') });
795
- }
301
+ const variants: Record<Status, BadgeVariantType> = {
302
+ paid: 'soft-green',
303
+ pending: 'soft-amber',
304
+ overdue: 'soft-red',
305
+ };
796
306
 
797
- const lastIndex = props.columns.length - 1;
798
- props.columns.forEach((column, index) => {
799
- const key = String(index);
800
- const frozen = column.width != null ? toLength(column.width) : null;
801
- const interval = columnIntervals.value[index];
802
-
803
- // The fill column is left `auto` so fixed layout (the inheriting table has
804
- // a definite `width: 100%`) hands it the leftover space — its own inherited
805
- // track plus the folded edge region plus any deficit. Skipped when the
806
- // table overflows: there is no leftover then, and it must content-size.
807
- if (index === fillColumnIndex.value && !overflowsParent.value) {
808
- tracks.push({ key, width: 'auto' });
809
- return;
810
- }
307
+ const invoices: Invoice[] = [
308
+ {
309
+ id: 'inv-1041',
310
+ number: 'INV-1041',
311
+ client: 'Northwind Traders',
312
+ status: 'paid',
313
+ amount: '€4,200.00',
314
+ },
315
+ {
316
+ id: 'inv-1042',
317
+ number: 'INV-1042',
318
+ client: 'Globex',
319
+ status: 'pending',
320
+ amount: '€980.50',
321
+ },
322
+ {
323
+ id: 'inv-1043',
324
+ number: 'INV-1043',
325
+ client: 'Initech',
326
+ status: 'overdue',
327
+ amount: '€15,600.00',
328
+ },
329
+ {
330
+ id: 'inv-1044',
331
+ number: 'INV-1044',
332
+ client: 'Umbrella',
333
+ status: 'pending',
334
+ amount: '€2,750.00',
335
+ },
336
+ ];
337
+ </script>
811
338
 
812
- let span: string | null;
813
- if (frozen != null) {
814
- span = frozen;
815
- } else if (isFlexColumn(index)) {
816
- // Root table auto column: a precise share of the leftover space from the
817
- // proportional probe rather than fixed layout's equal split. `auto` is
818
- // the fallback before the probe runs.
819
- span = (fixedLayout.value && probeColWidths.value[index]) || null;
820
- } else if (isOverflowColumn(interval)) {
821
- // Inheriting table, past the parent's last column: no track to snap to,
822
- // so size to its own content and let the table overflow (and scroll).
823
- span = columnMinWidths.value[index] ?? null;
824
- } else {
825
- // In range: snap to the parent's matching column.
826
- span = gridRangeWidth(interval.start, interval.end);
827
- }
339
+ <!-- Soft status palette — canonical source: ai/guides/design-language.md § Using color.
340
+ Non-scoped so variant classes on the child badge receive the accents. -->
341
+ <style>
342
+ .bb-badge.bb-badge--soft-green {
343
+ --accent: #16a34a;
344
+ }
345
+ .bb-badge.bb-badge--soft-amber {
346
+ --accent: #d97706;
347
+ }
348
+ .bb-badge.bb-badge--soft-red {
349
+ --accent: #dc2626;
350
+ }
351
+ .bb-badge.bb-badge--soft-green,
352
+ .bb-badge.bb-badge--soft-amber,
353
+ .bb-badge.bb-badge--soft-red {
354
+ --bg: color-mix(in oklab, var(--accent) 15%, var(--bb-panel));
355
+ --color: color-mix(in oklab, var(--accent) 80%, var(--bb-text));
356
+ --border-width: 1px;
357
+ --border-color: color-mix(in oklab, var(--accent) 25%, var(--bb-panel));
358
+ --ring: color-mix(in oklab, var(--accent) 45%, transparent);
359
+ }
360
+ </style>
361
+ ```
828
362
 
829
- const offsets: string[] = [];
830
- if (index === 0 && offsetStartActive.value) {
831
- offsets.push('var(--bb-table-offset-internal-start)');
832
- }
833
- if (index === lastIndex && offsetEndActive.value) {
834
- offsets.push('var(--bb-table-offset-internal-end)');
835
- }
363
+ Beyond per-column slots, the structural slots cover every other region:
364
+ `#header:select` (the select-all cell content), `#header:actions`, `#select`
365
+ (replace the per-row checkbox/radio; scope: `item`, `value`, `checked`,
366
+ `disabled`, `readonly`, `inputName`, `toggleSelected`), `#loading`, `#no-data`
367
+ (both below), and `#thead` / `#tbody` / `#tfoot` to replace an entire region
368
+ when you need full control (you then own its markup and semantics).
369
+
370
+ ### Loading and empty states
371
+
372
+ `loading` means two different things and the table tells them apart for you:
373
+
374
+ - **Nothing to show yet** (first load, or a filter that emptied the table) →
375
+ **skeleton rows**, whose shape you tune per column with `skeleton` (`text`,
376
+ `avatar`, `avatar-text`, `image`, `badge`). Between 4 and 10 rows, matching the
377
+ current row count when it has one.
378
+ - **Already showing rows** (a refetch: a `dependencies` change, a poll, a
379
+ save-then-reload) → **the rows stay put**, dimmed, with an indeterminate bar
380
+ along the header. Blanking a populated table to skeleton on every refetch loses
381
+ the user's place and flashes — so it doesn't.
382
+
383
+ A function `items` source triggers this automatically while it resolves; so does
384
+ the `loading` prop. `aria-busy` is set on the table in both cases, and the
385
+ `loading-text` announcement fires in both.
386
+
387
+ **While loading, the header and the rows are `inert`** — a refetch is about to
388
+ replace the rows, so editing, selecting or sorting them races the incoming data.
389
+ Same shape as `BbButton`: blocked by default, with `interactive-while-loading` as the
390
+ escape hatch for tables that must stay usable during a background refresh (a poll
391
+ that must not interrupt an open editor, say). The loading announcement is never
392
+ inert, so the busy state is always conveyed.
393
+
394
+ The `#loading` slot replaces the **skeleton**, so it follows the skeleton's rule:
395
+ it renders only when there is nothing to keep. (Scope: the current, possibly
396
+ stale `items` — handy for matching the previous row count.) When `items` resolves
397
+ empty the table shows its no-data row — localized default text, overridable via
398
+ `no-data-text` or fully via the `#no-data` slot. That slot fills the table's own
399
+ full-width empty-state cell (already centred, colspan already counted), so pass
400
+ the content — a message, a "clear filters" button — not a `<tr>` of your own.
401
+
402
+ Always set `loading-text`: it is the loading row's `aria-live` announcement
403
+ and — unlike `no-data-text` — it has **no built-in default**; omitting it
404
+ announces an empty string.
405
+
406
+ **Skeletons and a custom empty state**
836
407
 
837
- // When folding an offset, use a calc-safe length base (no `auto`) so the
838
- // column width = offset(s) + the column's own span.
839
- const width = offsets.length
840
- ? `calc(${[...offsets, span ?? '0px'].join(' + ')})`
841
- : (span ?? 'auto');
408
+ ```vue
409
+ <template>
410
+ <div class="flex max-w-2xl flex-col gap-2">
411
+ <div class="flex gap-1.5">
412
+ <BbButton size="sm" variant="outline" @click="loading = !loading">
413
+ Toggle loading
414
+ </BbButton>
415
+ <BbButton size="sm" variant="ghost" @click="empty = !empty">
416
+ Toggle empty
417
+ </BbButton>
418
+ </div>
419
+
420
+ <div
421
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
422
+ >
423
+ <BbTable
424
+ caption="Team members"
425
+ :columns="columns"
426
+ compact
427
+ item-value="id"
428
+ :items="empty ? [] : members"
429
+ :loading="loading"
430
+ loading-text="Loading members…"
431
+ >
432
+ <template #no-data>
433
+ <p class="p-3 text-center text-sm text-[color:var(--bb-text-muted)]">
434
+ No members match your filters yet.
435
+ </p>
436
+ </template>
437
+ </BbTable>
438
+ </div>
439
+ </div>
440
+ </template>
441
+ <script setup lang="ts">
442
+ import { ref } from 'vue';
443
+ import { BbButton, BbTable } from 'bitboss-ui';
444
+ import type { BbTableColumn } from 'bitboss-ui';
445
+
446
+ type Member = { id: string; name: string; email: string; role: string };
447
+
448
+ // `skeleton` shapes each column's placeholder while `loading` is true so the
449
+ // skeleton mirrors the real content (avatar + text, a badge, plain text).
450
+ const columns: Array<BbTableColumn<Member>> = [
451
+ { key: 'name', label: 'Name', skeleton: 'avatar-text' },
452
+ { key: 'email', label: 'Email' },
453
+ { key: 'role', label: 'Role', skeleton: 'badge', width: 90 },
454
+ ];
455
+
456
+ const members: Member[] = [
457
+ { id: 'u1', name: 'Ada Lovelace', email: 'ada@acme.io', role: 'Admin' },
458
+ { id: 'u2', name: 'Grace Hopper', email: 'grace@acme.io', role: 'Editor' },
459
+ { id: 'u3', name: 'Ken Thompson', email: 'ken@acme.io', role: 'Viewer' },
460
+ ];
461
+
462
+ const loading = ref(false);
463
+ const empty = ref(false);
464
+ </script>
465
+ ```
842
466
 
843
- tracks.push({ key, width });
844
- });
467
+ ### Async items and filters
845
468
 
846
- if (props.actions) {
847
- tracks.push({ key: 'actions', width: structural('actions') });
848
- }
469
+ Set `items` to a provider function and the table runs it, tracking loading
470
+ itself:
849
471
 
850
- return tracks;
851
- });
472
+ ```ts
473
+ type ItemsProvider<T> = (
474
+ prefill: boolean,
475
+ selected?: any // the current selection (v-model)
476
+ ) => T[] | Promise<T[]>;
477
+ ```
852
478
 
853
- /**
854
- * Standalone edge-offset custom properties consumed by the first/last cell
855
- * padding and the first/last column width. Set only when an offset is active;
856
- * otherwise the CSS default of `0px` makes both calcs collapse to their base.
857
- */
858
- const structuralOffset = computed<Record<string, string>>(() => {
859
- const style: Record<string, string> = {};
860
- if (offsetStartValue.value) {
861
- style['--bb-table-offset-start'] = offsetStartValue.value;
862
- }
863
- if (offsetEndValue.value) {
864
- style['--bb-table-offset-end'] = offsetEndValue.value;
865
- }
866
- return style;
867
- });
479
+ - On mount it is called `(true, selection)`; when `dependencies` (or the
480
+ `items` prop itself) change it is called `(false, selection)`, debounced by
481
+ `deps-debounce-time`.
482
+ - `dependencies` are compared **by value** (hashed), so `[filters]` refetches
483
+ only when content actually changes. Never wire a manual
484
+ `watch(filter, refetch)` — that is exactly what `dependencies` replaces.
485
+ - Out-of-order responses are discarded (only the latest request writes rows);
486
+ a rejected provider is logged via `console.error` and the previous rows are
487
+ kept — surface failures yourself (e.g. `useToast`) if the user must know.
488
+ - A plain array bypasses the provider machinery entirely.
868
489
 
869
- const containerStyle = computed(() => {
870
- const vars: Record<string, string> = {
871
- ...cssVars.value,
872
- ...structuralOffset.value,
873
- };
490
+ **Debounced search through the items function**
874
491
 
875
- if (naturalTotalWidth.value) {
876
- vars['--bb-table-natural-width'] = naturalTotalWidth.value;
877
- }
492
+ ```vue
493
+ <template>
494
+ <div class="flex max-w-2xl flex-col gap-2">
495
+ <BbTextInput
496
+ v-model="search"
497
+ clearable
498
+ compact
499
+ label="Search invoices"
500
+ label-mode="outside"
501
+ placeholder="Client or number…"
502
+ />
503
+
504
+ <!-- `items` is an async function; the table re-runs it (debounced) whenever
505
+ an entry in `dependencies` changes, showing skeletons while it awaits. -->
506
+ <div
507
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
508
+ >
509
+ <BbTable
510
+ caption="Invoices"
511
+ :columns="columns"
512
+ compact
513
+ :dependencies="[search]"
514
+ :deps-debounce-time="300"
515
+ item-value="id"
516
+ :items="fetchInvoices"
517
+ loading-text="Searching…"
518
+ />
519
+ </div>
520
+ </div>
521
+ </template>
522
+ <script setup lang="ts">
523
+ import { ref } from 'vue';
524
+ import { BbTable, BbTextInput } from 'bitboss-ui';
525
+ import type { BbTableColumn } from 'bitboss-ui';
878
526
 
879
- if (fillValue.value > 0) {
880
- vars['--bb-table-fill'] = `${fillValue.value}px`;
881
- }
527
+ type Invoice = { id: string; number: string; client: string; amount: string };
882
528
 
883
- return vars;
884
- });
529
+ const columns: Array<BbTableColumn<Invoice>> = [
530
+ { key: 'number', label: 'Invoice', width: 110 },
531
+ { key: 'client', label: 'Client' },
532
+ { key: 'amount', label: 'Amount', align: 'right', width: 100 },
533
+ ];
885
534
 
886
- /** Reads the header cells in render order: [select?, ...data, actions?]. */
887
- const headerCells = () =>
888
- Array.from(
889
- tableRef.value?.querySelectorAll<HTMLElement>(
890
- ':scope > thead > tr.bb-table-header-row > th'
891
- ) ?? []
535
+ const catalogue: Invoice[] = [
536
+ {
537
+ id: '1',
538
+ number: 'INV-1041',
539
+ client: 'Northwind Traders',
540
+ amount: '€4,200',
541
+ },
542
+ { id: '2', number: 'INV-1042', client: 'Globex', amount: '€980' },
543
+ { id: '3', number: 'INV-1043', client: 'Initech', amount: '€15,600' },
544
+ { id: '4', number: 'INV-1044', client: 'Umbrella', amount: '€2,750' },
545
+ ];
546
+
547
+ const search = ref('');
548
+
549
+ // Real apps forward the query to the backend; here we filter a local catalogue
550
+ // after a short simulated round-trip.
551
+ const fetchInvoices = async (): Promise<Invoice[]> => {
552
+ await new Promise((resolve) => setTimeout(resolve, 250));
553
+ const term = search.value.trim().toLowerCase();
554
+ if (!term) return catalogue;
555
+ return catalogue.filter(
556
+ (invoice) =>
557
+ invoice.client.toLowerCase().includes(term) ||
558
+ invoice.number.toLowerCase().includes(term)
892
559
  );
893
-
894
- /** Measures the rendered columns and publishes them as this table's tracks. */
895
- const measureTracks = () => {
896
- const keys = columnTracks.value.map((track) => track.key);
897
- const tracks: Record<string, string> = {};
898
- headerCells().forEach((cell, index) => {
899
- const key = keys[index];
900
- if (key != null) tracks[key] = `${cell.getBoundingClientRect().width}px`;
901
- });
902
- setTracks(tracks);
903
- };
904
-
905
- /**
906
- * Measures the intrinsic content width of the structural columns across header
907
- * and body. Fixed layout sizes columns from the header row only, so the body is
908
- * required here: the actions header is `sr-only`, while the real control lives
909
- * in the body. The measured child width is independent of the column width, so
910
- * pinning the column to it converges instead of oscillating.
911
- */
912
- const measureStructuralFit = () => {
913
- const table = tableRef.value;
914
- if (!table) return;
915
-
916
- const fit = (selector: string) => {
917
- let max = 0;
918
- table.querySelectorAll<HTMLElement>(selector).forEach((cell) => {
919
- // Sum each child's own intrinsic width (plus horizontal margins) — an
920
- // actions cell holds several side-by-side controls and the column must
921
- // fit them all. Crucially this is *not* their shared extent: the extent
922
- // depends on how the cell spreads them across its width, which feeds the
923
- // column width back into its own measurement and runs away into the
924
- // thousands. A child's own width does not depend on the cell, so this
925
- // converges. Out-of-flow children (e.g. an `sr-only` label parked far
926
- // off-screen) are skipped so they cannot inflate the result.
927
- let content = 0;
928
- cell.querySelectorAll<HTMLElement>(':scope > *').forEach((child) => {
929
- const childStyle = getComputedStyle(child);
930
- if (
931
- childStyle.position === 'absolute' ||
932
- childStyle.position === 'fixed'
933
- )
934
- return;
935
- const margin =
936
- parseFloat(childStyle.marginLeft) +
937
- parseFloat(childStyle.marginRight);
938
- content += child.getBoundingClientRect().width + (margin || 0);
939
- });
940
- const style = getComputedStyle(cell);
941
- const padding =
942
- parseFloat(style.paddingLeft) + parseFloat(style.paddingRight);
943
- max = Math.max(max, content + padding);
944
- });
945
- return max > 0 ? `${Math.ceil(max)}px` : null;
946
- };
947
-
948
- // Scoped to this table's own header/body rows: an unscoped descendant query
949
- // would also match the structural cells of a nested table living in an expand
950
- // row, letting a child's actions/select width hijack this table's column.
951
- structuralFit.select = props.selectable
952
- ? fit(
953
- ':scope > thead > tr > th.bb-table-header--select, :scope > tbody > tr > td.bb-table-data__cell--select'
954
- )
955
- : null;
956
- structuralFit.actions = props.actions
957
- ? fit(
958
- ':scope > thead > tr > th.bb-table-header--actions, :scope > tbody > tr > td.bb-table__cell--actions'
959
- )
960
- : null;
961
560
  };
561
+ </script>
562
+ ```
962
563
 
963
- const measure = () => {
964
- measureStructuralFit();
965
- measureTracks();
966
- };
967
-
968
- /**
969
- * Proportional `<col>` widths derived from an auto-layout probe, one entry per
970
- * data column (`null` for frozen columns — they keep their declared width).
971
- *
972
- * For each auto column the formula is:
973
- * width = max(minimum, available × (minimum / minimumTotal))
974
- *
975
- * where `minimum` is the column's `min-content` width (what it shrinks to in a
976
- * very narrow container, i.e. the floor below which content would be clipped),
977
- * `available` = containerWidth − frozenColumnWidths − structuralColumnWidths
978
- * and `minimumTotal` = sum of the auto columns' minimums.
979
- *
980
- * So when the container has room (`available ≥ minimumTotal`) the slack is shared
981
- * out proportionally to each column's minimum, scaling them up together; when it
982
- * does not, every column drops to its minimum and the table-level `min-width`
983
- * lets it overflow (the container scrolls) rather than squashing content further.
984
- *
985
- * Re-measured after any DOM mutation (debounced 200 ms) and after any container
986
- * resize (via a dedicated ResizeObserver on the container element).
987
- */
988
- const probeColWidths = ref<(string | null)[]>([]);
989
-
990
- /**
991
- * Each unfrozen column's measured `min-content` width (`null` for frozen). Used
992
- * to content-size the overflow columns of an inheriting table — those past the
993
- * parent's last column, with no track to snap to.
994
- */
995
- const columnMinWidths = ref<(string | null)[]>([]);
996
-
997
- /**
998
- * Leftover px the inheriting fill column adds so the table reaches its parent's
999
- * width. Published as `--bb-table-fill`; `0` when the table already fills or
1000
- * overflows. Derived self-correctingly from the live total (see the probe).
1001
- */
1002
- const fillValue = ref(0);
564
+ On a server-paginated screen the provider forwards filters, sort and page to
565
+ the backend and returns the current slice:
1003
566
 
1004
- /**
1005
- * Total minimum width of the table (all columns at their `min-content`, plus the
1006
- * structural columns). Published as `--bb-table-natural-width` and used as
1007
- * `min-width` on the `<table>` so the table scrolls horizontally only once the
1008
- * container is narrower than this true minimum.
1009
- */
1010
- const naturalTotalWidth = ref<string | null>(null);
1011
-
1012
- const runNaturalWidthProbe = () => {
1013
- const container = containerRef.value;
1014
- if (!container || !fixedLayout.value) return;
1015
- // Runs for a frozen-column table (to share space), or any inheriting table
1016
- // (to content-size overflow columns and drive the fill column's slack).
1017
- if (
1018
- !props.columns.some((col) => col.width != null) &&
1019
- !overflowsParent.value &&
1020
- fillColumnIndex.value < 0
1021
- )
1022
- return;
1023
-
1024
- const containerWidth = container.offsetWidth;
1025
- const dataOffset = props.selectable ? 1 : 0;
1026
-
1027
- // ── Real-table measurements ────────────────────────────────────────────
1028
- // Structural column widths come from measureStructuralFit (already run).
1029
- const selectWidth = structuralFit.select
1030
- ? parseFloat(structuralFit.select)
1031
- : 0;
1032
- const actionsWidth = structuralFit.actions
1033
- ? parseFloat(structuralFit.actions)
1034
- : 0;
1035
-
1036
- // Live rendered widths from the fixed-layout table. `frozenTotal` (declared
1037
- // widths) feeds the stable min-width floor; `nonFlexTotal` (frozen + inherited)
1038
- // is what the flex columns share the remainder of. The inherited part is only
1039
- // used for that `available` figure — never fed back into min-width, since those
1040
- // widths stretch with the table and would otherwise lock min-width above
1041
- // max-content and force a spurious uniform stretch on every column.
1042
- const realThs = headerCells();
1043
- let frozenTotal = 0;
1044
- let nonFlexTotal = 0;
1045
- props.columns.forEach((col, i) => {
1046
- const width = realThs[dataOffset + i]?.getBoundingClientRect().width ?? 0;
1047
- if (col.width != null) frozenTotal += width;
1048
- if (!isFlexColumn(i)) nonFlexTotal += width;
1049
- });
1050
-
1051
- // ── Offscreen probe for the auto columns' minimum (min-content) widths ──
1052
- // The clone's inner table is forced to `width: min-content` and the auto <col>
1053
- // hints are stripped so each <th> reports the narrowest width it can take
1054
- // before content would be clipped, independent of the container size or
1055
- // previous probe cycles.
1056
- const probe = document.createElement('div');
1057
- probe.style.cssText = [
1058
- 'position:absolute',
1059
- 'top:-9999px',
1060
- 'left:-9999px',
1061
- 'visibility:hidden',
1062
- 'pointer-events:none',
1063
- `width:${containerWidth}px`,
1064
- `height:${container.offsetHeight}px`,
1065
- 'overflow:visible',
1066
- ].join(';');
1067
-
1068
- const clone = container.cloneNode(true) as HTMLElement;
1069
- clone.classList.remove('bb-table--fixed');
1070
-
1071
- // Expand rows are full-width (colspan) and hold nested tables; under the
1072
- // auto-layout probe their content would back-pressure this table's own
1073
- // columns, so drop them and measure this table's columns in isolation.
1074
- clone
1075
- .querySelectorAll('.bb-table-expand__row')
1076
- .forEach((row) => row.remove());
1077
-
1078
- // Auto <col> hints are cleared so a previous cycle's computed width cannot
1079
- // act as a minimum and inflate the measurement; frozen hints are left intact
1080
- // since their declared width is a genuine floor for that column.
1081
- const probeColEls = Array.from(clone.querySelectorAll<HTMLElement>('col'));
1082
- props.columns.forEach((col, i) => {
1083
- if (col.width == null) {
1084
- const probeCol = probeColEls[dataOffset + i];
1085
- if (probeCol) probeCol.style.width = '';
1086
- }
567
+ ```ts
568
+ const fetchInvoices = async (): Promise<Invoice[]> => {
569
+ const res = await api.invoices.list({
570
+ search: search.value,
571
+ status: status.value,
572
+ orderBy: sort.value, // the v-model:sort array, forwarded as-is
573
+ page: page.value,
574
+ perPage,
1087
575
  });
576
+ totalItems.value = res.total;
577
+ return res.data;
578
+ };
579
+ // dependencies: [search, status, sort]
580
+ // page is NOT listed: a provider table already refetches when the shared
581
+ // (table-id) page moves — list only your own filters and sort here.
582
+ ```
1088
583
 
1089
- const innerTable = clone.querySelector<HTMLElement>('table');
1090
- if (innerTable) {
1091
- innerTable.style.width = 'min-content';
1092
- innerTable.style.minWidth = '0';
1093
- }
584
+ This is the shape of every server-driven listing — the full assembly (filters,
585
+ pagination, confirm-delete, toasts) is the records workspace recipe
586
+ (`./recipes/vue/records-workspace.md`), which works unchanged under Inertia or
587
+ Nuxt since the table itself never touches the router.
1094
588
 
1095
- probe.appendChild(clone);
1096
- document.body.appendChild(probe);
589
+ ### Pagination
1097
590
 
1098
- const headerRow = probe.querySelector<HTMLElement>(
1099
- 'table thead tr.bb-table-header-row'
1100
- );
1101
- const ths = headerRow
1102
- ? Array.from(headerRow.querySelectorAll<HTMLElement>(':scope > th'))
1103
- : [];
1104
-
1105
- // Min-content of every unfrozen column (0 for frozen). Independent of the live
1106
- // table's width, so it is a stable basis for both the flex share and the
1107
- // min-width floor — unlike the rendered inherited widths.
1108
- const minContents = props.columns.map((col, i) => {
1109
- if (col.width != null) return 0;
1110
- const th = ths[dataOffset + i];
1111
- return th ? th.getBoundingClientRect().width : 0;
1112
- });
591
+ `BbTable` never slices its own data — you do, or the server does. Feed it the
592
+ current slice as `items` and pass `page` / `per-page` / `total-items` so it
593
+ can announce `aria-rowcount` and per-row absolute indices (when `items` is an
594
+ array, `per-page` and `total-items` default to its length — so a one-page
595
+ table is accessible with just `page`). Pair it with `BbPagination` bound to
596
+ the same page ref.
1113
597
 
1114
- document.body.removeChild(probe);
598
+ **BbPagination alongside the table**
1115
599
 
1116
- // ── Proportional <col> widths ──────────────────────────────────────────
1117
- // Only the flex columns share `available`; the inherited columns are already
1118
- // laid out and excluded via `nonFlexTotal`.
1119
- const flexMinTotal = props.columns.reduce(
1120
- (s, _, i) => s + (isFlexColumn(i) ? minContents[i] : 0),
1121
- 0
1122
- );
1123
- const available = containerWidth - nonFlexTotal - selectWidth - actionsWidth;
1124
-
1125
- // Share the available space proportionally to each flex column's minimum, but
1126
- // never drop below that minimum — when space runs out the table overflows
1127
- // (and the container scrolls) instead of clipping content.
1128
- //
1129
- // Widths are kept sub-pixel (not rounded up): the raw shares sum to exactly
1130
- // `available`, whereas ceiling each column would over-allocate by up to a
1131
- // pixel apiece and, under `table-layout: fixed`, push the table a few px past
1132
- // the container and trip the horizontal scrollbar. `toFixed` only trims the
1133
- // float tail, well below the 1px a scrollbar needs.
1134
- const px = (n: number) => `${Number(n.toFixed(3))}px`;
1135
- probeColWidths.value = props.columns.map((_, i) => {
1136
- if (!isFlexColumn(i)) return null;
1137
- const minimum = minContents[i];
1138
- if (flexMinTotal <= 0) return px(minimum);
1139
- const share = available > 0 ? available * (minimum / flexMinTotal) : 0;
1140
- return px(Math.max(minimum, share));
1141
- });
600
+ ```vue
601
+ <template>
602
+ <div class="flex max-w-2xl flex-col gap-2">
603
+ <!-- `page` / `per-page` / `total-items` power the table's accessibility
604
+ announcements (aria-rowcount / aria-rowindex); the actual slicing is
605
+ yours (or the server's). -->
606
+ <div
607
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
608
+ >
609
+ <BbTable
610
+ caption="Invoices"
611
+ :columns="columns"
612
+ compact
613
+ item-value="id"
614
+ :items="pageRows"
615
+ :page="page"
616
+ :per-page="perPage"
617
+ :total-items="invoices.length"
618
+ />
619
+ </div>
620
+ <div class="flex items-center justify-between gap-2">
621
+ <p class="text-xs text-[color:var(--bb-text-muted)]">
622
+ {{ rangeStart }}–{{ rangeEnd }} of {{ invoices.length }}
623
+ </p>
624
+ <BbPagination v-model="page" :total-pages="totalPages" />
625
+ </div>
626
+ </div>
627
+ </template>
628
+ <script setup lang="ts">
629
+ import { computed, ref } from 'vue';
630
+ import { BbPagination, BbTable } from 'bitboss-ui';
631
+ import type { BbTableColumn } from 'bitboss-ui';
632
+
633
+ type Invoice = { id: string; number: string; client: string; amount: string };
634
+
635
+ const columns: Array<BbTableColumn<Invoice>> = [
636
+ { key: 'number', label: 'Invoice', width: 110 },
637
+ { key: 'client', label: 'Client' },
638
+ { key: 'amount', label: 'Amount', align: 'right', width: 100 },
639
+ ];
640
+
641
+ const invoices: Invoice[] = Array.from({ length: 13 }, (_, index) => ({
642
+ id: String(index + 1),
643
+ number: `INV-${1041 + index}`,
644
+ client: ['Northwind Traders', 'Globex', 'Initech', 'Umbrella'][index % 4],
645
+ amount: `€${((index + 1) * 640).toLocaleString('en-IE')}`,
646
+ }));
1142
647
 
1143
- // Min-content per unfrozen column, for content-sizing an inheriting table's
1144
- // overflow columns (no parent track to snap to).
1145
- columnMinWidths.value = minContents.map((w) => (w > 0 ? px(w) : null));
1146
-
1147
- // Leftover the inheriting fill column must absorb to reach the parent's width.
1148
- // `parentTotal` is the parent table's full width (the expand cell this table
1149
- // sits in); the container itself shrink-wraps to the table, so it can't be the
1150
- // reference. Self-correcting: the live total already includes last cycle's
1151
- // fill, so adding it back recovers the intrinsic total and the result is the
1152
- // same once the table fills (0 when it already fills or overflows).
1153
- if (fillColumnIndex.value >= 0) {
1154
- const parentTotal = Object.values(parentNode.value?.tracks ?? {}).reduce(
1155
- (s, w) => s + (parseFloat(w) || 0),
1156
- 0
1157
- );
1158
- const liveTotal = realThs.reduce(
1159
- (s, th) => s + th.getBoundingClientRect().width,
1160
- 0
1161
- );
1162
- fillValue.value = Math.max(0, parentTotal - liveTotal + fillValue.value);
1163
- } else {
1164
- fillValue.value = 0;
1165
- }
648
+ const perPage = 5;
649
+ const page = ref(1);
650
+ const totalPages = computed(() => Math.ceil(invoices.length / perPage));
651
+ const rangeStart = computed(() => (page.value - 1) * perPage + 1);
652
+ const rangeEnd = computed(() =>
653
+ Math.min(page.value * perPage, invoices.length)
654
+ );
1166
655
 
1167
- // Table min-width = frozen (declared) widths + every unfrozen column's
1168
- // min-content + structural. Built only from values that do not depend on the
1169
- // table's own width, so it can never exceed max-content and force a stretch.
1170
- const minContentTotal = minContents.reduce((s, w) => s + w, 0);
1171
- const naturalTotal =
1172
- frozenTotal + minContentTotal + selectWidth + actionsWidth;
1173
- naturalTotalWidth.value =
1174
- naturalTotal > 0 ? `${Math.ceil(naturalTotal)}px` : null;
1175
- };
656
+ const pageRows = computed(() =>
657
+ invoices.slice((page.value - 1) * perPage, page.value * perPage)
658
+ );
659
+ </script>
660
+ ```
1176
661
 
1177
- let naturalWidthDebounceTimer: ReturnType<typeof setTimeout> | null = null;
1178
- let mutationObserver: MutationObserver | undefined;
1179
-
1180
- /** Invalidates the cached natural width and schedules a fresh probe. */
1181
- const scheduleNaturalWidthProbe = () => {
1182
- if (naturalWidthDebounceTimer !== null)
1183
- clearTimeout(naturalWidthDebounceTimer);
1184
- naturalWidthDebounceTimer = setTimeout(() => {
1185
- naturalWidthDebounceTimer = null;
1186
- runNaturalWidthProbe();
1187
- }, 200);
1188
- };
662
+ For no-prop-drilling wiring, give the table an `id` and the pagination a
663
+ matching `table-id`: both join the same table context and stay in sync without
664
+ a shared ref. The pagination props are symmetric across the pair — `page` /
665
+ `per-page` / `total-items` / `total-pages` are accepted by both components and
666
+ publish into the same context, so the server's fields go to whichever one is
667
+ convenient. `total-pages` is a seed for when no row count is known (an
668
+ externally paged array table, or a server that only reports a page count): a
669
+ count derived from a real `total-items` always wins over it. Add `navigation`
670
+ to `BbPagination` to mirror the page in the URL query. The same context is
671
+ scriptable from anywhere via `useBbTableContext`
672
+ (`./composables/useBbTableContext.md`).
673
+
674
+ ### Sorting
675
+
676
+ Mark columns `sortable` and bind `v-model:sort` — an ordered
677
+ `BbTableSortEntry[]` (`[columnKey, 'asc' | 'desc']`). Header buttons cycle a
678
+ column unsorted → asc → desc → removed; a direction flip updates the entry in
679
+ place, so the column keeps its priority in a multi-column sort and other
680
+ entries are never touched. The array order **is** the priority: first entry
681
+ sorts first. The table only reports the model; **apply it to your data**
682
+ (client-side, as below) or forward it to the server as `orderBy` and refetch
683
+ via `dependencies`.
684
+
685
+ **Sortable headers driving a client-side sort**
1189
686
 
1190
- let resizeObserver: ResizeObserver | undefined;
1191
- /** Observes the container element specifically for size changes so the
1192
- * proportional <col> widths are recomputed whenever the container grows or
1193
- * shrinks — separately from the header-cell observer that drives measureTracks. */
1194
- let containerResizeObserver: ResizeObserver | undefined;
1195
-
1196
- const observeColumns = () => {
1197
- if (!resizeObserver) return;
1198
- resizeObserver.disconnect();
1199
- headerCells().forEach((cell) => resizeObserver?.observe(cell));
1200
- measure();
1201
- };
687
+ ```vue
688
+ <template>
689
+ <div class="flex max-w-2xl flex-col gap-1.5">
690
+ <!-- Mark columns `sortable` and bind `v-model:sort`. The header buttons
691
+ cycle asc → desc → off; the table only REPORTS the model — you sort
692
+ the data. -->
693
+ <div
694
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
695
+ >
696
+ <BbTable
697
+ v-model:sort="sort"
698
+ caption="Invoices"
699
+ :columns="columns"
700
+ compact
701
+ item-value="id"
702
+ :items="sortedInvoices"
703
+ />
704
+ </div>
705
+ <p class="text-xs text-[color:var(--bb-text-muted)]">
706
+ Sort model: <code>{{ JSON.stringify(sort) }}</code>
707
+ </p>
708
+ </div>
709
+ </template>
710
+ <script setup lang="ts">
711
+ import { computed, ref } from 'vue';
712
+ import { BbTable } from 'bitboss-ui';
713
+ import type { BbTableColumn, BbTableSortEntry } from 'bitboss-ui';
1202
714
 
1203
- onMounted(() => {
1204
- resizeObserver = new ResizeObserver(() => measure());
1205
- observeColumns();
1206
-
1207
- // Initial probe runs after the first render so all cells have content.
1208
- nextTick(runNaturalWidthProbe);
1209
-
1210
- // Invalidate the cache whenever rows or cell content change.
1211
- if (tableRef.value) {
1212
- mutationObserver = new MutationObserver(scheduleNaturalWidthProbe);
1213
- mutationObserver.observe(tableRef.value, {
1214
- childList: true,
1215
- subtree: true,
1216
- characterData: true,
1217
- });
1218
- }
715
+ type Invoice = { id: string; number: string; client: string; amount: number };
1219
716
 
1220
- // Re-probe whenever the container itself is resized so that proportional
1221
- // <col> widths stay accurate as the available space changes.
1222
- if (containerRef.value) {
1223
- containerResizeObserver = new ResizeObserver(scheduleNaturalWidthProbe);
1224
- containerResizeObserver.observe(containerRef.value);
1225
- }
717
+ const euro = new Intl.NumberFormat('en-IE', {
718
+ style: 'currency',
719
+ currency: 'EUR',
1226
720
  });
1227
721
 
1228
- onBeforeUnmount(() => {
1229
- resizeObserver?.disconnect();
1230
- containerResizeObserver?.disconnect();
1231
- mutationObserver?.disconnect();
1232
- if (naturalWidthDebounceTimer !== null)
1233
- clearTimeout(naturalWidthDebounceTimer);
722
+ const columns: Array<BbTableColumn<Invoice>> = [
723
+ { key: 'number', label: 'Invoice', sortable: true, width: 110 },
724
+ { key: 'client', label: 'Client', sortable: true },
725
+ {
726
+ key: 'amount',
727
+ label: 'Amount',
728
+ align: 'right',
729
+ sortable: true,
730
+ width: 110,
731
+ formatter: euro.format,
732
+ },
733
+ ];
734
+
735
+ const invoices: Invoice[] = [
736
+ { id: '1', number: 'INV-1041', client: 'Northwind Traders', amount: 4200 },
737
+ { id: '2', number: 'INV-1042', client: 'Globex', amount: 980 },
738
+ { id: '3', number: 'INV-1043', client: 'Initech', amount: 15600 },
739
+ { id: '4', number: 'INV-1044', client: 'Umbrella', amount: 2750 },
740
+ ];
741
+
742
+ const sort = ref<BbTableSortEntry[]>([['amount', 'desc']]);
743
+
744
+ // Apply the ordered sort model to the data — first entry wins ties first.
745
+ const sortedInvoices = computed(() => {
746
+ if (!sort.value.length) return invoices;
747
+ return [...invoices].sort((a, b) => {
748
+ for (const [key, direction] of sort.value) {
749
+ const left = a[key as keyof Invoice];
750
+ const right = b[key as keyof Invoice];
751
+ const compared =
752
+ typeof left === 'number' && typeof right === 'number'
753
+ ? left - right
754
+ : String(left).localeCompare(String(right));
755
+ if (compared !== 0) return direction === 'asc' ? compared : -compared;
756
+ }
757
+ return 0;
758
+ });
1234
759
  });
760
+ </script>
761
+ ```
1235
762
 
1236
- watch(columnTracks, () => nextTick(observeColumns), { flush: 'post' });
1237
-
1238
- /** Builds accessible label for selection controls from row content. */
1239
- const getAccessibleLabel = (columns: MappedCell[]) =>
1240
- [t('table.select').value, ...columns.map((col) => col.content)].join(' ');
1241
-
1242
- const containerTag = computed(() => (props.selectable ? 'fieldset' : 'div'));
1243
-
1244
- const loading = computed(() => props.loading || innerLoading.value);
763
+ A custom `#header:<key>` slot replaces the built-in sort button, but its scope
764
+ hands you `sortOrder` and `toggleSort` so your header can keep sorting alive
765
+ (render your own button and call `toggleSort`). The column-level `sorted`
766
+ field only seeds the header's `aria-sort` when no live model entry exists — it
767
+ does not move the visual indicator; to start pre-sorted, seed `v-model:sort`
768
+ instead (as the example does).
1245
769
 
1246
- const hasProvidedAccessibilityData = computed(
1247
- () => !isNil(props.page) && !isNil(props.perPage) && !isNil(props.totalItems)
1248
- );
770
+ ### Selection and bulk actions
1249
771
 
1250
- const containerClass = computed(() => ({
1251
- 'bb-table': true,
1252
- [`bb-table--align-${props.align}`]: true,
1253
- 'bb-table--compact': props.compact,
1254
- 'bb-table--fixed': fixedLayout.value,
1255
- 'bb-table--inherit-widths': sizeToColumns.value,
1256
- 'bb-table--fixed-header': props.fixedHeaders,
1257
- 'bb-table--loading': loading.value,
1258
- 'bb-table--empty': !options.value.length,
1259
- 'bb-table--selectable': !!props.selectable,
1260
- 'scrollbar-border': true,
1261
- }));
772
+ Add `selectable` for multi-select (`multiple` is the default) bound with
773
+ `v-model` — the model **must** be an array in multiple mode (the table throws
774
+ otherwise). With `item-value` set, the model holds ids rather than whole rows.
775
+ Pass `selectable` a predicate (`(item) => boolean`) to gate specific rows: the
776
+ excluded rows' controls disable and `toggleSelected` refuses them too.
1262
777
 
1263
- const replacementContentSpan = computed(
1264
- () =>
1265
- props.columns.length + Number(!!props.selectable) + Number(!!props.actions)
1266
- );
778
+ To drive a toolbar that lives **outside** the table subtree, give the table an
779
+ `id` and read `useBbTableContext(id).selected` — the handle mirrors the
780
+ table's selection both ways, so a bulk-action bar needs no prop drilling. As
781
+ everywhere in the library, the destructive bulk action is gated by
782
+ `useConfirm` before it touches the data.
1267
783
 
1268
- const slotNamesByKey = computed(() =>
1269
- Object.fromEntries(
1270
- props.columns.map((col) => [col.key, columnKeyToSlotName(col.key)])
1271
- )
1272
- );
1273
- const {
1274
- getter,
1275
- items: internalItems,
1276
- loading: innerLoading,
1277
- } = useItemsGetter({
1278
- items: toRef(props, 'items'),
1279
- });
784
+ **Multi-select with a context-driven bulk bar**
1280
785
 
1281
- const { hasPrefilled } = usePrefill({
1282
- trigger: true,
1283
- fn: async (isPrefill) => {
1284
- await getter(isPrefill, props.modelValue);
786
+ ```vue
787
+ <template>
788
+ <div class="flex max-w-2xl flex-col gap-2">
789
+ <!-- Bulk-action bar lives OUTSIDE the table and reads the selection through
790
+ the shared context by id — no prop drilling. The destructive action is
791
+ gated by `useConfirm`. -->
792
+ <div
793
+ v-if="selectedCount"
794
+ class="flex items-center gap-2 rounded-[var(--bb-radius)] border border-[color:var(--bb-border)] bg-[color:var(--bb-muted)] px-2.5 py-1.5"
795
+ >
796
+ <span class="text-sm">{{ selectedCount }} selected</span>
797
+ <BbButton
798
+ prepend:icon="lucide:trash-2"
799
+ size="sm"
800
+ variant="destructive"
801
+ @click="removeSelected"
802
+ >
803
+ Delete
804
+ </BbButton>
805
+ <BbButton size="sm" variant="ghost" @click="clear">Clear</BbButton>
806
+ </div>
807
+
808
+ <div
809
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
810
+ >
811
+ <BbTable
812
+ id="member-selection"
813
+ v-model="selected"
814
+ caption="Team members"
815
+ :columns="columns"
816
+ compact
817
+ item-value="id"
818
+ :items="members"
819
+ legend="Select team members"
820
+ name="members"
821
+ selectable
822
+ />
823
+ </div>
824
+
825
+ <!--
826
+ Standalone example: it mounts its own host because it has no app root.
827
+ A real app mounts exactly one <BbConfirm /> at the app root, and pages
828
+ mount none. See ai/guides/agent-contract.md § Global Host Rule.
829
+ -->
830
+ <BbConfirm />
831
+ </div>
832
+ </template>
833
+ <script setup lang="ts">
834
+ import { computed, ref } from 'vue';
835
+ import {
836
+ BbButton,
837
+ BbConfirm,
838
+ BbTable,
839
+ useBbTableContext,
840
+ useConfirm,
841
+ } from 'bitboss-ui';
842
+ import type { BbTableColumn } from 'bitboss-ui';
843
+
844
+ type Member = { id: string; name: string; email: string; role: string };
845
+
846
+ const columns: Array<BbTableColumn<Member>> = [
847
+ { key: 'name', label: 'Name' },
848
+ { key: 'email', label: 'Email' },
849
+ { key: 'role', label: 'Role', width: 90 },
850
+ ];
851
+
852
+ const members = ref<Member[]>([
853
+ { id: 'u1', name: 'Ada Lovelace', email: 'ada@acme.io', role: 'Admin' },
854
+ { id: 'u2', name: 'Grace Hopper', email: 'grace@acme.io', role: 'Editor' },
855
+ {
856
+ id: 'u3',
857
+ name: 'Margaret Hamilton',
858
+ email: 'maggie@acme.io',
859
+ role: 'Viewer',
1285
860
  },
1286
- });
1287
-
1288
- const { data: modelValueIndexedByHash } = useIndexById({
1289
- items: toRef(props, 'modelValue'),
1290
- });
1291
-
1292
- const { options } = useBaseOptions({
1293
- disabled: toRef(props, 'disabled'),
1294
- items: internalItems,
1295
- itemText: undefined,
1296
- itemValue: props.itemValue,
1297
- max: props.max,
1298
- selectable: true,
1299
- selectedIndexedByHash: modelValueIndexedByHash,
1300
- });
1301
-
1302
- const onOptionSelected = async (option: BaseOption) => {
1303
- if (props.multiple) {
1304
- emit('update:modelValue', props.modelValue.concat(option.value));
1305
- } else {
1306
- emit('update:modelValue', option.value);
1307
- }
861
+ { id: 'u4', name: 'Ken Thompson', email: 'ken@acme.io', role: 'Editor' },
862
+ ]);
863
+
864
+ // `item-value="id"` gives rows a stable identity, so `selected` holds ids.
865
+ const selected = ref<string[]>([]);
866
+
867
+ // Same id as the table — the handle mirrors the table's selection both ways.
868
+ const ctx = useBbTableContext('member-selection', { selected: [] });
869
+ const selectedCount = computed(() => ctx.selected.value?.length ?? 0);
870
+ const clear = () => (ctx.selected.value = []);
871
+
872
+ const { confirm } = useConfirm();
873
+
874
+ // Destructive bulk action: `await` the decision, act only on a yes.
875
+ const removeSelected = async () => {
876
+ const ids: string[] = ctx.selected.value ?? [];
877
+ const ok = await confirm({
878
+ title: `Delete ${ids.length} ${ids.length === 1 ? 'member' : 'members'}?`,
879
+ text: 'They will lose access to this workspace immediately.',
880
+ variant: 'destructive',
881
+ yes: { text: 'Delete', 'prepend:icon': 'lucide:trash-2' },
882
+ no: 'Cancel',
883
+ });
884
+ if (!ok) return;
885
+ members.value = members.value.filter((member) => !ids.includes(member.id));
886
+ clear();
1308
887
  };
888
+ </script>
889
+ ```
1309
890
 
1310
- const onOptionUnselected = async (option: BaseOption) => {
1311
- if (props.multiple) {
1312
- const copy = { ...modelValueIndexedByHash.value };
1313
- delete copy[option.valueHash];
1314
- emit('update:modelValue', Object.values(copy));
1315
- } else {
1316
- emit('update:modelValue', null);
1317
- }
1318
- };
891
+ More selection surface, all verified behavior:
892
+
893
+ - Every body slot exposes `toggleSelected`, so a bespoke control (a whole-row
894
+ button, a status chip) drives selection through the same path as the
895
+ checkbox — it even works without `selectable` (that prop only governs the
896
+ built-in selection column); explicit blocks (`disabled`, `readonly`, a
897
+ predicate exclusion) still apply.
898
+ - `item:selected` / `item:unselected` fire with the toggled row's value on
899
+ every user toggle — use them for per-row side effects; keep `v-model` as
900
+ the state.
901
+ - A selectable table renders as a `<fieldset>`; give it a `legend`. With
902
+ `name` set, each selected value renders a hidden input (objects are
903
+ JSON-stringified), so the table submits inside a plain HTML form.
904
+ - `disabled` makes the whole table inert; `readonly` leaves controls focusable
905
+ but ignores changes.
906
+
907
+ #### Selection across refetches
908
+
909
+ By default a `dependencies`-driven refetch replaces the rows but leaves
910
+ `v-model` untouched, so ids that just left the result set silently stay
911
+ selected. Set **`enforce-coherence`** — the same prop, same meaning as on the
912
+ selects and option groups — and after every load the table prunes its
913
+ row-keyed models of values whose row is gone: `v-model`,
914
+ `v-model:unselected-items`, `v-model:highlighted` and
915
+ `v-model:expanded-items`. Values whose rows survive the refetch stay put;
916
+ single-value models (`:multiple="false"`, single highlight) reset to `null`.
917
+ Set `item-value` so the match runs on stable identity.
918
+
919
+ One hard exclusion: **never combine it with pagination fed through
920
+ `dependencies`**. Every page turn is a refetch, so state held on rows of other
921
+ pages — cross-page selections, the whole `select-all` + `unselected-items`
922
+ pattern — would be pruned away. For paginated tables (or any custom rule),
923
+ reconcile in the provider instead: the table calls it `(prefill, selected)`
924
+ with the current selection, which is exactly where the new rows are known.
925
+
926
+ **Provider-side reconciliation, for when enforce-coherence doesn't fit**
1319
927
 
1320
- useHashedWatcher(
1321
- () => [props.dependencies, props.items],
1322
- async () => {
1323
- if (!hasPrefilled.value) {
1324
- return;
1325
- }
1326
- await getter(false, props.modelValue);
1327
- },
928
+ ```vue
929
+ <template>
930
+ <div class="flex max-w-2xl flex-col gap-2">
931
+ <div class="flex items-center gap-1.5">
932
+ <!-- Mirrored `false-value`: one role filter is always active. -->
933
+ <BbButton
934
+ v-for="filter in roleFilters"
935
+ :key="filter.value"
936
+ v-model="role"
937
+ :false-value="filter.value"
938
+ group
939
+ size="xs"
940
+ :true-value="filter.value"
941
+ variant="outline"
942
+ >
943
+ {{ filter.label }}
944
+ </BbButton>
945
+ <span class="ml-auto text-xs text-[color:var(--bb-text-muted)]">
946
+ {{ selected.length }} selected
947
+ </span>
948
+ </div>
949
+
950
+ <!-- The role change refetches via `dependencies`. `enforce-coherence`
951
+ would prune `v-model` for free here — this example keeps it off to
952
+ show the provider-side reconcile, the pattern paginated tables
953
+ (where the prop must stay off) still need. -->
954
+ <div
955
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
956
+ >
957
+ <BbTable
958
+ v-model="selected"
959
+ caption="Workspace members"
960
+ :columns="columns"
961
+ compact
962
+ :dependencies="[role]"
963
+ :deps-debounce-time="100"
964
+ item-value="id"
965
+ :items="fetchMembers"
966
+ legend="Select members"
967
+ loading-text="Loading members…"
968
+ selectable
969
+ />
970
+ </div>
971
+ </div>
972
+ </template>
973
+ <script setup lang="ts">
974
+ import { ref } from 'vue';
975
+ import { BbButton, BbTable } from 'bitboss-ui';
976
+ import type { BbTableColumn } from 'bitboss-ui';
977
+
978
+ type Role = 'Admin' | 'Editor' | 'Viewer';
979
+ type RoleFilter = 'all' | Role;
980
+ type Member = { id: string; name: string; email: string; role: Role };
981
+
982
+ const roleFilters: Array<{ label: string; value: RoleFilter }> = [
983
+ { label: 'All', value: 'all' },
984
+ { label: 'Admins', value: 'Admin' },
985
+ { label: 'Editors', value: 'Editor' },
986
+ ];
987
+
988
+ const columns: Array<BbTableColumn<Member>> = [
989
+ { key: 'name', label: 'Name' },
990
+ { key: 'email', label: 'Email' },
991
+ { key: 'role', label: 'Role', width: 90 },
992
+ ];
993
+
994
+ const directory: Member[] = [
995
+ { id: 'u1', name: 'Ada Lovelace', email: 'ada@acme.io', role: 'Admin' },
996
+ { id: 'u2', name: 'Grace Hopper', email: 'grace@acme.io', role: 'Editor' },
1328
997
  {
1329
- debounce: props.depsDebounceTime,
998
+ id: 'u3',
999
+ name: 'Margaret Hamilton',
1000
+ email: 'maggie@acme.io',
1001
+ role: 'Editor',
1002
+ },
1003
+ { id: 'u4', name: 'Ken Thompson', email: 'ken@acme.io', role: 'Viewer' },
1004
+ { id: 'u5', name: 'Barbara Liskov', email: 'barbara@acme.io', role: 'Admin' },
1005
+ ];
1006
+
1007
+ const role = ref<RoleFilter>('all');
1008
+ const selected = ref<string[]>([]);
1009
+
1010
+ // Without `enforce-coherence` a refetch replaces the rows but leaves the
1011
+ // selection untouched. The table calls the provider `(prefill, selected)`,
1012
+ // so the provider is the one spot where both the current selection and the new
1013
+ // rows are known — drop the ids that left the result set. Reconcile on every
1014
+ // call, not just refetches: `prefill` is `true` for every fetch a component
1015
+ // starts itself, so it can't tell an initial load from a `dependencies` refetch
1016
+ // (only a user-typed query reads `false`, and a table has no search field).
1017
+ const fetchMembers = async (
1018
+ _prefill: boolean,
1019
+ selectedIds?: string[]
1020
+ ): Promise<Member[]> => {
1021
+ await new Promise((resolve) => setTimeout(resolve, 200));
1022
+ const rows =
1023
+ role.value === 'all'
1024
+ ? directory
1025
+ : directory.filter((member) => member.role === role.value);
1026
+ if (selectedIds?.length) {
1027
+ selected.value = selectedIds.filter((id) =>
1028
+ rows.some((member) => member.id === id)
1029
+ );
1330
1030
  }
1331
- );
1031
+ return rows;
1032
+ };
1033
+ </script>
1034
+ ```
1332
1035
 
1333
- const mapColumnToCell = (
1334
- column: BbTableColumn<Item>,
1335
- item: BaseOption
1336
- ): MappedCell => {
1337
- const slotName = slotNamesByKey.value[column.key];
1338
- const align = column.align ?? 'left';
1339
- let content = getItemValue(item.item, column.key);
1036
+ ### Single select, select-all and limits
1340
1037
 
1341
- if (column.formatter && (!isNil(content) || column.formatOnNull !== false)) {
1342
- content = column.formatter(content, column.key, item.item);
1343
- }
1344
- if (column.placeholder && isNil(content)) {
1345
- content = column.placeholder;
1346
- }
1038
+ `:multiple="false"` renders radios and the model becomes one value (or
1039
+ `null`). Label the radio column with `select-text` (visually hidden).
1347
1040
 
1348
- return {
1349
- align,
1350
- classes: mergeCellClasses(
1351
- [`bb-table-data__cell--${align}`],
1352
- props.tdClass,
1353
- column.tdClass,
1354
- content,
1355
- column.key,
1356
- item.item
1357
- ),
1358
- content,
1359
- key: column.key,
1360
- label: column.label,
1361
- slotName,
1362
- };
1363
- };
1041
+ **Pick one plan via radio rows**
1364
1042
 
1365
- const mappedHeaders = computed(() =>
1366
- props.columns.map((column: BbTableColumn<Item>): MappedHeader => {
1367
- const align = column.align ?? 'left';
1368
- const slotName = `header:${slotNamesByKey.value[column.key]}`;
1369
- const sortDirection = column.sorted
1370
- ? column.sorted === 'asc'
1371
- ? 'ascending'
1372
- : 'descending'
1373
- : undefined;
1374
-
1375
- return {
1376
- align,
1377
- key: column.key,
1378
- label: column.label,
1379
- slotName,
1380
- classes: mergeHeaderClasses(
1381
- [`bb-table-header--${align}`],
1382
- props.thClass,
1383
- column.thClass
1384
- ),
1385
- sortDirection,
1386
- };
1387
- })
1388
- );
1043
+ ```vue
1044
+ <template>
1045
+ <div class="flex max-w-xl flex-col gap-1.5">
1046
+ <!-- `:multiple="false"` renders radios and the model is ONE value (or
1047
+ null) — not an array. `select-text` labels the radio column for
1048
+ screen readers. -->
1049
+ <div
1050
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
1051
+ >
1052
+ <BbTable
1053
+ v-model="planId"
1054
+ caption="Available plans"
1055
+ :columns="columns"
1056
+ compact
1057
+ item-value="id"
1058
+ :items="plans"
1059
+ legend="Choose a plan"
1060
+ :multiple="false"
1061
+ name="plan"
1062
+ select-text="Choose a plan"
1063
+ selectable
1064
+ />
1065
+ </div>
1066
+ <p class="text-sm text-[color:var(--bb-text-muted)]">
1067
+ Chosen plan:
1068
+ <span class="font-medium text-[color:var(--bb-text)]">{{
1069
+ chosen ? chosen.name : 'none yet'
1070
+ }}</span>
1071
+ </p>
1072
+ </div>
1073
+ </template>
1074
+ <script setup lang="ts">
1075
+ import { computed, ref } from 'vue';
1076
+ import { BbTable } from 'bitboss-ui';
1077
+ import type { BbTableColumn } from 'bitboss-ui';
1389
1078
 
1390
- const mappedItems = computed<MappedItem[]>(() => {
1391
- let expandedRows = 0;
1392
- return options.value.map((item: BaseOption, index: number): MappedItem => {
1393
- const disabled =
1394
- !props.selectable ||
1395
- (typeof props.selectable === 'function' &&
1396
- !props.selectable(item.item)) ||
1397
- !!item.disabled;
1398
-
1399
- // How many rows have passed
1400
- const indexPageOffset = (props.page ?? 0 - 1) * (props.perPage ?? 0);
1401
- const headRowOffset = 1;
1402
- const increment = index + 1;
1403
- const rowIndex = indexPageOffset + headRowOffset + increment + expandedRows;
1404
-
1405
- // Create a projection of the columns and run all formatter logic so
1406
- // we just display content in the template
1407
- const cols: MappedCell[] = props.columns.map((column) =>
1408
- mapColumnToCell(column, item)
1409
- );
1410
- const expanded = expandedItemsIndexedByHash.value[item.valueHash] ?? false;
1411
- if (expanded) {
1412
- expandedRows++;
1413
- }
1414
- const res = {
1415
- accessibleLabel: getAccessibleLabel(cols),
1416
- cols,
1417
- expanded,
1418
- onRowClick: (event: MouseEvent) =>
1419
- emit('click:row', event, item, !!item.selected),
1420
- onRowContextMenu: (event: MouseEvent) =>
1421
- emit('contextmenu:row', event, item, !!item.selected),
1422
- onRowDblClick: (event: MouseEvent) =>
1423
- emit('dblclick:row', event, item, !!item.selected),
1424
- item: item.item,
1425
- text: item.text,
1426
- rowIndex,
1427
- valueHash: item.valueHash,
1428
- value: item.value,
1429
- selected: item.selected,
1430
- disabled,
1431
- };
1432
- return res;
1433
- });
1434
- });
1079
+ type Plan = { id: string; name: string; seats: string; price: string };
1435
1080
 
1436
- const selectAll = defineModel<boolean>('selectAll', {
1437
- required: false,
1438
- default: false,
1439
- });
1081
+ const columns: Array<BbTableColumn<Plan>> = [
1082
+ { key: 'name', label: 'Plan' },
1083
+ { key: 'seats', label: 'Seats', width: 110 },
1084
+ { key: 'price', label: 'Price / month', align: 'right', width: 120 },
1085
+ ];
1440
1086
 
1441
- const expandedItems = defineModel<any[]>('expandedItems', {
1442
- required: false,
1443
- default: () => [],
1444
- });
1087
+ const plans: Plan[] = [
1088
+ { id: 'starter', name: 'Starter', seats: 'Up to 3', price: '€0' },
1089
+ { id: 'growth', name: 'Growth', seats: 'Up to 25', price: '€49' },
1090
+ { id: 'scale', name: 'Scale', seats: 'Unlimited', price: '€199' },
1091
+ ];
1445
1092
 
1446
- const { data: expandedItemsIndexedByHash } = useIndexById({
1447
- items: expandedItems,
1448
- });
1093
+ // Single-select model: one id (via `item-value`) or null.
1094
+ const planId = ref<string | null>(null);
1449
1095
 
1450
- const onExpandControllerKeydown = (event: KeyboardEvent, valueHash: string) => {
1451
- if (!(event.target instanceof HTMLElement)) return;
1452
- if (event.target instanceof HTMLButtonElement) return;
1453
- if (event.key !== 'Enter' && event.key !== ' ') return;
1454
- event.preventDefault();
1455
- toggleExpandedFor(valueHash);
1456
- };
1096
+ const chosen = computed(() => plans.find((plan) => plan.id === planId.value));
1097
+ </script>
1098
+ ```
1457
1099
 
1458
- const getExpandControllerProps = (item: MappedItem) => ({
1459
- 'aria-controls': isNil(expandedItemsIndexedByHash.value[item.valueHash])
1460
- ? undefined
1461
- : `expanded_${item.valueHash}`,
1462
- 'aria-expanded': item.expanded ? 'true' : 'false',
1463
- onClick: (event: MouseEvent) => {
1464
- event.stopPropagation();
1465
- toggleExpandedFor(item.valueHash);
1466
- },
1467
- onKeydown: (event: KeyboardEvent) =>
1468
- onExpandControllerKeydown(event, item.valueHash),
1469
- });
1100
+ `max` caps a multi-select: once reached, every unselected row's checkbox
1101
+ disables until something is deselected.
1102
+
1103
+ Multi-select tables show a **select-all** checkbox in the header
1104
+ (`v-model:select-all`; hide it with `disable-select-all`, relabel with
1105
+ `select-all-label`). Select-all is designed for the server-side "all across
1106
+ pages" pattern: when it is on, every row reads as selected and unchecking a
1107
+ row adds its value to `v-model:unselected-items` — so your bulk endpoint
1108
+ receives `{ all: true, except: unselectedItems }` instead of thousands of ids.
1109
+ Toggling select-all clears both `modelValue` and `unselected-items`; the
1110
+ header checkbox shows an indeterminate state for partial selections.
1111
+
1112
+ Two real constraints to design around (bugs documented from the test suite):
1113
+
1114
+ - The select-all header checkbox **never renders its checked state** — it
1115
+ emits and drives row state correctly, but stays visually unchecked; the
1116
+ indeterminate dash is what users see for select-all with exclusions.
1117
+ - Turning select-all on does **not** re-emit `update:modelValue` when the
1118
+ model is already empty. Treat `select-all` + `unselected-items` as the
1119
+ source of truth in all-mode; don't infer "everything selected" from
1120
+ `modelValue`.
1121
+
1122
+ **Native `<form>` submission + select-all:** the table renders one hidden
1123
+ input per value in `modelValue`, so a plain form POST works for explicit
1124
+ selections. It does **not** serialize the select-all/exclusion pattern — in
1125
+ all-mode `modelValue` is empty, so the form posts nothing even though every
1126
+ row appears selected. For "all across pages", submit via a handler that reads
1127
+ `{ all, except: unselectedItems }` from the v-models, not via native form
1128
+ fields.
1129
+
1130
+ ### Row expansion
1131
+
1132
+ Track open rows with `v-model:expanded-items` (an array of row values — set
1133
+ `item-value` so expansion is stable across refetches) and render their detail
1134
+ through the `#expand` slot, a full-width row below the main one. Trigger it
1135
+ from the `#actions` slot (enable the column with `actions`, label it with
1136
+ `actions-text`): the scope hands you `toggleExpanded` — a **callback** — plus
1137
+ the read-only `expanded` flag. Drive open/close through the callback; the
1138
+ `expanded` prop is one-way state, not something to mutate.
1139
+
1140
+ **Expandable detail rows**
1470
1141
 
1471
- const toggleExpandedFor = (valueHash: string) => {
1472
- const linkedOption = options.value.find((o) => o.valueHash === valueHash);
1473
- if (!linkedOption) return;
1474
- if (valueHash in expandedItemsIndexedByHash.value) {
1475
- expandedItems.value = expandedItems.value.filter(
1476
- (o) => hash(o) !== valueHash
1477
- );
1478
- } else {
1479
- expandedItems.value = [...expandedItems.value, linkedOption.value];
1480
- }
1142
+ ```vue
1143
+ <template>
1144
+ <!-- Track open rows with `v-model:expanded-items`. The `#actions` scope hands
1145
+ you `toggleExpanded` (a callback) and `expanded` (read-only) — drive the
1146
+ toggle through the callback, never by mutating the slot prop. -->
1147
+ <div
1148
+ class="max-w-2xl overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
1149
+ >
1150
+ <BbTable
1151
+ v-model:expanded-items="expanded"
1152
+ actions
1153
+ actions-text="Details"
1154
+ caption="Invoices"
1155
+ :columns="columns"
1156
+ compact
1157
+ item-value="id"
1158
+ :items="invoices"
1159
+ >
1160
+ <template #actions="{ expanded: isOpen, toggleExpanded }">
1161
+ <BbButton size="xs" variant="ghost" @click="toggleExpanded">
1162
+ {{ isOpen ? 'Hide' : 'Details' }}
1163
+ </BbButton>
1164
+ </template>
1165
+
1166
+ <template #expand="{ item }">
1167
+ <div class="bg-[color:var(--bb-muted)] px-3 py-2.5 text-sm">
1168
+ <p class="font-medium">{{ item.client }} · {{ item.number }}</p>
1169
+ <p class="text-xs text-[color:var(--bb-text-muted)]">
1170
+ Issued {{ item.issuedAt }} · net 30 · {{ item.lines }} line items
1171
+ </p>
1172
+ </div>
1173
+ </template>
1174
+ </BbTable>
1175
+ </div>
1176
+ </template>
1177
+ <script setup lang="ts">
1178
+ import { ref } from 'vue';
1179
+ import { BbButton, BbTable } from 'bitboss-ui';
1180
+ import type { BbTableColumn } from 'bitboss-ui';
1181
+
1182
+ type Invoice = {
1183
+ id: string;
1184
+ number: string;
1185
+ client: string;
1186
+ issuedAt: string;
1187
+ amount: string;
1188
+ lines: number;
1481
1189
  };
1482
1190
 
1483
- watch(selectAll, () => {
1484
- unselectedItems.clear();
1485
- emit('update:unselectedItems', []);
1486
- emit('update:modelValue', []);
1487
- });
1191
+ const columns: Array<BbTableColumn<Invoice>> = [
1192
+ { key: 'number', label: 'Invoice', width: 110 },
1193
+ { key: 'client', label: 'Client' },
1194
+ { key: 'amount', label: 'Amount', align: 'right', width: 100 },
1195
+ ];
1488
1196
 
1489
- const indeterminate = computed(() => {
1490
- if (props.multiple && props.modelValue.length > 0 && !props.selectAll) {
1491
- return true;
1492
- }
1493
- if (selectAll.value && unselectedItems.size > 0) {
1494
- return true;
1495
- }
1496
- return false;
1497
- });
1197
+ const invoices: Invoice[] = [
1198
+ {
1199
+ id: '1',
1200
+ number: 'INV-1041',
1201
+ client: 'Northwind Traders',
1202
+ issuedAt: '02/06/2026',
1203
+ amount: '€4,200',
1204
+ lines: 6,
1205
+ },
1206
+ {
1207
+ id: '2',
1208
+ number: 'INV-1042',
1209
+ client: 'Globex',
1210
+ issuedAt: '11/06/2026',
1211
+ amount: '€980',
1212
+ lines: 2,
1213
+ },
1214
+ {
1215
+ id: '3',
1216
+ number: 'INV-1043',
1217
+ client: 'Initech',
1218
+ issuedAt: '19/05/2026',
1219
+ amount: '€15,600',
1220
+ lines: 11,
1221
+ },
1222
+ ];
1498
1223
 
1499
- const unselectedItems = reactive(new Set<string>());
1500
- watch(
1501
- () => props.unselectedItems,
1502
- () => {
1503
- unselectedItems.clear();
1504
- props.unselectedItems.forEach((i) => {
1505
- unselectedItems.add(hash(i));
1506
- });
1507
- }
1508
- );
1224
+ // Holds the expanded rows' values (ids, via `item-value`).
1225
+ const expanded = ref<string[]>([]);
1226
+ </script>
1227
+ ```
1509
1228
 
1510
- const onInputChange = (item: BaseOption) => {
1511
- if (isSelected(item)) {
1512
- onOptionUnselected(item);
1513
- emit('item:unselected', item.value);
1514
- unselectedItems.add(item.valueHash);
1515
- emit('update:unselectedItems', [...props.unselectedItems, item.value]);
1516
- return;
1517
- }
1518
- if (!selectAll.value) {
1519
- onOptionSelected(item);
1520
- }
1521
- emit('item:selected', item.value);
1522
- unselectedItems.delete(item.valueHash);
1523
- emit(
1524
- 'update:unselectedItems',
1525
- props.unselectedItems.filter((i) => hash(i) !== hash(item.value))
1526
- );
1527
- };
1229
+ For a custom toggle control, `v-bind` the scope's `expandProps` onto it: you
1230
+ get `aria-expanded`/`aria-controls` kept in sync, a click handler that stops
1231
+ propagation (so the row click underneath doesn't also toggle highlight), and
1232
+ Enter/Space handling for non-button elements. The nested-table example below
1233
+ uses it.
1234
+
1235
+ ### Nested tables that align with the parent
1236
+
1237
+ A `BbTable` rendered inside another's `#expand` slot is independent by
1238
+ default. Add `inherit-column-widths` and it snaps its columns to the parent's
1239
+ tracks, so a line-items breakdown reads as a continuation of the invoice row
1240
+ above it (this also forces fixed layout). The mechanics:
1241
+
1242
+ - `inherit-column-widths` — `true` inherits from the nearest ancestor table;
1243
+ a string targets a specific ancestor's `id` (useful when an intermediate
1244
+ table has a different column count).
1245
+ - By default child columns map one-to-one onto the parent's data columns. A
1246
+ column's `snap` remaps it: a number is a start track (`2` occupies the
1247
+ parent's third data region), `[start, end]` spans a range (fractions
1248
+ allowed; `end: -1` runs to the end of the data region). Structural columns
1249
+ (`select`, actions) are accounted for automatically.
1250
+ - A child with **more** columns than the parent content-sizes the extras and
1251
+ scrolls; with fewer, the last width-free column absorbs the slack so both
1252
+ tables end flush.
1253
+ - **When the parent has `selectable`/`actions` columns, snap every child
1254
+ column** (`snap: base` … `base + n`, where `base` is `1` under a selectable
1255
+ parent, `0` otherwise). A width-free last column becomes the fill track and
1256
+ absorbs the parent's actions region — right-aligned content then sits under
1257
+ the expand/actions control instead of under the parent's last data column.
1258
+ With every column snapped, the parent's select and actions tracks fold into
1259
+ the child's first/last cell as _padding_, so the child's content stays flush
1260
+ with the parent's data grid on both edges (a child without its own select
1261
+ column starts flush with the parent's first data column — the inset itself
1262
+ reads as hierarchy).
1263
+ - **Scope any page-level cell-padding overrides to the parent's own cells.** A
1264
+ bare `:deep(tbody td:first-child)` also matches the nested table's cells and
1265
+ clobbers the offset padding it computes to stay aligned — use direct-child
1266
+ combinators (`:deep(> table > tbody > tr > td:first-child)`) so the child
1267
+ keeps its structural padding.
1268
+
1269
+ **Invoice lines aligned under the invoice columns**
1528
1270
 
1529
- const isSelected = (item: BaseOption): boolean => {
1530
- if (unselectedItems.has(item.valueHash)) return false;
1531
- return item.selected || selectAll.value;
1271
+ ```vue
1272
+ <template>
1273
+ <!-- The nested table opts into `inherit-column-widths`: its columns snap to
1274
+ the parent's tracks, so line items line up under the invoice columns.
1275
+ The toggle binds the scope's `expandProps` for wired-up aria state. -->
1276
+ <div
1277
+ class="max-w-2xl overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
1278
+ >
1279
+ <BbTable
1280
+ v-model:expanded-items="expanded"
1281
+ actions
1282
+ actions-text="Line items"
1283
+ caption="Invoices"
1284
+ :columns="columns"
1285
+ compact
1286
+ item-value="id"
1287
+ :items="invoices"
1288
+ >
1289
+ <template #actions="{ expanded: isOpen, expandProps }">
1290
+ <BbButton
1291
+ v-bind="expandProps"
1292
+ :append:icon="isOpen ? 'lucide:chevron-up' : 'lucide:chevron-down'"
1293
+ size="xs"
1294
+ variant="ghost"
1295
+ >
1296
+ Lines
1297
+ </BbButton>
1298
+ </template>
1299
+
1300
+ <template #expand="{ item }">
1301
+ <div class="bg-[color:var(--bb-muted)] px-1 py-1">
1302
+ <BbTable
1303
+ :caption="`Line items of ${item.number}`"
1304
+ :columns="lineColumns"
1305
+ compact
1306
+ inherit-column-widths
1307
+ item-value="id"
1308
+ :items="item.lines"
1309
+ />
1310
+ </div>
1311
+ </template>
1312
+ </BbTable>
1313
+ </div>
1314
+ </template>
1315
+ <script setup lang="ts">
1316
+ import { ref } from 'vue';
1317
+ import { BbButton, BbTable } from 'bitboss-ui';
1318
+ import type { BbTableColumn } from 'bitboss-ui';
1319
+
1320
+ type Line = { id: string; description: string; qty: number; amount: string };
1321
+ type Invoice = {
1322
+ id: string;
1323
+ number: string;
1324
+ client: string;
1325
+ amount: string;
1326
+ lines: Line[];
1532
1327
  };
1533
1328
 
1534
- /**
1535
- * When using this component for submission we add a bunch of hidden inputs
1536
- * so the submitted inputs is on par with v-model
1537
- */
1538
-
1539
- /**
1540
- * This is used to keep the value compatible to common html expected values.
1541
- * Convert to string everything that's not but do not double encode strings
1542
- */
1543
- const makeInputValue = when(
1544
- (item: unknown) => typeof item !== 'string',
1545
- JSON.stringify
1546
- );
1547
- const hiddenInputs = computed(() =>
1548
- [].concat(props.modelValue ?? []).map((current: unknown) => ({
1549
- disabled: props.disabled,
1550
- name: props.name,
1551
- type: 'hidden' as const,
1552
- value: makeInputValue(current),
1553
- }))
1554
- );
1555
-
1556
- /** Varied line widths for skeleton (percent) to avoid a mechanical look */
1557
- const skeletonLineWidths = [70, 45, 85, 55];
1329
+ const columns: Array<BbTableColumn<Invoice>> = [
1330
+ { key: 'number', label: 'Invoice', width: 110 },
1331
+ { key: 'client', label: 'Client' },
1332
+ { key: 'amount', label: 'Amount', align: 'right', width: 100 },
1333
+ ];
1558
1334
 
1559
- const skeletonLength = computed(() =>
1560
- clamp(options.value.length || props.columns.length || 1, 4, 10)
1561
- );
1335
+ const lineColumns: Array<BbTableColumn<Line>> = [
1336
+ { key: 'description', label: 'Description' },
1337
+ { key: 'qty', label: 'Qty', width: 60 },
1338
+ { key: 'amount', label: 'Amount', align: 'right', width: 100 },
1339
+ ];
1562
1340
 
1563
- watch(
1564
- () => [mappedItems.value, props.fixedColumns],
1565
- () => {
1566
- if (tableRef.value && props.fixedColumns?.length) {
1567
- applyFixedTableColumns(tableRef.value, props.fixedColumns);
1568
- }
1341
+ const invoices: Invoice[] = [
1342
+ {
1343
+ id: '1',
1344
+ number: 'INV-1041',
1345
+ client: 'Northwind Traders',
1346
+ amount: '€4,200',
1347
+ lines: [
1348
+ {
1349
+ id: '1a',
1350
+ description: 'Design retainer — June',
1351
+ qty: 1,
1352
+ amount: '€3,000',
1353
+ },
1354
+ {
1355
+ id: '1b',
1356
+ description: 'Icon set extension',
1357
+ qty: 24,
1358
+ amount: '€1,200',
1359
+ },
1360
+ ],
1569
1361
  },
1570
- { flush: 'post' }
1571
- );
1572
- </script>
1362
+ {
1363
+ id: '2',
1364
+ number: 'INV-1042',
1365
+ client: 'Globex',
1366
+ amount: '€980',
1367
+ lines: [
1368
+ { id: '2a', description: 'Accessibility audit', qty: 1, amount: '€980' },
1369
+ ],
1370
+ },
1371
+ ];
1573
1372
 
1574
- <style lang="postcss">
1575
- @import './index.css';
1576
- </style>
1373
+ const expanded = ref<string[]>([]);
1374
+ </script>
1577
1375
  ```
1578
1376
 
1579
- ## Types
1377
+ ### Row highlight (master–detail)
1580
1378
 
1581
- ```ts
1582
- import type { Classes } from '@/types/Classes';
1583
- import type { Option } from '@/types/Option';
1584
- import type { InputHTMLAttributes } from 'vue';
1379
+ `v-model:highlighted` is a parallel, UI-intent state — "the record whose
1380
+ details are open" — independent of selection. Clicking anywhere on a row
1381
+ toggles it: in single mode a new click replaces the previous highlight and
1382
+ clicking the highlighted row clears it back to `null`; `highlight-multiple`
1383
+ turns the model into an array (a non-array value then throws). Two kinds of
1384
+ clicks are ignored on purpose: those inside interactive elements (links,
1385
+ buttons, inputs, labels keep their own semantics) and those ending a text
1386
+ selection (dragging to copy a cell must not mutate state).
1585
1387
 
1586
- /**
1587
- * Recursively builds dot-notation paths from a type's keys.
1588
- * Depth-limited to 3 levels to avoid compiler performance issues.
1589
- */
1590
- type NestedPaths<T, Depth extends unknown[] = []> = Depth['length'] extends 3
1591
- ? never
1592
- : 0 extends 1 & T
1593
- ? string
1594
- : T extends object
1595
- ? T extends any[] | Date | Function | RegExp | Map<any, any> | Set<any>
1596
- ? never
1597
- : {
1598
- [K in Extract<keyof T, string>]:
1599
- | K
1600
- | `${K}.${NestedPaths<NonNullable<T[K]>, [...Depth, unknown]>}`;
1601
- }[Extract<keyof T, string>]
1602
- : never;
1388
+ Highlight ships **unstyled**: the row only gets `aria-current="true"` and the
1389
+ `bb-table-data__row--highlighted` class. Paint the active row yourself —
1390
+ idiomatically through the public `row-class` API against the highlighted
1391
+ value, as here.
1603
1392
 
1604
- /**
1605
- * Suggested keys for a column derived from an Item type.
1606
- * Provides autocomplete for nested dot-notation paths (up to 3 levels)
1607
- * while still allowing arbitrary string keys for virtual columns.
1608
- */
1609
- export type ItemKey<Item = any> = NestedPaths<Item> | (string & {});
1393
+ **Click a row to preview it**
1610
1394
 
1611
- /**
1612
- * Skeleton placeholder style for the loading state.
1613
- * Matches the expected content type for a better loading experience.
1614
- */
1615
- export type SkeletonType =
1616
- | 'text'
1617
- | 'avatar'
1618
- | 'avatar-text'
1619
- | 'image'
1620
- | 'badge';
1621
-
1622
- export type BaseColumn<Item = any> = {
1623
- /**
1624
- * When set to false the formatter will not run on nullish values.
1625
- * By default is true so you can create virtual columns with keys that do not
1626
- * reference a prop in the `item` and still extract data from the other
1627
- * arguments in the formatting function.
1628
- */
1629
- formatOnNull?: boolean;
1630
-
1631
- /**
1632
- * Formatting function used to generate enhanced content.
1633
- * @param content The content generated by the extraction of `Item[key]`
1634
- * @param key Key of the column taken from the column definition.
1635
- * @param item Item of the row.
1636
- */
1637
- formatter?: (content: any, key: string, item: Item) => any;
1638
-
1639
- /**
1640
- * Key identifying the column.
1641
- */
1642
- key: ItemKey<Item>;
1643
-
1644
- /**
1645
- * Text used in the header for this column.
1646
- */
1647
- label: string;
1648
-
1649
- /**
1650
- * Placeholder used when the pipeline returns null content.
1651
- */
1652
- placeholder?: string;
1653
- };
1654
-
1655
- /**
1656
- * Column definition for BbTable. Controls data extraction, formatting, alignment, and styling.
1657
- */
1658
- export type BbTableColumn<Item = any> = BaseColumn<Item> & {
1659
- /**
1660
- * Text alignment of the column.
1661
- */
1662
- align?: 'left' | 'right' | 'center';
1663
-
1664
- /**
1665
- * Skeleton placeholder style shown during loading. Defaults to `'text'`.
1666
- */
1667
- skeleton?: SkeletonType;
1668
-
1669
- /**
1670
- * Defines the sorting direction of the column.
1671
- */
1672
- sorted?: 'asc' | 'desc';
1673
-
1674
- /**
1675
- * Defines the classes to be passed to the `<td>` elements. Can also be a function for dynamic values.
1676
- */
1677
- tdClass?: ColumnClasses<Item>;
1678
-
1679
- /**
1680
- * Defines the classes to be passed to the `<th>`.
1681
- */
1682
- thClass?: Classes;
1683
-
1684
- /**
1685
- * Freezes the column to a fixed width (numbers are treated as px). When
1686
- * omitted the column syncs its width to the parent table's matching track.
1687
- */
1688
- width?: number | string;
1689
-
1690
- /**
1691
- * For a nested table, where this column snaps onto the parent's grid. The
1692
- * parent's columns form snap points where `select` occupies `[0, 1]`, data
1693
- * column `k` occupies `[k + 1, k + 2]` and `actions` occupies `[N + 1, N + 2]`.
1694
- *
1695
- * - A single number is a start snap point that takes one parent track, e.g.
1696
- * `2` → `[2, 3]`. It may skip ahead (`5` jumps straight to the sixth track),
1697
- * and the next column resumes from where it ends.
1698
- * - A `[start, end]` pair snaps between two points; the width is the sum of
1699
- * the fractional parent tracks it covers, so `[3, 4.5]` is the full fourth
1700
- * track plus half of the fifth. `end` of `-1` runs to the end of the data
1701
- * region.
1702
- *
1703
- * When omitted the column chains from the previous column's end to the next
1704
- * whole snap point (a one-to-one inheritance by index).
1705
- */
1706
- snap?: number | [start: number, end: number];
1707
- };
1395
+ ```vue
1396
+ <template>
1397
+ <div class="flex max-w-3xl flex-col gap-3 sm:flex-row sm:gap-3">
1398
+ <!-- Clicking a row toggles `v-model:highlighted` (the "open record" state,
1399
+ separate from selection). Highlight ships unstyled, so we paint the
1400
+ active row ourselves through the public `row-class` API. -->
1401
+ <div
1402
+ class="min-w-0 flex-1 overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
1403
+ >
1404
+ <BbTable
1405
+ v-model:highlighted="highlighted"
1406
+ caption="Invoices"
1407
+ :columns="columns"
1408
+ compact
1409
+ item-value="id"
1410
+ :items="invoices"
1411
+ :row-class="rowClass"
1412
+ />
1413
+ </div>
1414
+
1415
+ <aside
1416
+ class="w-full shrink-0 rounded-[var(--bb-radius)] border border-[color:var(--bb-border)] p-3 text-sm sm:w-52"
1417
+ >
1418
+ <template v-if="active">
1419
+ <p class="font-medium">{{ active.client }}</p>
1420
+ <p class="mt-0.5 text-xs text-[color:var(--bb-text-muted)]">
1421
+ {{ active.number }} · {{ active.amount }}
1422
+ </p>
1423
+ </template>
1424
+ <p v-else class="text-xs text-[color:var(--bb-text-muted)]">
1425
+ Select an invoice to preview it.
1426
+ </p>
1427
+ </aside>
1428
+ </div>
1429
+ </template>
1430
+ <script setup lang="ts">
1431
+ import { computed, ref } from 'vue';
1432
+ import { BbTable } from 'bitboss-ui';
1433
+ import type { BbTableColumn } from 'bitboss-ui';
1708
1434
 
1709
- export type BbTableProps<Item = any> = {
1710
- /**
1711
- * Function that accepts the columns and the current item as
1712
- * arguments and returns a label to be used for accessibility purposes.
1713
- */
1714
- accessibleLabel?: (columns: MappedCell[], item: any) => string;
1715
-
1716
- /**
1717
- * Displays the actions column.
1718
- */
1719
- actions?: boolean;
1720
-
1721
- /**
1722
- * Label used in the header of the actions column.
1723
- */
1724
- actionsText?: string;
1725
-
1726
- /**
1727
- * Text alignment of the columns.
1728
- */
1729
- align?: 'left' | 'center' | 'right';
1730
-
1731
- /**
1732
- * Stable id for this table's width context. When omitted a unique id is
1733
- * generated. Nested tables use the nearest ancestor id to inherit widths.
1734
- */
1735
- id?: string;
1736
-
1737
- /**
1738
- * Opt a nested table into inheriting its column widths from an ancestor
1739
- * table's matching tracks (accounting for `select`/`actions` columns). This
1740
- * also forces `table-layout: fixed`, so it is **off by default** — a nested
1741
- * table renders independently unless you ask for alignment.
1742
- * - `false` (the default): do not inherit; size independently.
1743
- * - `true`: inherit from the nearest ancestor table.
1744
- * - a table id (string): inherit from the specific ancestor in the chain with
1745
- * that id rather than the immediate parent. Use this when an intermediate
1746
- * table has a different column count, so the extra columns would otherwise
1747
- * misalign against it.
1748
- */
1749
- inheritColumnWidths?: boolean | string;
1750
-
1751
- /**
1752
- * Boolean that defines whether to display a "Select all" checkbox.
1753
- */
1754
- allowSelectAll?: boolean;
1755
-
1756
- /**
1757
- * Caption that describes the content of the table. Used for accessibility purposes.
1758
- */
1759
- caption?: string;
1760
-
1761
- /**
1762
- * Array of definitions of how the columns should be rendered.
1763
- *
1764
- */
1765
- columns: BbTableColumn<Item>[];
1766
-
1767
- /**
1768
- * Adds a CSS class that applies a compact style to the component.
1769
- */
1770
- compact?: boolean;
1771
-
1772
- /**
1773
- * Defines an array of dependencies that will trigger actions in the component upon change.
1774
- */
1775
- dependencies?: any[];
1776
-
1777
- /**
1778
- * Timeout used to debounce response to changes to dependencies.
1779
- */
1780
- depsDebounceTime?: number;
1781
-
1782
- /**
1783
- * Disables the component
1784
- */
1785
- disabled?: boolean;
1786
-
1787
- /**
1788
- * Boolean that defines whether to display or hide the caption.
1789
- * By default is true and the caption is hidden.
1790
- */
1791
- displayCaption?: boolean;
1792
-
1793
- /**
1794
- * Defines the table layout as fixed
1795
- * making each column take up the same amount of space.
1796
- */
1797
- fixed?: boolean;
1798
-
1799
- /**
1800
- * Definition for which column should be fixed.
1801
- * It can be an array of index of the column to fix on the left side of the table or
1802
- * an array of objects indicating the index and the position `left` or `right` where to affix the columns.
1803
- */
1804
- fixedColumns?: (number | { index: number; position: 'left' | 'right' })[];
1805
-
1806
- /**
1807
- * Boolean that sets the headers as sticky to the top of the table.
1808
- */
1809
- fixedHeaders?: boolean;
1810
-
1811
- items:
1812
- | Item[]
1813
- | ((prefill: boolean, modelValue?: any[]) => Promise<Item[]>)
1814
- | ((prefill: boolean, modelValue?: any[]) => Item[]);
1815
-
1816
- itemValue?: string | ((item: Item) => any);
1817
-
1818
- /**
1819
- * Text content of the legend.
1820
- */
1821
- legend?: string;
1822
-
1823
- /**
1824
- * Sets the component in a loading state, usually triggering some visual styles.
1825
- */
1826
- loading?: boolean;
1827
-
1828
- /**
1829
- * String displayed while items are being loaded.
1830
- */
1831
- loadingText?: string;
1832
-
1833
- /**
1834
- * Maximum number of items that can be selected.
1835
- */
1836
- max?: number;
1837
-
1838
- /**
1839
- * Used by v-model. Can be any serializable type.
1840
- */
1841
- modelValue?: any;
1842
-
1843
- /**
1844
- * Allows the selection of multiple items.
1845
- */
1846
- multiple?: boolean;
1847
-
1848
- /**
1849
- * Defines the name of the input.
1850
- */
1851
- name?: InputHTMLAttributes['name'];
1852
-
1853
- /**
1854
- * String displayed when there are no items to display.
1855
- */
1856
- noDataText?: string;
1857
-
1858
- /**
1859
- * Current page number starting from 1, used for accessibility purposes.
1860
- */
1861
- page?: number;
1862
-
1863
- /**
1864
- * Number of items per page, used for accessibility purposes.
1865
- */
1866
- perPage?: number;
1867
-
1868
- /**
1869
- * Sets the input in a readonly state.
1870
- */
1871
- readonly?: boolean;
1872
-
1873
- /**
1874
- * Defines whether the "Select all" checkbox is checked.
1875
- */
1876
- selectAll?: boolean;
1877
-
1878
- /**
1879
- * Defines whether the table is selectable.
1880
- * Can be a global boolean that affects all rows or a function
1881
- * that accepts an item and returns a boolean that only affects that item.
1882
- */
1883
- selectable?: boolean | ((item: Item) => boolean);
1884
-
1885
- /**
1886
- * Text of the label used by the "Select all" boolean.
1887
- */
1888
- selectAllLabel?: string;
1889
-
1890
- /**
1891
- * Text of the header used for the radio inputs when the table is not `multiple`.
1892
- */
1893
- selectText?: string;
1894
-
1895
- /**
1896
- * Defines the classes to be passed to the `<td>` elements. Can also be a function for dynamic values.
1897
- */
1898
- tdClass?: ColumnClasses<Item>;
1899
-
1900
- /**
1901
- * Defines the classes to be passed to the `<th>`.
1902
- */
1903
- thClass?: Classes;
1904
-
1905
- /**
1906
- * Total number of items there is.
1907
- */
1908
- totalItems?: number;
1909
-
1910
- /**
1911
- * Items that are not selected.
1912
- */
1913
- unselectedItems?: any[];
1914
- };
1435
+ type Invoice = { id: string; number: string; client: string; amount: string };
1915
1436
 
1916
- /**
1917
- * We allow table data classes to be a function so it can depend on the item
1918
- */
1919
- export type ColumnClasses<Item = any> =
1920
- | Classes
1921
- | ((content: any, key: string, item: Item) => Classes);
1437
+ const columns: Array<BbTableColumn<Invoice>> = [
1438
+ { key: 'number', label: 'Invoice', width: 110 },
1439
+ { key: 'client', label: 'Client' },
1440
+ { key: 'amount', label: 'Amount', align: 'right', width: 100 },
1441
+ ];
1922
1442
 
1923
- /**
1924
- * Projection that represents a row of cells with flags
1925
- * linked to the state of the item
1926
- */
1927
- export type MappedItem = Option & {
1928
- accessibleLabel: string;
1929
- cols: MappedCell[];
1930
- expanded: boolean;
1931
- onRowClick: (event: MouseEvent) => void;
1932
- onRowContextMenu: (event: MouseEvent) => void;
1933
- onRowDblClick: (event: MouseEvent) => void;
1934
- rowIndex: number | undefined;
1935
- };
1936
-
1937
- /**
1938
- * Projection that represents a header
1939
- */
1940
- export type MappedHeader = {
1941
- align: HTMLTableCellElement['align'];
1942
- classes?: Classes;
1943
- key: string;
1944
- label: string;
1945
- slotName: string;
1946
- sortDirection?: 'ascending' | 'descending';
1947
- };
1948
-
1949
- /**
1950
- * Projection that represents a cell of data for display.
1951
- */
1952
- export type MappedCell = {
1953
- align: string;
1954
- classes: Classes;
1955
- content: any;
1956
- key: string;
1957
- label: string;
1958
- slotName: string;
1959
- };
1443
+ const invoices: Invoice[] = [
1444
+ {
1445
+ id: '1',
1446
+ number: 'INV-1041',
1447
+ client: 'Northwind Traders',
1448
+ amount: '€4,200',
1449
+ },
1450
+ { id: '2', number: 'INV-1042', client: 'Globex', amount: '€980' },
1451
+ { id: '3', number: 'INV-1043', client: 'Initech', amount: '€15,600' },
1452
+ ];
1960
1453
 
1961
- export type ExpandControllerProps = {
1962
- 'aria-controls'?: string;
1963
- 'aria-expanded': 'true' | 'false';
1964
- onClick: () => void;
1965
- onKeydown: (event: KeyboardEvent) => void;
1966
- };
1454
+ // `item-value="id"` means `highlighted` is the row's id.
1455
+ const highlighted = ref<string | null>(null);
1967
1456
 
1968
- export type BbTableEvents = {
1969
- (e: 'click:row', event: MouseEvent, item: any, selected: boolean): void;
1970
- (e: 'contextmenu:row', event: MouseEvent, item: any, selected: boolean): void;
1971
- (e: 'dblclick:row', event: MouseEvent, item: any, selected: boolean): void;
1972
- (e: 'update:modelValue', value: any): void;
1973
- (e: 'update:selectAll', value: boolean): void;
1974
- (e: 'update:expandedItems', value: any[]): void;
1975
- (e: 'update:unselectedItems', value: any[]): void;
1976
- (e: 'item:selected', value: any): void;
1977
- (e: 'item:unselected', value: any): void;
1978
- };
1457
+ const active = computed(() =>
1458
+ invoices.find((invoice) => invoice.id === highlighted.value)
1459
+ );
1979
1460
 
1980
- export type BbTableSlots<Item = any> = {
1981
- /**
1982
- * Replaces the entire `<thead>` element. Use when you need full control over the header markup.
1983
- */
1984
- thead?: (props: object) => any;
1985
- /**
1986
- * Replaces the entire `<tbody>` element. Use when you need full control over the body markup.
1987
- */
1988
- tbody?: (props: object) => any;
1989
- /**
1990
- * Replaces the entire `<tfoot>` element. Use when you need full control over the footer markup.
1991
- */
1992
- tfoot?: (props: object) => any;
1993
- /**
1994
- * Replaces the default selection header cell content (checkbox or radio label).
1995
- * @param multiple - Whether the table allows multiple selection.
1996
- * @param selectAll - Whether the "select all" checkbox is currently checked.
1997
- * @param text - The accessible label text for the select-all control.
1998
- */
1999
- 'header:select'?: (props: {
2000
- multiple: boolean;
2001
- selectAll: boolean;
2002
- text: string;
2003
- }) => any;
2004
- /**
2005
- * Replaces the default actions header cell content.
2006
- * @param text - The accessible label text for the actions column header.
2007
- */
2008
- 'header:actions'?: (props: { text: string }) => any;
2009
- /**
2010
- * Content shown while table rows are loading (replaces the skeleton rows).
2011
- * @param items - The current (possibly stale) list of items, useful to match the previous row count.
2012
- */
2013
- loading?: (props: { items: Item[] }) => any;
2014
- /**
2015
- * Content shown when the items list is empty and not loading.
2016
- */
2017
- 'no-data'?: (props: object) => any;
2018
- /**
2019
- * Content rendered in the actions cell for each row.
2020
- * @param expanded - Whether this row is currently expanded.
2021
- * @param expandProps - Bind these onto an expand-toggle button to wire up aria attributes and keyboard handling.
2022
- * @param toggleExpanded - Toggles the expanded state for this row.
2023
- * @param item - The raw item for this row.
2024
- * @param value - The resolved value for this row.
2025
- */
2026
- actions?: (props: {
2027
- expanded?: boolean;
2028
- expandProps?: ExpandControllerProps;
2029
- toggleExpanded?: () => void;
2030
- item: Item;
2031
- value: any;
2032
- }) => any;
2033
- /**
2034
- * Full-width expandable content row rendered below each main row when expanded.
2035
- * @param expanded - Whether this row is currently expanded.
2036
- * @param expandProps - Bind these onto an expand-toggle button to wire up aria attributes and keyboard handling.
2037
- * @param toggleExpanded - Toggles the expanded state for this row.
2038
- * @param item - The raw item for this row.
2039
- * @param value - The resolved value for this row.
2040
- */
2041
- expand?: (props: {
2042
- expanded: boolean;
2043
- expandProps?: ExpandControllerProps;
2044
- toggleExpanded: () => void;
2045
- item: Item;
2046
- value: any;
2047
- }) => any;
2048
- /**
2049
- * Replaces the default checkbox/radio in the selection cell for each row.
2050
- * @param item - The raw item for this row.
2051
- * @param value - The resolved value for this row.
2052
- * @param checked - Whether this row is currently selected.
2053
- * @param disabled - Whether this row's selection control is disabled.
2054
- * @param inputName - The `name` attribute for the selection input.
2055
- * @param readonly - Whether the selection control is read-only.
2056
- */
2057
- select?: (props: {
2058
- item: Item;
2059
- value: any;
2060
- checked: boolean;
2061
- disabled: boolean;
2062
- inputName: string;
2063
- readonly: boolean;
2064
- }) => any;
2065
- } & {
2066
- /**
2067
- * Named slot for a specific column cell (`col-key`) or column header (`header:col-key`).
2068
- * @param classes - CSS classes computed for the cell.
2069
- * @param content - The formatted cell content.
2070
- * @param expanded - Whether this row is currently expanded (body slots only).
2071
- * @param expandProps - Bind these onto an expand-toggle button (body slots only).
2072
- * @param toggleExpanded - Toggles the expanded state for this row (body slots only).
2073
- * @param item - The raw item for this row (body slots only).
2074
- * @param value - The resolved row value (body slots only).
2075
- * @param items - The full items list (header slots only).
2076
- * @param label - The column label (header slots only).
2077
- */
2078
- [key: string]: (props: {
2079
- classes?: Classes[];
2080
- content?: any;
2081
- expanded?: boolean;
2082
- expandProps?: ExpandControllerProps;
2083
- toggleExpanded?: () => void;
2084
- item: Item;
2085
- value?: any;
2086
- items?: Item[];
2087
- label?: string;
2088
- }) => any;
2089
- };
1461
+ // Primary tint for the open row — selection chrome stays on content, not hover.
1462
+ const rowClass = (invoice: Invoice) =>
1463
+ invoice.id === highlighted.value
1464
+ ? 'cursor-pointer bg-[color:color-mix(in_oklab,var(--bb-primary)_8%,var(--bb-panel))]'
1465
+ : 'cursor-pointer';
1466
+ </script>
2090
1467
  ```
2091
1468
 
2092
- ## Styles
2093
-
2094
- ```css
2095
- .bb-table {
2096
- --padding-x: 16px;
2097
- --padding-y: 8px;
2098
- --actions-spacing: 8px;
2099
- /* Raw structural edge offset measured by the component (the parent's
2100
- select/actions track width). Default 0 keeps every calc at its base. */
2101
- --bb-table-offset-start: 0px;
2102
- --bb-table-offset-end: 0px;
2103
- /* Routing of that offset. By default it is "internal": added to the
2104
- first/last cell padding and folded into the first/last column width.
2105
- Swap internal↔external (set internal to 0 and external to the raw offset)
2106
- to pad the whole table instead of the edge cells. */
2107
- --bb-table-offset-internal-start: var(--bb-table-offset-start);
2108
- --bb-table-offset-internal-end: var(--bb-table-offset-end);
2109
- --bb-table-offset-external-start: 0px;
2110
- --bb-table-offset-external-end: 0px;
2111
- /* Slack the fill column adds to reach the parent's width. Reset per table so a
2112
- nested table never inherits an ancestor's value through the cascade. */
2113
- --bb-table-fill: 0px;
2114
- border-collapse: separate;
2115
- border-spacing: 0;
2116
- display: grid;
2117
- /* Override the UA `min-inline-size: min-content` that a <fieldset> container
2118
- (rendered for selectable tables) carries — without this the container can't
2119
- shrink below the table's content width, so it overflows its parent and
2120
- pushes the page instead of scrolling inside its own `overflow-x: auto`. */
2121
- min-width: 0;
2122
- overflow-x: auto;
2123
- padding-inline-end: var(--bb-table-offset-external-end);
2124
- padding-inline-start: var(--bb-table-offset-external-start);
2125
-
2126
- position: relative;
2127
-
2128
- &--align {
2129
- &-left {
2130
- text-align: left;
2131
- }
2132
- &-center {
2133
- text-align: center;
2134
- }
2135
- &-right {
2136
- text-align: right;
2137
- }
2138
- }
2139
-
2140
- &--compact {
2141
- --padding-x: 8px;
2142
- --padding-y: 4px;
2143
- }
2144
-
2145
- &--fixed {
2146
- > table {
2147
- table-layout: fixed;
2148
- width: 100%;
1469
+ ### Keyboard navigation and row events
2149
1470
 
2150
- thead {
2151
- .bb-table-header--actions {
2152
- width: auto;
2153
- }
2154
- }
2155
- }
2156
- }
1471
+ Add `keyboard-navigation` to make the body a single tab stop with a roving
1472
+ tabindex: ArrowUp/ArrowDown move the focused row (PageUp/PageDown stride ten
1473
+ rows, Home/End jump), and
1474
+ Enter/Space activate it through the same path as a mouse click — highlight
1475
+ toggle plus `click:row`. Focus and highlight stay independent: arrowing over
1476
+ rows opens nothing until the user commits. A mouse click re-anchors arrow
1477
+ navigation, focused rows show the standard focus ring, and keys pressed inside
1478
+ interactive descendants (or a nested table) keep their own semantics.
2157
1479
 
2158
- &--fixed-header {
2159
- table {
2160
- thead {
2161
- th {
2162
- position: sticky;
2163
- top: 0;
2164
- z-index: 1;
2165
- }
2166
- th[style*='sticky'] {
2167
- position: sticky;
2168
- top: 0;
2169
- z-index: 2;
2170
- }
2171
- }
2172
- }
2173
- }
2174
-
2175
- &--loading {
2176
- }
1480
+ **Arrow through the audit log, Enter opens**
2177
1481
 
2178
- &--empty {
2179
- }
1482
+ ```vue
1483
+ <template>
1484
+ <div class="flex max-w-2xl flex-col gap-1.5">
1485
+ <!-- `keyboard-navigation` makes the body one tab stop: ↑/↓ move the
1486
+ focused row, Home/End jump, Enter/Space open it (same path as a
1487
+ click, so it toggles `highlighted` and emits `click:row`). -->
1488
+ <div
1489
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
1490
+ >
1491
+ <BbTable
1492
+ v-model:highlighted="openEventId"
1493
+ caption="Audit log"
1494
+ :columns="columns"
1495
+ compact
1496
+ item-value="id"
1497
+ :items="events"
1498
+ keyboard-navigation
1499
+ :row-class="rowClass"
1500
+ />
1501
+ </div>
1502
+ <p class="text-xs text-[color:var(--bb-text-muted)]">
1503
+ Tab into the table, move with the arrow keys, press Enter to open a row.
1504
+ Open event:
1505
+ <span class="font-medium text-[color:var(--bb-text)]">{{
1506
+ openEvent ? `${openEvent.actor} — ${openEvent.action}` : 'none'
1507
+ }}</span>
1508
+ </p>
1509
+ </div>
1510
+ </template>
1511
+ <script setup lang="ts">
1512
+ import { computed, ref } from 'vue';
1513
+ import { BbTable } from 'bitboss-ui';
1514
+ import type { BbTableColumn } from 'bitboss-ui';
2180
1515
 
2181
- &--selectable {
2182
- tr {
2183
- > .bb-table-data__cell--select {
2184
- text-align: center;
2185
- width: 0px;
2186
- }
2187
- }
2188
- }
1516
+ type AuditEvent = { id: string; time: string; actor: string; action: string };
2189
1517
 
2190
- legend {
2191
- }
1518
+ const columns: Array<BbTableColumn<AuditEvent>> = [
1519
+ { key: 'time', label: 'Time', width: 70 },
1520
+ { key: 'actor', label: 'Actor' },
1521
+ { key: 'action', label: 'Action' },
1522
+ ];
2192
1523
 
2193
- table {
2194
- min-width: 100%;
2195
- }
1524
+ const events: AuditEvent[] = [
1525
+ { id: 'e1', time: '09:12', actor: 'ada@acme.io', action: 'Created API key' },
1526
+ {
1527
+ id: 'e2',
1528
+ time: '09:47',
1529
+ actor: 'grace@acme.io',
1530
+ action: 'Changed billing plan',
1531
+ },
1532
+ {
1533
+ id: 'e3',
1534
+ time: '10:03',
1535
+ actor: 'ken@acme.io',
1536
+ action: 'Invited 2 members',
1537
+ },
1538
+ { id: 'e4', time: '10:31', actor: 'ada@acme.io', action: 'Rotated secrets' },
1539
+ ];
2196
1540
 
2197
- /* A nested table inheriting its parent's widths sizes to the sum of its
2198
- columns instead of filling its container, so fixed layout cannot stretch
2199
- them out of alignment. Declared after `--fixed` and the base table rule so
2200
- it overrides their width/min-width at equal specificity. */
2201
- &--inherit-widths {
2202
- /* Never grow past the parent's expand cell: a child with more columns
2203
- than the parent overflows the cell, so cap the container here and let its
2204
- own `overflow-x: auto` scroll the surplus columns inside the expand row
2205
- rather than stretching the ancestor. */
2206
- max-width: 100%;
2207
- min-width: 0;
2208
-
2209
- table {
2210
- min-width: 0;
2211
- /* A *definite* width (not `max-content`) so `table-layout: fixed` stays
2212
- content-independent: inherited columns keep their colgroup widths and
2213
- align with the parent instead of growing to fit content. The fill
2214
- column is `auto`, so fixed layout hands it the leftover; when columns
2215
- overflow, the table grows past 100% and scrolls. */
2216
- width: 100%;
2217
- }
2218
- }
1541
+ const openEventId = ref<string | null>(null);
2219
1542
 
2220
- /* Declared after the base `table { min-width: 100% }` rule so it wins the
2221
- cascade at equal specificity. The probe publishes the natural content width
2222
- of all columns; the table fills its container freely but never compresses
2223
- below that floor — the container's `overflow-x: auto` then shows a
2224
- scrollbar instead of squishing content. */
2225
- &--fixed {
2226
- table {
2227
- min-width: var(--bb-table-natural-width, 0px);
2228
- }
2229
- }
1543
+ const openEvent = computed(() =>
1544
+ events.find((event) => event.id === openEventId.value)
1545
+ );
2230
1546
 
2231
- .bb-table-caption {
2232
- }
1547
+ // Highlight ships unstyled — paint the open row through the public row-class API.
1548
+ const rowClass = (event: AuditEvent) =>
1549
+ event.id === openEventId.value
1550
+ ? 'bg-[color:color-mix(in_oklab,var(--bb-primary)_8%,var(--bb-panel))]'
1551
+ : '';
1552
+ </script>
1553
+ ```
2233
1554
 
2234
- &-header-row {
2235
- }
1555
+ Row events: `click:row`, `dblclick:row` and `contextmenu:row` fire with
1556
+ `(event, row, selected)`. Note that `row` is the table's internal row wrapper,
1557
+ not your item: read the raw record from `row.item` and its resolved value from
1558
+ `row.value`. Reserve these events for side effects that are not navigation —
1559
+ logging, opening a drawer (though `v-model:highlighted` usually covers that
1560
+ without any handler).
2236
1561
 
2237
- &-header {
2238
- border-bottom: 1px solid var(--bb-border);
2239
- color: color-mix(in srgb, var(--bb-text), transparent 40%);
2240
- font-size: 14px;
2241
- font-weight: 500;
2242
- padding: var(--padding-y) var(--padding-x);
1562
+ ### Opening a record
2243
1563
 
2244
- &--center {
2245
- text-align: center;
2246
- }
1564
+ Row navigation goes through a real link component, never a row `@click`
1565
+ handler. Render a `BbButton` in a cell or the actions column with `href` (or
1566
+ `to` in a router app): the user gets a proper anchor, middle-click and
1567
+ open-in-new-tab for free.
2247
1568
 
2248
- &--right {
2249
- text-align: right;
2250
- }
1569
+ **A per-row link via BbButton**
2251
1570
 
2252
- &--select {
2253
- }
1571
+ ```vue
1572
+ <template>
1573
+ <!-- Row-level navigation is offloaded to BbButton: `href` (or `to` in a
1574
+ router app) makes it a real link, not a JS click handler. -->
1575
+ <div
1576
+ class="max-w-2xl overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
1577
+ >
1578
+ <BbTable
1579
+ actions
1580
+ actions-text="Actions"
1581
+ caption="Invoices"
1582
+ :columns="columns"
1583
+ compact
1584
+ item-value="id"
1585
+ :items="invoices"
1586
+ >
1587
+ <template #actions="{ item }">
1588
+ <BbButton
1589
+ append:icon="lucide:arrow-right"
1590
+ :href="`#invoice-${item.id}`"
1591
+ size="xs"
1592
+ variant="outline"
1593
+ >
1594
+ Open
1595
+ </BbButton>
1596
+ </template>
1597
+ </BbTable>
1598
+ </div>
1599
+ </template>
1600
+ <script setup lang="ts">
1601
+ import { BbButton, BbTable } from 'bitboss-ui';
1602
+ import type { BbTableColumn } from 'bitboss-ui';
2254
1603
 
2255
- &--actions {
2256
- width: 0;
1604
+ type Invoice = { id: string; number: string; client: string; amount: string };
2257
1605
 
2258
- .bb-table-header__content {
2259
- }
2260
- }
1606
+ const columns: Array<BbTableColumn<Invoice>> = [
1607
+ { key: 'number', label: 'Invoice', width: 110 },
1608
+ { key: 'client', label: 'Client' },
1609
+ { key: 'amount', label: 'Amount', align: 'right', width: 100 },
1610
+ ];
2261
1611
 
2262
- &__content {
2263
- }
2264
- }
1612
+ const invoices: Invoice[] = [
1613
+ {
1614
+ id: '1041',
1615
+ number: 'INV-1041',
1616
+ client: 'Northwind Traders',
1617
+ amount: '€4,200',
1618
+ },
1619
+ { id: '1042', number: 'INV-1042', client: 'Globex', amount: '€980' },
1620
+ { id: '1043', number: 'INV-1043', client: 'Initech', amount: '€15,600' },
1621
+ ];
1622
+ </script>
1623
+ ```
2265
1624
 
2266
- tbody {
2267
- tr {
2268
- &:not(:last-child) {
2269
- border-bottom: 1px solid var(--bb-border);
2270
- }
2271
- td {
2272
- color: var(--bb-text);
2273
- font-size: 14px;
2274
- font-weight: 400;
2275
- height: var(--bb-table-cell-h);
2276
- padding: var(--padding-y) var(--padding-x);
2277
- }
2278
-
2279
- td.bb-table-expand__cell {
2280
- /* Collapse to the content's height instead of inheriting the data-row
2281
- cell height, so an expand row with no slot content takes no space. */
2282
- height: auto;
2283
- padding: 0;
2284
-
2285
- &:empty {
2286
- display: none;
2287
- }
2288
- }
2289
- }
2290
- }
1625
+ In a router or Inertia app the same button carries the platform's navigation
1626
+ instead of an inert `href`:
2291
1627
 
2292
- /* Apply the structural edge offsets to the first/last *data* column. They
2293
- must land on the data cells, not the absolute first/last child: a select
2294
- or actions column would otherwise absorb the fill and leave the data
2295
- columns misaligned with the parent (and bloat the actions cell).
2296
-
2297
- The offset is clamped at 0 here: a positive fill widens the cell and is
2298
- reserved as extra edge padding, but a *negative* offset (a select/actions
2299
- column reclaimed from this column's width) must not eat into the normal
2300
- `--padding-x` — that only shrinks the column width, not its inner padding. */
2301
- thead tr > th.bb-table-header--edge-start,
2302
- tbody tr > td.bb-table-data__cell--edge-start {
2303
- padding-inline-start: calc(
2304
- var(--padding-x) + max(0px, var(--bb-table-offset-internal-start))
2305
- );
2306
- }
1628
+ ```vue
1629
+ <!-- vue-router / Nuxt -->
1630
+ <template #actions="{ item }">
1631
+ <BbButton :to="{ name: 'invoices.show', params: { id: item.id } }" size="xs">
1632
+ Open
1633
+ </BbButton>
1634
+ </template>
1635
+ ```
2307
1636
 
2308
- thead tr > th.bb-table-header--edge-end,
2309
- tbody tr > td.bb-table-data__cell--edge-end {
2310
- padding-inline-end: calc(
2311
- var(--padding-x) + max(0px, var(--bb-table-offset-internal-end))
2312
- );
2313
- }
1637
+ ```vue
1638
+ <!-- Inertia -->
1639
+ <template #actions="{ item }">
1640
+ <BbButton :href="`/invoices/${item.id}`" size="xs">Open</BbButton>
1641
+ </template>
1642
+ ```
2314
1643
 
2315
- &.bb-table--loading {
2316
- .bb-table-loading__row {
2317
- .bb-table-loading__cell {
2318
- padding: 0 var(--padding-x) !important;
2319
- vertical-align: top;
2320
- }
2321
- }
1644
+ ### Inline editing
2322
1645
 
2323
- .bb-table-loading__row--sr-only {
2324
- .bb-table-loading__cell {
2325
- padding: 0 !important;
2326
- border: none;
2327
- height: 0;
2328
- overflow: hidden;
2329
- }
2330
- }
2331
- }
1646
+ Cells can edit in place — no edit page, no modal. Two commit grammars, one
1647
+ per control kind:
2332
1648
 
2333
- &-skeleton {
2334
- &__row {
2335
- }
1649
+ - **Typed cells** (text, number) bind `:model-value` down and commit on
1650
+ `@change` (blur/Enter) — never `v-model`, which fires per keystroke and
1651
+ turns an autosave into one request per character.
1652
+ - **Option-backed cells** are a badge acting as the trigger of a
1653
+ `BbSelectPopover`: the pick IS the commit. The options resolve once at page
1654
+ level as an array (never a per-row provider — see the BbSelectPopover
1655
+ guide's table section), the badge carries an internal trailing chevron, and
1656
+ while the save runs the badge's `loading` prop swaps the spinner into the
1657
+ chevron's place — same size, nothing shifts.
2336
1658
 
2337
- &__cell {
2338
- height: var(--bb-table-cell-h);
2339
- overflow: hidden;
2340
- padding: var(--padding-y) var(--padding-x);
2341
- vertical-align: middle;
2342
-
2343
- &--select,
2344
- &--actions {
2345
- text-align: center;
2346
- width: 0;
2347
- }
2348
- }
1659
+ The deeper question — who owns the uncommitted value (a draft map with an
1660
+ explicit Save, or an optimistic autosave overlay, or the rows themselves —
1661
+ never) — is the [Inline Edit Playbook](./guides/inline-edit-playbook.md).
1662
+ This example uses the autosave grammar: per-field optimistic overlay,
1663
+ rollback on failure, and overlay retirement inside the items function.
2349
1664
 
2350
- &__cell-content {
2351
- display: flex;
2352
- flex-direction: column;
2353
- gap: 6px;
2354
- justify-content: center;
2355
- max-height: calc(var(--bb-table-cell-h) - var(--padding-y) * 2);
2356
- min-width: 96px;
2357
- }
1665
+ **Autosave cells: commit on blur, spinner in the chevron**
2358
1666
 
2359
- &__line {
2360
- background: linear-gradient(
2361
- 90deg,
2362
- var(--bb-border) 0%,
2363
- var(--bb-border) 35%,
2364
- color-mix(in srgb, var(--bb-border) 60%, white) 50%,
2365
- var(--bb-border) 65%,
2366
- var(--bb-border) 100%
2367
- );
2368
- background-size: 200% 100%;
2369
- border-radius: 6px;
2370
- flex-shrink: 0;
2371
- height: 10px;
2372
- animation: bb-table-skeleton-shimmer 1.8s ease-in-out infinite;
2373
- }
1667
+ ```vue
1668
+ <template>
1669
+ <div class="flex max-w-2xl flex-col gap-1.5">
1670
+ <div
1671
+ class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
1672
+ >
1673
+ <BbTable
1674
+ caption="Sprint tasks — every cell saves itself"
1675
+ :columns="columns"
1676
+ compact
1677
+ :dependencies="[rev]"
1678
+ item-value="id"
1679
+ :items="loadTasks"
1680
+ >
1681
+ <!-- Typed cell: bind down, commit on @change (blur/Enter) — never
1682
+ v-model, which fires per keystroke (one request per character). -->
1683
+ <template #title="{ item }">
1684
+ <BbTextInput
1685
+ compact
1686
+ hide-label
1687
+ label="Title"
1688
+ :model-value="valueOf(item, 'title')"
1689
+ @change="onTitleChange(item, $event)"
1690
+ />
1691
+ </template>
1692
+
1693
+ <!-- Option cell: the badge IS the trigger; the pick IS the commit.
1694
+ One page-level `statuses` array shared by every row (never a
1695
+ per-row provider). While the save runs, `loading` swaps the
1696
+ spinner into the chevron's place. -->
1697
+ <template #status="{ item }">
1698
+ <BbSelectPopover
1699
+ compact
1700
+ disable-writing
1701
+ item-text="label"
1702
+ item-value="value"
1703
+ :items="statuses"
1704
+ :model-value="valueOf(item, 'status')"
1705
+ width="170px"
1706
+ @update:model-value="persist(item, 'status', $event as Status)"
1707
+ >
1708
+ <template #activator="{ props }">
1709
+ <BbBadge
1710
+ append:icon="lucide:chevron-down"
1711
+ :loading="pending[item.id]?.status !== undefined"
1712
+ size="lg"
1713
+ :variant="VARIANT[valueOf(item, 'status')]"
1714
+ >
1715
+ <BbBadgeButton v-bind="props">{{
1716
+ labelFor(valueOf(item, 'status'))
1717
+ }}</BbBadgeButton>
1718
+ </BbBadge>
1719
+ </template>
1720
+ </BbSelectPopover>
1721
+ </template>
1722
+ </BbTable>
1723
+ </div>
1724
+
1725
+ <p v-if="lastFailure" class="text-xs text-[color:var(--bb-danger)]">
1726
+ {{ lastFailure }}
1727
+ </p>
1728
+ <p class="text-xs text-[color:var(--bb-text-muted)]">
1729
+ No Save button anywhere — edits persist per field, optimistically.
1730
+ BB-121's save always fails and rolls back.
1731
+ </p>
1732
+ </div>
1733
+ </template>
1734
+ <script setup lang="ts">
1735
+ import { reactive, ref } from 'vue';
1736
+ import {
1737
+ BbBadge,
1738
+ BbBadgeButton,
1739
+ BbSelectPopover,
1740
+ BbTable,
1741
+ BbTextInput,
1742
+ } from 'bitboss-ui';
1743
+ import type { BadgeVariantType, BbTableColumn } from 'bitboss-ui';
1744
+
1745
+ type Status = 'open' | 'in-progress' | 'blocked' | 'done';
1746
+ type Task = { id: string; ref: string; title: string; status: Status };
1747
+ type EditableKey = 'title' | 'status';
1748
+ type Patch = Partial<Pick<Task, EditableKey>>;
1749
+
1750
+ const statuses: Array<{ value: Status; label: string }> = [
1751
+ { value: 'open', label: 'Open' },
1752
+ { value: 'in-progress', label: 'In progress' },
1753
+ { value: 'blocked', label: 'Blocked' },
1754
+ { value: 'done', label: 'Done' },
1755
+ ];
1756
+
1757
+ const VARIANT: Record<Status, BadgeVariantType> = {
1758
+ open: 'soft-gray',
1759
+ 'in-progress': 'soft-blue',
1760
+ blocked: 'soft-red',
1761
+ done: 'soft-green',
1762
+ };
2374
1763
 
2375
- @media (prefers-reduced-motion: reduce) {
2376
- &__line {
2377
- animation: none;
2378
- background: var(--bb-border);
2379
- }
2380
- }
1764
+ const columns: Array<BbTableColumn<Task>> = [
1765
+ { key: 'ref', label: 'Ref', width: 90 },
1766
+ { key: 'title', label: 'Title' },
1767
+ { key: 'status', label: 'Status', width: 140 },
1768
+ ];
2381
1769
 
2382
- &__placeholder {
2383
- background-color: var(--bb-border);
2384
- border-radius: 6px;
2385
- flex-shrink: 0;
2386
- height: 22px;
2387
- margin: 0 auto;
2388
- min-width: 22px;
2389
- width: 22px;
2390
- }
1770
+ /* ---- Fake server (deterministic: BB-121 always rejects) ---------------- */
2391
1771
 
2392
- &__avatar {
2393
- aspect-ratio: 1;
2394
- background-color: var(--bb-border);
2395
- border-radius: 50%;
2396
- flex-shrink: 0;
2397
- height: calc(var(--bb-table-cell-h) - var(--padding-y) * 2);
2398
- min-width: calc(var(--bb-table-cell-h) - var(--padding-y) * 2);
2399
- width: calc(var(--bb-table-cell-h) - var(--padding-y) * 2);
2400
- }
1772
+ let server: Task[] = [
1773
+ {
1774
+ id: 't-1',
1775
+ ref: 'BB-104',
1776
+ title: 'Popover drops focus on Esc',
1777
+ status: 'open',
1778
+ },
1779
+ {
1780
+ id: 't-2',
1781
+ ref: 'BB-118',
1782
+ title: 'Header misaligns at 320px',
1783
+ status: 'in-progress',
1784
+ },
1785
+ {
1786
+ id: 't-3',
1787
+ ref: 'BB-121',
1788
+ title: 'Token audit for dark mode',
1789
+ status: 'blocked',
1790
+ },
1791
+ {
1792
+ id: 't-4',
1793
+ ref: 'BB-130',
1794
+ title: 'Ship the migration guide',
1795
+ status: 'done',
1796
+ },
1797
+ ];
2401
1798
 
2402
- &__avatar-text {
2403
- align-items: center;
2404
- display: flex;
2405
- gap: 12px;
2406
- max-height: calc(var(--bb-table-cell-h) - var(--padding-y) * 2);
2407
- min-width: 140px;
2408
- }
1799
+ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
2409
1800
 
2410
- &__avatar-text .bb-table-skeleton__avatar {
2411
- width: calc(var(--bb-table-cell-h) - var(--padding-y) * 2);
2412
- }
1801
+ const api = {
1802
+ list: async (): Promise<Task[]> => {
1803
+ await wait(150);
1804
+ return server.map((task) => ({ ...task }));
1805
+ },
1806
+ patch: async (id: string, patch: Patch): Promise<void> => {
1807
+ await wait(400);
1808
+ const row = server.find((task) => task.id === id);
1809
+ if (!row) return;
1810
+ if (row.ref === 'BB-121') throw new Error('save rejected');
1811
+ server = server.map((task) =>
1812
+ task.id === id ? { ...task, ...patch } : task
1813
+ );
1814
+ },
1815
+ };
2413
1816
 
2414
- &__avatar-text .bb-table-skeleton__cell-content {
2415
- flex: 1;
2416
- min-width: 0;
2417
- }
1817
+ /* ---- Autosave state (the Inline Edit Playbook's flagship pattern) ------ */
2418
1818
 
2419
- &__image {
2420
- aspect-ratio: 4 / 3;
2421
- background-color: var(--bb-border);
2422
- border-radius: 8px;
2423
- height: calc(var(--bb-table-cell-h) - var(--padding-y) * 2);
2424
- min-width: 64px;
2425
- width: auto;
2426
- }
1819
+ const rev = ref(0);
1820
+ /** Optimistic values, per row per field — two fields can be in flight independently. */
1821
+ const pending = reactive<Record<string, Patch>>({});
1822
+ const lastFailure = ref('');
2427
1823
 
2428
- &__badge {
2429
- background-color: var(--bb-border);
2430
- border-radius: 999px;
2431
- flex-shrink: 0;
2432
- height: 26px;
2433
- min-width: 72px;
2434
- width: 88px;
2435
- }
2436
- }
1824
+ // `key in` rather than `??` so an overlay holding a committed empty/null
1825
+ // value still wins over the row (matters once a field is clearable).
1826
+ const valueOf = <K extends EditableKey>(item: Task, key: K): Task[K] => {
1827
+ const patch = pending[item.id];
1828
+ return patch && key in patch ? (patch[key] as Task[K]) : item[key];
1829
+ };
2437
1830
 
2438
- @keyframes bb-table-skeleton-shimmer {
2439
- 0% {
2440
- background-position: 200% 0;
2441
- }
2442
- 100% {
2443
- background-position: -200% 0;
1831
+ /**
1832
+ * The overlay is retired HERE, in the fetch, and only for fields the response
1833
+ * confirms — never when the save resolves. Between the save landing and the
1834
+ * reload arriving the table still holds the OLD row; clearing early would
1835
+ * flash the cell new → old → new for the whole reload.
1836
+ */
1837
+ const loadTasks = async (): Promise<Task[]> => {
1838
+ const rows = await api.list();
1839
+ for (const row of rows) {
1840
+ const patch = pending[row.id];
1841
+ if (!patch) continue;
1842
+ for (const key of Object.keys(patch) as EditableKey[]) {
1843
+ if (patch[key] === row[key]) delete patch[key];
2444
1844
  }
1845
+ if (!Object.keys(patch).length) delete pending[row.id];
2445
1846
  }
1847
+ return rows;
1848
+ };
2446
1849
 
2447
- &-no-data {
2448
- &__row {
2449
- }
2450
- &__cell {
2451
- }
2452
- &__text {
1850
+ const persist = async <K extends EditableKey>(
1851
+ item: Task,
1852
+ key: K,
1853
+ next: Task[K]
1854
+ ) => {
1855
+ if (next === item[key]) return; // no-op commit
1856
+ (pending[item.id] ??= {})[key] = next; // optimistic: the UI moves now
1857
+ try {
1858
+ await api.patch(item.id, { [key]: next });
1859
+ rev.value += 1; // reload; loadTasks retires the overlay once it can
1860
+ } catch {
1861
+ // Rollback: drop just this field — the cell reverts to the row's value.
1862
+ delete pending[item.id]?.[key];
1863
+ if (pending[item.id] && !Object.keys(pending[item.id]).length) {
1864
+ delete pending[item.id];
2453
1865
  }
1866
+ lastFailure.value = `${item.ref}: ${key} save failed — rolled back.`;
2454
1867
  }
1868
+ };
2455
1869
 
2456
- &-data {
2457
- &__row {
2458
- }
2459
- &__cell {
2460
- &--center {
2461
- text-align: center;
2462
- }
2463
-
2464
- &--right {
2465
- text-align: right;
2466
- }
2467
-
2468
- &--select {
2469
- }
2470
- }
2471
- }
1870
+ const onTitleChange = (item: Task, event: Event) =>
1871
+ persist(item, 'title', (event.target as HTMLInputElement).value);
2472
1872
 
2473
- &-data-row {
2474
- }
1873
+ const labelFor = (status: Status) =>
1874
+ statuses.find((option) => option.value === status)?.label ?? '—';
1875
+ </script>
2475
1876
 
2476
- &__cell {
2477
- &--select {
2478
- }
1877
+ <!-- Soft status palette — canonical source: ai/guides/design-language.md § Using color.
1878
+ Non-scoped so variant classes on the child badge receive the accents. -->
1879
+ <style>
1880
+ .bb-badge.bb-badge--soft-gray {
1881
+ --accent: #64748b;
1882
+ }
1883
+ .bb-badge.bb-badge--soft-blue {
1884
+ --accent: #2563eb;
1885
+ }
1886
+ .bb-badge.bb-badge--soft-green {
1887
+ --accent: #16a34a;
1888
+ }
1889
+ .bb-badge.bb-badge--soft-red {
1890
+ --accent: #dc2626;
1891
+ }
1892
+ .bb-badge.bb-badge--soft-gray,
1893
+ .bb-badge.bb-badge--soft-blue,
1894
+ .bb-badge.bb-badge--soft-green,
1895
+ .bb-badge.bb-badge--soft-red {
1896
+ --bg: color-mix(in oklab, var(--accent) 15%, var(--bb-panel));
1897
+ --color: color-mix(in oklab, var(--accent) 80%, var(--bb-text));
1898
+ --border-width: 1px;
1899
+ --border-color: color-mix(in oklab, var(--accent) 25%, var(--bb-panel));
1900
+ --ring: color-mix(in oklab, var(--accent) 45%, transparent);
1901
+ }
1902
+ </style>
1903
+ ```
2479
1904
 
2480
- &--actions {
2481
- white-space: nowrap;
2482
- > *:not(:first-child) {
2483
- margin-left: var(--actions-spacing);
2484
- }
2485
- }
2486
- }
1905
+ The status colors are the registered `soft-*` badge variants: the plugin's
1906
+ `badgeVariants` option makes the names type-check, and the example carries
1907
+ their CSS in its own style block — the sanctioned consumer path. The
1908
+ canonical palette lives in the
1909
+ [design language guide](./guides/design-language.md).
1910
+
1911
+ ### Dense and wide tables
1912
+
1913
+ `compact` tightens row padding. `fixed-headers` makes the header stick to the
1914
+ top of the nearest scroll container. `fixed` switches to an equal-width fixed
1915
+ layout. `fixed-columns` pins columns while the rest scroll horizontally: an
1916
+ array of column indices (left-pinned) or `{ index, position: 'left'|'right' }`
1917
+ objects; indices count rendered columns (a selection column is index 0), and
1918
+ an out-of-range index logs a console error and is skipped. When content can't
1919
+ fit, the table keeps every column at its measured minimum and overflows its
1920
+ container instead of clipping — this is by design, not a bug to work around.
1921
+
1922
+ **The consumer overflow contract has two parts.** Wrap the table in an
1923
+ `overflow-x: auto` element (below), **and** make sure that wrapper is actually
1924
+ free to shrink below the table's content width. Inside a flex or grid layout,
1925
+ an item's automatic minimum size defaults to its own content's size along the
1926
+ container's main axis — so a `BbTable` inside an unconstrained flex row (a
1927
+ page shell's content column, a flex-direction: row split view) pushes that
1928
+ whole row wider instead of scrolling, even with `overflow-x: auto` already on
1929
+ the wrapper. Give that ancestor `min-width: 0` explicitly to let it shrink and
1930
+ hand scrolling to the table's own wrapper. (This only bites the flex **row**
1931
+ axis: a wrapper that is a cross-axis child of a `flex-direction: column`
1932
+ parent already has a definite width and doesn't need it — check which axis
1933
+ your wrapper actually sits on before sprinkling `min-width: 0` everywhere.)
1934
+
1935
+ **Compact rows with a sticky header**
2487
1936
 
2488
- &-check,
2489
- &-radio {
2490
- &__label {
2491
- cursor: pointer;
2492
- display: inline-block;
2493
- transform: translateY(2px);
2494
- user-select: none;
2495
-
2496
- &.bb-table {
2497
- &-check,
2498
- &-radio {
2499
- &__label--disabled {
2500
- cursor: default;
2501
- visibility: hidden;
2502
- }
2503
- }
2504
- }
2505
-
2506
- &.bb-table-radio__label--disabled {
2507
- cursor: default;
2508
- }
2509
-
2510
- &-text {
2511
- }
2512
- }
2513
- }
2514
- }
1937
+ ```vue
1938
+ <div
1939
+ class="max-h-52 max-w-2xl overflow-auto rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
1940
+ >
1941
+ <BbTable
1942
+ caption="Server fleet"
1943
+ compact
1944
+ fixed-headers
1945
+ :columns="[
1946
+ { key: 'host', label: 'Host' },
1947
+ { key: 'region', label: 'Region' },
1948
+ { key: 'cpu', label: 'CPU', align: 'right' },
1949
+ { key: 'memory', label: 'Memory', align: 'right' },
1950
+ { key: 'status', label: 'Status' },
1951
+ ]"
1952
+ :items="[
1953
+ { host: 'web-01', region: 'eu-west-1', cpu: '38%', memory: '61%', status: 'Healthy' },
1954
+ { host: 'web-02', region: 'eu-west-1', cpu: '52%', memory: '70%', status: 'Healthy' },
1955
+ { host: 'api-01', region: 'us-east-2', cpu: '74%', memory: '80%', status: 'Healthy' },
1956
+ { host: 'db-01', region: 'us-east-2', cpu: '81%', memory: '88%', status: 'Degraded' },
1957
+ { host: 'cache-01', region: 'ap-south-1', cpu: '22%', memory: '35%', status: 'Healthy' },
1958
+ ]"
1959
+ />
1960
+ </div>
2515
1961
  ```
1962
+
1963
+ ### Config and platform
1964
+
1965
+ - **Locale.** The plugin's `locale` (`it`/`en`) drives the built-in strings:
1966
+ the no-data text, the select-all label, the single-select header text, and
1967
+ the "Select" prefix of each row's accessible selection label. Override any
1968
+ of them per table via `no-data-text` / `select-all-label` / `select-text`.
1969
+ - **`actions-text` is localized** like the other built-in strings (`it` →
1970
+ `Azioni`, `en` → `Actions`). Seeing `Azioni` in an English app means the
1971
+ plugin is still on the default locale, which is `it` — set the plugin's
1972
+ `locale` option, or override per table with `actions-text` (it is visually
1973
+ hidden in the header but read by screen readers).
1974
+ - **SSR / Nuxt.** A provider `items` function participates in
1975
+ `onServerPrefetch`, so the first page can render server-side. The table
1976
+ context (and thus `table-id` pagination wiring) is client-side only —
1977
+ hydration-time onward.
1978
+ - **Forms.** With `name`, the selection submits as hidden inputs — no JS
1979
+ needed between the table and a plain `<form>` POST (Inertia included).
1980
+
1981
+ ### Works well with
1982
+
1983
+ - `BbPagination` — shared page ref, or zero-wiring via `id` + `table-id`.
1984
+ - `useBbTableContext` (`./composables/useBbTableContext.md`) — bulk-action
1985
+ toolbars, detail panels, "reset sort" buttons living outside the table.
1986
+ - `BbButton` — per-row links (`href`/`to`) in cells or `#actions`;
1987
+ `BbDropdown` for an overflow menu of row actions.
1988
+ - `BbBadge` / `BbAvatar` — status and identity cells via slots.
1989
+ - `BbTextInput` / `BbSelect` filters feeding `dependencies`.
1990
+ - `useConfirm` + `useToast` — destructive row actions with feedback; the full
1991
+ assembly is the records workspace recipe
1992
+ (`./recipes/vue/records-workspace.md`).
1993
+ - Inline editing — the ownership patterns are the
1994
+ [Inline Edit Playbook](./guides/inline-edit-playbook.md); the full autosave
1995
+ page is the inline-edit workspace recipe
1996
+ (`./recipes/vue/inline-edit-workspace.md`).
1997
+ - `BbTree` — when the data is genuinely recursive rather than rows + detail.
1998
+
1999
+ ### Gotchas and constraints
2000
+
2001
+ - Slot names are **lowercased**: a camelCase key like `issuedAt` yields
2002
+ `#issuedat`. Prefer snake_case keys.
2003
+ - `loading-text` has no default — an unset value announces nothing.
2004
+ - The `accessibleLabel` prop is currently ignored: row selection labels are
2005
+ always built from every cell's content. Rely on the default; don't expect a
2006
+ custom function to apply.
2007
+ - The select-all checkbox never renders checked, and enabling it does not
2008
+ re-emit an empty `modelValue` (see the select-all section) — in all-mode,
2009
+ read `select-all` + `unselected-items`, not `modelValue`.
2010
+ - Row event payloads are the internal row wrapper — your record is `row.item`.
2011
+ - A mistyped context/pagination `id` does not error; it silently syncs with
2012
+ nothing. If a toolbar "works" but the table never reacts, check the id.
2013
+ - In multiple mode `modelValue` must be an array and with
2014
+ `highlight-multiple`, `highlighted` must be one — both throw otherwise.
2015
+
2016
+ ### Anti-patterns
2017
+
2018
+ - Watching filters and refetching manually — list them in `dependencies`.
2019
+ - Expecting the table to sort, slice or fetch: it reports state; you apply it.
2020
+ - Hand-rolling selection pruning after a refetch — that's `enforce-coherence`
2021
+ (see Selection across refetches); reserve provider-side reconciliation for
2022
+ paginated tables, where the prop must stay off.
2023
+ - Pre-mapping items into `{ label, value }` — pass domain objects +
2024
+ `item-value`.
2025
+ - `@click:row` + `router.push` for navigation — render a real `BbButton` link.
2026
+ - Rebuilding selection controls in a cell slot — use `selectable`, the
2027
+ `#select` slot, or the scope's `toggleSelected`.
2028
+ - Mutating slot-scope flags (`expanded`, `selected`, `highlighted`) — they are
2029
+ one-way; call the paired `toggle*` callback.
2030
+ - Omitting `item-value` on tables with selection/expansion/highlight —
2031
+ object-identity matching breaks across refetches.
2032
+
2033
+ ### Accessibility
2034
+
2035
+ - Always give the table a `caption` describing its content. It is visually
2036
+ hidden by default (available to screen readers); set `display-caption` to
2037
+ show it as a visible title.
2038
+ - A selectable table is a `<fieldset>` — pass `legend`. Each selection control
2039
+ carries a visually-hidden label built from the row's cell contents.
2040
+ - Set `item-value` so selection, expansion and highlight track rows by stable
2041
+ identity rather than object reference.
2042
+ - Sortable headers set `aria-sort` and render real buttons; custom header
2043
+ slots should keep a button calling `toggleSort`.
2044
+ - When you paginate, pass `page` / `per-page` / `total-items` so the table
2045
+ announces the full result count (`aria-rowcount`) and each row's absolute
2046
+ index (`aria-rowindex`, expansion-aware), not just the current page.
2047
+ - The loading row is an `aria-live` status — always set `loading-text`.
2048
+ - The highlighted row carries `aria-current="true"`; add `keyboard-navigation`
2049
+ so row-level interaction is reachable without a mouse (roving tabindex,
2050
+ Arrow/PageUp/PageDown/Home/End, Enter/Space).
2051
+ - Keep row-level navigation in real `BbButton` links (`href` / `to`), never a
2052
+ bare `@click:row` — links are focusable and announced as links.
2053
+
2054
+ ## v-model
2055
+
2056
+ - `v-model:expandedItems` — type: `any[] \| undefined`
2057
+ - `v-model:highlighted` — type: `any`
2058
+ - `v-model` — type: `any`
2059
+ - `v-model:selectAll` — type: `boolean \| undefined`
2060
+ - `v-model:sort` — type: `BbTableSortEntry[] \| undefined`
2061
+ - `v-model:unselectedItems` — type: `any[] \| undefined`
2062
+
2063
+ ## Props
2064
+
2065
+ | Prop | Type | Default | Required | Description |
2066
+ | --- | --- | --- | --- | --- |
2067
+ | `accessibleLabel` | `((columns: MappedCell[], item: any) => string) \| undefined` | | | Function that accepts the columns and the current item as arguments and returns a label to be used for accessibility purposes. |
2068
+ | `actions` | `boolean \| undefined` | | | Displays the actions column. |
2069
+ | `actionsText` | `string \| undefined` | | | Label used in the header of the actions column. |
2070
+ | `align` | `"left" \| "center" \| "right" \| undefined` | `"left"` | | Text alignment of the columns. |
2071
+ | `caption` | `string \| undefined` | | | Caption that describes the content of the table. Used for accessibility purposes. |
2072
+ | `columns` | `InternalColumn<T>[] \| undefined` | `[]` | | Array of definitions of how the columns should be rendered. |
2073
+ | `compact` | `boolean \| undefined` | | | Adds a CSS class that applies a compact style to the component. |
2074
+ | `dependencies` | `unknown[] \| undefined` | `[]` | | Defines an array of dependencies that will trigger actions in the component upon change. |
2075
+ | `depsDebounceTime` | `number \| undefined` | `0` | | Timeout used to debounce response to changes to dependencies. |
2076
+ | `disabled` | `boolean \| undefined` | | | Disables the component |
2077
+ | `disableSelectAll` | `boolean \| undefined` | | | Hides the "Select all" checkbox that selectable multi-row tables display by default. |
2078
+ | `displayCaption` | `boolean \| undefined` | | | Boolean that defines whether to display or hide the caption. By default is true and the caption is hidden. |
2079
+ | `enforceCoherence` | `boolean \| undefined` | | | After every load (initial, `dependencies` refetch or a change of the `items` array) prunes the row-keyed models of values whose row is no longer in the result set: `modelValue`, `unselected-items`, `highlighted` and `expanded-items`. Values… |
2080
+ | `expandedItems` | `any[] \| undefined` | `[]` | | Used by `v-model:expandedItems`. Array of the currently expanded items — toggled by the row expand control and by external writes (mirrors the `highlighted`/`sort` v-model surfaces). Drives the `#expand` slot rows and their `aria-expanded`/… |
2081
+ | `fixed` | `boolean \| undefined` | | | Defines the table layout as fixed making each column take up the same amount of space. |
2082
+ | `fixedColumns` | `(number \| { index: number; position: "left" \| "right"; })[] \| undefined` | `[]` | | Definition for which column should be fixed. It can be an array of index of the column to fix on the left side of the table or an array of objects indicating the index and the position `left` or `right` where to affix the columns. |
2083
+ | `fixedHeaders` | `boolean \| undefined` | | | Boolean that sets the headers as sticky to the top of the table. |
2084
+ | `headerRowClass` | `Classes \| undefined` | | | Defines the classes to be passed to the header `<tr>`. |
2085
+ | `highlighted` | `any` | `undefined` | | Used by `v-model:highlighted`. A parallel, UI-intent state to selection — typically "the row whose details are open". A single item value (or `null`) by default; an array of values with `highlightMultiple`. Purely mechanical: highlighted ro… |
2086
+ | `highlightMultiple` | `boolean \| undefined` | | | Allows highlighting multiple rows: `highlighted` becomes an array of item values instead of a single value. |
2087
+ | `id` | `string \| undefined` | | | Stable id for this table's width context. When omitted a unique id is generated. Nested tables use the nearest ancestor id to inherit widths. |
2088
+ | `inheritColumnWidths` | `string \| boolean \| undefined` | | | Opt a nested table into inheriting its column widths from an ancestor table's matching tracks (accounting for `select`/`actions` columns). This also forces `table-layout: fixed`, so it is **off by default** — a nested table renders independ… |
2089
+ | `interactiveWhileLoading` | `boolean \| undefined` | | | Keeps the table interactive while it is loading. The header and the rows are `inert` while loading by default — a refetch is about to replace the rows, so editing, selecting or sorting them races the incoming data. This escape hatch exists … |
2090
+ | `items` | `Item[] \| ((prefill: boolean, modelValue?: any) => Item[] \| Promise<Item[]>)` | `[]` | yes | Rows to render, or a fetcher that returns them. |
2091
+ | `itemValue` | `ItemAccessor<Item> \| undefined` | | | |
2092
+ | `keyboardNavigation` | `boolean \| undefined` | | | Opt-in keyboard navigation with a roving tabindex: the body is one tab stop, ArrowUp/ArrowDown move the focused row (Home/End jump), and Enter/Space activate it through the same path as a mouse click (highlight toggle + `click:row`). Focus … |
2093
+ | `legend` | `string \| undefined` | | | Text content of the legend. |
2094
+ | `loading` | `boolean \| undefined` | | | Sets the component in a loading state, usually triggering some visual styles. With no rows yet this renders skeletons; over existing rows the rows stay on screen (dimmed, with an indeterminate bar) rather than being replaced — a refetch sho… |
2095
+ | `loadingText` | `string \| undefined` | | | String displayed while items are being loaded. |
2096
+ | `max` | `number \| undefined` | `Infinity` | | Maximum number of items that can be selected. |
2097
+ | `modelValue` | `any` | `[]` | | Used by v-model. Can be any serializable type. |
2098
+ | `multiple` | `boolean \| undefined` | `true` | | Allows the selection of multiple items. |
2099
+ | `name` | `string \| undefined` | | | Defines the name of the input. |
2100
+ | `noDataText` | `string \| undefined` | | | String displayed when there are no items to display. |
2101
+ | `page` | `number \| undefined` | | | Current page number starting from 1, used for accessibility purposes. |
2102
+ | `perPage` | `number \| undefined` | | | Number of items per page, used for accessibility purposes. |
2103
+ | `readonly` | `boolean \| undefined` | | | Sets the input in a readonly state. |
2104
+ | `rowClass` | `RowClasses<Item> \| undefined` | | | Defines the classes to be passed to each data `<tr>`. Can also be a function of the row's item for dynamic values. |
2105
+ | `selectable` | `boolean \| ((item: Item) => boolean) \| undefined` | | | Defines whether the table is selectable. Can be a global boolean that affects all rows or a function that accepts an item and returns a boolean that only affects that item. |
2106
+ | `selectAll` | `boolean \| undefined` | `false` | | Defines whether the "Select all" checkbox is checked. |
2107
+ | `selectAllLabel` | `string \| undefined` | | | Text of the label used by the "Select all" boolean. |
2108
+ | `selectText` | `string \| undefined` | | | Text of the header used for the radio inputs when the table is not `multiple`. |
2109
+ | `sort` | `BbTableSortEntry[] \| undefined` | `[]` | | Used by `v-model:sort`. Ordered array of `[columnKey, direction]` entries, one per actively sorted column. Bidirectional: the header buttons of `sortable` columns update it, and external writes update the header indicators. The table does n… |
2110
+ | `tdClass` | `ColumnClasses<Item> \| undefined` | | | Defines the classes to be passed to the `<td>` elements. Can also be a function for dynamic values. |
2111
+ | `thClass` | `Classes \| undefined` | | | Defines the classes to be passed to the `<th>`. |
2112
+ | `totalItems` | `number \| undefined` | | | Total number of items there is. |
2113
+ | `totalPages` | `number \| undefined` | | | Seeds the page count shown by a `BbPagination` paired through `id`. A shortcut for when the server reports a page count rather than a row count: the table renders nothing from it, it only flows into the shared table context. A known total a… |
2114
+ | `unselectedItems` | `any[] \| undefined` | `[]` | | Items that are not selected. |
2115
+
2116
+ ## Events
2117
+
2118
+ - `click:row` — `(e: "click:row", event: MouseEvent, item: any, selected: boolean): void`
2119
+ - `contextmenu:row` — `(e: "contextmenu:row", event: MouseEvent, item: any, selected: boolean): void`
2120
+ - `dblclick:row` — `(e: "dblclick:row", event: MouseEvent, item: any, selected: boolean): void`
2121
+ - `item:selected` — `(e: "item:selected", value: any): void`
2122
+ - `item:unselected` — `(e: "item:unselected", value: any): void`
2123
+ - `update:expandedItems` — `(e: "update:expandedItems", value: any[]): void`
2124
+ - `update:highlighted` — `(e: "update:highlighted", value: any): void`
2125
+ - `update:modelValue` — `(e: "update:modelValue", value: any): void`
2126
+ - `update:selectAll` — `(e: "update:selectAll", value: boolean): void`
2127
+ - `update:sort` — `(e: "update:sort", value: BbTableSortEntry[]): void`
2128
+ - `update:unselectedItems` — `(e: "update:unselectedItems", value: any[]): void`
2129
+
2130
+ ## Slots
2131
+
2132
+ - `actions` — scope: `{ expanded?: boolean \| undefined; expandProps?: ExpandControllerProps \| undefined; toggleExpanded?: (() => void) \| undefined; highlighted?: boolean \| undefi…` — Content rendered in the actions cell for each row.
2133
+ - `expand` — scope: `{ expanded: boolean; expandProps?: ExpandControllerProps \| undefined; toggleExpanded: () => void; highlighted?: boolean \| undefined; toggleHighlighted?: (() =…` — Full-width expandable content row rendered below each main row when expanded.
2134
+ - `header:actions` — scope: `{ text: string; }` — Replaces the default actions header cell content.
2135
+ - `header:select` — scope: `{ multiple: boolean; selectAll: boolean; text: string; }` — Replaces the default selection header cell content (checkbox or radio label).
2136
+ - `loading` — scope: `{ items: Item[]; }` — Content shown while table rows are loading (replaces the skeleton rows).
2137
+ - `no-data` — scope: `object` — Content shown when the items list is empty and not loading. It fills the table's own full-width empty-state cell (the colspan already accounts for the selection/actions columns), so provide the *content* — a message, an illustration, a "cle…
2138
+ - `select` — scope: `{ item: Item; value: any; checked: boolean; disabled: boolean; inputName: string; readonly: boolean; toggleSelected?: (() => void) \| undefined; }` — Replaces the default checkbox/radio in the selection cell for each row.
2139
+ - `tbody` — scope: `object` — Replaces the entire `<tbody>` element. Use when you need full control over the body markup.
2140
+ - `tfoot` — scope: `{ columnCount: number; }` — Replaces the entire `<tfoot>` element. Use when you need full control over the footer markup.
2141
+ - `thead` — scope: `object` — Replaces the entire `<thead>` element. Use when you need full control over the header markup.
2142
+
2143
+ ## See Also
2144
+
2145
+ - [BbTree](./BbTree.md) — Renders hierarchical expandable tree structures.
2146
+ - [BbPagination](./BbPagination.md) — Navigates paginated data sets by page index.