bitboss-ui 3.0.0-beta.61 → 3.0.0-beta.63

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 (108) hide show
  1. package/bin/bitboss-ui-mcp.mjs +103 -15
  2. package/bin/bitboss-ui.mjs +3 -1
  3. package/dist/ai/BbAlert.md +3 -0
  4. package/dist/ai/BbConfirm.md +46 -22
  5. package/dist/ai/BbForm.md +68 -5
  6. package/dist/ai/BbHost.md +11 -13
  7. package/dist/ai/BbPtScope.md +6 -4
  8. package/dist/ai/BbTable.md +180 -47
  9. package/dist/ai/BbToast.md +2 -1
  10. package/dist/ai/changelog.json +70 -4
  11. package/dist/ai/components.json +6 -6
  12. package/dist/ai/composables/useConfirm.md +8 -5
  13. package/dist/ai/composables/useToast.md +15 -15
  14. package/dist/ai/guides/agent-contract.md +3 -2
  15. package/dist/ai/guides/eslint-rules.md +2 -0
  16. package/dist/ai/guides/inertia-helpers.md +39 -7
  17. package/dist/ai/guides/installation-and-plugin-setup.md +2 -1
  18. package/dist/ai/guides/migration/components/bb-host.md +4 -2
  19. package/dist/ai/guides/migration/components/use-confirm.md +15 -15
  20. package/dist/ai/guides/migration/v2-to-v3.md +23 -0
  21. package/dist/ai/guides/migration/v3-beta-tooling.md +6 -0
  22. package/dist/ai/guides/passthrough.md +58 -58
  23. package/dist/ai/guides/validated-forms.md +8 -2
  24. package/dist/ai/helpers/inertia/manifest.json +18 -10
  25. package/dist/ai/helpers/inertia/useConfirmOnRouteLeave.ts +11 -2
  26. package/dist/ai/helpers/inertia/usePromiseForm.ts +17 -3
  27. package/dist/ai/helpers/inertia/useResourceContext.ts +1 -2
  28. package/dist/ai/manifest/components/BbHost.tree.json +1 -2
  29. package/dist/ai/manifest/components/BbTable.json +11 -7
  30. package/dist/ai/manifest/components/BbTable.tree.json +4 -1
  31. package/dist/ai/manifest/index.json +2 -2
  32. package/dist/ai/manifest/types.advanced.json +2 -2
  33. package/dist/ai/recipes/inertia/onboarding.md +10 -6
  34. package/dist/ai/recipes/inertia/record-form.md +6 -2
  35. package/dist/ai/recipes/inertia/wizard-form.md +9 -5
  36. package/dist/ai/source/BbConfirm.md +33 -21
  37. package/dist/ai/source/BbConfirmPortal.md +7 -16
  38. package/dist/ai/source/BbDatePickerInput.md +6 -1
  39. package/dist/ai/source/BbForm.md +7 -2
  40. package/dist/ai/source/BbHost.md +3 -3
  41. package/dist/ai/source/BbTable.md +440 -120
  42. package/dist/ai/source/BbTimePickerInput.md +5 -1
  43. package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +83 -74
  44. package/dist/components/BbConfirm/BbConfirmHost.d.ts +10 -0
  45. package/dist/components/BbConfirm/BbConfirmHost.js +32 -0
  46. package/dist/components/BbConfirm/BbConfirmPortal.vue_vue_type_script_setup_true_lang.js +8 -12
  47. package/dist/components/BbConfirm/confirmEntry.d.ts +4 -0
  48. package/dist/components/BbConfirm/confirmEntry.js +4 -0
  49. package/dist/components/BbConfirm/types.d.ts +2 -2
  50. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +211 -210
  51. package/dist/components/BbForm/BbForm.vue_vue_type_script_setup_true_lang.js +3 -1
  52. package/dist/components/BbHost/BbHost.vue_vue_type_script_setup_true_lang.js +2 -2
  53. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +1302 -1184
  54. package/dist/components/BbTable/BbTableDataRow.js +17 -12
  55. package/dist/components/BbTable/BbTableExpandRow.d.ts +1 -1
  56. package/dist/components/BbTable/BbTableExpandRow.js +29 -25
  57. package/dist/components/BbTable/BbTableSelectToggle.d.ts +1 -1
  58. package/dist/components/BbTable/types.d.ts +51 -18
  59. package/dist/components/BbTable/types.js +3 -0
  60. package/dist/components/BbTable/utils.d.ts +73 -13
  61. package/dist/components/BbTable/utils.js +148 -37
  62. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +108 -106
  63. package/dist/composables/useBbFormContext.d.ts +7 -3
  64. package/dist/composables/useConfirm.d.ts +327 -33
  65. package/dist/composables/useConfirm.js +93 -71
  66. package/dist/composables/useFormBinding.d.ts +5 -0
  67. package/dist/composables/useFormBinding.js +2 -1
  68. package/dist/i18n/locales/cs.json +1 -0
  69. package/dist/i18n/locales/da.json +1 -0
  70. package/dist/i18n/locales/de-CH.json +1 -0
  71. package/dist/i18n/locales/de.json +1 -0
  72. package/dist/i18n/locales/el.json +1 -0
  73. package/dist/i18n/locales/en-GB.json +1 -0
  74. package/dist/i18n/locales/en.json +1 -0
  75. package/dist/i18n/locales/es-MX.json +1 -0
  76. package/dist/i18n/locales/es.json +1 -0
  77. package/dist/i18n/locales/fi.json +1 -0
  78. package/dist/i18n/locales/fr.json +1 -0
  79. package/dist/i18n/locales/hu.json +1 -0
  80. package/dist/i18n/locales/it.json +1 -0
  81. package/dist/i18n/locales/ja.json +1 -0
  82. package/dist/i18n/locales/ko.json +1 -0
  83. package/dist/i18n/locales/nb.json +1 -0
  84. package/dist/i18n/locales/nl.json +1 -0
  85. package/dist/i18n/locales/pl.json +1 -0
  86. package/dist/i18n/locales/pt-BR.json +1 -0
  87. package/dist/i18n/locales/pt-PT.json +1 -0
  88. package/dist/i18n/locales/ro.json +1 -0
  89. package/dist/i18n/locales/ru.json +1 -0
  90. package/dist/i18n/locales/sv.json +1 -0
  91. package/dist/i18n/locales/tr.json +1 -0
  92. package/dist/i18n/locales/uk.json +1 -0
  93. package/dist/i18n/locales/zh-CN.json +1 -0
  94. package/dist/i18n/locales/zh-HK.json +1 -0
  95. package/dist/i18n/locales/zh-TW.json +1 -0
  96. package/dist/llms-full.txt +524 -212
  97. package/dist/llms-medium.txt +5 -3
  98. package/dist/locale-blueprint.json +1 -0
  99. package/dist/styles.css +2 -2
  100. package/dist/types/passthrough.d.ts +1 -1
  101. package/dist/types/ptComponentMap.d.ts +1 -1
  102. package/dist/utils/versionCheck.js +1 -1
  103. package/package.json +8 -4
  104. package/scripts/lib/doc-sections.mjs +26 -6
  105. package/scripts/lib/eslint-disable.mjs +2 -0
  106. package/scripts/lib/eslint-plugin.d.ts +2 -0
  107. package/scripts/lib/eslint-plugin.mjs +105 -0
  108. package/scripts/lib/validate-bb-markup.mjs +182 -66
