bitboss-ui 3.0.0-beta.0 → 3.0.0-beta.2

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 (35) hide show
  1. package/bin/bitboss-ui.mjs +133 -12
  2. package/dist/ai/changelog.json +1 -1
  3. package/dist/ai/components.json +2 -2
  4. package/dist/ai/guides/ai-router.md +2 -2
  5. package/dist/ai/guides/design-tokens.md +46 -6
  6. package/dist/ai/guides/installation-and-plugin-setup.md +71 -5
  7. package/dist/ai/guides/migration/components/bb-alert.md +37 -0
  8. package/dist/ai/guides/migration/components/bb-avatar.md +47 -8
  9. package/dist/ai/guides/migration/components/bb-badge.md +23 -1
  10. package/dist/ai/guides/migration/components/bb-button.md +64 -0
  11. package/dist/ai/guides/migration/components/bb-checkbox-group.md +55 -1
  12. package/dist/ai/guides/migration/components/bb-date-picker-input.md +9 -2
  13. package/dist/ai/guides/migration/components/bb-dialog.md +121 -11
  14. package/dist/ai/guides/migration/components/bb-icon.md +42 -0
  15. package/dist/ai/guides/migration/components/bb-offcanvas.md +35 -1
  16. package/dist/ai/guides/migration/components/bb-rating.md +52 -1
  17. package/dist/ai/guides/migration/components/bb-select.md +48 -0
  18. package/dist/ai/guides/migration/components/bb-table.md +156 -10
  19. package/dist/ai/guides/migration/components/bb-tabs.md +79 -1
  20. package/dist/ai/guides/migration/components/bb-text-input.md +23 -1
  21. package/dist/ai/guides/migration/components/bb-toast.md +44 -10
  22. package/dist/ai/guides/migration/components/use-confirm.md +48 -13
  23. package/dist/ai/guides/migration/v2-to-v3.md +626 -108
  24. package/dist/ai/index.md +9 -9
  25. package/dist/ai/source/BbDialog.md +0 -3
  26. package/dist/ai/source/BbDropdown.md +24 -1
  27. package/dist/ai/source/BbDropdownGroup.md +24 -1
  28. package/dist/index.d.ts +2 -1
  29. package/dist/llms-full.txt +1814 -367
  30. package/dist/llms-medium.txt +82 -16
  31. package/dist/llms.txt +11 -11
  32. package/dist/styles.css +1 -1
  33. package/llms.txt +12 -12
  34. package/package.json +2 -1
  35. package/scripts/lib/validate-bb-markup.mjs +105 -17
@@ -1,21 +1,103 @@
1
1
  ---
2
2
  title: 'Migration v2→v3: BbTable'
3
- summary: 'allowSelectAll inverted to disableSelectAll; loading no longer blanks populated tables and now inerts rows; #loading is first-load-only; #no-data fills a full-width table cell instead of replacing the row.'
3
+ summary: 'allowSelectAll inverted to disableSelectAll; loading no longer blanks populated tables and now inerts rows; #loading is first-load-only; #no-data fills a full-width table cell instead of replacing the row; columns gained sortable + rowClass, which collide with same-named consumer fields.'
4
4
  ---
5
5
 
