@keenmate/web-multiselect 1.12.0-rc05 → 1.12.0-rc06

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,6 +21,17 @@ 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 v1.12.0-rc06
25
+
26
+ - **Tree of options — options can render as an always-expanded hierarchy** — give each option a materialized dot-path (`"1"`, `"1.1"`, `"1.1.1"`, …) and set `path-member` (or `el.getPathCallback`) and tree mode auto-enables, deriving parent/level from the path and rendering depth-first with indentation. The hierarchy model is a trimmed lift of `@keenmate/web-treeview`'s `ltree` (`src/tree/`) so ordering matches the treeview. There is no collapse/chevron — reach for `@keenmate/web-treeview` when you need expand/collapse. A separate `renderTreeNode` path carries the same selection/checkbox/icon/subtitle content plus depth indentation (via `--ms-tree-depth` → `src/css/tree.css`, tuned with `--ms-tree-indent`), and search filters the hierarchy to matches plus their ancestors so indentation stays coherent.
27
+ - **Per-node selectability — mark tree nodes non-selectable without greying them out** — `is-selectable-member` (a data flag) or `el.getIsSelectableCallback` (a predicate over the built `LTreeNode`, so it can read the derived `node.hasChildren`) makes the common "leaves only" rule a one-liner. A non-selectable node is distinct from a disabled one: it renders *normally* but drops its checkbox, is skipped by keyboard focus, and can't be toggled by click/Enter or Select All. New `.ms__option--tree-unselectable` theming hook (plus `data-selectable="false"`) ships with only `cursor: default` and no hover highlight, so structural branch rows read as structure while staying visually normal.
28
+ - **External search now composes with tree mode** — previously the async `searchCallback` path set the flat `filteredOptions` but not the parallel `treeNodes`, so an externalized search over a tree rendered blank or mis-indexed. It now rebuilds the hierarchy from the returned matches, keeping their ancestors (`rebuildTreeVisibleFromMatches`) — the async analogue of the built-in matches-plus-ancestors behaviour — and the below-min-length / error fallbacks are tree-aware too. A new `examples-search-index.html` demonstrates delegating the whole search to FlexSearch (fuzzy / accent-insensitive / ranked) over both a flat list and the full ISCO-08 tree, with an exact/prefix code lookup so `2211` resolves to its node and `72` to the whole subtree. FlexSearch stays a dev/consumer dependency — never in the shipped bundle.
29
+ - **Badge full title — render a supplied breadcrumb on badges** — new `full-title-member` (or `el.getFullTitleCallback`) reads a fully-qualified label that ships with the data (e.g. `"Fruit / Pome fruit / Apple"`); it is never computed. Turn on `show-badge-full-title` and badges render that full title instead of the display value, falling back to the display value for options without one, while an explicit `getBadgeDisplayCallback` still wins. Pairs naturally with tree mode to show the full path in the badge.
30
+ - **Adjustable row height and long-title handling** — new CSS variables (no new attributes) control every `.ms__option`, flat and tree: `--ms-option-min-height` sets a consistent minimum row height for non-virtual rows (they still grow if content is taller; virtual rows keep the fixed `--ms-option-height`), and `--ms-option-title-white-space` / `-overflow` / `-text-overflow` flip a title from the default wrap to a one-line ellipsis. `.ms__option-title` is the label hook (the analog of web-treeview's `.wtv__node-label`), and because its flex ancestors already set `min-width: 0`, ellipsis works with no extra CSS — pair it with `enable-option-tooltips` to reveal the full text on hover.
31
+ - **Cascade checkboxes for tree mode, with a value policy** — set `checkbox-mode="cascade"` and checking a node toggles its whole subtree while a partially-selected branch shows a tristate (dash) box. Orthogonally, `cascade-select-policy` decides *which values* the selection emits: `rolled-up` (default) collapses a fully-selected subtree to one "complete node" value and surfaces individual descendants under partially-selected branches (the minimal cover — the bit svelte-treeview lacks); `leaves` emits only checked leaves; `all` emits every checked node like web-treeview. Canonical state is the set of checked leaf atoms and the emitted `selectedValues` is a pure projection, so badges/form/`change` are untouched and every policy round-trips. Non-breaking — `independent` (the previous behaviour) is the default. Logic is isolated in `src/tree/cascade.ts`; tristate renders from a CSS modifier class (virtual-scroll-safe). Demoed as §10 on `examples-tree.html`.
32
+ - **More pronounced option checkboxes** — the unchecked box draws its border from a dedicated mid-grey token (`--ms-checkbox-border-color`, default `#8f8f8f`) instead of the faint generic `--ms-border`, so it reads as a real, clickable control, with a slightly bolder checkmark. New themeable tokens `--ms-checkbox-border-width` (1px), `--ms-checkbox-border-color`, and `--ms-checkbox-checkmark-thickness` drive it, and `--ms-checkbox-border` / `--ms-checkbox-checked-border` recompose from the width token — override any of them to restyle.
33
+ - **Fixes — stable tree rows across search** — two virtual-scroll/tree regressions are fixed: row height no longer jumps as filtering crosses the virtual-scroll threshold (non-virtual rows now pin to `--ms-option-height` when virtual scroll is enabled), and clearing a search with Escape (or reopening after a search) no longer leaves the dropdown blank — both the Escape-clear and `close()` paths route through a tree-aware `resetVisibleToAll()` that rebuilds `treeNodes` from the full tree.
34
+
24
35
  ## What's New in v1.12.0-rc05
25
36
 
26
37
  > ⚠️ **Breaking change — event-handler rename, no aliases.** The fire-and-forget notification callbacks are renamed to `on*` with **no backward-compatible aliases**: `selectCallback` → `onSelect`, `deselectCallback` → `onDeselect`, `changeCallback` → `onChange`, and on `ActionButton`, `isVisibleCallback` / `isDisabledCallback` → `getIsVisibleCallback` / `getIsDisabledCallback`. If you assign these as element properties or config keys, **rename them or your handlers silently stop firing**. The `select` / `deselect` / `change` **DOM events are unchanged**, so `addEventListener(...)` consumers are unaffected. Full rationale in the first bullet below.
@@ -34,13 +45,6 @@ Reads `--base-*` variables from the page if [`@keenmate/theme-designer`](https:/
34
45
  - **Async search — dropdown stops jumping between empty and loading states** — the "No data" empty state and the loading spinner now share a min-height (`--ms-state-min-height`, a new themable variable), so the panel keeps a stable footprint instead of resizing as an async search swaps one block for the other.
35
46
  - **Docs — new live demo page for events & interceptors** — `examples-events-callbacks.html` shows the DOM events, the `on*` property twin firing in parallel, and interactive veto demos for the new interceptors.
36
47
 
37
- ## What's New in v1.12.0-rc04
38
-
39
- - **Placeholders — context-aware wording for pickers and empty cascades** — The input used to show `"Search..."` unconditionally, even when search was off or there was nothing to pick. Two new attributes fix that. `select-placeholder` (default `"Pick an option..."`) is shown when the input isn't a usable search box — `enable-search="false"`, or `search-input-mode="readonly"`/`"hidden"` — so a non-searchable picker stops mislabeling itself. `no-data-placeholder` is an opt-in string shown when the option list is empty, letting users see there's nothing to choose without opening the dropdown; it targets cascade multiselects whose parent isn't resolved yet, and stays unset by default so async-loaded selects don't flash an empty-state before their data lands. Placeholder resolution is now recomputed on live updates too, so changing these attributes, the search mode, or the option list updates the visible text on the fly. **Behavior change:** search-disabled instances without a custom placeholder now read `"Pick an option..."` — set `select-placeholder="Search..."` to keep the old text.
40
- - **setAttributes() — batch attribute updates in a single render** — Setting attributes one at a time triggered a full re-render per call. The new `setAttributes(attrs)` method on `<web-multiselect>` applies a whole map of attributes in one in-place update (or a single reinit at most, if any change is structural). Keys are kebab-case attribute names, exactly like `setAttribute`; a value of `null`/`undefined`/`false` removes the attribute and `true` sets it to `""`. It's particularly handy for i18n language switches that swap several placeholder strings at once without flicker.
41
- - **search-debounce — coalesce async search into one request** — A new `search-debounce` attribute (milliseconds, default `0`) debounces the async `searchCallback` so a burst of keystrokes collapses into a single request instead of one per character. Each keystroke resets the timer, and it applies to the async `searchCallback` path only — local in-memory filtering stays instant. The existing stale-result guard (results applied only if the term still matches) remains as a second line of defense against out-of-order responses. The examples page §8 now ships a "Debounced Search" demo with a live keystrokes-vs-API-calls counter that makes the coalescing obvious.
42
- - **searchCallback AbortSignal — cancel superseded in-flight requests** — `searchCallback` now receives an `AbortSignal` as its second argument: `(searchTerm, signal) => Promise<options[]>`. When a newer search supersedes an in-flight one — or the term drops below `min-search-length`, or the component is destroyed — the previous request's signal is aborted; forward it into `fetch(url, { signal })` to actually cancel the network call. It's backward compatible: the argument is optional, and existing callbacks that ignore it keep working with their stale results discarded as before. Aborted or superseded responses never overwrite the live filtered options.
43
-
44
48
  > ⚠️ **Security notice:** This component intentionally allows raw HTML in rendering callbacks to give developers full control over content display. If you display user-generated content, you must sanitize it yourself. See [docs/examples.md → HTML Injection (XSS) notice](./docs/examples.md#html-injection-xss-notice) for the complete list of affected callbacks.
45
49
 
46
50
  ## Demos & docs
package/dist/index.d.ts CHANGED
@@ -120,6 +120,31 @@ export declare const interactionLogger: any;
120
120
  */
121
121
  export declare const LOGGING_CATEGORIES: string[];
122
122
 
123
+ declare interface LTreeNode<T> {
124
+ treeId: string;
125
+ id: NodeId;
126
+ /** Materialized dot-path, e.g. "1.2.3". */
127
+ path: string;
128
+ /** This node's own segment relative to its parent. */
129
+ pathSegment: string;
130
+ parentPath: string | null | undefined;
131
+ /** Depth in the tree (1-based; root children are level 1). */
132
+ level: number | null | undefined;
133
+ /** Children keyed by prefixed segment (see `segmentPrefix` in ltree.ts). */
134
+ children: Record<string, LTreeNode<T>>;
135
+ hasChildren: boolean;
136
+ /**
137
+ * Whether this node may be selected. Defaults to `true`; set `false` (via
138
+ * `isSelectableMember`/`getIsSelectableCallback`) to make a node non-interactive
139
+ * — it renders normally (no grey/disabled styling) but has no checkbox, is
140
+ * skipped by keyboard focus, and cannot be toggled or selected by Select-All.
141
+ * This is distinct from `disabled` (which greys the row out).
142
+ */
143
+ isSelectable: boolean;
144
+ /** The original option object this node was built from. */
145
+ data: T | null | undefined;
146
+ }
147
+
123
148
  /**
124
149
  * Generic configuration options for the MultiSelect component
125
150
  * @template T The type of data items
@@ -137,6 +162,15 @@ declare interface MultiSelectConfig<T = any> {
137
162
  getDisplayValueCallback?: (item: T) => string;
138
163
  /** Callback to customize badge display text (defaults to display value if not provided) */
139
164
  getBadgeDisplayCallback?: (item: T) => string;
165
+ /**
166
+ * Member property name for a "full title" — a fully-qualified label that ships with the
167
+ * data (e.g. a breadcrumb like "Fruit / Pome fruit / Apple"). It is never computed by the
168
+ * component. When `isBadgeFullTitleShown` is on, badges display this instead of the display
169
+ * value (falling back to the display value when an option has none).
170
+ */
171
+ fullTitleMember?: string;
172
+ /** Callback to extract the full title from an item (takes precedence over `fullTitleMember`). */
173
+ getFullTitleCallback?: (item: T) => string;
140
174
  /** Callback to add custom CSS classes to badges - return string or array of class names */
141
175
  getBadgeClassCallback?: (item: T) => string | string[];
142
176
  /** Callback to inject custom CSS into Shadow DOM - return CSS string for styling custom classes */
@@ -153,6 +187,55 @@ declare interface MultiSelectConfig<T = any> {
153
187
  subtitleMember?: string;
154
188
  /** Callback to extract subtitle from item */
155
189
  getSubtitleCallback?: (item: T) => string;
190
+ /**
191
+ * Enable tree mode: options are rendered as a hierarchy, indented by depth.
192
+ * Auto-enabled when a path source (`pathMember`/`getPathCallback`) is set;
193
+ * pass `false` to force it off. The tree is always fully expanded — there is
194
+ * no collapse (use @keenmate/web-treeview if you need expand/collapse).
195
+ */
196
+ isTreeEnabled?: boolean;
197
+ /** Member property name holding each option's materialized dot-path (e.g. "1.2.3"). */
198
+ pathMember?: string;
199
+ /** Callback returning an option's materialized dot-path (takes precedence over pathMember). */
200
+ getPathCallback?: (item: T) => string;
201
+ /** Member holding an option's parent path (otherwise derived from its path). */
202
+ parentPathMember?: string;
203
+ /** Member holding an option's depth/level (otherwise derived from its path). */
204
+ levelMember?: string;
205
+ /** Member holding a precomputed hasChildren flag (otherwise derived from the tree). */
206
+ hasChildrenMember?: string;
207
+ /** Path separator for tree paths. Default: "." */
208
+ treePathSeparator?: string;
209
+ /**
210
+ * Member holding a per-option `isSelectable` flag for tree mode. A node with a
211
+ * falsy value renders normally (NOT greyed like `disabled`) but has no checkbox,
212
+ * is skipped by keyboard focus, and cannot be toggled or picked by Select-All.
213
+ * Options default to selectable. Tree mode only.
214
+ */
215
+ isSelectableMember?: string;
216
+ /**
217
+ * Callback deciding whether a tree node is selectable (takes precedence over
218
+ * `isSelectableMember`). Receives the built tree node, so `node.hasChildren` /
219
+ * `node.level` are available — e.g. `(node) => !node.hasChildren` for a
220
+ * leaves-only tree. Tree mode only.
221
+ */
222
+ getIsSelectableCallback?: (node: LTreeNode<T>) => boolean;
223
+ /**
224
+ * Tree checkbox interaction. `independent` (default) toggles only the clicked
225
+ * node. `cascade` checks a node's whole subtree and shows a tristate
226
+ * (checked / indeterminate / unchecked) box on branches. Tree + multiple only.
227
+ */
228
+ checkboxMode?: 'independent' | 'cascade';
229
+ /**
230
+ * In `cascade` mode, which values a selection emits (badges / form / change):
231
+ * - `rolled-up` (default) — minimal cover: a fully-selected subtree collapses
232
+ * to its root ("complete node"); partially-selected branches emit their
233
+ * individually-checked descendants. Rolls to the nearest selectable
234
+ * descendant when the complete node itself is non-selectable.
235
+ * - `leaves` — only the checked leaf-level nodes.
236
+ * - `all` — every fully-checked node (branches and leaves), like web-treeview.
237
+ */
238
+ cascadeSelectPolicy?: 'rolled-up' | 'leaves' | 'all';
156
239
  /** Member property name for group extraction */
157
240
  groupMember?: string;
158
241
  /** Callback to extract group from item */
@@ -199,6 +282,12 @@ declare interface MultiSelectConfig<T = any> {
199
282
  isAddNewAllowed?: boolean;
200
283
  /** Show count badge next to toggle icon (internal: isCounterShown) */
201
284
  isCounterShown?: boolean;
285
+ /**
286
+ * Make badges display each option's `fullTitleMember` / `getFullTitleCallback` value
287
+ * instead of its display value. Falls back to the display value for options without a
288
+ * full title. An explicit `getBadgeDisplayCallback` still takes precedence. Off by default.
289
+ */
290
+ isBadgeFullTitleShown?: boolean;
202
291
  /** Keep initial options visible when searchCallback is active and search term is empty/short (internal: isKeepOptionsOnSearch) */
203
292
  isKeepOptionsOnSearch?: boolean;
204
293
  /** Keep search text and filtered results when dropdown closes (default: true) */
@@ -359,11 +448,15 @@ export declare class MultiSelectElement<T = any> extends BaseElement {
359
448
  private _getIconCallback?;
360
449
  private _subtitleMember?;
361
450
  private _getSubtitleCallback?;
451
+ private _getFullTitleCallback?;
362
452
  private _groupMember?;
363
453
  private _getGroupCallback?;
364
454
  private _renderGroupLabelContentCallback?;
365
455
  private _disabledMember?;
366
456
  private _getDisabledCallback?;
457
+ private _getPathCallback?;
458
+ private _isTreeEnabled?;
459
+ private _getIsSelectableCallback?;
367
460
  private _getValueFormatCallback?;
368
461
  private _getBadgeTooltipCallback?;
369
462
  private _getOptionTooltipCallback?;
@@ -447,10 +540,28 @@ export declare class MultiSelectElement<T = any> extends BaseElement {
447
540
  get iconMember(): string | null;
448
541
  set subtitleMember(value: string | null);
449
542
  get subtitleMember(): string | null;
543
+ set fullTitleMember(value: string | null);
544
+ get fullTitleMember(): string | null;
450
545
  set groupMember(value: string | null);
451
546
  get groupMember(): string | null;
452
547
  set disabledMember(value: string | null);
453
548
  get disabledMember(): string | null;
549
+ set pathMember(value: string | null);
550
+ get pathMember(): string | null;
551
+ set parentPathMember(value: string | null);
552
+ get parentPathMember(): string | null;
553
+ set levelMember(value: string | null);
554
+ get levelMember(): string | null;
555
+ set hasChildrenMember(value: string | null);
556
+ get hasChildrenMember(): string | null;
557
+ set isSelectableMember(value: string | null);
558
+ get isSelectableMember(): string | null;
559
+ set treePathSeparator(value: string | null);
560
+ get treePathSeparator(): string | null;
561
+ set checkboxMode(value: 'independent' | 'cascade' | null);
562
+ get checkboxMode(): 'independent' | 'cascade' | null;
563
+ set cascadeSelectPolicy(value: 'rolled-up' | 'leaves' | 'all' | null);
564
+ get cascadeSelectPolicy(): 'rolled-up' | 'leaves' | 'all' | null;
454
565
  set getValueCallback(callback: ((item: T) => string | number) | undefined);
455
566
  get getValueCallback(): ((item: T) => string | number) | undefined;
456
567
  set getDisplayValueCallback(callback: ((item: T) => string) | undefined);
@@ -467,8 +578,24 @@ export declare class MultiSelectElement<T = any> extends BaseElement {
467
578
  get getIconCallback(): ((item: T) => string) | undefined;
468
579
  set getSubtitleCallback(callback: ((item: T) => string) | undefined);
469
580
  get getSubtitleCallback(): ((item: T) => string) | undefined;
581
+ /** Callback returning an option's full title (used by badges when show-badge-full-title is on). */
582
+ set getFullTitleCallback(callback: ((item: T) => string) | undefined);
583
+ get getFullTitleCallback(): ((item: T) => string) | undefined;
470
584
  set getGroupCallback(callback: ((item: T) => string) | undefined);
471
585
  get getGroupCallback(): ((item: T) => string) | undefined;
586
+ /** Callback returning an option's materialized dot-path (enables tree mode). */
587
+ set getPathCallback(callback: ((item: T) => string) | undefined);
588
+ get getPathCallback(): ((item: T) => string) | undefined;
589
+ /** Force tree mode on/off. When unset, tree mode auto-enables if a path source is present. */
590
+ set isTreeEnabled(value: boolean | undefined);
591
+ get isTreeEnabled(): boolean | undefined;
592
+ /**
593
+ * Callback deciding whether a tree node is selectable (takes precedence over
594
+ * `is-selectable-member`). Receives the built node — e.g.
595
+ * `el.getIsSelectableCallback = (node) => !node.hasChildren` for leaves only.
596
+ */
597
+ set getIsSelectableCallback(callback: ((node: LTreeNode<T>) => boolean) | undefined);
598
+ get getIsSelectableCallback(): ((node: LTreeNode<T>) => boolean) | undefined;
472
599
  set renderGroupLabelContentCallback(callback: ((groupName: string) => string | HTMLElement) | undefined);
473
600
  get renderGroupLabelContentCallback(): ((groupName: string) => string | HTMLElement) | undefined;
474
601
  set getDisabledCallback(callback: ((item: T) => boolean) | undefined);
@@ -591,6 +718,17 @@ export declare interface MultiSelectOptions extends MultiSelectConfig<MultiSelec
591
718
  onChange?: ((selectedOptions: MultiSelectOption[]) => void) | null;
592
719
  }
593
720
 
721
+ /**
722
+ * LTreeNode — a single node in the option tree.
723
+ *
724
+ * Trimmed lift of web-treeview's `ltree/ltree-node.ts`. The multiselect renders
725
+ * every node expanded (no collapse), so this keeps only the structural fields
726
+ * needed to order nodes and indent them: path/parent/level/children/hasChildren
727
+ * plus the original option `data`. Expand state, drag-drop, checkbox, selection
728
+ * and drop-position fields are all thrown out.
729
+ */
730
+ declare type NodeId = string | number;
731
+
594
732
  /**
595
733
  * Context provided to renderOptionContentCallback
596
734
  */
@@ -649,6 +787,10 @@ export declare class WebMultiSelect<T = any> {
649
787
  private selectedOptions;
650
788
  private allOptions;
651
789
  private filteredOptions;
790
+ private tree;
791
+ private treeNodes;
792
+ private cascadeIndex;
793
+ private cascadeCheckedAtoms;
652
794
  private hiddenInputs;
653
795
  private focusedIndex;
654
796
  private matchingIndices;
@@ -702,14 +844,72 @@ export declare class WebMultiSelect<T = any> {
702
844
  * text independently. Doesn't fit the extractField shape (no tuple/member layer of its own).
703
845
  */
704
846
  private getItemBadgeDisplayValue;
847
+ /**
848
+ * Full title — a fully-qualified label supplied with the data (never computed here). Used by
849
+ * badges when `isBadgeFullTitleShown` is on. Returns undefined when the option has none.
850
+ */
851
+ private getItemFullTitle;
705
852
  private getItemSearchValue;
706
853
  private getItemIcon;
707
854
  private getItemSubtitle;
708
855
  private getItemGroup;
709
856
  private getItemDisabled;
857
+ /**
858
+ * Tree mode: whether the visible node at `index` may be selected. Non-selectable
859
+ * nodes (see `isSelectableMember`/`getIsSelectableCallback`) still render — just
860
+ * without a checkbox — but are skipped by focus and cannot be toggled. Always
861
+ * true outside tree mode. `treeNodes` is index-aligned with `filteredOptions`.
862
+ */
863
+ private isIndexSelectable;
864
+ /** Whether an option may be selected. Always true outside tree mode. */
865
+ private isOptionSelectable;
710
866
  constructor(element: HTMLElement, options?: Partial<MultiSelectConfig<T>>);
711
867
  private init;
712
868
  private parseOptions;
869
+ /** Whether options should be rendered as an (always-expanded) tree. */
870
+ private isTreeMode;
871
+ /** (Re)build the ltree from `allOptions` and derive the visible flat list. */
872
+ private buildTree;
873
+ /**
874
+ * Whether cascade checkbox mode is active: a multi-select tree with
875
+ * `checkbox-mode="cascade"`. Checking a node then toggles its whole subtree
876
+ * and branches show a tristate box.
877
+ */
878
+ private isCascadeMode;
879
+ private cascadePolicy;
880
+ /** Refresh the derived checked-atom set from the emitted `selectedValues`. */
881
+ private refreshCascadeAtoms;
882
+ /**
883
+ * Toggle a tree node in cascade mode: flip its whole subtree, re-project the
884
+ * checked atoms to emitted values under the active policy, and commit the diff
885
+ * so badges / form / change events reflect the policy (rolled-up branches, etc.).
886
+ */
887
+ private toggleTreeCascade;
888
+ /**
889
+ * Derive `treeNodes` + `filteredOptions` from the full tree, applying the
890
+ * current search term. Matching nodes keep all their ancestors visible so
891
+ * indentation stays coherent (the tree is always fully expanded).
892
+ */
893
+ private rebuildTreeVisible;
894
+ /**
895
+ * Reset the visible list to "everything". **Tree-aware**: in tree mode it
896
+ * rebuilds `treeNodes` (kept index-aligned with `filteredOptions`) from the
897
+ * full tree, so the two never drift. A raw `filteredOptions = [...allOptions]`
898
+ * would leave `treeNodes` stale after clearing a search — the virtual list
899
+ * then reserves height for every option but renders blank rows because
900
+ * `treeNodes[index]` is undefined. Always use this to clear the visible list.
901
+ */
902
+ private resetVisibleToAll;
903
+ /**
904
+ * Tree mode: derive the visible list from an **external** set of matched
905
+ * options — e.g. the results returned by `searchCallback` — keeping each
906
+ * match's ancestors so indentation stays coherent. This is the async-search
907
+ * analogue of `rebuildTreeVisible`: the matching is done by the caller (their
908
+ * own index/engine) instead of a local substring test, but ancestor
909
+ * preservation and `treeNodes`/`filteredOptions` index-alignment still happen
910
+ * here. Pass all options to show the whole tree.
911
+ */
912
+ private rebuildTreeVisibleFromMatches;
713
913
  private buildHTML;
714
914
  /**
715
915
  * Check if virtual scroll should be used
@@ -738,6 +938,14 @@ export declare class WebMultiSelect<T = any> {
738
938
  private getBuiltInActionDisabled;
739
939
  private renderActionsHTML;
740
940
  private renderOption;
941
+ /**
942
+ * Render a single tree-mode row. Separate from `renderOption`: a tree row is
943
+ * indented by its depth (via the `--ms-tree-depth` custom property) and
944
+ * tagged branch/leaf, but otherwise carries the same selection/checkbox/
945
+ * icon/subtitle content. The tree is always fully expanded, so there is no
946
+ * chevron/toggle — every node is just a normal, selectable option.
947
+ */
948
+ private renderTreeNode;
741
949
  private highlightMatch;
742
950
  private groupOptions;
743
951
  /** Whether the input currently functions as a usable search field (drives placeholder wording). */
@@ -774,6 +982,13 @@ export declare class WebMultiSelect<T = any> {
774
982
  * Returning -1 from `compute` is a no-op (used for empty list / no match).
775
983
  */
776
984
  private focusBy;
985
+ /**
986
+ * Given a target index and a preferred direction, return the nearest index
987
+ * whose node is selectable (skipping non-selectable tree nodes). Falls back to
988
+ * the opposite direction, then to -1 if nothing is selectable. No-op outside
989
+ * tree mode.
990
+ */
991
+ private resolveSelectableIndex;
777
992
  private focusNext;
778
993
  private focusPrevious;
779
994
  private focusFirst;