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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/ai/BbTable.md +206 -98
  2. package/dist/ai/changelog.json +21 -3
  3. package/dist/ai/components.json +40 -15
  4. package/dist/ai/guides/component-picker.md +18 -18
  5. package/dist/ai/guides/icons-policy.md +7 -3
  6. package/dist/ai/guides/installation-and-plugin-setup.md +11 -14
  7. package/dist/ai/guides/migration/components/bb-table.md +131 -17
  8. package/dist/ai/recipes/inertia/approvals-inbox.md +0 -1
  9. package/dist/ai/recipes/inertia/inline-edit-workspace.md +0 -1
  10. package/dist/ai/recipes/inertia/ownership-atlas.md +8 -9
  11. package/dist/ai/recipes/inertia/record-form.md +0 -1
  12. package/dist/ai/recipes/inertia/records-workspace.md +0 -1
  13. package/dist/ai/recipes/inertia/upload-center.md +0 -1
  14. package/dist/ai/recipes/nuxt/approvals-inbox.md +0 -1
  15. package/dist/ai/recipes/nuxt/inline-edit-workspace.md +0 -1
  16. package/dist/ai/recipes/nuxt/record-form.md +0 -1
  17. package/dist/ai/recipes/nuxt/records-workspace.md +0 -1
  18. package/dist/ai/recipes/nuxt/upload-center.md +0 -1
  19. package/dist/ai/recipes/vue/approvals-inbox.md +0 -1
  20. package/dist/ai/recipes/vue/inline-edit-workspace.md +0 -1
  21. package/dist/ai/recipes/vue/records-workspace.md +0 -1
  22. package/dist/ai/recipes/vue/upload-center.md +0 -1
  23. package/dist/ai/source/BbTable.md +564 -173
  24. package/dist/components/BbTable/BbTable.vue.d.ts +2 -0
  25. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +865 -735
  26. package/dist/components/BbTable/types.d.ts +98 -46
  27. package/dist/components/BbTable/utils.d.ts +21 -16
  28. package/dist/components/BbTable/utils.js +8 -8
  29. package/dist/composables/useTableWidthContext.d.ts +24 -4
  30. package/dist/composables/useTableWidthContext.js +31 -28
  31. package/dist/deprecation/ai-deprecations.json.d.ts +22 -0
  32. package/dist/deprecation/ai-deprecations.json.js +1 -1
  33. package/dist/llms-full.txt +381 -173
  34. package/dist/llms-medium.txt +29 -32
  35. package/dist/vite.js +1 -1
  36. package/package.json +1 -1
  37. package/scripts/lib/eslint-plugin.mjs +9 -14
  38. package/scripts/lib/validate-bb-markup.mjs +0 -7
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "bitboss-ui",
4
- "version": "3.0.0-beta.20",
5
- "generatedAt": "2026-09-01T16:02:37.101Z",
4
+ "version": "3.0.0-beta.21",
5
+ "generatedAt": "2026-09-07T15:58:18.552Z",
6
6
  "composables": [
7
7
  {
8
8
  "name": "useBbConfig",
@@ -13161,13 +13161,6 @@
13161
13161
  "required": false,
13162
13162
  "description": "Function that accepts the columns and the current item as\narguments and returns a label to be used for accessibility purposes.\nThe cells arrive in RENDER order (they follow `order`, so the\nlabel reads like the row) — look a cell up by `key`, never by position."
13163
13163
  },
13164
- {
13165
- "name": "actions",
13166
- "type": "boolean | undefined",
13167
- "required": false,
13168
- "default": "false",
13169
- "description": "Displays the actions column."
13170
- },
13171
13164
  {
13172
13165
  "name": "actionsText",
13173
13166
  "type": "string | undefined",
@@ -13258,11 +13251,11 @@
13258
13251
  "description": "Splits the width equally among the columns that declare no `width`\n(each undeclared column becomes a `minmax(0, 1fr)` track)\nmaking each column take up the same amount of space."
13259
13252
  },
13260
13253
  {
13261
- "name": "fixedColumns",
13262
- "type": "(number | { index: number; position: \"left\" | \"right\"; })[] | undefined",
13254
+ "name": "fixedActions",
13255
+ "type": "boolean | undefined",
13263
13256
  "required": false,
13264
- "default": "[]",
13265
- "description": "Definition for which column should be fixed.\nIt can be an array of index of the column to fix on the left side of the table or\nan array of objects indicating the index and the position `left` or `right` where to affix the columns.\n\nIndices are POSITIONS in the rendered column list (a selection column is\nindex 0), like a spreadsheet's freeze panes — not per-column pins. With\n`order` the pinned slot holds and whichever column lands in it is\npinned; dragging a column out of a pinned slot unpins it."
13257
+ "default": "false",
13258
+ "description": "Pins the actions column (the one the `#actions` slot creates) to the\nright edge, `position: sticky`, in place. Data columns pin through\n`column.fixed`; this is the same feature for the structural column that\nhas no definition to carry it. No effect without an `#actions` slot."
13266
13259
  },
13267
13260
  {
13268
13261
  "name": "fixedHeaders",
@@ -13271,6 +13264,13 @@
13271
13264
  "default": "false",
13272
13265
  "description": "Boolean that sets the headers as sticky to the top of the table."
13273
13266
  },
13267
+ {
13268
+ "name": "fixedSelect",
13269
+ "type": "boolean | undefined",
13270
+ "required": false,
13271
+ "default": "false",
13272
+ "description": "Pins the selection column to the left edge, `position: sticky`, in\nplace. Data columns pin through `column.fixed`; this is the same feature\nfor the structural column that has no definition to carry it. No effect\nunless the table is `selectable`."
13273
+ },
13274
13274
  {
13275
13275
  "name": "headerRowClass",
13276
13276
  "type": "Classes | undefined",
@@ -13381,7 +13381,7 @@
13381
13381
  "type": "string[] | undefined",
13382
13382
  "required": false,
13383
13383
  "default": "[]",
13384
- "description": "Used by `v-model:order`. Ordered array of column keys — the render\norder of the data columns. Keys listed here render first, in this order;\ndeclared columns it omits follow, in declaration order (so a column added\nto `columns` later renders last); keys that match no declared column render\nnothing but are PRESERVED in place on every write — a column you hide by\nremoving it from `columns` (a column panel) keeps its slot and comes back\nwhere it was. The header drag / keyboard reorder of `reorderable`\nwrites it back complete (every declared key present, duplicates dropped —\nfirst occurrence wins) with exactly the moved column relocated: every other\nkey, hidden ones included, keeps its relative order, and a drop that\nchanges nothing emits nothing. Works uncontrolled when unbound, exactly\nlike `sort`. The `select` and `actions` columns are structural and never\npart of it."
13384
+ "description": "Used by `v-model:order`. Ordered array of column keys — the render\norder of the data columns. Keys listed here render first, in this order;\ndeclared columns it omits follow, in declaration order (so a column added\nto `columns` later renders last); a `hidden` column keeps its slot\nwithout rendering, and keys that match no declared column render\nnothing but are PRESERVED in place on every write. The header drag /\nkeyboard reorder of `reorderable` writes it back complete (every\ndeclared key present, hidden ones included, duplicates dropped first\noccurrence wins) with exactly the moved column relocated: every other\nkey keeps its relative order, and a drop that changes nothing emits\nnothing. Works uncontrolled when unbound, exactly like `sort`. The\n`select` and `actions` columns are structural and never part of it."
13385
13385
  },
13386
13386
  {
13387
13387
  "name": "page",
@@ -13506,6 +13506,11 @@
13506
13506
  "name": "dblclick:row",
13507
13507
  "signature": "(e: \"dblclick:row\", event: MouseEvent, item: any): void"
13508
13508
  },
13509
+ {
13510
+ "name": "hide:column",
13511
+ "signature": "(e: \"hide:column\", key: string): void",
13512
+ "description": "A column was hidden from its header (a `#header:<key>` /\n`#header:<key>:append` control calling the slot's `hideColumn()`). Fired once;\nthe table keeps the column hidden for the mount, or until the column's\n`hidden` flag changes — write `hidden: true` into the definition to\npersist it, `hidden: false` (a column panel) to show it again."
13513
+ },
13509
13514
  {
13510
13515
  "name": "item:selected",
13511
13516
  "signature": "(e: \"item:selected\", value: any): void"
@@ -13552,7 +13557,7 @@
13552
13557
  {
13553
13558
  "name": "actions",
13554
13559
  "type": "{ expanded?: boolean | undefined; expandProps?: ExpandControllerProps | undefined; toggleExpanded?: (() => void) | undefined; highlighted?: boolean | undefined; toggleHighlighted?: (() => void) | undefined; selected?: boolean | undefined; toggleSelected?: (() => void) | undefined; item: Item; value: any; }",
13555
- "description": "Content rendered in the actions cell for each row."
13560
+ "description": "Content rendered in the actions cell for each row. Providing this slot\nis what CREATES the actions column (there is no prop): the header cell\n(labelled by `actions-text`, replaceable through `#header:actions`) and\none cell per row appear with it, and go away with it. Pin it with\n`fixed-actions`."
13556
13561
  },
13557
13562
  {
13558
13563
  "name": "expand",
@@ -13661,6 +13666,26 @@
13661
13666
  "fix": {
13662
13667
  "kind": "rename"
13663
13668
  }
13669
+ },
13670
+ {
13671
+ "name": "actions",
13672
+ "new": null,
13673
+ "since": "3.0.0-beta.21",
13674
+ "migration": "ai/guides/migration/components/bb-table.md",
13675
+ "silent": true,
13676
+ "fix": {
13677
+ "kind": "remove"
13678
+ }
13679
+ },
13680
+ {
13681
+ "name": "fixedColumns",
13682
+ "new": null,
13683
+ "since": "3.0.0-beta.21",
13684
+ "migration": "ai/guides/migration/components/bb-table.md",
13685
+ "silent": true,
13686
+ "fix": {
13687
+ "kind": "manual"
13688
+ }
13664
13689
  }
13665
13690
  ],
13666
13691
  "renamed": [
@@ -231,24 +231,24 @@ a composable you want is not in this corpus's `composables/` folder — it is no
231
231
  here.** Check this list before inventing a name; if the need is genuinely absent,
232
232
  compose it or write it yourself.
233
233
 
234
- | Assumed component or composable | Reality |
235
- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
236
- | `BbColumnManager` — show/hide/reorder columns | Does not exist as a component. Order is `v-model:order` on `BbTable` (an array of keys any panel can write; `reorderable` adds the built-in header drag + keyboard handle); visibility is still filtering `columns` the hidden key keeps its slot in the order. Compose the panel from `BbPopover` + `BbCheckbox`. |
237
- | `BbKanban` / drag-and-drop board | Does not exist. There is no reusable DnD primitive in the library (`BbTable`'s header drag is internal to the table) — compose the board from `BbTable` (a table/list view of the same data) or hand-roll the drag-and-drop interaction yourself. |
238
- | `BbModal` | It is [BbDialog](../BbDialog.md). |
239
- | `BbDrawer` / `BbSheet` | It is [BbOffCanvas](../BbOffCanvas.md). |
240
- | `BbCombobox` / `BbAutocomplete` | It is [BbSelect](../BbSelect.md) / [BbSelectPopover](../BbSelectPopover.md) — search is built in, see `disable-writing`. |
241
- | `BbToggle` / `BbToggleGroup` | It is [BbButton](../BbButton.md) with `v-model` (+ `true-value` / `false-value`), which owns `aria-pressed` and the active class. |
242
- | `TagChip` / colored, read-only tag chip | It is [BbBadge](../BbBadge.md) — [BbTag](../BbTag.md) is a tags-_input_ only, not a display component. Use `BbBadge` with a registered `soft-*` variant for a fixed palette, or a one-off `--bg`/`--color` override per row for arbitrary/data-driven colors (BbBadge.md § One-off colors) — not a registered variant per distinct color. |
243
- | `BbTab` (singular) | The consumer API is [BbTabs](../BbTabs.md) / `BbTabsRoot`. `BbTab` appears only as the **v2 "before" side** of the migration diffs. |
244
- | `BbCard` | Does not exist — a card is a `<div>` with tokens. See [design-language](./design-language.md). |
245
- | `BbList` / `BbListItem` / `BbDescriptionList` | Do not exist — a list row is an `<li>` with tokens, and [BbBaseButton](../BbBaseButton.md) makes the whole row navigable. See the note under Data Display. |
246
- | `useLiveFeed` / any polling or cross-tab feed helper | Does not exist, and neither does a cross-tab helper — `useBroadcastChannelInstance` was removed (cross-tab messaging is not a UI concern). Use `useBroadcastChannel` from `@vueuse/core`; the polling and the merge are yours. |
247
- | `BbStepper` / `BbWizard` | Does not exist as a component; the wizard-form recipe composes one. |
248
- | `BbSkeleton` (standalone) | Only `BbTable` ships skeletons, via the column `skeleton` field. |
249
- | `BbCommand` / command palette | Does not exist. Compose `BbDialog` + `BbSelectPopover`. |
250
- | `BbCalendar` (month / scheduling view) | Does not exist. [BbDatePicker](../BbDatePicker.md) is a picker, not a calendar surface. |
251
- | `BbChart` / `BbGraph`, `BbCarousel`, `BbTimeline`, `BbDivider` | Do not exist. No charting, carousel or timeline primitive ships here. |
234
+ | Assumed component or composable | Reality |
235
+ | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
236
+ | `BbColumnManager` — show/hide/reorder columns | Does not exist as a component. Order is `v-model:order` on `BbTable` (an array of keys any panel can write; `reorderable` adds the built-in header drag + keyboard handle); visibility is `hidden: true` on the column definition (the column stays declared and keeps its slot in the order; header slots get a `hideColumn()` that fires `hide:column`). Compose the panel from `BbPopover` + `BbCheckbox`. |
237
+ | `BbKanban` / drag-and-drop board | Does not exist. There is no reusable DnD primitive in the library (`BbTable`'s header drag is internal to the table) — compose the board from `BbTable` (a table/list view of the same data) or hand-roll the drag-and-drop interaction yourself. |
238
+ | `BbModal` | It is [BbDialog](../BbDialog.md). |
239
+ | `BbDrawer` / `BbSheet` | It is [BbOffCanvas](../BbOffCanvas.md). |
240
+ | `BbCombobox` / `BbAutocomplete` | It is [BbSelect](../BbSelect.md) / [BbSelectPopover](../BbSelectPopover.md) — search is built in, see `disable-writing`. |
241
+ | `BbToggle` / `BbToggleGroup` | It is [BbButton](../BbButton.md) with `v-model` (+ `true-value` / `false-value`), which owns `aria-pressed` and the active class. |
242
+ | `TagChip` / colored, read-only tag chip | It is [BbBadge](../BbBadge.md) — [BbTag](../BbTag.md) is a tags-_input_ only, not a display component. Use `BbBadge` with a registered `soft-*` variant for a fixed palette, or a one-off `--bg`/`--color` override per row for arbitrary/data-driven colors (BbBadge.md § One-off colors) — not a registered variant per distinct color. |
243
+ | `BbTab` (singular) | The consumer API is [BbTabs](../BbTabs.md) / `BbTabsRoot`. `BbTab` appears only as the **v2 "before" side** of the migration diffs. |
244
+ | `BbCard` | Does not exist — a card is a `<div>` with tokens. See [design-language](./design-language.md). |
245
+ | `BbList` / `BbListItem` / `BbDescriptionList` | Do not exist — a list row is an `<li>` with tokens, and [BbBaseButton](../BbBaseButton.md) makes the whole row navigable. See the note under Data Display. |
246
+ | `useLiveFeed` / any polling or cross-tab feed helper | Does not exist, and neither does a cross-tab helper — `useBroadcastChannelInstance` was removed (cross-tab messaging is not a UI concern). Use `useBroadcastChannel` from `@vueuse/core`; the polling and the merge are yours. |
247
+ | `BbStepper` / `BbWizard` | Does not exist as a component; the wizard-form recipe composes one. |
248
+ | `BbSkeleton` (standalone) | Only `BbTable` ships skeletons, via the column `skeleton` field. |
249
+ | `BbCommand` / command palette | Does not exist. Compose `BbDialog` + `BbSelectPopover`. |
250
+ | `BbCalendar` (month / scheduling view) | Does not exist. [BbDatePicker](../BbDatePicker.md) is a picker, not a calendar surface. |
251
+ | `BbChart` / `BbGraph`, `BbCarousel`, `BbTimeline`, `BbDivider` | Do not exist. No charting, carousel or timeline primitive ships here. |
252
252
 
253
253
  App chrome — an app shell, sidebar, page header, selection dock — is
254
254
  deliberately **not** a component: it is yours to own. The per-platform
@@ -36,9 +36,13 @@ The `bitbossUi` Vite plugin (`iconDir` option) — or the Nuxt module (config ke
36
36
  `lucide:<name>` available; removing it makes them all errors. Nothing to
37
37
  configure.
38
38
  - **Dev**: all icons of installed sets are registered. Referencing an
39
- uninstalled prefix or a non-existent name fails loudly with an actionable
40
- message (`"lucide:fo" requires "@iconify-json/lucide" run: npm install -D …`
41
- or `icon "x" does not exist in the lucide icon set`).
39
+ uninstalled prefix or a non-existent name fails loudly in the Vite error
40
+ overlay with an actionable message. A typo inside an installed set reads
41
+ "does not exist in the lucide icon set — did you mean `lucide:…`?"; a set
42
+ that is not installed reads "no installed icon set has the prefix `mdi`
43
+ (installed sets: lucide) … run: `npm install -D @iconify-json/mdi`". Read
44
+ the message: a typo wants a different name, a missing set wants an install,
45
+ never a guess.
42
46
  - **Build**: source files (`.vue`/`.ts`/`.js`/`.tsx`/`.jsx`; tests and stories
43
47
  excluded) are scanned for static `provider:name` literals; each referenced
44
48
  icon is emitted as its own lazy chunk. This is why rule 4 exists — and why an
@@ -31,19 +31,16 @@ goes stable, so never reach for it here.
31
31
 
32
32
  Peer dependency: `vue ^3.5.12` — the only required one. `@inertiajs/vue3` is an **optional** peer, needed only in Inertia apps.
33
33
 
34
- `@iconify/collections` is an **optional** peer too, and npm never installs
35
- optional peers automatically install it yourself if you want the dev-time
36
- provider-icon diagnostics:
37
-
38
- ```bash
39
- npm install -D @iconify/collections
40
- ```
41
-
42
- Without it the plugin has no list of valid Iconify prefixes, so the dev-overlay
43
- errors below (the "install `@iconify-json/<prefix>`" hint and the "did you
44
- mean …?" suggestion for a mistyped icon name) never fire — a wrong provider
45
- icon then surfaces only as `BbIcon`'s generic runtime `Icon "…" does not exist.`
46
- Nothing else changes: icons that resolve still render, in dev and in production.
34
+ You do **not** need `@iconify/collections`. It is declared as an optional peer
35
+ for one reason only: when it happens to be installed, the dev-overlay message
36
+ for an unknown icon prefix can say for certain whether that prefix is a real
37
+ Iconify set ("requires `@iconify-json/mdi`, run …") or not ("neither an
38
+ installed set nor a known Iconify prefix"). Without it the same overlay fires
39
+ with a hedged wording ("no installed icon set has the prefix `mdi`; if it is an
40
+ Iconify set, run …"). A mistyped name inside a set you **have** installed
41
+ (`lucide:eye-misspelled`) is diagnosed from the set's own data in both cases,
42
+ with a "did you mean …?" suggestion. Nothing else changes: icons that resolve
43
+ still render, in dev and in production.
47
44
 
48
45
  **Known-good toolchain:** TypeScript **5.x or 6.x** if your build gate runs
49
46
  `vue-tsc`. Both type-check cleanly against the shipped `.d.ts`; the library
@@ -308,7 +305,7 @@ utilities. (Same trap as the scoped-`<style>` case above, one level up.)
308
305
  ## 6) Icons
309
306
 
310
307
  - `iconDir` (required) is scanned recursively for `.svg` files; each file becomes `local:<basename>`.
311
- - Provider icons (`lucide:pencil`, `mdi:home`, …) require the matching `@iconify-json/<prefix>` package installed in the consumer app. Missing packages/icons produce actionable dev-overlay errors **only when the optional `@iconify/collections` peer is installed** (see [Install](#1-install)); otherwise you get `BbIcon`'s generic runtime error instead. Production builds report neither this is a dev-time aid.
308
+ - Provider icons (`lucide:pencil`, `mdi:home`, …) require the matching `@iconify-json/<prefix>` package installed in the consumer app. In dev, every `prefix:name` the registry does not know produces a Vite error overlay with the fix: a mistyped name inside an installed set gets a "did you mean …?" hint, and an uninstalled prefix gets the list of installed sets plus the `npm install -D @iconify-json/<prefix>` command (the optional `@iconify/collections` peer only sharpens that wording, see [Install](#1-install)). A missing `local:` icon is a missing file in `iconDir` and surfaces as `BbIcon`'s runtime error naming it. Production builds report none of this the scan simply drops what does not resolve, so a wrong name fails at runtime there.
312
309
  - Production builds bundle **only statically referenced** provider icons — write icon names as literal strings (`icon="lucide:trash-2"`), never assemble them dynamically.
313
310
  - The plugin also writes `node_modules/.bitboss-ui/local-icons.json` and (in dev, unless `vscodeSettings: false`) wires `.vscode/settings.json` so the Iconify editor extension previews local icons.
314
311
 
@@ -7,20 +7,24 @@ summary: 'The table renders as a CSS grid of divs with ARIA roles, not a <table>
7
7
 
8
8
  ## Changes
9
9
 
10
- | v2 | v3 | Kind |
11
- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
12
- | `allowSelectAll?: boolean` (default `true`) | `disableSelectAll?: boolean` (default `false`) | rename, polarity inverted |
13
- | refetch with rows → skeleton | refetch keeps rows visible (dimmed + progress bar) | **behavior** — the skeleton only shows when there is nothing to display |
14
- | rows interactive while loading | header + rows go `inert` while loading | **behavior** — opt out with `interactive-while-loading` |
15
- | `#loading` slot fired on every load | fires on the **first load only** (it replaces the skeleton, so it follows the skeleton's rule) | **⚠ silent** for anyone styling refetches through it |
16
- | `#no-data` replaced the empty-state `<tr>` | fills the table's own full-width cell (`aria-colspan` counted, centred) | **behavior** — pass content, not a row |
17
- | `<table>` / `<thead>` / `<tbody>` / `<tr>` / `<th>` / `<td>` markup _(v2 and v3 ≤ beta.5)_ | a CSS grid of `<div>`s carrying `role="table\|rowgroup\|row\|columnheader\|cell"`; `.bb-table__table` / `__head` / `__body` / `__foot` are the new containers, every `.bb-table*` class survives | **⚠ silent** for element selectors, `#thead`/`#tbody`/`#tfoot` markup, copy/paste and print — see below |
18
- | `--fill` / `--natural-width` published on `.bb-table` _(v3 ≤ beta.5)_ | **removed** — nothing publishes them | **⚠ silent** — see below |
19
- | — | props `enforceCoherence` (prunes row-keyed models after each load; incompatible with pagination-via-`dependencies`), `expandedItems` + `#expand`, `sort`, `keyboardNavigation`, `highlighted`, `rowClass`, `order` (v-model) + `reorderable` | additive |
20
- | — | **column** fields `sortable?: boolean`, `rowClass?: ColumnClasses` | additive, **collides** — see below |
21
- | `useBbTableContext(id).total` _(v3 alphas 4)_ | `useBbTableContext(id).totalItems` | **hard rename** on the composable handle — see below |
22
- | `@click:row` / `@contextmenu:row` / `@dblclick:row` handlers receive `(event, item, selected)` _(v2 and v3 ≤ beta.5)_ | `(event, item)` — `item` is now your raw record (was the internal row wrapper, record at `row.item`); the trailing `selected` boolean is gone | **payload slimmed** — see below |
23
- | `--bb-table-*` custom properties | same names without the `bb-` prefix | **⚠ silent** for nested-table layouts — see below |
10
+ | v2 | v3 | Kind |
11
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
12
+ | `allowSelectAll?: boolean` (default `true`) | `disableSelectAll?: boolean` (default `false`) | rename, polarity inverted |
13
+ | refetch with rows → skeleton | refetch keeps rows visible (dimmed + progress bar) | **behavior** — the skeleton only shows when there is nothing to display |
14
+ | rows interactive while loading | header + rows go `inert` while loading | **behavior** — opt out with `interactive-while-loading` |
15
+ | `#loading` slot fired on every load | fires on the **first load only** (it replaces the skeleton, so it follows the skeleton's rule) | **⚠ silent** for anyone styling refetches through it |
16
+ | `#no-data` replaced the empty-state `<tr>` | fills the table's own full-width cell (`aria-colspan` counted, centred) | **behavior** — pass content, not a row |
17
+ | `<table>` / `<thead>` / `<tbody>` / `<tr>` / `<th>` / `<td>` markup _(v2 and v3 ≤ beta.5)_ | a CSS grid of `<div>`s carrying `role="table\|rowgroup\|row\|columnheader\|cell"`; `.bb-table__table` / `__head` / `__body` / `__foot` are the new containers, every `.bb-table*` class survives | **⚠ silent** for element selectors, `#thead`/`#tbody`/`#tfoot` markup, copy/paste and print — see below |
18
+ | `--fill` / `--natural-width` published on `.bb-table` _(v3 ≤ beta.5)_ | **removed** — nothing publishes them | **⚠ silent** — see below |
19
+ | — | props `enforceCoherence` (prunes row-keyed models after each load; incompatible with pagination-via-`dependencies`), `expandedItems` + `#expand`, `sort`, `keyboardNavigation`, `highlighted`, `rowClass`, `order` (v-model) + `reorderable`, `resizable` + `resize:column`, header-slot `hideColumn()` + `hide:column`, `fixedSelect` / `fixedActions` | additive |
20
+ | — | **column** fields `sortable?: boolean`, `rowClass?: ColumnClasses`, `hidden?: boolean` (the column stays declared — the way to hide, see § Do not override), `fixed?: 'left' \| 'right'` (replaces `fixedColumns`, see below) | additive, **collides** — see below |
21
+ | `actions?: boolean` prop _(v2 and v3 ≤ beta.20)_ | **removed** — the `#actions` slot creates the column by itself | **⚠ silent** if you passed the prop without the slot — see below |
22
+ | `fixedColumns` prop (rendered-slot indices, `[0, { index, position }]`) _(v2 and v3 ≤ beta.20)_ | **removed** — `fixed: 'left' \| 'right'` on the column definition, `fixed-select` / `fixed-actions` for the structural columns | **⚠ silent** — see below |
23
+ | column `snap?: number \| [number, number]` — gridline indices into the parent's rendered columns (`select` at 0, fractions, `-1` = data end) _(v3 ≤ beta.20)_ | `snap?: string \| [string, string]` — the PARENT's column key, or `'select'` / `'actions'`; a pair is inclusive; `'amount.5'` is a fraction. Same-key columns pair by default. Ignored (dev warning) under a parent that uses `order` / `reorderable` | **⚠ silent** — see below |
24
+ | `--table-{id}-track-{index}` published per data column _(v3 ≤ beta.20)_ | `--table-{id}-track-{key}` — the column key in its slot-name spelling (`address_city`); `select` / `actions` unchanged | **⚠ silent** for CSS that read the vars by index — see below |
25
+ | `useBbTableContext(id).total` _(v3 alphas ≤ 4)_ | `useBbTableContext(id).totalItems` | **hard rename** on the composable handle — see below |
26
+ | `@click:row` / `@contextmenu:row` / `@dblclick:row` handlers receive `(event, item, selected)` _(v2 and v3 ≤ beta.5)_ | `(event, item)` — `item` is now your raw record (was the internal row wrapper, record at `row.item`); the trailing `selected` boolean is gone | **payload slimmed** — see below |
27
+ | `--bb-table-*` custom properties | same names without the `bb-` prefix | **⚠ silent** for nested-table layouts — see below |
24
28
 
25
29
  ## Columns gained `sortable` and `rowClass` — check yours first
26
30
 
@@ -127,6 +131,113 @@ If you used `#loading` to show a custom refetch treatment, that role is gone —
127
131
  the built-in dim + progress bar covers refetches; `#loading` is now only the
128
132
  first-paint placeholder.
129
133
 
134
+ ### The `#actions` slot creates the actions column — the prop is gone (v3 beta, 2026-09-07)
135
+
136
+ `actions` used to be a boolean prop that rendered the column, with the
137
+ `#actions` slot filling it. The prop carried no information the slot did
138
+ not, and it had a footgun: a consumer who declared `{ key: 'actions' }` in
139
+ `columns` instead of setting the prop got a stray empty data cell and a slot
140
+ that never rendered. Now the slot alone creates the column — header cell
141
+ (labelled by `actions-text`, replaceable through `#header:actions`), one
142
+ cell per row, and the `actions` track for nested tables — and removing the
143
+ slot (or gating it with `v-if`) removes the column live.
144
+
145
+ ```diff
146
+ - <BbTable actions :columns="columns" :items="rows">
147
+ + <BbTable :columns="columns" :items="rows">
148
+ <template #actions="{ item }">…</template>
149
+ </BbTable>
150
+ ```
151
+
152
+ Delete the attribute. The removed-prop warner flags it in dev, the ESLint rule
153
+ flags it in-editor. The only silent case is a table that set `actions`
154
+ without ever providing the slot: it had an empty column, and now it does not.
155
+
156
+ ### `fixedColumns` → `fixed` on the column, `fixed-select` / `fixed-actions` (v3 beta, 2026-09-07)
157
+
158
+ Pins used to be **slot indices** into the rendered column list (a selection
159
+ column was index 0), like spreadsheet freeze panes: with `order` and
160
+ `reorderable` the pinned _slot_ held and whichever column landed in it was
161
+ pinned, which is fragile once users drag headers and hide columns. Pins are
162
+ now a property of the column, the way `width` and `hidden` are, and follow
163
+ it through reorders. The sticky behaviour itself is unchanged: in place, at
164
+ the column's own render position, offset by the pinned columns before it on
165
+ the same side.
166
+
167
+ ```diff
168
+ - <BbTable
169
+ - :columns="columns"
170
+ - :fixed-columns="[0, { index: columns.length + 1, position: 'right' }]"
171
+ - selectable
172
+ - >
173
+ + <BbTable :columns="columns" fixed-select fixed-actions selectable>
174
+ <template #actions="{ item }">…</template>
175
+ </BbTable>
176
+ ```
177
+
178
+ ```diff
179
+ const columns = [
180
+ - { key: 'name', label: 'Name' },
181
+ + { key: 'name', label: 'Name', fixed: 'left' },
182
+ { key: 'amount', label: 'Amount', align: 'right' },
183
+ ];
184
+ ```
185
+
186
+ Translate each index to the column that sat there: data columns get
187
+ `fixed: 'left' | 'right'` on their definition, the selection column becomes
188
+ `fixed-select`, the actions column `fixed-actions` (each has one sensible
189
+ side, so they are booleans). A nested table that pinned its own actions with
190
+ `{ index: childColumns.length, position: 'right' }` now says
191
+ `fixed-actions`. Silent on upgrade: the old prop is ignored (the warner and
192
+ the ESLint rule flag it), so a table that pinned columns stops pinning until
193
+ you move the pins onto the definitions.
194
+
195
+ ### `snap` names the parent column, not a gridline (v3 beta, 2026-09-07)
196
+
197
+ A nested table's `snap` was a number: a gridline index into the parent's
198
+ rendered columns, with the selection column at `0`, fractions for partial
199
+ tracks and `-1` for the end of the data region. Index `k` was whatever column
200
+ happened to render there, so a reorder or a hidden column on the parent
201
+ moved every snap. The parent now publishes its tracks under **column keys**,
202
+ and `snap` names the parent column:
203
+
204
+ ```diff
205
+ const holdings = [
206
+ - { key: 'name', label: 'Resource', snap: 1 },
207
+ - { key: 'kind', label: 'Type', snap: 2 },
208
+ - { key: 'monthlyCost', label: '$ / mo', align: 'right', snap: [3, 4.5] },
209
+ + { key: 'name', label: 'Resource', snap: 'name' },
210
+ + { key: 'kind', label: 'Type', snap: 'team' },
211
+ + { key: 'monthlyCost', label: '$ / mo', align: 'right', snap: ['region', 'monthly.5'] },
212
+ ];
213
+ ```
214
+
215
+ - Translate each index to the parent column that rendered there. The
216
+ selection column is `'select'`, the actions column `'actions'` — both are
217
+ targets by name, so `['name', 'actions']` runs a child column across the
218
+ parent's actions cell.
219
+ - A pair is **inclusive**: `['client', 'amount']` spans from the start of
220
+ `client` through the end of `amount` (the old `[a, b]` ended at the start
221
+ of `b`). A trailing `.digits` on a key is a fraction of that track. `-1`
222
+ is gone: the last column already runs to the end of the data region unless
223
+ it declares a `width` or a pair.
224
+ - A child column with no `snap` now pairs with the parent column of the
225
+ **same key** when there is one, so most children need no snaps at all; a
226
+ column the parent lacks (or hides) flows into the next free parent track.
227
+ - Under a parent that uses `order` or `reorderable`, snaps and key pairing
228
+ are ignored (dev builds warn) and the child inherits by position, as it
229
+ always did — a moving parent can only be followed slot by slot.
230
+
231
+ Silent on upgrade: a numeric `snap` fails the type check but is ignored at
232
+ runtime, so the child falls back to key pairing and flow, which for a child
233
+ sharing the parent's keys is the same alignment as before.
234
+
235
+ **The published track variables changed name with it.** `--table-{id}-track-{k}`
236
+ per data column became `--table-{id}-track-{key}`, the key in its slot-name
237
+ spelling (`address.city` → `address_city`); `select` and `actions` are
238
+ unchanged. Consumer CSS that read the numbered variables directly has to
239
+ switch to the keys. Nothing else about the bridge moved.
240
+
130
241
  ### `#no-data` is content, not a row
131
242
 
132
243
  v2 handed the slot the whole empty-state `<tr>`, so anything that was not a
@@ -547,8 +658,11 @@ grid actually raises is Chrome/Edge, 114 → 117 (123 for centred cells).
547
658
  - **Do not hide cells with `display: none`** (a `thClass`/`tdClass` of
548
659
  `hidden`, the v2/early-v3 narrow-screen recipe). A hidden grid item leaves
549
660
  its track in place: every following cell shifts one track over and the last
550
- track collapses. Hide a column by removing it from `columns` at the
551
- breakpoint, which removes the track.
661
+ track collapses. Hide a column with `hidden: true` on its definition at the
662
+ breakpoint, which removes the track while the column stays declared (so
663
+ its slot in `order` survives). Filtering it out of `columns` was the
664
+ interim advice and is now reserved for a column that does not exist for
665
+ this user at all.
552
666
  - **`contain: inline-size` on `.bb-table-expand__cell`,
553
667
  `.bb-table-no-data__cell` and `.bb-table-loading__cell`.** It is the only
554
668
  thing keeping a full-width cell's content from resizing the column tracks —
@@ -569,7 +683,7 @@ grid actually raises is Chrome/Edge, 114 → 117 (123 for centred cells).
569
683
 
570
684
  ### Small semantic and visual changes
571
685
 
572
- - **Pinned cells and the stuck header are opaque.** `fixed-columns` cells and
686
+ - **Pinned cells and the stuck header are opaque.** Pinned (`column.fixed`) cells and
573
687
  the `fixed-headers` band used to be transparent, so scrolled content showed
574
688
  through them. They now paint the table's surface local `--bg` (default
575
689
  `var(--bb-panel)`); a pinned cell also paints its row's hover/highlight
@@ -596,7 +596,6 @@ export function useApprovalsInbox(): ApprovalsInboxContext {
596
596
  v-model:highlighted="ctx.highlighted.value"
597
597
  v-model:select-all="ctx.table.all.value"
598
598
  v-model:unselected-items="ctx.table.unselected.value"
599
- actions
600
599
  caption="Expense approvals"
601
600
  :columns="columns"
602
601
  compact
@@ -826,7 +826,6 @@ export function useBacklogWorkspace(): BacklogWorkspaceContext {
826
826
  v-model:highlighted="highlightedIssue"
827
827
  v-model:select-all="ctx.table.all.value"
828
828
  v-model:unselected-items="ctx.table.unselected.value"
829
- actions
830
829
  caption="Sprint 24 backlog"
831
830
  :columns="columns"
832
831
  compact
@@ -63,10 +63,10 @@ OwnerPaginator::make(...), 'tabCounts' => [...], 'ownerOptions' => [...]])` —
63
63
  the page.
64
64
  - **The expand row is a real nested `BbTable` that inherits the parent's
65
65
  column tracks.** `inherit-column-widths` snaps the holdings table onto the
66
- owners table's grid; because the parent is `selectable`, every child
67
- column carries an explicit `snap` starting at `1` (`snap: 1…4`), so
68
- Resource sits exactly under Owner and the right-aligned $/mo columns share
69
- one edge. This is the recipe's signature move — the full defense is in
66
+ owners table's grid; every child column carries an explicit `snap` naming
67
+ the parent column it sits under (`snap: 'name'` `'monthly'` the
68
+ parent's `select` column is accounted for by name), so Resource sits
69
+ exactly under Owner and the right-aligned $/mo columns share one edge. This is the recipe's signature move — the full defense is in
70
70
  "Why it is built this way" and in the
71
71
  [BbTable guide](../../BbTable.md) ("Nested tables that align with the
72
72
  parent").
@@ -571,7 +571,6 @@ export function useOwnershipAtlas(): OwnershipAtlasContext {
571
571
  v-model:highlighted="highlightedOwner"
572
572
  v-model:select-all="ctx.table.all.value"
573
573
  v-model:unselected-items="ctx.table.unselected.value"
574
- actions
575
574
  actions-text="Holdings"
576
575
  caption="Resource owners"
577
576
  class="table-flush"
@@ -902,10 +901,10 @@ defineProps<{ owner: Owner }>();
902
901
  * the parent's first data column, and that inset is what reads as hierarchy.
903
902
  */
904
903
  const columns: Array<BbTableColumn<Holding>> = [
905
- { key: 'name', label: 'Resource', snap: 1 },
906
- { key: 'kind', label: 'Type', snap: 2 },
907
- { key: 'status', label: 'Status', snap: 3 },
908
- { key: 'monthlyCost', label: '$ / mo', align: 'right', snap: 4 },
904
+ { key: 'name', label: 'Resource', snap: 'name' },
905
+ { key: 'kind', label: 'Type', snap: 'team' },
906
+ { key: 'status', label: 'Status', snap: 'region' },
907
+ { key: 'monthlyCost', label: '$ / mo', align: 'right', snap: 'monthly' },
909
908
  ];
910
909
 
911
910
  const money = (value: number) =>
@@ -817,7 +817,6 @@ const ctx = useInvoiceForm();
817
817
  <!-- Inline-editing table (§6.3): every editable cell type is its own
818
818
  leaf. The amount column is virtual — computed from the row. -->
819
819
  <BbTable
820
- actions
821
820
  caption="Invoice line items"
822
821
  :columns="columns"
823
822
  compact
@@ -531,7 +531,6 @@ export function useInvoicesWorkspace(): InvoicesWorkspaceContext {
531
531
  v-model="ctx.table.selected.value"
532
532
  v-model:select-all="ctx.table.all.value"
533
533
  v-model:unselected-items="ctx.table.unselected.value"
534
- actions
535
534
  caption="Invoices"
536
535
  :columns="columns"
537
536
  compact
@@ -687,7 +687,6 @@ export function useUploadCenter(): UploadCenterContext {
687
687
  v-model="ctx.table.selected.value"
688
688
  v-model:select-all="ctx.table.all.value"
689
689
  v-model:unselected-items="ctx.table.unselected.value"
690
- actions
691
690
  caption="Expense receipts"
692
691
  :columns="columns"
693
692
  compact
@@ -567,7 +567,6 @@ export function useApprovalsInbox(): ApprovalsInboxContext {
567
567
 
568
568
  <BbTable
569
569
  id="approvals-table"
570
- actions
571
570
  caption="Expense approvals"
572
571
  :columns="columns"
573
572
  compact
@@ -804,7 +804,6 @@ export function useBacklogWorkspace(): BacklogWorkspaceContext {
804
804
  <BbTable
805
805
  id="backlog-table"
806
806
  v-model:highlighted="highlightedIssue"
807
- actions
808
807
  caption="Sprint 24 backlog"
809
808
  :columns="columns"
810
809
  compact
@@ -846,7 +846,6 @@ const ctx = useInvoiceForm();
846
846
  <!-- Inline-editing table (§6.3): every editable cell type is its own
847
847
  leaf. The amount column is virtual — computed from the row. -->
848
848
  <BbTable
849
- actions
850
849
  caption="Invoice line items"
851
850
  :columns="columns"
852
851
  compact
@@ -534,7 +534,6 @@ export function useInvoicesWorkspace(): InvoicesWorkspaceContext {
534
534
 
535
535
  <BbTable
536
536
  id="invoices-table"
537
- actions
538
537
  caption="Invoices"
539
538
  :columns="columns"
540
539
  compact
@@ -664,7 +664,6 @@ export function useUploadCenter(): UploadCenterContext {
664
664
 
665
665
  <BbTable
666
666
  id="receipts-table"
667
- actions
668
667
  caption="Expense receipts"
669
668
  :columns="columns"
670
669
  compact
@@ -502,7 +502,6 @@ export function useApprovalsInbox(): ApprovalsInboxContext {
502
502
 
503
503
  <BbTable
504
504
  id="approvals-table"
505
- actions
506
505
  caption="Expense approvals"
507
506
  :columns="columns"
508
507
  compact
@@ -706,7 +706,6 @@ export function useBacklogWorkspace(): BacklogWorkspaceContext {
706
706
  <BbTable
707
707
  id="backlog-table"
708
708
  v-model:highlighted="highlightedIssue"
709
- actions
710
709
  caption="Sprint 24 backlog"
711
710
  :columns="columns"
712
711
  compact
@@ -461,7 +461,6 @@ export function useInvoicesWorkspace(): InvoicesWorkspaceContext {
461
461
 
462
462
  <BbTable
463
463
  id="invoices-table"
464
- actions
465
464
  caption="Invoices"
466
465
  :columns="columns"
467
466
  compact