@keenmate/web-multiselect 2.2.0-rc02 → 2.2.0

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/README.md CHANGED
@@ -21,22 +21,15 @@ Reads `--base-*` variables from the page if [`@keenmate/theme-designer`](https:/
21
21
  - Custom rendering callbacks for options, badges, and group headers.
22
22
  - Form integration via standard hidden inputs (FormData-compatible).
23
23
 
24
- ## What's New in v2.2.0-rc02
25
-
26
- - **`custom-styles` attribute — style shadow-DOM internals with zero JavaScript** — The existing `customStylesCallback` was the only way to inject custom CSS into the component's shadow root, which locked out consumers who can't reach into script: static HTML pages, server-rendered markup, no-build sites. The new `custom-styles` attribute takes raw CSS as a string (`<web-multiselect custom-styles=".ms__badge { font-weight: bold; }">`) and injects it verbatim — selectors and all — into the same replaceable style slot at the top of the shadow root that the callback uses, so you can restyle badges, options, and your own custom-rendered content declaratively. It's reactive (changing or removing the attribute re-applies or clears the slot), mirrored by a `customStyles` property, and flows through the same dev-mode `--ms-*` lint. When both are set, `customStylesCallback` still wins — the attribute is the static fallback. Demonstrated no-JS on the Custom Rendering page (CR09) and the Basic Usage declarative card.
27
-
28
- - **Single-select no longer holds a stale multi-selection when seeded with several values** — `parseInitialSelection()` used to add every seeded value to the selection unconditionally, so a `multiple="false"` picker could hold — and highlight — multiple rows at once. It surfaced both from a declarative `initial-values="a,b,c"` on a single-select and, more visibly, from flipping `multiple` from `true` to `false` at runtime while items were selected: the reinit reseeds the live selection into the fresh single-select and left the extra rows highlighted. The picker now trims the seed to the first value when it isn't in multiple mode, so exactly one row stays selected.
29
-
30
- - **Async-search dropdown no longer runs off the bottom of the viewport** — An async `searchCallback` that seeds no options anchors its panel while it's still empty or showing the loader — short enough to fit below the input, so with the default `lock-placement` it froze its placement to `bottom`. When results arrived the panel grew to full height but stayed pinned below the input, overflowing the bottom edge instead of flipping up into the free space, because `renderDropdown()` rewrote the list without re-anchoring. `performAsyncSearch()` now calls a new `repositionDropdown()` after results render, tearing down and recreating the Floating-UI anchor so the flip-on-first-compute re-evaluates against the panel's real height, then re-freezes. Scoped to the async path only — locally filtered dropdowns open already populated and are unaffected.
31
-
32
- ## What's New in v2.2.0-rc01
33
-
34
- - **Tree cascade is now the default — check a branch, check its subtree** — In a multi-select tree, `checkbox-mode` now defaults to `cascade` (previously `independent`), so ticking a branch selects its whole subtree and branches render a tristate (checked / indeterminate / unchecked) box — what most tree-select UIs do. The emitted selection follows `cascade-select-policy` (default `rolled-up`: a fully-checked subtree collapses to its root value). This is a behavior change for existing tree consumers — set `checkbox-mode="independent"` to keep the old per-node toggling. Flat and single-select lists are unaffected, since there's no subtree to cascade into.
35
- - **Per-group select-all in flat grouped lists — `group-select-mode="cascade"`** — A new attribute puts a tristate checkbox on each group header in a flat, multi-select, grouped list; clicking it checks or unchecks all of that group's currently-visible members. The group name itself is never a selected value — `getValue()`, badges, and form output carry member values only — a partially-selected group reads indeterminate, and a header toggle fires a single `change`. Disabled members are excluded from the select-all. Default `none` leaves headers inert, as before.
36
- - **Per-group selected counts + one shared count formatter** — Every group header now shows a count of that group's selected members, rendered as the same small chip as the in-input `[N]` counter (not a new badge style). A new `getCountLabelCallback((selected, total) => string)` formats both the in-input counter and the group chip together so they always read the same way — default `[3]`, or return `` `${s}/${t}` `` for an "x / y of total" style, where `total` is the whole option list for the counter and the group's member count for a header.
37
- - **Order the selected items — `selected-order`** — Control the sequence chosen items appear in across badges, partial "+N more", and the selected-items popover: `as-selected` (default), `label-asc` / `label-desc`, `member` (by a `selected-order-member` property or `getSelectedOrderCallback`), or `custom` (a comparator). It's display-only — `getValue()`, form output, and `getSelected()` keep insertion order — and because the ordering runs in one shared place, the partial "+N more" split and its remove button always act on the items sorted *after* the visible slice.
38
- - **Every render callback now receives a context** — `renderGroupLabelContentCallback` gains a second `GroupLabelRenderContext` argument (the group's members and selection, e.g. `selectedCount`), and `renderSelectedItemContentCallback` / `renderSelectedContentCallback` now also receive a context carrying the presentation (`isFullscreen` / `isModal`) — matching the option and badge callbacks. Everything is additive, so existing one-argument callbacks keep working; branch on `isFullscreen` to render leaner content in the phone overlay.
39
- - **Fixes — selection preservation, hidden search field, "+N more" ✕** — Changing a cosmetic attribute (`badges-display-mode` / `badges-position`) no longer wipes the current selection (it's applied in place, and genuine rebuilds now preserve the runtime selection); `search-input-mode="hidden"` no longer collapses the input row, so the toggle stays at the trailing edge; and the "+N more" badge's ✕ now removes exactly the hidden items instead of silently opening the popover.
24
+ ## What's New in v2.2.0
25
+
26
+ - **Standardized callback context — one typed contract for action-button and display callbacks** — The action-button callbacks used to receive the untyped picker instance, and the display `get*` callbacks (badge display/class/tooltip, remove-button tooltip, selected-item class, option tooltip) got only the item. They now receive a typed second argument: a new `ActionContext<T>` (extends `PresentationContext`, carrying component state, the host element, and a `MultiSelectController<T>` imperative facade) for `getIsVisible`/`getIsDisabled`/`getText`/`getClass`/`getTooltip`/`onClick`, and the same `BadgeContentRenderContext` / `OptionContentRenderContext` their `render*` twin already gets for the display siblings. Everything is additive — existing one-argument callbacks keep working — and `ActionContext`, `MultiSelectController`, `MultiSelectKeyboardController`, and `ActionButton` are now exported from the package entry.
27
+ - **`custom-styles` attribute — style shadow-DOM internals with zero JavaScript** — The only way to inject custom CSS into the shadow root used to be `customStylesCallback`, which locked out static HTML, server-rendered markup, and no-build sites. The new `custom-styles` attribute takes raw CSS as a string and injects it verbatim into the same replaceable style slot the callback uses, so you can restyle badges, options, and your own custom-rendered content declaratively. It's reactive, mirrored by a `customStyles` property, and flows through the same dev-mode `--ms-*` lint; when both are set, `customStylesCallback` still wins.
28
+ - **Tree cascade by default, plus per-group select-all** — In a multi-select tree, `checkbox-mode` now defaults to `cascade`: ticking a branch selects its whole subtree, branches render tristate, and the emitted value follows `cascade-select-policy` (default `rolled-up`). This is a behavior change for existing tree consumers — set `checkbox-mode="independent"` to keep per-node toggling. Flat grouped lists get the parallel `group-select-mode="cascade"`, a tristate select-all checkbox on each group header (member values only — the group name is never a value).
29
+ - **Per-group selected counts + one shared count formatter** — Every group header now shows a count of that group's selected members, rendered as the same small chip as the in-input `[N]` counter. A new `getCountLabelCallback((selected, total) => string)` formats both places together so they always read the same way — default `[3]`, or return `` `${s}/${t}` `` for an x-of-total style.
30
+ - **Order the selected items — `selected-order`** — Control the sequence chosen items appear in across badges, the partial "+N more" split, and the popover: `as-selected` (default), `label-asc`/`label-desc`, `member` (via `selected-order-member` or `getSelectedOrderCallback`), or `custom` (a comparator). Display-only — `getValue()`, form output, and `getSelected()` keep insertion order.
31
+ - **Every render callback now carries the presentation context** — `renderSelectedItemContentCallback`, `renderSelectedContentCallback`, and `renderGroupLabelContentCallback` now receive a second context argument (matching the option and badge callbacks), so any custom renderer can branch on `isFullscreen`/`isModal` to render leaner content in the phone overlay. The context render-type interfaces are exported from the package entry, and it's additive — one-argument callbacks are unaffected.
32
+ - **Fixes — selection & layout robustness** — Single-select no longer keeps a stale multi-selection when seeded with several values; an async `searchCallback` dropdown no longer overflows the viewport when results grow after it opens near the bottom; changing a cosmetic attribute (`badges-display-mode`/`badges-position`) no longer wipes the selection; `search-input-mode="hidden"` no longer collapses the input row; and the "+N more" badge's ✕ now removes the hidden items instead of silently opening the popover.
40
33
 
41
34
  ## Demos & docs
42
35
 
@@ -925,6 +925,13 @@
925
925
  "type": {
926
926
  "text": "T"
927
927
  }
928
+ },
929
+ {
930
+ "name": "context",
931
+ "optional": true,
932
+ "type": {
933
+ "text": "BadgeContentRenderContext"
934
+ }
928
935
  }
929
936
  ],
930
937
  "description": "Badge display falls back to the regular display value rather than '[N/A]', so consumers can override badge\ntext independently. Doesn't fit the extractField shape (no tuple/member layer of its own)."
@@ -2091,6 +2098,36 @@
2091
2098
  },