6
6
  # BbTable — v2 → v3
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 `<td>` (colspan counted, centred) | **behavior** — pass content, not a row |
17
- | — | `enforceCoherence` (prunes row-keyed models after each load; incompatible with pagination-via-`dependencies`), `expandedItems` + `#expand`, `sort`, `keyboardNavigation`, `highlighted`, `rowClass` | additive |
18
- | `useBbTableContext(id).total` _(v3 alphas ≤ 4)_ | `useBbTableContext(id).totalItems` | **hard rename** on the composable handle — 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 `<td>` (colspan counted, centred) | **behavior** — pass content, not a row |
17
+ | — | props `enforceCoherence` (prunes row-keyed models after each load; incompatible with pagination-via-`dependencies`), `expandedItems` + `#expand`, `sort`, `keyboardNavigation`, `highlighted`, `rowClass` | additive |
18
+ | — | **column** fields `sortable?: boolean`, `rowClass?: ColumnClasses` | additive, **collides** — see below |
19
+ | `useBbTableContext(id).total` _(v3 alphas ≤ 4)_ | `useBbTableContext(id).totalItems` | **hard rename** on the composable handle — see below |
20
+ | `--bb-table-*` custom properties | same names without the `bb-` prefix | **⚠ silent** for nested-table layouts — see below |
21
+
22
+ ## Columns gained `sortable` and `rowClass` — check yours first
23
+
24
+ v2's `BbTableColumn` had neither field (`grep sortable` over the whole 2.1.135
25
+ `BbTable/types.d.ts` exits empty), and extra fields on a column object were
26
+ inert — so apps parked their own data there. A backend sort key named
27
+ `sortable`. A per-column class named `rowClass`. Both are now real library
28
+ fields, and both break in two directions at once: the **type** stops compiling
29
+ and the **behaviour** silently changes.
30
+
31
+ ### The type collision
32
+
33
+ Your column type is almost certainly an intersection:
34
+
35
+ ```ts
36
+ type Column = BbTableColumn<Patient> & { sortable?: string }; // backend sort key
37
+ ```
38
+
39
+ The intersection makes `sortable` `boolean & string` — i.e. `never`, whose
40
+ optional form TypeScript reports as `undefined`. Every column literal in your
41
+ app fails, and the message never mentions `BbTableColumn`:
42
+
43
+ ```
44
+ error TS2322: Type 'string' is not assignable to type 'undefined'.
45
+ ```
46
+
47
+ If your field is **required** (`{ sortable: string }`, no `?`) the whole object
48
+ type collapses to `never` and you get one error per property per literal:
49
+
50
+ ```
51
+ error TS2322: Type '"id"' is not assignable to type 'never'.
52
+ error TS2322: Type 'string' is not assignable to type 'never'.
53
+ ```
54
+
55
+ That fan-out is why a single shared column type produced 166 errors in one real
56
+ migration. Fix it at the type, not at the call sites:
57
+
58
+ ```diff
59
+ - type Column = BbTableColumn<Patient> & { sortable?: string };
60
+ + type Column = BbTableColumn<Patient> & { sortKey?: string };
61
+
62
+ // or keep the name and take the field off the library type
63
+ + type Column = Omit<BbTableColumn<Patient>, 'sortable'> & { sortable?: string };
64
+ ```
65
+
66
+ `Omit` compiles, but read the next section before choosing it.
67
+
68
+ ### The behaviour half — a truthy `sortable` turns on the built-in sort UI
69
+
70
+ `BbTable` coerces the field (`sortable: !!column.sortable` in `BbTable.vue`), so
71
+ **any** truthy value counts — including a sort-key string like
72
+ `'patients.created_at'`. The header stops rendering a plain label and renders a
73
+ `.bb-table-sort` button instead, and the `<th>` gains `aria-sort="none"`. An app
74
+ that already ships its own sortable-header UI now draws two.
75
+
76
+ The damage is bounded: `sort` is a controlled model (`v-model:sort`), so the
77
+ extra button emits and does nothing else — it cannot reorder your rows behind
78
+ your back. But it is visible, focusable, and announced.
79
+
80
+ So `Omit` silences the compiler and keeps the bug. Rename the field, or strip
81
+ it at the `BbTable` boundary:
82
+
83
+ ```ts
84
+ const tableColumns = computed(() =>
85
+ columns.value.map(({ sortable, ...rest }) => rest)
86
+ );
87
+ ```
88
+
89
+ Column `rowClass` is the same shape of trap without the type error, because
90
+ `ColumnClasses` already accepts `string`: a leftover `rowClass: 'w-40'` you
91
+ meant for the column typechecks fine and is now merged onto the whole row's
92
+ `<tr>`, alongside the table-level `rowClass` prop. A non-`Classes` shape
93
+ (`rowClass?: { colspan: number }`) does error, with the same `undefined`
94
+ wording.
95
+
96
+ Grep both before you start:
97
+
98
+ ```sh
99
+ rg -n 'sortable|rowClass' --type ts --type vue # keep the hits on column objects
100
+ ```
19
101
 
20
102
  ## Edits
