bitboss-ui 3.0.0-beta.57 → 3.0.0-beta.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/bitboss-ui-mcp.mjs +113 -26
- package/bin/bitboss-ui.mjs +43 -9
- package/dist/ai/BbDialog.md +153 -21
- package/dist/ai/BbDropdown.md +2 -2
- package/dist/ai/BbDropdownButton.md +148 -98
- package/dist/ai/BbForm.md +78 -7
- package/dist/ai/BbOffCanvas.md +25 -11
- package/dist/ai/BbTable.md +9 -1
- package/dist/ai/changelog.json +2015 -5
- package/dist/ai/components.json +87 -85
- package/dist/ai/composables/useBbDialogContext.md +26 -19
- package/dist/ai/composables/useBbFormContext.md +8 -10
- package/dist/ai/composables/useBbOffCanvasContext.md +4 -2
- package/dist/ai/guides/agent-contract.md +1 -1
- package/dist/ai/guides/ai-router.md +2 -1
- package/dist/ai/guides/eslint-rules.md +2 -2
- package/dist/ai/guides/icons-policy.md +6 -7
- package/dist/ai/guides/migration/components/bb-dropdown-button.md +32 -9
- package/dist/ai/guides/migration/v2-to-v3.md +50 -2
- package/dist/ai/guides/migration/v3-beta-tooling.md +36 -9
- package/dist/ai/helpers/inertia/manifest.json +1 -1
- package/dist/ai/index.md +2 -2
- package/dist/ai/manifest/common.json +119 -0
- package/dist/ai/manifest/components/BbAccordion.json +9 -10
- package/dist/ai/manifest/components/BbAlert.json +14 -15
- package/dist/ai/manifest/components/BbAsterisk.json +1 -2
- package/dist/ai/manifest/components/BbAvatar.json +8 -9
- package/dist/ai/manifest/components/BbBadge.json +10 -11
- package/dist/ai/manifest/components/BbBadgeButton.json +41 -42
- package/dist/ai/manifest/components/BbBaseButton.json +43 -44
- package/dist/ai/manifest/components/BbBaseCheckboxIcon.json +9 -10
- package/dist/ai/manifest/components/BbBaseRadioIcon.json +8 -9
- package/dist/ai/manifest/components/BbBaseSwitchIcon.json +9 -10
- package/dist/ai/manifest/components/BbBreadcrumbs.json +10 -11
- package/dist/ai/manifest/components/BbButton.json +52 -53
- package/dist/ai/manifest/components/BbCalendar.json +29 -30
- package/dist/ai/manifest/components/BbCheckbox.json +42 -43
- package/dist/ai/manifest/components/BbCheckboxGroup.json +61 -62
- package/dist/ai/manifest/components/BbCollapsible.json +6 -7
- package/dist/ai/manifest/components/BbColorInput.json +67 -68
- package/dist/ai/manifest/components/BbColorPalette.json +22 -23
- package/dist/ai/manifest/components/BbConfirm.json +1 -2
- package/dist/ai/manifest/components/BbDatePicker.json +42 -43
- package/dist/ai/manifest/components/BbDatePickerInput.json +74 -75
- package/dist/ai/manifest/components/BbDialog.json +32 -32
- package/dist/ai/manifest/components/BbDropdown.json +33 -34
- package/dist/ai/manifest/components/BbDropdownButton.json +72 -33
- package/dist/ai/manifest/components/BbDropdownGroup.json +4 -4
- package/dist/ai/manifest/components/BbDropzone.json +21 -22
- package/dist/ai/manifest/components/BbForm.json +10 -9
- package/dist/ai/manifest/components/BbHoverCard.json +16 -17
- package/dist/ai/manifest/components/BbIcon.json +4 -5
- package/dist/ai/manifest/components/BbIndicator.json +8 -9
- package/dist/ai/manifest/components/BbNumberInput.json +66 -67
- package/dist/ai/manifest/components/BbOffCanvas.json +32 -32
- package/dist/ai/manifest/components/BbPagination.json +14 -15
- package/dist/ai/manifest/components/BbPopover.json +21 -22
- package/dist/ai/manifest/components/BbProgress.json +6 -7
- package/dist/ai/manifest/components/BbPtScope.json +1 -1
- package/dist/ai/manifest/components/BbRadio.json +37 -38
- package/dist/ai/manifest/components/BbRadioGroup.json +62 -63
- package/dist/ai/manifest/components/BbRating.json +40 -41
- package/dist/ai/manifest/components/BbSelect.json +77 -78
- package/dist/ai/manifest/components/BbSelectPopover.json +51 -52
- package/dist/ai/manifest/components/BbSlider.json +40 -41
- package/dist/ai/manifest/components/BbSmoothHeight.json +7 -8
- package/dist/ai/manifest/components/BbSpinner.json +3 -4
- package/dist/ai/manifest/components/BbSwitch.json +42 -43
- package/dist/ai/manifest/components/BbSwitchGroup.json +62 -63
- package/dist/ai/manifest/components/BbTable.json +68 -69
- package/dist/ai/manifest/components/BbTabs.json +27 -28
- package/dist/ai/manifest/components/BbTabsList.json +2 -3
- package/dist/ai/manifest/components/BbTabsPanels.json +1 -2
- package/dist/ai/manifest/components/BbTabsRoot.json +26 -26
- package/dist/ai/manifest/components/BbTag.json +48 -49
- package/dist/ai/manifest/components/BbTextInput.json +64 -65
- package/dist/ai/manifest/components/BbTextarea.json +59 -60
- package/dist/ai/manifest/components/BbTimePicker.json +26 -27
- package/dist/ai/manifest/components/BbTimePickerInput.json +56 -57
- package/dist/ai/manifest/components/BbToast.json +1 -2
- package/dist/ai/manifest/components/BbTooltip.json +16 -17
- package/dist/ai/manifest/components/BbTree.json +4 -5
- package/dist/ai/manifest/components/ChipsBox.json +3 -3
- package/dist/ai/manifest/components/ClearableButton.json +1 -1
- package/dist/ai/manifest/components/CommaBox.json +1 -1
- package/dist/ai/manifest/components/CommonField.json +32 -32
- package/dist/ai/manifest/components/CommonFieldInput.json +15 -15
- package/dist/ai/manifest/components/CommonPopover.json +17 -17
- package/dist/ai/manifest/components/CommonTimeSelector.json +9 -9
- package/dist/ai/manifest/components/FlatListBox.json +14 -14
- package/dist/ai/manifest/components/GroupedListBox.json +15 -15
- package/dist/ai/manifest/components/ListBox.json +17 -17
- package/dist/ai/manifest/components/OptionsContainer.json +16 -16
- package/dist/ai/manifest/index.json +89 -85
- package/dist/ai/manifest/meta.json +42 -2
- package/dist/ai/manifest/types.advanced.json +10 -10
- package/dist/ai/manifest/types.api.json +51 -49
- package/dist/ai/recipes/inertia/approvals-inbox.md +6 -0
- package/dist/ai/recipes/inertia/inline-edit-workspace.md +6 -0
- package/dist/ai/recipes/inertia/onboarding.md +6 -0
- package/dist/ai/recipes/inertia/ownership-atlas.md +6 -0
- package/dist/ai/recipes/inertia/records-workspace.md +6 -0
- package/dist/ai/recipes/inertia/settings-workspace.md +6 -0
- package/dist/ai/recipes/inertia/upload-center.md +6 -0
- package/dist/ai/source/BbButton.md +11 -1
- package/dist/ai/source/BbDialog.md +55 -18
- package/dist/ai/source/BbDropdownButton.md +103 -112
- package/dist/ai/source/BbForm.md +63 -13
- package/dist/ai/source/BbOffCanvas.md +51 -16
- package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/BbButton/types.d.ts +6 -0
- package/dist/components/BbDialog/BbDialog.vue.d.ts +2 -2
- package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +191 -181
- package/dist/components/BbDialog/types.d.ts +21 -13
- package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +143 -100
- package/dist/components/BbDropdownButton/types.d.ts +25 -15
- package/dist/components/BbForm/BbForm.vue.d.ts +3 -3
- package/dist/components/BbForm/BbForm.vue_vue_type_script_setup_true_lang.js +195 -188
- package/dist/components/BbForm/formSchema.d.ts +9 -0
- package/dist/components/BbHost/BbHostOpening.js +1 -1
- package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +2 -2
- package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +242 -234
- package/dist/components/BbOffCanvas/types.d.ts +21 -13
- package/dist/composables/formSubmitLock.d.ts +6 -1
- package/dist/composables/hostOpenings.d.ts +10 -4
- package/dist/composables/hostOpenings.js +1 -1
- package/dist/composables/overlayRegistry.d.ts +22 -9
- package/dist/composables/overlayRegistry.js +175 -132
- package/dist/composables/useBbDialogContext.d.ts +29 -11
- package/dist/composables/useBbDialogContext.js +17 -8
- package/dist/composables/useBbFormContext.d.ts +12 -7
- package/dist/composables/useBbFormContext.js +7 -6
- package/dist/composables/useOnDemandOpener.d.ts +6 -13
- package/dist/composables/useOnDemandOpener.js +47 -22
- package/dist/directives/overlayTrigger.d.ts +4 -2
- package/dist/directives/overlayTrigger.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/llms-full.txt +625 -203
- package/dist/llms-medium.txt +3 -2
- package/dist/llms.txt +2 -2
- package/dist/styles.css +1 -1
- package/dist/utils/versionCheck.js +1 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/scripts/lib/agent-manifest.mjs +231 -18
- package/scripts/lib/validate-bb-markup.mjs +53 -9
package/bin/bitboss-ui-mcp.mjs
CHANGED
|
@@ -1161,6 +1161,19 @@ function getComponent({
|
|
|
1161
1161
|
const type = (manifest.types ?? []).find((t) => t.name === name);
|
|
1162
1162
|
if (type) {
|
|
1163
1163
|
const use = type.usedBy?.[0];
|
|
1164
|
+
// Q74.1: a component's own Props type is that component's props.
|
|
1165
|
+
const propsOf =
|
|
1166
|
+
type.component && name === `${type.component}Props`
|
|
1167
|
+
? type.component
|
|
1168
|
+
: null;
|
|
1169
|
+
// Q74.2: `fields` stand in for a declaration that adds nothing.
|
|
1170
|
+
const shape = type.definition
|
|
1171
|
+
? '`definition` above is the type itself'
|
|
1172
|
+
: type.fields
|
|
1173
|
+
? '`fields` above are its shape'
|
|
1174
|
+
: propsOf
|
|
1175
|
+
? `Its fields are ${possessive(propsOf)} props: get_component('${propsOf}')`
|
|
1176
|
+
: 'It is described above';
|
|
1164
1177
|
return {
|
|
1165
1178
|
found: true,
|
|
1166
1179
|
kind: 'type',
|
|
@@ -1179,9 +1192,10 @@ function getComponent({
|
|
|
1179
1192
|
...(type.fields ? { fields: type.fields } : {}),
|
|
1180
1193
|
// A union of private members: each member's fields (Q51.21).
|
|
1181
1194
|
...(type.members ? { members: type.members } : {}),
|
|
1195
|
+
...(propsOf ? { propsOf } : {}),
|
|
1182
1196
|
import: `import type { ${type.replacedBy ?? name} } from 'bitboss-ui';`,
|
|
1183
1197
|
hint: use
|
|
1184
|
-
? `A type, not a component: it types ${possessive(use.component)} \`${use.name}\` ${use.surface}.
|
|
1198
|
+
? `A type, not a component: it types ${possessive(use.component)} \`${use.name}\` ${use.surface}. ${shape}; call get_component('${use.component}') to see it in use.`
|
|
1185
1199
|
: 'A type, not a component — import it for the value you pass, never write it as a tag.',
|
|
1186
1200
|
};
|
|
1187
1201
|
}
|
|
@@ -2124,7 +2138,11 @@ function docIndex() {
|
|
|
2124
2138
|
? `accepted values ${type.values.join(' | ')}`
|
|
2125
2139
|
: type.fields
|
|
2126
2140
|
? `object shape, fields ${type.fields.join('; ')}`
|
|
2127
|
-
:
|
|
2141
|
+
: type.definition
|
|
2142
|
+
? String(type.definition)
|
|
2143
|
+
: type.component && type.name === `${type.component}Props`
|
|
2144
|
+
? `the props of ${type.component}`
|
|
2145
|
+
: '') +
|
|
2128
2146
|
`. import type { ${type.name} } from 'bitboss-ui'.`,
|
|
2129
2147
|
read: { tool: 'get_component', args: { name: type.name } },
|
|
2130
2148
|
});
|
|
@@ -2556,6 +2574,66 @@ function changeLine(row, focus) {
|
|
|
2556
2574
|
return `${row.id} ${row.summary ?? firstSentence(row.description, 160)}`;
|
|
2557
2575
|
}
|
|
2558
2576
|
|
|
2577
|
+
/**
|
|
2578
|
+
* One page of `[release, line]` pairs, grouped by release: it ends at
|
|
2579
|
+
* CHANGES_PAGE lines or CHANGES_PAGE_CHARS of them, so its size holds however
|
|
2580
|
+
* long the lines grow; the rest is a cursor away.
|
|
2581
|
+
*/
|
|
2582
|
+
function pageLines(lines, cursor) {
|
|
2583
|
+
const start = Math.max(0, Number(cursor ?? 0) || 0);
|
|
2584
|
+
let end = start;
|
|
2585
|
+
for (let used = 0; end < lines.length && end - start < CHANGES_PAGE; end++) {
|
|
2586
|
+
used += lines[end][1].length + 3;
|
|
2587
|
+
if (used > CHANGES_PAGE_CHARS && end > start) break;
|
|
2588
|
+
}
|
|
2589
|
+
const byRelease = {};
|
|
2590
|
+
for (const [release, line] of lines.slice(start, end))
|
|
2591
|
+
(byRelease[release] ??= []).push(line);
|
|
2592
|
+
return {
|
|
2593
|
+
byRelease,
|
|
2594
|
+
more:
|
|
2595
|
+
end < lines.length
|
|
2596
|
+
? { nextCursor: String(end), remaining: lines.length - end }
|
|
2597
|
+
: {},
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
/**
|
|
2602
|
+
* Q74.4: what the releases in range ADDED, as `[release, "area · what"]`,
|
|
2603
|
+
* oldest first; `unreleased` only when `until` is open. A component keeps
|
|
2604
|
+
* the lines of its own area (BbDialog → `dialog`) and those whose commits
|
|
2605
|
+
* touched its folder (a family scope: `inputs`).
|
|
2606
|
+
*/
|
|
2607
|
+
function addedPairs(doc, { since, until, component }) {
|
|
2608
|
+
const area = component
|
|
2609
|
+
? String(component).replace(/^Bb/, '').toLowerCase()
|
|
2610
|
+
: null;
|
|
2611
|
+
return Object.entries(doc.added ?? {})
|
|
2612
|
+
.filter(([at]) => {
|
|
2613
|
+
if (at === 'unreleased') return until == null;
|
|
2614
|
+
if (since != null && compareVersions(at, String(since)) <= 0)
|
|
2615
|
+
return false;
|
|
2616
|
+
if (until != null && compareVersions(at, String(until)) > 0) return false;
|
|
2617
|
+
return true;
|
|
2618
|
+
})
|
|
2619
|
+
.flatMap(([at, entries]) =>
|
|
2620
|
+
entries
|
|
2621
|
+
.map((entry) =>
|
|
2622
|
+
typeof entry === 'string' ? { text: entry, components: [] } : entry
|
|
2623
|
+
)
|
|
2624
|
+
.filter(
|
|
2625
|
+
({ text, components }) =>
|
|
2626
|
+
!area ||
|
|
2627
|
+
text.split(' · ')[0] === area ||
|
|
2628
|
+
components.includes(String(component))
|
|
2629
|
+
)
|
|
2630
|
+
.map(({ text }) => [at, text])
|
|
2631
|
+
);
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
const ADDED_HINT =
|
|
2635
|
+
'Fixes are not tracked. What the range added, one line per feature: list_changes({ since, kind: "added" }).';
|
|
2636
|
+
|
|
2559
2637
|
function changeLines(rows, cursor, withClassMap = false, focus) {
|
|
2560
2638
|
const lines = [];
|
|
2561
2639
|
const classRenames = {};
|
|
@@ -2576,25 +2654,12 @@ function changeLines(rows, cursor, withClassMap = false, focus) {
|
|
|
2576
2654
|
? -1
|
|
2577
2655
|
: compareVersions(a, b)
|
|
2578
2656
|
);
|
|
2657
|
+
const page = pageLines(lines, cursor);
|
|
2658
|
+
// The class map rides on the first page only.
|
|
2579
2659
|
const start = Math.max(0, Number(cursor ?? 0) || 0);
|
|
2580
|
-
// A page ends at CHANGES_PAGE lines or CHANGES_PAGE_CHARS of them, so its
|
|
2581
|
-
// size holds however long the summaries grow; the rest is a cursor away.
|
|
2582
|
-
let end = start;
|
|
2583
|
-
for (let used = 0; end < lines.length && end - start < CHANGES_PAGE; end++) {
|
|
2584
|
-
used += lines[end][1].length + 3;
|
|
2585
|
-
if (used > CHANGES_PAGE_CHARS && end > start) break;
|
|
2586
|
-
}
|
|
2587
|
-
const byRelease = {};
|
|
2588
|
-
for (const [release, line] of lines.slice(start, end))
|
|
2589
|
-
(byRelease[release] ??= []).push(line);
|
|
2590
2660
|
return {
|
|
2591
|
-
changes: byRelease,
|
|
2592
|
-
...
|
|
2593
|
-
? {
|
|
2594
|
-
nextCursor: String(end),
|
|
2595
|
-
remaining: lines.length - end,
|
|
2596
|
-
}
|
|
2597
|
-
: {}),
|
|
2661
|
+
changes: page.byRelease,
|
|
2662
|
+
...page.more,
|
|
2598
2663
|
...(Object.keys(classRenames).length && start === 0 && !withClassMap
|
|
2599
2664
|
? {
|
|
2600
2665
|
classRenames: `${Object.keys(classRenames).length} .bb-* class renames (the beta.36 sweep): the map with surface: 'class'`,
|
|
@@ -2692,6 +2757,24 @@ function listChanges({
|
|
|
2692
2757
|
}
|
|
2693
2758
|
}
|
|
2694
2759
|
|
|
2760
|
+
if (kind === 'added') {
|
|
2761
|
+
const pairs = addedPairs(doc, { since, until, component });
|
|
2762
|
+
const page = pageLines(pairs, cursor);
|
|
2763
|
+
return {
|
|
2764
|
+
version: doc.version,
|
|
2765
|
+
since: since ?? null,
|
|
2766
|
+
until: until ?? doc.version,
|
|
2767
|
+
...(component ? { filteredBy: { component } } : {}),
|
|
2768
|
+
count: pairs.length,
|
|
2769
|
+
added: page.byRelease,
|
|
2770
|
+
...page.more,
|
|
2771
|
+
hint:
|
|
2772
|
+
pairs.length === 0
|
|
2773
|
+
? 'Nothing added in that range (fixes are not tracked).'
|
|
2774
|
+
: 'One "area · what" line per feature, grouped by release, from its feat commit. Breaking changes: the same call without kind. Next page: cursor: nextCursor.',
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2695
2778
|
const rows = detail === 'rows';
|
|
2696
2779
|
const filtered = (doc.breaking ?? []).filter(
|
|
2697
2780
|
(row) =>
|
|
@@ -2751,7 +2834,8 @@ function listChanges({
|
|
|
2751
2834
|
Object.fromEntries(Object.entries(bucket).filter(([, n]) => n > 0)),
|
|
2752
2835
|
])
|
|
2753
2836
|
),
|
|
2754
|
-
|
|
2837
|
+
added: addedPairs(doc, {}).length,
|
|
2838
|
+
hint: 'Counts only (a missing count is 0). What a bump breaks: list_changes({ since: "<your version>" }), one line per change; from v2: since: "2". One component: { component: "BbSelect" }. Full rows: detail: "rows". `added`: features, kind: "added".',
|
|
2755
2839
|
};
|
|
2756
2840
|
}
|
|
2757
2841
|
|
|
@@ -2808,10 +2892,11 @@ function listChanges({
|
|
|
2808
2892
|
count: inRange.length,
|
|
2809
2893
|
...changeLines(inRange, cursor, surface === 'class', component),
|
|
2810
2894
|
guide: doc.guide,
|
|
2895
|
+
added: addedPairs(doc, { since, until, component }).length,
|
|
2811
2896
|
hint:
|
|
2812
2897
|
inRange.length === 0
|
|
2813
|
-
?
|
|
2814
|
-
:
|
|
2898
|
+
? `No breaking changes recorded in that range: nothing BREAKS. ${ADDED_HINT}`
|
|
2899
|
+
: `One line per change, grouped by the release that shipped it (id, components, old → new, or the break in one sentence). Whole rows: detail: "rows". The \`guide\` page explains each. Next page: cursor: nextCursor. \`added\`: features, kind: "added".`,
|
|
2815
2900
|
};
|
|
2816
2901
|
}
|
|
2817
2902
|
|
|
@@ -2824,7 +2909,7 @@ function listChanges({
|
|
|
2824
2909
|
guide: doc.guide,
|
|
2825
2910
|
hint:
|
|
2826
2911
|
inRange.length === 0
|
|
2827
|
-
?
|
|
2912
|
+
? `No breaking changes recorded in that range: nothing BREAKS. ${ADDED_HINT}`
|
|
2828
2913
|
: `Each entry names the component and the new spelling; \`guide\` is the migration page with the full explanation. ${ROW_FIELDS}`,
|
|
2829
2914
|
};
|
|
2830
2915
|
}
|
|
@@ -3018,7 +3103,7 @@ function buildServer() {
|
|
|
3018
3103
|
'list_changes',
|
|
3019
3104
|
{
|
|
3020
3105
|
description:
|
|
3021
|
-
'Breaking changes between bitboss-ui releases — what breaks if you bump. Call with since=<the version you are on>: one line per change, grouped by release. No arguments: counts per release. Narrow with component / surface / kind; detail: "rows" for the full records.
|
|
3106
|
+
'Breaking changes between bitboss-ui releases — what breaks if you bump. Call with since=<the version you are on>: one line per change, grouped by release. No arguments: counts per release. Narrow with component / surface / kind; detail: "rows" for the full records. kind: "added" lists what the range added (features); fixes are not tracked.',
|
|
3022
3107
|
inputSchema: {
|
|
3023
3108
|
since: z
|
|
3024
3109
|
.string()
|
|
@@ -3053,9 +3138,11 @@ function buildServer() {
|
|
|
3053
3138
|
'Only renames of this surface (`item` = a field of an `items` entry, e.g. a dropdown row `key` → `value`).'
|
|
3054
3139
|
),
|
|
3055
3140
|
kind: z
|
|
3056
|
-
.enum(['rename', 'behaviour'])
|
|
3141
|
+
.enum(['rename', 'behaviour', 'added'])
|
|
3057
3142
|
.optional()
|
|
3058
|
-
.describe(
|
|
3143
|
+
.describe(
|
|
3144
|
+
"Only renames, or only behaviour changes; 'added': what the range added (features), one line each."
|
|
3145
|
+
),
|
|
3059
3146
|
detail: z
|
|
3060
3147
|
.enum(['lines', 'rows'])
|
|
3061
3148
|
.optional()
|
package/bin/bitboss-ui.mjs
CHANGED
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
* add Copy Inertia helpers into the app as files it owns
|
|
41
41
|
* (scripts/lib/helpers-add.mjs); `helpers list|diff|update`
|
|
42
42
|
* reports on them and refreshes the ones the app has not edited.
|
|
43
|
-
* changelog Breaking changes between two releases
|
|
44
|
-
* answers "what breaks if I bump", without MCP. Reads the
|
|
43
|
+
* changelog Breaking changes between two releases, then what they added —
|
|
44
|
+
* `--since <version>` answers "what breaks if I bump", without MCP. Reads the
|
|
45
45
|
* installed `dist/ai/changelog.json`.
|
|
46
46
|
* mcp Start a stdio MCP server exposing the dist/ai knowledge base
|
|
47
47
|
* (search_docs, search_components, get_component, get_composable,
|
|
@@ -167,7 +167,7 @@ function coreRules(version) {
|
|
|
167
167
|
1. **Start at the AI Router**: \`${ROUTER}\` — which file to open next (do not dump the full catalogue into context).
|
|
168
168
|
2. **Obey the agent contract**: \`${CONTRACT}\`.
|
|
169
169
|
3. **Prefer \`Bb*\` consumer components**; never use entries marked "Reference Only - Internal" unless the user explicitly asks for internals. (\`BbBaseButton\` and the \`BbBase*Icon\` glyphs are consumer API; the \`Common*\` building blocks are not.)
|
|
170
|
-
4. **Never invent prop/event/slot names** — open \`${KNOWLEDGE_BASE}/<ComponentName>.md\` (Usage & Guidelines first) or its API file \`${KNOWLEDGE_BASE}/manifest/components/<ComponentName>.json\` (one member per line; the list is \`${KNOWLEDGE_BASE}/manifest/index.json\`). Never read \`components.json\` whole: it is the 1.2 MB file the tools use.
|
|
170
|
+
4. **Never invent prop/event/slot names** — open \`${KNOWLEDGE_BASE}/<ComponentName>.md\` (Usage & Guidelines first) or its API file \`${KNOWLEDGE_BASE}/manifest/components/<ComponentName>.json\` (one member per line; a row marked \`common: true\` is whole in \`${KNOWLEDGE_BASE}/manifest/common.json\`; the list is \`${KNOWLEDGE_BASE}/manifest/index.json\`). Never read \`components.json\` whole: it is the 1.2 MB file the tools use.
|
|
171
171
|
5. **Icons**: discover names first; never invent them (\`${KNOWLEDGE_BASE}/guides/icons-policy.md\`).
|
|
172
172
|
6. **Item-driven components**: declarative \`items\` (an object row reads its own \`label\` and \`value\`; \`item-text\` / \`item-value\` only for other field names, never to restate those two), \`dependencies\`, \`enforce-coherence\` — no manual watchers for coherence.
|
|
173
173
|
7. **Whole pages**: one recipe under \`${KNOWLEDGE_BASE}/recipes/{vue|nuxt|inertia}/\` (see the router table).
|
|
@@ -780,7 +780,7 @@ function printFindingLegend(findings) {
|
|
|
780
780
|
*/
|
|
781
781
|
/**
|
|
782
782
|
* `changelog [--since <v>] [--until <v>] [--json]` — what breaks between two
|
|
783
|
-
* releases, for anyone not driving the MCP server (a human at a terminal, CI,
|
|
783
|
+
* releases, then what they added (Q74.4: one line per feature), for anyone not driving the MCP server (a human at a terminal, CI,
|
|
784
784
|
* or an agent whose harness has no MCP). Same data and same range rules as the
|
|
785
785
|
* `list_changes` tool; this is the transport-free path to it.
|
|
786
786
|
*/
|
|
@@ -823,6 +823,22 @@ function changelogCommand({ since, until, json: jsonMode }) {
|
|
|
823
823
|
if (until != null && compareReleaseVersions(at, until) > 0) return false;
|
|
824
824
|
return true;
|
|
825
825
|
});
|
|
826
|
+
// Q74.4: what the range ADDED (feat commits, one line each), oldest first.
|
|
827
|
+
const inRange = (at) => {
|
|
828
|
+
if (!ranged) return true;
|
|
829
|
+
if (at === 'unreleased') return until == null;
|
|
830
|
+
if (since != null && compareReleaseVersions(at, since) <= 0) return false;
|
|
831
|
+
if (until != null && compareReleaseVersions(at, until) > 0) return false;
|
|
832
|
+
return true;
|
|
833
|
+
};
|
|
834
|
+
const added = Object.entries(doc.added ?? {})
|
|
835
|
+
.filter(([at]) => inRange(at))
|
|
836
|
+
.flatMap(([at, entries]) =>
|
|
837
|
+
entries.map((entry) => ({
|
|
838
|
+
at,
|
|
839
|
+
line: typeof entry === 'string' ? entry : entry.text,
|
|
840
|
+
}))
|
|
841
|
+
);
|
|
826
842
|
|
|
827
843
|
if (jsonMode) {
|
|
828
844
|
console.log(
|
|
@@ -833,6 +849,7 @@ function changelogCommand({ since, until, json: jsonMode }) {
|
|
|
833
849
|
until: until ?? doc.version,
|
|
834
850
|
count: rows.length,
|
|
835
851
|
breaking: rows,
|
|
852
|
+
added: added.map(({ at, line }) => `[${at}] ${line}`),
|
|
836
853
|
guide: doc.guide,
|
|
837
854
|
},
|
|
838
855
|
null,
|
|
@@ -845,13 +862,26 @@ function changelogCommand({ since, until, json: jsonMode }) {
|
|
|
845
862
|
const range = ranged
|
|
846
863
|
? `${since ?? 'the start of v3'} → ${until ?? doc.version}`
|
|
847
864
|
: `the whole v2 → v3 upgrade`;
|
|
865
|
+
const printAdded = () => {
|
|
866
|
+
if (!added.length) return;
|
|
867
|
+
// Compact by default: the whole v3 line is hundreds of lines.
|
|
868
|
+
if (!ranged) {
|
|
869
|
+
console.log(
|
|
870
|
+
`\n${added.length} addition(s) over ${range}: list one hop with --since <your version>.`
|
|
871
|
+
);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
console.log(`\n${added.length} addition(s), ${range}:\n`);
|
|
875
|
+
for (const { at, line } of added) console.log(` [${at}] ${line}`);
|
|
876
|
+
};
|
|
848
877
|
if (rows.length === 0) {
|
|
849
878
|
// "Nothing breaks" and "I could not tell you" must not look alike.
|
|
850
879
|
console.log(
|
|
851
880
|
`[bitboss-ui] changelog: no BREAKING changes in ${range}.\n` +
|
|
852
|
-
'
|
|
853
|
-
'
|
|
881
|
+
' Fixes are not tracked here, so this means nothing\n' +
|
|
882
|
+
' breaks — not that nothing changed.'
|
|
854
883
|
);
|
|
884
|
+
printAdded();
|
|
855
885
|
return;
|
|
856
886
|
}
|
|
857
887
|
console.log(
|
|
@@ -874,6 +904,7 @@ function changelogCommand({ since, until, json: jsonMode }) {
|
|
|
874
904
|
}
|
|
875
905
|
if (entry.guide) console.log(` ${entry.guide}`);
|
|
876
906
|
}
|
|
907
|
+
printAdded();
|
|
877
908
|
console.log(`\nFull migration guide: ${doc.guide}`);
|
|
878
909
|
}
|
|
879
910
|
|
|
@@ -1651,7 +1682,8 @@ Commands:
|
|
|
1651
1682
|
\`bitboss-ui/validated\`, retired: everything is in
|
|
1652
1683
|
\`bitboss-ui\`; \`--fix\` rewrites it in .vue files),
|
|
1653
1684
|
\`unregistered-rule\` (a string or object rule no
|
|
1654
|
-
\`defineRule\` in the project registers
|
|
1685
|
+
\`defineRule\` in the project registers (in a .md
|
|
1686
|
+
page, also one the page's own fences show); a
|
|
1655
1687
|
registration it cannot read is named, never skipped:
|
|
1656
1688
|
list such names under \`registeredRules\` in
|
|
1657
1689
|
bitboss-ui.check.json),
|
|
@@ -1721,8 +1753,10 @@ Commands:
|
|
|
1721
1753
|
Breaking changes between two releases — "what breaks if I
|
|
1722
1754
|
bump". \`--since\` is the version you are ON, and is
|
|
1723
1755
|
exclusive. With no range, every break in the v2 → v3
|
|
1724
|
-
upgrade.
|
|
1725
|
-
|
|
1756
|
+
upgrade. Then what the range added, one line per feature
|
|
1757
|
+
(with no range, only their count). Fixes are not tracked,
|
|
1758
|
+
so no breaking changes means nothing breaks, not that
|
|
1759
|
+
nothing changed.
|
|
1726
1760
|
npx bitboss-ui changelog --since 3.0.0-beta.20
|
|
1727
1761
|
Pair it with \`check --manifest\` to turn the list into the
|
|
1728
1762
|
files in YOUR app that break.
|
package/dist/ai/BbDialog.md
CHANGED
|
@@ -203,8 +203,9 @@ reads `data` in two places:
|
|
|
203
203
|
`data` (typed `any`, so `data.name` reads with no cast);
|
|
204
204
|
- in code, `useBbDialogContext<T>(id)` returns it typed, along with `open`,
|
|
205
205
|
`show(data?)` and `hide()`. A component inside the dialog can call it too.
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
A `show()` on an open dialog changes nothing: it joins the opening (its
|
|
207
|
+
promise, its data); the new data, trigger and `onComplete` are ignored. A
|
|
208
|
+
show during the close transition opens it again.
|
|
208
209
|
|
|
209
210
|
`data` stays set until the close transition has run, so the content never
|
|
210
211
|
flashes empty while the dialog fades out. The edit-record example in
|
|
@@ -241,7 +242,8 @@ The rules:
|
|
|
241
242
|
- **An open before the dialog mounts waits for it.** A `show()`, or a
|
|
242
243
|
`v-bb-dialog` click whose own handler renders the dialog through a `v-if`,
|
|
243
244
|
is held until the dialog registers, so one click opens it (up to 1s; then
|
|
244
|
-
it settles `'missing'` with a dev warning, and `onComplete` never runs).
|
|
245
|
+
it settles `'missing'` with a dev warning, and `onComplete` never runs). A
|
|
246
|
+
`hide()` meanwhile cancels a held `show()`, silently: it settles `'hide'`.
|
|
245
247
|
The warning suggests the nearest registered id for a typo, and says when a
|
|
246
248
|
dialog with that id exists but is out of reach (inside an on-demand dialog,
|
|
247
249
|
which is private, or parked by KeepAlive) or is a `BbOffCanvas`.
|
|
@@ -323,7 +325,6 @@ const replace = (saved: Member) => {
|
|
|
323
325
|
| `'close'` | the ✕ or the slots' `close()` |
|
|
324
326
|
| `'swipe'` | the phone sheet swiped away |
|
|
325
327
|
| `'hide'` | `hide()`, or the model set to `false` (also a bound `v-model` whose parent refused the open) |
|
|
326
|
-
| `'replaced'` | a newer `show()` on the open dialog took over |
|
|
327
328
|
| `'missing'` | no dialog with that id mounted within 1s |
|
|
328
329
|
| `'unmounted'` | the dialog unmounted (or KeepAlive parked it), or a waiting `show()`'s caller went away |
|
|
329
330
|
| `'failed'` | an on-demand dialog could not render |
|
|
@@ -340,6 +341,101 @@ const replace = (saved: Member) => {
|
|
|
340
341
|
flags the unawaited promise of a bare `edit.show(row)`, write
|
|
341
342
|
`void edit.show(row)`.
|
|
342
343
|
|
|
344
|
+
#### The page saves: a promise from `onComplete`
|
|
345
|
+
|
|
346
|
+
When the page owns the save (it knows the endpoint, the list to update),
|
|
347
|
+
`onComplete` returns the request's promise. `complete(value)` calls it first,
|
|
348
|
+
and the dialog stays open and **locked** until it settles: the ✕, Cancel,
|
|
349
|
+
Escape and the backdrop are refused, and a footer `BbButton` whose click
|
|
350
|
+
returns `complete(value)` spins. Resolved, the dialog closes and the opening
|
|
351
|
+
settles `{ completed: true, value }`; rejected, it stays open with its data
|
|
352
|
+
and `complete()` rejects with the same error, so the dialog can show it.
|
|
353
|
+
|
|
354
|
+
```vue
|
|
355
|
+
<template>
|
|
356
|
+
<BbButton v-bb-dialog="{ id: 'rename', data: row, onComplete: save }">
|
|
357
|
+
Rename
|
|
358
|
+
</BbButton>
|
|
359
|
+
<BbDialog id="rename" v-slot="{ data, complete }" title="Rename">
|
|
360
|
+
<BbForm :model="data" @submit="() => complete(data)">
|
|
361
|
+
<BbTextInput label="Name" name="name" />
|
|
362
|
+
<BbButton type="submit">Save</BbButton>
|
|
363
|
+
</BbForm>
|
|
364
|
+
</BbDialog>
|
|
365
|
+
</template>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
From code: `edit.show(row, { onComplete: save })` (both the id and the
|
|
369
|
+
component handle). A `save` function passed in `data` is no longer needed.
|
|
370
|
+
A sync `onComplete` (or none) closes at once, as before.
|
|
371
|
+
|
|
372
|
+
`onComplete` is just a function. A trigger inside a child component gets the
|
|
373
|
+
page's save the way your app already shares things: a function prop
|
|
374
|
+
(`:save="saveMember"`), a page `provide` / `inject`, or a store action. Not an
|
|
375
|
+
emit: an emit cannot return the promise.
|
|
376
|
+
|
|
377
|
+
To show a rejected save's field errors, keep them in a `ref` bound on the
|
|
378
|
+
form, `<BbForm :field-errors="fieldErrors">` (BbForm § The page's errors
|
|
379
|
+
object): the dialog stays open, and each message lands under its field until
|
|
380
|
+
the user edits it.
|
|
381
|
+
|
|
382
|
+
```vue
|
|
383
|
+
<script setup lang="ts">
|
|
384
|
+
import { ref } from 'vue';
|
|
385
|
+
|
|
386
|
+
const fieldErrors = ref<Record<string, string[]>>({});
|
|
387
|
+
</script>
|
|
388
|
+
|
|
389
|
+
<template>
|
|
390
|
+
<BbDialog id="rename" v-slot="{ data, complete }" title="Rename">
|
|
391
|
+
<BbForm
|
|
392
|
+
:field-errors="fieldErrors"
|
|
393
|
+
:model="data"
|
|
394
|
+
@submit="
|
|
395
|
+
() =>
|
|
396
|
+
complete(data).catch((error) => {
|
|
397
|
+
// Your save's rejection: the 422 body's `errors` by field path.
|
|
398
|
+
fieldErrors = error.errors ?? {};
|
|
399
|
+
})
|
|
400
|
+
"
|
|
401
|
+
>
|
|
402
|
+
<BbTextInput label="Name" name="name" />
|
|
403
|
+
<BbButton type="submit">Save</BbButton>
|
|
404
|
+
</BbForm>
|
|
405
|
+
</BbDialog>
|
|
406
|
+
</template>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
With Inertia, the page's `useForm().errors` is that object already; the
|
|
410
|
+
record-form recipe (`ai/recipes/inertia/record-form.md`) binds
|
|
411
|
+
`<BbForm :field-errors="form.errors">` the same way.
|
|
412
|
+
|
|
413
|
+
An Inertia visit (`router.put`, `form.put`) returns no promise and never
|
|
414
|
+
rejects on its own, so `onComplete` wraps it: a validation error rejects
|
|
415
|
+
with the errors (the example above reads `error.errors`), success resolves,
|
|
416
|
+
and a visit cut short by another visit resolves too, closing the dialog.
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
import { router } from '@inertiajs/vue3';
|
|
420
|
+
|
|
421
|
+
type User = { id: number; name: string };
|
|
422
|
+
|
|
423
|
+
const save = (user: User) =>
|
|
424
|
+
new Promise<void>((resolve, reject) => {
|
|
425
|
+
router.put(`/users/${user.id}`, user, {
|
|
426
|
+
onSuccess: () => resolve(),
|
|
427
|
+
onError: (errors) => reject({ errors }),
|
|
428
|
+
// Cancelled or interrupted: close. Anything else that ends here
|
|
429
|
+
// unsettled (a 500, a network error) keeps the dialog open.
|
|
430
|
+
// Both are no-ops after onSuccess / onError.
|
|
431
|
+
onFinish: (visit) =>
|
|
432
|
+
visit.cancelled || visit.interrupted
|
|
433
|
+
? resolve()
|
|
434
|
+
: reject(new Error('The save failed')),
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
343
439
|
#### The data is a copy
|
|
344
440
|
|
|
345
441
|
Every opening holds its own **reactive copy** of the `data` it was given
|
|
@@ -421,7 +517,7 @@ default.
|
|
|
421
517
|
</div>
|
|
422
518
|
</template>
|
|
423
519
|
<template #footer="{ data, close, complete, reset }">
|
|
424
|
-
<!-- `reset()`
|
|
520
|
+
<!-- `reset()` clears the default's keys; an edit keeps its `id`. -->
|
|
425
521
|
<BbButton variant="ghost" @click="reset">Clear</BbButton>
|
|
426
522
|
<BbButton variant="ghost" @click="close">Cancel</BbButton>
|
|
427
523
|
<BbButton
|
|
@@ -462,9 +558,11 @@ const save = (saved: Member) => {
|
|
|
462
558
|
</script>
|
|
463
559
|
```
|
|
464
560
|
|
|
465
|
-
- **`reset()`** (the slots', or `useBbDialogContext(id).reset()`) restores
|
|
466
|
-
|
|
467
|
-
|
|
561
|
+
- **`reset()`** (the slots', or `useBbDialogContext(id).reset()`) restores
|
|
562
|
+
the keys `default-data` declares from a fresh copy, on an edit too, and keeps
|
|
563
|
+
every key it doesn't declare: the row's `id` stays, so a cleared edit still
|
|
564
|
+
saves as an edit. It doesn't go back to the row's values; Cancel is what
|
|
565
|
+
discards an edit.
|
|
468
566
|
- **Every close resets it** as well: nothing to do on `@show` or `@hidden`.
|
|
469
567
|
- **An object, not a function.** The dialog copies it every time and never
|
|
470
568
|
writes it, so a module constant is safe.
|
|
@@ -577,10 +675,16 @@ const createForDesign = async () => {
|
|
|
577
675
|
KeepAlive park. A flow that changes the URL inside one dialog (a wizard
|
|
578
676
|
whose steps are routes) opens it from the parent route or the persistent
|
|
579
677
|
layout, which stays mounted. A `show()` from a store lives until closed.
|
|
580
|
-
- **
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
678
|
+
- **From a store.** Create the handle in the setup store's body (or under
|
|
679
|
+
`app.runWithContext()`), then `show()` / `hide()` from its actions or a
|
|
680
|
+
router hook. Created in a plain module, outside any injection context,
|
|
681
|
+
`show()` settles `'failed'` with a dev warning.
|
|
682
|
+
- **One handle, one dialog.** `useBbDialogContext(Component)` is the same
|
|
683
|
+
handle as the id form (`isReady`, `open`, `data`, `show`, `hide`,
|
|
684
|
+
`complete`, `reset`), about its own dialog: a `show()` while it is open
|
|
685
|
+
joins it. Another handle (another `useBbDialogContext(Component)` call, or
|
|
686
|
+
another trigger element) opens its own instance on top, with its own
|
|
687
|
+
promise. `isReady` says a `<BbHost />` is mounted to render it.
|
|
584
688
|
- **Each opening gets its own DOM id**, so two stacked instances stay valid
|
|
585
689
|
HTML; `aria-controls` on the trigger follows it. A template dialog keeps the
|
|
586
690
|
`id` you gave it. To find one in an e2e test, use its role and title,
|
|
@@ -968,6 +1072,18 @@ const form = useBbFormContext('edit-member-form');
|
|
|
968
1072
|
field uses, `form.setErrors({ ...errors, _request: [message] })`, and shows in
|
|
969
1073
|
the form's `unmatchedErrors` (see BbForm § Server errors).
|
|
970
1074
|
|
|
1075
|
+
#### Steps in one dialog
|
|
1076
|
+
|
|
1077
|
+
- **Each step's first field gets `autofocus`**: it fires when the field
|
|
1078
|
+
mounts, so focus follows the step with no watcher (`focus-target` picks only
|
|
1079
|
+
where the opening starts).
|
|
1080
|
+
- **A final success step with no field is a status region**
|
|
1081
|
+
(`role="status"`), announced as it appears, not a focus target.
|
|
1082
|
+
- **A footer button that changes type in place** (Next, `type="button"`,
|
|
1083
|
+
becoming Accept, `type="submit"`, during its own click): give each its own
|
|
1084
|
+
`key`. Unkeyed, Vue reuses the element, its type flips mid-click, and the
|
|
1085
|
+
click on Next submits the form.
|
|
1086
|
+
|
|
971
1087
|
### Lifecycle events and lazy content
|
|
972
1088
|
|
|
973
1089
|
Four events bracket the transitions — `show` fires as the model turns `true`,
|
|
@@ -987,11 +1103,16 @@ Every way to close, side by side:
|
|
|
987
1103
|
| Escape, Back on a URL dialog, a backdrop click, a swipe | the user | yes: `persistent` too | `reason: 'escape'` / `'backdrop'` / `'swipe'` | on the key, Back, click or swipe |
|
|
988
1104
|
| `hide()` (context), the model set to `false` | your code | no | `{ completed: false, reason: 'hide' }` | at once |
|
|
989
1105
|
| `complete(value)` (slots, context) | your code | no | `{ completed: true, value }` | at once |
|
|
990
|
-
| `@hide` | the dialog | it reports a close, it can't stop one |
|
|
1106
|
+
| `@hide` | the dialog | it reports a close, it can't stop one | emits it (the same object) | as any close above **starts** |
|
|
991
1107
|
| `@hidden` | the dialog | it reports a close, it can't stop one | already settled | after the close transition; focus returns, `data` clears and the body unmounts then |
|
|
992
1108
|
|
|
993
1109
|
A refused close fires nothing and settles nothing. Use `@hide` for what
|
|
994
|
-
follows the decision (a toast, a refresh), `@hidden` for teardown.
|
|
1110
|
+
follows the decision (a toast, a refresh), `@hidden` for teardown. `@hide`
|
|
1111
|
+
carries the opening's result, so a live preview reverts on a dismissal with
|
|
1112
|
+
no flag of its own: `@hide="(r) => { if (!r.completed) restore() }"`. To
|
|
1113
|
+
tell a save from a cancel, close a save with `complete()` (slots or context),
|
|
1114
|
+
not by writing the model: on a `v-model` dialog too, it closes through the
|
|
1115
|
+
model and reports `{ completed: true }`.
|
|
995
1116
|
|
|
996
1117
|
The body lives **per opening**: nothing inside the dialog renders until it
|
|
997
1118
|
opens, and the body unmounts after the close transition (right after
|
|
@@ -1003,7 +1124,9 @@ tests that query content before opening): `data` still clears while closed (to
|
|
|
1003
1124
|
`undefined`, or the `default-data` copy), so an eager body guards its own
|
|
1004
1125
|
reads. `transition-duration` (ms, default
|
|
1005
1126
|
`250`) times both the open/close animation and when `hidden` fires — reopening
|
|
1006
|
-
during the close transition keeps the dialog open instead of snapping shut
|
|
1127
|
+
during the close transition keeps the dialog open instead of snapping shut, with
|
|
1128
|
+
a fresh body: every new opening gets one, a reopen mid-close included (an
|
|
1129
|
+
`eager` body is kept).
|
|
1007
1130
|
|
|
1008
1131
|
### Size
|
|
1009
1132
|
|
|
@@ -1056,6 +1179,11 @@ Every dismissal path is governed by two props, and by a form inside:
|
|
|
1056
1179
|
- `true`: always locked.
|
|
1057
1180
|
- `false`: never locked, not even mid-save. Closing mid-save is then on you.
|
|
1058
1181
|
|
|
1182
|
+
Binding it to your own flag: beside a `BbForm`, another async action (a
|
|
1183
|
+
footer Delete) binds `:prevent-close="deleting || undefined"`; a plain
|
|
1184
|
+
`false` while idle would switch the form's lock off. A dialog with no
|
|
1185
|
+
`BbForm` binds `:prevent-close="saving"`.
|
|
1186
|
+
|
|
1059
1187
|
`persistent` keeps the ✕ (and the slots' `close`) as the deliberate exit;
|
|
1060
1188
|
`prevent-close` removes every exit but the model.
|
|
1061
1189
|
|
|
@@ -1580,6 +1708,10 @@ A `pt` value written in `<script>` (a constant, a preset, a wrapper's
|
|
|
1580
1708
|
`href`/`to` to the button.
|
|
1581
1709
|
- **Don't toggle `v-model` from `@hide`/`@hidden` handlers** to "cancel" a
|
|
1582
1710
|
close — use `persistent` instead; the close paths already guard correctly.
|
|
1711
|
+
- **Catch `complete()` in a reusable dialog** whenever an opener may pass an
|
|
1712
|
+
`onComplete` that rejects (`complete(data).catch(…)`, as in § The page
|
|
1713
|
+
saves). Uncaught, the rejection travels through the form's `@submit` and
|
|
1714
|
+
reaches `app.config.errorHandler`.
|
|
1583
1715
|
- **Don't leave the `header` slot without a close control** — it replaces the
|
|
1584
1716
|
default ×, so you must render your own wired to `close`.
|
|
1585
1717
|
- **Don't query dialog content before it has opened, or after it has closed**,
|
|
@@ -1625,7 +1757,7 @@ A `pt` value written in `<script>` (a constant, a preset, a wrapper's
|
|
|
1625
1757
|
| --- | --- | --- | --- | --- |
|
|
1626
1758
|
| `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: `close`, `content`, `description`, `footer`, `header`, `root`, `sheet`, `title`. States: `fullscreen`, `open`. 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 `BbDialogProps['pt']` (`PtValue` is not exported): § Typing `pt` in `<script>`. |
|
|
1627
1759
|
| `adaptive` | `boolean \| undefined` | ``config.adaptive` (`true`)` | | On mobile, renders as a `BbOffCanvas` drawer instead of a centred dialog; the surface is chosen when the dialog opens and kept until it closes. Unset: the global `config.adaptive`. |
|
|
1628
|
-
| `defaultData` | `Record<string, any> \| undefined` | | | The `data` of an opening that passes none (a create), as an object: copied at once and exposed as `data`, so inputs bind `data.x` with no guard. An opening with data lays it over this (`{ ...defaultData, ...row }`, one level). `reset()` restores
|
|
1760
|
+
| `defaultData` | `Record<string, any> \| undefined` | | | The `data` of an opening that passes none (a create), as an object: copied at once and exposed as `data`, so inputs bind `data.x` with no guard. An opening with data lays it over this (`{ ...defaultData, ...row }`, one level). `reset()` restores its keys, keeping an edit's `id`; a close restores it. |
|
|
1629
1761
|
| `description` | `string \| undefined` | | | One short line under the title, muted (the shadcn `DialogDescription` shape), and the only element `aria-describedby` points at. Keep it to a sentence; anything longer belongs in the body. |
|
|
1630
1762
|
| `eager` | `boolean \| undefined` | `false` | | Keep the body mounted while closed, from before the first opening. Without it the body mounts per opening and unmounts after `@hidden`. `data` still clears while closed, so an eager body guards its reads. |
|
|
1631
1763
|
| `focusTarget` | `string \| undefined` | | | CSS selector of the element to focus after opening. Defaults to the first tabbable element. |
|
|
@@ -1636,7 +1768,7 @@ A `pt` value written in `<script>` (a constant, a preset, a wrapper's
|
|
|
1636
1768
|
| `navigation` | `boolean \| undefined` | `false` | | Syncs the open state with the URL: opening writes `?<queryKey>=true`, closing removes it, a URL carrying the key opens the dialog on load, and Back closes it (refused like Escape when locked). Template dialogs only; a record id stays in your own query. |
|
|
1637
1769
|
| `offCanvasProps` | `Partial<BbOffCanvasProps> \| undefined` | | | Props for the phone sheet (the `BbOffCanvas` that `adaptive` renders on mobile), spread over its defaults. A `pt` inside it styles only the sheet (passthrough guide § Inside the sheet). |
|
|
1638
1770
|
| `persistent` | `boolean \| undefined` | `false` | | Prevent closing via outside clicks or Escape key. The header close (×) button STILL closes a persistent dialog — that is the documented escape hatch; combine with `hideClose` to remove it, or use `preventClose` to block every dismissal. Programmatic close is still allowed. |
|
|
1639
|
-
| `preventClose` | `boolean \| undefined` | ``undefined` (follows a saving form inside)` | | Refuses every user close (backdrop, Escape and the ✕, and the slots' `close`) with the deny nudge; `v-model` still closes. Unset: locked while a `BbForm` inside saves (an async `@submit`). `true`: always. `false`: never, not even mid-save. |
|
|
1771
|
+
| `preventClose` | `boolean \| undefined` | ``undefined` (follows a saving form inside)` | | Refuses every user close (backdrop, Escape and the ✕, and the slots' `close`) with the deny nudge; `v-model` still closes. Unset: locked while a `BbForm` inside saves (an async `@submit`). `true`: always. `false`: never, not even mid-save. Beside a form: `busy \|\| undefined`. |
|
|
1640
1772
|
| `queryKey` | `string \| undefined` | `"dialog"` | | The query key `navigation` writes. |
|
|
1641
1773
|
| `replace` | `boolean \| undefined` | `false` | | With `navigation`, replaces the history entry instead of pushing one, so Back leaves the page instead of closing the dialog. |
|
|
1642
1774
|
| `size` | `Responsive<NonNullable<number \| CssSize \| keyof BbDialogSizes \| undefined>> \| undefined` | `"sm"` | | Controls the maximum width of the dialog. Accepts named presets from `'xs'` to `'2xl'`, a custom CSS width string, or a numeric pixel value — or a per-breakpoint map (e.g. `{ default: 'sm', lg: 'lg' }`) to switch width responsively. |
|
|
@@ -1671,7 +1803,7 @@ States are listed in precedence order: when two are on at once and their entries
|
|
|
1671
1803
|
## Events
|
|
1672
1804
|
|
|
1673
1805
|
- `hidden` — `(): void` — Emitted after the close transition completes and the dialog is fully dismissed.
|
|
1674
|
-
- `hide` — `(): void` — Emitted as soon as the dialog starts to close (model becomes false).
|
|
1806
|
+
- `hide` — `(result: BbDialogResult): void` — Emitted as soon as the dialog starts to close (model becomes false), with the opening's result: the same object its promise settles with, `{ completed: true, value }` or `{ completed: false, reason }` (Q73.6).
|
|
1675
1807
|
- `show` — `(): void` — Emitted as soon as the dialog starts to open (model becomes true).
|
|
1676
1808
|
- `shown` — `(): void` — Emitted once the open transition has run (symmetric with `hidden`): the dialog is settled, so code that measures or anchors to its content sees the final layout. Not emitted for a dialog closed before it finished opening.
|
|
1677
1809
|
- `update:modelValue` — `(value: boolean): void` — Emitted with the new value whenever `v-model` changes.
|
|
@@ -1679,10 +1811,10 @@ States are listed in precedence order: when two are on at once and their entries
|
|
|
1679
1811
|
|
|
1680
1812
|
## Slots
|
|
1681
1813
|
|
|
1682
|
-
- `default` — scope: `BbDialogDefaultSlotProps` `{ close: () => void; complete: (value?: any) => void
|
|
1814
|
+
- `default` — scope: `BbDialogDefaultSlotProps` `{ close: () => void; complete: (value?: any) => Promise<void>; reset: () => void; data: any }` — Primary body content of the dialog, rendered in the scrollable body area.
|
|
1683
1815
|
- `description` — scope: `BbDialogDescriptionSlotProps` `{ text: string; data: any }` — Replaces the description text under the title. Renders the header even when `title` and `description` are unset — a slotted description is a description, the same rule `#title` follows.
|
|
1684
|
-
- `footer` — scope: `BbDialogFooterSlotProps` `{ close: () => void; complete: (value?: any) => void
|
|
1685
|
-
- `header` — scope: `BbDialogHeaderSlotProps` `{ titleId: string; close: () => void; complete: (value?: any) => void
|
|
1816
|
+
- `footer` — scope: `BbDialogFooterSlotProps` `{ close: () => void; complete: (value?: any) => Promise<void>; reset: () => void; data: any }` — Content rendered in the dialog footer, below the body area. Typically used for action buttons.
|
|
1817
|
+
- `header` — scope: `BbDialogHeaderSlotProps` `{ titleId: string; close: () => void; complete: (value?: any) => Promise<void>; reset: () => void; title: string; data: any }` — Replaces the whole header (title and ✕) on both surfaces, so it owns closing: wire your control to `close`, and `titleId` onto your title. It renders even without `title`. Its root is a flex item: give it `flex-1` to fill the width.
|
|
1686
1818
|
- `title` — scope: `BbDialogTitleSlotProps` `{ text: string; data: any }` — Replaces the default title text inside the header. Remains linked to the dialog through `aria-labelledby`. Providing this slot renders the header even when `title` is unset — a slotted title is a title.
|
|
1687
1819
|
|
|
1688
1820
|
## CSS custom properties
|
package/dist/ai/BbDropdown.md
CHANGED
|
@@ -2124,8 +2124,8 @@ A `pt` value written in `<script>` (a constant, a preset, a wrapper's
|
|
|
2124
2124
|
width; pass a number when menus must align across rows.
|
|
2125
2125
|
- Item `variant`s other than `destructive` render only a class hook — register
|
|
2126
2126
|
names via the `dropdownItemVariants` plugin option and ship their CSS.
|
|
2127
|
-
- `<BbDropdownGroup>` must sit in a `BbDropdown` (
|
|
2128
|
-
|
|
2127
|
+
- `<BbDropdownGroup>` must sit in a `BbDropdown`'s default slot (a
|
|
2128
|
+
`BbDropdownButton`'s `#dropdown`), its `id` must match a group's `id`, and a `multiple` group's
|
|
2129
2129
|
model must be an array — all three misuses throw.
|
|
2130
2130
|
- Placeholder texts default to the config locale's "Loading…" / "No results"
|
|
2131
2131
|
strings (`locale` plugin option) unless you set `loading-text` /
|