2092
2099
  "description": "Lazily build (and cache) the imperative facade passed to `keydownCallback`. Bound to the\nsame private actions the built-in key handling uses, so consumer shortcuts behave identically."
2093
2100
  },
2101
+ {
2102
+ "kind": "method",
2103
+ "name": "hostEl",
2104
+ "privacy": "private",
2105
+ "return": {
2106
+ "type": {
2107
+ "text": "HTMLElement"
2108
+ }
2109
+ },
2110
+ "description": "The host custom element — `this.element` is the internal `.ms` mount (inside the shadow\nroot), so the host is its root node's `host` when shadowed, else the mount itself. Used as\nthe ActionContext escape hatch (and where a wrapper hangs a server bridge)."
2111
+ },
2112
+ {
2113
+ "kind": "method",
2114
+ "name": "buildActionContext",
2115
+ "privacy": "private",
2116
+ "return": {
2117
+ "type": {
2118
+ "text": "ActionContext<T>"
2119
+ }
2120
+ },
2121
+ "parameters": [
2122
+ {
2123
+ "name": "button",
2124
+ "type": {
2125
+ "text": "ActionButton<T>"
2126
+ }
2127
+ }
2128
+ ],
2129
+ "description": "Build the ActionContext passed (as the additive 2nd arg) to every action-button\ncallback and the `onClick` event: a snapshot of live state plus the shared controller."
2130
+ },
2094
2131
  {
2095
2132
  "kind": "method",
2096
2133
  "name": "clearSearch",
@@ -3032,6 +3069,25 @@
3032
3069
  ],