21
103
 
@@ -101,3 +183,67 @@ unrelated and does not change.
101
183
 
102
184
  `BbPagination` consumers see the same rename through `table-id`; see
103
185
  [BbPagination](./bb-pagination.md).
186
+
187
+ ## The custom properties the table _publishes_ lost the `bb-` prefix
188
+
189
+ These are not theming inputs — they are values `BbTable` measures at runtime and
190
+ writes onto its own DOM, for the table's CSS and for descendants to read. You
191
+ reach for them for exactly one reason: aligning a nested table's columns to its
192
+ parent's. They all dropped the prefix.
193
+
194
+ | v2 | v3 |
195
+ | ------------------------------------------- | ------------------------------------------- |
196
+ | `--bb-table-{id}-track-{key}` | `--table-{id}-track-{key}` |
197
+ | `--bb-table-offset-start` / `-end` | `--offset-start` / `--offset-end` |
198
+ | `--bb-table-offset-internal-start` / `-end` | `--offset-internal-start` / `-internal-end` |
199
+ | `--bb-table-offset-external-start` / `-end` | `--offset-external-start` / `-external-end` |
200
+ | `--bb-table-fill` | `--fill` |
201
+ | `--bb-table-natural-width` | `--natural-width` |
202
+ | `--bb-table-cell-h` | `--cell-h` |
203
+
204
+ Only the track bridge keeps a `table-` segment, because it crosses component
205
+ boundaries; the rest are plain locals on `.bb-table`. `--padding-x`,
206
+ `--padding-y` and `--actions-spacing` were already unprefixed in v2 and keep
207
+ their names (their _values_ tightened: `16px`/`8px` → `12px`/`6px`, and the cell
208
+ height `42px` → `36px`).
209
+
210
+ **Every one of these fails silently.** They are read as
211
+ `var(--offset-internal-start)` with a fallback, so a v2 override lands on a name
212
+ nothing consumes and the layout quietly falls back to its default. The worst
213
+ case is code that _scans_ for them — a nested-table helper matching
214
+ `/--bb-table-[\w-]+-track/` over `element.style` matches nothing at all, no
215
+ error, just columns that no longer line up:
216
+
217
+ ```diff
218
+ - const track = /--bb-table-[\w-]+-track/;
219
+ + const track = /--table-[\w-]+-track/;
220
+ ```
221
+
222
+ Grep your app CSS and any style-scanning JS for `--bb-table-` — no name with
223
+ that prefix exists in v3.
224
+
225
+ The table is not the only component this happened to. Four more published
226
+ variables lost the prefix the same way: `--bb-select-popover-width` → `--w` on
227
+ `.bb-select-popover` (and the supported path is now the `width` prop, not a CSS
228
+ override), `--bb-icon-dimensions-w` → `--w` on `.bb-icon`,
229
+ `--bb-breadcrumbs-flex-basis` / `-flex-grow` → `--flex-basis` / `--flex-grow`,
230
+ and `--bb-picker-preview-color` → `--picker-preview-color`. Widen the grep
231
+ accordingly: `--bb-select-popover-`, `--bb-icon-dimensions-`, `--bb-breadcrumbs-`,
232
+ `--bb-picker-`. See
233
+ [design-tokens.md](../../design-tokens.md#migration-from-the-old-tokens) for the
234
+ theming-token side of the same rename.
235
+
236
+ ## `caption` names the selection fieldset when `legend` is absent
237
+
238
+ A `selectable` table wraps itself in a `<fieldset>` and needs an accessible
239
+ name. v3 resolves it as `legend`, then `caption`, then a generic localized
240
+ string — so a table that already has a `caption` is correctly named and does not
241
+ need a `legend`.
242
+
243
+ `npx bitboss-ui check` (and the ESLint rule behind it) still asks for `legend`
244
+ specifically, so `<BbTable selectable caption="Patients" />` fails the check
245
+ even though its fieldset is named. That one is a **quality nudge, not a broken
246
+ affordance** — unlike the other partner-prop pairs, ignoring it does not leave
247
+ anything silently doing nothing. Pass a `legend` when it says something the
248
+ caption does not ("select invoices to export" vs "Invoices"); otherwise suppress
249
+ the rule at the call site rather than duplicating the caption into a `<legend>`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: 'Migration v2→v3: BbTabs (was BbTab)'
3
- summary: Component renamed; querykey→queryKey; animations default on; label slots renamed and all dynamic names/URL slugs normalized.
3
+ summary: Component renamed; querykey→queryKey; animations default on; label slots renamed and all dynamic names/URL slugs normalized; the entire .bb-tab BEM tree renamed to .bb-tabs / .bb-tabs-list / .bb-tabs-panes.
4
4
  ---
5
5
 
6
6
  # BbTabs — v2 → v3
@@ -19,6 +19,8 @@ aliases survive as deprecated re-exports, the tag name does not).
19
19
  | pane slot `#<key>` (raw) | `#<slotKey(key)>` | normalization |
