@smilodon/core 1.4.6 → 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 +141 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +141 -28
- 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 +141 -28
- 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
|
@@ -1506,6 +1506,31 @@
|
|
|
1506
1506
|
position: relative;
|
|
1507
1507
|
}
|
|
1508
1508
|
|
|
1509
|
+
/* Allow authors to style selected state from outside the shadow root
|
|
1510
|
+
by setting attributes/classes on the host element. This mirrors the
|
|
1511
|
+
internal .option-container.selected rules but reads the same CSS
|
|
1512
|
+
custom properties so themes can fully control selected appearance. */
|
|
1513
|
+
:host([aria-selected="true"]) .option-container,
|
|
1514
|
+
:host(.smilodon-option--selected) .option-container {
|
|
1515
|
+
background: var(--select-option-selected-bg, #e3f2fd);
|
|
1516
|
+
color: var(--select-option-selected-color, #1976d2);
|
|
1517
|
+
border: var(--select-option-selected-border, var(--select-option-border, none));
|
|
1518
|
+
border-bottom: var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none));
|
|
1519
|
+
border-radius: var(--select-option-selected-border-radius, var(--select-option-border-radius, 0));
|
|
1520
|
+
box-shadow: var(--select-option-selected-shadow, var(--select-option-shadow, none));
|
|
1521
|
+
transform: var(--select-option-selected-transform, var(--select-option-transform, none));
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
:host([aria-selected="true"]) .option-container:hover,
|
|
1525
|
+
:host(.smilodon-option--selected) .option-container:hover {
|
|
1526
|
+
background: var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e3f2fd));
|
|
1527
|
+
color: var(--select-option-selected-hover-color, var(--select-option-selected-color, #1976d2));
|
|
1528
|
+
border: var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)));
|
|
1529
|
+
border-bottom: var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
|
|
1530
|
+
box-shadow: var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
|
|
1531
|
+
transform: var(--select-option-selected-hover-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1509
1534
|
.option-container {
|
|
1510
1535
|
display: flex;
|
|
1511
1536
|
align-items: center;
|
|
@@ -2392,6 +2417,20 @@
|
|
|
2392
2417
|
background: var(--select-options-bg, var(--select-dropdown-bg, var(--select-bg, white)));
|
|
2393
2418
|
}
|
|
2394
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
|
+
|
|
2395
2434
|
.option {
|
|
2396
2435
|
padding: var(--select-option-padding, 8px 12px);
|
|
2397
2436
|
cursor: pointer;
|
|
@@ -2565,9 +2604,12 @@
|
|
|
2565
2604
|
}
|
|
2566
2605
|
}
|
|
2567
2606
|
|
|
2568
|
-
/* Dark mode - Opt-in via class
|
|
2607
|
+
/* Dark mode - Opt-in via class, data attribute, or ancestor context */
|
|
2569
2608
|
:host(.dark-mode),
|
|
2570
|
-
:host([data-theme="dark"])
|
|
2609
|
+
:host([data-theme="dark"]),
|
|
2610
|
+
:host-context(.dark-mode),
|
|
2611
|
+
:host-context(.dark),
|
|
2612
|
+
:host-context([data-theme="dark"]) {
|
|
2571
2613
|
.input-container {
|
|
2572
2614
|
background: var(--select-dark-bg, #1f2937);
|
|
2573
2615
|
border-color: var(--select-dark-border, #4b5563);
|
|
@@ -2596,12 +2638,12 @@
|
|
|
2596
2638
|
}
|
|
2597
2639
|
|
|
2598
2640
|
.option:hover {
|
|
2599
|
-
background
|
|
2641
|
+
background: var(--select-dark-option-hover-bg, #374151);
|
|
2600
2642
|
color: var(--select-dark-option-hover-color, #f9fafb);
|
|
2601
2643
|
}
|
|
2602
2644
|
|
|
2603
2645
|
.option.selected {
|
|
2604
|
-
background
|
|
2646
|
+
background: var(--select-dark-option-selected-bg, #3730a3);
|
|
2605
2647
|
color: var(--select-dark-option-selected-text, #e0e7ff);
|
|
2606
2648
|
border: var(--select-dark-option-selected-border, var(--select-option-selected-border, var(--select-option-border, none)));
|
|
2607
2649
|
border-bottom: var(--select-dark-option-selected-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
|
|
@@ -2610,7 +2652,7 @@
|
|
|
2610
2652
|
}
|
|
2611
2653
|
|
|
2612
2654
|
.option.selected:hover {
|
|
2613
|
-
background
|
|
2655
|
+
background: var(--select-dark-option-selected-hover-bg, var(--select-dark-option-selected-bg, #3730a3));
|
|
2614
2656
|
color: var(--select-dark-option-selected-hover-color, var(--select-dark-option-selected-text, #e0e7ff));
|
|
2615
2657
|
border: var(--select-dark-option-selected-hover-border, var(--select-dark-option-selected-border, var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)))));
|
|
2616
2658
|
border-bottom: var(--select-dark-option-selected-hover-border-bottom, var(--select-dark-option-selected-border-bottom, var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)))));
|
|
@@ -2731,7 +2773,12 @@
|
|
|
2731
2773
|
});
|
|
2732
2774
|
}
|
|
2733
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
|
|
2734
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();
|
|
2735
2782
|
const target = e.target;
|
|
2736
2783
|
if (!this._config.enabled)
|
|
2737
2784
|
return;
|
|
@@ -2739,10 +2786,23 @@
|
|
|
2739
2786
|
return;
|
|
2740
2787
|
if (target && target.closest('.clear-control-button'))
|
|
2741
2788
|
return;
|
|
2742
|
-
|
|
2789
|
+
const wasClosed = !this._state.isOpen;
|
|
2790
|
+
if (wasClosed) {
|
|
2743
2791
|
this._handleOpen();
|
|
2744
2792
|
}
|
|
2793
|
+
// Focus the input (do not prevent default behavior)
|
|
2745
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
|
+
}
|
|
2746
2806
|
});
|
|
2747
2807
|
// Input container click - prevent event from reaching document listener
|
|
2748
2808
|
this._container.addEventListener('click', (e) => {
|
|
@@ -2750,6 +2810,12 @@
|
|
|
2750
2810
|
});
|
|
2751
2811
|
// Input focus/blur
|
|
2752
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.
|
|
2753
2819
|
this._input.addEventListener('blur', (e) => {
|
|
2754
2820
|
const related = e.relatedTarget;
|
|
2755
2821
|
if (related && (this._shadow.contains(related) || this._container.contains(related))) {
|
|
@@ -2757,6 +2823,10 @@
|
|
|
2757
2823
|
}
|
|
2758
2824
|
// Delay to allow option click/focus transitions
|
|
2759
2825
|
setTimeout(() => {
|
|
2826
|
+
if (this._suppressBlurClose) {
|
|
2827
|
+
// another pointerdown inside options is in progress; keep open
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2760
2830
|
const active = document.activeElement;
|
|
2761
2831
|
if (active && (this._shadow.contains(active) || this._container.contains(active))) {
|
|
2762
2832
|
return;
|
|
@@ -2771,11 +2841,33 @@
|
|
|
2771
2841
|
const query = e.target.value;
|
|
2772
2842
|
this._handleSearch(query);
|
|
2773
2843
|
});
|
|
2774
|
-
//
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
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
|
+
}
|
|
2779
2871
|
if (option && !option.hasAttribute('aria-disabled')) {
|
|
2780
2872
|
const indexStr = option.getAttribute('data-sm-index') ?? option.getAttribute('data-index');
|
|
2781
2873
|
const index = Number(indexStr);
|
|
@@ -2786,13 +2878,45 @@
|
|
|
2786
2878
|
});
|
|
2787
2879
|
}
|
|
2788
2880
|
}
|
|
2789
|
-
}
|
|
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);
|
|
2790
2886
|
// Keyboard navigation
|
|
2791
2887
|
this._input.addEventListener('keydown', (e) => this._handleKeydown(e));
|
|
2792
|
-
// Click outside to close
|
|
2888
|
+
// Click outside to close — robust detection across shadow DOM and custom renderers
|
|
2793
2889
|
document.addEventListener('pointerdown', (e) => {
|
|
2794
2890
|
const path = (e.composedPath && e.composedPath()) || [];
|
|
2795
|
-
|
|
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
|
+
}
|
|
2796
2920
|
if (!clickedInside) {
|
|
2797
2921
|
this._handleClose();
|
|
2798
2922
|
}
|
|
@@ -3256,13 +3380,14 @@
|
|
|
3256
3380
|
// FIX: Do not rely on this._optionsContainer.children[index] because filtering changes the children
|
|
3257
3381
|
// Instead, use the index to update state directly
|
|
3258
3382
|
const item = this._state.loadedItems[index];
|
|
3383
|
+
// Debug: log selection attempt
|
|
3259
3384
|
if (!item)
|
|
3260
3385
|
return;
|
|
3386
|
+
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3261
3387
|
// Keep active/focus styling aligned with the most recently interacted option.
|
|
3262
3388
|
// Without this, a previously selected item may retain active classes/styles
|
|
3263
3389
|
// after selecting a different option.
|
|
3264
3390
|
this._state.activeIndex = index;
|
|
3265
|
-
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3266
3391
|
if (this._config.selection.mode === 'single') {
|
|
3267
3392
|
// Single select: clear previous and select new
|
|
3268
3393
|
const wasSelected = this._state.selectedIndices.has(index);
|
|
@@ -3493,6 +3618,7 @@
|
|
|
3493
3618
|
const getValue = this._config.serverSide.getValueFromItem || ((item) => item?.value ?? item);
|
|
3494
3619
|
const selectedValues = selectedItems.map(getValue);
|
|
3495
3620
|
const selectedIndices = Array.from(this._state.selectedIndices);
|
|
3621
|
+
// Debug: log change payload
|
|
3496
3622
|
this._emit('change', { selectedItems, selectedValues, selectedIndices });
|
|
3497
3623
|
this._config.callbacks.onChange?.(selectedItems, selectedValues);
|
|
3498
3624
|
}
|
|
@@ -3816,19 +3942,6 @@
|
|
|
3816
3942
|
const header = document.createElement('div');
|
|
3817
3943
|
header.className = 'group-header';
|
|
3818
3944
|
header.textContent = group.label;
|
|
3819
|
-
Object.assign(header.style, {
|
|
3820
|
-
padding: '8px 12px',
|
|
3821
|
-
fontWeight: '600',
|
|
3822
|
-
color: '#6b7280',
|
|
3823
|
-
backgroundColor: '#f3f4f6',
|
|
3824
|
-
fontSize: '12px',
|
|
3825
|
-
textTransform: 'uppercase',
|
|
3826
|
-
letterSpacing: '0.05em',
|
|
3827
|
-
position: 'sticky',
|
|
3828
|
-
top: '0',
|
|
3829
|
-
zIndex: '1',
|
|
3830
|
-
borderBottom: '1px solid #e5e7eb'
|
|
3831
|
-
});
|
|
3832
3945
|
this._optionsContainer.appendChild(header);
|
|
3833
3946
|
group.options.forEach(item => {
|
|
3834
3947
|
// Find original index for correct ID generation and selection
|