3033
3070
  "description": "Normalize a class callback result (`string | string[] | null`) to a single\nspace-joined string with falsy entries dropped — e.g. `['a', '', 'b'] → \"a b\"`,\n`null → \"\"`. Callers add their own leading space / base class as needed."
3034
3071
  },
3072
+ {
3073
+ "kind": "method",
3074
+ "name": "badgeRenderContext",
3075
+ "privacy": "private",
3076
+ "return": {
3077
+ "type": {
3078
+ "text": "BadgeContentRenderContext"
3079
+ }
3080
+ },
3081
+ "parameters": [
3082
+ {
3083
+ "name": "isInPopover",
3084
+ "type": {
3085
+ "text": "boolean"
3086
+ }
3087
+ }
3088
+ ],
3089
+ "description": "Build the BadgeContentRenderContext handed to the sibling `get*` callbacks\n(badge display / class / tooltip), so they see the same context the `render*` badge\ncallbacks get: `displayMode`, `isInPopover`, plus the shared presentation fields."
3090
+ },
3035
3091
  {
3036
3092
  "kind": "method",
3037
3093
  "name": "renderBadgeHTML",
@@ -3242,6 +3298,13 @@
3242
3298
  "type": {
3243
3299
  "text": "T"
3244
3300
  }
3301
+ },
3302
+ {
3303
+ "name": "context",
3304
+ "optional": true,
3305
+ "type": {
3306
+ "text": "BadgeContentRenderContext"
3307
+ }
3245
3308
  }