20
20
  | URL value = raw key | URL value = normalized slug | **⚠ breaking for deep links** |
21
21
  | tab/panel DOM ids from raw key | normalized (also fixes invalid ids for spaced keys) | check E2E selectors |
22
+ | `.bb-tab*` CSS | `.bb-tabs*` / `.bb-tabs-list*` / `.bb-tabs-panes` | **⚠ silent** — the whole BEM tree renamed, see below |
23
+ | — | `block` (full-width, equal-width triggers) | additive — replaces hand-rolled full-width classes |
22
24
 
23
25
  `slotKey(key)` lowercases and replaces every non-word run with `_`:
24
26
  `"In Review"` → `in_review`, `"2024-03-15"` → `2024_03_15`. Keys that are
@@ -45,3 +47,79 @@ still selects that tab; only slots, ids, and the URL use the slug.
45
47
  the normalized slug (`?section=in_review`, not `?section=In%20Review`).
46
48
  Existing bookmarks with raw values fall back to the default tab — add a
47
49
  redirect if those links matter.
50
+
51
+ ## CSS: every class renamed with the component
52
+
53
+ The component rename took the whole BEM tree with it. v2 kept everything under
54
+ one block, `.bb-tab`; v3 splits it into `.bb-tabs` (root) plus two blocks of
55
+ their own, `.bb-tabs-list` (the strip) and `.bb-tabs-panes` (the pane wrapper).
56
+ **No `.bb-tab*` selector survives**, and nothing warns — your v2 tab CSS just
57
+ stops matching and the shipped styling shows through.
58
+
59
+ | v2 | v3 |
60
+ | ----------------------------------------- | ----------------------------------------------------------------- |
61
+ | `.bb-tab` | `.bb-tabs` |
62
+ | `.bb-tab--horizontal` | `.bb-tabs--horizontal` (+ `.bb-tabs-list--horizontal`) |
63
+ | `.bb-tab--vertical` | `.bb-tabs--vertical` (+ `.bb-tabs-list--vertical`) |
64
+ | `.bb-tab--disabled` | `.bb-tabs--disabled` (+ `.bb-tabs-list--disabled`) |
65
+ | `.bb-tab__label-boundary` | `.bb-tabs-list` |
66
+ | `.bb-tab__label-container` | `.bb-tabs-list__tablist` |
67
+ | `.bb-tab__label-container--no-transition` | `.bb-tabs-list__tablist--no-transition` |
68
+ | `.bb-tab__btn` | `.bb-tabs__trigger` |
69
+ | `.bb-tab__btn--active` | `.bb-tabs__trigger--active` |
70
+ | `.bb-tab__label` | `.bb-tabs__trigger-label`, wrapped in `.bb-tabs__trigger-content` |
71
+ | `.bb-tab__panes-container` | `.bb-tabs-panes` |
72
+ | `.bb-tab__pane` | `.bb-tabs__pane` |
73
+ | `.bb-tab__pane--shown` | `.bb-tabs__pane--shown` |
74
+
75
+ New in v3 with no v2 counterpart: `.bb-tabs--block` / `.bb-tabs-list--block`,
76
+ `.bb-tabs--compact` / `.bb-tabs-list--compact`, `.bb-tabs-list__prepend` and
77
+ `__append`, `.bb-tabs-list__tablist--measuring`.
78
+
79
+ Four things to know while porting rules:
80
+
81
+ - **The nesting did not change.** `.bb-tab__label-container` and
82
+ `.bb-tabs-list__tablist` are the same `<ul>`. The sliding active pill is
83
+ still that element's `::before`, still positioned from `--left` / `--top` /
84
+ `--width` / `--height` set inline on it. A v2 pill override moves by name
85
+ only, not up or down a level.
86
+ - **`.bb-tabs--horizontal` is rendered.** The direction modifier is applied
87
+ unconditionally (`bb-tabs--${direction}`, default `'horizontal'`), so a
88
+ renamed v2 rule keeps working. The library ships no _rule_ for the horizontal
89
+ modifier — only `--vertical` is styled — but that does not affect your
90
+ selectors. Do **not** rewrite these as `:not(.bb-tabs--vertical)`.
91
+ - **The `<ul>` is no longer an only child.** `.bb-tabs-list` is a grid holding
92
+ `__prepend`, the tablist, and `__append`. A v2 rule written as
93
+ `.bb-tab__label-boundary > *` now over-matches; target
94
+ `.bb-tabs-list__tablist` by name.
95
+ - **Block naming is not uniform** between the two new blocks: the pane wrapper
96
+ is `.bb-tabs-panes`, but the pane inside it is an element of the root,
97
+ `.bb-tabs__pane`. There is no `.bb-tabs-panes__pane`.
98
+
99
+ Grep for survivors — this catches every v2 name and no v3 one:
100
+
101
+ ```sh
102
+ rg -n 'bb-tab(__|--)|\.bb-tab\b'
103
+ ```
104
+
105
+ ## `block` replaces hand-rolled full-width tabs
106
+
107
+ v2 shipped no full-width tab list, so apps minted their own class — commonly a
108
+ `bb-tab--full-width` / `bb-tabs--full-width` rule stretching the `<ul>` and
109
+ setting `flex: 1` on each button. v3 has the prop:
110
+
111
+ ```diff
112
+ - <BbTab :items="items" class="bb-tabs--full-width">
113
+ + <BbTabs :items="items" block>
114
+ ```
115
+
116
+ `block` sets `.bb-tabs--block` and `.bb-tabs-list--block`: the strip stretches
117
+ to 100% and every trigger takes an equal share of the free space, floored at
118
+ the widest trigger's measured width (published as `--eq` on the tablist) so the
119
+ triggers stay equal even when the strip overflows and scrolls. Horizontal only
120
+ — the vertical strip skips the measurement.
121
+
122
+ Delete the hand-rolled rule when you switch. Left in place it fights `--block`
123
+ over the same `flex` and `min-width` declarations, and because your class name
124
+ is now unrecognised (`bb-tabs--full-width` collides with nothing the library
125
+ ships) neither the checker nor ESLint will tell you which one won.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: 'Migration v2→v3: BbTextInput'
3
- summary: The type prop narrows to the text-like input types — number/date values must move to their dedicated components.
3
+ summary: The type prop narrows to the text-like input types — number/date values must move to their dedicated components; a bare `floating` attribute was never a prop, the spelling is label-mode="floating".
4
4
  ---
