bitboss-ui 3.0.0-beta.21 → 3.0.0-beta.22
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.
- package/README.md +55 -3
- package/bin/bitboss-ui-mcp.mjs +419 -9
- package/bin/bitboss-ui.mjs +293 -22
- package/dist/ai/BbAccordion.md +21 -23
- package/dist/ai/BbAlert.md +6 -12
- package/dist/ai/BbAvatar.md +60 -24
- package/dist/ai/BbBadge.md +12 -20
- package/dist/ai/BbBaseButton.md +14 -16
- package/dist/ai/BbBaseCheckboxIcon.md +6 -10
- package/dist/ai/BbBaseRadioIcon.md +6 -10
- package/dist/ai/BbBaseSwitchIcon.md +5 -9
- package/dist/ai/BbBreadcrumbs.md +23 -34
- package/dist/ai/BbButton.md +35 -47
- package/dist/ai/BbCheckbox.md +21 -37
- package/dist/ai/BbCheckboxGroup.md +13 -13
- package/dist/ai/BbCollapsible.md +14 -26
- package/dist/ai/BbColorInput.md +13 -23
- package/dist/ai/BbColorPalette.md +23 -35
- package/dist/ai/BbConfirm.md +17 -32
- package/dist/ai/BbDatePickerInput.md +15 -21
- package/dist/ai/BbDialog.md +159 -62
- package/dist/ai/BbDropdown.md +21 -25
- package/dist/ai/BbDropdownButton.md +13 -30
- package/dist/ai/BbDropzone.md +9 -11
- package/dist/ai/BbIcon.md +44 -33
- package/dist/ai/BbIndicator.md +2 -2
- package/dist/ai/BbNumberInput.md +18 -20
- package/dist/ai/BbOffCanvas.md +42 -42
- package/dist/ai/BbPagination.md +21 -23
- package/dist/ai/BbPopover.md +15 -17
- package/dist/ai/BbProgress.md +40 -51
- package/dist/ai/BbRadio.md +9 -9
- package/dist/ai/BbRadioGroup.md +7 -11
- package/dist/ai/BbRating.md +8 -12
- package/dist/ai/BbSelect.md +32 -36
- package/dist/ai/BbSelectPopover.md +19 -31
- package/dist/ai/BbSlider.md +15 -19
- package/dist/ai/BbSmoothHeight.md +16 -31
- package/dist/ai/BbSpinner.md +118 -74
- package/dist/ai/BbSwitch.md +25 -18
- package/dist/ai/BbSwitchGroup.md +9 -14
- package/dist/ai/BbTable.md +102 -55
- package/dist/ai/BbTabs.md +59 -82
- package/dist/ai/BbTag.md +12 -23
- package/dist/ai/BbTextInput.md +20 -26
- package/dist/ai/BbTextarea.md +28 -34
- package/dist/ai/BbToast.md +14 -34
- package/dist/ai/BbTooltip.md +22 -20
- package/dist/ai/BbTree.md +46 -72
- package/dist/ai/changelog.json +169 -62
- package/dist/ai/components.json +1467 -9
- package/dist/ai/composables/useConfirm.md +5 -2
- package/dist/ai/composables/useToast.md +4 -2
- package/dist/ai/guides/agent-contract.md +7 -4
- package/dist/ai/guides/ai-router.md +37 -22
- package/dist/ai/guides/component-picker.md +23 -18
- package/dist/ai/guides/design-language.md +141 -13
- package/dist/ai/guides/design-tokens.md +29 -4
- package/dist/ai/guides/icons-policy.md +46 -21
- package/dist/ai/guides/installation-and-plugin-setup.md +253 -17
- package/dist/ai/guides/migration/components/bb-rating.md +26 -1
- package/dist/ai/guides/migration/components/bb-tooltip.md +18 -0
- package/dist/ai/guides/migration/v2-to-v3.md +50 -23
- package/dist/ai/index.md +1 -1
- package/dist/ai/recipes/inertia/approvals-inbox.md +3 -3
- package/dist/ai/recipes/inertia/inline-edit-workspace.md +2 -2
- package/dist/ai/recipes/inertia/onboarding.md +2 -2
- package/dist/ai/recipes/inertia/ownership-atlas.md +6 -6
- package/dist/ai/recipes/inertia/record-form.md +5 -5
- package/dist/ai/recipes/inertia/records-workspace.md +4 -4
- package/dist/ai/recipes/inertia/upload-center.md +6 -6
- package/dist/ai/recipes/inertia/wizard-form.md +5 -5
- package/dist/ai/recipes/nuxt/approvals-inbox.md +3 -3
- package/dist/ai/recipes/nuxt/inline-edit-workspace.md +2 -2
- package/dist/ai/recipes/nuxt/onboarding.md +2 -2
- package/dist/ai/recipes/nuxt/record-form.md +5 -5
- package/dist/ai/recipes/nuxt/records-workspace.md +4 -4
- package/dist/ai/recipes/nuxt/upload-center.md +6 -6
- package/dist/ai/recipes/nuxt/wizard-form.md +5 -5
- package/dist/ai/recipes/vue/approvals-inbox.md +3 -3
- package/dist/ai/recipes/vue/inline-edit-workspace.md +2 -2
- package/dist/ai/recipes/vue/onboarding.md +2 -2
- package/dist/ai/recipes/vue/record-form.md +10 -10
- package/dist/ai/recipes/vue/records-workspace.md +4 -4
- package/dist/ai/recipes/vue/upload-center.md +6 -6
- package/dist/ai/recipes/vue/wizard-form.md +5 -5
- package/dist/ai/source/BbBadge.md +1 -1
- package/dist/ai/source/BbBaseCheckbox.md +1 -1
- package/dist/ai/source/BbBaseDatePicker.md +5 -5
- package/dist/ai/source/BbBaseDatePickerInput.md +2 -2
- package/dist/ai/source/BbBaseRadio.md +1 -1
- package/dist/ai/source/BbBaseRating.md +5 -5
- package/dist/ai/source/BbBaseSelect.md +1 -1
- package/dist/ai/source/BbBaseSlider.md +5 -5
- package/dist/ai/source/BbBaseSwitch.md +1 -1
- package/dist/ai/source/BbBaseTimePickerInput.md +1 -1
- package/dist/ai/source/BbBreadcrumbs.md +20 -1
- package/dist/ai/source/BbButton.md +2 -2
- package/dist/ai/source/BbDialog.md +39 -8
- package/dist/ai/source/BbDropdown.md +1 -1
- package/dist/ai/source/BbDropzone.md +5 -5
- package/dist/ai/source/BbIcon.md +23 -10
- package/dist/ai/source/BbOffCanvas.md +50 -28
- package/dist/ai/source/BbPagination.md +2 -2
- package/dist/ai/source/BbPopover.md +12 -1
- package/dist/ai/source/BbSelectPopover.md +45 -7
- package/dist/ai/source/BbSpinner.md +2 -2
- package/dist/ai/source/BbTable.md +11 -11
- package/dist/ai/source/BbTooltip.md +57 -10
- package/dist/ai/source/ChipsBox.md +1 -1
- package/dist/ai/source/CommaBox.md +1 -1
- package/dist/ai/source/GuidesComponent.md +14 -7
- package/dist/ai/source/OptionsContainer.md +1 -1
- package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBaseCheckbox/BbBaseCheckbox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue.d.ts +1 -1
- package/dist/components/BbBaseRadio/BbBaseRadio.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/BbBaseSwitch/BbBaseSwitch.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +71 -71
- package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +102 -97
- package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +58 -58
- package/dist/components/BbDropzone/BbDropzone.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +33 -35
- package/dist/components/BbIcon/iconCache.d.ts +30 -0
- package/dist/components/BbIcon/iconCache.js +21 -3
- package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +57 -56
- package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +138 -138
- package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +43 -43
- package/dist/components/BbSpinner/BbSpinner.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbSpinner/types.d.ts +1 -1
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/components/BbTable/BbTableDataRow.js +2 -2
- package/dist/components/BbToast/BbToastRegion.vue_vue_type_script_setup_true_lang.js +62 -62
- package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +81 -78
- package/dist/components/BbTooltip/types.d.ts +8 -1
- package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/CommaBox/CommaBox.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/OptionsContainer/OptionsContainer.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/composables/useConfirm.d.ts +22 -0
- package/dist/composables/useConfirm.js +43 -40
- package/dist/composables/useSafeAreaInternal.js +1 -1
- package/dist/composables/useToast.d.ts +16 -0
- package/dist/composables/useToast.js +20 -16
- package/dist/composables/useViewportWidth.d.ts +20 -6
- package/dist/composables/useViewportWidth.js +8 -7
- package/dist/icon-scan.d.ts +276 -0
- package/dist/icon-scan.js +485 -0
- package/dist/icons-whitelist.d.ts +5 -4
- package/dist/llms-full.txt +1979 -1527
- package/dist/llms-medium.txt +461 -74
- package/dist/llms.txt +1 -1
- package/dist/nuxt.js +10 -7
- package/dist/plugin.js +1 -0
- package/dist/runtime/nuxt-plugin.js +2 -1
- package/dist/styles.css +1 -1
- package/dist/types/Config.d.ts +18 -0
- package/dist/utils/cssLayers.d.ts +91 -0
- package/dist/utils/cssLayers.js +43 -0
- package/dist/utils/injectLibraryStyles.d.ts +3 -1
- package/dist/utils/injectLibraryStyles.js +24 -23
- package/dist/vite-plugin.d.ts +74 -11
- package/dist/vite.js +292 -253
- package/llms.txt +1 -1
- package/package.json +7 -5
- package/scripts/lib/ensure-manifest.mjs +28 -5
- package/scripts/lib/eslint-plugin.d.ts +35 -0
- package/scripts/lib/eslint-plugin.mjs +299 -3
- package/scripts/lib/hand-roll-hints.mjs +50 -6
- package/scripts/lib/html-attributes.mjs +42 -0
- package/scripts/lib/mcp-config.mjs +38 -34
- package/scripts/lib/public-type-exports.mjs +246 -0
- package/scripts/lib/validate-bb-markup.mjs +205 -12
|
@@ -1074,7 +1074,7 @@ const formattedSize = computed(() => {
|
|
|
1074
1074
|
class="flex-1"
|
|
1075
1075
|
:model-value="upload.progress"
|
|
1076
1076
|
/>
|
|
1077
|
-
<span class="text-xs
|
|
1077
|
+
<span class="text-xs text-[color:var(--bb-text-muted)] tabular-nums"
|
|
1078
1078
|
>{{ upload.progress }}%</span
|
|
1079
1079
|
>
|
|
1080
1080
|
</div>
|
|
@@ -1508,7 +1508,7 @@ const clearSelection = () => {
|
|
|
1508
1508
|
<section class="flex flex-col gap-1.5">
|
|
1509
1509
|
<div class="flex items-center justify-between">
|
|
1510
1510
|
<h3
|
|
1511
|
-
class="text-xs font-semibold
|
|
1511
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1512
1512
|
>
|
|
1513
1513
|
Extracted fields
|
|
1514
1514
|
</h3>
|
|
@@ -1548,7 +1548,7 @@ const clearSelection = () => {
|
|
|
1548
1548
|
one-click cell as in the table. -->
|
|
1549
1549
|
<section class="flex flex-col gap-1.5">
|
|
1550
1550
|
<h3
|
|
1551
|
-
class="text-xs font-semibold
|
|
1551
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1552
1552
|
>
|
|
1553
1553
|
Details
|
|
1554
1554
|
</h3>
|
|
@@ -1565,7 +1565,7 @@ const clearSelection = () => {
|
|
|
1565
1565
|
<!-- Activity feed. -->
|
|
1566
1566
|
<section class="flex flex-col gap-1.5">
|
|
1567
1567
|
<h3
|
|
1568
|
-
class="text-xs font-semibold
|
|
1568
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1569
1569
|
>
|
|
1570
1570
|
Activity
|
|
1571
1571
|
</h3>
|
|
@@ -1712,7 +1712,7 @@ const longDate = (value: string) =>
|
|
|
1712
1712
|
<!-- Recent receipts: names drill down again — the stack keeps growing. -->
|
|
1713
1713
|
<section class="flex flex-col gap-1.5">
|
|
1714
1714
|
<h3
|
|
1715
|
-
class="text-xs font-semibold
|
|
1715
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1716
1716
|
>
|
|
1717
1717
|
Recent receipts
|
|
1718
1718
|
</h3>
|
|
@@ -1730,7 +1730,7 @@ const longDate = (value: string) =>
|
|
|
1730
1730
|
{{ recent.filename }}
|
|
1731
1731
|
</BbButton>
|
|
1732
1732
|
<BbBadge variant="outline">{{ recent.status }}</BbBadge>
|
|
1733
|
-
<span class="text-xs
|
|
1733
|
+
<span class="text-xs text-[color:var(--bb-text-muted)] tabular-nums">
|
|
1734
1734
|
{{ currency.format(recent.amount) }}
|
|
1735
1735
|
</span>
|
|
1736
1736
|
</li>
|
|
@@ -1042,7 +1042,7 @@ const today = new Date().toISOString().slice(0, 10);
|
|
|
1042
1042
|
>
|
|
1043
1043
|
<div class="flex items-center justify-between gap-2">
|
|
1044
1044
|
<h3
|
|
1045
|
-
class="text-xs font-semibold
|
|
1045
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1046
1046
|
>
|
|
1047
1047
|
Audience
|
|
1048
1048
|
</h3>
|
|
@@ -1089,7 +1089,7 @@ const today = new Date().toISOString().slice(0, 10);
|
|
|
1089
1089
|
>
|
|
1090
1090
|
<div class="flex items-center justify-between gap-2">
|
|
1091
1091
|
<h3
|
|
1092
|
-
class="text-xs font-semibold
|
|
1092
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1093
1093
|
>
|
|
1094
1094
|
Content
|
|
1095
1095
|
</h3>
|
|
@@ -1120,7 +1120,7 @@ const today = new Date().toISOString().slice(0, 10);
|
|
|
1120
1120
|
>
|
|
1121
1121
|
<div class="flex items-center justify-between gap-2">
|
|
1122
1122
|
<h3
|
|
1123
|
-
class="text-xs font-semibold
|
|
1123
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1124
1124
|
>
|
|
1125
1125
|
Schedule
|
|
1126
1126
|
</h3>
|
|
@@ -1286,7 +1286,7 @@ const isReview = computed(() => ctx.step.value === 'review');
|
|
|
1286
1286
|
<!-- The segment's definition, so "who is in here" is answerable in place. -->
|
|
1287
1287
|
<section class="flex flex-col gap-1.5">
|
|
1288
1288
|
<h3
|
|
1289
|
-
class="text-xs font-semibold
|
|
1289
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1290
1290
|
>
|
|
1291
1291
|
Conditions
|
|
1292
1292
|
</h3>
|
|
@@ -1305,7 +1305,7 @@ const isReview = computed(() => ctx.step.value === 'review');
|
|
|
1305
1305
|
<!-- Recent campaigns to this segment: fatigue check before sending. -->
|
|
1306
1306
|
<section class="flex flex-col gap-1.5">
|
|
1307
1307
|
<h3
|
|
1308
|
-
class="text-xs font-semibold
|
|
1308
|
+
class="text-xs font-semibold tracking-wide text-[color:var(--bb-text-muted)] uppercase"
|
|
1309
1309
|
>
|
|
1310
1310
|
Recent campaigns
|
|
1311
1311
|
</h3>
|
|
@@ -166,7 +166,7 @@ const inputAttributes = computed(() => {
|
|
|
166
166
|
// readonly is announced only via aria-readonly.
|
|
167
167
|
'aria-readonly': props.readonly ? true : undefined,
|
|
168
168
|
checked: internalChecked.value,
|
|
169
|
-
class: 'sr-only',
|
|
169
|
+
class: 'bb-sr-only',
|
|
170
170
|
disabled: props.disabled,
|
|
171
171
|
id: props.id,
|
|
172
172
|
indeterminate: props.indeterminate,
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
:class="isCoarse ? `bb-base-date-picker--unit-${homeMode}` : undefined"
|
|
14
14
|
:style="{ '--weeks': weeksInMonth }"
|
|
15
15
|
>
|
|
16
|
-
<span aria-live="polite" class="sr-only">{{ formattedYear }}</span>
|
|
17
|
-
<div aria-live="polite" class="sr-only">{{ cursorAnnouncement }}</div>
|
|
16
|
+
<span aria-live="polite" class="bb-sr-only">{{ formattedYear }}</span>
|
|
17
|
+
<div aria-live="polite" class="bb-sr-only">{{ cursorAnnouncement }}</div>
|
|
18
18
|
<div class="bb-base-date-picker__body">
|
|
19
19
|
<!-- No controls row under `type="year"`: the year list scrolls itself
|
|
20
20
|
(and extends lazily at both ends), so there is nothing for the
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
stroke-width="1.5"
|
|
43
43
|
/>
|
|
44
44
|
</svg>
|
|
45
|
-
<span class="sr-only">{{ previousPageLabel }}</span>
|
|
45
|
+
<span class="bb-sr-only">{{ previousPageLabel }}</span>
|
|
46
46
|
</BbBaseButton>
|
|
47
47
|
|
|
48
48
|
<div class="bb-base-date-picker__nav-center">
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
stroke-width="1.5"
|
|
99
99
|
/>
|
|
100
100
|
</svg>
|
|
101
|
-
<span class="sr-only">{{ nextPageLabel }}</span>
|
|
101
|
+
<span class="bb-sr-only">{{ nextPageLabel }}</span>
|
|
102
102
|
</BbBaseButton>
|
|
103
103
|
</div>
|
|
104
104
|
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
:id="`${globalId}-status`"
|
|
142
142
|
aria-atomic="true"
|
|
143
143
|
aria-live="polite"
|
|
144
|
-
class="sr-only"
|
|
144
|
+
class="bb-sr-only"
|
|
145
145
|
role="status"
|
|
146
146
|
>
|
|
147
147
|
{{ modeAnnouncement }}
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
|
|
166
166
|
<span
|
|
167
167
|
:id="descriptionId"
|
|
168
|
-
class="bb-base-date-picker-input__accessible-description sr-only"
|
|
168
|
+
class="bb-base-date-picker-input__accessible-description bb-sr-only"
|
|
169
169
|
>{{ t('baseDatePickerInput.dateFormat') }} {{ formatHint }}</span
|
|
170
170
|
>
|
|
171
171
|
<template v-if="$slots.suffix" #suffix><slot name="suffix" /></template>
|
|
@@ -1006,7 +1006,7 @@ const timeHintKey = computed(() =>
|
|
|
1006
1006
|
: 'baseDatePickerInput.timeFormat'
|
|
1007
1007
|
);
|
|
1008
1008
|
|
|
1009
|
-
/** Localized format hint used by the placeholder and the sr-only description. */
|
|
1009
|
+
/** Localized format hint used by the placeholder and the bb-sr-only description. */
|
|
1010
1010
|
const formatHint = computed(() => {
|
|
1011
1011
|
// The hint has to describe the fields that are actually there — promising
|
|
1012
1012
|
// DD/MM/YYYY to a two-field month input is worse than no hint at all.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
:role="groupRole"
|
|
21
21
|
>
|
|
22
22
|
<slot name="prepend"></slot>
|
|
23
|
-
<span v-if="showClearHint" :id="clearHintId" class="sr-only">{{
|
|
23
|
+
<span v-if="showClearHint" :id="clearHintId" class="bb-sr-only">{{
|
|
24
24
|
clearHintText
|
|
25
25
|
}}</span>
|
|
26
26
|
<label
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
'bb-base-rating__option--readonly': readonly,
|
|
35
35
|
}"
|
|
36
36
|
>
|
|
37
|
-
<span class="bb-base-rating__label-text sr-only">{{
|
|
37
|
+
<span class="bb-base-rating__label-text bb-sr-only">{{
|
|
38
38
|
option.value == 1
|
|
39
39
|
? t('rating.singleStar', option.value, props.stars).value
|
|
40
40
|
: t('rating.multipleStars', option.value, props.stars).value
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
:aria-describedby="computedAriaDescribedby"
|
|
45
45
|
:autofocus="autofocus && !index"
|
|
46
46
|
:checked="option.checked"
|
|
47
|
-
class="sr-only"
|
|
47
|
+
class="bb-sr-only"
|
|
48
48
|
:disabled="option.disabled"
|
|
49
49
|
:name="option.name"
|
|
50
50
|
:required="required"
|
|
@@ -508,7 +508,7 @@ export const sizeMap: Sizes = {
|
|
|
508
508
|
--color: var(--bb-primary);
|
|
509
509
|
--ring-color: var(--bb-ring);
|
|
510
510
|
--size: 24px;
|
|
511
|
-
--
|
|
511
|
+
--space: 0px;
|
|
512
512
|
|
|
513
513
|
/* Highlights all previous and current stars when hovering the component so it previews stars */
|
|
514
514
|
&:not(.bb-base-rating--disabled):not(.bb-base-rating--readonly):not(
|
|
@@ -558,7 +558,7 @@ export const sizeMap: Sizes = {
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
&__inner-container {
|
|
561
|
-
column-gap: var(--
|
|
561
|
+
column-gap: var(--space);
|
|
562
562
|
display: inline-flex;
|
|
563
563
|
flex-wrap: wrap;
|
|
564
564
|
}
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
so a visually-hidden copy nested inside it would double every reading.
|
|
191
191
|
The chips keep their ✕ buttons — those are real tab stops and must stay
|
|
192
192
|
named; the activator's name just stops reading them. -->
|
|
193
|
-
<span :id="`${id}__selection`" class="sr-only">{{
|
|
193
|
+
<span :id="`${id}__selection`" class="bb-sr-only">{{
|
|
194
194
|
accessibleSelectionText(text)
|
|
195
195
|
}}</span>
|
|
196
196
|
<template #append-outer
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<input
|
|
31
31
|
:id="id"
|
|
32
32
|
ref="hiddenInput"
|
|
33
|
-
class="sr-only"
|
|
33
|
+
class="bb-sr-only"
|
|
34
34
|
:disabled="disabled"
|
|
35
35
|
:name="name"
|
|
36
36
|
:readonly="true"
|
|
@@ -132,10 +132,10 @@
|
|
|
132
132
|
<!-- Localized qualifiers referenced by the range thumbs' aria-labelledby
|
|
133
133
|
so the two identical role=slider spans are distinguishable to AT. -->
|
|
134
134
|
<template v-if="props.range && props.id">
|
|
135
|
-
<span :id="`${props.id}__thumb_label_lower`" class="sr-only">{{
|
|
135
|
+
<span :id="`${props.id}__thumb_label_lower`" class="bb-sr-only">{{
|
|
136
136
|
t('slider.minimumThumb').value
|
|
137
137
|
}}</span>
|
|
138
|
-
<span :id="`${props.id}__thumb_label_higher`" class="sr-only">{{
|
|
138
|
+
<span :id="`${props.id}__thumb_label_higher`" class="bb-sr-only">{{
|
|
139
139
|
t('slider.maximumThumb').value
|
|
140
140
|
}}</span>
|
|
141
141
|
</template>
|
|
@@ -181,11 +181,11 @@ const { t } = useLocale();
|
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
183
|
* `role="slider"` is name-from-author: the container's visible `<label>`
|
|
184
|
-
* targets the sr-only hidden input (`for`/`id`), which never names these
|
|
184
|
+
* targets the bb-sr-only hidden input (`for`/`id`), which never names these
|
|
185
185
|
* sibling thumb spans — without an explicit reference the focused thumb
|
|
186
186
|
* announces as an unnamed "slider, N". The wrapped layer (BbSlider →
|
|
187
187
|
* BbBaseInputContainer) renders that label as `${id}__label`, so the thumbs
|
|
188
|
-
* point at it; range mode appends a localized sr-only qualifier so the two
|
|
188
|
+
* point at it; range mode appends a localized bb-sr-only qualifier so the two
|
|
189
189
|
* thumbs are distinguishable. Bare BbBaseSlider without a container keeps its
|
|
190
190
|
* documented hole: the reference targets nothing and the name stays empty.
|
|
191
191
|
*/
|
|
@@ -158,7 +158,7 @@ const inputAttributes = computed(() => {
|
|
|
158
158
|
// readonly is NOT disabled — native `disabled` conveys disabled.
|
|
159
159
|
'aria-readonly': props.readonly ? true : undefined,
|
|
160
160
|
checked: internalChecked.value,
|
|
161
|
-
class: 'sr-only',
|
|
161
|
+
class: 'bb-sr-only',
|
|
162
162
|
disabled: props.disabled,
|
|
163
163
|
id: props.id,
|
|
164
164
|
indeterminate: props.indeterminate,
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
|
|
130
130
|
<span
|
|
131
131
|
:id="descriptionId"
|
|
132
|
-
class="bb-base-time-picker-input__accessible-description sr-only"
|
|
132
|
+
class="bb-base-time-picker-input__accessible-description bb-sr-only"
|
|
133
133
|
>{{ formatHint }}</span
|
|
134
134
|
>
|
|
135
135
|
<template v-if="$slots.suffix" #suffix><slot name="suffix" /></template>
|
|
@@ -174,9 +174,28 @@ const props = withDefaults(defineProps<BbBreadcrumbsProps>(), {
|
|
|
174
174
|
defineSlots<BbBreadcrumbsSlots>();
|
|
175
175
|
const { t } = useLocale();
|
|
176
176
|
const logger = useLogger();
|
|
177
|
-
const FLEX_BASIS_OFFSET_WIDTH = 5;
|
|
178
177
|
const SAFETY_MARGIN_WIDTH = 20;
|
|
179
178
|
const COLLAPSE_BUFFER = 1;
|
|
179
|
+
/**
|
|
180
|
+
* Slack added to `--flex-basis` on top of the width the items actually need.
|
|
181
|
+
*
|
|
182
|
+
* It is DERIVED, not tuned: the basis is the width this component asks the flex
|
|
183
|
+
* row for, and `recompute()` must agree that everything fits at exactly that
|
|
184
|
+
* width. Substituting `availableWidth = estimatedRequiredWidth` into
|
|
185
|
+
* `effectiveAvailableWidth` cancels the offset and the safety margin and leaves
|
|
186
|
+
* `FLEX_BASIS_OFFSET_WIDTH - COLLAPSE_BUFFER`, which then has to clear
|
|
187
|
+
* `isNearThreshold`'s `SAFETY_MARGIN_WIDTH / 2` band.
|
|
188
|
+
*
|
|
189
|
+
* At the old hand-picked `5` it did not: the slack came to `5 - 1 = 4`, inside
|
|
190
|
+
* the 10px band, so at its own basis the component always declared one item
|
|
191
|
+
* overflowed. That flipped `--flex-grow` to `1`, the row grew it, everything
|
|
192
|
+
* then fitted, `--flex-grow` went back to `0`, it shrank to the basis, and the
|
|
193
|
+
* cycle repeated every frame — a self-inflicted resize→recompute loop that only
|
|
194
|
+
* showed up once an app stopped overriding `flex-basis` (Tailwind v4 puts
|
|
195
|
+
* utilities in `@layer utilities`, which lose to our unlayered rule).
|
|
196
|
+
*/
|
|
197
|
+
const COLLAPSE_BUFFER_AND_BAND = COLLAPSE_BUFFER + SAFETY_MARGIN_WIDTH / 2;
|
|
198
|
+
const FLEX_BASIS_OFFSET_WIDTH = COLLAPSE_BUFFER_AND_BAND + 1;
|
|
180
199
|
const FONT_WIDTH = 8.5;
|
|
181
200
|
|
|
182
201
|
const slots = useSlots();
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
class="bb-button__prepend-icon"
|
|
52
52
|
:icon="props['prepend:icon']"
|
|
53
53
|
/>
|
|
54
|
-
<span class="bb-button__content" :class="{ 'sr-only': !!icon }"
|
|
54
|
+
<span class="bb-button__content" :class="{ 'bb-sr-only': !!icon }"
|
|
55
55
|
><slot>{{ text }}</slot></span
|
|
56
56
|
>
|
|
57
|
-
<span v-if="loading" class="bb-button__loading-label sr-only">{{
|
|
57
|
+
<span v-if="loading" class="bb-button__loading-label bb-sr-only">{{
|
|
58
58
|
t('common.loadingText')
|
|
59
59
|
}}</span>
|
|
60
60
|
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
'bb-dialog--mobile': isMobile,
|
|
51
51
|
'bb-dialog--deny-close': denyClose,
|
|
52
52
|
'bb-dialog--covered': isCovered,
|
|
53
|
+
'bb-dialog--stacked': isStackedAbove,
|
|
53
54
|
}"
|
|
54
55
|
:inert="(!model && !isVisible) || isCovered"
|
|
55
56
|
:style="{
|
|
@@ -430,6 +431,20 @@ const isCovered = computed(() => {
|
|
|
430
431
|
return list[list.length - 1] !== stackId;
|
|
431
432
|
});
|
|
432
433
|
|
|
434
|
+
/**
|
|
435
|
+
* True when this dialog sits ABOVE another member of its stack. A stack paints
|
|
436
|
+
* ONE backdrop, owned by its bottom member; everyone above rides on it with a
|
|
437
|
+
* transparent backdrop of their own (see `--stacked` in index.css). Two things
|
|
438
|
+
* fall out: swapping members never fades a backdrop, so the page behind never
|
|
439
|
+
* blinks mid-crossfade, and N stacked dialogs cost one blur pass instead of N.
|
|
440
|
+
*/
|
|
441
|
+
const isStackedAbove = computed(() => {
|
|
442
|
+
if (!props.stack) return false;
|
|
443
|
+
const list = stacks.value[props.stack];
|
|
444
|
+
if (!list || !list.includes(stackId)) return false;
|
|
445
|
+
return list[0] !== stackId;
|
|
446
|
+
});
|
|
447
|
+
|
|
433
448
|
let releaseTopLayer: (() => void) | null = null;
|
|
434
449
|
const toggleDialog = async (value: boolean) => {
|
|
435
450
|
if (value) {
|
|
@@ -460,13 +475,21 @@ const toggleDialog = async (value: boolean) => {
|
|
|
460
475
|
emit('shown');
|
|
461
476
|
} else {
|
|
462
477
|
emit('hide');
|
|
478
|
+
// Leave the stack NOW, not after the fade. The member beneath uncovers
|
|
479
|
+
// at once and crossfades in under this one's fade-out, so it is fully
|
|
480
|
+
// visible one transition after the close — not two, with a dead window
|
|
481
|
+
// where nothing was on screen (measured 2026-09-10: 250ms of blank, the
|
|
482
|
+
// parent finishing at ~520ms). Safe to do early: while this dialog is
|
|
483
|
+
// still natively open, the HTML modal algorithm keeps everything outside
|
|
484
|
+
// it inert regardless of our classes, and Escape stays owned through the
|
|
485
|
+
// overlay stack until the listener is removed below.
|
|
486
|
+
partStack();
|
|
463
487
|
const currentOpenedTimes = openedTimes.value;
|
|
464
488
|
await wait(props.transitionDuration);
|
|
465
|
-
// Reopened during the transition — keep it open
|
|
466
|
-
//
|
|
489
|
+
// Reopened during the transition — keep it open. The open path already
|
|
490
|
+
// re-joined the stack (on top); the top-layer host was never released.
|
|
467
491
|
if (currentOpenedTimes !== openedTimes.value) return;
|
|
468
492
|
removeEscapeListener();
|
|
469
|
-
partStack();
|
|
470
493
|
dialog.value?.close();
|
|
471
494
|
// Release the top-layer host only after close(): releasing at close-start
|
|
472
495
|
// would re-home body toasts under a still-modal dialog, freezing them for
|
|
@@ -842,14 +865,22 @@ export type { BbDialogSlots as BaseDialogSlots };
|
|
|
842
865
|
animation: bb-dialog-deny-bounce 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
843
866
|
}
|
|
844
867
|
|
|
845
|
-
/* A dialog covered by a later sibling of the same stack fades out
|
|
846
|
-
only the top-most member of the stack stays visible.
|
|
868
|
+
/* A dialog covered by a later sibling of the same stack fades its BOX out —
|
|
869
|
+
only the top-most member of the stack stays visible. Its backdrop stays:
|
|
870
|
+
the bottom member owns the stack's one backdrop (below). */
|
|
847
871
|
&.bb-dialog--covered {
|
|
848
872
|
opacity: 0;
|
|
873
|
+
}
|
|
849
874
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
875
|
+
/* A member above the bottom of its stack rides on the bottom member's
|
|
876
|
+
backdrop and paints none of its own. Swapping members therefore never
|
|
877
|
+
fades a backdrop — no brightness blink mid-crossfade — and the stack costs
|
|
878
|
+
one blur pass however deep it goes. Must follow `--open`, which sets the
|
|
879
|
+
backdrop to 1 at equal specificity. */
|
|
880
|
+
&.bb-dialog--stacked::backdrop {
|
|
881
|
+
-webkit-backdrop-filter: none;
|
|
882
|
+
backdrop-filter: none;
|
|
883
|
+
opacity: 0;
|
|
853
884
|
}
|
|
854
885
|
|
|
855
886
|
&::backdrop {
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
menu container: `role="menu"` admits only items and groups, and a
|
|
95
95
|
reader in menu mode walks items — so the presentational row inside
|
|
96
96
|
is skipped and an empty search would announce nothing. -->
|
|
97
|
-
<span class="sr-only" role="status">{{ statusText }}</span>
|
|
97
|
+
<span class="bb-sr-only" role="status">{{ statusText }}</span>
|
|
98
98
|
<div v-if="$slots.footer" class="bb-dropdown__footer">
|
|
99
99
|
<slot
|
|
100
100
|
:disabled="disabled"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
:aria-label="dropzoneAriaLabel"
|
|
42
42
|
:aria-labelledby="ariaLabelledby"
|
|
43
43
|
:autofocus="autofocus"
|
|
44
|
-
class="sr-only"
|
|
44
|
+
class="bb-sr-only"
|
|
45
45
|
:disabled="disabled"
|
|
46
46
|
:multiple="multiple"
|
|
47
47
|
type="file"
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
half of that composed name ("<your copy> Select files"), so the
|
|
55
55
|
name still says the control takes files. Without it the localized
|
|
56
56
|
`aria-label` is the whole name and this span would be dead
|
|
57
|
-
sr-only chatter on every dropzone. -->
|
|
58
|
-
<span v-if="hasSlotLabel" :id="defaultLabelId" class="sr-only">{{
|
|
57
|
+
bb-sr-only chatter on every dropzone. -->
|
|
58
|
+
<span v-if="hasSlotLabel" :id="defaultLabelId" class="bb-sr-only">{{
|
|
59
59
|
dropzoneAriaLabel
|
|
60
60
|
}}</span>
|
|
61
61
|
</label>
|
|
@@ -330,7 +330,7 @@ const errorContainerId = `${containerId}_errors`;
|
|
|
330
330
|
const warningContainerId = `${containerId}_warnings`;
|
|
331
331
|
/** Handed to the slot as `labelId`; the consumer puts it on their label node. */
|
|
332
332
|
const slotLabelId = `${containerId}_label`;
|
|
333
|
-
/** Our sr-only copy of the localized instruction, appended to that name. */
|
|
333
|
+
/** Our bb-sr-only copy of the localized instruction, appended to that name. */
|
|
334
334
|
const defaultLabelId = `${containerId}_default_label`;
|
|
335
335
|
|
|
336
336
|
const dragging = ref(false);
|
|
@@ -394,7 +394,7 @@ onUpdated(syncSlotLabel);
|
|
|
394
394
|
|
|
395
395
|
/**
|
|
396
396
|
* `aria-labelledby` wins over `aria-label`, so the localized instruction has to
|
|
397
|
-
* be re-supplied as a referenced node — hence the sr-only span. Consumer copy
|
|
397
|
+
* be re-supplied as a referenced node — hence the bb-sr-only span. Consumer copy
|
|
398
398
|
* first: WCAG 2.5.3 (Label in Name) wants the visible label at the START of the
|
|
399
399
|
* accessible name so speech input can match what the user reads.
|
|
400
400
|
*/
|
package/dist/ai/source/BbIcon.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import {
|
|
19
19
|
computed,
|
|
20
20
|
inject,
|
|
21
|
-
|
|
21
|
+
shallowRef,
|
|
22
22
|
onServerPrefetch,
|
|
23
23
|
onBeforeMount,
|
|
24
24
|
watch,
|
|
@@ -36,8 +36,10 @@ import { useConfig } from '@/composables/useConfig';
|
|
|
36
36
|
import {
|
|
37
37
|
cacheKeyFor as cacheKey,
|
|
38
38
|
EMPTY_ICON,
|
|
39
|
+
MISSING_ICON,
|
|
39
40
|
parseIcon,
|
|
40
41
|
parsedIcons,
|
|
42
|
+
resolveRegistryIcon,
|
|
41
43
|
type ParsedIcon,
|
|
42
44
|
} from './iconCache';
|
|
43
45
|
import type { BbIconProps, BbIconSize } from './types';
|
|
@@ -52,7 +54,10 @@ if (process.env.NODE_ENV !== 'production')
|
|
|
52
54
|
warnRemovedAttrs('BbIcon', removedPropsFor('BbIcon'));
|
|
53
55
|
|
|
54
56
|
const logger = useLogger();
|
|
55
|
-
|
|
57
|
+
// Shallow: the registry is a (possibly Proxy-wrapped) map of loaders — there is
|
|
58
|
+
// nothing to track inside it, and a deep reactive wrapper would proxy the
|
|
59
|
+
// plugin's own Proxy on every lookup.
|
|
60
|
+
const icons = shallowRef<IconRegistry>();
|
|
56
61
|
|
|
57
62
|
const loadIcons = () => {
|
|
58
63
|
// Prefer the typed Symbol key; fall back to the legacy string key for one
|
|
@@ -102,7 +107,10 @@ const cacheKeyFor = (value: string) => cacheKey(value, isSvgPayload(value));
|
|
|
102
107
|
* Seeded from the module cache: an icon this page has already resolved renders
|
|
103
108
|
* complete in the FIRST render pass, with no loader promise and no regex.
|
|
104
109
|
*/
|
|
105
|
-
|
|
110
|
+
// Shallow on purpose: a parsed icon is immutable, and the placeholder is
|
|
111
|
+
// recognised by IDENTITY (`=== MISSING_ICON`) — a deep ref would hand back a
|
|
112
|
+
// reactive proxy of it and the comparison would never hold.
|
|
113
|
+
const parsed = shallowRef<ParsedIcon | null>(
|
|
106
114
|
parsedIcons.get(cacheKeyFor(props.icon)) ?? null
|
|
107
115
|
);
|
|
108
116
|
|
|
@@ -136,13 +144,13 @@ const getIcon = async () => {
|
|
|
136
144
|
logger.throw('Icons have not been loaded');
|
|
137
145
|
}
|
|
138
146
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
parsed.value =
|
|
147
|
+
// Never throws: a name that does not resolve yields the built-in
|
|
148
|
+
// placeholder (and one console.error per name per page), so the component
|
|
149
|
+
// tree around the icon stays mounted and the gap is visible where the icon
|
|
150
|
+
// should be — in dev exactly as in production.
|
|
151
|
+
const resolved = await resolveRegistryIcon(key, icons.value!);
|
|
152
|
+
// The prop may have moved on while the loader was in flight.
|
|
153
|
+
if (cacheKeyFor(props.icon) === key) parsed.value = resolved;
|
|
146
154
|
};
|
|
147
155
|
|
|
148
156
|
onServerPrefetch(async () => {
|
|
@@ -217,6 +225,11 @@ const iconAttributes = computed(() => {
|
|
|
217
225
|
// Icons should never be focusable or keyboard navigable since they're not interactive
|
|
218
226
|
base['focusable'] = 'false';
|
|
219
227
|
|
|
228
|
+
// The placeholder stands in for a name that did not resolve: sized and
|
|
229
|
+
// coloured exactly like the icon it replaces, and findable in the DOM (and
|
|
230
|
+
// in tests) by the name that was asked for.
|
|
231
|
+
if (parsed.value === MISSING_ICON) base['data-bb-icon-missing'] = props.icon;
|
|
232
|
+
|
|
220
233
|
return base;
|
|
221
234
|
});
|
|
222
235
|
</script>
|