bitboss-ui 3.0.0-beta.19 → 3.0.0-beta.20

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 (46) hide show
  1. package/dist/ai/BbDropdown.md +11 -5
  2. package/dist/ai/BbOffCanvas.md +21 -10
  3. package/dist/ai/BbSelectPopover.md +4 -2
  4. package/dist/ai/changelog.json +1 -1
  5. package/dist/ai/components.json +3 -3
  6. package/dist/ai/source/BbDropdown.md +224 -82
  7. package/dist/ai/source/BbDropdownGroup.md +217 -82
  8. package/dist/ai/source/BbOffCanvas.md +306 -176
  9. package/dist/ai/source/BbSelect.md +11 -1
  10. package/dist/ai/source/BbSelectPopover.md +141 -19
  11. package/dist/components/BbAlert/BbAlert.vue_vue_type_script_setup_true_lang.js +4 -4
  12. package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +5 -5
  13. package/dist/components/BbBaseCheckboxGroup/BbBaseCheckboxGroup.vue_vue_type_script_setup_true_lang.js +4 -4
  14. package/dist/components/BbBaseDatePicker/BbBaseDatePickerInputDaySelector.vue_vue_type_script_setup_true_lang.js +6 -6
  15. package/dist/components/BbBaseDatePickerInput/BbBaseDatePickerInput.vue_vue_type_script_setup_true_lang.js +4 -4
  16. package/dist/components/BbBaseInputContainer/BbBaseInputContainer.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/components/BbBaseRadioGroup/BbBaseRadioGroup.vue_vue_type_script_setup_true_lang.js +4 -4
  18. package/dist/components/BbBaseRating/BbBaseRating.vue_vue_type_script_setup_true_lang.js +4 -4
  19. package/dist/components/BbBaseSlider/BbBaseSlider.vue_vue_type_script_setup_true_lang.js +5 -5
  20. package/dist/components/BbBaseSwitchGroup/BbBaseSwitchGroup.vue_vue_type_script_setup_true_lang.js +4 -4
  21. package/dist/components/BbBaseTimePickerInput/BbBaseTimePickerInput.vue_vue_type_script_setup_true_lang.js +4 -4
  22. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +35 -35
  23. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +6 -6
  24. package/dist/components/BbConfirm/BbConfirm.vue_vue_type_script_setup_true_lang.js +17 -17
  25. package/dist/components/BbDialog/BbDialog.vue_vue_type_script_setup_true_lang.js +4 -4
  26. package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +88 -108
  27. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +5 -4
  28. package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +225 -208
  29. package/dist/components/BbDropdown/DropdownPipelineResolver.vue_vue_type_script_setup_true_lang.js +21 -21
  30. package/dist/components/BbDropdown/types.d.ts +7 -4
  31. package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +5 -5
  32. package/dist/components/BbOffCanvas/BbOffCanvas.vue_vue_type_script_setup_true_lang.js +238 -216
  33. package/dist/components/BbOffCanvas/types.d.ts +4 -4
  34. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +6 -3
  35. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +41 -35
  36. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +18 -18
  37. package/dist/components/BbToast/BbToastMessage.vue_vue_type_script_setup_true_lang.js +13 -13
  38. package/dist/components/ChipsBox/ChipsBox.vue_vue_type_script_setup_true_lang.js +17 -17
  39. package/dist/components/ClearableButton/ClearableButton.vue_vue_type_script_setup_true_lang.js +5 -5
  40. package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +84 -84
  41. package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +52 -52
  42. package/dist/composables/useBaseOptions.js +15 -15
  43. package/dist/index.js +32 -32
  44. package/dist/llms-full.txt +36 -17
  45. package/dist/styles.css +1 -1
  46. package/package.json +1 -1
@@ -686,9 +686,12 @@ What each knob does:
686
686
  then searching for something else leaves the earlier selection unresolvable
687
687
  and breaks the `v-model` round-trip. **Recommended for every `multiple`
688
688
  searchable group.**
