bitboss-ui 3.0.0-beta.21 → 3.0.0-beta.23

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 (252) hide show
  1. package/README.md +55 -3
  2. package/bin/bitboss-ui-mcp.mjs +419 -9
  3. package/bin/bitboss-ui.mjs +293 -22
  4. package/dist/ai/BbAccordion.md +21 -23
  5. package/dist/ai/BbAlert.md +6 -12
  6. package/dist/ai/BbAvatar.md +60 -24
  7. package/dist/ai/BbBadge.md +12 -20
  8. package/dist/ai/BbBaseButton.md +14 -16
  9. package/dist/ai/BbBaseCheckboxIcon.md +6 -10
  10. package/dist/ai/BbBaseColorInput.md +1 -0
  11. package/dist/ai/BbBaseDatePickerInput.md +1 -0
  12. package/dist/ai/BbBaseNumberInput.md +1 -0
  13. package/dist/ai/BbBaseRadioIcon.md +6 -10
  14. package/dist/ai/BbBaseSelect.md +1 -0
  15. package/dist/ai/BbBaseSwitchIcon.md +5 -9
  16. package/dist/ai/BbBaseTag.md +1 -0
  17. package/dist/ai/BbBaseTextInput.md +2 -1
  18. package/dist/ai/BbBaseTextarea.md +1 -0
  19. package/dist/ai/BbBaseTimePickerInput.md +1 -0
  20. package/dist/ai/BbBreadcrumbs.md +23 -34
  21. package/dist/ai/BbButton.md +35 -47
  22. package/dist/ai/BbCheckbox.md +21 -37
  23. package/dist/ai/BbCheckboxGroup.md +13 -13
  24. package/dist/ai/BbCollapsible.md +14 -26
  25. package/dist/ai/BbColorInput.md +27 -23
  26. package/dist/ai/BbColorPalette.md +23 -35
  27. package/dist/ai/BbConfirm.md +17 -32
  28. package/dist/ai/BbDatePickerInput.md +29 -21
  29. package/dist/ai/BbDialog.md +159 -62
  30. package/dist/ai/BbDropdown.md +21 -25
  31. package/dist/ai/BbDropdownButton.md +13 -30
  32. package/dist/ai/BbDropzone.md +9 -11
  33. package/dist/ai/BbIcon.md +44 -33
  34. package/dist/ai/BbIndicator.md +2 -2
  35. package/dist/ai/BbNumberInput.md +32 -20
  36. package/dist/ai/BbOffCanvas.md +42 -42
  37. package/dist/ai/BbPagination.md +21 -23
  38. package/dist/ai/BbPopover.md +15 -17
  39. package/dist/ai/BbProgress.md +40 -51
  40. package/dist/ai/BbRadio.md +9 -9
  41. package/dist/ai/BbRadioGroup.md +7 -11
  42. package/dist/ai/BbRating.md +8 -12
  43. package/dist/ai/BbSelect.md +46 -36
  44. package/dist/ai/BbSelectPopover.md +19 -31
  45. package/dist/ai/BbSlider.md +15 -19
  46. package/dist/ai/BbSmoothHeight.md +16 -31
  47. package/dist/ai/BbSpinner.md +118 -74
  48. package/dist/ai/BbSwitch.md +25 -18
  49. package/dist/ai/BbSwitchGroup.md +9 -14
  50. package/dist/ai/BbTable.md +102 -55
  51. package/dist/ai/BbTabs.md +59 -82
  52. package/dist/ai/BbTag.md +26 -23
  53. package/dist/ai/BbTextInput.md +63 -27
  54. package/dist/ai/BbTextarea.md +42 -34
  55. package/dist/ai/BbTimePickerInput.md +14 -0
  56. package/dist/ai/BbToast.md +14 -34
  57. package/dist/ai/BbTooltip.md +22 -20
  58. package/dist/ai/BbTree.md +46 -72
  59. package/dist/ai/CommonInputWrapper.md +1 -0
  60. package/dist/ai/changelog.json +170 -62
  61. package/dist/ai/components.json +1678 -11
  62. package/dist/ai/composables/useConfirm.md +5 -2
  63. package/dist/ai/composables/useToast.md +4 -2
  64. package/dist/ai/guides/agent-contract.md +7 -4
  65. package/dist/ai/guides/ai-router.md +37 -22
  66. package/dist/ai/guides/component-picker.md +23 -18
  67. package/dist/ai/guides/design-language.md +151 -21
  68. package/dist/ai/guides/design-tokens.md +29 -4
  69. package/dist/ai/guides/icons-policy.md +46 -21
  70. package/dist/ai/guides/installation-and-plugin-setup.md +286 -17
  71. package/dist/ai/guides/migration/components/bb-rating.md +26 -1
  72. package/dist/ai/guides/migration/components/bb-tooltip.md +18 -0
  73. package/dist/ai/guides/migration/v2-to-v3.md +50 -23
  74. package/dist/ai/index.md +1 -1
  75. package/dist/ai/recipes/inertia/approvals-inbox.md +3 -3
  76. package/dist/ai/recipes/inertia/inline-edit-workspace.md +2 -2
  77. package/dist/ai/recipes/inertia/onboarding.md +2 -2
  78. package/dist/ai/recipes/inertia/ownership-atlas.md +6 -6
  79. package/dist/ai/recipes/inertia/record-form.md +5 -5
  80. package/dist/ai/recipes/inertia/records-workspace.md +4 -4
  81. package/dist/ai/recipes/inertia/upload-center.md +6 -6
  82. package/dist/ai/recipes/inertia/wizard-form.md +5 -5
  83. package/dist/ai/recipes/nuxt/approvals-inbox.md +3 -3
  84. package/dist/ai/recipes/nuxt/inline-edit-workspace.md +2 -2
  85. package/dist/ai/recipes/nuxt/onboarding.md +2 -2
  86. package/dist/ai/recipes/nuxt/record-form.md +5 -5
  87. package/dist/ai/recipes/nuxt/records-workspace.md +4 -4
  88. package/dist/ai/recipes/nuxt/upload-center.md +6 -6
  89. package/dist/ai/recipes/nuxt/wizard-form.md +5 -5
  90. package/dist/ai/recipes/vue/approvals-inbox.md +3 -3
  91. package/dist/ai/recipes/vue/inline-edit-workspace.md +2 -2
  92. package/dist/ai/recipes/vue/onboarding.md +2 -2
  93. package/dist/ai/recipes/vue/record-form.md +10 -10
  94. package/dist/ai/recipes/vue/records-workspace.md +4 -4
  95. package/dist/ai/recipes/vue/upload-center.md +6 -6
  96. package/dist/ai/recipes/vue/wizard-form.md +5 -5
  97. package/dist/ai/source/BbBadge.md +1 -1
  98. package/dist/ai/source/BbBaseCheckbox.md +1 -1
  99. package/dist/ai/source/BbBaseColorInput.md +7 -0
  100. package/dist/ai/source/BbBaseDatePicker.md +5 -5
  101. package/dist/ai/source/BbBaseDatePickerInput.md +9 -2
  102. package/dist/ai/source/BbBaseNumberInput.md +7 -0
  103. package/dist/ai/source/BbBaseRadio.md +1 -1
  104. package/dist/ai/source/BbBaseRating.md +5 -5
  105. package/dist/ai/source/BbBaseSelect.md +8 -1
  106. package/dist/ai/source/BbBaseSlider.md +5 -5
  107. package/dist/ai/source/BbBaseSwitch.md +1 -1
  108. package/dist/ai/source/BbBaseTag.md +7 -0
  109. package/dist/ai/source/BbBaseTextInput.md +7 -0
  110. package/dist/ai/source/BbBaseTextarea.md +7 -0
  111. package/dist/ai/source/BbBaseTimePickerInput.md +8 -1
  112. package/dist/ai/source/BbBreadcrumbs.md +20 -1
  113. package/dist/ai/source/BbButton.md +2 -2
  114. package/dist/ai/source/BbColorInput.md +14 -0
  115. package/dist/ai/source/BbDatePickerInput.md +14 -0
  116. package/dist/ai/source/BbDialog.md +39 -8
  117. package/dist/ai/source/BbDropdown.md +1 -1
  118. package/dist/ai/source/BbDropzone.md +5 -5
  119. package/dist/ai/source/BbIcon.md +23 -10
  120. package/dist/ai/source/BbNumberInput.md +14 -0
  121. package/dist/ai/source/BbOffCanvas.md +50 -28
  122. package/dist/ai/source/BbPagination.md +2 -2
  123. package/dist/ai/source/BbPopover.md +12 -1
  124. package/dist/ai/source/BbSelect.md +14 -0
  125. package/dist/ai/source/BbSelectPopover.md +45 -7
  126. package/dist/ai/source/BbSpinner.md +2 -2
  127. package/dist/ai/source/BbTable.md +11 -11
  128. package/dist/ai/source/BbTag.md +14 -0
  129. package/dist/ai/source/BbTextInput.md +14 -0
  130. package/dist/ai/source/BbTextarea.md +14 -0
  131. package/dist/ai/source/BbTimePickerInput.md +14 -0
  132. package/dist/ai/source/BbTooltip.md +57 -10
  133. package/dist/ai/source/ChipsBox.md +1 -1
  134. package/dist/ai/source/CommaBox.md +1 -1
  135. package/dist/ai/source/CommonInputWrapper.md +128 -33
  136. package/dist/ai/source/GuidesComponent.md +14 -7
  137. package/dist/ai/source/OptionsContainer.md +1 -1
  138. package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +1 -1
  139. package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +1 -1
  140. package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue_vue_type_script_setup_true_lang.js +1 -1
  141. package/dist/components/BbBaseColorInput/BbBaseColorInput.vue_vue_type_script_setup_true_lang.js +4 -1
  142. package/dist/components/BbBaseColorInput/types.d.ts +5 -0
  143. package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue_vue_type_script_setup_true_lang.js +4 -4
  144. package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +1 -1
  145. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +5 -2
  146. package/dist/components/BbBaseDatePickerInput/types.d.ts +5 -0
  147. package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue.d.ts +1 -1
  148. package/dist/components/BbBaseNumberInput/BbBaseNumberInput.vue_vue_type_script_setup_true_lang.js +4 -1
  149. package/dist/components/BbBaseNumberInput/types.d.ts +5 -0
  150. package/dist/components/BbBaseRadio/BbBaseRadio.vue_vue_type_script_setup_true_lang.js +1 -1
  151. package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +3 -3
  152. package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +4 -1
  153. package/dist/components/BbBaseSelect/types.d.ts +5 -0
  154. package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +3 -3
  155. package/dist/components/BbBaseSwitch/BbBaseSwitch.vue_vue_type_script_setup_true_lang.js +1 -1
  156. package/dist/components/BbBaseTag/BbBaseTag.vue_vue_type_script_setup_true_lang.js +4 -1
  157. package/dist/components/BbBaseTag/types.d.ts +5 -0
  158. package/dist/components/BbBaseTextInput/BbBaseTextInput.vue_vue_type_script_setup_true_lang.js +4 -1
  159. package/dist/components/BbBaseTextInput/types.d.ts +5 -0
  160. package/dist/components/BbBaseTextarea/BbBaseTextarea.vue_vue_type_script_setup_true_lang.js +4 -1
  161. package/dist/components/BbBaseTextarea/types.d.ts +5 -0
  162. package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +5 -2
  163. package/dist/components/BbBaseTimePickerInput/types.d.ts +5 -0
  164. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +71 -71
  165. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +2 -2
  166. package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +5 -2
  167. package/dist/components/BbColorInput/types.d.ts +12 -0
  168. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +4 -2
  169. package/dist/components/BbDatePickerInput/types.d.ts +12 -0
  170. package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +102 -97
  171. package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +1 -1
  172. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +58 -58
  173. package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +2 -2
  174. package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +33 -35
  175. package/dist/components/BbIcon/iconCache.d.ts +30 -0
  176. package/dist/components/BbIcon/iconCache.js +21 -3
  177. package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +5 -2
  178. package/dist/components/BbNumberInput/types.d.ts +12 -0
  179. package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +57 -56
  180. package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +2 -2
  181. package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +138 -138
  182. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +4 -2
  183. package/dist/components/BbSelect/types.d.ts +12 -0
  184. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +43 -43
  185. package/dist/components/BbSpinner/BbSpinner.vue_vue_type_script_setup_true_lang.js +1 -1
  186. package/dist/components/BbSpinner/types.d.ts +1 -1
  187. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +8 -8
  188. package/dist/components/BbTable/BbTableDataRow.js +2 -2
  189. package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +5 -2
  190. package/dist/components/BbTag/types.d.ts +12 -0
  191. package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +5 -2
  192. package/dist/components/BbTextInput/types.d.ts +12 -0
  193. package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +5 -2
  194. package/dist/components/BbTextarea/types.d.ts +12 -0
  195. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +4 -2
  196. package/dist/components/BbTimePickerInput/types.d.ts +12 -0
  197. package/dist/components/BbToast/BbToastRegion.vue_vue_type_script_setup_true_lang.js +62 -62
  198. package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +81 -78
  199. package/dist/components/BbTooltip/types.d.ts +8 -1
  200. package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +1 -1
  201. package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +1 -1
  202. package/dist/components/CommonInputWrapper/CommonInputWrapper.vue.d.ts +1 -0
  203. package/dist/components/CommonInputWrapper/CommonInputWrapper.vue_vue_type_script_setup_true_lang.js +4 -2
  204. package/dist/components/CommonInputWrapper/types.d.ts +10 -0
  205. package/dist/components/OptionsContainer/OptionsContainer.vue_vue_type_script_setup_true_lang.js +1 -1
  206. package/dist/composables/useConfirm.d.ts +22 -0
  207. package/dist/composables/useConfirm.js +43 -40
  208. package/dist/composables/useSafeAreaInternal.js +1 -1
  209. package/dist/composables/useToast.d.ts +16 -0
  210. package/dist/composables/useToast.js +20 -16
  211. package/dist/composables/useViewportWidth.d.ts +20 -6
  212. package/dist/composables/useViewportWidth.js +8 -7
  213. package/dist/icon-scan.d.ts +276 -0
  214. package/dist/icon-scan.js +485 -0
  215. package/dist/icons-whitelist.d.ts +5 -4
  216. package/dist/index.d.ts +2 -0
  217. package/dist/input-variants.d.ts +18 -0
  218. package/dist/llms-full.txt +2173 -1537
  219. package/dist/llms-medium.txt +504 -82
  220. package/dist/llms.txt +1 -1
  221. package/dist/nuxt-module.d.ts +1 -0
  222. package/dist/nuxt.js +10 -7
  223. package/dist/plugin.js +1 -0
  224. package/dist/runtime/nuxt-plugin.js +2 -1
  225. package/dist/styles.css +1 -1
  226. package/dist/types/Config.d.ts +18 -0
  227. package/dist/types/InputVariant.d.ts +22 -0
  228. package/dist/utils/cssLayers.d.ts +91 -0
  229. package/dist/utils/cssLayers.js +43 -0
  230. package/dist/utils/injectLibraryStyles.d.ts +3 -1
  231. package/dist/utils/injectLibraryStyles.js +24 -23
  232. package/dist/validated/BbColorInput.vue_vue_type_script_setup_true_lang.js +1 -0
  233. package/dist/validated/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +1 -0
  234. package/dist/validated/BbNumberInput.vue_vue_type_script_setup_true_lang.js +1 -0
  235. package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +1 -0
  236. package/dist/validated/BbTag.vue_vue_type_script_setup_true_lang.js +1 -0
  237. package/dist/validated/BbTextInput.vue_vue_type_script_setup_true_lang.js +1 -0
  238. package/dist/validated/BbTextarea.vue_vue_type_script_setup_true_lang.js +1 -0
  239. package/dist/validated/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +1 -0
  240. package/dist/validated/index.d.ts +1 -0
  241. package/dist/vite-plugin.d.ts +89 -12
  242. package/dist/vite.js +331 -281
  243. package/llms.txt +1 -1
  244. package/package.json +7 -5
  245. package/scripts/lib/ensure-manifest.mjs +28 -5
  246. package/scripts/lib/eslint-plugin.d.ts +35 -0
  247. package/scripts/lib/eslint-plugin.mjs +299 -3
  248. package/scripts/lib/hand-roll-hints.mjs +50 -6
  249. package/scripts/lib/html-attributes.mjs +42 -0
  250. package/scripts/lib/mcp-config.mjs +38 -34
  251. package/scripts/lib/public-type-exports.mjs +246 -0
  252. package/scripts/lib/validate-bb-markup.mjs +205 -12