5
5
 
6
6
  # BbTextInput — v2 → v3
@@ -36,6 +36,28 @@ Also relevant here: the shared input wrapper's CSS block was renamed — any CSS
36
36
  or test selectors on `.bb-common-input-inner-container*` must move to
37
37
  `.common-input-wrapper--*` (see [main guide §5](../v2-to-v3.md)).
38
38
 
39
+ ## `floating` is not a prop — it is a value of `label-mode`
40
+
41
+ ```diff
42
+ - <BbTextInput v-model="email" label="Email" floating />
43
+ + <BbTextInput v-model="email" label="Email" label-mode="floating" />
44
+ ```
45
+
46
+ `labelMode?: 'outside' | 'floating' | 'inside'` — same in v2 as in v3. A bare
47
+ `floating` was never a `BbTextInput` prop on either version; it falls through
48
+ `$attrs` onto the root element and does nothing, silently. Applies to the whole
49
+ input family, which shares `labelMode`.
50
+
51
+ `bitboss-ui check` flags it, but points the wrong way:
52
+
53
+ > prop `floating` is not in BbTextInput's API — `floating` belongs to
54
+ > BbDatePicker, BbDatePickerInput, not BbTextInput
55
+
56
+ That is true and irrelevant. Those two do declare a real `floating` boolean, and
57
+ it means something else entirely there (emit plain calendar strings instead of
58
+ zoned ISO instants). The answer is `label-mode="floating"` on the component you
59
+ already have.
60
+
39
61
  ## DOM: `--hidden-label` layout class removed (2026-08-07)