689
- - **`maxHeight`** defaults to `280` (≈ 10 rows) on a searchable group, because a
690
- searched list should scroll rather than grow. Non-searchable groups are
691
- uncapped unless you set it.
689
+ - **`maxHeight`** defaults to `280` (≈ 10 rows) on a searchable group **in the
690
+ flyout**, because a searched list should scroll rather than grow. The
691
+ adaptive sheet ignores that default (and any explicit cap): the off-canvas
692
+ body is the only scrollport, so a downward swipe on a row can dismiss the
693
+ sheet the same way a swipe on the handle does. Non-searchable flyout groups
694
+ are uncapped unless you set it.
692
695
 
693
696
  Two knobs live on the **dropdown**, not the group, because they apply to every
694
697
  searchable group in the menu:
@@ -1125,8 +1128,11 @@ mid-session never swaps a live menu between flyout and sheet.
1125
1128
  stay a flyout everywhere — e.g. deterministic UI tests or dense desktop-only
1126
1129
  tooling.
1127
1130
  - `offCanvasProps` forwards overrides to the underlying `BbOffCanvas` (merged
1128
- over `direction: 'bottom'`, `size: 'auto'`, `draggable: true`); it is
1129
- ignored when `adaptive` resolves to `false`.
1131
+ over `direction: 'bottom'`, `size: 'auto'`, `draggable: true`,
1132
+ `hideClose: true`); it is ignored when `adaptive` resolves to `false`.
1133
+ Pass `hideClose: false` to bring the close control back. The sheet search
1134
+ field (on `searchable` groups) uses the same soft filled treatment as
1135
+ `BbSelect`'s adaptive sheet.
1130
1136
 
1131
1137
  ### Works well with
1132
1138
 
@@ -621,15 +621,26 @@ Set `draggable` to let the user flick the panel toward its edge to dismiss; a
621
621
  grab handle appears on the inner edge as the affordance. A draggable,
622
622
  `auto`-sized bottom panel is the canonical mobile sheet.
623
623
 
624
- The gesture is physical, not just a tap target:
625
-
626
- - Dragging past **a quarter of the panel's extent**, or a fast **flick toward
627
- the edge**, dismisses it; a shorter drag springs back to fully open.
628
- - On a vertical (top/bottom) sheet with a scrollable body, the drag **hands off
629
- to native scrolling** first you only start dragging the sheet once the body
630
- is scrolled to its edge, so scrolling a long sheet never accidentally closes
631
- it.
632
- - `persistent` / `disabled` still win: a drag that would dismiss instead plays
624
+ The gesture is powered by VueUse `useSwipe` with a **fixed pixel threshold**
625
+ (not a fraction of the panel height), so a tall filters sheet and a short menu
626
+ dismiss with the same motion:
627
+
628
+ - Swiping **about 50px toward the exit edge** dismisses it; a shorter swipe
629
+ springs back. Motion matches Vaul: the sheet exits on inline `transform`
630
+ (iOS easing, 500ms) while the backdrop fades on the same clock. Styles stay
631
+ frozen off-screen until the normal close wait finishes, so `dialog.close()`
632
+ does not flash the panel or jump the blur. Escape / backdrop / × still use
633
+ the open/close `translate` path.
634
+ - Only travel on the dismiss axis counts — a mostly horizontal flick on a short
635
+ bottom sheet does not latch or close it.
636
+ - On a vertical sheet with a scrollable body, the swipe **hands off to native
637
+ scrolling** first — you only drag the sheet once every scrollable ancestor
638
+ under the finger is at its dismiss edge (latched at touchstart, so a row
639
+ button and the empty panel chrome behave the same). A downward flick that
640
+ starts by scrolling _into_ the list stays a scroll for the rest of the
641
+ gesture; once the sheet is following the finger, reversing back to rest
642
+ does not start a scroll — lift to cancel.
643
+ - `persistent` / `disabled` still win: a swipe that would dismiss instead plays
633
644
  the "deny" nudge and springs back.
634
645
 
635
646
  **A draggable bottom sheet**
@@ -987,7 +998,7 @@ The panel is a native `<dialog>` opened with `showModal()` — the same mechanic
987
998
  | --- | --- | --- | --- | --- |
