@keenmate/web-multiselect 2.0.0-rc06 → 2.0.0-rc08
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 +13 -7
- package/custom-elements.json +159 -9
- package/dist/index.d.ts +133 -3
- package/dist/multiselect.js +960 -822
- package/dist/multiselect.umd.js +14 -14
- package/dist/style.css +1 -1
- package/docs/examples.md +22 -1
- package/docs/usage.md +25 -1
- package/package.json +2 -2
- package/src/css/floating.css +61 -1
- package/src/css/options.css +4 -0
- package/src/css/variables.css +32 -3
- package/vscode.html-custom-data.json +6 -1
- package/web-types.json +20 -5
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { InputDef } from '@keenmate/web-components-core';
|
|
|
4
4
|
import { Logger } from '@keenmate/web-components-core';
|
|
5
5
|
import { LogLevelDesc } from '@keenmate/web-components-core';
|
|
6
6
|
import { Placement } from '@keenmate/web-components-core/positioning';
|
|
7
|
+
import { PresentationContext } from '@keenmate/web-components-core';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Action button configuration for dropdown actions (Select All, Clear All, custom actions)
|
|
@@ -59,7 +60,7 @@ declare type ActionsPosition = 'top' | 'bottom';
|
|
|
59
60
|
/**
|
|
60
61
|
* Context provided to renderBadgeContentCallback
|
|
61
62
|
*/
|
|
62
|
-
declare interface BadgeContentRenderContext {
|
|
63
|
+
declare interface BadgeContentRenderContext extends PresentationContext {
|
|
63
64
|
/** Current badges display mode */
|
|
64
65
|
displayMode: BadgesDisplayMode;
|
|
65
66
|
/** Whether the badge is being rendered in the selected items popover */
|
|
@@ -387,6 +388,17 @@ declare interface MultiSelectConfig<T = any> {
|
|
|
387
388
|
* - `navigate` — keep all options visible and jump focus to matches
|
|
388
389
|
*/
|
|
389
390
|
searchMode?: SearchMode;
|
|
391
|
+
/**
|
|
392
|
+
* Show a clickable mode toggle in the phone fullscreen overlay's search header that
|
|
393
|
+
* flips `searchMode` between `filter` and `navigate` live (no reopen). Default `false`.
|
|
394
|
+
*
|
|
395
|
+
* The overlay has room for the affordance and touch users can't reach the desktop
|
|
396
|
+
* `Ctrl`+`Arrow` match-stepping, so this exposes both modes on the device where it
|
|
397
|
+
* matters most. The toggle sits at the leading edge of the search field; its icon
|
|
398
|
+
* reflects the current mode (magnifier = navigate, funnel = filter). No effect in the
|
|
399
|
+
* floating presentation or when search is disabled/hidden.
|
|
400
|
+
*/
|
|
401
|
+
isSearchModeToggleShown?: boolean;
|
|
390
402
|
/**
|
|
391
403
|
* Layout mode for the action buttons. Default: `nowrap`.
|
|
392
404
|
*
|
|
@@ -463,6 +475,15 @@ declare interface MultiSelectConfig<T = any> {
|
|
|
463
475
|
searchCallback?: ((searchTerm: string, signal?: AbortSignal) => Promise<T[]>) | null;
|
|
464
476
|
/** Callback to add a new option when isAddNewAllowed is true */
|
|
465
477
|
addNewCallback?: ((value: string) => T | Promise<T>) | null;
|
|
478
|
+
/**
|
|
479
|
+
* Intercept keyboard input before the built-in handling. Runs on every keydown (open or
|
|
480
|
+
* closed) with a {@link MultiSelectKeydownContext} carrying the event, current state, and a
|
|
481
|
+
* {@link MultiSelectKeyboardController}. Return `true` to mark the key fully handled — the
|
|
482
|
+
* component then runs none of its own key logic (you own `preventDefault`); return
|
|
483
|
+
* `false`/`undefined` to fall through to the defaults. Use it to remap keys (Vim `j`/`k`),
|
|
484
|
+
* add shortcuts (Ctrl+A → select all), or suppress a default. Property-only.
|
|
485
|
+
*/
|
|
486
|
+
keydownCallback?: ((context: MultiSelectKeydownContext<T>) => boolean | void) | null;
|
|
466
487
|
/** Event handler: an option was selected (fire-and-forget; return value ignored). Mirrors the bubbling `select` CustomEvent on the element. */
|
|
467
488
|
onSelect?: ((option: T) => void) | null;
|
|
468
489
|
/** Event handler: an option was deselected (fire-and-forget). Mirrors the bubbling `deselect` CustomEvent on the element. */
|
|
@@ -602,6 +623,71 @@ declare type MultiSelectEvents = {
|
|
|
602
623
|
change: MultiSelectEventDetail;
|
|
603
624
|
};
|
|
604
625
|
|
|
626
|
+
/**
|
|
627
|
+
* Imperative facade handed to `keydownCallback` so a consumer can drive the picker without
|
|
628
|
+
* reaching into internals. Every method mirrors a built-in keyboard action.
|
|
629
|
+
*/
|
|
630
|
+
declare interface MultiSelectKeyboardController<T = any> {
|
|
631
|
+
/** Move focus to the next / previous option. */
|
|
632
|
+
focusNext(): void;
|
|
633
|
+
focusPrevious(): void;
|
|
634
|
+
/** Move focus to the first / last option. */
|
|
635
|
+
focusFirst(): void;
|
|
636
|
+
focusLast(): void;
|
|
637
|
+
/** Move focus by a page (10 rows). */
|
|
638
|
+
focusPageUp(): void;
|
|
639
|
+
focusPageDown(): void;
|
|
640
|
+
/** Navigate-mode only: jump focus to the next / previous match. */
|
|
641
|
+
focusNextMatch(): void;
|
|
642
|
+
focusPreviousMatch(): void;
|
|
643
|
+
/** Focus a specific index in the filtered list (ignored if out of range). */
|
|
644
|
+
focusIndex(index: number): void;
|
|
645
|
+
/** Toggle the currently focused option (no-op if nothing is focused). */
|
|
646
|
+
toggleFocused(): void;
|
|
647
|
+
/** Toggle a specific option by its value (select ⇄ deselect). */
|
|
648
|
+
toggleValue(value: string | number): void;
|
|
649
|
+
/** Select a specific option by its value (no-op if already selected). */
|
|
650
|
+
selectValue(value: string | number): void;
|
|
651
|
+
/** Deselect a specific option by its value (no-op if not selected). */
|
|
652
|
+
deselectValue(value: string | number): void;
|
|
653
|
+
/** Open / close the dropdown. */
|
|
654
|
+
open(): void;
|
|
655
|
+
close(): void;
|
|
656
|
+
/** Set the search box text (runs the search). */
|
|
657
|
+
setSearch(term: string): void;
|
|
658
|
+
/** Clear the search box and reset the visible list. */
|
|
659
|
+
clearSearch(): void;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Context passed to `keydownCallback`, which runs before all built-in keyboard handling.
|
|
664
|
+
* Return `true` to mark the key fully handled (the component then runs none of its own
|
|
665
|
+
* key logic — you own `preventDefault`); return `false`/`undefined` to fall through to the
|
|
666
|
+
* defaults. Mirrors the veto-hook shape used across KM components.
|
|
667
|
+
*/
|
|
668
|
+
declare interface MultiSelectKeydownContext<T = any> {
|
|
669
|
+
/** The raw keyboard event — call `preventDefault()` yourself if you handle the key. */
|
|
670
|
+
event: KeyboardEvent;
|
|
671
|
+
/** `event.key`, for convenience. */
|
|
672
|
+
key: string;
|
|
673
|
+
/** Whether the dropdown is currently open. */
|
|
674
|
+
isOpen: boolean;
|
|
675
|
+
/** How the open panel is presented (`floating` / `fullscreen`). */
|
|
676
|
+
presentation: 'floating' | 'fullscreen';
|
|
677
|
+
/** The current search term. */
|
|
678
|
+
searchTerm: string;
|
|
679
|
+
/** Index of the focused option in the filtered list (`-1` when nothing is focused). */
|
|
680
|
+
focusedIndex: number;
|
|
681
|
+
/** The focused option object, or `null`. */
|
|
682
|
+
focusedOption: T | null;
|
|
683
|
+
/** The options currently visible (after filtering). */
|
|
684
|
+
filteredOptions: ReadonlyArray<T>;
|
|
685
|
+
/** The currently selected values. */
|
|
686
|
+
selectedValues: ReadonlyArray<string>;
|
|
687
|
+
/** Imperative actions mirroring the built-in keyboard behavior. */
|
|
688
|
+
controller: MultiSelectKeyboardController<T>;
|
|
689
|
+
}
|
|
690
|
+
|
|
605
691
|
/**
|
|
606
692
|
* Legacy interface for backward reference
|
|
607
693
|
* Note: New code should use generic types with member/callback properties
|
|
@@ -647,9 +733,15 @@ export declare interface MultiSelectOptions extends MultiSelectConfig<MultiSelec
|
|
|
647
733
|
declare type NodeId = string | number;
|
|
648
734
|
|
|
649
735
|
/**
|
|
650
|
-
* Context provided to renderOptionContentCallback
|
|
736
|
+
* Context provided to renderOptionContentCallback.
|
|
737
|
+
*
|
|
738
|
+
* Extends the shared {@link PresentationContext} from `@keenmate/web-components-core`, so it
|
|
739
|
+
* also carries `presentation` (`'floating' | 'modal' | 'fullscreen'` — this component only ever
|
|
740
|
+
* emits `floating`/`fullscreen`), `isFullscreen`, and `isModal`. Branch on `isFullscreen` to
|
|
741
|
+
* render leaner content in the phone overlay. Reactive: swapping presentation re-renders and
|
|
742
|
+
* re-invokes the callback with the new value.
|
|
651
743
|
*/
|
|
652
|
-
declare interface OptionContentRenderContext {
|
|
744
|
+
declare interface OptionContentRenderContext extends PresentationContext {
|
|
653
745
|
/** Index of the option in the filtered list */
|
|
654
746
|
index: number;
|
|
655
747
|
/** Whether the option is currently selected */
|
|
@@ -761,6 +853,7 @@ export declare class WebMultiSelect<T = any> {
|
|
|
761
853
|
private cascadeCheckedAtoms;
|
|
762
854
|
private hiddenInputs;
|
|
763
855
|
private focusedIndex;
|
|
856
|
+
private keyboardController;
|
|
764
857
|
private matchingIndices;
|
|
765
858
|
private searchTerm;
|
|
766
859
|
private isLoading;
|
|
@@ -778,6 +871,7 @@ export declare class WebMultiSelect<T = any> {
|
|
|
778
871
|
private fullscreenHeader;
|
|
779
872
|
private fullscreenSearchInput;
|
|
780
873
|
private fullscreenSearchClear;
|
|
874
|
+
private fullscreenModeToggle;
|
|
781
875
|
private fullscreenNav;
|
|
782
876
|
private fullscreenNavCount;
|
|
783
877
|
private fullscreenNavPrev;
|
|
@@ -999,6 +1093,16 @@ export declare class WebMultiSelect<T = any> {
|
|
|
999
1093
|
* search is unusable → the search placeholder.
|
|
1000
1094
|
*/
|
|
1001
1095
|
private getPlaceholderText;
|
|
1096
|
+
/**
|
|
1097
|
+
* The search field placeholder. An explicit `searchPlaceholder` always wins and stays
|
|
1098
|
+
* fixed. Otherwise the default is "Search..." — except when the in-overlay mode toggle
|
|
1099
|
+
* is enabled (`isSearchModeToggleShown`), where it becomes mode-aware so the field labels
|
|
1100
|
+
* the current behavior: "Search…" in navigate mode, "Filter…" in filter mode. Refreshed
|
|
1101
|
+
* on a live mode switch (see setSearchModeLive → refreshSearchPlaceholder).
|
|
1102
|
+
*/
|
|
1103
|
+
private getSearchPlaceholder;
|
|
1104
|
+
/** Re-apply the (possibly mode-aware) placeholder to the live inputs after a mode switch. */
|
|
1105
|
+
private refreshSearchPlaceholder;
|
|
1002
1106
|
private renderBadges;
|
|
1003
1107
|
private attachEvents;
|
|
1004
1108
|
private handleSearch;
|
|
@@ -1040,6 +1144,12 @@ export declare class WebMultiSelect<T = any> {
|
|
|
1040
1144
|
private focusPageDown;
|
|
1041
1145
|
private focusNextMatch;
|
|
1042
1146
|
private focusPreviousMatch;
|
|
1147
|
+
/** Lazily build (and cache) the imperative facade passed to `keydownCallback`. Bound to the
|
|
1148
|
+
* same private actions the built-in key handling uses, so consumer shortcuts behave identically. */
|
|
1149
|
+
private getKeyboardController;
|
|
1150
|
+
/** Clear the search box (both the main input and the fullscreen search) and reset the visible
|
|
1151
|
+
* list. Shared by Escape and the keyboard controller. */
|
|
1152
|
+
private clearSearch;
|
|
1043
1153
|
private scrollToFocused;
|
|
1044
1154
|
private toggleOption;
|
|
1045
1155
|
/**
|
|
@@ -1199,6 +1309,26 @@ export declare class WebMultiSelect<T = any> {
|
|
|
1199
1309
|
* header survives re-renders.
|
|
1200
1310
|
*/
|
|
1201
1311
|
private buildFullscreenHeader;
|
|
1312
|
+
/** Build the navigate-mode match navigator (count + prev/next) and append it to the
|
|
1313
|
+
* fullscreen header, once. No-op if already built or the header isn't present. The
|
|
1314
|
+
* nav wraps onto its own full-width row under the search box (header is flex-wrap;
|
|
1315
|
+
* the nav takes 100% basis). */
|
|
1316
|
+
private ensureFullscreenNav;
|
|
1317
|
+
/** Remove the match navigator (switching to filter mode, which has no jump UI). */
|
|
1318
|
+
private removeFullscreenNav;
|
|
1319
|
+
/** Flip searchMode filter<->navigate from the in-overlay toggle. */
|
|
1320
|
+
private toggleSearchModeLive;
|
|
1321
|
+
/**
|
|
1322
|
+
* Switch searchMode in place — the overlay's toggle path. The `search-mode` attribute
|
|
1323
|
+
* is reinit-on-change (it rebuilds and closes the overlay); this instead mutates the
|
|
1324
|
+
* live config, adds/removes the match navigator to match, and re-projects the current
|
|
1325
|
+
* term under the new mode (filter narrows the list / navigate keeps all + highlights),
|
|
1326
|
+
* all without tearing the open sheet down. Focus stays on the search field.
|
|
1327
|
+
*/
|
|
1328
|
+
private setSearchModeLive;
|
|
1329
|
+
/** Sync the mode toggle's icon (via data-mode) and labels with the current searchMode.
|
|
1330
|
+
* No-op when the toggle isn't built (opt-out, floating panel, or search hidden). */
|
|
1331
|
+
private updateFullscreenModeToggle;
|
|
1202
1332
|
/**
|
|
1203
1333
|
* Sync the fullscreen match navigator (navigate mode only) with the current search
|
|
1204
1334
|
* state: hide it until there's a term, then show "N of M" while a match is focused
|