@@ -78,7 +78,9 @@ needs is declared here, not in markup:
78
78
  even when the value is nullish, so a virtual column whose `key` matches no
79
79
  field can still compute from `item`; set `formatOnNull: false` when a
80
80
  formatter must only see real values.
81
- - **`placeholder`** fills the cell when the pipeline still ends nullish.
81
+ - **`placeholder`** fills the cell when the pipeline still ends nullish. Its
82
+ cell carries `--placeholder`, a cell with a value `--value`: style a blank
83
+ through those, not a slot ([Empty cells](#empty-cells-value-and-placeholder)).
82
84
  - **`align`** (`'left' | 'center' | 'right'`) per column; the table-level
83
85
  `align` prop sets the default for the whole table (`left`).
84
86
  - **`width`** freezes the column: numbers and bare numeric strings are px
@@ -88,11 +90,22 @@ needs is declared here, not in markup:
88
90
  (the table is a CSS grid — the tracks are the width API, there is no
89
91
  `table-layout`). An unfrozen text column also has a **floor estimated from
90
92
  what it shows**: short values (IDs, emails, dates) ask for one line, prose
91
- for about three, fitted to the room the sized columns leave — short values
92
- give way first and wrap, prose keeps at least half its width, and only then
93
- does the table scroll sideways. The header is always part of that floor, so
94
- a heading never clips. No floor on `fixed` or `virtual` tables, slot
95
- columns, or under a custom `#thead`. A grid keyword or function is the track as written:
93
+ for about three, fitted to the room the sized columns leave. Values a line
94
+ break would harm stay whole: a short name or status ("Poltrona 10", "In
95
+ attesa di verifica"), an amount with its currency, a range or pair ("0,00 € /
96
+ 450,00 €"), a date or code ("16/10/2026", a grouped IBAN). When room runs
97
+ short, longer values wrap first, then prose narrows, then a long heading of
98
+ several words takes two lines; a table that scrolls sideways anyway keeps
99
+ all of those whole. Only a table that would otherwise _start_ scrolling
100
+ wraps them, and long-form text never runs wider than a readable measure.
101
+ You rarely need `whitespace-nowrap` in a `td-class`. The header is always
102
+ part of that floor, so a heading never clips. (On a server-rendered page
103
+ the server cannot know the width: a table whose headings need two lines
104
+ takes them, once, when the page hydrates.) The floors read every row up
105
+ to 2,000; past that, the first 1,000 and a sample across the rest (a
106
+ `virtual` table has no floors at all). No floor on `fixed` or
107
+ `virtual` tables, slot columns, or under a custom `#thead`, so never slot a
108
+ column only to restyle its blank: use [`--placeholder`](#empty-cells-value-and-placeholder). A grid keyword or function is the track as written:
96
109
  `'auto'`, `'2fr'`, `'minmax(0, 1fr)'` (a share that may shrink below its
97
110
  content), `'fit-content(200px)'`. When _every_ column declares a length, the
98
111
  lengths become floors and the surplus is shared in proportion to their
@@ -354,7 +367,9 @@ not sure — write the name through the function instead of by hand:
354
367
  `#[slotKey('Qualità')]`, with `import { slotKey } from 'bitboss-ui'`; the same
355
368
  goes for slot names built in code (columns from config, a dynamic `<template>`
356
369
  loop). A hand copy that differs by one character makes the slot silently never
357
- match; `bitboss-ui check` catches it when `columns` is in the same file. Keep the column in `columns`
370
+ match; `bitboss-ui check` catches it when `columns` is in the same file, and
371
+ warns on a loop over keys it cannot see (`#[column.key]` over a prop) until
372
+ the name goes through `slotKey`. Keep the column in `columns`
358
373
  even when you fully slot it: label, alignment and width still come from there.
359
374
 
360
375
  Body cell scope: `content` (the formatted value), `item` (raw row), `value`
@@ -692,7 +707,9 @@ empty the table shows its empty row — localized default text, overridable via
692
707
  `empty-text` or fully via the `#empty` slot. That slot fills the table's own
693
708
  full-width empty-state cell (already centred, already spanning every column),
694
709
  so pass the content — a message, a "clear filters" button — not a row of your
695
- own.
710
+ own. On a table that scrolls sideways (wide columns at phone width) the
711
+ content centres in the visible part, not on the whole grid, and stays there
712
+ as the table scrolls.
696
713
 
697
714
  `loading-text` is the loading row's `aria-live` announcement. Like
698
715
  `empty-text` it has a **localized default** (`common.loadingText` —
@@ -1096,13 +1113,19 @@ scriptable from anywhere via `useBbTableContext`
1096
1113
  Mark columns `sortable` and bind `v-model:sort` — an ordered
1097
1114
  `BbTableSortEntry[]` (`[columnKey, 'asc' | 'desc']`). A click anywhere on a
1098
1115
  sortable header (its whitespace included — AG Grid parity; the built-in
1099
- button is the keyboard/AT control) cycles the column unsorted → asc → desc →
1100
- removed; a direction flip updates the entry in
1101
- place, so the column keeps its priority in a multi-column sort and other
1102
- entries are never touched. The array order **is** the priority: first entry
1103
- sorts first. The table only reports the model; **apply it to your data**
1104
- (client-side, as below) or forward it to the server as `orderBy` and refetch
1105
- via `dependencies`.
1116
+ button is the keyboard/AT control), or Enter on it, sorts by **that column
1117
+ alone**, cycling it unsorted → asc → desc → removed (`[]`).
1118
+
1119
+ **Shift+click** (or Shift+Enter) adds the column instead: it joins the end of
1120
+ the model, a direction flip updates its entry in place (it keeps its priority)
1121
+ and its third click removes it alone; the other entries are never touched. Set
1122
+ `multi-sort` when a plain click should add too. The array order **is** the
1123
+ priority: first entry sorts first. A several-entry model you write yourself
1124
+ is shown as is in either mode; the next plain click replaces it.
1125
+
1126
+ The table only reports the model; **apply it to your data** (client-side, as
1127
+ below) or forward it to the server as `orderBy` and refetch via
1128
+ `dependencies`.
1106
1129
 
1107
1130
  The affordance is one arrow: nothing at rest, a faint arrow previewed while
1108
1131
  the header is hovered or focused, solid once the column is sorted (up for
@@ -1114,9 +1137,9 @@ third click.
1114
1137
  ```vue
1115
1138
  <template>
1116
1139
  <div class="flex max-w-2xl flex-col gap-1.5">
1117
- <!-- Mark columns `sortable` and bind `v-model:sort`. The header buttons
1118
- cycle asc → desc → off; the table only REPORTS the model — you sort
1119
- the data. -->
1140
+ <!-- Mark columns `sortable` and bind `v-model:sort`. A click sorts by
1141
+ that column (asc → desc → off), Shift+click adds it; the table only
1142
+ REPORTS the model — you sort the data. -->
1120
1143
  <div
1121
1144
  class="overflow-hidden rounded-(--bb-radius) border border-(--bb-border)"
1122
1145
  >
@@ -1189,7 +1212,9 @@ const sortedInvoices = computed(() => {
1189
1212
 
1190
1213
  A custom `#<key>:header` slot replaces the built-in sort button, but its scope
1191
1214
  hands you `sortOrder` and `toggleSort` so your header can keep sorting alive
1192
- (render your own button and call `toggleSort`). To merely add something next
1215
+ (render your own button with `@click="toggleSort"`: given the event, Shift adds
1216
+ the column like on the built-in header; `toggleSort()` with no event sorts by
1217
+ that column alone). To merely add something next
1193
1218
  to the control — a resize handle, a filter icon — use
1194
1219
  `#<key>:header:prepend/append` instead and keep the built-in button (see
1195
1220
  "Custom cells and headers"). The column-level `sorted`
@@ -1211,7 +1236,8 @@ it **renders** in — an array of column keys, the same split as `sortable` +
1211
1236
  Grid style, a chip with the label follows), release to commit. The same
1212
1237
  header row is also one keyboard tab stop (the AG Grid grammar): ←/→ move
1213
1238
  focus between headers, **Shift+←/→** move the column one slot
1214
- (Shift+Home/End to the ends), Enter sorts a sortable header.
1239
+ (Shift+Home/End to the ends), Enter sorts a sortable header (Shift+Enter
1240
+ adds it to the sort).
1215
1241
  Each committed move writes the model once and is announced.
1216
1242
 
1217
1243
  The model's contract is what makes a column panel trivial:
@@ -2014,6 +2040,33 @@ fixed first track).
2014
2040
  The actions-column toggle remains valid — prefer it when expansion reveals
2015
2041
  _detail about the row_ rather than _children of it_.
2016
2042
 
2043
+ #### Styling the detail area
2044
+
2045
+ The cell that holds `#expand` (it spans every column) is the `expand`
2046
+ passthrough part. It has no padding of its own, so the slot's content sits
2047
+ flush; give the area a surface or an inset there:
2048
+
2049
+ ```vue
2050
+ <BbTable
2051
+ v-model:expanded-items="open"
2052
+ :columns="columns"
2053
+ :items="invoices"
2054
+ item-value="id"
2055
+ pt:expand="bg-(--bb-surface-hover) px-4 py-3"
2056
+ pt:expand:highlighted="bg-(--bb-muted)"
2057
+ >
2058
+ <template #expand="{ item }">…</template>
2059
+ </BbTable>
2060
+ ```
2061
+
2062
+ It takes the data row's `selected`, `highlighted` and `expanded` (always
2063
+ on), and, like `pt:row`, a
2064
+ per-item function: `:pt:expand="({ item }) => item.overdue && 'bg-red-50'"`.
2065
+ It is not a `cell`, so `pt:cell` never reaches it: a table that moves its row
2066
+ rules off the cells (`pt:cell="border-b-0"`, a top rule on `pt:row`) takes
2067
+ the expand cell's off too (`pt:expand="border-b-0"`), or the line under each
2068
+ open row is drawn twice.
2069
+
2017
2070
  ### Reordering rows
2018
2071
 
2019
2072
  `reorderable-rows` adds a slim drag handle at the start of every row: the user drags
@@ -2214,10 +2267,12 @@ keeps until that column's `width` changes:
2214
2267
  - A resized width is always px; `width` still accepts `'20%'` / `'12rem'`
2215
2268
  for what you declare yourself.
2216
2269
 
2217
- Headers never wrap. A column you have not sized is never narrower than its
2218
- own heading — the label is the table's size hint, so an empty or slim column
2219
- with a long heading still reads on a narrow screen (the table overflows
2220
- instead). Once a track is a length (a declared `width`, a user resize) the
2270
+ Headers stay on one line. A column you have not sized is never narrower
2271
+ than its own heading — the label is the table's size hint, so an empty or
2272
+ slim column with a long heading still reads on a narrow screen. The one
2273
+ exception: a long heading of several words ("Numero e data emissione") takes
2274
+ two lines when the alternative is the table scrolling sideways; short
2275
+ headings never do, and no `pt` class is needed for it. Once a track is a length (a declared `width`, a user resize) the
2221
2276
  label cannot widen it: squeezed, it ellipsizes and the sort arrow stays; at
2222
2277
  the narrowest only the arrow (and the handle) survive, and the column is
2223
2278
  still resizable. The data cells of a resized column clip with an ellipsis
@@ -3312,7 +3367,7 @@ const columns = [
3312
3367
  ];
3313
3368
  ```
3314
3369
 
3315
- Three rules:
3370
+ Four rules:
3316
3371
 
3317
3372
  - **The pin is the column's.** Under `order` / `reorderable` it travels with
3318
3373
  the cell, and a `hidden` pinned column pins nothing. The offset is still
@@ -3327,6 +3382,15 @@ Three rules:
3327
3382
  - **Nested tables pin against the ancestor scrollport.** A child that
3328
3383
  inherits the parent's tracks has no scrollport of its own, so both tables'
3329
3384
  pins land on the same visible edge (see Nested tables).
3385
+ - **Pinned cells sit at `z-index: 2`.** Body and header cells alike; under
3386
+ `fixed-headers` the stuck header band and its pinned headers sit at `3`,
3387
+ and so do the `fixed-first` / `fixed-last` rows; column resize handles sit
3388
+ at `1`. Cell content that sets its own `z-index` (a positioned badge with
3389
+ `z-10`, say) paints over the pinned column as it scrolls under it: keep
3390
+ such content at `z-index: 1` or lower, or put `isolate` on its wrapper so
3391
+ its layers stay inside it. The table root opens no stacking context of
3392
+ its own, so these values also count against the page: a sticky app bar
3393
+ that a scrolled table must pass under needs `z-index: 4` or more.
3330
3394
 
3331
3395
  ### Virtual rows
3332
3396
 
@@ -3468,6 +3532,60 @@ root (`--cue-w: 0px` turns it off):
3468
3532
  }
3469
3533
  ```
3470
3534
 
3535
+ #### Empty cells: `--value` and `--placeholder`
3536
+
3537
+ Every data cell says what the column's pipeline (`formatter`, then
3538
+ `placeholder`) produced: **`bb-table__cell--value`** when it shows a value,
3539
+ **`bb-table__cell--placeholder`** when the value was empty and the
3540
+ column's `placeholder` shows instead. The same two are `pt` states on
3541
+ `cell`. The class names the outcome, not the text, so a placeholder can read
3542
+ "–", "N/D" or anything else and the styling holds. A cell whose value is empty
3543
+ with no `placeholder` has neither. Slot cells carry it too (the class follows
3544
+ the `content` the slot is handed); the select, drag and actions cells never
3545
+ do.
3546
+
3547
+ ```vue
3548
+ <script setup lang="ts">
3549
+ import { BbTable, type BbTableColumn } from 'bitboss-ui';
3550
+
3551
+ type Person = { id: number; name: string; email: string | null; city: string };
3552
+
3553
+ const blank = (value: unknown) =>
3554
+ typeof value === 'string' && value.trim() === '' ? null : value;
3555
+
3556
+ const columns: Array<BbTableColumn<Person>> = [
3557
+ { key: 'name', label: 'Nome' },
3558
+ { key: 'email', label: 'Email', placeholder: 'N/D' },
3559
+ { key: 'city', label: 'Provincia', placeholder: '–', formatter: blank },
3560
+ ];
3561
+
3562
+ const people: Person[] = [
3563
+ { id: 1, name: 'Giulia Bianchi', email: null, city: 'Milano' },
3564
+ { id: 2, name: 'Marco Rossi', email: 'marco@example.com', city: ' ' },
3565
+ ];
3566
+ </script>
3567
+
3568
+ <template>
3569
+ <BbTable
3570
+ :columns="columns"
3571
+ item-value="id"
3572
+ :items="people"
3573
+ pt:cell:placeholder="text-(--bb-text-muted)"
3574
+ />
3575
+ </template>
3576
+ ```
3577
+
3578
+ Or in CSS, once for the whole app:
3579
+ `.bb-table__cell--placeholder { color: var(--bb-text-muted); }`.
3580
+
3581
+ - **The placeholder shows only for `null` / `undefined`.** An empty or
3582
+ whitespace-only string is a value. To treat it as blank, return `null` from
3583
+ the `formatter` (`blank` above), and the placeholder takes over.
3584
+ - **Don't slot a column just to restyle its blank.** A slot column gets no
3585
+ width floor (see Columns), so a wrapper that slots every column to print a
3586
+ muted dash costs every column its floor. `placeholder` plus the class does
3587
+ the same with none of that.
3588
+
3471
3589
  #### The table as a card
3472
3590
 
3473
3591
  The root paints no background, border or radius of its own — it is the
@@ -3517,7 +3635,7 @@ through element names, which match nothing:
3517
3635
  | header cell | `.bb-table__cell-column-header` (+ `--select`, `--actions`) | `columnheader` |
3518
3636
  | body group | `.bb-table__body` | `rowgroup` |
3519
3637
  | data row | `.bb-table__row` (+ `--highlighted`) | `row` |
3520
- | data cell | `.bb-table__cell` (+ `--select`) | `cell` (`gridcell` with `keyboard-navigation`) |
3638
+ | data cell | `.bb-table__cell` (+ `--select`; `--value` / `--placeholder`: what the pipeline produced) | `cell` (`gridcell` with `keyboard-navigation`) |
3521
3639
  | actions cell | `.bb-table__cell--actions` | `cell` (`gridcell` with `keyboard-navigation`) |
3522
3640
  | expand row | `.bb-table-expand__row` > `.bb-table-expand__cell` | `row` > `cell` (`gridcell`) |
3523
3641
  | empty state | `.bb-table-empty__row` > `.bb-table-empty__cell` | `row` > `cell` |
@@ -3652,21 +3770,25 @@ object form, a style and attributes — optionally only while a state is on.
3652
3770
  Parts: `root` (the container), `caption` (the visible caption), `table` (the
3653
3771
  grid), `head` / `body` / `foot` (the row groups), `header-row`,
3654
3772
  `cell-column-header` (every header cell — the select and actions ones
3655
- included, a broadcast), `row` (every DATA row, a broadcast) and `cell` (every
3656
- data cell — the select and actions cells included, a broadcast), and three
3657
- glyphs: `sort-icon`, `select-icon` (each row's checkbox or radio and the
3658
- select-all) and `drag-icon` (on the chip a column drag carries). The grid is
3659
- `div`s with ARIA roles, so these are parts, not `th` / `td` selectors.
3660
-
3661
- States: `loading` and `empty` (the table); `selected`, `highlighted`,
3773
+ included, a broadcast), `row` (every DATA row, a broadcast), `cell` (every
3774
+ data cell — the select and actions cells included, a broadcast), `expand`
3775
+ (the cell holding an open row's `#expand`), `first-row` / `last-row` (the
3776
+ edge rows), `drag-handle` (each row's grip under `reorderable-rows`), and
3777
+ three glyphs: `sort-icon`, `select-icon` (each row's checkbox or radio and
3778
+ the select-all) and `drag-icon` (on the chip a column drag carries). The grid
3779
+ is `div`s with ARIA roles, so these are parts, not `th` / `td` selectors.
3780
+
3781
+ States: `loading` and `empty` (the table); `value` / `placeholder` per data
3782
+ cell (what its pipeline produced, see [Empty cells](#empty-cells-value-and-placeholder)); `selected`, `highlighted`,
3662
3783
  `expanded` and `disabled` resolved per row — a cell takes its row's states,
3663
- and a row's `select-icon` its `selected` / `disabled`; `sorted` /
3784
+ a row's `select-icon` its `selected` / `disabled`, and its `expand` its
3785
+ `selected` / `highlighted` / `expanded`; `sorted` /
3664
3786
  `sorted-desc` per header, on its cell and its `sort-icon`; `indeterminate` on
3665
3787
  the select-all glyph. **`disabled` means the row cannot be selected**
3666
3788
  (`selectable` rejects it, or the table is `disabled`): the row still clicks,
3667
- highlights and expands, and a table without selection never has it. Expand
3668
- rows, the empty row and the skeleton are not parts — `pt:row:expanded` and
3669
- `pt:body:empty` cover the usual asks.
3789
+ highlights and expands, and a table without selection never has it. The
3790
+ empty row and the skeleton are not parts — `pt:body:empty` covers the usual
3791
+ ask.
3670
3792
 
3671
3793
  The selectable invoices table above, restyled through those parts: muted
3672
3794
  type on `pt:head`, weight on `pt:row:selected` (Globex is checked), a muted
@@ -3843,8 +3965,9 @@ A value that depends on the ITEM — a danger wash on overdue rows — is a
3843
3965
  **per-item function** on `pt:row`: `({ item, highlighted }) => …`, called
3844
3966
  with the row's item and every state as a boolean for it, its result merged after the
3845
3967
  state entries (so the overdue wash below steps aside while the row is
3846
- highlighted). `rowClass` still works and is computed once per row rather than
3847
- per render; `pt:cell` takes no function (`tdClass` is the per-cell hook).
3968
+ highlighted). `pt:expand` takes one the same way. `rowClass` still works and
3969
+ is computed once per row rather than per render; `pt:cell` takes no function
3970
+ (`tdClass` is the per-cell hook).
3848
3971
 
3849
3972
  **pt:row as a function — overdue invoices washed by status**
3850
3973
 
@@ -4048,7 +4171,13 @@ for a preset that fits any row type (never `<any>`), not an imported
4048
4171
  - Set `item-value` so selection, expansion and highlight track rows by stable
4049
4172
  identity rather than object reference.
4050
4173
  - Sortable headers set `aria-sort` and render real buttons; custom header
4051
- slots should keep a button calling `toggleSort`.
4174
+ slots should keep a button calling `toggleSort`. Enter sorts by the column
4175
+ alone and **Shift+Enter adds it** to a multi-column sort. The sortable
4176
+ header's accessible description says so (on the sort button, or on the
4177
+ header cell when the header row is a keyboard surface, after the
4178
+ reorder / resize instructions); with `multi-sort` there is no hint, since a
4179
+ plain Enter adds. A custom `#<key>:header` button carries no hint: describe
4180
+ it yourself if it should.
4052
4181
  - **The role follows the keyboard.** A plain table is `role="table"` with
4053
4182
  `cell`s. `keyboard-navigation` makes it `role="grid"` (the APG data grid:
4054
4183
  one tab stop, cell focus, `gridcell`s, `aria-selected` on selectable rows
@@ -4105,7 +4234,7 @@ for a preset that fits any row type (never `<any>`), not an imported
4105
4234
 
4106
4235
  | Prop | Type | Default | Required | Description |
4107
4236
  | --- | --- | --- | --- | --- |
4108
- | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `body`, `caption`, `cell`, `cellColumnHeader`, `dragHandle`, `dragIcon`, `firstRow`, `foot`, `head`, `headerRow`, `lastRow`, `root`, `row`, `selectIcon`, `sortIcon`, `table`. States: `disabled`, `empty`, `expanded`, `highlighted`, `indeterminate`, `loading`, `selected`, `sorted`, `sortedDesc`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. In `<script>` a `pt` value is typed `BbTableProps<Row>['pt']` (`BbTableProps['pt']` for a preset that fits any row type, never `<any>`) (`PtValue` is not exported): § Typing `pt` in `<script>`. |
4237
+ | `pt:<part>`, `pt:<part>:<state>`, `pt` | `PtValue` | | | Passthrough — a class list, or `{ class, style, attrs }`, bound to one named part of the component; the state form applies only while that state is on. Parts: `body`, `caption`, `cell`, `cellColumnHeader`, `dragHandle`, `dragIcon`, `expand`, `firstRow`, `foot`, `head`, `headerRow`, `lastRow`, `root`, `row`, `selectIcon`, `sortIcon`, `table`. States: `disabled`, `empty`, `expanded`, `highlighted`, `indeterminate`, `loading`, `placeholder`, `selected`, `sorted`, `sortedDesc`, `value`. What each one is: _Passthrough parts and states_ below. `pt` is the object form with the same keys minus the prefix (`{ icon: '…', 'icon:loading': '…' }`). See `guides/passthrough.md`. In `<script>` a `pt` value is typed `BbTableProps<Row>['pt']` (`BbTableProps['pt']` for a preset that fits any row type, never `<any>`) (`PtValue` is not exported): § Typing `pt` in `<script>`. |
4109
4238
  | `accessibleLabel` | `((columns: MappedCell[], item: any) => string) \| undefined` | | | The label of each row's selection checkbox, from `(columns, item)`. Unset: the row's name in the locale's select template (its `rowHeader` cells, else its first column: "Select INV-1041"). The cells arrive in render order: look one up by `key`, never by position. |
4110
4239
  | `actionsText` | `string \| undefined` | | | Label used in the header of the actions column. |
4111
4240
  | `align` | `"left" \| "center" \| "right" \| undefined` | `"left"` | | Text alignment of the columns. |
@@ -4142,6 +4271,7 @@ for a preset that fits any row type (never `<any>`), not an imported
4142
4271
  | `max` | `number \| undefined` | `Infinity` | | Maximum number of items that can be selected. |
4143
4272
  | `modelValue` | `any` | `[]` | | Used by v-model. Can be any serializable type. |
4144
4273
  | `multiple` | `boolean \| undefined` | `true` | | Allows the selection of multiple items. |
4274
+ | `multiSort` | `boolean \| undefined` | `false` | | A plain click on a sortable header adds the column to the sort, as Shift+click always does. Off, a plain click makes the sort that column alone. |
4145
4275
  | `name` | `string \| undefined` | | | Defines the name of the input. |
4146
4276
  | `order` | `string[] \| undefined` | `[]` | | Used by `v-model:order`: the column keys in render order. Columns it omits follow in declaration order; unknown keys stay put. A `reorderable` move writes it back complete. Works unbound, like `sort`; never holds `select` / `actions`. |
4147
4277
  | `page` | `string \| number \| undefined` | | | Current page (from 1), for the rows' `aria-rowindex` and a `BbPagination` paired through `id`. Doesn't slice `items`: pass the current page's rows. Numeric strings are coerced. |
@@ -4177,8 +4307,9 @@ for a preset that fits any row type (never `<any>`), not an imported
4177
4307
  | `cellColumnHeader` | Every column header cell (`role="columnheader"`), the select and actions headers included — a broadcast resolved per header. Not a `<th>`: the grid is `div`s with ARIA roles. |
4178
4308
  | `sortIcon` | A sortable column's sort arrow, resolved per header. |
4179
4309
  | `body` | The body row group. |
4180
- | `row` | Every DATA row — a broadcast resolved per row. Expand rows and the skeleton are not parts. Also takes a per-item function, called with the row's raw item, once per rendered row: keep it cheap. |
4310
+ | `row` | Every DATA row — a broadcast resolved per row. The skeleton is not a part, and an expand row is styled through `expand`. Also takes a per-item function, called with the row's raw item, once per rendered row: keep it cheap. |
4181
4311
  | `cell` | Every data cell, the select and actions cells included — a broadcast resolved with its row's states (`role="cell"`, `gridcell` under `keyboardNavigation`) — and the cells of the `firstRow` / `lastRow`, with no row state. Takes no function: use `tdClass` for a per-cell hook. |
4312
+ | `expand` | The cell under an open row that holds the `#expand` slot and spans every column — resolved with its data row's `selected`, `highlighted` and `expanded` (always on: the cell exists only while its row is open). Not a `cell`, so `pt:cell` never reaches it: a rule taken off the cells (`pt:cell="border-b-0"`) needs `pt:expand="border-b-0"` too. Also takes a per-item function, called with the open row's raw item. |
4182
4313
  | `firstRow` | The row of `#<key>:first` cells, before the data rows. |
4183
4314
  | `lastRow` | The row of `#<key>:last` cells, after the data rows (a totals row: `pt:lastRow="font-semibold"`). |
4184
4315
  | `selectIcon` | The selection glyph: each row's checkbox (radio with single selection) and the select-all checkbox — resolved per node. Not rendered when the `select` / `select:header` slots replace them. |
@@ -4190,14 +4321,16 @@ States are listed in precedence order: when two are on at once and their entries
4190
4321
 
4191
4322
  | State | When it is on |
4192
4323
  | --- | --- |
4324
+ | `value` | Per node, on a DATA `cell`: the formatter / placeholder pipeline produced a value (not `null` / `undefined`), slot cells included (`bb-table__cell--value`). Not on the select, drag or actions cells. |
4325
+ | `placeholder` | Per node, on a DATA `cell`: the value was empty and the column's `placeholder` shows instead, whatever its text (`bb-table__cell--placeholder`). A cell with an empty value and no `placeholder` has neither state. |
4193
4326
  | `empty` | Component-wide: there are no rows (`bb-table--empty`). |
4194
4327
  | `sorted` | Per node, on `cellColumnHeader` and `sortIcon`: the column is sorted, either direction (`bb-table__cell-column-header--sorted`). |
4195
4328
  | `sortedDesc` | Per node, on `cellColumnHeader` and `sortIcon`: sorted descending (`bb-table__cell-column-header--sorted-desc`). Ascending is `sorted` alone. |
4196
- | `selected` | Per node, on `row`, its cells and its `selectIcon`: a checked row; on the select-all glyph, every row is selected. |
4329
+ | `selected` | Per node, on `row`, its cells, its `expand` and its `selectIcon`: a checked row; on the select-all glyph, every row is selected. |
4197
4330
  | `indeterminate` | Per node, on the select-all `selectIcon`: some rows are selected, not all. |
4198
4331
  | `disabled` | Per node, on `row`, its cells and its `selectIcon`: the row cannot be SELECTED — `selectable` rejects it, or the table is `disabled` (`bb-table__row--disabled`). It still clicks, highlights and expands. Never on a table without selection. The select-all glyph takes it from the table's `disabled`. |
4199
- | `expanded` | Per node, on `row` and its cells: the row's expand row is open (`bb-table__row--expanded`). |
4200
- | `highlighted` | Per node, on `row` and its cells: the highlighted row. |
4332
+ | `expanded` | Per node, on `row`, its cells and its `expand`: the row's expand row is open (`bb-table__row--expanded`). |
4333
+ | `highlighted` | Per node, on `row`, its cells and its `expand`: the highlighted row. |
4201
4334
  | `loading` | Component-wide: the table is `loading`. |
4202
4335
 
4203
4336
  ## Events
@@ -4224,8 +4357,8 @@ States are listed in precedence order: when two are on at once and their entries
4224
4357
  - `actions:header` — scope: `{ text: string; }` — Replaces the default actions header cell content.
4225
4358
  - `empty` — scope: `{ error: unknown; }` — Content shown when there are no rows and nothing is loading, inside the table's own full-width cell: pass content, never a row. `error` is what the latest failed load threw (`null` when none).
4226
4359
  - `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.
4227
- - `header:append` — scope: `BbTableHeaderAffixSlotProps<Item>` `{ classes?: Classes; columnKey: string; items?: Item[]; label?: string; sortable?: boolean; sortOrder?: "asc" \| "desc" \| null; toggleSort?: (() => void); hideColumn?: (() => void) }` — Content after every data column's header content (the label + sort control, or its own `<key>:header` replacement). A column's `<key>:header:append` wins over this for that column; they never stack. Branch on `columnKey` for per-column output.
4228
- - `header:prepend` — scope: `BbTableHeaderAffixSlotProps<Item>` `{ classes?: Classes; columnKey: string; items?: Item[]; label?: string; sortable?: boolean; sortOrder?: "asc" \| "desc" \| null; toggleSort?: (() => void); hideColumn?: (() => void) }` — Content before every data column's header content (the label + sort control, or its own `<key>:header` replacement). A column's `<key>:header:prepend` wins over this for that column; they never stack. Branch on `columnKey` for per-column output.
4360
+ - `header:append` — scope: `BbTableHeaderAffixSlotProps<Item>` `{ classes?: Classes; columnKey: string; items?: Item[]; label?: string; sortable?: boolean; sortOrder?: "asc" \| "desc" \| null; toggleSort?: ((event?: MouseEvent \| KeyboardEvent \| undefined) => void); hideColumn?: (() => void) }` — Content after every data column's header content (the label + sort control, or its own `<key>:header` replacement). A column's `<key>:header:append` wins over this for that column; they never stack. Branch on `columnKey` for per-column output.
4361
+ - `header:prepend` — scope: `BbTableHeaderAffixSlotProps<Item>` `{ classes?: Classes; columnKey: string; items?: Item[]; label?: string; sortable?: boolean; sortOrder?: "asc" \| "desc" \| null; toggleSort?: ((event?: MouseEvent \| KeyboardEvent \| undefined) => void); hideColumn?: (() => void) }` — Content before every data column's header content (the label + sort control, or its own `<key>:header` replacement). A column's `<key>:header:prepend` wins over this for that column; they never stack. Branch on `columnKey` for per-column output.
4229
4362
  - `loading` — scope: `{ items: Item[]; }` — Content shown while table rows are loading (replaces the skeleton rows).
4230
4363
  - `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.
4231
4364
  - `select:header` — scope: `{ multiple: boolean; selectAll: boolean; text: string; }` — Replaces the default selection header cell content (checkbox or radio label).
@@ -191,7 +191,8 @@ Three things worth internalising:
191
191
 
192
192
  #### Variant icons
193
193
 
194
- The object form of the plugin's `toastVariants` sets each variant's icon for
194
+ A default icon per variant, set once: the object form of the plugin's
195
+ `toastVariants` option sets each variant's icon for
195
196
  the whole app. A toast without its own `icon` uses its variant's; its own
196
197
  `icon` wins; `hideIcon: true` hides either (and does nothing when there is no
197
198
  icon). The loading spinner still shows with `hideIcon`: it is state, not
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 3,
3
3
  "library": "bitboss-ui",
4
- "version": "3.0.0-beta.61",
4
+ "version": "3.0.0-beta.63",
5
5
  "upgrade": "v2-to-v3",
6
6
  "guide": "ai/guides/migration/v2-to-v3.md",
7
7
  "legend": {
@@ -71,7 +71,9 @@
71
71
  "3.0.0-beta.58",
72
72
  "3.0.0-beta.59",
73
73
  "3.0.0-beta.60",
74
- "3.0.0-beta.61"
74
+ "3.0.0-beta.61",
75
+ "3.0.0-beta.62",
76
+ "3.0.0-beta.63"
75
77
  ],
76
78
  "unpublished": [
77
79
  "3.0.0-alpha.2",
@@ -94,7 +96,7 @@
94
96
  ],
95
97
  "summary": {
96
98
  "renames": 220,
97
- "behaviourBreaks": 228,
99
+ "behaviourBreaks": 231,
98
100
  "bySurface": {
99
101
  "prop": 15,
100
102
  "option": 1,
@@ -105,7 +107,7 @@
105
107
  "class": 138
106
108
  },
107
109
  "unreleased": 0,
108
- "added": 409
110
+ "added": 416
109
111
  },
110
112
  "added": {
111
113
  "3.0.0-alpha.1": [
@@ -2067,6 +2069,33 @@
2067
2069
  "BbSelectPopover"
2068
2070
  ]
2069
2071
  }
2072
+ ],
2073
+ "3.0.0-beta.62": [
2074
+ {
2075
+ "text": "bbtable · pt:expand part for the expand cell",
2076
+ "components": [
2077
+ "BbTable"
2078
+ ]
2079
+ },
2080
+ "table · single sort by default, shift+click adds",
2081
+ {
2082
+ "text": "check · warn on computed slot names that skip slotKey",
2083
+ "components": [
2084
+ "BbTable"
2085
+ ]
2086
+ },
2087
+ "table · keep short multi-word values on one line",
2088
+ "table · sortable headers describe shift+enter"
2089
+ ],
2090
+ "3.0.0-beta.63": [
2091
+ "table · cells name what the pipeline produced, value or placeholder",
2092
+ {
2093
+ "text": "confirm · every confirm() call opens its own dialog",
2094
+ "components": [
2095
+ "BbHost",
2096
+ "BbPtScope"
2097
+ ]
2098
+ }
2070
2099
  ]
2071
2100
  },
