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.
- package/bin/bitboss-ui-mcp.mjs +103 -15
- package/bin/bitboss-ui.mjs +3 -1
- package/dist/ai/BbAlert.md +3 -0
- package/dist/ai/BbConfirm.md +46 -22
- package/dist/ai/BbForm.md +68 -5
- package/dist/ai/BbHost.md +11 -13
- package/dist/ai/BbPtScope.md +6 -4
- package/dist/ai/BbTable.md +180 -47
- package/dist/ai/BbToast.md +2 -1
- package/dist/ai/changelog.json +70 -4
- package/dist/ai/components.json +6 -6
- package/dist/ai/composables/useConfirm.md +8 -5
- package/dist/ai/composables/useToast.md +15 -15
- package/dist/ai/guides/agent-contract.md +3 -2
- package/dist/ai/guides/eslint-rules.md +2 -0
- package/dist/ai/guides/inertia-helpers.md +39 -7
- package/dist/ai/guides/installation-and-plugin-setup.md +2 -1
- package/dist/ai/guides/migration/components/bb-host.md +4 -2
- package/dist/ai/guides/migration/components/use-confirm.md +15 -15
- package/dist/ai/guides/migration/v2-to-v3.md +23 -0
- package/dist/ai/guides/migration/v3-beta-tooling.md +6 -0
- package/dist/ai/guides/passthrough.md +58 -58
- package/dist/ai/guides/validated-forms.md +8 -2
- package/dist/ai/helpers/inertia/manifest.json +18 -10
- package/dist/ai/helpers/inertia/useConfirmOnRouteLeave.ts +11 -2
- package/dist/ai/helpers/inertia/usePromiseForm.ts +17 -3
- package/dist/ai/helpers/inertia/useResourceContext.ts +1 -2
- package/dist/ai/manifest/components/BbHost.tree.json +1 -2
- package/dist/ai/manifest/components/BbTable.json +11 -7
- package/dist/ai/manifest/components/BbTable.tree.json +4 -1
- package/dist/ai/manifest/index.json +2 -2
- package/dist/ai/manifest/types.advanced.json +2 -2
- package/dist/ai/recipes/inertia/onboarding.md +10 -6
- package/dist/ai/recipes/inertia/record-form.md +6 -2
- package/dist/ai/recipes/inertia/wizard-form.md +9 -5
- package/dist/ai/source/BbConfirm.md +33 -21
- package/dist/ai/source/BbConfirmPortal.md +7 -16
- package/dist/ai/source/BbDatePickerInput.md +6 -1
- package/dist/ai/source/BbForm.md +7 -2
- package/dist/ai/source/BbHost.md +3 -3
- package/dist/ai/source/BbTable.md +440 -120
- package/dist/ai/source/BbTimePickerInput.md +5 -1
- package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +83 -74
- package/dist/components/BbConfirm/BbConfirmHost.d.ts +10 -0
- package/dist/components/BbConfirm/BbConfirmHost.js +32 -0
- package/dist/components/BbConfirm/BbConfirmPortal.vue_vue_type_script_setup_true_lang.js +8 -12
- package/dist/components/BbConfirm/confirmEntry.d.ts +4 -0
- package/dist/components/BbConfirm/confirmEntry.js +4 -0
- package/dist/components/BbConfirm/types.d.ts +2 -2
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +211 -210
- package/dist/components/BbForm/BbForm.vue_vue_type_script_setup_true_lang.js +3 -1
- package/dist/components/BbHost/BbHost.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +1302 -1184
- package/dist/components/BbTable/BbTableDataRow.js +17 -12
- package/dist/components/BbTable/BbTableExpandRow.d.ts +1 -1
- package/dist/components/BbTable/BbTableExpandRow.js +29 -25
- package/dist/components/BbTable/BbTableSelectToggle.d.ts +1 -1
- package/dist/components/BbTable/types.d.ts +51 -18
- package/dist/components/BbTable/types.js +3 -0
- package/dist/components/BbTable/utils.d.ts +73 -13
- package/dist/components/BbTable/utils.js +148 -37
- package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +108 -106
- package/dist/composables/useBbFormContext.d.ts +7 -3
- package/dist/composables/useConfirm.d.ts +327 -33
- package/dist/composables/useConfirm.js +93 -71
- package/dist/composables/useFormBinding.d.ts +5 -0
- package/dist/composables/useFormBinding.js +2 -1
- package/dist/i18n/locales/cs.json +1 -0
- package/dist/i18n/locales/da.json +1 -0
- package/dist/i18n/locales/de-CH.json +1 -0
- package/dist/i18n/locales/de.json +1 -0
- package/dist/i18n/locales/el.json +1 -0
- package/dist/i18n/locales/en-GB.json +1 -0
- package/dist/i18n/locales/en.json +1 -0
- package/dist/i18n/locales/es-MX.json +1 -0
- package/dist/i18n/locales/es.json +1 -0
- package/dist/i18n/locales/fi.json +1 -0
- package/dist/i18n/locales/fr.json +1 -0
- package/dist/i18n/locales/hu.json +1 -0
- package/dist/i18n/locales/it.json +1 -0
- package/dist/i18n/locales/ja.json +1 -0
- package/dist/i18n/locales/ko.json +1 -0
- package/dist/i18n/locales/nb.json +1 -0
- package/dist/i18n/locales/nl.json +1 -0
- package/dist/i18n/locales/pl.json +1 -0
- package/dist/i18n/locales/pt-BR.json +1 -0
- package/dist/i18n/locales/pt-PT.json +1 -0
- package/dist/i18n/locales/ro.json +1 -0
- package/dist/i18n/locales/ru.json +1 -0
- package/dist/i18n/locales/sv.json +1 -0
- package/dist/i18n/locales/tr.json +1 -0
- package/dist/i18n/locales/uk.json +1 -0
- package/dist/i18n/locales/zh-CN.json +1 -0
- package/dist/i18n/locales/zh-HK.json +1 -0
- package/dist/i18n/locales/zh-TW.json +1 -0
- package/dist/llms-full.txt +524 -212
- package/dist/llms-medium.txt +5 -3
- package/dist/locale-blueprint.json +1 -0
- package/dist/styles.css +2 -2
- package/dist/types/passthrough.d.ts +1 -1
- package/dist/types/ptComponentMap.d.ts +1 -1
- package/dist/utils/versionCheck.js +1 -1
- package/package.json +8 -4
- package/scripts/lib/doc-sections.mjs +26 -6
- package/scripts/lib/eslint-disable.mjs +2 -0
- package/scripts/lib/eslint-plugin.d.ts +2 -0
- package/scripts/lib/eslint-plugin.mjs +105 -0
- package/scripts/lib/validate-bb-markup.mjs +182 -66
package/bin/bitboss-ui-mcp.mjs
CHANGED
|
@@ -1923,9 +1923,17 @@ let cachedDocIndex = null;
|
|
|
1923
1923
|
function docIndex() {
|
|
1924
1924
|
if (cachedDocIndex) return cachedDocIndex;
|
|
1925
1925
|
const entries = [];
|
|
1926
|
-
|
|
1926
|
+
// What a document says before its first `##` (its lead), and then every
|
|
1927
|
+
// section. The lead used to be skipped, and a composable was cut at 4000
|
|
1928
|
+
// chars: useToast's object-form row sat past the cut (2026-10-20).
|
|
1929
|
+
// `check-mcp` walks dist/ai and fails on any doc unit left out of here.
|
|
1930
|
+
const addSections = (source, markdown, read, lead, skip = () => false) => {
|
|
1931
|
+
const intro = stripFrontmatter(markdown).split(/^## /m)[0].trim();
|
|
1932
|
+
if (lead && intro.length >= 40)
|
|
1933
|
+
entries.push({ source, ...lead, text: intro });
|
|
1927
1934
|
for (const section of splitSections(markdown)) {
|
|
1928
|
-
|
|
1935
|
+
// `skip` first: it tracks the `##` it is under, even a bare one.
|
|
1936
|
+
if (skip(section) || section.own.length < 40) continue;
|
|
1929
1937
|
entries.push({
|
|
1930
1938
|
source,
|
|
1931
1939
|
section: section.title,
|
|
@@ -1936,13 +1944,18 @@ function docIndex() {
|
|
|
1936
1944
|
};
|
|
1937
1945
|
const guidesDir = join(AI_ROOT, 'guides');
|
|
1938
1946
|
for (const path of collectGuidePaths(guidesDir)) {
|
|
1947
|
+
const markdown = readFileSync(join(guidesDir, path), 'utf-8');
|
|
1939
1948
|
addSections(
|
|
1940
1949
|
`guide ${path}`,
|
|
1941
|
-
|
|
1950
|
+
markdown,
|
|
1942
1951
|
(title) => ({
|
|
1943
1952
|
tool: 'get_guide',
|
|
1944
1953
|
args: { name: path, sections: [title] },
|
|
1945
|
-
})
|
|
1954
|
+
}),
|
|
1955
|
+
{
|
|
1956
|
+
section: frontmatter(markdown).title ?? path,
|
|
1957
|
+
read: { tool: 'get_guide', args: { name: path } },
|
|
1958
|
+
}
|
|
1946
1959
|
);
|
|
1947
1960
|
}
|
|
1948
1961
|
const composablesDir = join(AI_ROOT, 'composables');
|
|
@@ -1952,16 +1965,22 @@ function docIndex() {
|
|
|
1952
1965
|
)) {
|
|
1953
1966
|
const markdown = readFileSync(join(composablesDir, file), 'utf-8');
|
|
1954
1967
|
const name = file.slice(0, -3);
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1968
|
+
addSections(
|
|
1969
|
+
`composable ${name}`,
|
|
1970
|
+
markdown,
|
|
1971
|
+
(title) => ({
|
|
1972
|
+
tool: 'get_composable',
|
|
1973
|
+
args: { name, sections: [title] },
|
|
1974
|
+
}),
|
|
1975
|
+
{
|
|
1976
|
+
section: frontmatter(markdown).summary ?? name,
|
|
1977
|
+
read: { tool: 'get_composable', args: { name } },
|
|
1978
|
+
}
|
|
1979
|
+
);
|
|
1961
1980
|
}
|
|
1962
1981
|
}
|
|
1963
1982
|
const manifest = loadComponentsManifest();
|
|
1964
|
-
for (const component of manifest.components
|
|
1983
|
+
for (const component of manifest.components) {
|
|
1965
1984
|
const path = join(PACKAGE_ROOT, 'dist', component.contract ?? '');
|
|
1966
1985
|
if (component.contract && existsSync(path)) {
|
|
1967
1986
|
for (const section of contractGuideSections(
|
|
@@ -1979,6 +1998,11 @@ function docIndex() {
|
|
|
1979
1998
|
});
|
|
1980
1999
|
}
|
|
1981
2000
|
}
|
|
2001
|
+
// BbToast and BbConfirm are reference-only (a composable drives them)
|
|
2002
|
+
// but their guides are the docs: § Variant icons never ranked
|
|
2003
|
+
// (progetto-32, 2026-10-20). Internals have no guide, so nothing else
|
|
2004
|
+
// comes in; their props stay out.
|
|
2005
|
+
if (component.referenceOnly) continue;
|
|
1982
2006
|
for (const prop of component.props) {
|
|
1983
2007
|
if (prop.name.startsWith('pt')) continue;
|
|
1984
2008
|
entries.push({
|
|
@@ -2003,6 +2027,45 @@ function docIndex() {
|
|
|
2003
2027
|
},
|
|
2004
2028
|
});
|
|
2005
2029
|
}
|
|
2030
|
+
// Slots, v-models and pt parts / states: `members` serves each row, and
|
|
2031
|
+
// "BbSelect option slot" had nothing to find.
|
|
2032
|
+
const member = (section, text, name) =>
|
|
2033
|
+
entries.push({
|
|
2034
|
+
source: `component ${component.name}`,
|
|
2035
|
+
section,
|
|
2036
|
+
text,
|
|
2037
|
+
read: {
|
|
2038
|
+
tool: 'get_component',
|
|
2039
|
+
args: { name: component.name, members: [name] },
|
|
2040
|
+
},
|
|
2041
|
+
});
|
|
2042
|
+
for (const slot of component.slots ?? [])
|
|
2043
|
+
member(
|
|
2044
|
+
`slot #${slot.name}`,
|
|
2045
|
+
`#${slot.name} slot. ${slot.description ?? ''}`,
|
|
2046
|
+
`#${slot.name}`
|
|
2047
|
+
);
|
|
2048
|
+
for (const model of component.models ?? []) {
|
|
2049
|
+
const name =
|
|
2050
|
+
model.name === 'modelValue' ? 'v-model' : `v-model:${model.name}`;
|
|
2051
|
+
member(
|
|
2052
|
+
name,
|
|
2053
|
+
`${name}: ${model.type ?? ''}. ${model.description ?? ''}`,
|
|
2054
|
+
name
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
for (const part of component.pt?.parts ?? [])
|
|
2058
|
+
member(
|
|
2059
|
+
`pt part ${part.name}`,
|
|
2060
|
+
`pt:${part.name} part. ${part.description ?? ''}`,
|
|
2061
|
+
`pt:${part.name}`
|
|
2062
|
+
);
|
|
2063
|
+
for (const state of component.pt?.states ?? [])
|
|
2064
|
+
member(
|
|
2065
|
+
`pt state ${state.name}`,
|
|
2066
|
+
`pt state ${state.name}. ${state.description ?? ''}`,
|
|
2067
|
+
`pt:${state.name}`
|
|
2068
|
+
);
|
|
2006
2069
|
/*
|
|
2007
2070
|
* The rename registry: an agent upgrading an app searches the OLD name
|
|
2008
2071
|
* (`enabled-while-loading`, `pt:input`, `#no-data`), which no current
|
|
@@ -2053,6 +2116,22 @@ function docIndex() {
|
|
|
2053
2116
|
text: frontmatter(markdown).summary ?? name,
|
|
2054
2117
|
read: { tool: 'get_recipe', args: { platform, name } },
|
|
2055
2118
|
});
|
|
2119
|
+
// The prose sections; the code files under "The code" are `files`.
|
|
2120
|
+
let inCode = false;
|
|
2121
|
+
addSections(
|
|
2122
|
+
`recipe ${platform}/${name}`,
|
|
2123
|
+
markdown,
|
|
2124
|
+
(title) => ({
|
|
2125
|
+
tool: 'get_recipe',
|
|
2126
|
+
args: { platform, name, sections: [title] },
|
|
2127
|
+
}),
|
|
2128
|
+
null,
|
|
2129
|
+
(section) => {
|
|
2130
|
+
if (section.level === 2)
|
|
2131
|
+
inCode = /^the (code|files)\b/i.test(section.title);
|
|
2132
|
+
return inCode;
|
|
2133
|
+
}
|
|
2134
|
+
);
|
|
2056
2135
|
}
|
|
2057
2136
|
}
|
|
2058
2137
|
// Class renames (the sweep map rows of changelog.json): an app's
|
|
@@ -2151,10 +2230,18 @@ function docIndex() {
|
|
|
2151
2230
|
if (existsSync(readme)) {
|
|
2152
2231
|
// Served by get_guide under the name 'README' (it used to point at a
|
|
2153
2232
|
// "read file" call, which is not a tool).
|
|
2154
|
-
addSections(
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2233
|
+
addSections(
|
|
2234
|
+
'README',
|
|
2235
|
+
readFileSync(readme, 'utf-8'),
|
|
2236
|
+
(title) => ({
|
|
2237
|
+
tool: 'get_guide',
|
|
2238
|
+
args: { name: 'README', sections: [title] },
|
|
2239
|
+
}),
|
|
2240
|
+
{
|
|
2241
|
+
section: 'README',
|
|
2242
|
+
read: { tool: 'get_guide', args: { name: 'README' } },
|
|
2243
|
+
}
|
|
2244
|
+
);
|
|
2158
2245
|
}
|
|
2159
2246
|
cachedDocIndex = entries;
|
|
2160
2247
|
return entries;
|
|
@@ -3341,6 +3428,7 @@ export {
|
|
|
3341
3428
|
SERVER_INSTRUCTIONS,
|
|
3342
3429
|
searchComponents,
|
|
3343
3430
|
searchDocs,
|
|
3431
|
+
docIndex,
|
|
3344
3432
|
getComposable,
|
|
3345
3433
|
getComponent,
|
|
3346
3434
|
listTokens,
|
package/bin/bitboss-ui.mjs
CHANGED
|
@@ -1735,7 +1735,9 @@ Commands:
|
|
|
1735
1735
|
\`warnings[]\` (never fail the run) use the same shape with
|
|
1736
1736
|
\`severity: "warning"\`: \`likely-squashed-slot\` (a slot
|
|
1737
1737
|
name that looks like a camelCase key squashed, e.g.
|
|
1738
|
-
\`#createdat\`), \`
|
|
1738
|
+
\`#createdat\`), \`computed-slot-key\` (a computed slot
|
|
1739
|
+
name over keys \`check\` cannot see, not spelled
|
|
1740
|
+
through \`slotKey(…)\`), \`local-bb-component\` (your own
|
|
1739
1741
|
component uses the \`Bb\` prefix; rename it),
|
|
1740
1742
|
\`single-reason\` (an \`items\` provider that tests
|
|
1741
1743
|
\`reason\` against one cause other than \`'search'\`;
|
package/dist/ai/BbAlert.md
CHANGED
|
@@ -107,6 +107,9 @@ distinct success tone.
|
|
|
107
107
|
|
|
108
108
|
#### Variant icons
|
|
109
109
|
|
|
110
|
+
A default icon per variant, set once for the whole app: the object form of
|
|
111
|
+
the plugin's `alertVariants` option.
|
|
112
|
+
|
|
110
113
|
```ts
|
|
111
114
|
// vite.config.ts (Nuxt: the `bitboss` key)
|
|
112
115
|
bitbossUi({
|
package/dist/ai/BbConfirm.md
CHANGED
|
@@ -72,23 +72,21 @@ first — this is that gate.
|
|
|
72
72
|
Mount `<BbHost />` **exactly once, at the app root** (`App.vue` under Vue,
|
|
73
73
|
`app.vue` under Nuxt, the `createInertiaApp` render root under Inertia) — a
|
|
74
74
|
single instance serves the whole app, and `confirm()` can then be called from
|
|
75
|
-
anywhere, components and stores alike. **
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
row show only the second, and the first silently answers "no". If you need both
|
|
79
|
-
questions asked, `await` the first before opening the second.
|
|
75
|
+
anywhere, components and stores alike. **Every call opens its own dialog**,
|
|
76
|
+
and a new one covers the open one, which stays pending and comes back when
|
|
77
|
+
the newer one closes (see [Several confirms at once](#several-confirms-at-once)).
|
|
80
78
|
A second host renders nothing (a dev-mode console warning says so). With no
|
|
81
79
|
host, `await confirm(...)` resolves `false` after one second, with a dev
|
|
82
80
|
warning. A host that unmounts while its dialog is open (a layout the user
|
|
83
81
|
navigated out of) gets the same second to come back; after that the open
|
|
84
82
|
confirm resolves `false` without running a callback, so it never hangs.
|
|
85
83
|
|
|
86
|
-
**Areas with their own look.**
|
|
87
|
-
tokens of the
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
**Areas with their own look.** A confirm takes the passthrough scope and the
|
|
85
|
+
tokens of the component that called `useConfirm()`, wherever the host is
|
|
86
|
+
mounted: a page inside a `<BbPtScope>` ([passthrough guide](./guides/passthrough.md) § Named scopes)
|
|
87
|
+
gets confirms in its area's look with nothing to set up. A component portal
|
|
88
|
+
injects from that component's tree too. A `useConfirm()` called outside a
|
|
89
|
+
component (a store, module level) takes the host's.
|
|
92
90
|
|
|
93
91
|
```vue
|
|
94
92
|
<template>
|
|
@@ -105,10 +103,9 @@ import { BbHost } from 'bitboss-ui';
|
|
|
105
103
|
standalone example has no app root to mount it in.** In a real app the host is
|
|
106
104
|
already mounted at the root; a page that calls `confirm()` mounts nothing.
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
`
|
|
111
|
-
sets its own (see [Size, fullscreen and focus](#size-fullscreen-and-focus)).
|
|
106
|
+
Everything — content, buttons, behavior — travels in the message you pass to
|
|
107
|
+
`confirm()`; the width every confirm defaults to is the plugin's
|
|
108
|
+
`confirm.defaultSize` (see [Size, fullscreen and focus](#size-fullscreen-and-focus)).
|
|
112
109
|
At least one of `title`, `text`, `portal` should be set (dev warning
|
|
113
110
|
otherwise).
|
|
114
111
|
|
|
@@ -402,7 +399,7 @@ the promise `false`, so your `if (ok)` branch is never reached accidentally.
|
|
|
402
399
|
decline-only behavior in `no.onClick`, not in the `if (!ok)` branch. The rule
|
|
403
400
|
that decides whether `no.onClick` runs: **user-initiated** dismissals
|
|
404
401
|
(backdrop, `Escape`, the X) honor `dismissAsNo`; **system-initiated** ones
|
|
405
|
-
(`duration` timeout, owner unmount,
|
|
402
|
+
(`duration` timeout, owner unmount, `dismissAll()`)
|
|
406
403
|
always resolve quietly, never running any callback. The knobs:
|
|
407
404
|
|
|
408
405
|
- `dismissAsNo` (default `true`) — user-initiated implicit dismissals also run
|
|
@@ -435,7 +432,7 @@ the peek closes. For app-lifetime confirms that survive any unmount, call
|
|
|
435
432
|
`useConfirm()` outside a component scope (app setup, a store, module level).
|
|
436
433
|
|
|
437
434
|
`useConfirm()` also returns `dismissAll()` — an imperative escape hatch that
|
|
438
|
-
quietly dismisses
|
|
435
|
+
quietly dismisses every open confirm regardless of owner, for
|
|
439
436
|
app-level events like logout or a global error boundary.
|
|
440
437
|
|
|
441
438
|
The classic case for `persistent` is the unsaved-changes prompt: an accidental
|
|
@@ -511,6 +508,8 @@ By default the dialog closes as soon as the promise settles. Set
|
|
|
511
508
|
- `close()` only hides the dialog; **it never resolves anything**. Calling it
|
|
512
509
|
on a dialog whose buttons haven't run yet leaves that `confirm()` promise
|
|
513
510
|
pending forever — it is a flow terminator, not a substitute for the buttons.
|
|
511
|
+
- `close()` hides the newest open confirm opened through the **same**
|
|
512
|
+
`useConfirm()`, never another component's.
|
|
514
513
|
|
|
515
514
|
**Keep the dialog open across steps**
|
|
516
515
|
|
|
@@ -585,6 +584,31 @@ const exportWorkspace = async () => {
|
|
|
585
584
|
</script>
|
|
586
585
|
```
|
|
587
586
|
|
|
587
|
+
### Several confirms at once
|
|
588
|
+
|
|
589
|
+
Confirms stack. A `confirm()` while another is open (from a button's
|
|
590
|
+
`onClick`, or two calls without an `await` between them) covers the open one
|
|
591
|
+
in place; the one beneath stays pending, and comes back, still waiting for
|
|
592
|
+
its answer, when the newer one closes. Escape and the backdrop answer only
|
|
593
|
+
the one on top. Two un-awaited calls are therefore both asked, newest first:
|
|
594
|
+
`await` each one when the order matters.
|
|
595
|
+
|
|
596
|
+
`stack` picks the `BbDialog` stack a confirm joins (every confirm joins the
|
|
597
|
+
confirms' own by default). Name a dialog's `stack` and the confirm replaces
|
|
598
|
+
that dialog's view while it is open, as a drill-in step would ([BbDialog](./BbDialog.md)
|
|
599
|
+
§ Stacked dialogs); a name no other dialog uses shows it on top of the other
|
|
600
|
+
confirms, each dimming the one beneath.
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
// An unsaved-changes question that takes the editor's place, not a layer on it.
|
|
604
|
+
const discard = await confirm({
|
|
605
|
+
title: 'Discard your changes?',
|
|
606
|
+
stack: 'editor', // the editor's <BbDialog stack="editor">
|
|
607
|
+
variant: 'destructive',
|
|
608
|
+
yes: 'Discard',
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
588
612
|
### Rich content with portals
|
|
589
613
|
|
|
590
614
|
When plain `text` isn't enough, render custom content in the dialog body via a
|
|
@@ -740,9 +764,9 @@ The map is typed where you write it by `definePt`, never with an imported
|
|
|
740
764
|
|
|
741
765
|
- **Don't hand-build a yes/no dialog** out of `BbDialog` + buttons — busy
|
|
742
766
|
states, dismissal semantics, ownership and focus handling already live here.
|
|
743
|
-
- **
|
|
744
|
-
|
|
745
|
-
|
|
767
|
+
- **Two `confirm()` calls without an `await` between them ask both, newest
|
|
768
|
+
first** — the second covers the first. `await` each one when the questions
|
|
769
|
+
must come in order.
|
|
746
770
|
- **Don't forget the `await`** — `confirm(...)` without it never gates
|
|
747
771
|
anything.
|
|
748
772
|
- **Don't call `close()` to "cancel"** a dialog whose buttons haven't run —
|
|
@@ -753,8 +777,8 @@ The map is typed where you write it by `definePt`, never with an imported
|
|
|
753
777
|
- With `returning: true`, an implicit dismiss under the default
|
|
754
778
|
`dismissAsNo: true` resolves with `no.onClick`'s return value — give No a
|
|
755
779
|
callback that returns a sensible "declined" value.
|
|
756
|
-
System dismissals (`duration`, `dismissAll()`,
|
|
757
|
-
|
|
780
|
+
System dismissals (`duration`, `dismissAll()`, owner unmount) still resolve
|
|
781
|
+
`false`, so handle `false` too.
|
|
758
782
|
- `confirm()` rejects keys `BbConfirmMessage` doesn't declare, also on a
|
|
759
783
|
message built in a variable first: build it from `BbConfirmMessage` fields
|
|
760
784
|
only (spread a wider object and the extra keys fail to type-check).
|
package/dist/ai/BbForm.md
CHANGED
|
@@ -1076,7 +1076,11 @@ const bodyRules = computed(() => (publishing.value ? 'required' : ''));
|
|
|
1076
1076
|
```
|
|
1077
1077
|
|
|
1078
1078
|
Both switch live: the next validation uses the new schema or rules, and
|
|
1079
|
-
switching back lets a submit through that was blocked a moment ago.
|
|
1079
|
+
switching back lets a submit through that was blocked a moment ago. A message
|
|
1080
|
+
already on screen is not taken down by the switch: it stays until the field
|
|
1081
|
+
validates again (an edit, a blur, the next submit). If the condition lives in
|
|
1082
|
+
another field and the stale message would mislead, show it through
|
|
1083
|
+
[`field-errors`](#a-message-of-your-own-that-comes-and-goes) instead.
|
|
1080
1084
|
|
|
1081
1085
|
### While it saves
|
|
1082
1086
|
|
|
@@ -1353,6 +1357,54 @@ async function save(values: Record<string, unknown>) {
|
|
|
1353
1357
|
}
|
|
1354
1358
|
```
|
|
1355
1359
|
|
|
1360
|
+
##### A message of your own that comes and goes
|
|
1361
|
+
|
|
1362
|
+
The object does not have to come from a server. A check that spans fields is
|
|
1363
|
+
a `computed`: its message shows exactly while the check fails, with no code to
|
|
1364
|
+
clear it. Gate it on a submit attempt so it does not greet an empty form:
|
|
1365
|
+
|
|
1366
|
+
```vue
|
|
1367
|
+
<script setup lang="ts">
|
|
1368
|
+
import { computed, ref } from 'vue';
|
|
1369
|
+
import { BbButton, BbDatePickerInput, BbForm, BbSelect } from 'bitboss-ui';
|
|
1370
|
+
|
|
1371
|
+
const patients = [
|
|
1372
|
+
{ label: 'Anna Rossi', value: 1 },
|
|
1373
|
+
{ label: 'Luca Bianchi', value: 2 },
|
|
1374
|
+
];
|
|
1375
|
+
const visit = ref<{ patient: number | null; date: string | null }>({
|
|
1376
|
+
patient: null,
|
|
1377
|
+
date: null,
|
|
1378
|
+
});
|
|
1379
|
+
const tried = ref(false);
|
|
1380
|
+
const missingDate = computed(() => !visit.value.patient && !visit.value.date);
|
|
1381
|
+
|
|
1382
|
+
const ownErrors = computed(() => ({
|
|
1383
|
+
date:
|
|
1384
|
+
tried.value && missingDate.value
|
|
1385
|
+
? 'Pick a date when no patient is selected.'
|
|
1386
|
+
: undefined,
|
|
1387
|
+
}));
|
|
1388
|
+
|
|
1389
|
+
const save = (values: Record<string, unknown>) => {
|
|
1390
|
+
tried.value = true;
|
|
1391
|
+
if (missingDate.value) return; // field-errors never block a submit
|
|
1392
|
+
console.log('book', values);
|
|
1393
|
+
};
|
|
1394
|
+
</script>
|
|
1395
|
+
|
|
1396
|
+
<template>
|
|
1397
|
+
<BbForm :model="visit" :field-errors="ownErrors" @submit="save">
|
|
1398
|
+
<BbSelect label="Patient" name="patient" :items="patients" clearable />
|
|
1399
|
+
<BbDatePickerInput label="Date" name="date" />
|
|
1400
|
+
<BbButton type="submit">Book</BbButton>
|
|
1401
|
+
</BbForm>
|
|
1402
|
+
</template>
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
Picking a patient takes the message down at once. When the page also holds
|
|
1406
|
+
server errors, pass both: `:field-errors="{ ...form.errors, ...ownErrors }"`.
|
|
1407
|
+
|
|
1356
1408
|
- **The page owns them.** Editing the field does not hide its message; the
|
|
1357
1409
|
next object does (Inertia replaces `form.errors` on every answer and clears
|
|
1358
1410
|
it on success). Want it gone as the user types? Clear it yourself
|
|
@@ -1449,9 +1501,19 @@ const save = async (values: Record<string, unknown>) => {
|
|
|
1449
1501
|
- A request-level message (Laravel's `message`, a 403) has no path: give it a
|
|
1450
1502
|
key no field uses IN THE SAME CALL, `form.setErrors({ ...body.errors, _request: [body.message] })`, and it
|
|
1451
1503
|
shows in the same summary.
|
|
1452
|
-
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1504
|
+
- **`setErrors` hands over one server answer; it never takes a message
|
|
1505
|
+
back.** A field's message stays until the user edits the field or it is
|
|
1506
|
+
validated again. A submit validates every field, so a submit → 422 →
|
|
1507
|
+
`setErrors` round always starts clean. A second call without a submit
|
|
1508
|
+
between them replaces `unmatchedErrors` only: the first call's field
|
|
1509
|
+
messages stay, and `null` or a missing key clears nothing. So put
|
|
1510
|
+
everything one answer says into one map.
|
|
1511
|
+
- **Don't use `setErrors` for a message you will take back yourself**, like
|
|
1512
|
+
your own cross-field check ("a date is required when no patient is
|
|
1513
|
+
selected"). Compute it into [`field-errors`](#a-message-of-your-own-that-comes-and-goes)
|
|
1514
|
+
instead: it shows while its key holds a message and goes the moment it
|
|
1515
|
+
does not. Calling `validate()` to wipe a `setErrors` message runs every
|
|
1516
|
+
rule on every field, so it puts messages on fields the user never touched.
|
|
1455
1517
|
- `setErrors` does not emit `invalid`. It does move focus to the first failing
|
|
1456
1518
|
field on screen, like a failed submit (`{ focus: false }` opts out; see
|
|
1457
1519
|
[A failed submit focuses the first invalid field](#a-failed-submit-focuses-the-first-invalid-field)).
|
|
@@ -1617,7 +1679,8 @@ The `id` is also the `<form>`'s own (it follows a BbForm; a plain `<form>`
|
|
|
1617
1679
|
submits but has no save state to follow), so a `<BbButton form="id" type="submit">`
|
|
1618
1680
|
outside it submits it too, through the same validation, and spins while the
|
|
1619
1681
|
save runs exactly like a submit button inside (`disable-auto-loading` opts
|
|
1620
|
-
out): no `:loading` to bind.
|
|
1682
|
+
out): no `:loading` to bind. Server errors (`setErrors`, `field-errors`) then
|
|
1683
|
+
focus the first failing field, as they do after a submit from inside.
|
|
1621
1684
|
|
|
1622
1685
|
```vue
|
|
1623
1686
|
<script setup lang="ts">
|
package/dist/ai/BbHost.md
CHANGED
|
@@ -87,12 +87,13 @@ The same keys go in the build plugin (`bitbossUi({ confirm, toastPosition })`).
|
|
|
87
87
|
|
|
88
88
|
### Areas with their own look
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
A confirm takes the passthrough scope and tokens of the component that called
|
|
91
|
+
`useConfirm()`, wherever the host sits: nothing to set up. Toasts take the
|
|
92
|
+
scope and tokens of the place the HOST sits. When the app is split into areas
|
|
93
|
+
that each wrap their pages in `<BbPtScope>`
|
|
94
|
+
([passthrough guide](./guides/passthrough.md) § Named scopes) and toasts need
|
|
95
|
+
each area's look, mount one `<BbHost />` inside each area's layout, within its
|
|
96
|
+
scope, and none at the app root. That holds only if **every** page renders inside exactly one of those
|
|
96
97
|
layouts, so there is still one host at a time. See
|
|
97
98
|
[Agent Contract § Global Host Rule](./guides/agent-contract.md).
|
|
98
99
|
|
|
@@ -142,14 +143,11 @@ without a mismatch.
|
|
|
142
143
|
- Listed nodes are the ones `BbHost`'s API reaches or that declare a documented CSS variable; the rest fold into "also mounts". The complete tree, repeats and undocumented locals included, is `manifest/components/BbHost.tree.json`.
|
|
143
144
|
|
|
144
145
|
- `BbHost` _(this component)_
|
|
145
|
-
- `BbConfirm` _(internal — not importable, reach it through `BbConfirm`)_ — its own pt parts (`BbConfirm`): `text` → `div.bb-confirm__text`, `action` → `BbButton`; hands `BbDialog` the pt map `passthrough.forward(DIALOG_PARTS, BB_DIALOG_PT.states)` (ours → theirs)
|
|
146
|
-
- `BbDialog` _(public — [contract](./BbDialog.md))_ — its own pt parts (`BbDialog`): `header` → `div.bb-dialog__header`, `title` → `span.bb-dialog__title`, `description` → `p.bb-dialog__description`, `close` → `CloseButton`, `content` → `div.bb-dialog__content`, `footer` → `div.bb-dialog__footer`; hands `BbOffCanvas` the pt map `{ root: 'root', header: 'header', title: 'title', description: 'description', content: 'content', footer: 'footer', close: 'close', sheet: 'root', }` (ours → theirs); documented CSS variables: `--px`, `--close-size`
|
|
147
|
-
- `CloseButton` _(internal — not importable, reach it through `BbDialog`)_ — documented CSS variables: `--size`, `--p`
|
|
148
|
-
- `BbOffCanvas` _(public — [contract](./BbOffCanvas.md))_ — its own pt parts (`BbOffCanvas`): `header` → `div.bb-offcanvas__header`, `title` → `span.bb-offcanvas__title`, `description` → `p.bb-offcanvas__description`, `close` → `CloseButton`, `content` → `div.bb-offcanvas__content`, `footer` → `div.bb-offcanvas__footer`; documented CSS variables: `--stack-x`, `--snap-y`, `--stack-fill`, `--stack-transition-duration`, `--px`, `--handle-w`, `--handle-h`, `--handle-bg`, `--safe-top`, `--close-size`; also mounts `CloseButton`
|
|
149
|
-
- `BbButton` _(public — [contract](./BbButton.md))_ — its own pt parts (`BbButton`): `root` → `BbBaseButton`, `spinner` → `BbSpinner`, `icon` → `BbIcon`, `text` → `span.bb-button__text`; documented CSS variables: `--h-xs`, `--h`, `--icon-size`, `--px`, `--fs`, `--r`, `--gap`, `--bg`, `--bg-hover`, `--bg-pressed`, `--fg`, `--border-color`, `--ring-color`, `--ring-size`, `--bw`, `--fg-hover`, `--border-hover`, `--fg-pressed`, `--border-pressed`, `--bg-disabled`, `--fg-disabled`, `--border-disabled`, `--opacity-disabled`, `--elev-tier`, `--elev`; also mounts `BbIcon`, `BbSpinner`
|
|
150
|
-
- `BbBaseButton` _(public — [contract](./BbBaseButton.md))_ — its own pt parts (`BbBaseButton`): `root` → `RouterComponent`, `root` → `a`, `root` → `component`; also mounts `RouterComponent`
|
|
151
146
|
- `BbToast` _(internal — not importable, reach it through `BbToast`)_ — documented CSS variables: `--gap`, `--offset`, `--width`, `--lift`, `--stack-y`, `--stack-scale`, `--enter-y`, `--swipe-amount`, `--z`, `--r`, `--ring-size`, `--bg`, `--border-color`, `--fg`, `--icon-color`
|
|
152
|
-
- `BbToastRegion` _(internal — not importable, reach it through `BbToast`)_ — its own pt parts (`BbToast`): `root` → `ol.bb-toast
|
|
147
|
+
- `BbToastRegion` _(internal — not importable, reach it through `BbToast`)_ — its own pt parts (`BbToast`): `root` → `ol.bb-toast`
|
|
148
|
+
- `BbToastMessage` _(internal — not importable, reach it through `BbToast`)_ — also mounts `BbIcon`, `BbSpinner`
|
|
149
|
+
- `BbButton` _(public — [contract](./BbButton.md))_ — its own pt parts (`BbButton`): `root` → `BbBaseButton`, `spinner` → `BbSpinner`, `icon` → `BbIcon`, `text` → `span.bb-button__text`; documented CSS variables: `--h-xs`, `--h`, `--icon-size`, `--px`, `--fs`, `--r`, `--gap`, `--bg`, `--bg-hover`, `--bg-pressed`, `--fg`, `--border-color`, `--ring-color`, `--ring-size`, `--bw`, `--fg-hover`, `--border-hover`, `--fg-pressed`, `--border-pressed`, `--bg-disabled`, `--fg-disabled`, `--border-disabled`, `--opacity-disabled`, `--elev-tier`, `--elev`; also mounts `BbIcon`, `BbSpinner`
|
|
150
|
+
- `BbBaseButton` _(public — [contract](./BbBaseButton.md))_ — its own pt parts (`BbBaseButton`): `root` → `RouterComponent`, `root` → `a`, `root` → `component`; also mounts `RouterComponent`
|
|
153
151
|
|
|
154
152
|
## See Also
|
|
155
153
|
|
package/dist/ai/BbPtScope.md
CHANGED
|
@@ -96,9 +96,10 @@ The scope follows the **component tree**, not the DOM:
|
|
|
96
96
|
in. A directive written directly in `<BbPtScope>`'s slot, in the same
|
|
97
97
|
template as the scope, sees the scope above it instead — keep the scope in a
|
|
98
98
|
layout or a parent component.
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
- A `confirm()` takes the scope of the component that called `useConfirm()`,
|
|
100
|
+
wherever `<BbHost />` is mounted. Toasts take the scope where the host is
|
|
101
|
+
mounted: for toasts in the area's look, mount one `<BbHost />` inside each
|
|
102
|
+
area's layout, within its scope, and none at the app root — valid only
|
|
102
103
|
when every page renders inside exactly one of those layouts (see
|
|
103
104
|
[Agent Contract § Global Host Rule](./guides/agent-contract.md)).
|
|
104
105
|
|
|
@@ -112,7 +113,8 @@ The scope follows the **component tree**, not the DOM:
|
|
|
112
113
|
|
|
113
114
|
### Works well with
|
|
114
115
|
|
|
115
|
-
- `BbHost` — mount one inside each scoped area layout
|
|
116
|
+
- `BbHost` — mount one inside each scoped area layout when toasts need the
|
|
117
|
+
area's look.
|
|
116
118
|
- The [passthrough guide](./guides/passthrough.md) — grammar, merging and the
|
|
117
119
|
full parts table.
|
|
118
120
|
|