@keenmate/web-multiselect 2.1.0 → 2.2.0-rc01

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/dist/index.d.ts CHANGED
@@ -72,7 +72,7 @@ declare type ActionsPosition = 'top' | 'bottom';
72
72
  /**
73
73
  * Context provided to renderBadgeContentCallback
74
74
  */
75
- declare interface BadgeContentRenderContext extends PresentationContext {
75
+ export declare interface BadgeContentRenderContext extends PresentationContext {
76
76
  /** Current badges display mode */
77
77
  displayMode: BadgesDisplayMode;
78
78
  /** Whether the badge is being rendered in the selected items popover */
@@ -114,6 +114,35 @@ export { EnvironmentSnapshot }
114
114
 
115
115
  export { getEnvironment }
116
116
 
117
+ /**
118
+ * Context handed to `renderGroupLabelContentCallback` as its second argument, so a custom group
119
+ * header can reflect the selection — e.g. render a "3 / 8" count next to the title. The fields are
120
+ * populated for every group header; the selection fields are meaningful mainly under
121
+ * `groupSelectMode: 'cascade'` (a flat multi-select grouped list). Under the default rendering
122
+ * (no callback) the component draws the count itself; with a callback, YOU own the content and can
123
+ * render the count however you like from these fields.
124
+ *
125
+ * Like {@link OptionContentRenderContext} / {@link BadgeContentRenderContext}, it extends the
126
+ * shared {@link PresentationContext} (`presentation` / `isFullscreen` / `isModal`), so a group
127
+ * header can also render leaner in the phone fullscreen overlay.
128
+ */
129
+ export declare interface GroupLabelRenderContext<T = any> extends PresentationContext {
130
+ /** The group name (identical to the callback's first argument). */
131
+ groupName: string;
132
+ /** The group's currently-visible (filtered) members, in render order. */
133
+ members: T[];
134
+ /** The subset of `members` that are currently selected (includes disabled-but-selected). */
135
+ selectedMembers: T[];
136
+ /** `selectedMembers.length` — the number to show "behind the group title". */
137
+ selectedCount: number;
138
+ /** `members.length` — total visible members in the group. */
139
+ memberCount: number;
140
+ /** Visible, non-disabled members — the cascade "select-all" denominator. */
141
+ selectableCount: number;
142
+ /** Tristate roll-up of the group under cascade selection. */
143
+ checkState: 'checked' | 'indeterminate' | 'unchecked';
144
+ }
145
+
117
146
  export declare const initLogger: Logger;
118
147
 
119
148
  export declare const interactionLogger: Logger;
@@ -191,6 +220,25 @@ declare interface MultiSelectConfig<T = any> {
191
220
  getDisplayValueCallback?: (item: T) => string;
192
221
  /** Callback to customize badge display text (defaults to display value if not provided) */
193
222
  getBadgeDisplayCallback?: (item: T) => string;
223
+ /**
224
+ * Order of the CURRENTLY-SELECTED items *where they are displayed* — badges, partial mode
225
+ * (i.e. which items sit behind the "+N more" badge), and the selected-items popover. This is a
226
+ * display concern only: `getValue()`, the form output, and `getSelected()` always keep
227
+ * as-selected (insertion) order regardless of this setting, and the options dropdown is never
228
+ * reordered.
229
+ * - `as-selected` (default) — the order items were picked.
230
+ * - `label-asc` / `label-desc` — by the badge label, A→Z / Z→A (locale-aware).
231
+ * - `member` — by the `selectedOrderMember` property (or `getSelectedOrderCallback`); numeric
232
+ * keys sort numerically, everything else with a locale string compare.
233
+ * - `custom` — delegate to `selectedOrderCompareCallback`.
234
+ */
235
+ selectedOrder?: 'as-selected' | 'label-asc' | 'label-desc' | 'member' | 'custom';
236
+ /** Property name used as the sort key when `selectedOrder === 'member'` (selected-items display only). */
237
+ selectedOrderMember?: string;
238
+ /** Extract the sort key when `selectedOrder === 'member'` (overrides `selectedOrderMember`). */
239
+ getSelectedOrderCallback?: (item: T) => string | number;
240
+ /** Comparator used when `selectedOrder === 'custom'`; standard `(a,b) => number` contract. */
241
+ selectedOrderCompareCallback?: (a: T, b: T) => number;
194
242
  /**
195
243
  * Member property name for a "full title" — a fully-qualified label that ships with the
196
244
  * data (e.g. a breadcrumb like "Fruit / Pome fruit / Apple"). It is never computed by the
@@ -250,9 +298,10 @@ declare interface MultiSelectConfig<T = any> {
250
298
  */
251
299
  getIsSelectableCallback?: (node: LTreeNode<T>) => boolean;
252
300
  /**
253
- * Tree checkbox interaction. `independent` (default) toggles only the clicked
254
- * node. `cascade` checks a node's whole subtree and shows a tristate
255
- * (checked / indeterminate / unchecked) box on branches. Tree + multiple only.
301
+ * Tree checkbox interaction. `cascade` (default) checks a node's whole subtree
302
+ * and shows a tristate (checked / indeterminate / unchecked) box on branches —
303
+ * what most tree-select UIs do. `independent` toggles only the clicked node.
304
+ * Tree + multiple only (no subtree to cascade otherwise). Unset → cascade.
256
305
  */
257
306
  checkboxMode?: 'independent' | 'cascade';
258
307
  /**
@@ -270,8 +319,23 @@ declare interface MultiSelectConfig<T = any> {
270
319
  groupMember?: string;
271
320
  /** Callback to extract group from item */
272
321
  getGroupCallback?: (item: T) => string;
273
- /** Callback to customize group label content (can return HTML) */
274
- renderGroupLabelContentCallback?: (groupName: string) => string | HTMLElement;
322
+ /**
323
+ * Callback to customize group label content (can return HTML). Receives the group name and a
324
+ * {@link GroupLabelRenderContext} with the group's members and selection (e.g. `selectedCount`),
325
+ * so a custom header can show a per-group count. The second argument is additive — existing
326
+ * one-argument callbacks keep working.
327
+ */
328
+ renderGroupLabelContentCallback?: (groupName: string, context: GroupLabelRenderContext<T>) => string | HTMLElement;
329
+ /**
330
+ * Group-header selection in a flat (non-tree) grouped, multi-select list.
331
+ * - `none` (default) — group headers are inert labels.
332
+ * - `cascade` — each header shows a **tristate** checkbox that checks/unchecks
333
+ * all of that group's currently-visible members. The group itself is never a
334
+ * selected value (`getValue()`/badges/form carry member values only); a
335
+ * partially-selected group reads indeterminate. Flat + multiple only — no
336
+ * effect in tree mode (use `checkboxMode`) or single-select.
337
+ */
338
+ groupSelectMode?: 'none' | 'cascade';
275
339
  /** Member property name for disabled state extraction */
276
340
  disabledMember?: string;
277
341
  /** Callback to extract disabled state from item */
@@ -291,12 +355,22 @@ declare interface MultiSelectConfig<T = any> {
291
355
  * selected-items popover keeps using renderSelectedItemContentCallback).
292
356
  */
293
357
  renderBadgeCallback?: (item: T, context: BadgeContentRenderContext) => string | HTMLElement | null | undefined;
294
- /** Custom renderer for selected item content in popover - return HTML string or HTMLElement */
295
- renderSelectedItemContentCallback?: (item: T) => string | HTMLElement;
358
+ /**
359
+ * Custom renderer for selected item content in the selected-items popover — return HTML string
360
+ * or HTMLElement. Receives a {@link BadgeContentRenderContext} (2nd arg) since a popover item
361
+ * is rendered through the same badge path: `isInPopover` is `true`, plus the shared
362
+ * presentation fields. The second argument is additive; one-argument callbacks keep working.
363
+ */
364
+ renderSelectedItemContentCallback?: (item: T, context: BadgeContentRenderContext) => string | HTMLElement;
296
365
  /** Callback to add custom CSS classes to selected items in popover - return string or array of class names */
297
366
  getSelectedItemClassCallback?: (item: T) => string | string[];
298
- /** Custom renderer for selected item display in single-select mode - return plain text */
299
- renderSelectedContentCallback?: (item: T) => string;
367
+ /**
368
+ * Custom renderer for the selected item display in single-select mode — return plain text (it
369
+ * becomes the input value). Receives a {@link SelectedContentRenderContext} (2nd arg) carrying
370
+ * the shared presentation fields. The second argument is additive; one-argument callbacks keep
371
+ * working.
372
+ */
373
+ renderSelectedContentCallback?: (item: T, context: SelectedContentRenderContext) => string;
300
374
  /** HTML form field ID/name for hidden input */
301
375
  formFieldId?: string;
302
376
  /**
@@ -595,8 +669,22 @@ declare interface MultiSelectConfig<T = any> {
595
669
  onDeselect?: ((option: T) => void) | null;
596
670
  /** Event handler: the selection set changed (fire-and-forget). Mirrors the bubbling `change` CustomEvent on the element. */
597
671
  onChange?: ((selectedOptions: T[]) => void) | null;
598
- /** Callback to format count badge text (for i18n/pluralization). When moreCount is provided, it's for the "+X more" badge in partial mode. */
672
+ /**
673
+ * Formats the badges-area count/summary text: the `count` mode badge ("N selected") and the
674
+ * partial-mode "+X more" badge (when `moreCount` is provided). NOT the small `[N]` chip — that's
675
+ * {@link getCountLabelCallback}. For i18n/pluralization.
676
+ */
599
677
  getCounterCallback?: ((count: number, moreCount?: number) => string) | null;
678
+ /**
679
+ * Formats the small count CHIP shared by the in-input counter (`show-counter`) and each group
680
+ * header's per-group count. Distinct from {@link getCounterCallback}, which formats the
681
+ * badges-area "N selected" / "+X more" text. Receives `selected` and `total`: for the in-input
682
+ * counter `total` is the whole option list; for a group header it's that group's member count.
683
+ * Return the label as plain text. Default `[selected]` (e.g. `[3]`). Set it to
684
+ * `` (s, t) => `${s}/${t}` `` for an "x / y" style. One callback drives both so they always
685
+ * read the same way.
686
+ */
687
+ getCountLabelCallback?: ((selected: number, total: number) => string) | null;
600
688
  /** Enable tooltips on selected item badges (internal: isBadgeTooltipsEnabled) */
601
689
  isBadgeTooltipsEnabled?: boolean;
602
690
  /** Callback to generate custom tooltip content for a badge */
@@ -928,7 +1016,7 @@ export { observeViewport }
928
1016
  * render leaner content in the phone overlay. Reactive: swapping presentation re-renders and
929
1017
  * re-invokes the callback with the new value.
930
1018
  */
931
- declare interface OptionContentRenderContext extends PresentationContext {
1019
+ export declare interface OptionContentRenderContext extends PresentationContext {
932
1020
  /** Index of the option in the filtered list */
933
1021
  index: number;
934
1022
  /** Whether the option is currently selected */
@@ -1014,6 +1102,13 @@ export declare type SearchInputMode = 'normal' | 'readonly' | 'hidden';
1014
1102
  */
1015
1103
  export declare type SearchMode = 'filter' | 'navigate';
1016
1104
 
1105
+ /**
1106
+ * Context handed to `renderSelectedContentCallback` (single-select selected-value display). Carries
1107
+ * only the shared {@link PresentationContext} fields (`presentation` / `isFullscreen` / `isModal`),
1108
+ * so the single-select label can render leaner in the phone fullscreen overlay.
1109
+ */
1110
+ export declare type SelectedContentRenderContext = PresentationContext;
1111
+
1017
1112
  /**
1018
1113
  * Set the level of one category. Accepts the full prefixed name
1019
1114
  * (`MULTISELECT:UI`) or the bare suffix (`UI`) — both normalize to the category
@@ -1136,6 +1231,14 @@ export declare class WebMultiSelect<T = any> {
1136
1231
  */
1137
1232
  private getItemFullTitle;
1138
1233
  private getItemSearchValue;
1234
+ /** Sort key for `selectedOrder === 'member'` (member/callback pattern). */
1235
+ private getItemSortKey;
1236
+ /**
1237
+ * Selected options in the order they should be DISPLAYED (badges / partial "+N more" / popover).
1238
+ * Never mutates state — always returns a fresh array. Display concern only: getValue()/form
1239
+ * output/getSelected() keep as-selected (insertion) order. See `selectedOrder`.
1240
+ */
1241
+ private getOrderedSelectedOptions;
1139
1242
  private getItemIcon;
1140
1243
  private getItemSubtitle;
1141
1244
  private getItemGroup;
@@ -1158,8 +1261,10 @@ export declare class WebMultiSelect<T = any> {
1158
1261
  private buildTree;
1159
1262
  /**
1160
1263
  * Whether cascade checkbox mode is active: a multi-select tree with
1161
- * `checkbox-mode="cascade"`. Checking a node then toggles its whole subtree
1162
- * and branches show a tristate box.
1264
+ * `checkbox-mode` NOT set to `independent`. Checking a node then toggles its
1265
+ * whole subtree and branches show a tristate box. Cascade is the DEFAULT
1266
+ * (unset → cascade); opt out per-instance with `checkbox-mode="independent"`.
1267
+ * Only ever active in tree + multiple — no subtree to cascade otherwise.
1163
1268
  */
1164
1269
  private isCascadeMode;
1165
1270
  private cascadePolicy;
@@ -1250,6 +1355,46 @@ export declare class WebMultiSelect<T = any> {
1250
1355
  * Check if any options have groups
1251
1356
  */
1252
1357
  private hasGroups;
1358
+ /**
1359
+ * Whether the flat-group cascade checkbox is active: a multi-select, grouped,
1360
+ * non-tree list with `group-select-mode="cascade"`. When on, each group header
1361
+ * gets a tristate checkbox that toggles all of that group's visible members.
1362
+ * Tree mode has its own `checkbox-mode` cascade, so this stays flat-only.
1363
+ */
1364
+ private isGroupCascadeActive;
1365
+ /**
1366
+ * Tristate check-state of a group from its members: `checked` if every
1367
+ * non-disabled member is selected, `unchecked` if none are, else
1368
+ * `indeterminate`. Disabled members are excluded from the denominator so a
1369
+ * group with a stuck-disabled member can still read fully checked. An empty
1370
+ * (or all-disabled) group reads `unchecked`.
1371
+ */
1372
+ private groupCheckState;
1373
+ /**
1374
+ * Selection roll-up for a flat group's (visible) members: which are selected, how many, and
1375
+ * the tristate check-state. `selectedCount` counts every selected member (including a
1376
+ * disabled-but-selected one) — it's the "N behind the group title". `checkState` excludes
1377
+ * disabled members from its denominator (mirrors the select-all), so a group with a stuck
1378
+ * disabled member can still read fully `checked`. Shared by the header count, the tristate
1379
+ * checkbox, and the `renderGroupLabelContentCallback` context.
1380
+ */
1381
+ private groupSelectionInfo;
1382
+ /**
1383
+ * Formats the small count chip shared by the in-input counter and the per-group header count.
1384
+ * Default `[selected]` (matches the historical in-input `[N]`); a `getCountLabelCallback` can
1385
+ * switch both to e.g. `selected/total`.
1386
+ */
1387
+ private formatCountLabel;
1388
+ /** Trailing count chip for a group header — any grouped list (rendered only when >0 selected). */
1389
+ private groupCountHtml;
1390
+ /**
1391
+ * Shared markup for a `.ms__checkbox` input — the single source of truth for option rows, tree
1392
+ * nodes, and group headers. Indeterminate is a pure CSS state (the box is `appearance: none`, so
1393
+ * no native `input.indeterminate` is needed — virtual-scroll-safe) plus `aria-checked="mixed"`.
1394
+ */
1395
+ private checkboxHtml;
1396
+ /** Group-header tristate checkbox (maps the group's roll-up state onto `checkboxHtml`). */
1397
+ private groupCheckboxHtml;
1253
1398
  private renderDropdown;
1254
1399
  /**
1255
1400
  * Round the OUTER corners of the row at the very top and the row at the very
@@ -1479,6 +1624,16 @@ export declare class WebMultiSelect<T = any> {
1479
1624
  private deselectOption;
1480
1625
  private selectAll;
1481
1626
  clearAll(): void;
1627
+ /**
1628
+ * Flat-group cascade toggle: check or uncheck every (visible) member of a group
1629
+ * in one shot. If the group is fully checked → deselect all its members; else →
1630
+ * select all its non-disabled members. Operates on the currently-filtered
1631
+ * members (same scope as Select-All) and, like Select-All / Clear-All,
1632
+ * batch-mutates then fires a single `commit` — so one render and one `change`
1633
+ * event, and it deliberately bypasses the per-item beforeSelect/beforeDeselect
1634
+ * veto. The group name itself is never added to the selection.
1635
+ */
1636
+ private toggleGroup;
1482
1637
  /**
1483
1638
  * Inline clear (✕) handler: wipe the whole selection and any search text, then
1484
1639
  * restore focus to the input. clearAll() → commit() → renderBadges() already