@smilodon/core 1.4.7 → 1.4.10
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 +28 -0
- package/dist/index.cjs +191 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +191 -44
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.umd.js +191 -44
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/dist/types/src/components/enhanced-select.d.ts +10 -0
- package/dist/types/src/types.d.ts +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -646,6 +646,28 @@ enhanced-select.dark-mode {
|
|
|
646
646
|
--select-dropdown-bg /* Dropdown background (white) */
|
|
647
647
|
--select-dropdown-border /* Dropdown border color (#ccc) */
|
|
648
648
|
--select-dropdown-shadow /* Dropdown shadow */
|
|
649
|
+
--select-empty-padding /* Empty/no-results container padding */
|
|
650
|
+
--select-empty-color /* Text color for empty/no-results models */
|
|
651
|
+
--select-empty-font-size /* Font size */
|
|
652
|
+
--select-empty-bg /* Background for empty/no-results state */
|
|
653
|
+
--select-empty-min-height /* Minimum height of empty state box */
|
|
654
|
+
|
|
655
|
+
/* Arrow/button */
|
|
656
|
+
--select-arrow-size /* Width & height of SVG icon (16px default) */
|
|
657
|
+
--select-arrow-color /* Icon color (#667eea) */
|
|
658
|
+
--select-arrow-hover-color /* Icon color when hovered (#667eea) */
|
|
659
|
+
--select-arrow-hover-bg /* Background when hover (rgba(102,126,234,0.08)) */
|
|
660
|
+
--select-arrow-width /* Container width (40px) */
|
|
661
|
+
--select-arrow-border-radius /* Container border radius */
|
|
662
|
+
|
|
663
|
+
/* Group headers (when using groupedItems or flat items with `group` property) */
|
|
664
|
+
--select-group-header-padding /* Padding inside header (8px 12px) */
|
|
665
|
+
--select-group-header-color /* Text color (#6b7280) */
|
|
666
|
+
--select-group-header-bg /* Background (#f3f4f6) */
|
|
667
|
+
--select-group-header-font-size
|
|
668
|
+
--select-group-header-text-transform
|
|
669
|
+
--select-group-header-letter-spacing
|
|
670
|
+
--select-group-header-border-bottom
|
|
649
671
|
```
|
|
650
672
|
|
|
651
673
|
**Dark Mode (Opt-in)**
|
|
@@ -659,6 +681,8 @@ enhanced-select.dark-mode {
|
|
|
659
681
|
--select-dark-option-bg /* Dark option background (#1f2937) */
|
|
660
682
|
--select-dark-option-hover-bg /* Dark hover background (#374151) */
|
|
661
683
|
--select-dark-option-selected-bg /* Dark selected bg (#3730a3) */
|
|
684
|
+
--select-dark-group-header-color /* Dark header text */
|
|
685
|
+
--select-dark-group-header-bg /* Dark header background */
|
|
662
686
|
```
|
|
663
687
|
|
|
664
688
|
**Complete CSS Variables List (60+ variables)**
|
|
@@ -698,6 +722,10 @@ enhanced-select {
|
|
|
698
722
|
**Badge Remove/Delete Button (Multi-Select)**
|
|
699
723
|
The × button that removes selected items in multi-select mode is fully customizable:
|
|
700
724
|
|
|
725
|
+
**Group Header & No‑Results Parts**
|
|
726
|
+
Both the group header and the no-results message are exposed as shadow parts (`group-header` and `no-results`) so you can target them with `::part()` selectors or CSS variables. This makes it straightforward to match the look of your host framework or UI kit.
|
|
727
|
+
|
|
728
|
+
|
|
701
729
|
```css
|
|
702
730
|
enhanced-select {
|
|
703
731
|
/* Customize badge appearance */
|
package/dist/index.cjs
CHANGED
|
@@ -1875,7 +1875,22 @@ class EnhancedSelect extends HTMLElement {
|
|
|
1875
1875
|
}
|
|
1876
1876
|
set classMap(map) {
|
|
1877
1877
|
this._classMap = map;
|
|
1878
|
-
this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map));
|
|
1878
|
+
this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map || this._groupHeaderRenderer));
|
|
1879
|
+
if (!this.isConnected)
|
|
1880
|
+
return;
|
|
1881
|
+
this._renderOptions();
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* DOM-based renderer for group headers. When provided, the component will
|
|
1885
|
+
* call this function for each group during rendering. The returned element
|
|
1886
|
+
* will receive `.group-header` and `part="group-header"` automatically.
|
|
1887
|
+
*/
|
|
1888
|
+
get groupHeaderRenderer() {
|
|
1889
|
+
return this._groupHeaderRenderer;
|
|
1890
|
+
}
|
|
1891
|
+
set groupHeaderRenderer(renderer) {
|
|
1892
|
+
this._groupHeaderRenderer = renderer;
|
|
1893
|
+
this._setGlobalStylesMirroring(Boolean(this._optionRenderer || this._classMap || renderer));
|
|
1879
1894
|
if (!this.isConnected)
|
|
1880
1895
|
return;
|
|
1881
1896
|
this._renderOptions();
|
|
@@ -1934,6 +1949,8 @@ class EnhancedSelect extends HTMLElement {
|
|
|
1934
1949
|
this._initializeObservers();
|
|
1935
1950
|
}
|
|
1936
1951
|
connectedCallback() {
|
|
1952
|
+
// register instance
|
|
1953
|
+
EnhancedSelect._instances.add(this);
|
|
1937
1954
|
// WORKAROUND: Force display style on host element for Angular compatibility
|
|
1938
1955
|
// Angular's rendering seems to not apply :host styles correctly in some cases
|
|
1939
1956
|
// Must be done in connectedCallback when element is attached to DOM
|
|
@@ -1952,6 +1969,8 @@ class EnhancedSelect extends HTMLElement {
|
|
|
1952
1969
|
}
|
|
1953
1970
|
}
|
|
1954
1971
|
disconnectedCallback() {
|
|
1972
|
+
// unregister instance
|
|
1973
|
+
EnhancedSelect._instances.delete(this);
|
|
1955
1974
|
// Cleanup observers
|
|
1956
1975
|
this._resizeObserver?.disconnect();
|
|
1957
1976
|
this._intersectionObserver?.disconnect();
|
|
@@ -2237,6 +2256,8 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2237
2256
|
right: 0;
|
|
2238
2257
|
bottom: 0;
|
|
2239
2258
|
width: var(--select-arrow-width, 40px);
|
|
2259
|
+
/* allow explicit height override even though container normally stretches */
|
|
2260
|
+
height: var(--select-arrow-height, auto);
|
|
2240
2261
|
display: flex;
|
|
2241
2262
|
align-items: center;
|
|
2242
2263
|
justify-content: center;
|
|
@@ -2302,9 +2323,14 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2302
2323
|
background-color: var(--select-arrow-hover-bg, rgba(102, 126, 234, 0.08));
|
|
2303
2324
|
}
|
|
2304
2325
|
|
|
2326
|
+
.dropdown-arrow:hover {
|
|
2327
|
+
/* legacy alias --select-arrow-hover for icon color */
|
|
2328
|
+
color: var(--select-arrow-hover, var(--select-arrow-hover-color, #667eea));
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2305
2331
|
.dropdown-arrow {
|
|
2306
|
-
width: var(--select-arrow-size, 16px);
|
|
2307
|
-
height: var(--select-arrow-size, 16px);
|
|
2332
|
+
width: var(--select-arrow-width, var(--select-arrow-size, 16px));
|
|
2333
|
+
height: var(--select-arrow-height, var(--select-arrow-size, 16px));
|
|
2308
2334
|
color: var(--select-arrow-color, #667eea);
|
|
2309
2335
|
transition: transform 0.2s ease, color 0.2s ease;
|
|
2310
2336
|
transform: translateY(0);
|
|
@@ -2413,6 +2439,20 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2413
2439
|
background: var(--select-options-bg, var(--select-dropdown-bg, var(--select-bg, white)));
|
|
2414
2440
|
}
|
|
2415
2441
|
|
|
2442
|
+
.group-header {
|
|
2443
|
+
padding: var(--select-group-header-padding, 8px 12px);
|
|
2444
|
+
font-weight: var(--select-group-header-weight, 600);
|
|
2445
|
+
color: var(--select-group-header-color, #6b7280);
|
|
2446
|
+
background-color: var(--select-group-header-bg, #f3f4f6);
|
|
2447
|
+
font-size: var(--select-group-header-font-size, 12px);
|
|
2448
|
+
text-transform: var(--select-group-header-text-transform, uppercase);
|
|
2449
|
+
letter-spacing: var(--select-group-header-letter-spacing, 0.05em);
|
|
2450
|
+
position: sticky;
|
|
2451
|
+
top: 0;
|
|
2452
|
+
z-index: 1;
|
|
2453
|
+
border-bottom: var(--select-group-header-border-bottom, 1px solid #e5e7eb);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2416
2456
|
.option {
|
|
2417
2457
|
padding: var(--select-option-padding, 8px 12px);
|
|
2418
2458
|
cursor: pointer;
|
|
@@ -2586,9 +2626,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2586
2626
|
}
|
|
2587
2627
|
}
|
|
2588
2628
|
|
|
2589
|
-
/* Dark mode - Opt-in via class
|
|
2629
|
+
/* Dark mode - Opt-in via class, data attribute, or ancestor context */
|
|
2590
2630
|
:host(.dark-mode),
|
|
2591
|
-
:host([data-theme="dark"])
|
|
2631
|
+
:host([data-theme="dark"]),
|
|
2632
|
+
:host-context(.dark-mode),
|
|
2633
|
+
:host-context(.dark),
|
|
2634
|
+
:host-context([data-theme="dark"]) {
|
|
2592
2635
|
.input-container {
|
|
2593
2636
|
background: var(--select-dark-bg, #1f2937);
|
|
2594
2637
|
border-color: var(--select-dark-border, #4b5563);
|
|
@@ -2641,6 +2684,13 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2641
2684
|
|
|
2642
2685
|
.option.active:not(.selected) {
|
|
2643
2686
|
background-color: var(--select-dark-option-active-bg, #374151);
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
/* Group header in dark mode */
|
|
2690
|
+
.group-header {
|
|
2691
|
+
color: var(--select-dark-group-header-color, var(--select-group-header-color, #6b7280));
|
|
2692
|
+
background-color: var(--select-dark-group-header-bg, var(--select-group-header-bg, #374151));
|
|
2693
|
+
}
|
|
2644
2694
|
color: var(--select-dark-option-active-color, #f9fafb);
|
|
2645
2695
|
outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
|
|
2646
2696
|
}
|
|
@@ -2723,19 +2773,14 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2723
2773
|
this._boundArrowClick = (e) => {
|
|
2724
2774
|
e.stopPropagation();
|
|
2725
2775
|
e.preventDefault();
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
this.
|
|
2730
|
-
|
|
2731
|
-
this._config.callbacks.onOpen();
|
|
2732
|
-
}
|
|
2733
|
-
else if (!this._state.isOpen && this._config.callbacks.onClose) {
|
|
2734
|
-
this._config.callbacks.onClose();
|
|
2776
|
+
// delegate to the existing open/close helpers so we don't accidentally
|
|
2777
|
+
// drift out of sync with the logic in those methods (focus, events,
|
|
2778
|
+
// scroll-to-selected, etc.)
|
|
2779
|
+
if (this._state.isOpen) {
|
|
2780
|
+
this._handleClose();
|
|
2735
2781
|
}
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
setTimeout(() => this._scrollToSelected(), 50);
|
|
2782
|
+
else {
|
|
2783
|
+
this._handleOpen();
|
|
2739
2784
|
}
|
|
2740
2785
|
};
|
|
2741
2786
|
this._arrowContainer.addEventListener('click', this._boundArrowClick);
|
|
@@ -2752,7 +2797,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2752
2797
|
});
|
|
2753
2798
|
}
|
|
2754
2799
|
// Input container click - focus input and open dropdown
|
|
2800
|
+
// Prevent the original pointer event from bubbling/causing default focus behavior
|
|
2801
|
+
// which can interfere with option click handling when opening the dropdown
|
|
2755
2802
|
this._inputContainer.addEventListener('pointerdown', (e) => {
|
|
2803
|
+
// Prevent propagation to document click listener but do NOT preventDefault.
|
|
2804
|
+
// Allow default so browser events (click) on newly opened options still fire.
|
|
2805
|
+
e.stopPropagation();
|
|
2756
2806
|
const target = e.target;
|
|
2757
2807
|
if (!this._config.enabled)
|
|
2758
2808
|
return;
|
|
@@ -2760,10 +2810,27 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2760
2810
|
return;
|
|
2761
2811
|
if (target && target.closest('.clear-control-button'))
|
|
2762
2812
|
return;
|
|
2763
|
-
|
|
2813
|
+
const wasClosed = !this._state.isOpen;
|
|
2814
|
+
if (wasClosed) {
|
|
2764
2815
|
this._handleOpen();
|
|
2765
2816
|
}
|
|
2817
|
+
else {
|
|
2818
|
+
// clicking the input while open should close the dropdown too
|
|
2819
|
+
this._handleClose();
|
|
2820
|
+
}
|
|
2821
|
+
// Focus the input (do not prevent default behavior)
|
|
2766
2822
|
this._input.focus();
|
|
2823
|
+
// If we just opened the dropdown, transfer pointer capture to the
|
|
2824
|
+
// options container so the subsequent pointerup lands there instead of
|
|
2825
|
+
// staying with the input container (which would swallow the event).
|
|
2826
|
+
if (wasClosed && this._optionsContainer && typeof e.pointerId === 'number') {
|
|
2827
|
+
try {
|
|
2828
|
+
this._optionsContainer.setPointerCapture(e.pointerId);
|
|
2829
|
+
}
|
|
2830
|
+
catch (_err) {
|
|
2831
|
+
// Some browsers may throw if element is not yet "connected"; ignore
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2767
2834
|
});
|
|
2768
2835
|
// Input container click - prevent event from reaching document listener
|
|
2769
2836
|
this._container.addEventListener('click', (e) => {
|
|
@@ -2771,6 +2838,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2771
2838
|
});
|
|
2772
2839
|
// Input focus/blur
|
|
2773
2840
|
this._input.addEventListener('focus', () => this._handleOpen());
|
|
2841
|
+
// When the input loses focus we normally close the dropdown, but
|
|
2842
|
+
// clicking an option will blur the input before the option's click
|
|
2843
|
+
// handler executes. To avoid the blur timer closing the dropdown
|
|
2844
|
+
// prematurely we use a short-lived flag that is set whenever we start
|
|
2845
|
+
// interacting with the options container. The close callback checks this
|
|
2846
|
+
// flag and skips closing if the user is about to click an option.
|
|
2774
2847
|
this._input.addEventListener('blur', (e) => {
|
|
2775
2848
|
const related = e.relatedTarget;
|
|
2776
2849
|
if (related && (this._shadow.contains(related) || this._container.contains(related))) {
|
|
@@ -2778,6 +2851,10 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2778
2851
|
}
|
|
2779
2852
|
// Delay to allow option click/focus transitions
|
|
2780
2853
|
setTimeout(() => {
|
|
2854
|
+
if (this._suppressBlurClose) {
|
|
2855
|
+
// another pointerdown inside options is in progress; keep open
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2781
2858
|
const active = document.activeElement;
|
|
2782
2859
|
if (active && (this._shadow.contains(active) || this._container.contains(active))) {
|
|
2783
2860
|
return;
|
|
@@ -2792,11 +2869,33 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2792
2869
|
const query = e.target.value;
|
|
2793
2870
|
this._handleSearch(query);
|
|
2794
2871
|
});
|
|
2795
|
-
//
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2872
|
+
// If the user presses down inside the options container we should
|
|
2873
|
+
// temporarily suppress blur-based closing until after the click has
|
|
2874
|
+
// been handled. Setting a flag that gets cleared in the next tick is
|
|
2875
|
+
// sufficient.
|
|
2876
|
+
this._optionsContainer.addEventListener('pointerdown', () => {
|
|
2877
|
+
this._suppressBlurClose = true;
|
|
2878
|
+
setTimeout(() => {
|
|
2879
|
+
this._suppressBlurClose = false;
|
|
2880
|
+
}, 0);
|
|
2881
|
+
});
|
|
2882
|
+
// Delegated click listener for improved event handling (robust across shadow DOM)
|
|
2883
|
+
const handleOptionEvent = (e) => {
|
|
2884
|
+
const path = (e.composedPath && e.composedPath()) || [e.target];
|
|
2885
|
+
let option = null;
|
|
2886
|
+
for (const node of path) {
|
|
2887
|
+
if (!(node instanceof Element))
|
|
2888
|
+
continue;
|
|
2889
|
+
try {
|
|
2890
|
+
if (node.matches('[data-sm-selectable], [data-selectable], [data-sm-state]')) {
|
|
2891
|
+
option = node;
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
catch (err) {
|
|
2896
|
+
continue;
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2800
2899
|
if (option && !option.hasAttribute('aria-disabled')) {
|
|
2801
2900
|
const indexStr = option.getAttribute('data-sm-index') ?? option.getAttribute('data-index');
|
|
2802
2901
|
const index = Number(indexStr);
|
|
@@ -2807,13 +2906,45 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2807
2906
|
});
|
|
2808
2907
|
}
|
|
2809
2908
|
}
|
|
2810
|
-
}
|
|
2909
|
+
};
|
|
2910
|
+
this._optionsContainer.addEventListener('click', handleOptionEvent);
|
|
2911
|
+
// also watch pointerup to catch cases where the pointerdown started outside
|
|
2912
|
+
// (e.g. on the input) and the click never fires
|
|
2913
|
+
this._optionsContainer.addEventListener('pointerup', handleOptionEvent);
|
|
2811
2914
|
// Keyboard navigation
|
|
2812
2915
|
this._input.addEventListener('keydown', (e) => this._handleKeydown(e));
|
|
2813
|
-
// Click outside to close
|
|
2916
|
+
// Click outside to close — robust detection across shadow DOM and custom renderers
|
|
2814
2917
|
document.addEventListener('pointerdown', (e) => {
|
|
2815
2918
|
const path = (e.composedPath && e.composedPath()) || [];
|
|
2816
|
-
|
|
2919
|
+
let clickedInside = false;
|
|
2920
|
+
for (const node of path) {
|
|
2921
|
+
if (node === this || node === this._container) {
|
|
2922
|
+
clickedInside = true;
|
|
2923
|
+
break;
|
|
2924
|
+
}
|
|
2925
|
+
if (node instanceof Node) {
|
|
2926
|
+
try {
|
|
2927
|
+
if (this._shadow && this._shadow.contains(node)) {
|
|
2928
|
+
clickedInside = true;
|
|
2929
|
+
break;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
catch (err) {
|
|
2933
|
+
// ignore
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
if (node instanceof Element) {
|
|
2937
|
+
try {
|
|
2938
|
+
if (node.matches('[data-sm-selectable], [data-selectable], [data-sm-state], .input-container, .select-container, .dropdown-arrow-container, .clear-control-button')) {
|
|
2939
|
+
clickedInside = true;
|
|
2940
|
+
break;
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
catch (err) {
|
|
2944
|
+
// ignore
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2817
2948
|
if (!clickedInside) {
|
|
2818
2949
|
this._handleClose();
|
|
2819
2950
|
}
|
|
@@ -2862,6 +2993,16 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2862
2993
|
_handleOpen() {
|
|
2863
2994
|
if (!this._config.enabled || this._state.isOpen)
|
|
2864
2995
|
return;
|
|
2996
|
+
// close any other open selects before proceeding
|
|
2997
|
+
EnhancedSelect._instances.forEach(inst => {
|
|
2998
|
+
if (inst !== this)
|
|
2999
|
+
inst._handleClose();
|
|
3000
|
+
});
|
|
3001
|
+
// Always focus the input when opening so callers (arrow click,
|
|
3002
|
+
// programmatic `open()`, etc.) get the keyboard cursor. This was a
|
|
3003
|
+
// frequent source of confusion in #14 where people opened the dropdown
|
|
3004
|
+
// but the text field never received focus.
|
|
3005
|
+
this._input.focus();
|
|
2865
3006
|
this._markOpenStart();
|
|
2866
3007
|
this._state.isOpen = true;
|
|
2867
3008
|
this._dropdown.style.display = 'block';
|
|
@@ -3277,13 +3418,14 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3277
3418
|
// FIX: Do not rely on this._optionsContainer.children[index] because filtering changes the children
|
|
3278
3419
|
// Instead, use the index to update state directly
|
|
3279
3420
|
const item = this._state.loadedItems[index];
|
|
3421
|
+
// Debug: log selection attempt
|
|
3280
3422
|
if (!item)
|
|
3281
3423
|
return;
|
|
3424
|
+
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3282
3425
|
// Keep active/focus styling aligned with the most recently interacted option.
|
|
3283
3426
|
// Without this, a previously selected item may retain active classes/styles
|
|
3284
3427
|
// after selecting a different option.
|
|
3285
3428
|
this._state.activeIndex = index;
|
|
3286
|
-
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3287
3429
|
if (this._config.selection.mode === 'single') {
|
|
3288
3430
|
// Single select: clear previous and select new
|
|
3289
3431
|
const wasSelected = this._state.selectedIndices.has(index);
|
|
@@ -3514,6 +3656,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3514
3656
|
const getValue = this._config.serverSide.getValueFromItem || ((item) => item?.value ?? item);
|
|
3515
3657
|
const selectedValues = selectedItems.map(getValue);
|
|
3516
3658
|
const selectedIndices = Array.from(this._state.selectedIndices);
|
|
3659
|
+
// Debug: log change payload
|
|
3517
3660
|
this._emit('change', { selectedItems, selectedValues, selectedIndices });
|
|
3518
3661
|
this._config.callbacks.onChange?.(selectedItems, selectedValues);
|
|
3519
3662
|
}
|
|
@@ -3833,23 +3976,25 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3833
3976
|
const query = this._state.searchQuery.toLowerCase();
|
|
3834
3977
|
// Handle Grouped Items Rendering (when no search query)
|
|
3835
3978
|
if (this._state.groupedItems.length > 0 && !query) {
|
|
3836
|
-
this._state.groupedItems.forEach(group => {
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
}
|
|
3979
|
+
this._state.groupedItems.forEach((group, groupIndex) => {
|
|
3980
|
+
let header;
|
|
3981
|
+
if (this.groupHeaderRenderer) {
|
|
3982
|
+
header = this.groupHeaderRenderer(group, groupIndex);
|
|
3983
|
+
// make sure the returned element has the correct semantics so
|
|
3984
|
+
// people can style it. we add the class/part even if the renderer
|
|
3985
|
+
// returned something else to ensure backward compatibility.
|
|
3986
|
+
if (!(header instanceof HTMLElement)) {
|
|
3987
|
+
// fall back to default if API is misused
|
|
3988
|
+
header = document.createElement('div');
|
|
3989
|
+
header.textContent = String(group.label);
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
else {
|
|
3993
|
+
header = document.createElement('div');
|
|
3994
|
+
header.textContent = group.label;
|
|
3995
|
+
}
|
|
3996
|
+
header.classList.add('group-header');
|
|
3997
|
+
header.setAttribute('part', 'group-header');
|
|
3853
3998
|
this._optionsContainer.appendChild(header);
|
|
3854
3999
|
group.options.forEach(item => {
|
|
3855
4000
|
// Find original index for correct ID generation and selection
|
|
@@ -4122,6 +4267,8 @@ class EnhancedSelect extends HTMLElement {
|
|
|
4122
4267
|
}
|
|
4123
4268
|
}
|
|
4124
4269
|
}
|
|
4270
|
+
/** live set of all connected instances; used to auto-close siblings */
|
|
4271
|
+
EnhancedSelect._instances = new Set();
|
|
4125
4272
|
// Register custom element
|
|
4126
4273
|
if (!customElements.get('enhanced-select')) {
|
|
4127
4274
|
customElements.define('enhanced-select', EnhancedSelect);
|