40
62
 
41
63
  `BbBaseInputContainer` — the wrapper behind every labelled control in the
@@ -22,24 +22,58 @@ plugin's `toastPosition` config (default `bottom-right`).
22
22
 
23
23
  ## `toast()` options
24
24
 
25
- | v2 | v3 | Kind |
26
- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
27
- | `theme?: string` | `variant?: ToastVariantType` (built-ins `default, success, info, warning, destructive`) | typed rename |
28
- | `persistent` | unchanged | |
29
- | `title`, `text`, `icon` | unchanged | |
30
- | — | `duration`, `hideClose`, `position`, `loading`, `id` + `updateToast()`, `dismissToast(reason)`, `portal` / `portalProps`, `actions: ActionButtonConfig[]` | additive |
25
+ | v2 | v3 | Kind |
26
+ | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------- |
27
+ | `theme?: string` | `variant?: ToastVariantType` (built-ins `default, success, info, warning, destructive`) | typed rename |
28
+ | `timeout?: number` | `duration?: number` | rename |
29
+ | `showClose?: boolean` (default `true`) | `hideClose?: boolean` (default `false`) | rename, polarity inverted |
30
+ | `persistent` | unchanged | |
31
+ | `title`, `text`, `icon` | unchanged (`text` is now optional) | |
32
+ | — | `position`, `loading`, `id` + `updateToast()`, `dismissToast(reason)`, `portal` / `portalProps`, `actions` | additive |
31
33
 
32
- `toast('plain string')` still works. The `BbToastMessage` type export is gone
33
- — type options as `BbToastOptions`.
34
+ `timeout` → `duration` is the one to grep for. It is an options-object key, not
35
+ a template attribute, so neither `bitboss-ui check` nor the eslint plugin sees
36
+ it; TS catches a literal `toast({ timeout: 8000 })` on excess-property grounds,
37
+ but a JS call site — or options built up in a variable — just drops the key and
38
+ takes the default, which also shortened: **v2 6000ms, v3 4000ms**. Same rule at
39
+ both ends: a non-positive value throws. `rg -n 'timeout' --type ts --type js`
40
+ over your toast call sites.
41
+
42
+ `showClose` inverts, it does not disappear: `showClose: false` → `hideClose: true`,
43
+ and `showClose: true` was the default so **delete it**.
34
44
 
35
45
  ```diff
36
- - toast({ title: 'Deleted', theme: 'success' });
37
- + toast({ title: 'Deleted', variant: 'success' });
46
+ - toast({ title: 'Deleted', theme: 'success', timeout: 4000, showClose: false });
47
+ + toast({ title: 'Deleted', variant: 'success', duration: 4000, hideClose: true });
38
48
 
39
49
  // the hand-rolled undo pattern collapses to:
40
50
  + toast({ title: 'Deleted', actions: [{ text: 'Undo', onClick: restore }] });
41
51
  ```
42
52
 