@@ -109,7 +109,7 @@
109
109
  aria-expanded="true"
110
110
  :aria-invalid="computedAriaInvalid"
111
111
  :aria-label="searchInputAriaLabel"
112
- class="bb-select-popover__focus-proxy sr-only"
112
+ class="bb-select-popover__focus-proxy bb-sr-only"
113
113
  role="combobox"
114
114
  tabindex="-1"
115
115
  type="button"
@@ -642,6 +642,22 @@ const onOutsideInteraction = async (event: Event) => {
642
642
  const insideActivator = positionAnchorEl.value?.contains(target);
643
643
  const insidePopover = !!target.closest('.bb-common-popover');
644
644
  if (insideActivator || insidePopover) return;
645
+ // Focus landing on an ANCESTOR of the activator is a focus fallback, not
646
+ // the user going elsewhere. A mousedown on a non-focusable node inside a
647
+ // modal `<dialog>` — the field's own `<label>` — parks focus on the dialog
648
+ // element itself, which arrived here as an outside `focusin` and closed
649
+ // the panel one tick before the label's click reached the activator and
650
+ // reopened it: a close/open flash that a label outside any dialog never
651
+ // showed (focus falls to `body` there, which fires no `focusin`). Scoped
652
+ // to `focusin` on purpose: a CLICK whose target is that ancestor (the
653
+ // dialog's own padding) is a real outside click and must still close.
654
+ if (
655
+ event.type === 'focusin' &&
656
+ positionAnchorEl.value &&
657
+ target.contains(positionAnchorEl.value)
658
+ ) {
659
+ return;
660
+ }
645
661
  const label =
646
662
  target instanceof HTMLLabelElement ? target : target.closest('label');
647
663
  if (label?.control && positionAnchorEl.value?.contains(label.control)) return;
@@ -1231,8 +1247,22 @@ const open = async ({
1231
1247
  // Join the shared Escape stack only in flyout mode — the sheet returned
1232
1248
  // above and BbOffCanvas registers itself instead.
1233
1249
  pushEscapeEntry(escapeKey);
1234
- document.addEventListener('click', onOutsideInteraction);
1235
- document.addEventListener('focusin', onOutsideInteraction);
1250
+ // CAPTURE, not bubble. Every overlay root — a `<dialog>`, an off-canvas,
1251
+ // another popover — binds `overlaySurfaceIsolation`, which stops `click`
1252
+ // from bubbling past the surface so handlers BEHIND it never hear a click
1253
+ // aimed at it. A select declared INSIDE such a surface is behind nothing,
1254
+ // but its document listener sits above the boundary all the same: a click
1255
+ // on the dialog's own chrome was swallowed at the dialog and never
1256
+ // reached a bubble-phase watcher here, so it could not close the panel.
1257
+ // (It used to close by accident: mousedown parked focus on the dialog and
1258
+ // the `focusin` path treated that as leaving — see the ancestor guard.)
1259
+ // Capture runs before the boundary and before any consumer `.stop`, and
1260
+ // the inside/label guards in `onOutsideInteraction` already carry the
1261
+ // meaning, so nothing inside the select's own chrome is affected.
1262
+ document.addEventListener('click', onOutsideInteraction, { capture: true });
1263
+ document.addEventListener('focusin', onOutsideInteraction, {
1264
+ capture: true,
1265
+ });
1236
1266
  document.addEventListener('keydown', onEsc, { capture: true });
1237
1267
 
1238
1268
  if (!focusPopoverContent) return;
@@ -1259,8 +1289,12 @@ const close = async () => {
1259
1289
 
1260
1290
  pause();
1261
1291
  removeEscapeEntry(escapeKey);
1262
- document.removeEventListener('click', onOutsideInteraction);
1263
- document.removeEventListener('focusin', onOutsideInteraction);
1292
+ document.removeEventListener('click', onOutsideInteraction, {
1293
+ capture: true,
1294
+ });
1295
+ document.removeEventListener('focusin', onOutsideInteraction, {
1296
+ capture: true,
1297
+ });
1264
1298
  document.removeEventListener('keydown', onEsc, { capture: true });
1265
1299
  };
1266
1300
 
@@ -1575,8 +1609,12 @@ onBeforeUnmount(() => {
1575
1609
  // `shown` is a plain local ref that unmounting never resets, so the orphaned
1576
1610
  // capture-phase handler keeps passing its own guard and swallowing the key.
1577
1611
  removeEscapeEntry(escapeKey);
1578
- document.removeEventListener('click', onOutsideInteraction);
1579
- document.removeEventListener('focusin', onOutsideInteraction);
1612
+ document.removeEventListener('click', onOutsideInteraction, {
1613
+ capture: true,
1614
+ });
1615
+ document.removeEventListener('focusin', onOutsideInteraction, {
1616
+ capture: true,
1617
+ });
1580
1618
  document.removeEventListener('keydown', onEsc, { capture: true });
1581
1619
  });
1582
1620
  /**
@@ -8,7 +8,7 @@
8
8
  <template>
9
9
  <span v-if="label" role="status">
10
10
  <BbIcon class="bb-spinner" :icon="spinnerSvg" :size="size" />
11
- <span class="sr-only">{{ label }}</span>
11
+ <span class="bb-sr-only">{{ label }}</span>
12
12
  </span>
13
13
  <BbIcon v-else class="bb-spinner" :icon="spinnerSvg" :size="size" />
14
14
  </template>
@@ -47,7 +47,7 @@ export type Size = IconSize;
47
47
  export type BbSpinnerProps = Pick<BbIconProps, 'size'> & {
48
48
  /**
49
49
  * Accessible loading announcement. When set, the spinner is wrapped in a
50
- * `role="status"` element containing this text rendered `sr-only`, so
50
+ * `role="status"` element containing this text rendered `bb-sr-only`, so
51
51
  * screen readers announce the loading state. When omitted the markup is
52
52
  * unchanged and the spinner is purely decorative.
53
53
  */
@@ -16,7 +16,7 @@
16
16
  :inert="disabled"
17
17
  :style="containerStyle"
18
18
  >
19
- <legend v-if="selectable" class="sr-only">{{ legendText }}</legend>
19
+ <legend v-if="selectable" class="bb-sr-only">{{ legendText }}</legend>
20
20
  <!-- The caption lives OUTSIDE the table role (a `caption` role is invalid
21
21
  there for a div table) and names the table through `aria-labelledby`. -->
22
22
  <div
@@ -24,7 +24,7 @@
24
24
  :id="captionId"
25
25
  class="bb-table-caption"
26
26
  :class="{
27
- 'sr-only': !displayCaption,
27
+ 'bb-sr-only': !displayCaption,
28
28
  }"
29
29
  >
30
30
  {{ caption }}
@@ -40,7 +40,7 @@
40
40
  <div
41
41
  v-if="reorderable || resizable"
42
42
  :id="headerInstructionsId"
43
- class="sr-only"
43
+ class="bb-sr-only"
44
44
  >
45
45
  <template v-if="reorderable"
46
46
  >{{ t('table.reorderInstructions').value }}
@@ -63,14 +63,14 @@
63
63
  from outside `role="menu"`. -->
64
64
  <span
65
65
  aria-live="polite"
66
- class="bb-table-loading__text sr-only"
66
+ class="bb-table-loading__text bb-sr-only"
67
67
  role="status"
68
68
  >{{ loading ? loadingText || t('common.loadingText').value : '' }}</span
69
69
  >
70
70
  <template v-if="reorderable">
71
71
  <span
72
72
  aria-live="polite"
73
- class="bb-table__reorder-status sr-only"
73
+ class="bb-table__reorder-status bb-sr-only"
74
74
  role="status"
75
75
  >{{ columnOrderStatus }}</span
76
76
  >
@@ -175,13 +175,13 @@
175
175
  :name="`select_all_${name || ''}`"
176
176
  :readonly="readonly"
177
177
  />
178
- <span class="bb-table-check__label-text sr-only"
178
+ <span class="bb-table-check__label-text bb-sr-only"
179
179
  >{{ selectAllLabel || t('table.selectAllLabel').value }}
180
180
  </span>
181
181
  </label>
182
182
  <span
183
183
  v-else-if="!multiple"
184
- class="bb-table-radio__label-text sr-only"
184
+ class="bb-table-radio__label-text bb-sr-only"
185
185
  >{{ selectText || t('table.selectText').value }}</span
186
186
  >
187
187
  </span>
@@ -306,7 +306,7 @@
306
306
  name="header:actions"
307
307
  :text="actionsText || t('table.actionsText').value"
308
308
  >
309
- <span class="bb-table-header__content sr-only">
309
+ <span class="bb-table-header__content bb-sr-only">
310
310
  {{ actionsText || t('table.actionsText').value }}
311
311
  </span>
312
312
  </slot>
@@ -821,7 +821,7 @@ const {
821
821
  /** Id of the caption element the grid root is labelled by. */
822
822
  const captionId = computed(() => `${tableId.value}__caption`);
823
823
 
824
- /** Id of the sr-only keyboard instructions every data header is described by. */
824
+ /** Id of the bb-sr-only keyboard instructions every data header is described by. */
825
825
  const headerInstructionsId = computed(
826
826
  () => `${tableId.value}__header_instructions`
827
827
  );
@@ -6537,7 +6537,7 @@ export type BbTableSlots<Item = any> = {
6537
6537
 
6538
6538
  /* The label is an inline-block so the affixes keep their inline flow, and
6539
6539
  it ellipsizes within the cell; the select/actions headers keep their
6540
- sr-only / control content untouched. */
6540
+ bb-sr-only / control content untouched. */
6541
6541
  &-header:not(.bb-table-header--select):not(.bb-table-header--actions)
6542
6542
  > .bb-table-header__content {
6543
6543
  display: inline-block;
@@ -7115,7 +7115,7 @@ export type BbTableSlots<Item = any> = {
7115
7115
  dependency change (a filter, a poll, an autosave round-trip). Keep the rows
7116
7116
  on screen, mark them stale, and run an indeterminate bar along the header/body
7117
7117
  boundary. `aria-busy` on the root already carries the semantics and the
7118
- sr-only row in the body still announces, so the bar is decorative — hence a
7118
+ bb-sr-only row in the body still announces, so the bar is decorative — hence a
7119
7119
  pseudo-element rather than a node. `.bb-table--empty` is "nothing to keep". */
7120
7120
  .bb-table--loading:not(.bb-table--empty) {
7121
7121
  & .bb-table__body {
@@ -51,6 +51,7 @@
51
51
  :prepend:icon="props['prepend:icon']"
52
52
  :readonly="readonly"
53
53
  :required="required"
54
+ :variant="variant"
54
55
  v-bind="eventListeners"
55
56
  >
56
57
  <template #prepend-outer="data"
@@ -144,6 +145,7 @@ const eventListeners = {
144
145
  ## Types
145
146
 
146
147
  ```ts
148
+ import type { InputVariantType } from '@/types/InputVariant';
147
149
  import type { HTMLAttributes, InputHTMLAttributes } from 'vue';
148
150
  import type { InputDirection } from '@/types/InputDirection';
149
151
  import type { IconType } from '@/types/Icon';
@@ -193,6 +195,18 @@ export type BbTagProps = {
193
195
  */
194
196
  compact?: boolean;
195
197
 
198
+ /**
199
+ * Visual variant of the field box — the same names and tokens as the
200
+ * `BbButton` variants. Colours only: height, padding and border width are
201
+ * identical across variants, so a form never reflows when one changes.
202
+ * `'ghost'` has no border in any state (errors and warnings show through
203
+ * the icon and the messages); every variant keeps the focus ring.
204
+ * Register extra names with the plugin's `inputVariants` option.
205
+ *
206
+ * @defaultValue `'outline'`
207
+ */
208
+ variant?: InputVariantType;
209
+
196
210
  /**
197
211
  * Descriptive text displayed below the label and above the input. Unlike the hint it is always
198
212
  * visible, and it is linked to the input via `aria-describedby` (after any
@@ -57,6 +57,7 @@
57
57
  :readonly="readonly"
58
58
  :required="required"
59
59
  :type="type"
60
+ :variant="variant"
60
61
  v-bind="eventListeners"
61
62
  >
62
63
  <template #append-outer><slot name="append-outer" /></template>
@@ -136,6 +137,7 @@ const eventListeners = {
136
137
  ## Types
137
138
 
138
139
  ```ts
140
+ import type { InputVariantType } from '@/types/InputVariant';
139
141
  import type { HTMLAttributes, InputHTMLAttributes } from 'vue';
140
142
  import type { InputDirection } from '@/types/InputDirection';
141
143
  import type { MaskInputOptions } from 'maska';
@@ -171,6 +173,18 @@ export type BbTextInputProps = {
171
173
  */
172
174
  compact?: boolean;
173
175
 
176
+ /**
177
+ * Visual variant of the field box — the same names and tokens as the
178
+ * `BbButton` variants. Colours only: height, padding and border width are
179
+ * identical across variants, so a form never reflows when one changes.
180
+ * `'ghost'` has no border in any state (errors and warnings show through
181
+ * the icon and the messages); every variant keeps the focus ring.
182
+ * Register extra names with the plugin's `inputVariants` option.
183
+ *
184
+ * @defaultValue `'outline'`
185
+ */
186
+ variant?: InputVariantType;
187
+
174
188
  /**
175
189
  * Descriptive text displayed below the label and above the input. Unlike the hint it is always
176
190
  * visible, and it is linked to the input via `aria-describedby` (after any
@@ -53,6 +53,7 @@
53
53
  :readonly="readonly"
54
54
  :required="required"
55
55
  :rows="rows"
56
+ :variant="variant"
56
57
  v-bind="eventListeners"
57
58
  >
58
59
  <template #append-outer><slot name="append-outer" /></template>
@@ -132,6 +133,7 @@ const eventListeners = {
132
133
  ## Types
133
134
 
134
135
  ```ts
136
+ import type { InputVariantType } from '@/types/InputVariant';
135
137
  import type { HTMLAttributes, InputHTMLAttributes } from 'vue';
136
138
  import type { InputDirection } from '@/types/InputDirection';
137
139
  import type { IconType } from '@/types/Icon';
@@ -172,6 +174,18 @@ export type BbTextareaProps = {
172
174
  */
173
175
  compact?: boolean;
174
176
 
177
+ /**
178
+ * Visual variant of the field box — the same names and tokens as the
179
+ * `BbButton` variants. Colours only: height, padding and border width are
180
+ * identical across variants, so a form never reflows when one changes.
181
+ * `'ghost'` has no border in any state (errors and warnings show through
182
+ * the icon and the messages); every variant keeps the focus ring.
183
+ * Register extra names with the plugin's `inputVariants` option.
184
+ *
185
+ * @defaultValue `'outline'`
186
+ */
187
+ variant?: InputVariantType;
188
+
175
189
  /**
176
190
  * Descriptive text displayed below the label and above the input. Unlike the hint it is always
177
191
  * visible, and it is linked to the input via `aria-describedby` (after any
@@ -57,6 +57,7 @@
57
57
  :seconds="seconds"
58
58
  :step="step"
59
59
  :transition-duration="transitionDuration"
60
+ :variant="variant"
60
61
  v-bind="eventListeners"
61
62
  >
62
63
  <template #prepend-outer><slot name="prepend-outer"></slot></template>
@@ -136,6 +137,7 @@ const eventListeners = {
136
137
  ## Types
137
138
 
138
139
  ```ts
140
+ import type { InputVariantType } from '@/types/InputVariant';
139
141
  import type { HTMLAttributes, InputHTMLAttributes } from 'vue';
140
142
  import type { InputDirection } from '@/types/InputDirection';
141
143
  import type { IconType } from '@/types/Icon';
@@ -184,6 +186,18 @@ export type BbTimePickerInputProps = {
184
186
  /** Sets the component in a compact state. */
185
187
  compact?: boolean;
186
188
 
189
+ /**
190
+ * Visual variant of the field box — the same names and tokens as the
191
+ * `BbButton` variants. Colours only: height, padding and border width are
192
+ * identical across variants, so a form never reflows when one changes.
193
+ * `'ghost'` has no border in any state (errors and warnings show through
194
+ * the icon and the messages); every variant keeps the focus ring.
195
+ * Register extra names with the plugin's `inputVariants` option.
196
+ *
197
+ * @defaultValue `'outline'`
198
+ */
199
+ variant?: InputVariantType;
200
+
187
201
  /**
188
202
  * Descriptive text displayed below the label and above the input. Always
189
203
  * visible and linked to the input via `aria-describedby` (after any
@@ -6,7 +6,7 @@
6
6
 
7
7
  ```vue
8
8
  <template>
9
- <span v-if="resolvedDescription" :id="descId" class="sr-only">{{
9
+ <span v-if="resolvedDescription" :id="descId" class="bb-sr-only">{{
10
10
  resolvedDescription
11
11
  }}</span>
12
12
 
@@ -48,7 +48,7 @@
48
48
  :id="id"
49
49
  class="bb-tooltip__content"
50
50
  role="tooltip"
51
- :style="{ width: parsedWidth }"
51
+ :style="{ '--max-w': parsedWidth }"
52
52
  >
53
53
  <slot>{{ text }}</slot>
54
54
  </span>
@@ -73,7 +73,6 @@ import { warnRemovedAttrs } from '@/utilities/functions/warnRemovedAttrs';
73
73
  import CommonPopover from '../CommonPopover/CommonPopover.vue';
74
74
  import { useElementSize } from '@vueuse/core';
75
75
  import { extractDomContainer } from '@/utilities/functions/extractDomContainer';
76
- import { parseWidthString } from '@/utilities/functions/parseWidthString';
77
76
  import type { BbTooltipProps, BbTooltipSlots } from './types';
78
77
  import { transitions } from '@/utilities/misc/transitions';
79
78
  export type { BbTooltipProps, BbTooltipSlots };
@@ -338,10 +337,14 @@ onBeforeUnmount(() => {
338
337
  document.removeEventListener('keydown', onEsc);
339
338
  });
340
339
 
340
+ /** A bare number, or a number written as a string — both mean pixels. */
341
+ const bareNumberPattern = /^-?\d*\.?\d+$/;
342
+ const percentPattern = /^(-?\d*\.?\d+)%$/;
343
+
341
344
  const percentWidth = computed(() => {
342
- if (!props.width) return undefined;
343
- const { value, unit } = parseWidthString(props.width);
344
- return unit === '%' ? value : undefined;
345
+ if (typeof props.width !== 'string') return undefined;
346
+ const match = props.width.trim().match(percentPattern);
347
+ return match ? parseFloat(match[1]) : undefined;
345
348
  });
346
349
 
347
350
  // Only `%` widths need the activator measured — skip the ResizeObserver for
@@ -353,13 +356,29 @@ const { width: activatorWidth } = useElementSize(
353
356
  box: 'border-box',
354
357
  }
355
358
  );
359
+
360
+ /**
361
+ * `width` resolved to the `--max-w` cap. `undefined` leaves the stylesheet's
362
+ * default measure in place, which is also what an unmeasured percentage falls
363
+ * back to — a `0px` cap would collapse the bubble on the first frame.
364
+ *
365
+ * A percentage is resolved here against the activator, because CSS would
366
+ * resolve it against the bubble's own containing block (the viewport) instead.
367
+ * Every other value is handed to CSS verbatim, so `calc()`, `min()` and the
368
+ * width keywords work and a typo styles nothing rather than throwing during
369
+ * render.
370
+ */
356
371
  const parsedWidth = computed(() => {
357
- if (!props.width) return undefined;
372
+ if (props.width == null || props.width === '') return undefined;
358
373
  if (percentWidth.value != null) {
359
- if (!activatorEl.value) return '0px';
374
+ if (!activatorWidth.value) return undefined;
360
375
  return `${activatorWidth.value * (percentWidth.value / 100)}px`;
361
376
  }
362
- return `${parseWidthString(props.width).value}px`;
377
+ if (typeof props.width === 'number') {
378
+ return Number.isFinite(props.width) ? `${props.width}px` : undefined;
379
+ }
380
+ const raw = props.width.trim();
381
+ return bareNumberPattern.test(raw) ? `${raw}px` : raw;
363
382
  });
364
383
  </script>
365
384
 
@@ -466,7 +485,14 @@ export type BbTooltipProps = {
466
485
  */
467
486
  delay?: number;
468
487
  /**
469
- * Width of the tooltip bubble in pixels, or as a percentage of the activator.
488
+ * Caps the width of the tooltip bubble: it wraps at this measure but still
489
+ * shrinks to its text, so a short hint never sits in an oversized bubble.
490
+ * A bare number is read as pixels, a percentage is measured against the
491
+ * activator, and any other CSS length (`'20rem'`, `'min(320px, 90vw)'`)
492
+ * is passed through.
493
+ *
494
+ * Replaces the default cap of `40ch`, which is also reachable in CSS as
495
+ * `--max-w` on the tooltip root.
470
496
  */
471
497
  width?: number | string;
472
498
  };
@@ -524,6 +550,16 @@ export type BbTooltipSlots = {
524
550
  --fg: var(--bb-primary-fg);
525
551
  --border-color: var(--bb-primary);
526
552
  --fs: calc(var(--bb-fs) - 2px);
553
+ /*
554
+ * Default measure. CommonPopover caps the bubble at the viewport minus its
555
+ * `padding`, which on a wide screen is a single line running the width of
556
+ * the page — unreadable, and the reason a hint needs a cap of its own.
557
+ * `ch` (not px) so the column tracks the font: the same 40ch measure the
558
+ * dropdown item description uses. The `width` prop overrides it by writing
559
+ * `--max-w` inline on the content; a consumer can override it in CSS on
560
+ * this root.
561
+ */
562
+ --max-w: 40ch;
527
563
  --px: 12px;
528
564
  --py: 6px;
529
565
 
@@ -553,6 +589,17 @@ export type BbTooltipSlots = {
553
589
  display: inline-flex;
554
590
  font-size: var(--fs);
555
591
  line-height: 1.4;
592
+ max-width: var(--max-w);
593
+ /* A capped bubble has to break what it cannot wrap: the hint a tooltip
594
+ most often carries is a file path or a URL, and a token longer than
595
+ the cap would otherwise run straight out of the painted bubble.
596
+ `anywhere`, not `break-word`: the text sits in an anonymous flex item
597
+ whose automatic minimum size is its min-content width, and only
598
+ `anywhere` lets a break point count toward min-content — under
599
+ `break-word` the item stays as wide as the longest token and
600
+ overflows the cap anyway. Max-content is untouched either way, so
601
+ ordinary prose still shrink-wraps to its natural width. */
602
+ overflow-wrap: anywhere;
556
603
  padding: var(--py) var(--px);
557
604
  }
558
605
  }
@@ -34,7 +34,7 @@
34
34
  widget role the input owns or controls, and the only fitting one,
35
35
  `role="option"`, has children-presentational semantics — it would erase each
36
36
  chip's ✕ button, a real tab stop, from the accessibility tree. -->
37
- <span class="sr-only" role="status">{{ highlightAnnouncement }}</span>
37
+ <span class="bb-sr-only" role="status">{{ highlightAnnouncement }}</span>
38
38
  </template>
39
39
 
40
40
  <script setup lang="ts">
@@ -22,7 +22,7 @@
22
22
  the only signal a highlight moved. This polite status node — mounted empty,
23
23
  because a live region added together with its content does not announce —
24
24
  says which item the cursor is on. -->
25
- <span class="sr-only" role="status">{{ highlightAnnouncement }}</span>
25
+ <span class="bb-sr-only" role="status">{{ highlightAnnouncement }}</span>
26
26
  </template>
27
27
 
28
28
  <script setup lang="ts">