3246
3309
  ],
3247
3310
  "description": "Build the badge-text tooltip content (callback overrides; default = displayValue + optional subtitle on next line)."
@@ -3268,6 +3331,13 @@
3268
3331
  "type": {
3269
3332
  "text": "T"
3270
3333
  }
3334
+ },
3335
+ {
3336
+ "name": "context",
3337
+ "optional": true,
3338
+ "type": {
3339
+ "text": "BadgeContentRenderContext"
3340
+ }
3271
3341
  }
3272
3342
  ],
3273
3343
  "description": "Build the remove-button tooltip text (callback > format string with {0} > \"Remove {name}\")."
@@ -3306,6 +3376,13 @@
3306
3376
  "type": {
3307
3377
  "text": "T"
3308
3378
  }
3379
+ },
3380
+ {
3381
+ "name": "context",
3382
+ "optional": true,
3383
+ "type": {
3384
+ "text": "OptionContentRenderContext"
3385
+ }
3309
3386
  }
3310
3387
  ],
3311
3388
  "description": "Build the option tooltip content (callback overrides; default = displayValue + optional subtitle on next line)."
package/dist/index.d.ts CHANGED
@@ -22,7 +22,7 @@ import { TABLET_MIN_SHORT_SIDE } from '@keenmate/web-components-core';
22
22
  * Action button configuration for dropdown actions (Select All, Clear All, custom actions)
23
23
  * @template T The type of data items
24
24
  */