2072
2101
  "breaking": [
@@ -8445,6 +8474,43 @@
8445
8474
  "ruling": "owner 2026-10-16 (Q75.1 R7 reading b, R2, G4, G5, G7)",
8446
8475
  "guide": "v2-to-v3.md § 6 item 94 + BbSelect.md § Rows the form already holds",
8447
8476
  "firstReleasedIn": "3.0.0-beta.59"
8477
+ },
8478
+ {
8479
+ "id": "f368",
8480
+ "kind": "behaviour",
8481
+ "summary": "BbTable: a plain header click sorts by that column alone; Shift+click adds a column; multi-sort makes a plain click add",
8482
+ "components": [
8483
+ "BbTable"
8484
+ ],
8485
+ "description": "BbTable sorts by ONE column by default: a plain click (or Enter / Space) on a sortable header makes v-model:sort that column alone at its next state (unsorted → asc → desc → []); Shift+click / Shift+Enter adds the column or cycles it in place, leaving the other entries (a plain click's old behaviour). multi-sort (new, default false) makes a plain click add too. The model's shape is unchanged and a several-entry model written from outside is shown as is (the next plain click replaces it). The header slots' toggleSort takes the event (@click=\"toggleSort\" gets Shift); toggleSort() sorts single. Tables that want click-adds set multi-sort",
8486
+ "ruling": "owner 2026-10-21 (Q76.9 b)",
8487
+ "guide": "v2-to-v3.md § 6 item 95 + BbTable.md § Sorting",
8488
+ "firstReleasedIn": "3.0.0-beta.62"
8489
+ },
8490
+ {
8491
+ "id": "f369",
8492
+ "kind": "behaviour",
8493
+ "summary": "BbTable: unsized text columns keep names, statuses, amounts, ranges, dates and codes on one line; a long heading may take two lines instead of a sideways scroll",
8494
+ "components": [
8495
+ "BbTable"
8496
+ ],
8497
+ "description": "BbTable: an unsized text column keeps the values a line break would harm on ONE line (short names and statuses, an amount with its currency, a pair or range, a spelled-out date, a code or IBAN; voted by a five-lens panel on a 392-value corpus, widthCorpus.*.json), and a table that scrolls sideways anyway no longer squeezes them. A table that fitted before still fits: only then do those values give way. A long heading of several words takes two lines when the alternative is a sideways scroll (--wrap, Q76.9 e); short headings never wrap, and the pt:cell-column-header=\"whitespace-normal\" workaround is no longer needed. The floors are converted at the body text's size (they used the table root's 16px, ~14% wide), and structural columns (drag handle, selection, actions) no longer count as room. Unsized columns change width (wider for units, narrower otherwise)",
8498
+ "ruling": "owner 2026-10-16 (Q75.3) + 2026-10-21 (Q76.9 e)",
8499
+ "guide": "v2-to-v3.md § 6 item 96 + BbTable.md § Columns, § Column resize",
8500
+ "firstReleasedIn": "3.0.0-beta.62"
8501
+ },
8502
+ {
8503
+ "id": "f370",
8504
+ "kind": "behaviour",
8505
+ "summary": "confirm(): every call opens its own dialog; a new one covers the open one instead of resolving it false; it takes the caller's pt scope; new stack option",
8506
+ "components": [
8507
+ "BbConfirm",
8508
+ "BbHost"
8509
+ ],
8510
+ "description": "confirm(): every call opens its own dialog, under the caller's provides. A call while another confirm is open COVERS it (a shared confirm BbDialog stack) instead of resolving it false: the first stays pending and comes back when the newer one closes, so two un-awaited calls ask both, newest first; Escape and the backdrop answer only the top one. close() hides the newest open confirm opened through the same useConfirm() (it used to hide THE confirm); dismissAll() dismisses every open one. The dialog takes the <BbPtScope> and tokens of the component that called useConfirm() (a store's: the host's), so areas need no host of their own for confirms; a component portal injects from the caller's tree. New message option stack (join a dialog's stack: the confirm replaces its view)",
8511
+ "ruling": "owner 2026-10-22 (Q75.9)",
8512
+ "guide": "v2-to-v3.md § 6 item 97 + components/use-confirm.md + components/bb-host.md + BbConfirm.md § Several confirms at once + useConfirm.md",
8513
+ "firstReleasedIn": "3.0.0-beta.63"
8448
8514
  }
8449
8515
  ]
8450
8516
  }