bitboss-ui 3.0.0-beta.57 → 3.0.0-beta.59

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 (232) hide show
  1. package/bin/bitboss-ui-mcp.mjs +118 -31
  2. package/bin/bitboss-ui.mjs +43 -9
  3. package/dist/ai/BbBadgeButton.md +3 -3
  4. package/dist/ai/BbBaseButton.md +3 -3
  5. package/dist/ai/BbCheckbox.md +6 -6
  6. package/dist/ai/BbCheckboxGroup.md +6 -6
  7. package/dist/ai/BbColorInput.md +8 -8
  8. package/dist/ai/BbDatePickerInput.md +8 -8
  9. package/dist/ai/BbDialog.md +177 -26
  10. package/dist/ai/BbDropdown.md +2 -2
  11. package/dist/ai/BbDropdownButton.md +148 -98
  12. package/dist/ai/BbDropzone.md +5 -5
  13. package/dist/ai/BbForm.md +82 -11
  14. package/dist/ai/BbNumberInput.md +8 -8
  15. package/dist/ai/BbOffCanvas.md +33 -15
  16. package/dist/ai/BbRadio.md +4 -4
  17. package/dist/ai/BbRadioGroup.md +6 -6
  18. package/dist/ai/BbRating.md +7 -7
  19. package/dist/ai/BbSelect.md +104 -24
  20. package/dist/ai/BbSelectPopover.md +6 -6
  21. package/dist/ai/BbSlider.md +6 -6
  22. package/dist/ai/BbSwitch.md +6 -6
  23. package/dist/ai/BbSwitchGroup.md +6 -6
  24. package/dist/ai/BbTable.md +43 -31
  25. package/dist/ai/BbTag.md +8 -8
  26. package/dist/ai/BbTextInput.md +8 -8
  27. package/dist/ai/BbTextarea.md +8 -8
  28. package/dist/ai/BbTimePickerInput.md +6 -6
  29. package/dist/ai/BbTree.md +1 -1
  30. package/dist/ai/CommonField.md +5 -5
  31. package/dist/ai/CommonFieldInput.md +1 -1
  32. package/dist/ai/FlatListBox.md +1 -0
  33. package/dist/ai/GroupedListBox.md +1 -0
  34. package/dist/ai/ListBox.md +1 -0
  35. package/dist/ai/changelog.json +2052 -5
  36. package/dist/ai/components.json +88 -85
  37. package/dist/ai/composables/useBbDialogContext.md +26 -19
  38. package/dist/ai/composables/useBbFormContext.md +8 -10
  39. package/dist/ai/composables/useBbOffCanvasContext.md +4 -2
  40. package/dist/ai/guides/agent-contract.md +1 -1
  41. package/dist/ai/guides/ai-router.md +2 -1
  42. package/dist/ai/guides/coherence-playbook.md +3 -2
  43. package/dist/ai/guides/eslint-rules.md +2 -2
  44. package/dist/ai/guides/fetch-items-playbook.md +3 -2
  45. package/dist/ai/guides/icons-policy.md +6 -7
  46. package/dist/ai/guides/migration/components/bb-dropdown-button.md +32 -9
  47. package/dist/ai/guides/migration/components/bb-select.md +1 -1
  48. package/dist/ai/guides/migration/v2-to-v3.md +59 -3
  49. package/dist/ai/guides/migration/v3-beta-tooling.md +36 -9
  50. package/dist/ai/helpers/inertia/manifest.json +1 -1
  51. package/dist/ai/index.md +2 -2
  52. package/dist/ai/manifest/common.json +119 -0
  53. package/dist/ai/manifest/components/BbAccordion.json +9 -10
  54. package/dist/ai/manifest/components/BbAlert.json +14 -15
  55. package/dist/ai/manifest/components/BbAsterisk.json +1 -2
  56. package/dist/ai/manifest/components/BbAvatar.json +8 -9
  57. package/dist/ai/manifest/components/BbBadge.json +10 -11
  58. package/dist/ai/manifest/components/BbBadgeButton.json +41 -42
  59. package/dist/ai/manifest/components/BbBaseButton.json +43 -44
  60. package/dist/ai/manifest/components/BbBaseCheckboxIcon.json +9 -10
  61. package/dist/ai/manifest/components/BbBaseRadioIcon.json +8 -9
  62. package/dist/ai/manifest/components/BbBaseSwitchIcon.json +9 -10
  63. package/dist/ai/manifest/components/BbBreadcrumbs.json +10 -11
  64. package/dist/ai/manifest/components/BbButton.json +52 -53
  65. package/dist/ai/manifest/components/BbCalendar.json +29 -30
  66. package/dist/ai/manifest/components/BbCheckbox.json +42 -43
  67. package/dist/ai/manifest/components/BbCheckboxGroup.json +61 -62
  68. package/dist/ai/manifest/components/BbCollapsible.json +6 -7
  69. package/dist/ai/manifest/components/BbColorInput.json +67 -68
  70. package/dist/ai/manifest/components/BbColorPalette.json +22 -23
  71. package/dist/ai/manifest/components/BbConfirm.json +1 -2
  72. package/dist/ai/manifest/components/BbDatePicker.json +42 -43
  73. package/dist/ai/manifest/components/BbDatePickerInput.json +74 -75
  74. package/dist/ai/manifest/components/BbDialog.json +32 -32
  75. package/dist/ai/manifest/components/BbDropdown.json +33 -34
  76. package/dist/ai/manifest/components/BbDropdownButton.json +72 -33
  77. package/dist/ai/manifest/components/BbDropdownGroup.json +4 -4
  78. package/dist/ai/manifest/components/BbDropzone.json +21 -22
  79. package/dist/ai/manifest/components/BbForm.json +10 -9
  80. package/dist/ai/manifest/components/BbHoverCard.json +16 -17
  81. package/dist/ai/manifest/components/BbIcon.json +4 -5
  82. package/dist/ai/manifest/components/BbIndicator.json +8 -9
  83. package/dist/ai/manifest/components/BbNumberInput.json +66 -67
  84. package/dist/ai/manifest/components/BbOffCanvas.json +32 -32
  85. package/dist/ai/manifest/components/BbPagination.json +14 -15
  86. package/dist/ai/manifest/components/BbPopover.json +21 -22
  87. package/dist/ai/manifest/components/BbProgress.json +6 -7
  88. package/dist/ai/manifest/components/BbPtScope.json +1 -1
  89. package/dist/ai/manifest/components/BbRadio.json +37 -38
  90. package/dist/ai/manifest/components/BbRadioGroup.json +62 -63
  91. package/dist/ai/manifest/components/BbRating.json +40 -41
  92. package/dist/ai/manifest/components/BbSelect.json +79 -78
  93. package/dist/ai/manifest/components/BbSelectPopover.json +51 -52
  94. package/dist/ai/manifest/components/BbSelectPopover.tree.json +4 -0
  95. package/dist/ai/manifest/components/BbSlider.json +40 -41
  96. package/dist/ai/manifest/components/BbSmoothHeight.json +7 -8
  97. package/dist/ai/manifest/components/BbSpinner.json +3 -4
  98. package/dist/ai/manifest/components/BbSwitch.json +42 -43
  99. package/dist/ai/manifest/components/BbSwitchGroup.json +62 -63
  100. package/dist/ai/manifest/components/BbTable.json +68 -69
  101. package/dist/ai/manifest/components/BbTabs.json +27 -28
  102. package/dist/ai/manifest/components/BbTabsList.json +2 -3
  103. package/dist/ai/manifest/components/BbTabsPanels.json +1 -2
  104. package/dist/ai/manifest/components/BbTabsRoot.json +26 -26
  105. package/dist/ai/manifest/components/BbTag.json +48 -49
  106. package/dist/ai/manifest/components/BbTextInput.json +64 -65
  107. package/dist/ai/manifest/components/BbTextarea.json +59 -60
  108. package/dist/ai/manifest/components/BbTimePicker.json +26 -27
  109. package/dist/ai/manifest/components/BbTimePickerInput.json +56 -57
  110. package/dist/ai/manifest/components/BbToast.json +1 -2
  111. package/dist/ai/manifest/components/BbTooltip.json +16 -17
  112. package/dist/ai/manifest/components/BbTree.json +4 -5
  113. package/dist/ai/manifest/components/ChipsBox.json +3 -3
  114. package/dist/ai/manifest/components/ClearableButton.json +1 -1
  115. package/dist/ai/manifest/components/CommaBox.json +1 -1
  116. package/dist/ai/manifest/components/CommonField.json +32 -32
  117. package/dist/ai/manifest/components/CommonFieldInput.json +15 -15
  118. package/dist/ai/manifest/components/CommonPopover.json +17 -17
  119. package/dist/ai/manifest/components/CommonTimeSelector.json +9 -9
  120. package/dist/ai/manifest/components/FlatListBox.json +15 -14
  121. package/dist/ai/manifest/components/GroupedListBox.json +16 -15
  122. package/dist/ai/manifest/components/ListBox.json +18 -17
  123. package/dist/ai/manifest/components/OptionsContainer.json +16 -16
  124. package/dist/ai/manifest/index.json +89 -85
  125. package/dist/ai/manifest/meta.json +43 -2
  126. package/dist/ai/manifest/types.advanced.json +10 -10
  127. package/dist/ai/manifest/types.api.json +52 -49
  128. package/dist/ai/recipes/inertia/approvals-inbox.md +6 -0
  129. package/dist/ai/recipes/inertia/inline-edit-workspace.md +6 -0
  130. package/dist/ai/recipes/inertia/onboarding.md +6 -0
  131. package/dist/ai/recipes/inertia/ownership-atlas.md +6 -0
  132. package/dist/ai/recipes/inertia/records-workspace.md +6 -0
  133. package/dist/ai/recipes/inertia/settings-workspace.md +6 -0
  134. package/dist/ai/recipes/inertia/upload-center.md +6 -0
  135. package/dist/ai/source/BbBaseButton.md +9 -11
  136. package/dist/ai/source/BbButton.md +15 -4
  137. package/dist/ai/source/BbCheckbox.md +12 -12
  138. package/dist/ai/source/BbCheckboxGroup.md +12 -12
  139. package/dist/ai/source/BbColorInput.md +20 -21
  140. package/dist/ai/source/BbDatePickerInput.md +20 -21
  141. package/dist/ai/source/BbDialog.md +77 -40
  142. package/dist/ai/source/BbDropdownButton.md +103 -112
  143. package/dist/ai/source/BbDropzone.md +11 -12
  144. package/dist/ai/source/BbForm.md +77 -27
  145. package/dist/ai/source/BbNumberInput.md +20 -21
  146. package/dist/ai/source/BbOffCanvas.md +67 -32
  147. package/dist/ai/source/BbRadio.md +12 -12
  148. package/dist/ai/source/BbRadioGroup.md +12 -12
  149. package/dist/ai/source/BbRating.md +16 -16
  150. package/dist/ai/source/BbSelect.md +323 -282
  151. package/dist/ai/source/BbSelectPopover.md +946 -48
  152. package/dist/ai/source/BbSlider.md +12 -12
  153. package/dist/ai/source/BbTable.md +66 -78
  154. package/dist/ai/source/BbTag.md +20 -21
  155. package/dist/ai/source/BbTextInput.md +20 -21
  156. package/dist/ai/source/BbTextarea.md +20 -21
  157. package/dist/ai/source/BbTimePickerInput.md +14 -15
  158. package/dist/ai/source/BbTree.md +4 -5
  159. package/dist/ai/source/CommonField.md +18 -18
  160. package/dist/ai/source/CommonFieldInput.md +4 -4
  161. package/dist/ai/source/FlatListBox.md +14 -0
  162. package/dist/ai/source/GroupedListBox.md +14 -0
  163. package/dist/ai/source/ListBox.md +8 -0
  164. package/dist/components/BbBaseButton/types.d.ts +9 -11
  165. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +1 -1
  166. package/dist/components/BbButton/types.d.ts +6 -0
  167. package/dist/components/BbCheckbox/types.d.ts +12 -12
  168. package/dist/components/BbCheckboxGroup/types.d.ts +12 -12
  169. package/dist/components/BbColorInput/types.d.ts +20 -21
  170. package/dist/components/BbDatePickerInput/types.d.ts +20 -21
  171. package/dist/components/BbDialog/BbDialog.vue.d.ts +2 -2
  172. package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +192 -182
  173. package/dist/components/BbDialog/types.d.ts +42 -34
  174. package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +143 -100
  175. package/dist/components/BbDropdownButton/types.d.ts +25 -15
  176. package/dist/components/BbDropzone/types.d.ts +11 -12
  177. package/dist/components/BbForm/BbForm.vue.d.ts +17 -17
  178. package/dist/components/BbForm/BbForm.vue_vue_type_script_setup_true_lang.js +195 -188
  179. package/dist/components/BbForm/formSchema.d.ts +9 -0
  180. package/dist/components/BbHost/BbHostOpening.js +1 -1
  181. package/dist/components/BbNumberInput/types.d.ts +20 -21
  182. package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +2 -2
  183. package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +242 -234
  184. package/dist/components/BbOffCanvas/types.d.ts +36 -28
  185. package/dist/components/BbRadio/types.d.ts +12 -12
  186. package/dist/components/BbRadioGroup/types.d.ts +12 -12
  187. package/dist/components/BbRating/types.d.ts +16 -16
  188. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +291 -283
  189. package/dist/components/BbSelect/types.d.ts +43 -31
  190. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +773 -487
  191. package/dist/components/BbSelectPopover/knownItems.d.ts +60 -0
  192. package/dist/components/BbSelectPopover/knownItems.js +26 -0
  193. package/dist/components/BbSelectPopover/types.d.ts +16 -18
  194. package/dist/components/BbSlider/types.d.ts +12 -12
  195. package/dist/components/BbTable/types.d.ts +66 -78
  196. package/dist/components/BbTag/types.d.ts +20 -21
  197. package/dist/components/BbTextInput/types.d.ts +20 -21
  198. package/dist/components/BbTextarea/types.d.ts +20 -21
  199. package/dist/components/BbTimePickerInput/types.d.ts +14 -15
  200. package/dist/components/BbTree/types.d.ts +4 -5
  201. package/dist/components/CommonField/types.d.ts +18 -18
  202. package/dist/components/CommonFieldInput/types.d.ts +4 -4
  203. package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +73 -68
  204. package/dist/components/FlatListBox/types.d.ts +6 -0
  205. package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +35 -30
  206. package/dist/components/GroupedListBox/types.d.ts +6 -0
  207. package/dist/components/ListBox/ListBox.vue_vue_type_script_setup_true_lang.js +5 -0
  208. package/dist/components/ListBox/types.d.ts +6 -0
  209. package/dist/composables/formSubmitLock.d.ts +6 -1
  210. package/dist/composables/hostOpenings.d.ts +10 -4
  211. package/dist/composables/hostOpenings.js +1 -1
  212. package/dist/composables/overlayRegistry.d.ts +22 -9
  213. package/dist/composables/overlayRegistry.js +175 -132
  214. package/dist/composables/useBbDialogContext.d.ts +29 -11
  215. package/dist/composables/useBbDialogContext.js +17 -8
  216. package/dist/composables/useBbFormContext.d.ts +12 -7
  217. package/dist/composables/useBbFormContext.js +7 -6
  218. package/dist/composables/useFormBinding.d.ts +7 -9
  219. package/dist/composables/useOnDemandOpener.d.ts +6 -13
  220. package/dist/composables/useOnDemandOpener.js +47 -22
  221. package/dist/directives/overlayTrigger.d.ts +4 -2
  222. package/dist/directives/overlayTrigger.js +1 -0
  223. package/dist/index.d.ts +3 -3
  224. package/dist/llms-full.txt +943 -401
  225. package/dist/llms-medium.txt +3 -2
  226. package/dist/llms.txt +2 -2
  227. package/dist/styles.css +2 -2
  228. package/dist/utils/versionCheck.js +1 -1
  229. package/llms.txt +2 -2
  230. package/package.json +1 -1
  231. package/scripts/lib/agent-manifest.mjs +231 -18
  232. package/scripts/lib/validate-bb-markup.mjs +53 -9