53
+ `toast('plain string')` still works. The `BbToastMessage` type export is gone
54
+ — type options as `BbToastOptions` (and a partial update as `BbToastUpdate`).
55
+
56
+ ## What `useToast()` returns
57
+
58
+ | v2 | v3 |
59
+ | -------------------------------- | ------------------------------------------------------------ |
60
+ | `{ toast, dismiss, dismissAll }` | `{ toast, dismiss, update }` |
61
+ | `dismiss(id)` — id required | `dismiss(id?, reason?)` — **no argument dismisses them all** |
62
+ | `dismissAll()` | removed — call `dismiss()` with no argument |
63
+ | — | `update(id, options)` |
64
+
65
+ ```diff
66
+ - const { toast, dismissAll } = useToast();
67
+ - dismissAll();
68
+ + const { toast, dismiss } = useToast();
69
+ + dismiss();
70
+ ```
71
+
72
+ Nothing catches this for you — `dismissAll` is not a template attribute, so
73
+ `bitboss-ui check` and the eslint plugin never see it. Grep:
74
+ `rg -n 'dismissAll' --type ts --type js --type vue`. Hits on a `useConfirm()`
75
+ destructure are fine: `dismissAll` is **new** there, and unrelated.
76
+
43
77
  Notes on `actions` (additive but with rules): handlers run with a spinner and
44
78
  sibling-disable, and hold the dismiss timer; portal toasts ignore `actions`.
45
79
  Styling hooks moved from `theme` classes to `bb-toast-message--<variant>` —
@@ -1,25 +1,34 @@
1
1
  ---
2
2
  title: 'Migration v2→v3: confirm() / useConfirm'
3
- summary: yesText/onYes/noText/onNo collapse into yes/no configs, actions becomes an array-or-false, labels are localized, footer buttons default md.
3
+ summary: yesText/onYes/noText/onNo collapse into yes/no configs, actions becomes an array-or-false, theme→variant, timeout→duration, autoClose flips to default-true, labels are localized, footer buttons default md.
4
4
  ---
5
5
 
6
6
  # `confirm()` / `useConfirm` — v2 → v3
7
7
 
8
8
  ## Options
9
9
 
10
- | v2 | v3 | Kind |
11
- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
12
- | `yes?: boolean`, `yesText?: string`, `onYes?: () => …` | `yes?: false \| string \| ConfirmButtonConfig` (`{ text, variant, size, icon?, onClick? }`) | collapsed — **`yes: false` still hides the button, unchanged from v2.** Leave it alone; only `yesText`/`onYes` move into the config |
13
- | `no?: boolean`, `noText?: string`, `onNo?: () => …` | `no?: false \| string \| ConfirmButtonConfig` | collapsed — same: **`no: false` is a no-op migration**, it hides the button exactly as in v2 |
14
- | `actions?: boolean` | `actions?: false \| ConfirmButtonConfig[]` | `false` hides the footer; an array replaces the yes/no pair with custom buttons |
15
- | default labels `"OK"` / `"Annulla"` (hard-coded) | localized via the active `locale` | **⚠ silent** — English apps stop showing Italian "Annulla"; apps that _wanted_ the literals must pass them |
16
- | footer buttons `lg` | footer buttons default `md` | visual |
17
- | `size` | `size?: keyof DialogSizes` — forwards to `BbDialog` | see the dialog width change in [main guide §2](../v2-to-v3.md) |
18
- | — | `hideClose`, `variant` (`default, destructive`), `portal` / `portalProps` (mutually exclusive with `text`) | additive |
19
- | second `confirm()` while one is open: first promise **never settled** (hung forever) | the superseded promise resolves `false` (implicit dismiss, no callbacks) | **⚠ behavioral** (bugfix 2026-07-18; the replace-not-queue semantics are unchanged) |
10
+ | v2 | v3 | Kind |
11
+ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
12
+ | `yes?: boolean`, `yesText?: string`, `onYes?: () => …` | `yes?: false \| string \| ConfirmButtonConfig` (`{ text, variant, size, icon?, onClick? }`) | collapsed — **`yes: false` still hides the button, unchanged from v2.** Leave it alone; only `yesText`/`onYes` move into the config |
13
+ | `no?: boolean`, `noText?: string`, `onNo?: () => …` | `no?: false \| string \| ConfirmButtonConfig` | collapsed — same: **`no: false` is a no-op migration**, it hides the button exactly as in v2 |
14
+ | `actions?: boolean` | `actions?: false \| ConfirmButtonConfig[]` | `false` hides the footer; an array replaces the yes/no pair with custom buttons |
15
+ | default labels `"OK"` / `"Annulla"` (hard-coded) | localized via the active `locale` | **⚠ silent** — English apps stop showing Italian "Annulla"; apps that _wanted_ the literals must pass them |
16
+ | footer buttons `lg` | footer buttons default `md` | visual |
17
+ | `size` | `size?: keyof DialogSizes` — forwards to `BbDialog` | see the dialog width change in [main guide §2](../v2-to-v3.md) |
18
+ | `theme?: string` | `variant?: ConfirmVariantType` (built-ins `default`, `destructive`) | typed rename — unregistered names are a compile error; add yours via the plugin's `confirmVariants` |
19
+ | `timeout?: number` | `duration?: number` | rename — auto-dismisses, resolves `false` quietly (no `no.onClick`), and implicitly forces `autoClose: true` |
20
+ | `autoClose?: boolean` — **opt-in**, omitted means the dialog stays open | `autoClose?: boolean` — **default `true`** | **⚠ silent behavior flip** — a v2 multi-step flow that kept the dialog open after resolve must now pass `autoClose: false` |
21
+ | `hideHeader?: boolean` (a union that made `title` required when `false`) | removed — `title` is plain optional; omit it and no header renders | removed |
22
+ | `useConfirm().setLoading(value)` | removed — busy state is driven by the button's `onClick` promise | removed — `useConfirm()` now returns `{ confirm, close, dismissAll }` |
23
+ | — | `hideClose`, `portal` / `portalProps` (mutually exclusive with `text`) | additive |
24
+ | second `confirm()` while one is open: first promise **never settled** (hung forever) | the superseded promise resolves `false` (implicit dismiss, no callbacks) | **⚠ behavioral** (bugfix 2026-07-18; the replace-not-queue semantics are unchanged) |
20
25
 
