bitboss-ui 3.0.0-beta.2 → 3.0.0-beta.20
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 +791 -88
- 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 +110 -2
- package/dist/ai/components.json +882 -90
- 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 +2 -2
- 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 +4 -2
- package/dist/ai/guides/installation-and-plugin-setup.md +74 -9
- 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 +446 -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/layout-scaffold.md +5 -1
- package/dist/ai/recipes/inertia/ownership-atlas.md +41 -20
- package/dist/ai/recipes/inertia/records-workspace.md +26 -7
- package/dist/ai/recipes/nuxt/layout-scaffold.md +5 -1
- package/dist/ai/recipes/nuxt/records-workspace.md +26 -7
- package/dist/ai/recipes/vue/layout-scaffold.md +5 -1
- package/dist/ai/recipes/vue/records-workspace.md +26 -7
- 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 +4642 -1387
- 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 +8 -4
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +1319 -616
- 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 +247 -86
- package/dist/components/BbTable/utils.d.ts +87 -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/useToast.js +5 -5
- package/dist/deprecation/ai-deprecations.json.d.ts +99 -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 +3712 -1381
- package/dist/llms-medium.txt +113 -43
- 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 +6 -2
- 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 +146 -0
- package/scripts/lib/html-attributes.mjs +91 -0
- package/scripts/lib/validate-bb-markup.mjs +59 -4
- 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
package/dist/ai/BbTable.md
CHANGED
|
@@ -25,18 +25,20 @@ keyboard navigation, so your markup stays a column definition plus a few slots.
|
|
|
25
25
|
domain objects straight to `items` and set `item-value` for stable row
|
|
26
26
|
identity — never pre-map rows into `{ label, value }` shapes.
|
|
27
27
|
|
|
28
|
-
The table **reports** state (sort, selection, page, expansion,
|
|
29
|
-
through v-models; it never
|
|
28
|
+
The table **reports** state (sort, column order, selection, page, expansion,
|
|
29
|
+
highlight) through v-models; it never sorts, slices, or fetches on its own. You apply
|
|
30
30
|
that state to your data source (client-side or via `dependencies`-driven
|
|
31
31
|
refetch). This keeps it equally at home in a client-only screen and a
|
|
32
32
|
server-paginated one.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
**Every row in `items` mounts to the DOM unless you say `virtual`.** A few
|
|
35
|
+
hundred rows is fine for a client-only screen. Past roughly 1,000 rows,
|
|
36
|
+
either page it — feed `BbTable` the current slice as `items` and pass `page`
|
|
37
|
+
/ `per-page` / `total-items` (see § Pagination) so the server, not the DOM,
|
|
38
|
+
holds the rest — or, when the whole dataset legitimately lives in the
|
|
39
|
+
browser (local filtering, a spreadsheet-shaped tool), add `virtual` and let
|
|
40
|
+
the table window its rows (see § Virtual rows). The two compose: a large
|
|
41
|
+
page can be windowed.
|
|
40
42
|
|
|
41
43
|
### Columns: the full grammar
|
|
42
44
|
|
|
@@ -55,13 +57,15 @@ needs is declared here, not in markup:
|
|
|
55
57
|
`align` prop sets the default for the whole table (`left`).
|
|
56
58
|
- **`width`** freezes the column: numbers and bare numeric strings are px
|
|
57
59
|
(`130`, `'130'`); anything carrying a unit passes through (`'12rem'`,
|
|
58
|
-
`'20%'`).
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
`'20%'`). A declared width is the column's grid track and holds exactly;
|
|
61
|
+
unfrozen columns size to their content and share any leftover space equally
|
|
62
|
+
(the table is a CSS grid — the tracks are the width API, there is no
|
|
63
|
+
`table-layout`). When _every_ column declares a width, the surplus is shared
|
|
64
|
+
in proportion to the declared widths so the table still fills its container.
|
|
61
65
|
- **`skeleton`** picks the loading placeholder shape (see Loading below).
|
|
62
66
|
- **`sortable` / `sorted`** — see Sorting.
|
|
63
67
|
- **`thClass` / `tdClass` / `rowClass`** style the header cell, the data cells,
|
|
64
|
-
and the whole
|
|
68
|
+
and the whole row. `tdClass` and `rowClass` may be functions
|
|
65
69
|
`(content, key, item) => Classes` for state-dependent styling. Table-level
|
|
66
70
|
`thClass` / `tdClass` / `rowClass` / `headerRowClass` apply everywhere; a
|
|
67
71
|
row's classes accumulate the table `rowClass` plus every column's.
|
|
@@ -155,7 +159,7 @@ const invoices: Invoice[] = [
|
|
|
155
159
|
},
|
|
156
160
|
];
|
|
157
161
|
|
|
158
|
-
// `row-class` is applied to each
|
|
162
|
+
// `row-class` is applied to each row (`.bb-table-data__row`); use it to flag rows by state. Soft amber
|
|
159
163
|
// wash matches the design-language attention accent — public API, no internals.
|
|
160
164
|
const rowClass = (invoice: Invoice) =>
|
|
161
165
|
invoice.status === 'overdue'
|
|
@@ -207,7 +211,11 @@ Override any column's body with a `#<key>` slot and its header with a
|
|
|
207
211
|
characters turned into `_` (dashes are kept as-is), **lowercased**:
|
|
208
212
|
`address.city` → `#address_city`, `first_name` → `#first_name`, `issued-at` →
|
|
209
213
|
`#issued-at`, but also `issuedAt` → `#issuedat` — prefer snake_case keys so
|
|
210
|
-
slot names read naturally.
|
|
214
|
+
slot names read naturally. Building slot names in code (columns from config, a
|
|
215
|
+
dynamic `<template>` loop)? Import the normalizer itself — `import { slotKey }
|
|
216
|
+
from 'bitboss-ui'` — instead of restating the rule: a hand copy that differs by
|
|
217
|
+
one character (`\W+` collapses dashes; the real rule keeps them) makes the slot
|
|
218
|
+
silently never match. Keep the column in `columns`
|
|
211
219
|
even when you fully slot it: label, alignment and width still come from there.
|
|
212
220
|
|
|
213
221
|
Body cell scope: `content` (the formatted value), `item` (raw row), `value`
|
|
@@ -219,6 +227,22 @@ one-way state; the `toggle*` callbacks are the write path.
|
|
|
219
227
|
Header scope: `label`, `items` (the full list — handy for counts), `sortable`,
|
|
220
228
|
`sortOrder`, `toggleSort`, `classes`.
|
|
221
229
|
|
|
230
|
+
**Add beside a header instead of replacing it** with
|
|
231
|
+
`#header:<key>:prepend` / `#header:<key>:append` — rendered before / after the
|
|
232
|
+
column's existing header content (the label + sort control, or your own
|
|
233
|
+
`#header:<key>` replacement), so a resize grip or reorder handle no longer
|
|
234
|
+
costs you the built-in sort button. The table-wide pair `#header:prepend` /
|
|
235
|
+
`#header:append` applies one template to every data column's header (never the
|
|
236
|
+
structural select/actions cells); both forms receive the header scope above
|
|
237
|
+
plus `columnKey` (the raw column key) so a table-wide template can branch per
|
|
238
|
+
column. When both a per-column and the table-wide slot are provided for the
|
|
239
|
+
same side, the per-column one wins for that column — they never stack. Affixes
|
|
240
|
+
render even beside a full `#header:<key>` replacement (the same rule as
|
|
241
|
+
BbDropdown's `#<key>:prepend/append`). One caveat: a column key that
|
|
242
|
+
normalizes to `prepend` or `append` collides with the table-wide slot names;
|
|
243
|
+
the table-wide interpretation wins, per-column header slots are unavailable
|
|
244
|
+
for that column, and dev builds warn.
|
|
245
|
+
|
|
222
246
|
Below, the status badges carry brand glyphs shipped in the app's `iconDir`,
|
|
223
247
|
referenced as `local:*` names on the badge's `prepend:icon`. Per the icons
|
|
224
248
|
policy the dynamic status maps to **full-name literals** (a lookup object) —
|
|
@@ -365,7 +389,10 @@ Beyond per-column slots, the structural slots cover every other region:
|
|
|
365
389
|
(replace the per-row checkbox/radio; scope: `item`, `value`, `checked`,
|
|
366
390
|
`disabled`, `readonly`, `inputName`, `toggleSelected`), `#loading`, `#no-data`
|
|
367
391
|
(both below), and `#thead` / `#tbody` / `#tfoot` to replace an entire region
|
|
368
|
-
when you need full control (you then own its markup and semantics
|
|
392
|
+
when you need full control (you then own its markup and semantics: the table
|
|
393
|
+
is a CSS grid, so pass `<div role="row">` rows holding `<div role="cell">` /
|
|
394
|
+
`<div role="columnheader">` cells — never `<tr>`/`<td>`; a spanning cell uses
|
|
395
|
+
`grid-column: span N` with `#tfoot`'s `columnCount`, see **Styling hooks**).
|
|
369
396
|
|
|
370
397
|
### Loading and empty states
|
|
371
398
|
|
|
@@ -396,8 +423,9 @@ it renders only when there is nothing to keep. (Scope: the current, possibly
|
|
|
396
423
|
stale `items` — handy for matching the previous row count.) When `items` resolves
|
|
397
424
|
empty the table shows its no-data row — localized default text, overridable via
|
|
398
425
|
`no-data-text` or fully via the `#no-data` slot. That slot fills the table's own
|
|
399
|
-
full-width empty-state cell (already centred,
|
|
400
|
-
the content — a message, a "clear filters" button — not a
|
|
426
|
+
full-width empty-state cell (already centred, already spanning every column),
|
|
427
|
+
so pass the content — a message, a "clear filters" button — not a row of your
|
|
428
|
+
own.
|
|
401
429
|
|
|
402
430
|
`loading-text` is the loading row's `aria-live` announcement. Like
|
|
403
431
|
`no-data-text` it has a **localized default** (`common.loadingText` —
|
|
@@ -685,14 +713,21 @@ scriptable from anywhere via `useBbTableContext`
|
|
|
685
713
|
### Sorting
|
|
686
714
|
|
|
687
715
|
Mark columns `sortable` and bind `v-model:sort` — an ordered
|
|
688
|
-
`BbTableSortEntry[]` (`[columnKey, 'asc' | 'desc']`).
|
|
689
|
-
|
|
716
|
+
`BbTableSortEntry[]` (`[columnKey, 'asc' | 'desc']`). A click anywhere on a
|
|
717
|
+
sortable header (its whitespace included — AG Grid parity; the built-in
|
|
718
|
+
button is the keyboard/AT control) cycles the column unsorted → asc → desc →
|
|
719
|
+
removed; a direction flip updates the entry in
|
|
690
720
|
place, so the column keeps its priority in a multi-column sort and other
|
|
691
721
|
entries are never touched. The array order **is** the priority: first entry
|
|
692
722
|
sorts first. The table only reports the model; **apply it to your data**
|
|
693
723
|
(client-side, as below) or forward it to the server as `orderBy` and refetch
|
|
694
724
|
via `dependencies`.
|
|
695
725
|
|
|
726
|
+
The affordance is one arrow: nothing at rest, a faint arrow previewed while
|
|
727
|
+
the header is hovered or focused, solid once the column is sorted (up for
|
|
728
|
+
ascending, down for descending — the AG Grid convention), gone again on the
|
|
729
|
+
third click.
|
|
730
|
+
|
|
696
731
|
**Sortable headers driving a client-side sort**
|
|
697
732
|
|
|
698
733
|
```vue
|
|
@@ -773,11 +808,225 @@ const sortedInvoices = computed(() => {
|
|
|
773
808
|
|
|
774
809
|
A custom `#header:<key>` slot replaces the built-in sort button, but its scope
|
|
775
810
|
hands you `sortOrder` and `toggleSort` so your header can keep sorting alive
|
|
776
|
-
(render your own button and call `toggleSort`).
|
|
811
|
+
(render your own button and call `toggleSort`). To merely add something next
|
|
812
|
+
to the control — a resize grip, a filter icon — use
|
|
813
|
+
`#header:<key>:prepend/append` instead and keep the built-in button (see
|
|
814
|
+
"Custom cells and headers"). The column-level `sorted`
|
|
777
815
|
field only seeds the header's `aria-sort` when no live model entry exists — it
|
|
778
816
|
does not move the visual indicator; to start pre-sorted, seed `v-model:sort`
|
|
779
817
|
instead (as the example does).
|
|
780
818
|
|
|
819
|
+
### Column order
|
|
820
|
+
|
|
821
|
+
`columns` declares what a column **is**; `v-model:order` is the order
|
|
822
|
+
it **renders** in — an array of column keys, the same split as `sortable` +
|
|
823
|
+
`v-model:sort`. Two things write it:
|
|
824
|
+
|
|
825
|
+
- **Your own control** — a column panel, a settings drawer, a persisted
|
|
826
|
+
preference. Bind the model and write the array; the table follows. No flag
|
|
827
|
+
needed for this.
|
|
828
|
+
- **The user, on the table** — with `reorderable`, every data header
|
|
829
|
+
is a grab surface: drag it and the column moves live under the pointer (AG
|
|
830
|
+
Grid style, a chip with the label follows), release to commit. The same
|
|
831
|
+
header row is also one keyboard tab stop (the AG Grid grammar): ←/→ move
|
|
832
|
+
focus between headers, **Shift+←/→** move the column one slot
|
|
833
|
+
(Shift+Home/End to the ends), Enter sorts a sortable header.
|
|
834
|
+
Each committed move writes the model once and is announced.
|
|
835
|
+
|
|
836
|
+
The model's contract is what makes a column panel trivial:
|
|
837
|
+
|
|
838
|
+
- Keys listed first render first; declared columns the array omits follow, in
|
|
839
|
+
declaration order (a column added to `columns` later renders last).
|
|
840
|
+
- Keys that match no declared column render nothing but are **preserved in
|
|
841
|
+
place**. So hide a column by filtering it out of `columns` — never out of
|
|
842
|
+
the order — and it comes back exactly where it was.
|
|
843
|
+
- A user move relocates **exactly one key**; everything else, hidden keys
|
|
844
|
+
included, keeps its relative order. The first move on a partial (or empty)
|
|
845
|
+
model emits the completed array — persist that.
|
|
846
|
+
- A drop that changes nothing emits nothing; unbound, the table keeps the
|
|
847
|
+
order as per-mount state like `sort`.
|
|
848
|
+
|
|
849
|
+
**A column panel and the header drag sharing one order**
|
|
850
|
+
|
|
851
|
+
```vue
|
|
852
|
+
<template>
|
|
853
|
+
<div class="flex max-w-2xl flex-col gap-3">
|
|
854
|
+
<!-- ONE order, two writers: the panel's buttons and the table's own
|
|
855
|
+
header drag / keyboard handles both write `v-model:order`.
|
|
856
|
+
Hiding a column only filters `columns` — its key stays in the order,
|
|
857
|
+
so it comes back where it was. -->
|
|
858
|
+
<ul
|
|
859
|
+
aria-label="Columns"
|
|
860
|
+
class="flex flex-wrap gap-2 text-xs text-[color:var(--bb-text-muted)]"
|
|
861
|
+
>
|
|
862
|
+
<li
|
|
863
|
+
v-for="(column, index) in panelColumns"
|
|
864
|
+
:key="column.key"
|
|
865
|
+
class="flex items-center gap-1 rounded-[var(--bb-radius-sm)] border border-[color:var(--bb-border)] py-0.5 pl-2 pr-1"
|
|
866
|
+
>
|
|
867
|
+
<BbCheckbox
|
|
868
|
+
:label="column.label"
|
|
869
|
+
:model-value="!hidden.has(column.key)"
|
|
870
|
+
@update:model-value="toggleVisible(column.key)"
|
|
871
|
+
/>
|
|
872
|
+
<BbButton
|
|
873
|
+
:aria-label="`Move ${column.label} left`"
|
|
874
|
+
:disabled="index === 0"
|
|
875
|
+
icon="lucide:chevron-left"
|
|
876
|
+
size="xs"
|
|
877
|
+
variant="ghost"
|
|
878
|
+
@click="move(column.key, -1)"
|
|
879
|
+
/>
|
|
880
|
+
<BbButton
|
|
881
|
+
:aria-label="`Move ${column.label} right`"
|
|
882
|
+
:disabled="index === panelColumns.length - 1"
|
|
883
|
+
icon="lucide:chevron-right"
|
|
884
|
+
size="xs"
|
|
885
|
+
variant="ghost"
|
|
886
|
+
@click="move(column.key, 1)"
|
|
887
|
+
/>
|
|
888
|
+
</li>
|
|
889
|
+
</ul>
|
|
890
|
+
<div
|
|
891
|
+
class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
|
|
892
|
+
>
|
|
893
|
+
<BbTable
|
|
894
|
+
v-model:order="order"
|
|
895
|
+
caption="Invoices"
|
|
896
|
+
:columns="visibleColumns"
|
|
897
|
+
compact
|
|
898
|
+
item-value="id"
|
|
899
|
+
:items="invoices"
|
|
900
|
+
reorderable
|
|
901
|
+
/>
|
|
902
|
+
</div>
|
|
903
|
+
<p class="text-xs text-[color:var(--bb-text-muted)]">
|
|
904
|
+
Order model: <code>{{ JSON.stringify(order) }}</code>
|
|
905
|
+
</p>
|
|
906
|
+
</div>
|
|
907
|
+
</template>
|
|
908
|
+
<script setup lang="ts">
|
|
909
|
+
import { computed, ref } from 'vue';
|
|
910
|
+
import { BbButton, BbCheckbox, BbTable } from 'bitboss-ui';
|
|
911
|
+
import type { BbTableColumn } from 'bitboss-ui';
|
|
912
|
+
|
|
913
|
+
type Invoice = {
|
|
914
|
+
id: string;
|
|
915
|
+
number: string;
|
|
916
|
+
client: string;
|
|
917
|
+
issued: string;
|
|
918
|
+
amount: number;
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
const euro = new Intl.NumberFormat('en-IE', {
|
|
922
|
+
style: 'currency',
|
|
923
|
+
currency: 'EUR',
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// The declaration: what a column IS. Never reordered.
|
|
927
|
+
const columns: Array<BbTableColumn<Invoice>> = [
|
|
928
|
+
{ key: 'number', label: 'Invoice', width: 110 },
|
|
929
|
+
{ key: 'client', label: 'Client' },
|
|
930
|
+
{ key: 'issued', label: 'Issued', width: 110 },
|
|
931
|
+
{
|
|
932
|
+
key: 'amount',
|
|
933
|
+
label: 'Amount',
|
|
934
|
+
align: 'right',
|
|
935
|
+
width: 110,
|
|
936
|
+
formatter: euro.format,
|
|
937
|
+
},
|
|
938
|
+
];
|
|
939
|
+
|
|
940
|
+
const invoices: Invoice[] = [
|
|
941
|
+
{
|
|
942
|
+
id: '1',
|
|
943
|
+
number: 'INV-1041',
|
|
944
|
+
client: 'Northwind Traders',
|
|
945
|
+
issued: '2026-08-02',
|
|
946
|
+
amount: 4200,
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
id: '2',
|
|
950
|
+
number: 'INV-1042',
|
|
951
|
+
client: 'Globex',
|
|
952
|
+
issued: '2026-08-05',
|
|
953
|
+
amount: 980,
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
id: '3',
|
|
957
|
+
number: 'INV-1043',
|
|
958
|
+
client: 'Initech',
|
|
959
|
+
issued: '2026-08-11',
|
|
960
|
+
amount: 15600,
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
id: '4',
|
|
964
|
+
number: 'INV-1044',
|
|
965
|
+
client: 'Umbrella',
|
|
966
|
+
issued: '2026-08-19',
|
|
967
|
+
amount: 2750,
|
|
968
|
+
},
|
|
969
|
+
];
|
|
970
|
+
|
|
971
|
+
// The state: render order (keys). Persist THIS. Seed it with the full key
|
|
972
|
+
// list — it also completes itself on the first move.
|
|
973
|
+
const order = ref<string[]>(columns.map((column) => column.key));
|
|
974
|
+
const hidden = ref(new Set<string>());
|
|
975
|
+
|
|
976
|
+
// Visibility is just filtering the declaration; the hidden key keeps its
|
|
977
|
+
// slot in `order`.
|
|
978
|
+
const visibleColumns = computed(() =>
|
|
979
|
+
columns.filter((column) => !hidden.value.has(column.key))
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
// The panel lists every declared column in the order the model has them.
|
|
983
|
+
const panelColumns = computed(() =>
|
|
984
|
+
[...columns].sort(
|
|
985
|
+
(a, b) => order.value.indexOf(a.key) - order.value.indexOf(b.key)
|
|
986
|
+
)
|
|
987
|
+
);
|
|
988
|
+
|
|
989
|
+
const toggleVisible = (key: string) => {
|
|
990
|
+
const next = new Set(hidden.value);
|
|
991
|
+
if (next.has(key)) next.delete(key);
|
|
992
|
+
else next.add(key);
|
|
993
|
+
hidden.value = next;
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
const move = (key: string, delta: 1 | -1) => {
|
|
997
|
+
const next = [...order.value];
|
|
998
|
+
const from = next.indexOf(key);
|
|
999
|
+
const to = from + delta;
|
|
1000
|
+
if (from < 0 || to < 0 || to >= next.length) return;
|
|
1001
|
+
next.splice(from, 1);
|
|
1002
|
+
next.splice(to, 0, key);
|
|
1003
|
+
order.value = next;
|
|
1004
|
+
};
|
|
1005
|
+
</script>
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
**Pinned slots are positions, not columns.** `fixed-columns` indexes rendered
|
|
1009
|
+
slots like a spreadsheet's freeze panes (a selection column is slot 0). With
|
|
1010
|
+
column order the slot holds: whichever column lands in it is pinned, and
|
|
1011
|
+
dragging the identity column out of its pinned slot (slot 1 when
|
|
1012
|
+
`selectable`, slot 0 otherwise) unpins it (AG Grid pins columns; BbTable
|
|
1013
|
+
pins positions). Keep the reorder + pin combination for slots that
|
|
1014
|
+
never move — `select`, `actions`, or a first column you also lock by
|
|
1015
|
+
normalizing the model in your `@update:order` handler.
|
|
1016
|
+
|
|
1017
|
+
**Nested tables.** An inheriting child aligns to the parent's rails by
|
|
1018
|
+
**position**, so a reordered parent re-pairs the child's columns unless the
|
|
1019
|
+
child follows. Give both tables the same `v-model:order` when they
|
|
1020
|
+
share keys (and the child declares no `snap`) and they stay paired by key.
|
|
1021
|
+
`snap` points are positional in the parent's rendered order: reorder the
|
|
1022
|
+
parent and a snap-mapped child follows its slots, but never reorder (or share
|
|
1023
|
+
the order with) the snap-mapped child itself — dev builds warn.
|
|
1024
|
+
|
|
1025
|
+
Apply the model synchronously in your handler and persist afterwards: an
|
|
1026
|
+
`await` before the write moves the cell in a later flush, after the table
|
|
1027
|
+
has re-focused the handle, and keyboard focus is usually lost (whether it
|
|
1028
|
+
survives depends on which cell Vue's keyed patch detaches).
|
|
1029
|
+
|
|
781
1030
|
### Selection and bulk actions
|
|
782
1031
|
|
|
783
1032
|
Add `selectable` for multi-select (`multiple` is the default) bound with
|
|
@@ -1277,12 +1526,109 @@ fixed first track).
|
|
|
1277
1526
|
The actions-column toggle remains valid — prefer it when expansion reveals
|
|
1278
1527
|
_detail about the row_ rather than _children of it_.
|
|
1279
1528
|
|
|
1529
|
+
### Column resize
|
|
1530
|
+
|
|
1531
|
+
`resizable` puts a handle on the trailing edge of every data header:
|
|
1532
|
+
drag it and the column follows live, release to commit; double-click it to go
|
|
1533
|
+
back to the declared width. From the keyboard, **Alt+←/→** resizes the
|
|
1534
|
+
focused header in 10px steps (the header row is a tab stop whenever columns
|
|
1535
|
+
are resizable or reorderable).
|
|
1536
|
+
|
|
1537
|
+
There is no width model on purpose. `width` on the column definition stays
|
|
1538
|
+
the single declared truth, and a user resize is an **override** the table
|
|
1539
|
+
keeps until that column's `width` changes:
|
|
1540
|
+
|
|
1541
|
+
- Each commit fires `@resize:column="(key, width) => …"` **once** with the new width
|
|
1542
|
+
in px — or `null` after a double-click reset.
|
|
1543
|
+
- To persist, write the reported width back into your column definition (a
|
|
1544
|
+
`computed` over your persisted widths). The definition wins whenever it
|
|
1545
|
+
changes, so a programmatic `width` change always takes effect.
|
|
1546
|
+
- Unbound, the override lives for the mount, like an unbound `sort`.
|
|
1547
|
+
- A resized width is always px; `width` still accepts `'20%'` / `'12rem'`
|
|
1548
|
+
for what you declare yourself.
|
|
1549
|
+
|
|
1550
|
+
Headers never wrap. A column you have not sized is never narrower than its
|
|
1551
|
+
own heading — the label is the table's size hint, so an empty or slim column
|
|
1552
|
+
with a long heading still reads on a narrow screen (the table overflows
|
|
1553
|
+
instead). Once a track is a length (a declared `width`, a user resize) the
|
|
1554
|
+
label cannot widen it: squeezed, it ellipsizes and the sort arrow stays; at
|
|
1555
|
+
the narrowest only the arrow (and the handle) survive, and the column is
|
|
1556
|
+
still resizable. The data cells of a resized column clip with an ellipsis
|
|
1557
|
+
instead of spilling into the neighbour (a squeezed track cannot wrap a single
|
|
1558
|
+
word); every other cell keeps wrapping. While a resize runs the whole page
|
|
1559
|
+
shows the resize cursor.
|
|
1560
|
+
|
|
1561
|
+
A resized column becomes an **exact** track; the other columns keep doing
|
|
1562
|
+
what they did — unfrozen ones share the surplus, and a table whose columns
|
|
1563
|
+
are all declared with a `width` keeps sharing it among the un-resized ones —
|
|
1564
|
+
so a filling table stays filled and nothing collapses. **Shift+drag** moves
|
|
1565
|
+
the border instead: the right neighbour absorbs the delta, the total stays
|
|
1566
|
+
put, and both columns are reported. A table that inherits its widths from a
|
|
1567
|
+
parent (`inherit-column-widths`) renders no handles — its tracks are the
|
|
1568
|
+
parent's.
|
|
1569
|
+
|
|
1570
|
+
**Resizable columns reporting widths back into the definition**
|
|
1571
|
+
|
|
1572
|
+
```vue
|
|
1573
|
+
<template>
|
|
1574
|
+
<div class="flex max-w-2xl flex-col gap-1.5">
|
|
1575
|
+
<div
|
|
1576
|
+
class="overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
|
|
1577
|
+
>
|
|
1578
|
+
<BbTable
|
|
1579
|
+
caption="Invoices"
|
|
1580
|
+
:columns="columns"
|
|
1581
|
+
compact
|
|
1582
|
+
item-value="id"
|
|
1583
|
+
:items="invoices"
|
|
1584
|
+
resizable
|
|
1585
|
+
@resize:column="onResize"
|
|
1586
|
+
/>
|
|
1587
|
+
</div>
|
|
1588
|
+
<p class="text-xs text-[color:var(--bb-text-muted)]">
|
|
1589
|
+
Widths: <code>{{ JSON.stringify(widths) }}</code>
|
|
1590
|
+
</p>
|
|
1591
|
+
</div>
|
|
1592
|
+
</template>
|
|
1593
|
+
<script setup lang="ts">
|
|
1594
|
+
import { computed, ref } from 'vue';
|
|
1595
|
+
import { BbTable } from 'bitboss-ui';
|
|
1596
|
+
import type { BbTableColumn } from 'bitboss-ui';
|
|
1597
|
+
|
|
1598
|
+
type Invoice = { id: string; number: string; client: string; amount: number };
|
|
1599
|
+
|
|
1600
|
+
const base: Array<BbTableColumn<Invoice>> = [
|
|
1601
|
+
{ key: 'number', label: 'Invoice' },
|
|
1602
|
+
{ key: 'client', label: 'Client' },
|
|
1603
|
+
{ key: 'amount', label: 'Amount', align: 'right' },
|
|
1604
|
+
];
|
|
1605
|
+
// Persist THIS (localStorage, a user preference…): key → px.
|
|
1606
|
+
const widths = ref<Record<string, number>>({});
|
|
1607
|
+
// The definition carries the persisted widths; the table treats them as the
|
|
1608
|
+
// declared truth, so nothing jumps after a reload.
|
|
1609
|
+
const columns = computed(() =>
|
|
1610
|
+
base.map((column) => ({ ...column, width: widths.value[column.key] }))
|
|
1611
|
+
);
|
|
1612
|
+
const onResize = (key: string, width: number | null) => {
|
|
1613
|
+
const next = { ...widths.value };
|
|
1614
|
+
if (width === null) delete next[key];
|
|
1615
|
+
else next[key] = width;
|
|
1616
|
+
widths.value = next;
|
|
1617
|
+
};
|
|
1618
|
+
const invoices: Invoice[] = [
|
|
1619
|
+
{ id: '1', number: 'INV-1041', client: 'Northwind Traders', amount: 4200 },
|
|
1620
|
+
{ id: '2', number: 'INV-1042', client: 'Globex', amount: 980 },
|
|
1621
|
+
{ id: '3', number: 'INV-1043', client: 'Initech', amount: 15600 },
|
|
1622
|
+
];
|
|
1623
|
+
</script>
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1280
1626
|
### Nested tables that align with the parent
|
|
1281
1627
|
|
|
1282
1628
|
A `BbTable` rendered inside another's `#expand` slot is independent by
|
|
1283
1629
|
default. Add `inherit-column-widths` and it snaps its columns to the parent's
|
|
1284
1630
|
tracks, so a line-items breakdown reads as a continuation of the invoice row
|
|
1285
|
-
above it
|
|
1631
|
+
above it. The mechanics:
|
|
1286
1632
|
|
|
1287
1633
|
- `inherit-column-widths` — `true` inherits from the nearest ancestor table;
|
|
1288
1634
|
a string targets a specific ancestor's `id` (useful when an intermediate
|
|
@@ -1293,23 +1639,39 @@ above it (this also forces fixed layout). The mechanics:
|
|
|
1293
1639
|
allowed; `end: -1` runs to the end of the data region). Structural columns
|
|
1294
1640
|
(`select`, actions) are accounted for automatically.
|
|
1295
1641
|
- A child with **more** columns than the parent content-sizes the extras and
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
the
|
|
1305
|
-
with
|
|
1306
|
-
|
|
1307
|
-
|
|
1642
|
+
overflows into the parent's scroll (see below); with **fewer**, the leading
|
|
1643
|
+
columns stay 1:1, the actions column inherits the parent's actions width,
|
|
1644
|
+
and the last content column runs across every remaining parent track so
|
|
1645
|
+
both tables end flush (a right-aligned total lands under the parent's last
|
|
1646
|
+
column). Give that last column a `width` or an explicit `[start, end]`
|
|
1647
|
+
snap only if you want it to stop short — the leftover then joins the
|
|
1648
|
+
actions region instead.
|
|
1649
|
+
- **An inheriting child does not scroll on its own — the parent's scrollport
|
|
1650
|
+
is the only one.** That is what keeps the two grids locked together: a child
|
|
1651
|
+
with its own scroll position would fall out of alignment the moment either
|
|
1652
|
+
one moved, which is the whole thing `inherit-column-widths` exists to
|
|
1653
|
+
prevent. One scrollbar moves both.
|
|
1654
|
+
- **The parent's `selectable`/`actions` columns are handled for you** — the
|
|
1655
|
+
child's content columns map to the parent's content columns 1:1 (no `snap`
|
|
1656
|
+
needed for that), and the parent's select/actions _regions_ are reserved on
|
|
1657
|
+
each side: a child with its own select or actions column widens that cell
|
|
1658
|
+
to the parent's (two equal cells, controls at the row's end, so the ⌃ sits
|
|
1659
|
+
under the parent's); a child without one folds the region into its first/
|
|
1660
|
+
last cell as _padding_, so its content stays flush with the parent's data
|
|
1661
|
+
grid on both edges (the inset reads as hierarchy). Only a child cell
|
|
1662
|
+
_wider_ than the parent's — a select the parent lacks, more buttons — moves
|
|
1663
|
+
anything, and only the adjacent content column: it shrinks by the surplus
|
|
1664
|
+
so every other rail holds. What a child publishes to its own children is
|
|
1665
|
+
the same contract (content rails + regions), so a three-level nest aligns
|
|
1666
|
+
at every depth.
|
|
1308
1667
|
- **Scope any page-level cell-padding overrides to the parent's own cells.** A
|
|
1309
|
-
bare `:deep(
|
|
1310
|
-
clobbers the offset padding it computes to stay aligned — use
|
|
1311
|
-
|
|
1312
|
-
|
|
1668
|
+
bare `:deep([role='cell']:first-child)` also matches the nested table's
|
|
1669
|
+
cells and clobbers the offset padding it computes to stay aligned — use the
|
|
1670
|
+
direct-child chain through the grid's containers
|
|
1671
|
+
(`:deep(> .bb-table__table > .bb-table__body > [role='row'] > :first-child)`)
|
|
1672
|
+
so the child, which lives inside `.bb-table-expand__cell`, keeps its
|
|
1673
|
+
structural padding. The canonical `.table-flush` idiom is under **Styling
|
|
1674
|
+
hooks**.
|
|
1313
1675
|
|
|
1314
1676
|
**Invoice lines aligned under the invoice columns**
|
|
1315
1677
|
|
|
@@ -1419,6 +1781,47 @@ const expanded = ref<string[]>([]);
|
|
|
1419
1781
|
</script>
|
|
1420
1782
|
```
|
|
1421
1783
|
|
|
1784
|
+
#### Affixed actions on both tables
|
|
1785
|
+
|
|
1786
|
+
A nested table can carry its own `fixed-columns` — child rows usually need
|
|
1787
|
+
their own row actions — and both action columns pin to the same visible edge:
|
|
1788
|
+
|
|
1789
|
+
```vue
|
|
1790
|
+
<BbTable
|
|
1791
|
+
id="programmes"
|
|
1792
|
+
actions
|
|
1793
|
+
:columns="columns"
|
|
1794
|
+
:fixed-columns="[{ index: columns.length, position: 'right' }]"
|
|
1795
|
+
:items="rows"
|
|
1796
|
+
item-value="id"
|
|
1797
|
+
>
|
|
1798
|
+
<template #expand="{ item }">
|
|
1799
|
+
<BbTable
|
|
1800
|
+
actions
|
|
1801
|
+
:columns="childColumns"
|
|
1802
|
+
:fixed-columns="[{ index: childColumns.length, position: 'right' }]"
|
|
1803
|
+
inherit-column-widths="programmes"
|
|
1804
|
+
:items="item.children"
|
|
1805
|
+
item-value="id"
|
|
1806
|
+
/>
|
|
1807
|
+
</template>
|
|
1808
|
+
</BbTable>
|
|
1809
|
+
```
|
|
1810
|
+
|
|
1811
|
+
This works because the child shares the parent's scrollport. `position: sticky`
|
|
1812
|
+
resolves against the nearest scrolling ancestor, and the child's own box is as
|
|
1813
|
+
wide as the parent's **content** — so if it opened a scrollport of its own, the
|
|
1814
|
+
child's actions would affix to an edge sitting out in the overflow, hundreds of
|
|
1815
|
+
pixels past what anyone can see, while the parent's pinned correctly. Snap the
|
|
1816
|
+
child's actions column onto the parent's actions track (see the snapping rule
|
|
1817
|
+
above) so the two columns line up rather than merely both being pinned.
|
|
1818
|
+
|
|
1819
|
+
A pinned cell is opaque — it paints the table's `--bg` surface because it
|
|
1820
|
+
slides over its siblings — so a nested table sitting on a tinted expand row
|
|
1821
|
+
(`--bb-muted` is the usual choice) shows white pinned actions unless you tell
|
|
1822
|
+
it which surface it is on: `.nest .bb-table { --bg: var(--bb-muted); }`. Same
|
|
1823
|
+
rule as any table on a non-panel surface (see **Styling hooks**).
|
|
1824
|
+
|
|
1422
1825
|
#### Flush tables + inherited widths: mind the edge padding
|
|
1423
1826
|
|
|
1424
1827
|
`table-flush` removes the outer cell padding so the table reads edge-to-edge in
|
|
@@ -1432,9 +1835,10 @@ Two rules keep them honest:
|
|
|
1432
1835
|
1. **Snap against the same base you paint.** Measure the parent's columns with
|
|
1433
1836
|
the same flush/padded state the child renders in — never measure padded and
|
|
1434
1837
|
render flush.
|
|
1435
|
-
2. **Never add nest padding on the body only.** Padding the child's
|
|
1436
|
-
not its header (or vice versa) reintroduces exactly
|
|
1437
|
-
removed. Put the inset on the nest container, where
|
|
1838
|
+
2. **Never add nest padding on the body only.** Padding the child's body
|
|
1839
|
+
(`.bb-table__body`) but not its header (or vice versa) reintroduces exactly
|
|
1840
|
+
the offset the snap just removed. Put the inset on the nest container, where
|
|
1841
|
+
both row groups inherit it.
|
|
1438
1842
|
|
|
1439
1843
|
### Row highlight (master–detail)
|
|
1440
1844
|
|
|
@@ -1447,11 +1851,42 @@ clicks are ignored on purpose: those inside interactive elements (links,
|
|
|
1447
1851
|
buttons, inputs, labels keep their own semantics) and those ending a text
|
|
1448
1852
|
selection (dragging to copy a cell must not mutate state).
|
|
1449
1853
|
|
|
1854
|
+
**Highlight is opt-in, and the model's value is the switch.** Bind nothing and
|
|
1855
|
+
the mechanic is entirely off — a row click emits `click:row` and does nothing
|
|
1856
|
+
else: no class, no `aria-current`, no state. That matters because the table has
|
|
1857
|
+
nowhere to put a highlight it invents; a table that highlighted itself would
|
|
1858
|
+
drive a cursor the app can neither read nor clear.
|
|
1859
|
+
|
|
1860
|
+
The switch reads the value, not the binding: **`undefined` means "never
|
|
1861
|
+
initialised" and keeps highlight off; `null` means "initialised and currently
|
|
1862
|
+
empty" and turns it on.** So initialise the model:
|
|
1863
|
+
|
|
1864
|
+
```ts
|
|
1865
|
+
const highlighted = ref(null); // ✅ connected
|
|
1866
|
+
const highlighted = ref(); // ❌ undefined — highlight stays off (warns in dev)
|
|
1867
|
+
```
|
|
1868
|
+
|
|
1869
|
+
A value seeded through the shared context counts exactly the same, and binds
|
|
1870
|
+
two ways just like the prop:
|
|
1871
|
+
|
|
1872
|
+
```ts
|
|
1873
|
+
const highlighted = ref(null);
|
|
1874
|
+
useBbTableContext('invoices-table', { highlighted });
|
|
1875
|
+
```
|
|
1876
|
+
|
|
1877
|
+
`click:row` is never gated — it fires on every row click whether or not
|
|
1878
|
+
highlight is connected, so listening for clicks costs you nothing. Calling
|
|
1879
|
+
`toggleHighlighted` from a cell slot on an unconnected table does nothing and
|
|
1880
|
+
warns once in dev.
|
|
1881
|
+
|
|
1450
1882
|
Highlight ships with a **default cursor**: the row gets `aria-current="true"`
|
|
1451
1883
|
and the `bb-table-data__row--highlighted` class, and the shipped stylesheet
|
|
1452
|
-
paints that class with a
|
|
1453
|
-
distinct from hover and from a plain focus ring, and readable
|
|
1454
|
-
and dark.
|
|
1884
|
+
paints that class with a quiet neutral fill plus a left accent bar in your
|
|
1885
|
+
brand primary — distinct from hover and from a plain focus ring, and readable
|
|
1886
|
+
in both light and dark. The fill is `--bb-muted` (the shared "selected/active"
|
|
1887
|
+
stop on the neutral surface ladder, one step above the `--bb-surface-hover`
|
|
1888
|
+
used for row hover) and steps to `--bb-pressed` while hovered, so it never
|
|
1889
|
+
depends on what your `--bb-primary` happens to be — only the accent bar does. Screen-reader users get the `aria-current` announcement either way;
|
|
1455
1890
|
sighted users now get a visible cursor out of the box too, including once
|
|
1456
1891
|
`keyboard-navigation` is on and the roving tabindex needs to be seen. Override
|
|
1457
1892
|
it with `row-class` (or equivalent visible styling) whenever the default
|
|
@@ -1636,9 +2071,11 @@ const rowClass = (event: AuditEvent) =>
|
|
|
1636
2071
|
```
|
|
1637
2072
|
|
|
1638
2073
|
Row events: `click:row`, `dblclick:row` and `contextmenu:row` fire with
|
|
1639
|
-
`(event,
|
|
1640
|
-
|
|
1641
|
-
|
|
2074
|
+
`(event, item)` — the native event and **your record exactly as you passed it
|
|
2075
|
+
in `items`**, nothing else. No wrapper to unpack, no third argument; selection
|
|
2076
|
+
state is already yours through the `v-model`, and the row's value resolves
|
|
2077
|
+
from the record through `item-value` exactly as the models do. Reserve these
|
|
2078
|
+
events for side effects that are not navigation —
|
|
1642
2079
|
logging, opening a drawer (though `v-model:highlighted` usually covers that
|
|
1643
2080
|
without any handler).
|
|
1644
2081
|
|
|
@@ -2005,8 +2442,14 @@ layout. `fixed-columns` pins columns while the rest scroll horizontally: an
|
|
|
2005
2442
|
array of column indices (left-pinned) or `{ index, position: 'left'|'right' }`
|
|
2006
2443
|
objects; indices count rendered columns (a selection column is index 0), and
|
|
2007
2444
|
an out-of-range index logs a console error and is skipped. When content can't
|
|
2008
|
-
fit, the table keeps every column at its measured minimum
|
|
2009
|
-
container instead of clipping
|
|
2445
|
+
fit, the table keeps every column at its measured minimum — its header label
|
|
2446
|
+
included — and overflows its container instead of clipping; this is by
|
|
2447
|
+
design, not a bug to work around. Note that truncating cells yourself
|
|
2448
|
+
(`overflow: hidden` + `text-overflow: ellipsis` via `tdClass`) removes that
|
|
2449
|
+
column's content floor — only the label floors it then, and the grid pads
|
|
2450
|
+
every column equally with whatever is left — so declare a `width` on the
|
|
2451
|
+
columns you truncate, or truncate with `white-space: nowrap` alone and let
|
|
2452
|
+
the table scroll.
|
|
2010
2453
|
|
|
2011
2454
|
**The consumer overflow contract has two parts.** Wrap the table in an
|
|
2012
2455
|
`overflow-x: auto` element (below), **and** make sure that wrapper is actually
|
|
@@ -2049,6 +2492,107 @@ your wrapper actually sits on before sprinkling `min-width: 0` everywhere.)
|
|
|
2049
2492
|
</div>
|
|
2050
2493
|
```
|
|
2051
2494
|
|
|
2495
|
+
### Virtual rows
|
|
2496
|
+
|
|
2497
|
+
`virtual` windows the body: only the rows in the vertical scrollport (plus a
|
|
2498
|
+
few on each side) are mounted, and two spacer rows hold the scrollport at the
|
|
2499
|
+
full list height, so 5,000 or 50,000 rows scroll at the cost of a few dozen.
|
|
2500
|
+
Everything else is the same table — the rows stay in flow in the grid, so the
|
|
2501
|
+
stuck header, `fixed-columns`, nested tables, selection, highlight and the
|
|
2502
|
+
keyboard grammar work untouched. One prop, no knobs.
|
|
2503
|
+
|
|
2504
|
+
- **Give it a scroller.** Bound the table's height (`max-height` / `height`
|
|
2505
|
+
on the table, or a flex item with `min-height: 0`) and the table itself
|
|
2506
|
+
scrolls its rows; left unbounded it grows with the page and the page (or
|
|
2507
|
+
the nearest scrolling ancestor) is the scroller. `virtual` implies a stuck
|
|
2508
|
+
header — a windowed body without column names is a scroller, not a table.
|
|
2509
|
+
- **Columns are sized once.** A windowed body only ever holds a few rows, so
|
|
2510
|
+
`auto` tracks would move as rows come and go. The first rendered batch is
|
|
2511
|
+
measured and its widths become floors (`minmax(px, 1fr)`): they still share
|
|
2512
|
+
surplus, later rows never widen them — and the cells of those columns are
|
|
2513
|
+
single-line and **clip with an ellipsis** (like a resized column) so a
|
|
2514
|
+
longer value that shows up later never spills into its neighbour. Want a
|
|
2515
|
+
column to wrap (prose, notes)? Declare a `width` on it: a declared width
|
|
2516
|
+
wins as always and keeps wrapping. A resize overrides as always, and an
|
|
2517
|
+
empty→non-empty transition (a refetch) re-measures from the new first
|
|
2518
|
+
batch.
|
|
2519
|
+
- **A fast wheel never shows blank space.** A band of skeleton rows is kept
|
|
2520
|
+
rendered beyond the window on both sides (cheap: empty cells at the
|
|
2521
|
+
measured row height), so a wheel that the compositor scrolls ahead of the
|
|
2522
|
+
main thread reveals skeletons, not blank spacer, and they turn into real
|
|
2523
|
+
rows as the window reaches them. While the scroll is fast, rows never
|
|
2524
|
+
mounted before stay skeletons and are swapped for real rows as soon as
|
|
2525
|
+
it slows; rows you have already seen stay real. A moderate scroll never
|
|
2526
|
+
sees a skeleton.
|
|
2527
|
+
- **Dynamic heights are fine.** Every mounted row is measured (wrapping
|
|
2528
|
+
cells, a "show more", an expand row that opens a nested table three levels
|
|
2529
|
+
deep), measurements are cached by row so a row seen once stays right, and a
|
|
2530
|
+
height change _above_ the viewport moves the scroll offset with it so what
|
|
2531
|
+
you are reading does not jump.
|
|
2532
|
+
- **Focus is never lost to an unmount.** The row (or expand row) that owns
|
|
2533
|
+
focus stays mounted however far it scrolls — a control inside a nested
|
|
2534
|
+
table included — and is released on blur. Arrow keys, Home/End and
|
|
2535
|
+
PageUp/PageDown work across the whole list (the target row is scrolled in,
|
|
2536
|
+
mounted, then focused).
|
|
2537
|
+
- **Nested tables are not windowed.** A nested table inside an expand row is
|
|
2538
|
+
one tall row to the virtualizer; give a huge nested dataset its own page.
|
|
2539
|
+
`virtual` is ignored on a table that inherits its widths, and under a
|
|
2540
|
+
`#tbody` slot.
|
|
2541
|
+
- **Rows keep their absolute position for AT** (`aria-rowcount` /
|
|
2542
|
+
`aria-rowindex`), page or no page — a virtual table is one page unless you
|
|
2543
|
+
say otherwise.
|
|
2544
|
+
|
|
2545
|
+
**5,000 rows in a 320px scroller**
|
|
2546
|
+
|
|
2547
|
+
```vue
|
|
2548
|
+
<template>
|
|
2549
|
+
<!-- Bounding the table's height is what makes it the scroller of its own
|
|
2550
|
+
rows; `virtual` windows them and implies the stuck header. -->
|
|
2551
|
+
<div
|
|
2552
|
+
class="max-w-2xl overflow-hidden rounded-[var(--bb-radius)] border border-[color:var(--bb-border)]"
|
|
2553
|
+
>
|
|
2554
|
+
<BbTable
|
|
2555
|
+
caption="Ledger"
|
|
2556
|
+
class="max-h-80"
|
|
2557
|
+
:columns="columns"
|
|
2558
|
+
compact
|
|
2559
|
+
:fixed-columns="[0]"
|
|
2560
|
+
item-value="id"
|
|
2561
|
+
:items="rows"
|
|
2562
|
+
virtual
|
|
2563
|
+
/>
|
|
2564
|
+
</div>
|
|
2565
|
+
</template>
|
|
2566
|
+
<script setup lang="ts">
|
|
2567
|
+
import { BbTable } from 'bitboss-ui';
|
|
2568
|
+
import type { BbTableColumn } from 'bitboss-ui';
|
|
2569
|
+
|
|
2570
|
+
type Entry = {
|
|
2571
|
+
id: number;
|
|
2572
|
+
number: string;
|
|
2573
|
+
client: string;
|
|
2574
|
+
issued: string;
|
|
2575
|
+
amount: string;
|
|
2576
|
+
};
|
|
2577
|
+
|
|
2578
|
+
const columns: Array<BbTableColumn<Entry>> = [
|
|
2579
|
+
{ key: 'number', label: 'Invoice' },
|
|
2580
|
+
{ key: 'client', label: 'Client' },
|
|
2581
|
+
{ key: 'issued', label: 'Issued' },
|
|
2582
|
+
{ key: 'amount', label: 'Amount', align: 'right' },
|
|
2583
|
+
];
|
|
2584
|
+
|
|
2585
|
+
const clients = ['Northwind Traders', 'Globex', 'Initech', 'Umbrella', 'Hooli'];
|
|
2586
|
+
const rows: Entry[] = Array.from({ length: 5000 }, (_, i) => ({
|
|
2587
|
+
id: i,
|
|
2588
|
+
number: `INV-${10000 + i}`,
|
|
2589
|
+
client: clients[i % clients.length],
|
|
2590
|
+
issued: `2026-${String(1 + (i % 12)).padStart(2, '0')}-${String(1 + (i % 28)).padStart(2, '0')}`,
|
|
2591
|
+
amount: `€${((i * 37) % 9000) + 100}`,
|
|
2592
|
+
}));
|
|
2593
|
+
</script>
|
|
2594
|
+
```
|
|
2595
|
+
|
|
2052
2596
|
### Narrow screens: hide columns, don't scroll everything
|
|
2053
2597
|
|
|
2054
2598
|
An ops table with ten columns is unusable on a phone as a horizontal scroller —
|
|
@@ -2057,10 +2601,14 @@ leave the screen. Collapse instead of scroll:
|
|
|
2057
2601
|
|
|
2058
2602
|
- **Rank the columns.** Identity + status + the one number that matters stay;
|
|
2059
2603
|
everything else is secondary.
|
|
2060
|
-
- **
|
|
2061
|
-
`
|
|
2062
|
-
|
|
2063
|
-
|
|
2604
|
+
- **Drop the secondary ones from `columns` at the breakpoint** — a `computed`
|
|
2605
|
+
over `useMobile()` (or your own `matchMedia`) that filters the secondary keys
|
|
2606
|
+
out. Removing the column removes its grid track. Do **not** hide cells with
|
|
2607
|
+
`thClass`/`tdClass` + `display: none`: the table is a grid, a hidden cell
|
|
2608
|
+
leaves its track in place and every following cell shifts one track over.
|
|
2609
|
+
A dropped key keeps its slot in `order` (the write-back never drops
|
|
2610
|
+
unknown keys), so the column returns where it was — never filter the
|
|
2611
|
+
order array.
|
|
2064
2612
|
- **Stack what survives into the primary cell.** The identity slot renders the
|
|
2065
2613
|
name plus the hidden facts under it, at `--bb-text-muted`, so nothing is
|
|
2066
2614
|
actually lost.
|
|
@@ -2069,6 +2617,132 @@ Keep the sticky/pinned identity column for the case where the full grid really
|
|
|
2069
2617
|
must stay reachable (a spreadsheet-shaped tool), not as the default for a
|
|
2070
2618
|
records list.
|
|
2071
2619
|
|
|
2620
|
+
### Styling hooks
|
|
2621
|
+
|
|
2622
|
+
**The table's surface is `--bg`** (default `var(--bb-panel)`), a local on
|
|
2623
|
+
`.bb-table`. Cells are transparent at rest so the row can tint them, but
|
|
2624
|
+
anything that slides over scrolled content — the stuck header band with
|
|
2625
|
+
`fixed-headers`, and `fixed-columns` cells — is opaque and paints `--bg` (a
|
|
2626
|
+
pinned cell also picks up its row's hover/highlight tint, so it never reads
|
|
2627
|
+
as a hole). When a table sits on a surface that is not the panel colour, set
|
|
2628
|
+
the one knob and everything pinned follows:
|
|
2629
|
+
|
|
2630
|
+
```css
|
|
2631
|
+
.my-card .bb-table {
|
|
2632
|
+
--bg: var(--card-bg);
|
|
2633
|
+
}
|
|
2634
|
+
```
|
|
2635
|
+
|
|
2636
|
+
The table is a **CSS grid of `<div>`s with ARIA roles, not a `<table>`**: the
|
|
2637
|
+
root `.bb-table__table[role=table]` owns the column tracks and every row group
|
|
2638
|
+
and row is a subgrid of it. Style it through the classes and the roles — never
|
|
2639
|
+
through element names, which match nothing:
|
|
2640
|
+
|
|
2641
|
+
| Region | Class | Role |
|
|
2642
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
|
2643
|
+
| grid root | `.bb-table__table` | `table` |
|
|
2644
|
+
| header group | `.bb-table__head` | `rowgroup` |
|
|
2645
|
+
| header row | `.bb-table-header-row` | `row` |
|
|
2646
|
+
| header cell | `.bb-table-header` (+ `--select`, `--actions`) | `columnheader` |
|
|
2647
|
+
| body group | `.bb-table__body` | `rowgroup` |
|
|
2648
|
+
| data row | `.bb-table-data__row` (+ `--highlighted`) | `row` |
|
|
2649
|
+
| data cell | `.bb-table-data__cell` (+ `--select`) | `cell` |
|
|
2650
|
+
| actions cell | `.bb-table__cell--actions` | `cell` |
|
|
2651
|
+
| expand row | `.bb-table-expand__row` > `.bb-table-expand__cell` | `row` > `cell` |
|
|
2652
|
+
| empty state | `.bb-table-no-data__row` > `.bb-table-no-data__cell` | `row` > `cell` |
|
|
2653
|
+
| footer group | `.bb-table__foot` (`#tfoot` content) | `rowgroup` only when `#tfoot` is provided |
|
|
2654
|
+
| focused header | `.bb-table-header[data-column-key]:focus-visible` (`reorderable` only; inset ring, the header row is one roving tab stop) | `columnheader` |
|
|
2655
|
+
| dragged column | `.bb-table-header--dragging` on the header; `.bb-table__head--dragging` on the head group during a drag | — |
|
|
2656
|
+
| clipped header | `.bb-table-header--clip` (the track is a length or an equal split, so the label ellipsizes instead of flooring the column) | `columnheader` |
|
|
2657
|
+
| virtual table | `.bb-table--virtual` on the container (`overflow: auto`, the scroller once its height is bounded) | — |
|
|
2658
|
+
| spacer row | `.bb-table-spacer__row` (a `virtual` body's stand-in for unmounted rows, `aria-hidden`; never style its height) | — (`aria-hidden`) |
|
|
2659
|
+
| skeleton row | `.bb-table-placeholder__row` > `.bb-table-placeholder__cell` (a `virtual` body's skeleton band and fast-scroll stand-in) | `row` > `cell` (`aria-hidden`) |
|
|
2660
|
+
| drag chip | `.bb-table-column-ghost` (a `popover="manual"` in the top layer; paints `--bb-panel`, not the table's `--bg`) | — (`aria-hidden`) |
|
|
2661
|
+
| caption | `.bb-table-caption` (before the root, `aria-labelledby`) | — |
|
|
2662
|
+
|
|
2663
|
+
`[role='row']`, `[role='cell']` and `[role='columnheader']` are supported
|
|
2664
|
+
hooks, not incidental markup — use them when you mean "every row kind" or
|
|
2665
|
+
"every cell"; use the classes when you mean one kind. Cells are direct
|
|
2666
|
+
children of their row, so `:first-child` / `:last-child` / `:nth-child(n)`
|
|
2667
|
+
address columns.
|
|
2668
|
+
|
|
2669
|
+
The full-bleed idiom every workspace page reaches for — the table bleeds past
|
|
2670
|
+
the content padding and hands the inset back to its outermost cells — is
|
|
2671
|
+
written with a **direct-child chain** so a nested table inheriting the
|
|
2672
|
+
parent's tracks (it lives inside `.bb-table-expand__cell`) is never touched:
|
|
2673
|
+
|
|
2674
|
+
```css
|
|
2675
|
+
.table-flush {
|
|
2676
|
+
--page-inset: 1rem;
|
|
2677
|
+
margin-inline: calc(var(--page-inset) * -1);
|
|
2678
|
+
width: calc(100% + var(--page-inset) * 2);
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
.table-flush
|
|
2682
|
+
:deep(> .bb-table__table > .bb-table__head > [role='row'] > :first-child),
|
|
2683
|
+
.table-flush
|
|
2684
|
+
:deep(
|
|
2685
|
+
> .bb-table__table
|
|
2686
|
+
> .bb-table__body
|
|
2687
|
+
> [role='row']
|
|
2688
|
+
> :first-child:not(.bb-table-expand__cell)
|
|
2689
|
+
) {
|
|
2690
|
+
padding-inline-start: var(--page-inset);
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
.table-flush
|
|
2694
|
+
:deep(> .bb-table__table > .bb-table__head > [role='row'] > :last-child),
|
|
2695
|
+
.table-flush
|
|
2696
|
+
:deep(
|
|
2697
|
+
> .bb-table__table
|
|
2698
|
+
> .bb-table__body
|
|
2699
|
+
> [role='row']
|
|
2700
|
+
> :last-child:not(.bb-table-expand__cell)
|
|
2701
|
+
) {
|
|
2702
|
+
padding-inline-end: var(--page-inset);
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
/* Expand rows stay edge-to-edge so the nested table can snap onto the parent
|
|
2706
|
+
tracks without an extra inset shifting it sideways. */
|
|
2707
|
+
.table-flush
|
|
2708
|
+
:deep(
|
|
2709
|
+
> .bb-table__table > .bb-table__body > [role='row'] > .bb-table-expand__cell
|
|
2710
|
+
) {
|
|
2711
|
+
padding-inline: 0;
|
|
2712
|
+
}
|
|
2713
|
+
```
|
|
2714
|
+
|
|
2715
|
+
Leave the geometry alone: `display: grid` / `gap: 0` on the root, row groups
|
|
2716
|
+
and rows, `grid-column: 1 / -1` **and `contain: inline-size`** on the
|
|
2717
|
+
full-width cells (without the containment, expand-row content resizes the
|
|
2718
|
+
parent's columns), `white-space: nowrap` on the actions cell (it sizes the
|
|
2719
|
+
actions track), and `position: sticky` on `.bb-table__head` under
|
|
2720
|
+
`fixed-headers`.
|
|
2721
|
+
|
|
2722
|
+
When you replace a region with `#thead` / `#tbody` / `#tfoot`, ship the same
|
|
2723
|
+
shape the table does — role-complete rows, spanning with `grid-column` rather
|
|
2724
|
+
than `colspan`:
|
|
2725
|
+
|
|
2726
|
+
```vue
|
|
2727
|
+
<template #tfoot="{ columnCount }">
|
|
2728
|
+
<div role="row">
|
|
2729
|
+
<div
|
|
2730
|
+
role="cell"
|
|
2731
|
+
:aria-colspan="columnCount - 1"
|
|
2732
|
+
:style="{ gridColumn: `span ${columnCount - 1}` }"
|
|
2733
|
+
>
|
|
2734
|
+
Total
|
|
2735
|
+
</div>
|
|
2736
|
+
<div role="cell" class="text-right">{{ total }}</div>
|
|
2737
|
+
</div>
|
|
2738
|
+
</template>
|
|
2739
|
+
```
|
|
2740
|
+
|
|
2741
|
+
A `<tr>` inside the grid is not merely unsemantic — the browser wraps it in an
|
|
2742
|
+
implicit `<table>` and the cells never land on the tracks. Nothing lints the
|
|
2743
|
+
roles: a `<div>` row without them renders fine and is invisible to a screen
|
|
2744
|
+
reader.
|
|
2745
|
+
|
|
2072
2746
|
### Config and platform
|
|
2073
2747
|
|
|
2074
2748
|
- **Locale.** The plugin's `locale` (`it`/`en`) drives the built-in strings:
|
|
@@ -2080,10 +2754,13 @@ records list.
|
|
|
2080
2754
|
plugin is still on the default locale, which is `it` — set the plugin's
|
|
2081
2755
|
`locale` option, or override per table with `actions-text` (it is visually
|
|
2082
2756
|
hidden in the header but read by screen readers).
|
|
2083
|
-
- **SSR / Nuxt.**
|
|
2084
|
-
`onServerPrefetch
|
|
2085
|
-
|
|
2086
|
-
|
|
2757
|
+
- **SSR / Nuxt.** Under `bitboss-ui/nuxt` a provider `items` function
|
|
2758
|
+
participates in `onServerPrefetch` and its rows travel in the Nuxt payload, so
|
|
2759
|
+
the first page renders server-side and hydrates without re-fetching. On other
|
|
2760
|
+
SSR hosts the provider runs on the client only and the server renders the
|
|
2761
|
+
empty table — see [SSR prefill](./guides/ssr-prefill.md). The table context
|
|
2762
|
+
(and thus `table-id` pagination wiring) is client-side only — hydration-time
|
|
2763
|
+
onward.
|
|
2087
2764
|
- **Forms.** With `name`, the selection submits as hidden inputs — no JS
|
|
2088
2765
|
needed between the table and a plain `<form>` POST (Inertia included).
|
|
2089
2766
|
|
|
@@ -2092,6 +2769,8 @@ records list.
|
|
|
2092
2769
|
- `BbPagination` — shared page ref, or zero-wiring via `id` + `table-id`.
|
|
2093
2770
|
- `useBbTableContext` (`./composables/useBbTableContext.md`) — bulk-action
|
|
2094
2771
|
toolbars, detail panels, "reset sort" buttons living outside the table.
|
|
2772
|
+
(Column order is not mirrored there: a column panel needs `columns` too,
|
|
2773
|
+
so it lives where `v-model:order` is bound.)
|
|
2095
2774
|
- `BbButton` — per-row links (`href`/`to`) in cells or `#actions`;
|
|
2096
2775
|
`BbDropdown` for an overflow menu of row actions.
|
|
2097
2776
|
- `BbBadge` / `BbAvatar` — status and identity cells via slots.
|
|
@@ -2114,12 +2793,18 @@ records list.
|
|
|
2114
2793
|
localized `common.loadingText`, so an unset value still announces.
|
|
2115
2794
|
- Row selection labels default to the localized "select" word plus every
|
|
2116
2795
|
cell's content. `accessible-label` overrides that per row — it is called
|
|
2117
|
-
`(columns, item)` with the mapped cells
|
|
2118
|
-
|
|
2796
|
+
`(columns, item)` with the mapped cells (in **render** order — look them up
|
|
2797
|
+
by `key`, never by position) and the raw record, and whatever it returns
|
|
2798
|
+
becomes the checkbox's label.
|
|
2799
|
+
- `fixed-columns` pins **slots**, not columns: under `order` the
|
|
2800
|
+
column that lands in a pinned slot is the pinned one (see Column order).
|
|
2801
|
+
- A column panel that holds a partial `order` receives the completed
|
|
2802
|
+
array on the user's first move — its persisted list grows to every key.
|
|
2119
2803
|
- Enabling select-all does not re-emit an empty `modelValue` (see the
|
|
2120
2804
|
select-all section) — in all-mode, read `select-all` +
|
|
2121
2805
|
`unselected-items`, not `modelValue`.
|
|
2122
|
-
- Row event payloads are
|
|
2806
|
+
- Row event payloads are `(event, item)` with `item` your raw record — there is
|
|
2807
|
+
no wrapper to unpack and no `selected` argument.
|
|
2123
2808
|
- A mistyped context/pagination `id` does not error; it silently syncs with
|
|
2124
2809
|
nothing. If a toolbar "works" but the table never reacts, check the id.
|
|
2125
2810
|
- In multiple mode `modelValue` must be an array, or it throws.
|
|
@@ -2154,6 +2839,15 @@ records list.
|
|
|
2154
2839
|
identity rather than object reference.
|
|
2155
2840
|
- Sortable headers set `aria-sort` and render real buttons; custom header
|
|
2156
2841
|
slots should keep a button calling `toggleSort`.
|
|
2842
|
+
- With `reorderable` the header row becomes one roving tab stop
|
|
2843
|
+
(the AG Grid grammar): ←/→ move focus between headers, Home/End to the
|
|
2844
|
+
ends, **Shift+←/→** move the focused column one slot (Shift+Home/End to
|
|
2845
|
+
the ends), Enter/Space sorts a sortable header — every move committed
|
|
2846
|
+
immediately and announced through a polite status region ("X moved to
|
|
2847
|
+
position 2 of 5"); a one-line sr-only instruction describes each header.
|
|
2848
|
+
The sort button leaves the tab order (the header is the target; it still
|
|
2849
|
+
works for the mouse). NVDA/JAWS browse mode intercepts arrows on a focused
|
|
2850
|
+
element — the same focus-mode step as row keyboard navigation.
|
|
2157
2851
|
- When you paginate, pass `page` / `per-page` / `total-items` so the table
|
|
2158
2852
|
announces the full result count (`aria-rowcount`) and each row's absolute
|
|
2159
2853
|
index (`aria-rowindex`, expansion-aware), not just the current page.
|
|
@@ -2173,6 +2867,7 @@ records list.
|
|
|
2173
2867
|
- `v-model:expandedItems` — type: `any[] \| undefined`
|
|
2174
2868
|
- `v-model:highlighted` — type: `any`
|
|
2175
2869
|
- `v-model` — type: `any`
|
|
2870
|
+
- `v-model:order` — type: `string[] \| undefined`
|
|
2176
2871
|
- `v-model:selectAll` — type: `boolean \| undefined`
|
|
2177
2872
|
- `v-model:sort` — type: `BbTableSortEntry[] \| undefined`
|
|
2178
2873
|
- `v-model:unselectedItems` — type: `any[] \| undefined`
|
|
@@ -2181,64 +2876,70 @@ records list.
|
|
|
2181
2876
|
|
|
2182
2877
|
| Prop | Type | Default | Required | Description |
|
|
2183
2878
|
| --- | --- | --- | --- | --- |
|
|
2184
|
-
| `accessibleLabel` | `((columns: MappedCell[], item: any) => string) \| undefined` | | | Function that accepts the columns and the current item as arguments and returns a label to be used for accessibility purposes. |
|
|
2185
|
-
| `actions` | `boolean \| undefined` |
|
|
2879
|
+
| `accessibleLabel` | `((columns: MappedCell[], item: any) => string) \| undefined` | | | Function that accepts the columns and the current item as arguments and returns a label to be used for accessibility purposes. The cells arrive in RENDER order (they follow `order`, so the label reads like the row) — look a cell up by `key`… |
|
|
2880
|
+
| `actions` | `boolean \| undefined` | `false` | | Displays the actions column. |
|
|
2186
2881
|
| `actionsText` | `string \| undefined` | | | Label used in the header of the actions column. |
|
|
2187
2882
|
| `align` | `"left" \| "center" \| "right" \| undefined` | `"left"` | | Text alignment of the columns. |
|
|
2188
2883
|
| `caption` | `string \| undefined` | | | Caption that describes the content of the table. Used for accessibility purposes. |
|
|
2189
2884
|
| `columns` | `InternalColumn<T>[] \| undefined` | `[]` | | Array of definitions of how the columns should be rendered. |
|
|
2190
|
-
| `compact` | `boolean \| undefined` |
|
|
2885
|
+
| `compact` | `boolean \| undefined` | `false` | | Adds a CSS class that applies a compact style to the component. |
|
|
2191
2886
|
| `dependencies` | `unknown[] \| undefined` | `[]` | | Defines an array of dependencies that will trigger actions in the component upon change. |
|
|
2192
2887
|
| `depsDebounceTime` | `number \| undefined` | `0` | | Timeout used to debounce response to changes to dependencies. |
|
|
2193
|
-
| `disabled` | `boolean \| undefined` |
|
|
2194
|
-
| `disableSelectAll` | `boolean \| undefined` |
|
|
2195
|
-
| `displayCaption` | `boolean \| undefined` |
|
|
2196
|
-
| `enforceCoherence` | `boolean \| undefined` |
|
|
2888
|
+
| `disabled` | `boolean \| undefined` | `false` | | Disables the component |
|
|
2889
|
+
| `disableSelectAll` | `boolean \| undefined` | `false` | | Hides the "Select all" checkbox that selectable multi-row tables display by default. |
|
|
2890
|
+
| `displayCaption` | `boolean \| undefined` | `false` | | Boolean that defines whether to display or hide the caption. By default is true and the caption is hidden. |
|
|
2891
|
+
| `enforceCoherence` | `boolean \| undefined` | `false` | | After every load (initial, `dependencies` refetch or a change of the `items` array) prunes the row-keyed models of values whose row is no longer in the result set: `modelValue`, `unselected-items`, `highlighted` and `expanded-items`. Values… |
|
|
2197
2892
|
| `expandedItems` | `any[] \| undefined` | `[]` | | Used by `v-model:expandedItems`. Array of the currently expanded items — toggled by the row expand control and by external writes (mirrors the `highlighted`/`sort` v-model surfaces). Drives the `#expand` slot rows and their `aria-expanded`/… |
|
|
2198
|
-
| `fixed` | `boolean \| undefined` |
|
|
2199
|
-
| `fixedColumns` | `(number \| { index: number; position: "left" \| "right"; })[] \| undefined` | `[]` | | Definition for which column should be fixed. It can be an array of index of the column to fix on the left side of the table or an array of objects indicating the index and the position `left` or `right` where to affix the columns. |
|
|
2200
|
-
| `fixedHeaders` | `boolean \| undefined` |
|
|
2201
|
-
| `headerRowClass` | `Classes \| undefined` | | | Defines the classes to be passed to the header
|
|
2202
|
-
| `highlighted` | `any` | `undefined` | | Used by `v-model:highlighted`. A parallel, UI-intent state to selection — typically "the row whose details are open". A single item value (or `null`) — highlight is single by design.
|
|
2893
|
+
| `fixed` | `boolean \| undefined` | `false` | | Splits the width equally among the columns that declare no `width` (each undeclared column becomes a `minmax(0, 1fr)` track) making each column take up the same amount of space. |
|
|
2894
|
+
| `fixedColumns` | `(number \| { index: number; position: "left" \| "right"; })[] \| undefined` | `[]` | | Definition for which column should be fixed. It can be an array of index of the column to fix on the left side of the table or an array of objects indicating the index and the position `left` or `right` where to affix the columns. Indices a… |
|
|
2895
|
+
| `fixedHeaders` | `boolean \| undefined` | `false` | | Boolean that sets the headers as sticky to the top of the table. |
|
|
2896
|
+
| `headerRowClass` | `Classes \| undefined` | | | Defines the classes to be passed to the header row. |
|
|
2897
|
+
| `highlighted` | `any` | `undefined` | | Used by `v-model:highlighted`. A parallel, UI-intent state to selection — typically "the row whose details are open". A single item value (or `null`) — highlight is single by design. **Opt-in, and `undefined` is the opt-out.** Leave it unbo… |
|
|
2203
2898
|
| `id` | `string \| undefined` | | | Stable id for this table's width context. When omitted a unique id is generated. Nested tables use the nearest ancestor id to inherit widths. |
|
|
2204
|
-
| `inheritColumnWidths` | `string \| boolean \| undefined` | | | Opt a nested table into inheriting its column widths from an ancestor table's matching tracks (accounting for `select`/`actions` columns). This also
|
|
2205
|
-
| `interactiveWhileLoading` | `boolean \| undefined` |
|
|
2899
|
+
| `inheritColumnWidths` | `string \| boolean \| undefined` | | | Opt a nested table into inheriting its column widths from an ancestor table's matching tracks (accounting for `select`/`actions` columns). This also makes the parent's tracks authoritative, so it is **off by default** — a nested table rende… |
|
|
2900
|
+
| `interactiveWhileLoading` | `boolean \| undefined` | `false` | | Keeps the table interactive while it is loading. The header and the rows are `inert` while loading by default — a refetch is about to replace the rows, so editing, selecting or sorting them races the incoming data. This escape hatch exists … |
|
|
2206
2901
|
| `items` | `Item[] \| ((prefill: boolean, modelValue?: any) => Item[] \| Promise<Item[]>)` | `[]` | yes | Rows to render, or a fetcher that returns them. |
|
|
2207
2902
|
| `itemValue` | `ItemAccessor<Item> \| undefined` | | | |
|
|
2208
|
-
| `keyboardNavigation` | `boolean \| undefined` |
|
|
2903
|
+
| `keyboardNavigation` | `boolean \| undefined` | `false` | | Opt-in keyboard navigation with a roving tabindex: the body is one tab stop, ArrowUp/ArrowDown move the focused row (Home/End jump), and Enter/Space activate it through the same path as a mouse click (highlight toggle + `click:row`). Focus … |
|
|
2209
2904
|
| `legend` | `string \| undefined` | | | Text content of the `<legend>` a `selectable` table renders (it becomes a `<fieldset>`, and the legend is that fieldset's accessible name). Omit it and the table falls back to `caption`, then to a localized default — the fieldset always end… |
|
|
2210
|
-
| `loading` | `boolean \| undefined` |
|
|
2211
|
-
| `loadingText` | `string \| undefined` |
|
|
2905
|
+
| `loading` | `boolean \| undefined` | `false` | | Sets the component in a loading state, usually triggering some visual styles. With no rows yet this renders skeletons; over existing rows the rows stay on screen (dimmed, with an indeterminate bar) rather than being replaced — a refetch sho… |
|
|
2906
|
+
| `loadingText` | `string \| undefined` | `the localized `common.loadingText` ("Loading...")` | | Screen-reader announcement for the loading row's `aria-live` status. Visually hidden — the skeleton (or the `#loading` slot) is the visible signal. |
|
|
2212
2907
|
| `max` | `number \| undefined` | `Infinity` | | Maximum number of items that can be selected. |
|
|
2213
2908
|
| `modelValue` | `any` | `[]` | | Used by v-model. Can be any serializable type. |
|
|
2214
2909
|
| `multiple` | `boolean \| undefined` | `true` | | Allows the selection of multiple items. |
|
|
2215
2910
|
| `name` | `string \| undefined` | | | Defines the name of the input. |
|
|
2216
|
-
| `noDataText` | `string \| undefined` |
|
|
2911
|
+
| `noDataText` | `string \| undefined` | `the localized `common.noDataText` ("No data to display")` | | String displayed when there are no items to display. |
|
|
2912
|
+
| `order` | `string[] \| undefined` | `[]` | | Used by `v-model:order`. Ordered array of column keys — the render order of the data columns. Keys listed here render first, in this order; declared columns it omits follow, in declaration order (so a column added to `columns` later renders… |
|
|
2217
2913
|
| `page` | `string \| number \| undefined` | | | Current page number starting from 1, used for accessibility purposes. Also flows into the shared table context, so a `BbPagination` paired through `id` reads it as the current page. Numeric strings are coerced (server pagination fields ofte… |
|
|
2218
2914
|
| `perPage` | `string \| number \| undefined` | | | Number of items per page, used for accessibility purposes. Also flows into the shared table context read by a `BbPagination` paired through `id`. When omitted, an array `items` table infers it from `items.length`; an explicit value (or a co… |
|
|
2219
|
-
| `readonly` | `boolean \| undefined` |
|
|
2220
|
-
| `
|
|
2915
|
+
| `readonly` | `boolean \| undefined` | `false` | | Sets the input in a readonly state. |
|
|
2916
|
+
| `reorderable` | `boolean \| undefined` | `false` | | Lets the user reorder the data columns: drag a header cell (AG Grid style — the column moves live under the pointer and a chip with its label follows) or, from the keyboard, use each header's reorder handle (ArrowLeft / ArrowRight move the … |
|
|
2917
|
+
| `resizable` | `boolean \| undefined` | `false` | | Lets the user resize the data columns of a root table: drag the handle on a header's trailing edge (the column follows live, the width commits on release), or press Alt + ArrowLeft / ArrowRight on the focused header; double-click the handle… |
|
|
2918
|
+
| `rowClass` | `RowClasses<Item> \| undefined` | | | Defines the classes to be passed to each data row. Can also be a function of the row's item for dynamic values. |
|
|
2221
2919
|
| `selectable` | `boolean \| ((item: Item) => boolean) \| undefined` | | | Defines whether the table is selectable. Can be a global boolean that affects all rows or a function that accepts an item and returns a boolean that only affects that item. |
|
|
2222
2920
|
| `selectAll` | `boolean \| undefined` | `false` | | Defines whether the "Select all" checkbox is checked. |
|
|
2223
2921
|
| `selectAllLabel` | `string \| undefined` | | | Text of the label used by the "Select all" boolean. |
|
|
2224
2922
|
| `selectText` | `string \| undefined` | | | Text of the header used for the radio inputs when the table is not `multiple`. |
|
|
2225
2923
|
| `sort` | `BbTableSortEntry[] \| undefined` | `[]` | | Used by `v-model:sort`. Ordered array of `[columnKey, direction]` entries, one per actively sorted column. Bidirectional: the header buttons of `sortable` columns update it, and external writes update the header indicators. The table does n… |
|
|
2226
|
-
| `tdClass` | `ColumnClasses<Item> \| undefined` | | | Defines the classes to be passed to
|
|
2227
|
-
| `thClass` | `Classes \| undefined` | | | Defines the classes to be passed to
|
|
2924
|
+
| `tdClass` | `ColumnClasses<Item> \| undefined` | | | Defines the classes to be passed to every data cell. Can also be a function for dynamic values. |
|
|
2925
|
+
| `thClass` | `Classes \| undefined` | | | Defines the classes to be passed to every header cell. |
|
|
2228
2926
|
| `totalItems` | `string \| number \| undefined` | | | Total number of items there is. Also flows into the shared table context, where a `BbPagination` paired through `id` derives its page count from it. When omitted, an array `items` table infers it from `items.length`; an explicit value (or a… |
|
|
2229
2927
|
| `totalPages` | `string \| number \| undefined` | | | Seeds the page count shown by a `BbPagination` paired through `id`. A shortcut for when the server reports a page count rather than a row count: the table renders nothing from it, it only flows into the shared table context. A known total a… |
|
|
2230
2928
|
| `unselectedItems` | `any[] \| undefined` | `[]` | | Items that are not selected. |
|
|
2929
|
+
| `virtual` | `boolean \| undefined` | `false` | | Windows the body: only the rows in (and just around) the vertical scrollport are mounted, so a client-side dataset of any size scrolls at a constant cost. The table itself is the scroller as soon as you bound its height (`max-height` / `hei… |
|
|
2231
2930
|
|
|
2232
2931
|
## Events
|
|
2233
2932
|
|
|
2234
|
-
- `click:row` — `(e: "click:row", event: MouseEvent, item: any
|
|
2235
|
-
- `contextmenu:row` — `(e: "contextmenu:row", event: MouseEvent, item: any
|
|
2236
|
-
- `dblclick:row` — `(e: "dblclick:row", event: MouseEvent, item: any
|
|
2933
|
+
- `click:row` — `(e: "click:row", event: MouseEvent, item: any): void`
|
|
2934
|
+
- `contextmenu:row` — `(e: "contextmenu:row", event: MouseEvent, item: any): void`
|
|
2935
|
+
- `dblclick:row` — `(e: "dblclick:row", event: MouseEvent, item: any): void`
|
|
2237
2936
|
- `item:selected` — `(e: "item:selected", value: any): void`
|
|
2238
2937
|
- `item:unselected` — `(e: "item:unselected", value: any): void`
|
|
2938
|
+
- `resize:column` — `(e: "resize:column", key: string, width: number \| null): void` — A column was resized by the user (`resizable`): the new width in px, or `null` when the handle was double-clicked to reset the column to its declared width.
|
|
2239
2939
|
- `update:expandedItems` — `(e: "update:expandedItems", value: any[]): void`
|
|
2240
2940
|
- `update:highlighted` — `(e: "update:highlighted", value: any): void`
|
|
2241
2941
|
- `update:modelValue` — `(e: "update:modelValue", value: any): void`
|
|
2942
|
+
- `update:order` — `(e: "update:order", value: string[]): void`
|
|
2242
2943
|
- `update:selectAll` — `(e: "update:selectAll", value: boolean): void`
|
|
2243
2944
|
- `update:sort` — `(e: "update:sort", value: BbTableSortEntry[]): void`
|
|
2244
2945
|
- `update:unselectedItems` — `(e: "update:unselectedItems", value: any[]): void`
|
|
@@ -2248,13 +2949,15 @@ records list.
|
|
|
2248
2949
|
- `actions` — scope: `{ expanded?: boolean \| undefined; expandProps?: ExpandControllerProps \| undefined; toggleExpanded?: (() => void) \| undefined; highlighted?: boolean \| undefi…` — Content rendered in the actions cell for each row.
|
|
2249
2950
|
- `expand` — scope: `{ expanded: boolean; expandProps?: ExpandControllerProps \| undefined; toggleExpanded: () => void; highlighted?: boolean \| undefined; toggleHighlighted?: (() =…` — Full-width expandable content row rendered below each main row when expanded.
|
|
2250
2951
|
- `header:actions` — scope: `{ text: string; }` — Replaces the default actions header cell content.
|
|
2952
|
+
- `header:append` — scope: `BbTableHeaderAffixSlotProps<Item>` — Content rendered after every data column's header cell content — the label + sort control, or the column's own `header:<key>` replacement. A column's `header:<key>:append` slot wins over this one for that column; they never stack. Branch on…
|
|
2953
|
+
- `header:prepend` — scope: `BbTableHeaderAffixSlotProps<Item>` — Content rendered before every data column's header cell content — the label + sort control, or the column's own `header:<key>` replacement. A column's `header:<key>:prepend` slot wins over this one for that column; they never stack. Branch …
|
|
2251
2954
|
- `header:select` — scope: `{ multiple: boolean; selectAll: boolean; text: string; }` — Replaces the default selection header cell content (checkbox or radio label).
|
|
2252
2955
|
- `loading` — scope: `{ items: Item[]; }` — Content shown while table rows are loading (replaces the skeleton rows).
|
|
2253
|
-
- `no-data` — scope: `object` — Content shown when the items list is empty and not loading. It fills the table's own full-width empty-state cell (
|
|
2956
|
+
- `no-data` — scope: `object` — Content shown when the items list is empty and not loading. It fills the table's own full-width empty-state cell (it already spans the selection/actions columns), so provide the *content* — a message, an illustration, a "clear filters" butt…
|
|
2254
2957
|
- `select` — scope: `{ item: Item; value: any; checked: boolean; disabled: boolean; inputName: string; readonly: boolean; toggleSelected?: (() => void) \| undefined; }` — Replaces the default checkbox/radio in the selection cell for each row.
|
|
2255
|
-
- `tbody` — scope: `object` — Replaces the
|
|
2256
|
-
- `tfoot` — scope: `{ columnCount: number; }` —
|
|
2257
|
-
- `thead` — scope: `object` — Replaces the
|
|
2958
|
+
- `tbody` — scope: `object` — Replaces the content of the body row group (`.bb-table__body`). Use when you need full control over the body markup. Provide role-complete rows — `<div role="row">` holding one `<div role="cell">` per column — never a `<tr>`/`<td>`. A full-…
|
|
2959
|
+
- `tfoot` — scope: `{ columnCount: number; }` — Fills the footer row group (`.bb-table__foot`). Use when you need a footer. Provide role-complete rows — `<div role="row">` holding `<div role="cell">` elements — never a `<tr>`/`<td>`.
|
|
2960
|
+
- `thead` — scope: `object` — Replaces the content of the header row group (`.bb-table__head`). Use when you need full control over the header markup. The table is a CSS grid, not a `<table>`: provide role-complete rows — `<div role="row">` holding one `<div role="colum…
|
|
2258
2961
|
|
|
2259
2962
|
## See Also
|
|
2260
2963
|
|