@smilodon/core 1.4.7 → 1.4.9
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.cjs +113 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +113 -25
- 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 +113 -25
- 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/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2413,6 +2413,20 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2413
2413
|
background: var(--select-options-bg, var(--select-dropdown-bg, var(--select-bg, white)));
|
|
2414
2414
|
}
|
|
2415
2415
|
|
|
2416
|
+
.group-header {
|
|
2417
|
+
padding: var(--select-group-header-padding, 8px 12px);
|
|
2418
|
+
font-weight: var(--select-group-header-weight, 600);
|
|
2419
|
+
color: var(--select-group-header-color, #6b7280);
|
|
2420
|
+
background-color: var(--select-group-header-bg, #f3f4f6);
|
|
2421
|
+
font-size: var(--select-group-header-font-size, 12px);
|
|
2422
|
+
text-transform: var(--select-group-header-text-transform, uppercase);
|
|
2423
|
+
letter-spacing: var(--select-group-header-letter-spacing, 0.05em);
|
|
2424
|
+
position: sticky;
|
|
2425
|
+
top: 0;
|
|
2426
|
+
z-index: 1;
|
|
2427
|
+
border-bottom: var(--select-group-header-border-bottom, 1px solid #e5e7eb);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2416
2430
|
.option {
|
|
2417
2431
|
padding: var(--select-option-padding, 8px 12px);
|
|
2418
2432
|
cursor: pointer;
|
|
@@ -2586,9 +2600,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2586
2600
|
}
|
|
2587
2601
|
}
|
|
2588
2602
|
|
|
2589
|
-
/* Dark mode - Opt-in via class
|
|
2603
|
+
/* Dark mode - Opt-in via class, data attribute, or ancestor context */
|
|
2590
2604
|
:host(.dark-mode),
|
|
2591
|
-
:host([data-theme="dark"])
|
|
2605
|
+
:host([data-theme="dark"]),
|
|
2606
|
+
:host-context(.dark-mode),
|
|
2607
|
+
:host-context(.dark),
|
|
2608
|
+
:host-context([data-theme="dark"]) {
|
|
2592
2609
|
.input-container {
|
|
2593
2610
|
background: var(--select-dark-bg, #1f2937);
|
|
2594
2611
|
border-color: var(--select-dark-border, #4b5563);
|
|
@@ -2752,7 +2769,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2752
2769
|
});
|
|
2753
2770
|
}
|
|
2754
2771
|
// Input container click - focus input and open dropdown
|
|
2772
|
+
// Prevent the original pointer event from bubbling/causing default focus behavior
|
|
2773
|
+
// which can interfere with option click handling when opening the dropdown
|
|
2755
2774
|
this._inputContainer.addEventListener('pointerdown', (e) => {
|
|
2775
|
+
// Prevent propagation to document click listener but do NOT preventDefault.
|
|
2776
|
+
// Allow default so browser events (click) on newly opened options still fire.
|
|
2777
|
+
e.stopPropagation();
|
|
2756
2778
|
const target = e.target;
|
|
2757
2779
|
if (!this._config.enabled)
|
|
2758
2780
|
return;
|
|
@@ -2760,10 +2782,23 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2760
2782
|
return;
|
|
2761
2783
|
if (target && target.closest('.clear-control-button'))
|
|
2762
2784
|
return;
|
|
2763
|
-
|
|
2785
|
+
const wasClosed = !this._state.isOpen;
|
|
2786
|
+
if (wasClosed) {
|
|
2764
2787
|
this._handleOpen();
|
|
2765
2788
|
}
|
|
2789
|
+
// Focus the input (do not prevent default behavior)
|
|
2766
2790
|
this._input.focus();
|
|
2791
|
+
// If we just opened the dropdown, transfer pointer capture to the
|
|
2792
|
+
// options container so the subsequent pointerup lands there instead of
|
|
2793
|
+
// staying with the input container (which would swallow the event).
|
|
2794
|
+
if (wasClosed && this._optionsContainer && typeof e.pointerId === 'number') {
|
|
2795
|
+
try {
|
|
2796
|
+
this._optionsContainer.setPointerCapture(e.pointerId);
|
|
2797
|
+
}
|
|
2798
|
+
catch (_err) {
|
|
2799
|
+
// Some browsers may throw if element is not yet "connected"; ignore
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2767
2802
|
});
|
|
2768
2803
|
// Input container click - prevent event from reaching document listener
|
|
2769
2804
|
this._container.addEventListener('click', (e) => {
|
|
@@ -2771,6 +2806,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2771
2806
|
});
|
|
2772
2807
|
// Input focus/blur
|
|
2773
2808
|
this._input.addEventListener('focus', () => this._handleOpen());
|
|
2809
|
+
// When the input loses focus we normally close the dropdown, but
|
|
2810
|
+
// clicking an option will blur the input before the option's click
|
|
2811
|
+
// handler executes. To avoid the blur timer closing the dropdown
|
|
2812
|
+
// prematurely we use a short-lived flag that is set whenever we start
|
|
2813
|
+
// interacting with the options container. The close callback checks this
|
|
2814
|
+
// flag and skips closing if the user is about to click an option.
|
|
2774
2815
|
this._input.addEventListener('blur', (e) => {
|
|
2775
2816
|
const related = e.relatedTarget;
|
|
2776
2817
|
if (related && (this._shadow.contains(related) || this._container.contains(related))) {
|
|
@@ -2778,6 +2819,10 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2778
2819
|
}
|
|
2779
2820
|
// Delay to allow option click/focus transitions
|
|
2780
2821
|
setTimeout(() => {
|
|
2822
|
+
if (this._suppressBlurClose) {
|
|
2823
|
+
// another pointerdown inside options is in progress; keep open
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2781
2826
|
const active = document.activeElement;
|
|
2782
2827
|
if (active && (this._shadow.contains(active) || this._container.contains(active))) {
|
|
2783
2828
|
return;
|
|
@@ -2792,11 +2837,33 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2792
2837
|
const query = e.target.value;
|
|
2793
2838
|
this._handleSearch(query);
|
|
2794
2839
|
});
|
|
2795
|
-
//
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2840
|
+
// If the user presses down inside the options container we should
|
|
2841
|
+
// temporarily suppress blur-based closing until after the click has
|
|
2842
|
+
// been handled. Setting a flag that gets cleared in the next tick is
|
|
2843
|
+
// sufficient.
|
|
2844
|
+
this._optionsContainer.addEventListener('pointerdown', () => {
|
|
2845
|
+
this._suppressBlurClose = true;
|
|
2846
|
+
setTimeout(() => {
|
|
2847
|
+
this._suppressBlurClose = false;
|
|
2848
|
+
}, 0);
|
|
2849
|
+
});
|
|
2850
|
+
// Delegated click listener for improved event handling (robust across shadow DOM)
|
|
2851
|
+
const handleOptionEvent = (e) => {
|
|
2852
|
+
const path = (e.composedPath && e.composedPath()) || [e.target];
|
|
2853
|
+
let option = null;
|
|
2854
|
+
for (const node of path) {
|
|
2855
|
+
if (!(node instanceof Element))
|
|
2856
|
+
continue;
|
|
2857
|
+
try {
|
|
2858
|
+
if (node.matches('[data-sm-selectable], [data-selectable], [data-sm-state]')) {
|
|
2859
|
+
option = node;
|
|
2860
|
+
break;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
catch (err) {
|
|
2864
|
+
continue;
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2800
2867
|
if (option && !option.hasAttribute('aria-disabled')) {
|
|
2801
2868
|
const indexStr = option.getAttribute('data-sm-index') ?? option.getAttribute('data-index');
|
|
2802
2869
|
const index = Number(indexStr);
|
|
@@ -2807,13 +2874,45 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2807
2874
|
});
|
|
2808
2875
|
}
|
|
2809
2876
|
}
|
|
2810
|
-
}
|
|
2877
|
+
};
|
|
2878
|
+
this._optionsContainer.addEventListener('click', handleOptionEvent);
|
|
2879
|
+
// also watch pointerup to catch cases where the pointerdown started outside
|
|
2880
|
+
// (e.g. on the input) and the click never fires
|
|
2881
|
+
this._optionsContainer.addEventListener('pointerup', handleOptionEvent);
|
|
2811
2882
|
// Keyboard navigation
|
|
2812
2883
|
this._input.addEventListener('keydown', (e) => this._handleKeydown(e));
|
|
2813
|
-
// Click outside to close
|
|
2884
|
+
// Click outside to close — robust detection across shadow DOM and custom renderers
|
|
2814
2885
|
document.addEventListener('pointerdown', (e) => {
|
|
2815
2886
|
const path = (e.composedPath && e.composedPath()) || [];
|
|
2816
|
-
|
|
2887
|
+
let clickedInside = false;
|
|
2888
|
+
for (const node of path) {
|
|
2889
|
+
if (node === this || node === this._container) {
|
|
2890
|
+
clickedInside = true;
|
|
2891
|
+
break;
|
|
2892
|
+
}
|
|
2893
|
+
if (node instanceof Node) {
|
|
2894
|
+
try {
|
|
2895
|
+
if (this._shadow && this._shadow.contains(node)) {
|
|
2896
|
+
clickedInside = true;
|
|
2897
|
+
break;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
catch (err) {
|
|
2901
|
+
// ignore
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
if (node instanceof Element) {
|
|
2905
|
+
try {
|
|
2906
|
+
if (node.matches('[data-sm-selectable], [data-selectable], [data-sm-state], .input-container, .select-container, .dropdown-arrow-container, .clear-control-button')) {
|
|
2907
|
+
clickedInside = true;
|
|
2908
|
+
break;
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
catch (err) {
|
|
2912
|
+
// ignore
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2817
2916
|
if (!clickedInside) {
|
|
2818
2917
|
this._handleClose();
|
|
2819
2918
|
}
|
|
@@ -3277,13 +3376,14 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3277
3376
|
// FIX: Do not rely on this._optionsContainer.children[index] because filtering changes the children
|
|
3278
3377
|
// Instead, use the index to update state directly
|
|
3279
3378
|
const item = this._state.loadedItems[index];
|
|
3379
|
+
// Debug: log selection attempt
|
|
3280
3380
|
if (!item)
|
|
3281
3381
|
return;
|
|
3382
|
+
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3282
3383
|
// Keep active/focus styling aligned with the most recently interacted option.
|
|
3283
3384
|
// Without this, a previously selected item may retain active classes/styles
|
|
3284
3385
|
// after selecting a different option.
|
|
3285
3386
|
this._state.activeIndex = index;
|
|
3286
|
-
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3287
3387
|
if (this._config.selection.mode === 'single') {
|
|
3288
3388
|
// Single select: clear previous and select new
|
|
3289
3389
|
const wasSelected = this._state.selectedIndices.has(index);
|
|
@@ -3514,6 +3614,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3514
3614
|
const getValue = this._config.serverSide.getValueFromItem || ((item) => item?.value ?? item);
|
|
3515
3615
|
const selectedValues = selectedItems.map(getValue);
|
|
3516
3616
|
const selectedIndices = Array.from(this._state.selectedIndices);
|
|
3617
|
+
// Debug: log change payload
|
|
3517
3618
|
this._emit('change', { selectedItems, selectedValues, selectedIndices });
|
|
3518
3619
|
this._config.callbacks.onChange?.(selectedItems, selectedValues);
|
|
3519
3620
|
}
|
|
@@ -3837,19 +3938,6 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3837
3938
|
const header = document.createElement('div');
|
|
3838
3939
|
header.className = 'group-header';
|
|
3839
3940
|
header.textContent = group.label;
|
|
3840
|
-
Object.assign(header.style, {
|
|
3841
|
-
padding: '8px 12px',
|
|
3842
|
-
fontWeight: '600',
|
|
3843
|
-
color: '#6b7280',
|
|
3844
|
-
backgroundColor: '#f3f4f6',
|
|
3845
|
-
fontSize: '12px',
|
|
3846
|
-
textTransform: 'uppercase',
|
|
3847
|
-
letterSpacing: '0.05em',
|
|
3848
|
-
position: 'sticky',
|
|
3849
|
-
top: '0',
|
|
3850
|
-
zIndex: '1',
|
|
3851
|
-
borderBottom: '1px solid #e5e7eb'
|
|
3852
|
-
});
|
|
3853
3941
|
this._optionsContainer.appendChild(header);
|
|
3854
3942
|
group.options.forEach(item => {
|
|
3855
3943
|
// Find original index for correct ID generation and selection
|