21
- `autoClose`, `title`, `text`, and the returned promise semantics otherwise
22
- carry over.
26
+ `title`, `text`, and the returned promise semantics otherwise carry over.
27
+ `autoClose` does **not** — see its row above.
28
+
29
+ Grep for the collapsed keys: `rg -n 'yesText|noText|onYes|onNo|hideHeader|setLoading|autoClose|timeout' --type ts --type js --type vue`.
30
+ TypeScript catches all of them (excess-property checks on the options literal,
31
+ a hard property error on `setLoading`); a JS app gets no signal at all.
23
32
 
24
33
  ## Edits
25
34
 
@@ -52,3 +61,29 @@ drops the whole footer. Both spellings survive v2 → v3 untouched.
52
61
 
53
62
  If you only ever called `confirm('Are you sure?')`, nothing changes except the
54
63
  (localized) button labels and their size.
64
+
65
+ ## CSS hooks
66
+
67
+ | v2 | v3 |
68
+ | ------------------------------------------- | ------------------------------------------------------------------------------------- |
69
+ | `.bb-confirm__content`, `.bb-confirm__text` | unchanged — still the right targets for the body copy |
70
+ | `.bb-confirm__no` / `.bb-confirm__yes` | **gone** — the footer buttons render as bare `BbButton`s with no confirm class |
71
+ | `.bb-confirm .bb-base-dialog__*` | `.bb-confirm .bb-dialog__*` — plus `.bb-offcanvas__*`, the adaptive sheet's parts |
72
+ | — | `.bb-confirm--<variant>` on the dialog root — the lever for per-variant confirm skins |
73
+
74
+ `.bb-confirm__text` survives, so a v2 rule targeting the body copy still lands;
75
+ do not relocate it onto `.bb-dialog__body`.
76
+
77
+ The cancel button is the one that lost its hook. Restyle it per call instead of
78
+ in CSS — v3 defaults it to `size: 'md', variant: 'outline'`, and the Yes button
79
+ to `primary` (or `destructive` when the confirm `variant` is `destructive`):
80
+
81
+ ```diff
82
+ - .bb-confirm .bb-base-dialog__footer .bb-confirm__no:hover:not(:disabled) {
83
+ - --color: var(--bb-primary);
84
+ - }
85
+ + confirm({ title: '…', no: { text: 'Keep', variant: 'ghost' } });
86
+ ```
87
+
88
+ For a whole-skin change, register a confirm variant and hang the rules off
89
+ `.bb-confirm--<name>` rather than reaching for the old element classes.