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