25
- declare interface ActionButton<T = any> {
25
+ export declare interface ActionButton<T = any> {
26
26
  /** Action identifier ('select-all', 'clear-all', or 'custom' for custom actions) */
27
27
  action: 'select-all' | 'clear-all' | 'custom';
28
28
  /** Button text label */
@@ -42,18 +42,53 @@ declare interface ActionButton<T = any> {
42
42
  isVisible?: boolean;
43
43
  /** Static disabled state - set to true to disable button */
44
44
  isDisabled?: boolean;
45
- /** Custom click handler (required for 'custom' action) */
46
- onClick?: (multiselect: any) => void | Promise<void>;
47
- /** Dynamic visibility callback - return false to hide button (takes priority over isVisible) */
48
- getIsVisibleCallback?: (multiselect: any) => boolean;
49
- /** Dynamic disabled state callback - return true to disable button (takes priority over isDisabled) */
50
- getIsDisabledCallback?: (multiselect: any) => boolean;
51
- /** Dynamic text callback - return button text (takes priority over text) */
52
- getTextCallback?: (multiselect: any) => string;
53
- /** Dynamic CSS class callback - return class name(s) (takes priority over cssClass) */
54
- getClassCallback?: (multiselect: any) => string | string[];
55
- /** Dynamic tooltip callback - return tooltip text (takes priority over tooltip) */
56
- getTooltipCallback?: (multiselect: any) => string;
45
+ /**
46
+ * Custom click handler (required for 'custom' action). The 1st arg is the live picker
47
+ * instance (as before); the additive 2nd arg is a typed {@link ActionContext} (state +
48
+ * controller). One-argument handlers keep working.
49
+ */
50
+ onClick?: (multiselect: any, context?: ActionContext<T>) => void | Promise<void>;
51
+ /** Dynamic visibility callback - return false to hide button (takes priority over isVisible). Additive 2nd arg: {@link ActionContext}. */
52
+ getIsVisibleCallback?: (multiselect: any, context?: ActionContext<T>) => boolean;
53
+ /** Dynamic disabled state callback - return true to disable button (takes priority over isDisabled). Additive 2nd arg: {@link ActionContext}. */
54
+ getIsDisabledCallback?: (multiselect: any, context?: ActionContext<T>) => boolean;
55
+ /** Dynamic text callback - return button text (takes priority over text). Additive 2nd arg: {@link ActionContext}. */
56
+ getTextCallback?: (multiselect: any, context?: ActionContext<T>) => string;
57
+ /** Dynamic CSS class callback - return class name(s) (takes priority over cssClass). Additive 2nd arg: {@link ActionContext}. */
58
+ getClassCallback?: (multiselect: any, context?: ActionContext<T>) => string | string[];
59
+ /** Dynamic tooltip callback - return tooltip text (takes priority over tooltip). Additive 2nd arg: {@link ActionContext}. */
60
+ getTooltipCallback?: (multiselect: any, context?: ActionContext<T>) => string;
61
+ }
62
+
63
+ /**
64
+ * Context handed (as the additive 2nd argument) to the action-button callbacks —
65
+ * {@link ActionButton.getTextCallback} / `getIsVisibleCallback` / `getIsDisabledCallback` /
66
+ * `getClassCallback` / `getTooltipCallback` — and to the `onClick` event. A typed snapshot of
67
+ * live state plus a {@link MultiSelectController} facade, replacing reliance on the untyped picker
68
+ * instance passed as the first argument. Extends {@link PresentationContext}, so a callback can
69
+ * branch on how the panel is presented (floating vs fullscreen), consistent with the render callbacks.
70
+ */
71
+ export declare interface ActionContext<T = any> extends PresentationContext {
72
+ /** The action button config entry this callback belongs to. */
73
+ button: ActionButton<T>;
74
+ /** Currently selected scalar values. */
75
+ selectedValues: (string | number)[];
76
+ /** Currently selected option objects, in selection order. */
77
+ selectedOptions: T[];
78
+ /** All available options (post assignment). */
79
+ options: ReadonlyArray<T>;
80
+ /** `selectedOptions.length` — convenience. */
81
+ selectedCount: number;
82
+ /** `options.length` — convenience (the Select-All denominator). */
83
+ optionCount: number;
84
+ /** Whether the dropdown is currently open. */
85
+ isOpen: boolean;
86
+ /** The current search term. */
87
+ searchTerm: string;
88
+ /** Imperative facade — drive the picker (selection / dropdown / search / messages). */
89
+ controller: MultiSelectController<T>;
90
+ /** Escape hatch: the host custom element, for anything not on the controller. */
91
+ element: HTMLElement & Record<string, any>;
57
92
  }
58
93
 
59
94
  /** Horizontal arrangement of buttons within an action row. `stretch` = full-width (default). */
@@ -218,8 +253,15 @@ declare interface MultiSelectConfig<T = any> {
218
253
  displayValueMember?: string;
219
254
  /** Callback to extract display value from item */
220
255
  getDisplayValueCallback?: (item: T) => string;
221
- /** Callback to customize badge display text (defaults to display value if not provided) */
222
- getBadgeDisplayCallback?: (item: T) => string;
256
+ /**
257
+ * Callback to customize badge display text (defaults to display value if not provided).
258
+ * The second argument is additive: when the value is computed while rendering a specific
259
+ * badge/popover item it receives that item's {@link BadgeContentRenderContext} (`displayMode`,
260
+ * `isInPopover`, and the shared presentation fields); it is **absent** when the value is needed
261
+ * outside a render (e.g. selected-order sorting, the counter chip's title), so one-argument
262
+ * callbacks keep working. Treat `context` as optional.
263
+ */
264
+ getBadgeDisplayCallback?: (item: T, context?: BadgeContentRenderContext) => string;
223
265
  /**
224
266
  * Order of the CURRENTLY-SELECTED items *where they are displayed* — badges, partial mode
225
267
  * (i.e. which items sit behind the "+N more" badge), and the selected-items popover. This is a
@@ -248,8 +290,13 @@ declare interface MultiSelectConfig<T = any> {
248
290
  fullTitleMember?: string;
249
291
  /** Callback to extract the full title from an item (takes precedence over `fullTitleMember`). */
250
292
  getFullTitleCallback?: (item: T) => string;
251
- /** Callback to add custom CSS classes to badges - return string or array of class names */
252
- getBadgeClassCallback?: (item: T) => string | string[];
293
+ /**
294
+ * Callback to add custom CSS classes to badges - return string or array of class names.
295
+ * Additive 2nd arg: receives the badge's {@link BadgeContentRenderContext} when invoked during
296
+ * a badge render (the same context {@link renderBadgeContentCallback} gets), so classes can react
297
+ * to `displayMode` / `isInPopover` / presentation. Optional — one-argument callbacks keep working.
298
+ */
299
+ getBadgeClassCallback?: (item: T, context?: BadgeContentRenderContext) => string | string[];
253
300
  /** Callback to inject custom CSS into Shadow DOM - return CSS string for styling custom classes */
254
301
  customStylesCallback?: () => string;
255
302
  /**
@@ -369,8 +416,13 @@ declare interface MultiSelectConfig<T = any> {
369
416
  * presentation fields. The second argument is additive; one-argument callbacks keep working.
370
417
  */
371
418
  renderSelectedItemContentCallback?: (item: T, context: BadgeContentRenderContext) => string | HTMLElement;
372
- /** Callback to add custom CSS classes to selected items in popover - return string or array of class names */
373
- getSelectedItemClassCallback?: (item: T) => string | string[];
419
+ /**
420
+ * Callback to add custom CSS classes to selected items in popover - return string or array of
421
+ * class names. Additive 2nd arg: receives the {@link BadgeContentRenderContext} for the popover
422
+ * item (`isInPopover` is `true`) — the same context {@link renderSelectedItemContentCallback}
423
+ * gets. Optional — one-argument callbacks keep working.
424
+ */
425
+ getSelectedItemClassCallback?: (item: T, context?: BadgeContentRenderContext) => string | string[];
374
426
  /**
375
427
  * Custom renderer for the selected item display in single-select mode — return plain text (it
376
428
  * becomes the input value). Receives a {@link SelectedContentRenderContext} (2nd arg) carrying
@@ -694,10 +746,17 @@ declare interface MultiSelectConfig<T = any> {
694
746
  getCountLabelCallback?: ((selected: number, total: number) => string) | null;
695
747
  /** Enable tooltips on selected item badges (internal: isBadgeTooltipsEnabled) */
696
748
  isBadgeTooltipsEnabled?: boolean;
697
- /** Callback to generate custom tooltip content for a badge */
698
- getBadgeTooltipCallback?: ((item: T) => string | HTMLElement) | null;
699
- /** Callback to generate custom tooltip text for a remove button */
700
- getRemoveButtonTooltipCallback?: ((item: T) => string) | null;
749
+ /**
750
+ * Callback to generate custom tooltip content for a badge. Additive 2nd arg: receives the
751
+ * badge's {@link BadgeContentRenderContext} (`displayMode` / `isInPopover` / presentation), the
752
+ * same context {@link renderBadgeContentCallback} gets. Optional — one-argument callbacks keep working.
753
+ */
754
+ getBadgeTooltipCallback?: ((item: T, context?: BadgeContentRenderContext) => string | HTMLElement) | null;
755
+ /**
756
+ * Callback to generate custom tooltip text for a remove button. Additive 2nd arg: the badge's
757
+ * {@link BadgeContentRenderContext}. Optional — one-argument callbacks keep working.
758
+ */
759
+ getRemoveButtonTooltipCallback?: ((item: T, context?: BadgeContentRenderContext) => string) | null;
701
760
  /** Format string for remove button tooltip text. Use {0} as placeholder for item name. Default: "Remove {0}" */
702
761
  removeButtonTooltipText?: string;
703
762
  /**
@@ -713,8 +772,14 @@ declare interface MultiSelectConfig<T = any> {
713
772
  badgeTooltipOffset?: number;
714
773
  /** Enable tooltips on dropdown options (internal: isOptionTooltipsEnabled) */
715
774
  isOptionTooltipsEnabled?: boolean;
716
- /** Callback to generate custom tooltip content for a dropdown option. Default: display value, plus subtitle on the next line when present. */
717
- getOptionTooltipCallback?: ((item: T) => string | HTMLElement) | null;
775
+ /**
776
+ * Callback to generate custom tooltip content for a dropdown option. Default: display value, plus
777
+ * subtitle on the next line when present. Additive 2nd arg: receives the row's
778
+ * {@link OptionContentRenderContext} (`index`, `isSelected`, `isFocused`, `isMatched`,
779
+ * `isDisabled`, presentation), the same context {@link renderOptionContentCallback} gets, so an
780
+ * option tooltip can match how the row itself was rendered. Optional — one-argument callbacks keep working.
781
+ */
782
+ getOptionTooltipCallback?: ((item: T, context?: OptionContentRenderContext) => string | HTMLElement) | null;
718
783
  /**
719
784
  * Option tooltip placement (Floating UI `Placement`). Default `top-start`
720
785
  * (anchored to the row's start edge, so it doesn't center on a full-width row).
@@ -736,6 +801,46 @@ declare interface MultiSelectConfig<T = any> {
736
801
  hostElement?: HTMLElement;
737
802
  }
738
803
 
804
+ /**
805
+ * Imperative facade for driving a live picker from a callback without reaching into internals.
806
+ * Shared base of the callback controllers: {@link MultiSelectKeyboardController} (handed to
807
+ * `keydownCallback`) extends it with focus-navigation, and {@link ActionContext} exposes it to
808
+ * the action-button callbacks. Every method mirrors a public element method.
809
+ */
810
+ export declare interface MultiSelectController<T = any> {
811
+ /** The selected option objects, in selection order (mirrors `el.getSelected()`). */
812
+ getSelected(): T[];
813
+ /** The selection as returned to forms/consumers — scalar or array per `multiple` (mirrors `el.getValue()`). */
814
+ getValue(): string | number | (string | number)[] | null;
815
+ /** All available options (post assignment, pre-filter). */
816
+ getOptions(): ReadonlyArray<T>;
817
+ /** Replace the selection. Silent by default; pass `{ notify: true }` to emit ONE aggregate `change`. */
818
+ setSelected(values: (string | number)[], opts?: {
819
+ notify?: boolean;
820
+ }): void;
821
+ /** Select every selectable option. */
822
+ selectAll(): void;
823
+ /** Clear the whole selection. */
824
+ clearAll(): void;
825
+ /** Toggle a single option by its value (select ⇄ deselect). */
826
+ toggleValue(value: string | number): void;
827
+ open(): void;
828
+ close(): void;
829
+ toggle(): void;
830
+ /** Set the search box text (runs the search, exactly as if typed). */
831
+ search(term: string): void;
832
+ /** Clear the search box and restore the full list (does not touch the selection). */
833
+ clearSearch(): void;
834
+ /** Scroll a specific option / group / index into view. */
835
+ scrollToValue(value: string | number): void;
836
+ scrollToGroup(group: string): void;
837
+ scrollToIndex(index: number): void;
838
+ /** Surface a transient message ("toast"), visible even in the fullscreen overlay. */
839
+ showMessage(content: string | HTMLElement, opts?: MessageOptions): void;
840
+ /** Dismiss the transient message, if any. */
841
+ hideMessage(): void;
842
+ }
843
+
739
844
  export declare class MultiSelectElement<T = any> extends BlissElement<MultiSelectEvents> {
740
845
  #private;
741
846
  static formAssociated: boolean;
@@ -902,10 +1007,11 @@ declare type MultiSelectEvents = {
902
1007
  };
903
1008
 
904
1009
  /**
905
- * Imperative facade handed to `keydownCallback` so a consumer can drive the picker without
906
- * reaching into internals. Every method mirrors a built-in keyboard action.
1010
+ * Imperative facade handed to `keydownCallback`: the shared {@link MultiSelectController} plus the
1011
+ * focus-navigation methods that only make sense mid-keystroke. Every method mirrors a built-in
1012
+ * keyboard action.
907
1013
  */
908
- declare interface MultiSelectKeyboardController<T = any> {
1014
+ export declare interface MultiSelectKeyboardController<T = any> extends MultiSelectController<T> {
909
1015
  /** Move focus to the next / previous option. */
910
1016
  focusNext(): void;
911
1017
  focusPrevious(): void;
@@ -922,19 +1028,12 @@ declare interface MultiSelectKeyboardController<T = any> {
922
1028
  focusIndex(index: number): void;
923
1029
  /** Toggle the currently focused option (no-op if nothing is focused). */
924
1030
  toggleFocused(): void;
925
- /** Toggle a specific option by its value (select ⇄ deselect). */
926
- toggleValue(value: string | number): void;
927
1031
  /** Select a specific option by its value (no-op if already selected). */
928
1032
  selectValue(value: string | number): void;
929
1033
  /** Deselect a specific option by its value (no-op if not selected). */
930
1034
  deselectValue(value: string | number): void;
931
- /** Open / close the dropdown. */
932
- open(): void;
933
- close(): void;
934
- /** Set the search box text (runs the search). */
1035
+ /** @deprecated Alias of {@link MultiSelectController.search}. */
935
1036
  setSearch(term: string): void;
936
- /** Clear the search box and reset the visible list. */
937
- clearSearch(): void;
938
1037
  }
939
1038
 
940
1039
  /**
@@ -1541,6 +1640,17 @@ export declare class WebMultiSelect<T = any> {
1541
1640
  /** Lazily build (and cache) the imperative facade passed to `keydownCallback`. Bound to the
1542
1641
  * same private actions the built-in key handling uses, so consumer shortcuts behave identically. */
1543
1642
  private getKeyboardController;
1643
+ /**
1644
+ * The host custom element — `this.element` is the internal `.ms` mount (inside the shadow
1645
+ * root), so the host is its root node's `host` when shadowed, else the mount itself. Used as
1646
+ * the {@link ActionContext} escape hatch (and where a wrapper hangs a server bridge).
1647
+ */
1648
+ private hostEl;
1649
+ /**
1650
+ * Build the {@link ActionContext} passed (as the additive 2nd arg) to every action-button
1651
+ * callback and the `onClick` event: a snapshot of live state plus the shared controller.
1652
+ */
1653
+ private buildActionContext;
1544
1654
  /** Clear the search box (both the main input and the fullscreen search) and reset the visible
1545
1655
  * list. Shared by Escape and the keyboard controller. */
1546
1656
  /**
@@ -1909,6 +2019,12 @@ export declare class WebMultiSelect<T = any> {
1909
2019
  * `null → ""`. Callers add their own leading space / base class as needed.
1910
2020
  */
1911
2021
  private classSuffix;
2022
+ /**
2023
+ * Build the {@link BadgeContentRenderContext} handed to the sibling `get*` callbacks
2024
+ * (badge display / class / tooltip), so they see the same context the `render*` badge
2025
+ * callbacks get: `displayMode`, `isInPopover`, plus the shared presentation fields.
2026
+ */
2027
+ private badgeRenderContext;
1912
2028
  /**
1913
2029
  * Render a removable badge for a selected option (used by the badges/partial display modes
1914
2030
  * and by the selected-items popover).