@@ -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
- On an open dialog, `show(newData)` replaces the data and a bare `show()`
207
- (or a trigger without `data`) keeps it.
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()` restores the default, on an edit too: it clears. -->
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 a
466
- fresh copy of `default-data`, on an edit too: it clears the form, it doesn't
467
- go back to the row. Cancel is what discards an edit.
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
- - **A second `show()` stacks** a second instance on top, with its own promise.
581
- Give a create flow one trigger: several buttons each opening the same
582
- component stack copies. Opened by id, the single mounted dialog dedupes
583
- instead.
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 | settled together with it | as any close above **starts** |
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
 
@@ -1392,7 +1520,26 @@ const route = useRoute();
1392
1520
  ```
1393
1521
 
1394
1522
  Template dialogs only: a dialog opened on demand has no place in the page URL
1395
- (`navigation` is ignored there, with a dev warning). On the phone the adaptive
1523
+ (`navigation` is ignored there, with a dev warning). A wrapper component that
1524
+ should live in the URL is rendered in the page and opened by id: reload opens
1525
+ it, Back closes it, closing strips the key. State it needs fresh on every
1526
+ opening (a form object made in setup) goes in its body, a small component
1527
+ inside the BbDialog whose setup runs per opening, or in the opening's `data`
1528
+ with `default-data`:
1529
+
1530
+ ```vue
1531
+ <!-- CreatePatientDialog.vue renders
1532
+ <BbDialog id="create-patient" navigation query-key="create">
1533
+ <PatientForm /> (its own setup: a fresh form per opening)
1534
+ </BbDialog> -->
1535
+ <CreatePatientDialog />
1536
+ <BbButton v-bb-dialog="'create-patient'">New patient</BbButton>
1537
+ ```
1538
+
1539
+ On demand stays for openings with no page to live in (a command palette, a
1540
+ store action, stacked copies of one dialog), which want no URL anyway.
1541
+
1542
+ On the phone the adaptive
1396
1543
  sheet is the same dialog: one key, and Back is refused by the sheet's own
