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