bitboss-ui 3.0.0-beta.2 → 3.0.0-beta.21
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 +72 -33
- package/dist/ai/BbAccordion.md +3 -3
- package/dist/ai/BbAlert.md +9 -5
- package/dist/ai/BbAsterisk.md +17 -0
- package/dist/ai/BbAvatar.md +12 -3
- package/dist/ai/BbBadge.md +15 -10
- package/dist/ai/BbBadgeButton.md +16 -16
- package/dist/ai/BbBaseButton.md +16 -16
- package/dist/ai/BbBaseCheckbox.md +8 -8
- package/dist/ai/BbBaseCheckboxGroup.md +13 -13
- package/dist/ai/BbBaseCheckboxIcon.md +26 -9
- package/dist/ai/BbBaseColorInput.md +11 -11
- package/dist/ai/BbBaseDatePicker.md +6 -6
- package/dist/ai/BbBaseDatePickerInput.md +15 -15
- package/dist/ai/BbBaseInputContainer.md +12 -12
- package/dist/ai/BbBaseNumberInput.md +8 -8
- package/dist/ai/BbBaseRadio.md +5 -5
- package/dist/ai/BbBaseRadioGroup.md +7 -7
- package/dist/ai/BbBaseRadioIcon.md +25 -9
- package/dist/ai/BbBaseRating.md +5 -5
- package/dist/ai/BbBaseSelect.md +22 -21
- package/dist/ai/BbBaseSlider.md +9 -9
- package/dist/ai/BbBaseSwitch.md +8 -8
- package/dist/ai/BbBaseSwitchGroup.md +13 -13
- package/dist/ai/BbBaseSwitchIcon.md +28 -10
- package/dist/ai/BbBaseTag.md +10 -10
- package/dist/ai/BbBaseTextInput.md +9 -9
- package/dist/ai/BbBaseTextarea.md +9 -9
- package/dist/ai/BbBaseTimePickerInput.md +13 -13
- package/dist/ai/BbBreadcrumbs.md +2 -2
- package/dist/ai/BbButton.md +15 -15
- package/dist/ai/BbCheckbox.md +18 -17
- package/dist/ai/BbCheckboxGroup.md +15 -14
- package/dist/ai/BbCollapsible.md +11 -9
- package/dist/ai/BbColorInput.md +15 -15
- package/dist/ai/BbColorPalette.md +12 -6
- package/dist/ai/BbConfirm.md +12 -5
- package/dist/ai/BbDatePicker.md +13 -13
- package/dist/ai/BbDatePickerInput.md +20 -20
- package/dist/ai/BbDialog.md +78 -12
- package/dist/ai/BbDropdown.md +132 -22
- package/dist/ai/BbDropdownButton.md +12 -12
- package/dist/ai/BbDropdownGroup.md +1 -1
- package/dist/ai/BbDropzone.md +4 -4
- package/dist/ai/BbForm.md +49 -14
- package/dist/ai/BbIcon.md +6 -0
- package/dist/ai/BbIndicator.md +5 -5
- package/dist/ai/BbNumberInput.md +11 -11
- package/dist/ai/BbOffCanvas.md +90 -20
- package/dist/ai/BbPagination.md +3 -3
- package/dist/ai/BbPopover.md +32 -17
- package/dist/ai/BbProgress.md +7 -4
- package/dist/ai/BbRadio.md +16 -15
- package/dist/ai/BbRadioGroup.md +20 -17
- package/dist/ai/BbRating.md +11 -11
- package/dist/ai/BbSelect.md +138 -37
- package/dist/ai/BbSelectPopover.md +40 -41
- package/dist/ai/BbSlider.md +37 -22
- package/dist/ai/BbSmoothHeight.md +1 -1
- package/dist/ai/BbSwitch.md +25 -10
- package/dist/ai/BbSwitchGroup.md +17 -14
- package/dist/ai/BbTable.md +920 -109
- package/dist/ai/BbTabs.md +28 -14
- package/dist/ai/BbTabsList.md +1 -1
- package/dist/ai/BbTabsPanes.md +1 -1
- package/dist/ai/BbTabsRoot.md +10 -10
- package/dist/ai/BbTag.md +14 -14
- package/dist/ai/BbTextInput.md +16 -15
- package/dist/ai/BbTextarea.md +13 -13
- package/dist/ai/BbTimePicker.md +10 -10
- package/dist/ai/BbTimePickerInput.md +17 -17
- package/dist/ai/BbToast.md +1 -1
- package/dist/ai/BbToastPortal.md +1 -1
- package/dist/ai/BbTooltip.md +12 -10
- package/dist/ai/BbTree.md +1 -1
- package/dist/ai/ChipsBox.md +1 -1
- package/dist/ai/CommonInputWrapper.md +10 -10
- package/dist/ai/CommonPopover.md +6 -6
- package/dist/ai/FlatListBox.md +5 -5
- package/dist/ai/GroupedListBox.md +5 -5
- package/dist/ai/ListBox.md +4 -4
- package/dist/ai/OptionsContainer.md +4 -4
- package/dist/ai/changelog.json +128 -2
- package/dist/ai/components.json +917 -100
- package/dist/ai/composables/useBbConfig.md +72 -11
- package/dist/ai/composables/useConfirm.md +5 -3
- package/dist/ai/guides/ai-router.md +9 -4
- package/dist/ai/guides/component-picker.md +18 -18
- package/dist/ai/guides/design-language.md +14 -14
- package/dist/ai/guides/design-tokens.md +25 -18
- package/dist/ai/guides/fetch-items-playbook.md +1 -1
- package/dist/ai/guides/icons-policy.md +11 -5
- package/dist/ai/guides/installation-and-plugin-setup.md +85 -23
- package/dist/ai/guides/migration/components/bb-checkbox-group.md +30 -0
- package/dist/ai/guides/migration/components/bb-checkbox.md +37 -3
- package/dist/ai/guides/migration/components/bb-collapsible.md +5 -5
- package/dist/ai/guides/migration/components/bb-dialog.md +62 -34
- package/dist/ai/guides/migration/components/bb-offcanvas.md +29 -13
- package/dist/ai/guides/migration/components/bb-rating.md +8 -2
- package/dist/ai/guides/migration/components/bb-select.md +5 -2
- package/dist/ai/guides/migration/components/bb-table.md +560 -28
- package/dist/ai/guides/migration/components/bb-text-input.md +6 -6
- package/dist/ai/guides/migration/v2-to-v3.md +260 -89
- package/dist/ai/guides/ssr-prefill.md +120 -0
- package/dist/ai/index.md +10 -9
- package/dist/ai/recipes/inertia/approvals-inbox.md +0 -1
- package/dist/ai/recipes/inertia/inline-edit-workspace.md +0 -1
- package/dist/ai/recipes/inertia/layout-scaffold.md +5 -1
- package/dist/ai/recipes/inertia/ownership-atlas.md +49 -29
- package/dist/ai/recipes/inertia/record-form.md +0 -1
- package/dist/ai/recipes/inertia/records-workspace.md +26 -8
- package/dist/ai/recipes/inertia/upload-center.md +0 -1
- package/dist/ai/recipes/nuxt/approvals-inbox.md +0 -1
- package/dist/ai/recipes/nuxt/inline-edit-workspace.md +0 -1
- package/dist/ai/recipes/nuxt/layout-scaffold.md +5 -1
- package/dist/ai/recipes/nuxt/record-form.md +0 -1
- package/dist/ai/recipes/nuxt/records-workspace.md +26 -8
- package/dist/ai/recipes/nuxt/upload-center.md +0 -1
- package/dist/ai/recipes/vue/approvals-inbox.md +0 -1
- package/dist/ai/recipes/vue/inline-edit-workspace.md +0 -1
- package/dist/ai/recipes/vue/layout-scaffold.md +5 -1
- package/dist/ai/recipes/vue/records-workspace.md +26 -8
- package/dist/ai/recipes/vue/upload-center.md +0 -1
- package/dist/ai/source/BbAlert.md +60 -2
- package/dist/ai/source/BbAvatar.md +3 -3
- package/dist/ai/source/BbBadge.md +60 -15
- package/dist/ai/source/BbBadgeButton.md +8 -2
- package/dist/ai/source/BbBaseButton.md +3 -1
- package/dist/ai/source/BbBaseCheckboxGroup.md +6 -2
- package/dist/ai/source/BbBaseDatePicker.md +3 -1
- package/dist/ai/source/BbBaseDatePickerInput.md +5 -1
- package/dist/ai/source/BbBaseInputContainer.md +52 -1
- package/dist/ai/source/BbBaseRadioGroup.md +4 -0
- package/dist/ai/source/BbBaseRating.md +3 -1
- package/dist/ai/source/BbBaseSelect.md +71 -16
- package/dist/ai/source/BbBaseSwitchGroup.md +4 -0
- package/dist/ai/source/BbBaseTimePickerInput.md +5 -1
- package/dist/ai/source/BbBreadcrumbs.md +31 -0
- package/dist/ai/source/BbButton.md +51 -24
- package/dist/ai/source/BbCheckbox.md +15 -1
- package/dist/ai/source/BbCheckboxGroup.md +2 -1
- package/dist/ai/source/BbCollapsible.md +7 -1
- package/dist/ai/source/BbColorInput.md +3 -4
- package/dist/ai/source/BbColorPalette.md +3 -4
- package/dist/ai/source/BbDatePicker.md +5 -1
- package/dist/ai/source/BbDatePickerInput.md +5 -1
- package/dist/ai/source/BbDialog.md +175 -19
- package/dist/ai/source/BbDropdown.md +376 -69
- package/dist/ai/source/BbDropdownGroup.md +330 -59
- package/dist/ai/source/BbForm.md +93 -5
- package/dist/ai/source/BbIcon.md +46 -36
- package/dist/ai/source/BbNumberInput.md +3 -4
- package/dist/ai/source/BbOffCanvas.md +474 -194
- package/dist/ai/source/BbPopover.md +24 -17
- package/dist/ai/source/BbProgress.md +2 -2
- package/dist/ai/source/BbRadio.md +15 -1
- package/dist/ai/source/BbRadioGroup.md +3 -1
- package/dist/ai/source/BbSelect.md +50 -2
- package/dist/ai/source/BbSelectPopover.md +171 -23
- package/dist/ai/source/BbSlider.md +3 -4
- package/dist/ai/source/BbSwitch.md +15 -1
- package/dist/ai/source/BbSwitchGroup.md +8 -5
- package/dist/ai/source/BbTable.md +5092 -1446
- package/dist/ai/source/BbTabs.md +6 -7
- package/dist/ai/source/BbTabsList.md +6 -7
- package/dist/ai/source/BbTabsPanes.md +6 -7
- package/dist/ai/source/BbTabsRoot.md +6 -7
- package/dist/ai/source/BbTag.md +3 -4
- package/dist/ai/source/BbTextInput.md +3 -4
- package/dist/ai/source/BbTextarea.md +3 -4
- package/dist/ai/source/BbTimePicker.md +4 -1
- package/dist/ai/source/BbTimePickerInput.md +5 -1
- package/dist/ai/source/BbToast.md +3 -1
- package/dist/ai/source/BbTooltip.md +5 -1
- package/dist/ai/source/CommonInputWrapper.md +4 -0
- package/dist/ai/source/CommonPopover.md +6 -0
- package/dist/ai/source/FlatListBox.md +11 -5
- package/dist/ai/source/GroupedListBox.md +7 -5
- package/dist/components/BbAlert/BbAlert.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/BbAlert/types.d.ts +10 -1
- package/dist/components/BbAvatar/BbAvatar.vue.d.ts +1 -1
- package/dist/components/BbAvatar/BbAvatar.vue_vue_type_script_setup_true_lang.js +33 -33
- package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +22 -16
- package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +36 -15
- package/dist/components/BbBadge/badgeButtonContext.d.ts +21 -8
- package/dist/components/BbBadge/types.d.ts +8 -2
- package/dist/components/BbBaseButton/BbBaseButton.vue_vue_type_script_setup_true_lang.js +47 -47
- package/dist/components/BbBaseButton/RouterComponent.vue_vue_type_script_setup_true_lang.js +14 -14
- package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue_vue_type_script_setup_true_lang.js +30 -28
- package/dist/components/BbBaseCheckboxGroup/types.d.ts +2 -2
- package/dist/components/BbBaseDatePicker/BbBaseDatePicker.vue_vue_type_script_setup_true_lang.js +167 -167
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue.d.ts +9 -9
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +78 -78
- package/dist/components/BbBaseDatePicker/BbBaseDatePickerMonthSelector.vue_vue_type_script_setup_true_lang.js +64 -64
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue.d.ts +1 -1
- package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +138 -138
- package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue_vue_type_script_setup_true_lang.js +24 -22
- package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +40 -40
- package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +74 -65
- package/dist/components/BbBaseSelect/types.d.ts +22 -0
- package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue_vue_type_script_setup_true_lang.js +30 -28
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue.d.ts +1 -1
- package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +104 -104
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +35 -35
- package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +85 -81
- package/dist/components/BbCheckboxGroup/types.d.ts +2 -1
- package/dist/components/BbCollapsible/types.d.ts +7 -1
- package/dist/components/BbColorInput/BbColorInput.vue.d.ts +1 -1
- package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +18 -18
- package/dist/components/BbColorPalette/BbColorPalette.vue.d.ts +2 -2
- package/dist/components/BbColorPalette/BbColorPalette.vue_vue_type_script_setup_true_lang.js +50 -50
- package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +17 -17
- package/dist/components/BbDatePicker/BbDatePicker.vue.d.ts +1 -1
- package/dist/components/BbDatePicker/BbDatePicker.vue_vue_type_script_setup_true_lang.js +100 -100
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +21 -21
- package/dist/components/BbDialog/BbDialog.vue.d.ts +1 -1
- package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +133 -107
- package/dist/components/BbDialog/types.d.ts +29 -2
- package/dist/components/BbDropdown/AdaptiveDropdown.vue.d.ts +2 -0
- package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +99 -100
- package/dist/components/BbDropdown/BbDropdown.vue.d.ts +2 -2
- package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +208 -201
- package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +225 -208
- package/dist/components/BbDropdown/DropdownPipelineResolver.vue_vue_type_script_setup_true_lang.js +21 -21
- package/dist/components/BbDropdown/types.d.ts +20 -5
- package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +46 -46
- package/dist/components/BbIcon/iconCache.d.ts +33 -0
- package/dist/components/BbIcon/iconCache.js +17 -0
- package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +1 -1
- package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +15 -15
- package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +1 -1
- package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +254 -208
- package/dist/components/BbOffCanvas/types.d.ts +30 -5
- package/dist/components/BbPagination/BbPagination.vue.d.ts +1 -1
- package/dist/components/BbPopover/BbPopover.vue.d.ts +2 -2
- package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +149 -146
- package/dist/components/BbPopover/types.d.ts +3 -2
- package/dist/components/BbRadioGroup/types.d.ts +3 -1
- package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +27 -23
- package/dist/components/BbSelect/types.d.ts +32 -0
- package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +221 -213
- package/dist/components/BbSlider/BbSlider.vue.d.ts +1 -1
- package/dist/components/BbSlider/BbSlider.vue_vue_type_script_setup_true_lang.js +20 -20
- package/dist/components/BbTable/BbTable.vue.d.ts +10 -4
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +1433 -600
- package/dist/components/BbTable/BbTableDataRow.d.ts +162 -0
- package/dist/components/BbTable/BbTableDataRow.js +136 -0
- package/dist/components/BbTable/BbTableExpandRow.d.ts +73 -0
- package/dist/components/BbTable/BbTableExpandRow.js +52 -0
- package/dist/components/BbTable/types.d.ts +324 -111
- package/dist/components/BbTable/utils.d.ts +92 -4
- package/dist/components/BbTable/utils.js +71 -36
- package/dist/components/BbTabs/types.d.ts +0 -8
- package/dist/components/BbTag/BbTag.vue.d.ts +1 -1
- package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +18 -18
- package/dist/components/BbTextInput/BbTextInput.vue.d.ts +1 -1
- package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +18 -18
- package/dist/components/BbTextarea/BbTextarea.vue.d.ts +1 -1
- package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +54 -54
- package/dist/components/BbTimePicker/BbTimePicker.vue.d.ts +1 -1
- package/dist/components/BbTimePicker/BbTimePicker.vue_vue_type_script_setup_true_lang.js +80 -80
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +61 -61
- package/dist/components/BbToast/BbToast.vue_vue_type_script_setup_true_lang.js +17 -17
- package/dist/components/BbToast/BbToastMessage.vue_vue_type_script_setup_true_lang.js +13 -13
- package/dist/components/BbTooltip/types.d.ts +5 -1
- package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +17 -17
- package/dist/components/ClearableButton/ClearableButton.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/CommonPopover/CommonPopover.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +83 -83
- package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +56 -56
- package/dist/composables/useBaseOptions.js +30 -20
- package/dist/composables/useBbConfig.d.ts +27 -6
- package/dist/composables/useBbConfig.js +8 -2
- package/dist/composables/useConfig.d.ts +59 -0
- package/dist/composables/useConfig.js +9 -0
- package/dist/composables/useItemsGetter.d.ts +11 -0
- package/dist/composables/useItemsGetter.js +44 -18
- package/dist/composables/useLocale.js +10 -7
- package/dist/composables/useMobile.js +5 -5
- package/dist/composables/usePrefill.d.ts +21 -2
- package/dist/composables/usePrefill.js +25 -19
- package/dist/composables/useSsrState.d.ts +20 -0
- package/dist/composables/useSsrState.js +13 -0
- package/dist/composables/useTableWidthContext.d.ts +24 -4
- package/dist/composables/useTableWidthContext.js +31 -28
- package/dist/composables/useToast.js +5 -5
- package/dist/deprecation/ai-deprecations.json.d.ts +121 -11
- package/dist/deprecation/ai-deprecations.json.js +1 -1
- package/dist/directives/bbDropdown.d.ts +15 -4
- package/dist/directives/bbDropdown.js +31 -9
- package/dist/directives/createPopoverDirective.d.ts +62 -0
- package/dist/directives/createPopoverDirective.js +63 -23
- package/dist/i18n/locales/cs.json +4 -1
- package/dist/i18n/locales/da.json +4 -1
- package/dist/i18n/locales/de.json +4 -1
- package/dist/i18n/locales/el.json +4 -1
- package/dist/i18n/locales/en.json +4 -1
- package/dist/i18n/locales/es.json +4 -1
- package/dist/i18n/locales/fi.json +4 -1
- package/dist/i18n/locales/fr.json +4 -1
- package/dist/i18n/locales/hu.json +4 -1
- package/dist/i18n/locales/it.json +4 -1
- package/dist/i18n/locales/ja.json +4 -1
- package/dist/i18n/locales/ko.json +4 -1
- package/dist/i18n/locales/nb.json +4 -1
- package/dist/i18n/locales/nl.json +4 -1
- package/dist/i18n/locales/pl.json +4 -1
- package/dist/i18n/locales/pt.json +4 -1
- package/dist/i18n/locales/ro.json +4 -1
- package/dist/i18n/locales/ru.json +4 -1
- package/dist/i18n/locales/sv.json +4 -1
- package/dist/i18n/locales/tr.json +4 -1
- package/dist/i18n/locales/uk.json +4 -1
- package/dist/i18n/locales/zh-cn.json +4 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +69 -67
- package/dist/llms-full.txt +3997 -1458
- package/dist/llms-medium.txt +140 -73
- package/dist/llms.txt +7 -6
- package/dist/locale-blueprint.json +4 -1
- package/dist/nuxt-auto-imports.d.ts +100 -0
- package/dist/nuxt-auto-imports.js +55 -0
- package/dist/nuxt-module.d.ts +22 -1
- package/dist/nuxt.js +22 -8
- package/dist/plugin.d.ts +10 -1
- package/dist/plugin.js +20 -19
- package/dist/runtime/nuxt-plugin.js +28 -21
- package/dist/styles.css +1 -1
- package/dist/utilities/misc/listboxMetrics.d.ts +3 -3
- package/dist/utilities/misc/listboxMetrics.js +2 -2
- package/dist/utilities/misc/ssrStateInjectionKey.d.ts +42 -0
- package/dist/utilities/misc/ssrStateInjectionKey.js +4 -0
- package/dist/validated/BbForm.vue_vue_type_script_setup_true_lang.js +45 -27
- package/dist/validated/BbSelect.vue.d.ts +2 -0
- package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +1 -0
- package/dist/validated/useValidatedField.js +25 -23
- package/dist/vite.js +7 -3
- package/llms.txt +8 -7
- package/package.json +2 -2
- package/scripts/lib/eslint-plugin.d.ts +19 -1
- package/scripts/lib/eslint-plugin.mjs +155 -14
- package/scripts/lib/html-attributes.mjs +91 -0
- package/scripts/lib/validate-bb-markup.mjs +59 -11
- package/dist/assets/svgs/sort.svg_raw.js +0 -4
- package/dist/components/BbBadge/BadgeAppendIcon.vue.d.ts +0 -3
- package/dist/components/BbBadge/BadgeAppendIcon.vue.js +0 -5
- package/dist/components/BbBadge/BadgeAppendIcon.vue_vue_type_script_setup_true_lang.js +0 -22
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
<template>
|
|
9
9
|
<dialog
|
|
10
10
|
ref="dialog"
|
|
11
|
+
:aria-describedby="
|
|
12
|
+
hasRenderedAtLeastOnce && hasDescription() ? descriptionId : undefined
|
|
13
|
+
"
|
|
11
14
|
:aria-labelledby="
|
|
12
|
-
hasRenderedAtLeastOnce &&
|
|
15
|
+
hasRenderedAtLeastOnce && (hasTitle() || $slots.header)
|
|
16
|
+
? titleId
|
|
17
|
+
: undefined
|
|
13
18
|
"
|
|
14
19
|
class="bb-offcanvas"
|
|
15
20
|
:class="
|
|
@@ -20,6 +25,7 @@
|
|
|
20
25
|
'bb-offcanvas--fullscreen': isFullscreen,
|
|
21
26
|
'bb-offcanvas--deny-close': denyClose,
|
|
22
27
|
'bb-offcanvas--dragging': isDragging,
|
|
28
|
+
'bb-offcanvas--swipe-exit': swipeExiting,
|
|
23
29
|
'bb-offcanvas--instant-stack': instantStack,
|
|
24
30
|
'bb-offcanvas--covered': isCovered,
|
|
25
31
|
'bb-offcanvas--has-handle': draggable,
|
|
@@ -39,7 +45,7 @@
|
|
|
39
45
|
@cancel="cancelableClose"
|
|
40
46
|
@click.self="onBackdropClick"
|
|
41
47
|
@pointercancel="resetBackdropPointerState"
|
|
42
|
-
@pointerdown="
|
|
48
|
+
@pointerdown="onBackdropPointerDown"
|
|
43
49
|
>
|
|
44
50
|
<template v-if="hasRenderedAtLeastOnce">
|
|
45
51
|
<div
|
|
@@ -49,9 +55,18 @@
|
|
|
49
55
|
></div>
|
|
50
56
|
<div v-if="renderHeader()" class="bb-offcanvas__header">
|
|
51
57
|
<slot :close="close" name="header" :title="title" :title-id="titleId">
|
|
52
|
-
<span :id="titleId" class="bb-offcanvas__title">
|
|
58
|
+
<span v-if="hasTitle()" :id="titleId" class="bb-offcanvas__title">
|
|
53
59
|
<slot name="title" :text="title">{{ title }}</slot>
|
|
54
60
|
</span>
|
|
61
|
+
<p
|
|
62
|
+
v-if="hasDescription()"
|
|
63
|
+
:id="descriptionId"
|
|
64
|
+
class="bb-offcanvas__description"
|
|
65
|
+
>
|
|
66
|
+
<slot name="description" :text="description">{{
|
|
67
|
+
description
|
|
68
|
+
}}</slot>
|
|
69
|
+
</p>
|
|
55
70
|
<CloseButton
|
|
56
71
|
v-if="!hideClose"
|
|
57
72
|
:aria-label="t('offCanvas.closeLabel').value"
|
|
@@ -62,6 +77,34 @@
|
|
|
62
77
|
<div ref="body" class="bb-offcanvas__body scrollbar-border">
|
|
63
78
|
<div class="bb-offcanvas__body-content">
|
|
64
79
|
<slot :close="close"></slot>
|
|
80
|
+
|
|
81
|
+
<!--
|
|
82
|
+
Q18.12 — the headerless close.
|
|
83
|
+
Without it a panel with no `title` and no `#header` had NO close
|
|
84
|
+
control at all: for an adaptive BbDialog that meant a floating ×
|
|
85
|
+
on desktop and none on the phone, from one template. The
|
|
86
|
+
positioning CSS for this button already shipped
|
|
87
|
+
(`index.css` `.bb-offcanvas__body-content > .bb-close-button`) —
|
|
88
|
+
it was dead, waiting for the element.
|
|
89
|
+
|
|
90
|
+
The guard is the dialog's, so delegating the close stays
|
|
91
|
+
possible the same two ways: `hide-close` drops it entirely, and
|
|
92
|
+
a `#header` slot means you are rendering your own control.
|
|
93
|
+
|
|
94
|
+
AFTER the slot, unlike BbDialog, and that is load-bearing.
|
|
95
|
+
`showModal()` focuses the first focusable element in the dialog,
|
|
96
|
+
so rendering the × first made it steal the opening focus — it
|
|
97
|
+
broke BbColorPalette, whose contract is that opening the sheet
|
|
98
|
+
focuses the palette. The button is `position: absolute`, so DOM
|
|
99
|
+
order does not move it visually; it only decides who the browser
|
|
100
|
+
focuses and where the × sits in the tab order (last, after the
|
|
101
|
+
content it closes).
|
|
102
|
+
-->
|
|
103
|
+
<CloseButton
|
|
104
|
+
v-if="!renderHeader() && !hideClose"
|
|
105
|
+
:aria-label="t('offCanvas.closeLabel').value"
|
|
106
|
+
@click="onCloseButtonClick"
|
|
107
|
+
/>
|
|
65
108
|
</div>
|
|
66
109
|
</div>
|
|
67
110
|
|
|
@@ -82,6 +125,7 @@ import {
|
|
|
82
125
|
watch,
|
|
83
126
|
type Ref,
|
|
84
127
|
} from 'vue';
|
|
128
|
+
import { useSwipe } from '@vueuse/core';
|
|
85
129
|
import { parseSize } from '@/utilities/functions/parseSize';
|
|
86
130
|
import {
|
|
87
131
|
pushEscapeEntry,
|
|
@@ -90,6 +134,7 @@ import {
|
|
|
90
134
|
} from '@/utils/overlayEscapeStack';
|
|
91
135
|
import { useId } from '@/composables/useId';
|
|
92
136
|
import { useLocale } from '@/composables/useLocale';
|
|
137
|
+
import { useLogger } from '@/composables/useLogger';
|
|
93
138
|
import { useMobile } from '@/composables/useMobile';
|
|
94
139
|
import { viewportWidth, viewportHeight } from '@/composables/useViewportWidth';
|
|
95
140
|
import { useBreakpoint } from '@/composables/useBreakpoint';
|
|
@@ -99,7 +144,7 @@ import { warnRemovedAttrs } from '@/utilities/functions/warnRemovedAttrs';
|
|
|
99
144
|
import { wait } from '@/utilities/functions/wait';
|
|
100
145
|
import { transitions } from '@/utilities/misc/transitions';
|
|
101
146
|
import { overlaySurfaceIsolation } from '@/utils/overlaySurfaceIsolation';
|
|
102
|
-
import {
|
|
147
|
+
import { useConfig } from '@/composables/useConfig';
|
|
103
148
|
import CloseButton from '../CloseIcon/CloseButton.vue';
|
|
104
149
|
import { useOffCanvasStack } from './useOffCanvasStack';
|
|
105
150
|
import { registerTopLayerHost } from '@/composables/useTopLayer';
|
|
@@ -109,7 +154,6 @@ import type {
|
|
|
109
154
|
BbOffCanvasSlots,
|
|
110
155
|
Sizes,
|
|
111
156
|
} from './types';
|
|
112
|
-
export type { BbOffCanvasProps, BbOffCanvasEvents, BbOffCanvasSlots, Sizes };
|
|
113
157
|
|
|
114
158
|
/**
|
|
115
159
|
* Used by v-model to open / close the panel. A panel with no `v-model`
|
|
@@ -118,7 +162,6 @@ export type { BbOffCanvasProps, BbOffCanvasEvents, BbOffCanvasSlots, Sizes };
|
|
|
118
162
|
* @defaultValue `false`
|
|
119
163
|
*/
|
|
120
164
|
const model = defineModel<boolean>({ default: false });
|
|
121
|
-
|
|
122
165
|
const props = withDefaults(defineProps<BbOffCanvasProps>(), {
|
|
123
166
|
side: 'left',
|
|
124
167
|
size: 'sm',
|
|
@@ -126,10 +169,10 @@ const props = withDefaults(defineProps<BbOffCanvasProps>(), {
|
|
|
126
169
|
draggable: false,
|
|
127
170
|
stackGap: 60,
|
|
128
171
|
});
|
|
129
|
-
|
|
130
172
|
const emit = defineEmits<BbOffCanvasEvents>();
|
|
131
|
-
|
|
132
173
|
const slots = defineSlots<BbOffCanvasSlots>();
|
|
174
|
+
const config = useConfig();
|
|
175
|
+
export type { BbOffCanvasProps, BbOffCanvasEvents, BbOffCanvasSlots, Sizes };
|
|
133
176
|
|
|
134
177
|
if (process.env.NODE_ENV !== 'production')
|
|
135
178
|
warnRemovedAttrs('BbOffCanvas', removedPropsFor('BbOffCanvas'));
|
|
@@ -139,7 +182,15 @@ const { t } = useLocale();
|
|
|
139
182
|
// computed would latch its first value and miss a slot that appears later (e.g.
|
|
140
183
|
// BbDropdown's adaptive sheet only provides `#header` once you drill into a
|
|
141
184
|
// submenu). Evaluated per render, it tracks the slot's presence correctly.
|
|
142
|
-
|
|
185
|
+
// `#title` counts as a title, exactly like the prop — the twin of BbDialog's
|
|
186
|
+
// `renderHeader`. Without it a panel that only slotted its title rendered no
|
|
187
|
+
// header, so the declared slot was silently inert (and an adaptive BbDialog
|
|
188
|
+
// forwards `#title` straight through to here).
|
|
189
|
+
const hasTitle = () => !!props.title || !!slots.title;
|
|
190
|
+
// A description raises the header on its own — a describing line with no title
|
|
191
|
+
// is a legitimate shape — but it is NOT a name, so `aria-labelledby` keys off
|
|
192
|
+
// `hasTitle()` while the header keys off both.
|
|
193
|
+
const renderHeader = () => hasTitle() || !!slots.header || hasDescription();
|
|
143
194
|
const shouldPreventClose = computed(
|
|
144
195
|
() => !!(props.disabled || props.persistent)
|
|
145
196
|
);
|
|
@@ -154,6 +205,11 @@ const hasRenderedAtLeastOnce: Ref<boolean> = useUntil(
|
|
|
154
205
|
);
|
|
155
206
|
|
|
156
207
|
const titleId = `title_${useId().id.value}`;
|
|
208
|
+
// Twin of BbDialog's: `aria-describedby` points at the dedicated description
|
|
209
|
+
// and nothing else, which is what makes the wiring safe here (see the note on
|
|
210
|
+
// BbDialog's `descriptionId`).
|
|
211
|
+
const descriptionId = `desc_${useId().id.value}`;
|
|
212
|
+
const hasDescription = () => !!props.description || !!slots.description;
|
|
157
213
|
const openedTimes = ref(0);
|
|
158
214
|
const isVisible = ref(false);
|
|
159
215
|
|
|
@@ -291,19 +347,41 @@ const toggleDialog = async (value: boolean) => {
|
|
|
291
347
|
releaseTopLayer?.();
|
|
292
348
|
releaseTopLayer = null;
|
|
293
349
|
isVisible.value = false;
|
|
350
|
+
swipeDismissPending = false;
|
|
351
|
+
swipeExiting.value = false;
|
|
294
352
|
clearDragVisual();
|
|
295
353
|
emit('hidden');
|
|
296
354
|
}
|
|
297
355
|
};
|
|
298
356
|
|
|
357
|
+
/*
|
|
358
|
+
* Q18.13 — see the twin in BbDialog. `aria-labelledby` points at `titleId` as
|
|
359
|
+
* soon as a `#header` slot exists, but the heading inside it is the author's,
|
|
360
|
+
* so a forgotten id leaves the panel announcing as unnamed with no signal.
|
|
361
|
+
* Warn once, in dev, after the header is in the DOM.
|
|
362
|
+
*/
|
|
363
|
+
const warnIfHeaderNameIsDangling = () => {
|
|
364
|
+
if (process.env.NODE_ENV === 'production') return;
|
|
365
|
+
if (!slots.header || props.title) return;
|
|
366
|
+
nextTick(() => {
|
|
367
|
+
if (!dialog.value || document.getElementById(titleId)) return;
|
|
368
|
+
useLogger().warn(
|
|
369
|
+
`BbOffCanvas: the #header slot does not contain an element with id="${titleId}", so aria-labelledby points at nothing and the panel announces as unnamed. Bind the slot's \`titleId\` to your heading: <template #header="{ titleId }"><h2 :id="titleId">…</h2></template>.`
|
|
370
|
+
);
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
|
|
299
374
|
watch(model, toggleDialog);
|
|
300
375
|
onMounted(() => {
|
|
301
376
|
if (model.value) toggleDialog(true);
|
|
377
|
+
warnIfHeaderNameIsDangling();
|
|
302
378
|
});
|
|
303
379
|
onBeforeUnmount(() => {
|
|
304
380
|
clearTimeout(denyCloseTimer);
|
|
305
381
|
removeEscapeListener();
|
|
306
|
-
|
|
382
|
+
body.value?.removeEventListener('scroll', onBodyScroll);
|
|
383
|
+
bodyResizeObserver?.disconnect();
|
|
384
|
+
bodyResizeObserver = null;
|
|
307
385
|
leaveStack();
|
|
308
386
|
releaseTopLayer?.();
|
|
309
387
|
releaseTopLayer = null;
|
|
@@ -526,50 +604,108 @@ watch(
|
|
|
526
604
|
);
|
|
527
605
|
|
|
528
606
|
// ---------------------------------------------------------------------------
|
|
529
|
-
//
|
|
607
|
+
// Swipe-to-dismiss — VueUse `useSwipe` (fixed px threshold, not panel height).
|
|
530
608
|
// ---------------------------------------------------------------------------
|
|
531
609
|
const body = ref<HTMLElement | null>(null);
|
|
532
610
|
const isDragging = ref(false);
|
|
611
|
+
/** Fades `::backdrop` in parallel with the swipe-exit `transform` (Vaul overlay). */
|
|
612
|
+
const swipeExiting = ref(false);
|
|
533
613
|
|
|
534
|
-
/**
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const
|
|
614
|
+
/**
|
|
615
|
+
* Absolute travel toward the exit edge before the sheet dismisses.
|
|
616
|
+
* Height-ratio thresholds made tall filter sheets feel sticky and short ones
|
|
617
|
+
* nearly impossible; VueUse's default 50px reads the same on both.
|
|
618
|
+
*/
|
|
619
|
+
const SWIPE_THRESHOLD = 50;
|
|
620
|
+
/**
|
|
621
|
+
* Start following the finger well below the dismiss threshold. `useSwipe`'s
|
|
622
|
+
* threshold also gates `onSwipe`, so a 50px track delay left the first 50px to
|
|
623
|
+
* native pan — on a row `<button>` that pan never reached the sheet body.
|
|
624
|
+
*/
|
|
625
|
+
const SWIPE_TRACK_PX = 8;
|
|
626
|
+
/**
|
|
627
|
+
* Vaul sheet motion — 500ms / `cubic-bezier(0.32, 0.72, 0, 1)` (iOS sheet curve).
|
|
628
|
+
* Used for spring-back AND swipe-dismiss; both stay on the drag `transform`
|
|
629
|
+
* layer (never a CSS keyframe swap — that samples identity and flashes).
|
|
630
|
+
* @see https://emilkowal.ski/ui/building-a-drawer-component
|
|
631
|
+
*/
|
|
632
|
+
const SWIPE_EXIT_MS = 500;
|
|
633
|
+
const SWIPE_EXIT_EASE = 'cubic-bezier(0.32, 0.72, 0, 1)';
|
|
634
|
+
|
|
635
|
+
/** True while a swipe-dismiss transform is finishing — blocks clear mid-exit. */
|
|
636
|
+
let swipeDismissPending = false;
|
|
637
|
+
|
|
638
|
+
let bodyResizeObserver: ResizeObserver | null = null;
|
|
639
|
+
/** Candidate gesture (touch on the panel, not the backdrop). */
|
|
640
|
+
let swipeCandidate = false;
|
|
641
|
+
/** Started inside the scrollable body — may need to hand off to scroll. */
|
|
642
|
+
let swipeStartedInBody = false;
|
|
643
|
+
/** Sheet is following the finger (dismiss-axis past threshold). */
|
|
644
|
+
let swipeClaimed = false;
|
|
645
|
+
/**
|
|
646
|
+
* Vaul `shouldDrag`: decided at touchstart, not per-move. Live `scrollTop`
|
|
647
|
+
* during the first 50px lets native pan and dismiss fight (tall auto sheets
|
|
648
|
+
* only overflow by a few px, so the edge flickers). Once this gesture is
|
|
649
|
+
* scrolling, it stays scrolling.
|
|
650
|
+
*/
|
|
651
|
+
let swipeLockedToScroll = false;
|
|
540
652
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
samples: Array<{ t: number; pos: number }>;
|
|
653
|
+
/**
|
|
654
|
+
* Allow vertical panning on the body so a tall sheet can scroll. Dismiss vs
|
|
655
|
+
* scroll is latched in `useSwipe` (`scrollLocksThisGesture`); directional
|
|
656
|
+
* `pan-up` / `pan-down` at the edge blocked scrolling filter sheets on mobile.
|
|
657
|
+
* `pan-y` lives in CSS; this only clears the inline `none` written during drag.
|
|
658
|
+
*/
|
|
659
|
+
const syncBodyDismissTouchAction = () => {
|
|
660
|
+
body.value?.style.removeProperty('touch-action');
|
|
550
661
|
};
|
|
551
|
-
let gesture: DragGesture | null = null;
|
|
552
662
|
|
|
553
|
-
const
|
|
554
|
-
|
|
663
|
+
const onBodyScroll = () => syncBodyDismissTouchAction();
|
|
664
|
+
|
|
665
|
+
watch(
|
|
666
|
+
body,
|
|
667
|
+
(el, prev) => {
|
|
668
|
+
prev?.removeEventListener('scroll', onBodyScroll);
|
|
669
|
+
bodyResizeObserver?.disconnect();
|
|
670
|
+
bodyResizeObserver = null;
|
|
671
|
+
if (!el) return;
|
|
672
|
+
el.addEventListener('scroll', onBodyScroll, { passive: true });
|
|
673
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
674
|
+
bodyResizeObserver = new ResizeObserver(() =>
|
|
675
|
+
syncBodyDismissTouchAction()
|
|
676
|
+
);
|
|
677
|
+
bodyResizeObserver.observe(el);
|
|
678
|
+
}
|
|
679
|
+
syncBodyDismissTouchAction();
|
|
680
|
+
},
|
|
681
|
+
{ flush: 'post' }
|
|
682
|
+
);
|
|
555
683
|
|
|
556
|
-
|
|
557
|
-
|
|
684
|
+
watch([() => props.draggable, () => props.side, isVisible] as const, () =>
|
|
685
|
+
syncBodyDismissTouchAction()
|
|
686
|
+
);
|
|
558
687
|
|
|
559
688
|
/**
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
* resting offset happen in the same synchronous tick — no race with Vue's flush.
|
|
689
|
+
* Travel toward the exit edge in px. `useSwipe`'s lengthX/Y are
|
|
690
|
+
* `start − end`, so a finger moving down yields a negative lengthY.
|
|
563
691
|
*/
|
|
692
|
+
const closeAmountFromSwipe = () => {
|
|
693
|
+
if (dragAxis.value === 'y') {
|
|
694
|
+
return closeSign.value === 1 ? -lengthY.value : lengthY.value;
|
|
695
|
+
}
|
|
696
|
+
return closeSign.value === 1 ? -lengthX.value : lengthX.value;
|
|
697
|
+
};
|
|
698
|
+
|
|
564
699
|
const beginDragVisual = () => {
|
|
565
700
|
if (dialog.value) dialog.value.style.transition = 'none';
|
|
701
|
+
if (body.value && dragAxis.value === 'y') {
|
|
702
|
+
body.value.style.touchAction = 'none';
|
|
703
|
+
}
|
|
566
704
|
};
|
|
567
705
|
|
|
568
706
|
const applyDragVisual = (closeAmount: number) => {
|
|
569
707
|
const el = dialog.value;
|
|
570
708
|
if (!el) return;
|
|
571
|
-
// Follow the pointer toward the exit edge with a direct compositor translate
|
|
572
|
-
// (no CSS var, no calc). Over-opening does nothing — no elastic stretch.
|
|
573
709
|
const px = closeAmount > 0 ? closeAmount * closeSign.value : 0;
|
|
574
710
|
el.style.transform =
|
|
575
711
|
dragAxis.value === 'x'
|
|
@@ -577,159 +713,185 @@ const applyDragVisual = (closeAmount: number) => {
|
|
|
577
713
|
: `translate3d(0, ${px}px, 0)`;
|
|
578
714
|
};
|
|
579
715
|
|
|
580
|
-
/**
|
|
716
|
+
/** Fully off-screen on the drag layer — same units as Vaul's `slideTo*` `to`. */
|
|
717
|
+
const applyDismissTransform = () => {
|
|
718
|
+
const el = dialog.value;
|
|
719
|
+
if (!el) return;
|
|
720
|
+
el.style.transform =
|
|
721
|
+
props.side === 'bottom'
|
|
722
|
+
? 'translate3d(0, 100%, 0)'
|
|
723
|
+
: props.side === 'top'
|
|
724
|
+
? 'translate3d(0, -100%, 0)'
|
|
725
|
+
: props.side === 'left'
|
|
726
|
+
? 'translate3d(-100%, 0, 0)'
|
|
727
|
+
: 'translate3d(100%, 0, 0)';
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Vaul `resetDrawer`: spring to rest on the same inline `transform` that drag
|
|
732
|
+
* wrote — never clear to identity then re-enable transition (one-frame flash).
|
|
733
|
+
*/
|
|
581
734
|
const endDragVisual = (springBack: boolean) => {
|
|
582
735
|
const el = dialog.value;
|
|
583
736
|
if (!el) return;
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
737
|
+
if (springBack) {
|
|
738
|
+
el.style.transition = `transform ${SWIPE_EXIT_MS}ms ${SWIPE_EXIT_EASE}`;
|
|
739
|
+
el.style.transform = 'translate3d(0, 0, 0)';
|
|
740
|
+
window.setTimeout(() => {
|
|
741
|
+
if (!el.isConnected || swipeDismissPending) return;
|
|
742
|
+
el.style.removeProperty('transform');
|
|
743
|
+
el.style.removeProperty('transition');
|
|
744
|
+
syncBodyDismissTouchAction();
|
|
745
|
+
}, SWIPE_EXIT_MS);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
el.style.transition = '';
|
|
749
|
+
syncBodyDismissTouchAction();
|
|
588
750
|
};
|
|
589
751
|
|
|
590
|
-
/** Hard reset with no animation, once the panel is fully hidden. */
|
|
591
752
|
const clearDragVisual = () => {
|
|
592
753
|
const el = dialog.value;
|
|
593
754
|
if (!el) return;
|
|
755
|
+
// Leave the frozen off-screen `transform` until `toggleDialog` has waited
|
|
756
|
+
// out the backdrop fade and is about to `dialog.close()`. Clearing earlier
|
|
757
|
+
// restores the stylesheet `transform` transition while `--open` can still
|
|
758
|
+
// be at rest — that is the on-screen flash.
|
|
759
|
+
if (swipeDismissPending) return;
|
|
594
760
|
el.style.removeProperty('transition');
|
|
595
761
|
el.style.removeProperty('transform');
|
|
596
762
|
};
|
|
597
763
|
|
|
598
|
-
/**
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
764
|
+
/**
|
|
765
|
+
* Vaul dismiss: drawer and overlay move together on one clock, both on their
|
|
766
|
+
* own properties (drawer = inline `transform`, overlay = `::backdrop` opacity).
|
|
767
|
+
* `close()` is called only after the drawer is off-screen; `toggleDialog` then
|
|
768
|
+
* waits `transitionDuration` so the already-fading backdrop can finish before
|
|
769
|
+
* `dialog.close()` tears it down. Styles stay frozen until that wait ends.
|
|
770
|
+
*/
|
|
771
|
+
const finishDismissFromDrag = async () => {
|
|
772
|
+
const el = dialog.value;
|
|
773
|
+
if (!el) {
|
|
774
|
+
close();
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
swipeDismissPending = true;
|
|
778
|
+
swipeExiting.value = true;
|
|
779
|
+
el.style.transition = `transform ${SWIPE_EXIT_MS}ms ${SWIPE_EXIT_EASE}`;
|
|
780
|
+
applyDismissTransform();
|
|
781
|
+
await wait(SWIPE_EXIT_MS);
|
|
782
|
+
if (!model.value) {
|
|
783
|
+
swipeDismissPending = false;
|
|
784
|
+
swipeExiting.value = false;
|
|
785
|
+
el.style.removeProperty('transform');
|
|
786
|
+
el.style.removeProperty('transition');
|
|
787
|
+
syncBodyDismissTouchAction();
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
// Freeze the off-screen pose. Keep `transition: none` so `close()` cannot
|
|
791
|
+
// run a CSS `translate` close on the same element. Backdrop keeps fading
|
|
792
|
+
// via `--swipe-exit` / `--open` removal — those live on `::backdrop`.
|
|
793
|
+
el.style.transition = 'none';
|
|
794
|
+
applyDismissTransform();
|
|
795
|
+
close();
|
|
612
796
|
};
|
|
613
797
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
798
|
+
/** Whether the scrollable body should absorb the gesture as a scroll instead. */
|
|
799
|
+
const isVerticallyScrollable = (el: HTMLElement) => {
|
|
800
|
+
const { overflowY } = getComputedStyle(el);
|
|
801
|
+
if (overflowY !== 'auto' && overflowY !== 'scroll') return false;
|
|
802
|
+
return el.scrollHeight > el.clientHeight + 1;
|
|
617
803
|
};
|
|
618
804
|
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (!props.draggable) return;
|
|
624
|
-
// The dialog element itself is the backdrop — handled by click, not drag.
|
|
625
|
-
if (event.target === event.currentTarget) return;
|
|
626
|
-
if (event.button !== 0 || !dialog.value) return;
|
|
627
|
-
if (gesture) abandonGesture();
|
|
628
|
-
|
|
629
|
-
const rect = dialog.value.getBoundingClientRect();
|
|
630
|
-
gesture = {
|
|
631
|
-
pointerId: event.pointerId,
|
|
632
|
-
startX: event.clientX,
|
|
633
|
-
startY: event.clientY,
|
|
634
|
-
size: dragAxis.value === 'x' ? rect.width : rect.height,
|
|
635
|
-
startedInBody: !!(event.target as HTMLElement).closest?.(
|
|
636
|
-
'.bb-offcanvas__body'
|
|
637
|
-
),
|
|
638
|
-
decided: false,
|
|
639
|
-
active: false,
|
|
640
|
-
samples: [{ t: performance.now(), pos: axisPos(event) }],
|
|
641
|
-
};
|
|
642
|
-
window.addEventListener('pointermove', onPanelPointerMove, {
|
|
643
|
-
passive: false,
|
|
644
|
-
});
|
|
645
|
-
window.addEventListener('pointerup', onPanelPointerUp);
|
|
646
|
-
window.addEventListener('pointercancel', onPanelPointerCancel);
|
|
647
|
-
};
|
|
805
|
+
const isAtDismissEdge = (el: HTMLElement) =>
|
|
806
|
+
closeSign.value === 1
|
|
807
|
+
? el.scrollTop <= 1
|
|
808
|
+
: el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
|
|
648
809
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const dominantAlongAxis = Math.abs(along) >= Math.abs(cross);
|
|
662
|
-
// Cross-axis swipe, or one the body can scroll: hand off to native scroll.
|
|
663
|
-
if (
|
|
664
|
-
!dominantAlongAxis ||
|
|
665
|
-
(gesture.startedInBody && bodyAbsorbsScroll(closeAmount))
|
|
666
|
-
) {
|
|
667
|
-
abandonGesture();
|
|
668
|
-
return;
|
|
810
|
+
/**
|
|
811
|
+
* True when a scrollable ancestor under the finger is not at its dismiss edge.
|
|
812
|
+
* Latched at start so a 1px overflow on a `fit-content` (then capped) sheet
|
|
813
|
+
* cannot flip mid-gesture as native pan moves `scrollTop`.
|
|
814
|
+
*/
|
|
815
|
+
const scrollLocksThisGesture = (target: Element) => {
|
|
816
|
+
const root = dialog.value;
|
|
817
|
+
if (!root) return false;
|
|
818
|
+
let el: Element | null = target;
|
|
819
|
+
while (el && el !== root) {
|
|
820
|
+
if (el instanceof HTMLElement && isVerticallyScrollable(el)) {
|
|
821
|
+
if (!isAtDismissEdge(el)) return true;
|
|
669
822
|
}
|
|
670
|
-
|
|
671
|
-
isDragging.value = true;
|
|
672
|
-
beginDragVisual();
|
|
823
|
+
el = el.parentElement;
|
|
673
824
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
event.preventDefault();
|
|
677
|
-
gesture.samples.push({ t: performance.now(), pos: axisPos(event) });
|
|
678
|
-
if (gesture.samples.length > 6) gesture.samples.shift();
|
|
679
|
-
|
|
680
|
-
// Write synchronously (no rAF hop): the panel stays glued to the pointer with
|
|
681
|
-
// no added latency. Multiple moves per frame just overwrite the inline
|
|
682
|
-
// `transform`; the browser still paints once, at the frame boundary.
|
|
683
|
-
applyDragVisual(along * closeSign.value);
|
|
825
|
+
return false;
|
|
684
826
|
};
|
|
685
827
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
start = samples[i];
|
|
693
|
-
if (last.t - samples[i].t >= 50) break;
|
|
694
|
-
}
|
|
695
|
-
const dt = last.t - start.t;
|
|
696
|
-
return dt > 0 ? ((last.pos - start.pos) / dt) * closeSign.value : 0;
|
|
828
|
+
const resetSwipeState = () => {
|
|
829
|
+
swipeCandidate = false;
|
|
830
|
+
swipeStartedInBody = false;
|
|
831
|
+
swipeClaimed = false;
|
|
832
|
+
swipeLockedToScroll = false;
|
|
833
|
+
isDragging.value = false;
|
|
697
834
|
};
|
|
698
835
|
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
(
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
836
|
+
const { lengthX, lengthY } = useSwipe(dialog, {
|
|
837
|
+
threshold: SWIPE_TRACK_PX,
|
|
838
|
+
// Non-passive so we can preventDefault once the sheet is following the
|
|
839
|
+
// finger — otherwise Chrome cancels the gesture for native pan.
|
|
840
|
+
passive: false,
|
|
841
|
+
onSwipeStart: (event) => {
|
|
842
|
+
resetSwipeState();
|
|
843
|
+
if (!props.draggable || !isVisible.value) return;
|
|
844
|
+
// The dialog element itself is the backdrop — click closes, not swipe.
|
|
845
|
+
if (event.target === dialog.value) return;
|
|
846
|
+
const target = event.target;
|
|
847
|
+
if (!(target instanceof Element)) return;
|
|
848
|
+
swipeCandidate = true;
|
|
849
|
+
swipeStartedInBody = !!target.closest('.bb-offcanvas__body');
|
|
850
|
+
swipeLockedToScroll = swipeStartedInBody && scrollLocksThisGesture(target);
|
|
851
|
+
},
|
|
852
|
+
onSwipe: (event) => {
|
|
853
|
+
if (!swipeCandidate || !props.draggable || swipeLockedToScroll) return;
|
|
854
|
+
const closeAmount = closeAmountFromSwipe();
|
|
855
|
+
|
|
856
|
+
if (!swipeClaimed) {
|
|
857
|
+
// Finger moving into content: this gesture stays a scroll even if they
|
|
858
|
+
// later reverse toward the dismiss edge.
|
|
859
|
+
if (swipeStartedInBody && closeAmount < -SWIPE_TRACK_PX) {
|
|
860
|
+
swipeLockedToScroll = true;
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
// Require the dismiss-axis itself to clear the track slop. A mostly
|
|
864
|
+
// horizontal flick on a short sheet must not latch the panel just
|
|
865
|
+
// because |dx| exceeded the track threshold.
|
|
866
|
+
if (closeAmount < SWIPE_TRACK_PX) return;
|
|
867
|
+
swipeClaimed = true;
|
|
868
|
+
isDragging.value = true;
|
|
869
|
+
beginDragVisual();
|
|
870
|
+
}
|
|
722
871
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
872
|
+
// Once the sheet is following the finger, this gesture stays a drag
|
|
873
|
+
// until lift — even after reversing to rest. `touch-action` is sampled
|
|
874
|
+
// at touchstart (`pan-y`), so dropping preventDefault here would let
|
|
875
|
+
// native scroll start mid-drag (sheet at 0, list jumping).
|
|
876
|
+
event.preventDefault();
|
|
877
|
+
applyDragVisual(closeAmount);
|
|
878
|
+
},
|
|
879
|
+
onSwipeEnd: () => {
|
|
880
|
+
if (!swipeClaimed) {
|
|
881
|
+
resetSwipeState();
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const closeAmount = closeAmountFromSwipe();
|
|
885
|
+
resetSwipeState();
|
|
886
|
+
// Fixed px threshold — same on a tall filters sheet and a short menu.
|
|
887
|
+
if (closeAmount >= SWIPE_THRESHOLD && !shouldPreventClose.value) {
|
|
888
|
+
void finishDismissFromDrag();
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
730
891
|
endDragVisual(true);
|
|
731
|
-
|
|
732
|
-
}
|
|
892
|
+
if (closeAmount >= SWIPE_THRESHOLD) triggerDenyCloseFeedback(300);
|
|
893
|
+
},
|
|
894
|
+
});
|
|
733
895
|
</script>
|
|
734
896
|
|
|
735
897
|
<style lang="postcss">
|
|
@@ -747,6 +909,18 @@ export type { Sizes };
|
|
|
747
909
|
type OffCanvasSize = NonNullable<Size<Sizes>['size']> | 'auto';
|
|
748
910
|
|
|
749
911
|
export type BbOffCanvasProps = {
|
|
912
|
+
/**
|
|
913
|
+
* One short line under the title, in the header — the shadcn
|
|
914
|
+
* `DialogDescription` shape: same block as the title, lower hierarchy,
|
|
915
|
+
* muted.
|
|
916
|
+
*
|
|
917
|
+
* It is also what `aria-describedby` points at. That wiring was dropped in
|
|
918
|
+
* v3 along with the old `description` prop, on the reasoning that pointing
|
|
919
|
+
* it at arbitrary body content would read whole forms aloud — true, and the
|
|
920
|
+
* reason it aims at THIS element only. Keep it to a sentence; anything
|
|
921
|
+
* longer belongs in the body.
|
|
922
|
+
*/
|
|
923
|
+
description?: string;
|
|
750
924
|
/**
|
|
751
925
|
* Disable EVERY user-initiated dismissal: backdrop click, Escape,
|
|
752
926
|
* drag-to-dismiss AND the header close (×) button (denied attempts play the
|
|
@@ -769,10 +943,10 @@ export type BbOffCanvasProps = {
|
|
|
769
943
|
*/
|
|
770
944
|
fullscreen?: boolean | 'mobile';
|
|
771
945
|
/**
|
|
772
|
-
* Enable drag-to-dismiss
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
* backdrop, or Escape only).
|
|
946
|
+
* Enable drag-to-dismiss via VueUse `useSwipe` (fixed ~50px threshold toward
|
|
947
|
+
* the exit edge, including from the scrollable body once it is at its scroll
|
|
948
|
+
* edge). A grab handle on the inner edge is the visual cue. When `false`, the
|
|
949
|
+
* panel is static (dismiss via the close button, backdrop, or Escape only).
|
|
776
950
|
* @defaultValue `false`
|
|
777
951
|
*/
|
|
778
952
|
draggable?: boolean;
|
|
@@ -849,18 +1023,32 @@ export type BbOffCanvasHeaderSlotProps = {
|
|
|
849
1023
|
};
|
|
850
1024
|
|
|
851
1025
|
/** Props exposed by the `title` slot. */
|
|
1026
|
+
/** Props exposed by the `description` slot. */
|
|
1027
|
+
export type BbOffCanvasDescriptionSlotProps = {
|
|
1028
|
+
/** The `description` prop value. */
|
|
1029
|
+
text: BbOffCanvasProps['description'];
|
|
1030
|
+
};
|
|
1031
|
+
|
|
852
1032
|
export type BbOffCanvasTitleSlotProps = {
|
|
853
1033
|
/** The `title` prop value. */
|
|
854
1034
|
text: BbOffCanvasProps['title'];
|
|
855
1035
|
};
|
|
856
1036
|
|
|
857
1037
|
export type BbOffCanvasSlots = {
|
|
1038
|
+
/**
|
|
1039
|
+
* Replaces the description text under the title. Renders the header even
|
|
1040
|
+
* when `title` and `description` are unset — a slotted description is a
|
|
1041
|
+
* description, the same rule `#title` follows.
|
|
1042
|
+
*/
|
|
1043
|
+
description?: (props: BbOffCanvasDescriptionSlotProps) => any;
|
|
858
1044
|
/**
|
|
859
1045
|
* Replaces the entire off-canvas header section (title + close button). Maintains accessibility wiring via the provided props.
|
|
860
1046
|
*/
|
|
861
1047
|
header?: (props: BbOffCanvasHeaderSlotProps) => any;
|
|
862
1048
|
/**
|
|
863
|
-
* Replaces the default title text inside the header. Remains linked to the
|
|
1049
|
+
* Replaces the default title text inside the header. Remains linked to the
|
|
1050
|
+
* panel through `aria-labelledby`. Providing this slot renders the header even
|
|
1051
|
+
* when `title` is unset — a slotted title is a title.
|
|
864
1052
|
*/
|
|
865
1053
|
title?: (props: BbOffCanvasTitleSlotProps) => any;
|
|
866
1054
|
/**
|
|
@@ -892,6 +1080,13 @@ export type BbOffCanvasSlots = {
|
|
|
892
1080
|
/* Duration for the stack shift + pad. Zeroed while a peer resizes so parents
|
|
893
1081
|
track the live size instead of lagging behind a 250ms animation. */
|
|
894
1082
|
--stack-transition-duration: var(--transition-duration);
|
|
1083
|
+
/* Axis split of --bb-panel-p: --px (inline) / --py
|
|
1084
|
+
(block), each branching off it. Set the pair only when the axes differ;
|
|
1085
|
+
setting --bb-panel-p alone keeps them equal. Local on this root class
|
|
1086
|
+
(not :root) so scoped --bb-panel-p overrides keep reaching the slots;
|
|
1087
|
+
for per-part asymmetry, scope --py to the part class. */
|
|
1088
|
+
--px: var(--bb-panel-p);
|
|
1089
|
+
--py: var(--bb-panel-p);
|
|
895
1090
|
--transition-timing-function: var(--bb-ease);
|
|
896
1091
|
background-color: var(--bb-panel);
|
|
897
1092
|
border: 1px solid var(--bb-border);
|
|
@@ -1067,6 +1262,11 @@ export type BbOffCanvasSlots = {
|
|
|
1067
1262
|
transform-origin: center bottom;
|
|
1068
1263
|
}
|
|
1069
1264
|
|
|
1265
|
+
&.bb-offcanvas--top .bb-offcanvas__body,
|
|
1266
|
+
&.bb-offcanvas--bottom .bb-offcanvas__body {
|
|
1267
|
+
touch-action: pan-y;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1070
1270
|
&.bb-offcanvas--fullscreen {
|
|
1071
1271
|
border-radius: 0;
|
|
1072
1272
|
height: 100%;
|
|
@@ -1127,7 +1327,20 @@ export type BbOffCanvasSlots = {
|
|
|
1127
1327
|
background-color var(--transition-duration) var(--bb-ease);
|
|
1128
1328
|
}
|
|
1129
1329
|
|
|
1130
|
-
/*
|
|
1330
|
+
/* Vaul overlay: fade the dim with the swipe-exit, not after it. `--open`
|
|
1331
|
+
stays on for the 500ms so `dialog.close()` does not tear the backdrop
|
|
1332
|
+
down mid-fade; this only overrides opacity. */
|
|
1333
|
+
&.bb-offcanvas--open.bb-offcanvas--swipe-exit::backdrop {
|
|
1334
|
+
opacity: 0;
|
|
1335
|
+
transition:
|
|
1336
|
+
opacity 500ms cubic-bezier(0.32, 0.72, 0, 1),
|
|
1337
|
+
background-color 500ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/* Grab handle: reliable drag surface on touch, centred on the inner edge.
|
|
1341
|
+
z-index above the body: a sheet search (flyout leftover `z-index: 1`, or
|
|
1342
|
+
`position: relative`) would otherwise paint over the 4px pill once the
|
|
1343
|
+
headerless body scrolls its padding-top away. */
|
|
1131
1344
|
.bb-offcanvas__handle {
|
|
1132
1345
|
background-color: var(--bb-border);
|
|
1133
1346
|
border-radius: 999px;
|
|
@@ -1135,7 +1348,7 @@ export type BbOffCanvasSlots = {
|
|
|
1135
1348
|
flex-shrink: 0;
|
|
1136
1349
|
position: absolute;
|
|
1137
1350
|
touch-action: none;
|
|
1138
|
-
z-index:
|
|
1351
|
+
z-index: 2;
|
|
1139
1352
|
}
|
|
1140
1353
|
|
|
1141
1354
|
&.bb-offcanvas--bottom .bb-offcanvas__handle,
|
|
@@ -1171,24 +1384,27 @@ export type BbOffCanvasSlots = {
|
|
|
1171
1384
|
`:first-child` because an absolutely-positioned drag handle can precede the
|
|
1172
1385
|
body in the DOM, which would defeat `:first-child`. */
|
|
1173
1386
|
&.bb-offcanvas--no-header .bb-offcanvas__body-content {
|
|
1174
|
-
padding-top: var(--
|
|
1387
|
+
padding-top: var(--py);
|
|
1175
1388
|
}
|
|
1176
1389
|
|
|
1177
1390
|
/* Make room for the handle so it does not overlap header content. */
|
|
1178
1391
|
&.bb-offcanvas--has-handle.bb-offcanvas--bottom .bb-offcanvas__header,
|
|
1179
1392
|
&.bb-offcanvas--has-handle.bb-offcanvas--bottom.bb-offcanvas--no-header
|
|
1180
1393
|
.bb-offcanvas__body-content {
|
|
1181
|
-
padding-top: calc(var(--
|
|
1394
|
+
padding-top: calc(var(--py) + 12px);
|
|
1182
1395
|
}
|
|
1183
1396
|
|
|
1184
1397
|
.bb-offcanvas__header {
|
|
1398
|
+
/* Shared by the close control and by the space the title reserves for it,
|
|
1399
|
+
* so the two can never drift apart. */
|
|
1400
|
+
--close-size: 28px;
|
|
1401
|
+
|
|
1185
1402
|
align-items: center;
|
|
1186
1403
|
column-gap: 12px;
|
|
1187
1404
|
display: flex;
|
|
1188
1405
|
flex-shrink: 0;
|
|
1189
1406
|
justify-content: space-between;
|
|
1190
|
-
padding: var(--
|
|
1191
|
-
var(--bb-panel-p);
|
|
1407
|
+
padding: var(--py) var(--px) calc(var(--py) / 2) var(--px);
|
|
1192
1408
|
position: relative;
|
|
1193
1409
|
/* Header is a dependable drag surface even when content scrolls. */
|
|
1194
1410
|
touch-action: none;
|
|
@@ -1197,38 +1413,102 @@ export type BbOffCanvasSlots = {
|
|
|
1197
1413
|
font-size: var(--dialog-title-fs, calc(var(--bb-fs) + 2px));
|
|
1198
1414
|
font-weight: 500;
|
|
1199
1415
|
line-height: var(--dialog-title-fs, calc(var(--bb-fs) + 2px));
|
|
1416
|
+
/* Long titles wrap rather than truncate — the title usually names the
|
|
1417
|
+
* record you are acting on, so hiding the end of it is worse than a
|
|
1418
|
+
* second line. `anywhere` because what overflows is typically one
|
|
1419
|
+
* unbroken id with nowhere to break. Matters more here than on the
|
|
1420
|
+
* dialog: an adaptive sheet is as narrow as the phone. */
|
|
1421
|
+
min-width: 0;
|
|
1422
|
+
overflow-wrap: anywhere;
|
|
1423
|
+
|
|
1424
|
+
/* The close control is out of flow (below), so `column-gap` has
|
|
1425
|
+
* nothing to push against and the title would run under it. Reserve
|
|
1426
|
+
* the space here instead — only when there is actually a control to
|
|
1427
|
+
* clear, so `hideClose` keeps the full width. */
|
|
1428
|
+
&:has(~ .bb-close-button) {
|
|
1429
|
+
padding-right: calc(var(--close-size) + 4px);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
/* Only when a description is present. A header without one keeps its
|
|
1433
|
+
original single-row, vertically-centred layout exactly — this must not
|
|
1434
|
+
move any dialog that does not opt in. */
|
|
1435
|
+
&:has(.bb-offcanvas__description) {
|
|
1436
|
+
align-items: flex-start;
|
|
1437
|
+
flex-direction: column;
|
|
1438
|
+
row-gap: 2px;
|
|
1200
1439
|
}
|
|
1201
1440
|
|
|
1441
|
+
.bb-offcanvas__description {
|
|
1442
|
+
color: var(--bb-text-muted);
|
|
1443
|
+
font-size: var(--bb-fs);
|
|
1444
|
+
font-weight: 400;
|
|
1445
|
+
line-height: 20px;
|
|
1446
|
+
margin: 0;
|
|
1447
|
+
/* Clears the absolutely-positioned close button the same way the title
|
|
1448
|
+
does. The × sits at the top-right, and a one-line title is short
|
|
1449
|
+
enough that the description's first line can still run under it. */
|
|
1450
|
+
&:has(~ .bb-close-button) {
|
|
1451
|
+
padding-right: calc(var(--close-size) + 4px);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/* Absolute on purpose: pinned to the panel corner, so a title that wraps
|
|
1456
|
+
* to two lines does not drag the × down with it, and the control never
|
|
1457
|
+
* adds its 28px to the header's height (the title's line box sets that).
|
|
1458
|
+
* The negative offsets align the ICON's edges with the padding box —
|
|
1459
|
+
* the button's own `--p` sits outside it. */
|
|
1202
1460
|
> .bb-close-button {
|
|
1203
|
-
--size:
|
|
1461
|
+
--size: var(--close-size);
|
|
1204
1462
|
--p: 7px;
|
|
1205
1463
|
position: absolute;
|
|
1206
|
-
right: calc(var(--
|
|
1207
|
-
top: calc(var(--
|
|
1464
|
+
right: calc(var(--px) - var(--p));
|
|
1465
|
+
top: calc(var(--py) - var(--p));
|
|
1208
1466
|
}
|
|
1209
1467
|
}
|
|
1210
1468
|
|
|
1211
1469
|
.bb-offcanvas__body {
|
|
1212
1470
|
flex: 1 1 auto;
|
|
1471
|
+
/* Keep descendant z-index (sticky/relative search) inside this scrollport
|
|
1472
|
+
so it cannot cover the absolutely-positioned grab handle. */
|
|
1473
|
+
isolation: isolate;
|
|
1474
|
+
/* Cap to the panel, not to the content — default `min-height: auto` lets a
|
|
1475
|
+
tall sheet (filter dropdown) grow the flex item past `max-height: 100%`
|
|
1476
|
+
and then nothing scrolls. */
|
|
1477
|
+
min-height: 0;
|
|
1213
1478
|
overflow: auto;
|
|
1479
|
+
overscroll-behavior: contain;
|
|
1214
1480
|
position: relative;
|
|
1215
1481
|
|
|
1216
|
-
&:first-child {
|
|
1217
|
-
.bb-offcanvas__body-content {
|
|
1218
|
-
padding-top: var(--bb-panel-p);
|
|
1219
|
-
|
|
1220
|
-
> .bb-close-button {
|
|
1221
|
-
--size: 28px;
|
|
1222
|
-
--p: 7px;
|
|
1223
|
-
position: absolute;
|
|
1224
|
-
right: calc(var(--bb-panel-p) - var(--p));
|
|
1225
|
-
top: calc(var(--bb-panel-p) - var(--p));
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
1482
|
.bb-offcanvas__body-content {
|
|
1231
|
-
padding: 0px var(--
|
|
1483
|
+
padding: 0px var(--px) var(--dialog-gap, var(--py));
|
|
1484
|
+
|
|
1485
|
+
/*
|
|
1486
|
+
* The headerless close control, pinned to the panel corner.
|
|
1487
|
+
*
|
|
1488
|
+
* NOT keyed on `.bb-offcanvas__body:first-child`, which is what this
|
|
1489
|
+
* rule used to sit under. A DRAGGABLE sheet renders
|
|
1490
|
+
* `.bb-offcanvas__handle` before the body, so `:first-child` never
|
|
1491
|
+
* matched there, the button fell back to `position: static`, and it
|
|
1492
|
+
* painted in normal flow AFTER the content — i.e. at the bottom of the
|
|
1493
|
+
* sheet. Every adaptive `BbSelectPopover` hit this: its sheet is
|
|
1494
|
+
* `draggable: true` and titleless, so it takes exactly this path.
|
|
1495
|
+
* Measured before the fix: `position: static`, 228px below the body top.
|
|
1496
|
+
*
|
|
1497
|
+
* The same `:first-child` fragility was already fixed a few rules up
|
|
1498
|
+
* for the no-header padding, with the reason spelled out
|
|
1499
|
+
* ("an absolutely-positioned drag handle can precede the body"); this
|
|
1500
|
+
* rule simply did not get the same treatment. No condition is needed
|
|
1501
|
+
* at all — the element only renders when there is no header
|
|
1502
|
+
* (`v-if="!renderHeader() && !hideClose"`), which is what the comment
|
|
1503
|
+
* beside it in the SFC already claimed this selector did.
|
|
1504
|
+
*/
|
|
1505
|
+
> .bb-close-button {
|
|
1506
|
+
--size: 28px;
|
|
1507
|
+
--p: 7px;
|
|
1508
|
+
position: absolute;
|
|
1509
|
+
right: calc(var(--px) - var(--p));
|
|
1510
|
+
top: calc(var(--py) - var(--p));
|
|
1511
|
+
}
|
|
1232
1512
|
}
|
|
1233
1513
|
|
|
1234
1514
|
/* Keep nested overlays out of flow while closed, mirroring dialog behavior. */
|
|
@@ -1240,7 +1520,7 @@ export type BbOffCanvasSlots = {
|
|
|
1240
1520
|
|
|
1241
1521
|
.bb-offcanvas__footer {
|
|
1242
1522
|
border-top: 1px solid var(--bb-border);
|
|
1243
|
-
padding: var(--dialog-gap, var(--
|
|
1523
|
+
padding: var(--dialog-gap, var(--py)) var(--px);
|
|
1244
1524
|
pointer-events: auto;
|
|
1245
1525
|
|
|
1246
1526
|
&:empty {
|