988
999
  | `description` | `string \| undefined` | | | One short line under the title, in the header — the shadcn `DialogDescription` shape: same block as the title, lower hierarchy, muted. It is also what `aria-describedby` points at. That wiring was dropped in v3 along with the old `descripti… |
989
1000
  | `disabled` | `boolean \| undefined` | `false` | | Disable EVERY user-initiated dismissal: backdrop click, Escape, drag-to-dismiss AND the header close (×) button (denied attempts play the deny nudge). Use it while an operation is in flight (e.g. a save) so the drawer cannot be dismissed mi… |
990
- | `draggable` | `boolean \| undefined` | `false` | | Enable drag-to-dismiss. The panel can be dragged from anywhere toward its edge to close, and a grab handle is shown on the inner edge as the affordance. When `false`, the panel is static (dismiss via the close button, backdrop, or Escape on… |
1001
+ | `draggable` | `boolean \| undefined` | `false` | | Enable drag-to-dismiss via VueUse `useSwipe` (fixed ~50px threshold toward the exit edge, including from the scrollable body once it is at its scroll edge). A grab handle on the inner edge is the visual cue. When `false`, the panel is stati… |
991
1002
  | `eager` | `boolean \| undefined` | `false` | | Render offcanvas content immediately instead of waiting for the first open. |
992
1003
  | `focusTarget` | `string \| undefined` | | | CSS selector of the element to focus after opening. Defaults to the first tabbable element. |
993
1004
  | `fullscreen` | `boolean \| "mobile" \| undefined` | | | Display the offcanvas fullscreen. Use `'mobile'` to restrict fullscreen to small devices. |
@@ -1172,8 +1172,10 @@ const variantFor = (value: Status | null): BadgeVariantType =>
1172
1172
  With `adaptive` (default: global `config.adaptive`, which is `true`), the panel
1173
1173
  opens as a bottom off-canvas sheet on mobile viewports (below
1174
1174
  `config.mobileMaxWidth`) instead of a floating popover — same search, options and
1175
- `#header`/`#footer` slots, plus a representation of the current selection between
1176
- them. The surface is **latched per open cycle**: a mid-open resize never swaps
1175
+ `#header`/`#footer` slots. In **multiple** mode the current selection also shows
1176
+ as removable chips above the search (so picks stay clearable when filtered or
1177
+ scrolled away); single select relies on the listbox checkmark and closes on pick.
1178
+ The surface is **latched per open cycle**: a mid-open resize never swaps
1177
1179
  popover ↔ sheet. `off-canvas-props` forwards extra `BbOffCanvas` props to the
1178
1180
  sheet (`size`, `draggable`, `fullscreen`, `stack`); `:adaptive="false"` keeps a
1179
1181
  floating popover everywhere.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "bitboss-ui",
4
- "version": "3.0.0-beta.19",
4
+ "version": "3.0.0-beta.20",
5
5
  "upgrade": "v2-to-v3",
6
6
  "guide": "ai/guides/migration/v2-to-v3.md",