1397
1544
  gates. Under Inertia, a Back that changes only dialog keys never reaches
1398
1545
  Inertia, so the page is not remounted; this needs `bitbossUiPlugin` installed
@@ -1580,6 +1727,10 @@ A `pt` value written in `<script>` (a constant, a preset, a wrapper's
1580
1727
  `href`/`to` to the button.
1581
1728
  - **Don't toggle `v-model` from `@hide`/`@hidden` handlers** to "cancel" a
1582
1729
  close — use `persistent` instead; the close paths already guard correctly.
1730
+ - **Catch `complete()` in a reusable dialog** whenever an opener may pass an
1731
+ `onComplete` that rejects (`complete(data).catch(…)`, as in § The page
1732
+ saves). Uncaught, the rejection travels through the form's `@submit` and
1733
+ reaches `app.config.errorHandler`.
1583
1734
  - **Don't leave the `header` slot without a close control** — it replaces the
1584
1735
  default ×, so you must render your own wired to `close`.
1585
1736
  - **Don't query dialog content before it has opened, or after it has closed**,
@@ -1625,7 +1776,7 @@ A `pt` value written in `<script>` (a constant, a preset, a wrapper's
1625
1776
  | --- | --- | --- | --- | --- |
1626
1777
  | `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
1778
  | `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 a fresh copy, and every close does too. |
1779
+ | `defaultData` | `Record<string, any> \| undefined` | | | What `data` is when an opening passes none (a create): an object, copied at once, so inputs bind `data.x` with no guard. An opening's data lays over it (`{ ...defaultData, ...row }`, one level). `reset()` restores its keys but keeps an edit's `id`; a close restores it. |
1629
1780
  | `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
1781
  | `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
1782
  | `focusTarget` | `string \| undefined` | | | CSS selector of the element to focus after opening. Defaults to the first tabbable element. |
@@ -1633,16 +1784,16 @@ A `pt` value written in `<script>` (a constant, a preset, a wrapper's
1633
1784
  | `hideClose` | `boolean \| undefined` | `false` | | Hide the default close (×) button. The button is shown by default; set this to remove it (e.g. for a `persistent` dialog you don't want dismissible — the × otherwise still closes a persistent dialog). |
1634
1785
  | `id` | `string \| undefined` | | | Names the dialog for `v-bb-dialog="'edit'"` and `useBbDialogContext`, found nearest to the trigger, so two pages may reuse an id. Also the DOM `id`: one dialog per id, rows pass `data`. |
1635
1786
  | `modelValue` | `boolean \| undefined` | `false` | | Used by v-model to open / close the dialog. A dialog with no `v-model` starts closed. |
1636
- | `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. |
1787
+ | `navigation` | `boolean \| undefined` | `false` | | Syncs the open state with the URL: opening writes `?<queryKey>=true`, closing removes it, a URL with the key opens the dialog on load, and Back closes it (refused whenever Escape is). Template dialogs only; keep a record id in your own query. |
1637
1788
  | `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
- | `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. |
1789
+ | `persistent` | `boolean \| undefined` | `false` | | Blocks closing by outside click or Escape. The header close (×) STILL closes a persistent dialog, the documented escape hatch: add `hideClose` to remove it, or use `preventClose` to block every dismissal. Programmatic close still works. |
1790
+ | `preventClose` | `boolean \| undefined` | ``undefined` (follows a saving form inside)` | | Refuses every user close (backdrop, Escape, the ✕, 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
1791
  | `queryKey` | `string \| undefined` | `"dialog"` | | The query key `navigation` writes. |
1641
1792
  | `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
1793
  | `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. |
1643
1794
  | `snap` | `BbSnapPoint \| undefined` | | | The phone sheet's snap stop (Q71.8), one of the `snapPoints` entries. The desktop dialog ignores it. |
1644
- | `snapPoints` | `BbSnapPoint[] \| undefined` | | | Heights the phone sheet rests at, smallest first: a fraction of the screen (`0.5`; `1` = as tall as it gets) or a pixel height (`'240px'`). The first stop is the opening height; `v-model:snap` reads and sets the stop. Phone sheet only: the desktop dialog ignores it. |
1645
- | `stack` | `string \| undefined` | | | Name of the stack this dialog belongs to. Dialogs sharing the same `stack` value replace one another: only the most recently opened stays visible while the others in the stack are hidden beneath it. Dialogs in different stacks — or with no `stack` — never affect each other and remain fully visible. |
1795
+ | `snapPoints` | `BbSnapPoint[] \| undefined` | | | Heights the phone sheet rests at, smallest first: a screen fraction (`0.5`; `1` = as tall as it gets) or pixels (`'240px'`). The first is the opening height; `v-model:snap` reads and sets the stop. Sheet only: the centred dialog ignores it. |
1796
+ | `stack` | `string \| undefined` | | | Name of this dialog's stack. Dialogs with the same `stack` replace one another: only the latest opened shows, the rest of the stack hide beneath it. Dialogs in different stacks, or with no `stack`, never affect each other and stay fully visible. |
1646
1797
  | `title` | `string \| undefined` | | | Title text announced to assistive technologies. If omitted, no header is rendered (unless a `header` slot is supplied) and accessibility must be handled manually. |
1647
1798
  | `transitionDuration` | `number \| undefined` | `250` | | Controls the duration of the open and close animations. |
1648
1799
 
@@ -1671,7 +1822,7 @@ States are listed in precedence order: when two are on at once and their entries
1671
1822
  ## Events
1672
1823
 
1673
1824
  - `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).
1825
+ - `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
1826
  - `show` — `(): void` — Emitted as soon as the dialog starts to open (model becomes true).
1676
1827
  - `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
1828
  - `update:modelValue` — `(value: boolean): void` — Emitted with the new value whenever `v-model` changes.
@@ -1679,10 +1830,10 @@ States are listed in precedence order: when two are on at once and their entries
1679
1830
 
1680
1831
  ## Slots
1681
1832
 
1682
- - `default` — scope: `BbDialogDefaultSlotProps` `{ close: () => void; complete: (value?: any) => void; reset: () => void; data: any }` — Primary body content of the dialog, rendered in the scrollable body area.
1833
+ - `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
1834
  - `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; reset: () => void; data: any }` — Content rendered in the dialog footer, below the body area. Typically used for action buttons.
1685
- - `header` — scope: `BbDialogHeaderSlotProps` `{ titleId: string; close: () => void; complete: (value?: any) => 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.
1835
+ - `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.
1836
+ - `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
1837
  - `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
1838
 
1688
1839
  ## CSS custom properties
@@ -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` (or `BbDropdownButton`)
2128
- default slot, its `id` must match a group's `id`, and a `multiple` group's
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` /