7
7
  "summary": {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "library": "bitboss-ui",
4
- "version": "3.0.0-beta.19",
5
- "generatedAt": "2026-09-01T10:39:24.755Z",
4
+ "version": "3.0.0-beta.20",
5
+ "generatedAt": "2026-09-01T16:02:37.101Z",
6
6
  "composables": [
7
7
  {
8
8
  "name": "useBbConfig",
@@ -9456,7 +9456,7 @@
9456
9456
  "type": "boolean | undefined",
9457
9457
  "required": false,
9458
9458
  "default": "false",
9459
- "description": "Enable drag-to-dismiss. The panel can be dragged from anywhere toward its\nedge to close, and a grab handle is shown on the inner edge as the\naffordance. When `false`, the panel is static (dismiss via the close button,\nbackdrop, or Escape only)."
9459
+ "description": "Enable drag-to-dismiss via VueUse `useSwipe` (fixed ~50px threshold toward\nthe exit edge, including from the scrollable body once it is at its scroll\nedge). A grab handle on the inner edge is the visual cue. When `false`, the\npanel is static (dismiss via the close button, backdrop, or Escape only)."
9460
9460
  },
9461
9461
  {
9462
9462
  "name": "eager",
@@ -275,6 +275,13 @@ const offcanvasBindings = computed(() => ({
275
275
  eager: props.eager,
276
276
  disabled: props.disabled,
277
277
  transitionDuration: props.transitionDuration,
278
+ /*
279
+ * No ✕ on this sheet — same call as BbSelectPopover. A bottom sheet already
280
+ * offers drag-down, backdrop tap and Escape; the picker's own job (tap a
281
+ * row) dismisses most menus too. Before the spread so
282
+ * `offCanvasProps: { hideClose: false }` still brings it back.
283
+ */
284
+ hideClose: true,
278
285
  ...props.offCanvasProps,
279
286
  }));
280
287
 
@@ -1573,11 +1580,14 @@ export type InternalGroup = DropdownEntryBase & {
1573
1580
  * predictable size regardless of row content. A `number` is pixels; a `string`
1574
1581
  * is any CSS length (`'50vh'`, `'20rem'`). `'none'` (or `0`) removes the cap.
1575
1582
  *
1576
- * `searchable` groups default to `280` (≈ 10 rows of 28px), since a searched
1577
- * list is usually long; set your own value to tweak, or `'none'` to uncap.
1578
- * Non-searchable groups are uncapped unless you set this.
1583
+ * `searchable` groups default to `280` (≈ 10 rows of 28px) **on the flyout**,
1584
+ * since a searched list is usually long; set your own value to tweak, or
1585
+ * `'none'` to uncap. The adaptive sheet ignores the cap so the OffCanvas
1586
+ * body stays the only scroller (nested group overflow trapped swipes that
1587
+ * started on a row). Non-searchable flyout groups are uncapped unless you
1588
+ * set this.
1579
1589
  *
1580
- * @defaultValue `280` for `searchable` groups, otherwise unset
1590
+ * @defaultValue `280` for `searchable` flyout groups, otherwise unset
1581
1591
  */
1582
1592
  maxHeight?: number | string;
1583
1593
  /**
@@ -2038,7 +2048,9 @@ export type BbDropdownGroupEvents = {
2038
2048
  ## Styles
2039
2049
 
2040
2050
  ```css
2041
- /* @inherit --transition-duration — set inline by the adaptive surface */
2051
+ /* @inherit --transition-duration — set inline by the adaptive surface;
2052
+ @inherit --py — the adaptive sheet's rules target BbOffCanvas elements, which
2053
+ declare it on the panel */
2042
2054
  .bb-dropdown {
2043
2055
  overflow: visible;
2044
2056
 
@@ -2561,6 +2573,98 @@ export type BbDropdownGroupEvents = {
2561
2573
  up smaller than the list beneath it whatever that constant becomes. */
2562
2574
  --dialog-title-fs: var(--sheet-option-fs, 1rem);
2563
2575
 
2576
+ /* Root sheet title via `offCanvasProps.title` (no drill nav / custom
2577
+ `#header`): centre it and tighten the title→divider air like BbSelect.
2578
+ `:not(:has(.bb-dropdown__nav))` keeps consumer `#header` content and the
2579
+ drill nav on their own layouts. */
2580
+ .bb-offcanvas__header:not(:has(.bb-dropdown__nav)) {
2581
+ border-bottom: var(--bb-border-w) solid var(--bb-border);
2582
+ padding-bottom: calc(var(--py) - 6px);
2583
+
2584
+ > .bb-offcanvas__title {
2585
+ flex: 1 1 auto;
2586
+ text-align: center;
2587
+ }
2588
+ }
2589
+
2590
+ /* Drill nav owns the title→body air. Drop the OffCanvas header's own bottom
2591
+ pad so it does not stack under the nav and push chips / search below
2592
+ BbSelect's. */
2593
+ .bb-offcanvas__header:has(.bb-dropdown__nav--drill) {
2594
+ padding-bottom: 0;
2595
+ }
2596
+
2597
+ /* Body used to start flush under the header. Match BbSelect's step down
2598
+ from the title rule. Specificity beats OffCanvas's nested shorthand.
2599
+ Skip headerless sheets (`--no-header`): OffCanvas already supplies the
2600
+ top inset (and the handle clearance) that this would otherwise crush to
2601
+ 0.5rem — the filters root with no title / drill nav / `#header`. */
2602
+ &.bb-offcanvas:not(.bb-offcanvas--no-header)
2603
+ .bb-offcanvas__body
2604
+ .bb-offcanvas__body-content {
2605
+ padding-top: 0.5rem;
2606
+ }
2607
+
2608
+ /* Flat multi-group sheets: space a following section after the previous
2609
+ group's items/search. */
2610
+ .bb-dropdown__group-items + .bb-dropdown__group-title,
2611
+ .bb-dropdown__group-search + .bb-dropdown__group-title,
2612
+ .bb-dropdown__divider + .bb-dropdown__group-title {
2613
+ margin-top: 0.5rem;
2614
+ }
2615
+
2616
+ /* Group headers on the sheet read as BbSelect's sheet label — centred title
2617
+ weight/size, not the flyout's faint left-aligned section caption. No
2618
+ hairline here: the select sheet label's rule lives on the OffCanvas
2619
+ header, and repeating it under every group stacked like extra chrome.
2620
+ Nested drills with `hideLabel` skip this entirely; the drill nav title is
2621
+ already that label. */
2622
+ .bb-dropdown__group-title {
2623
+ align-items: center;
2624
+ border-bottom: 0;
2625
+ color: var(--bb-text);
2626
+ column-gap: 6px;
2627
+ display: flex;
2628
+ font-size: var(--dialog-title-fs, var(--sheet-option-fs, 1rem));
2629
+ font-weight: 500;
2630
+ justify-content: center;
2631
+ letter-spacing: normal;
2632
+ /* Match select's title→body air (`padding-bottom: calc(var(--py) - 6px)`
2633
+ on the OffCanvas header) so the label sits above the soft search the
2634
+ same way City sits above its field. */
2635
+ padding: 0.25rem 0 calc(var(--py, var(--bb-panel-p)) - 6px);
2636
+
2637
+ .group-title__content {
2638
+ flex: 1 1 auto;
2639
+ line-height: normal;
2640
+ overflow-x: clip;
2641
+ overflow-y: visible;
2642
+ text-align: center;
2643
+ }
2644
+
2645
+ .group-title__prepend-icon,
2646
+ .group-title__append-icon {
2647
+ --size: 1.25rem;
2648
+ font-size: var(--size);
2649
+ }
2650
+ }
2651
+
2652
+ /* Headerless sheet: the first group title IS the sheet label (Flat filters'
2653
+ "Status" beside BbSelect's "City"). Body already clears the handle via
2654
+ `--no-header` padding (`calc(var(--py) + 12px)`); the items-container's
2655
+ 4px (meant for air under a nav divider) and the title's own 0.25rem top
2656
+ pad were stacking on top and dropping the label ~8px below City.
2657
+ Measured: City title @29px, Flat Status @33–37px before this. */
2658
+ &.bb-offcanvas--no-header .bb-dropdown__items-container {
2659
+ padding-top: 0;
2660
+ }
2661
+
2662
+ &.bb-offcanvas--no-header
2663
+ .bb-dropdown__items-container
2664
+ > .bb-dropdown__group-title:first-child {
2665
+ padding-top: 0;
2666
+ }
2667
+
2564
2668
  /* Clip the sideways level slide so it never spills a horizontal scrollbar. */
2565
2669
  .bb-dropdown__adaptive-body {
2566
2670
  overflow-x: clip;
@@ -2589,6 +2693,14 @@ export type BbDropdownGroupEvents = {
2589
2693
  * construction, so this selector needs no viewport clause of its own. The
2590
2694
  * JS half lives in `useTouchRowLadder`, for the virtualized option lists
2591
2695
  * whose row heights feed a virtualizer instead of a stylesheet. */
2696
+ /* Belt for an explicit `maxHeight` leaking onto the sheet: never nest a
2697
+ second scrollport inside the OffCanvas body. */
2698
+ .bb-dropdown__group-items--capped {
2699
+ display: contents;
2700
+ max-height: none;
2701
+ overflow: visible;
2702
+ }
2703
+
2592
2704
  .bb-dropdown__items-container {
2593
2705
  /* Published by AdaptiveDropdown from `listboxMetrics`, the same helpers the
2594
2706
  select's sheet reads. The fallbacks are the shipped touch values, so the
@@ -2597,19 +2709,31 @@ export type BbDropdownGroupEvents = {
2597
2709
  max-height: none;
2598
2710
  min-width: 0;
2599
2711
  overflow-y: visible;
2600
- /* A row's hover wash is a filled rounded rect; without this it begins
2601
- flush against the nav divider and the two read as one smudged block. */
2712
+ /* Horizontal inset is the OffCanvas body's `--px`. Do not re-pad here
2713
+ (or on the rows) to fake alignment with search/chips. */
2602
2714
  padding: 4px 0 0;
2603
2715
  width: 100%;
2604
2716
 
2605
2717
  .bb-dropdown__item {
2606
2718
  height: var(--sheet-row-h, 44px);
2607
- padding: 2px 10px;
2719
+ /* Flyout rows are `2px 6px`. +4px start so the label shares the
2720
+ search field's text edge (`padding-inline: 0.75rem` on the fill). */
2721
+ padding-left: calc(6px + 4px);
2722
+ /* iOS treats a <button> as a tap target and will not hand the pan to
2723
+ the sheet body unless the control itself opts into vertical panning.
2724
+ Without this, dismiss/scroll work on the handle and miss on rows. */
2725
+ touch-action: pan-y;
2608
2726
 
2609
2727
  &.bb-dropdown__item--description {
2610
2728
  min-height: var(--sheet-row-h, 44px);
2611
2729
  }
2612
2730
 
2731
+ .item__content {
2732
+ line-height: normal;
2733
+ overflow-x: clip;
2734
+ overflow-y: visible;
2735
+ }
2736
+
2613
2737
  /* One step under the row label, the same step the listbox puts between
2614
2738
  an option and its group header — so the two sheets' secondary text
2615
2739
  agrees as well as their primary. */
@@ -2621,7 +2745,7 @@ export type BbDropdownGroupEvents = {
2621
2745
 
2622
2746
  .bb-dropdown__placeholder {
2623
2747
  min-height: var(--sheet-row-h, 44px);
2624
- padding: 2px 10px;
2748
+ padding-left: calc(6px + 4px);
2625
2749
  }
2626
2750
 
2627
2751
  /* Icons scale with the label they sit beside. The desktop sizes (16px
@@ -2629,37 +2753,72 @@ export type BbDropdownGroupEvents = {
2629
2753
  44px row with an 18px label they read as undersized — the chevron that
2630
2754
  says "this opens another level" ended up the smallest thing in the row.
2631
2755
  One step up, so the glyph keeps roughly the proportion to its label that
2632
- it has on the desktop panel. */
2756
+ it has on the desktop panel. BbIcon reads `--size`; keep `font-size` on
2757
+ the same token so the glyph box and the type size cannot drift. */
2633
2758
  .item__prepend-icon,
2634
2759
  .item__append-icon,
2635
2760
  .item__check,
2636
2761
  .item__chevron {
2637
- --size: 20px;
2638
- font-size: 20px;
2762
+ --size: 1.25rem;
2763
+ font-size: var(--size);
2639
2764
  }
2640
2765
 
2641
- .bb-dropdown__group-title {
2642
- font-size: calc(var(--sheet-option-fs, 1rem) - 2px);
2643
- padding: 8px 10px 4px;
2644
- }
2766
+ /* Group titles are restyled as select-sheet labels on `--adaptive`
2767
+ (see block above); do not re-apply the flyout caption metrics here. */
2645
2768
  }
2646
2769
 
2770
+ /* Soft filled search — mirrors BbSelectPopover's sheet field: no underline
2771
+ rule, faint `--bb-muted` wash, and the same inset as sheet chips / row
2772
+ padding so the fill lines up. */
2647
2773
  .bb-dropdown__group-search {
2648
- padding: 8px 10px;
2774
+ background-color: var(--bb-muted);
2775
+ border-bottom: 0;
2776
+ border-radius: var(--bb-radius);
2777
+ /* Undo the flyout's full-bleed negative margins. No extra inline inset —
2778
+ the OffCanvas body already pads `--px`. */
2779
+ margin: 0 0 0.25rem;
2780
+ padding: 0.625rem 0.75rem;
2781
+ position: relative;
2782
+ top: auto;
2783
+ transition: background-color var(--bb-transition-fast) ease;
2784
+ /* Flyout search is sticky `z-index: 1`; that same stacking painted over
2785
+ the sheet's grab handle once the body scrolled. */
2786
+ z-index: auto;
2787
+
2788
+ /* One emphasis stop above the resting --bb-muted (6%) fill. */
2789
+ &:focus-within {
2790
+ background-color: color-mix(in oklab, var(--bb-text) 8%, var(--bb-panel));
2791
+ }
2792
+
2793
+ /* Lens on the trailing edge, same side as BbSelect's sheet search. */
2794
+ .bb-dropdown__search-icon {
2795
+ order: 1;
2796
+ }
2797
+
2798
+ .bb-dropdown__search-input {
2799
+ font-size: var(--bb-input-fs);
2800
+ order: 0;
2801
+ }
2802
+
2803
+ /* Busy strip sits on the field's bottom edge (no border to overlay). */
2804
+ .bb-dropdown__group-loading {
2805
+ border-radius: 0 0 var(--bb-radius) var(--bb-radius);
2806
+ bottom: 0;
2807
+ }
2649
2808
  }
2650
2809
 
2651
2810
  /* Removable selection chips for the current multi-select level — sit above the
2652
2811
  menu (and its search), mirroring BbSelect's sheet. */
2653
2812
  .bb-dropdown__sheet-selection {
2654
2813
  flex-shrink: 0;
2655
- padding: 0 4px 8px;
2814
+ padding: 0 0 0.5rem;
2656
2815
  text-align: left;
2657
2816
  }
2658
2817
 
2659
2818
  .bb-dropdown__sheet-chips {
2660
2819
  display: flex;
2661
2820
  flex-wrap: wrap;
2662
- gap: 8px;
2821
+ gap: 0.5rem;
2663
2822
  }
2664
2823
 
2665
2824
  /* Touch target: the xl badge is a ~24px pill — fine inline on desktop, too
@@ -2668,9 +2827,9 @@ export type BbDropdownGroupEvents = {
2668
2827
  padding and a bigger clear glyph; the badge API stays untouched. Mirrors
2669
2828
  BbSelectPopover's sheet chips. */
2670
2829
  .bb-dropdown__sheet-chip {
2671
- --min-size: 34px;
2672
- --padding-inline: 12px;
2673
- --clear-size: 20px;
2830
+ --min-size: 2.125rem;
2831
+ --padding-inline: 0.75rem;
2832
+ --clear-size: 1.25rem;
2674
2833
  }
2675
2834
 
2676
2835
  .bb-dropdown__nav {
@@ -2679,83 +2838,66 @@ export type BbDropdownGroupEvents = {
2679
2838
  display: flex;
2680
2839
  width: 100%;
2681
2840
 
2682
- /* Drilled in: a navigation bar. Equal gutters either side so the title is
2683
- truly centred and cannot drift as the back button appears; the sheet's
2684
- own body padding provides the outer inset. */
2841
+ /* Drilled in: a navigation bar. The TITLE LINE sets the bar height — same
2842
+ as BbSelect's OffCanvas title so City and Status share one baseline.
2843
+ The back control is a BbButton ghost sm, absolutely pinned to the
2844
+ OffCanvas header with equal inset from the sheet's top and left. */
2685
2845
  &--drill {
2686
- /* A hairline under the bar. The list starts immediately below it and a
2687
- hovered first row's wash ran right up to the title, so the header
2688
- read as part of the list rather than as the thing you came back
2689
- through. INSET to the body padding rather than full-bleed: bleeding
2690
- it needed negative inline margins, and against this grid only the
2691
- leading one took (the bar measured 1→357 inside a 0→390 sheet), so
2692
- the rule that was meant to look deliberate looked like a mistake.
2693
- Inset, it lines up with the rows it separates. */
2694
- border-bottom: var(--bb-border-w) solid var(--bb-border);
2695
- column-gap: 4px;
2696
- display: grid;
2697
- grid-template-columns: 40px minmax(0, 1fr) 40px;
2698
- padding-bottom: 6px;
2699
-
2700
- /* No overhang in the bar. The base rule pulls the button 8px into the
2701
- sheet's padding to keep a 40px touch target without looking inset;
2702
- here the grid column already reserves that space, and the pull only
2703
- broke the symmetry the centred title depends on. Centred in its own
2704
- column, the glyph lands within a pixel of the row labels below —
2705
- which is the alignment the eye actually reads. */
2846
+ /* Title line sets the bar height same as BbSelect's OffCanvas title.
2847
+ Sit in the header's `--px`; do not pull out and re-pad to fake a
2848
+ full-bleed bar. Bottom pad matches BbSelect's
2849
+ `calc(var(--py) - 6px)`. No `position: relative` the back button's
2850
+ containing block is the header. */
2851
+ display: block;
2852
+ padding-bottom: calc(var(--py, var(--bb-panel-p)) - 6px);
2853
+
2854
+ /* Equal inset from the sheet edges. Header is `position: relative`
2855
+ in OffCanvas. Size/chrome come from BbButton ghost sm.
2856
+ Use `--bb-panel-p`, not `--px`: BbButton redefines `--px` as its
2857
+ own padding token, which collapsed the inset to 10px. */
2706
2858
  .bb-dropdown__nav-back {
2707
- margin-inline-start: 0;
2859
+ left: var(--bb-panel-p);
2860
+ position: absolute;
2861
+ top: var(--bb-panel-p);
2862
+
2863
+ /* Same chevron_right asset the submenu rows use, pointed back. */
2864
+ .bb-button__icon {
2865
+ transform: rotate(180deg);
2866
+ }
2867
+ }
2868
+
2869
+ /* Spacer kept in the DOM for the drill a11y tests; layout is now the
2870
+ title's equal inline gutters below. */
2871
+ .bb-dropdown__nav-gutter {
2872
+ display: none;
2708
2873
  }
2709
2874
  }
2710
2875
  }
2711
2876
 
2712
- /* Pure spacer, mirroring the back button's box. */
2877
+ /* Pure spacer, mirroring the back button's box (root / non-drill layouts). */
2713
2878
  .bb-dropdown__nav-gutter {
2714
- height: 40px;
2715
- width: 40px;
2716
- }
2717
-
2718
- .bb-dropdown__nav-back {
2719
- align-items: center;
2720
- background: transparent;
2721
- border: none;
2722
- border-radius: var(--bb-radius-sm);
2723
- color: var(--bb-text);
2724
- cursor: pointer;
2725
- display: inline-flex;
2726
- flex-shrink: 0;
2727
- /* Touch target: this is the sheet's only way back up a level. */
2728
- height: 40px;
2729
- justify-content: center;
2730
- margin-inline-start: -8px;
2731
- width: 40px;
2732
-
2733
- &:hover {
2734
- background-color: color-mix(in oklab, var(--bb-text) 8%, transparent);
2735
- }
2736
-
2737
- .bb-dropdown__nav-back-icon {
2738
- /* The same glyph, at the same size and weight, as the `>` a row shows
2739
- for a submenu — they are the two halves of one navigation and used to
2740
- differ in colour, so the way OUT looked like a different kind of
2741
- control from the way IN. */
2742
- --size: 20px;
2743
- color: var(--bb-text-muted);
2744
- /* Reuse `chevron_right` pointed the other way (no left asset exists). */
2745
- transform: rotate(180deg);
2746
- }
2879
+ height: 24px;
2880
+ width: 24px;
2747
2881
  }
2748
2882
 
2749
- .bb-dropdown__nav-title {
2883
+ .bb-offcanvas__title.bb-dropdown__nav-title {
2750
2884
  flex: auto;
2751
2885
  font-weight: 500;
2886
+ /* Same 1em line box as BbSelect's sheet title so the cap sits on the
2887
+ same baseline. Descenders ("Tags") paint into the drill nav's bottom
2888
+ pad — `overflow-y: visible`; ellipsis still clips on X only. */
2752
2889
  min-width: 0;
2753
- overflow: hidden;
2890
+ overflow-x: clip;
2891
+ overflow-y: visible;
2754
2892
  text-overflow: ellipsis;
2755
2893
  white-space: nowrap;
2756
2894
  }
2757
2895
 
2758
2896
  .bb-dropdown__nav--drill .bb-dropdown__nav-title {
2897
+ /* Equal side gutters matching BbButton sm icon-only (24px) so the label
2898
+ centres. */
2899
+ display: block;
2900
+ padding-inline: 24px;
2759
2901
  text-align: center;
2760
2902
  }
2761
2903
  }