@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.js
CHANGED
|
@@ -1500,6 +1500,31 @@ class SelectOption extends HTMLElement {
|
|
|
1500
1500
|
position: relative;
|
|
1501
1501
|
}
|
|
1502
1502
|
|
|
1503
|
+
/* Allow authors to style selected state from outside the shadow root
|
|
1504
|
+
by setting attributes/classes on the host element. This mirrors the
|
|
1505
|
+
internal .option-container.selected rules but reads the same CSS
|
|
1506
|
+
custom properties so themes can fully control selected appearance. */
|
|
1507
|
+
:host([aria-selected="true"]) .option-container,
|
|
1508
|
+
:host(.smilodon-option--selected) .option-container {
|
|
1509
|
+
background: var(--select-option-selected-bg, #e3f2fd);
|
|
1510
|
+
color: var(--select-option-selected-color, #1976d2);
|
|
1511
|
+
border: var(--select-option-selected-border, var(--select-option-border, none));
|
|
1512
|
+
border-bottom: var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none));
|
|
1513
|
+
border-radius: var(--select-option-selected-border-radius, var(--select-option-border-radius, 0));
|
|
1514
|
+
box-shadow: var(--select-option-selected-shadow, var(--select-option-shadow, none));
|
|
1515
|
+
transform: var(--select-option-selected-transform, var(--select-option-transform, none));
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
:host([aria-selected="true"]) .option-container:hover,
|
|
1519
|
+
:host(.smilodon-option--selected) .option-container:hover {
|
|
1520
|
+
background: var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e3f2fd));
|
|
1521
|
+
color: var(--select-option-selected-hover-color, var(--select-option-selected-color, #1976d2));
|
|
1522
|
+
border: var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)));
|
|
1523
|
+
border-bottom: var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
|
|
1524
|
+
box-shadow: var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
|
|
1525
|
+
transform: var(--select-option-selected-hover-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1503
1528
|
.option-container {
|
|
1504
1529
|
display: flex;
|
|
1505
1530
|
align-items: center;
|
|
@@ -2386,6 +2411,20 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2386
2411
|
background: var(--select-options-bg, var(--select-dropdown-bg, var(--select-bg, white)));
|
|
2387
2412
|
}
|
|
2388
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
|
+
|
|
2389
2428
|
.option {
|
|
2390
2429
|
padding: var(--select-option-padding, 8px 12px);
|
|
2391
2430
|
cursor: pointer;
|
|
@@ -2559,9 +2598,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2559
2598
|
}
|
|
2560
2599
|
}
|
|
2561
2600
|
|
|
2562
|
-
/* Dark mode - Opt-in via class
|
|
2601
|
+
/* Dark mode - Opt-in via class, data attribute, or ancestor context */
|
|
2563
2602
|
:host(.dark-mode),
|
|
2564
|
-
:host([data-theme="dark"])
|
|
2603
|
+
:host([data-theme="dark"]),
|
|
2604
|
+
:host-context(.dark-mode),
|
|
2605
|
+
:host-context(.dark),
|
|
2606
|
+
:host-context([data-theme="dark"]) {
|
|
2565
2607
|
.input-container {
|
|
2566
2608
|
background: var(--select-dark-bg, #1f2937);
|
|
2567
2609
|
border-color: var(--select-dark-border, #4b5563);
|
|
@@ -2590,12 +2632,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2590
2632
|
}
|
|
2591
2633
|
|
|
2592
2634
|
.option:hover {
|
|
2593
|
-
background
|
|
2635
|
+
background: var(--select-dark-option-hover-bg, #374151);
|
|
2594
2636
|
color: var(--select-dark-option-hover-color, #f9fafb);
|
|
2595
2637
|
}
|
|
2596
2638
|
|
|
2597
2639
|
.option.selected {
|
|
2598
|
-
background
|
|
2640
|
+
background: var(--select-dark-option-selected-bg, #3730a3);
|
|
2599
2641
|
color: var(--select-dark-option-selected-text, #e0e7ff);
|
|
2600
2642
|
border: var(--select-dark-option-selected-border, var(--select-option-selected-border, var(--select-option-border, none)));
|
|
2601
2643
|
border-bottom: var(--select-dark-option-selected-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
|
|
@@ -2604,7 +2646,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2604
2646
|
}
|
|
2605
2647
|
|
|
2606
2648
|
.option.selected:hover {
|
|
2607
|
-
background
|
|
2649
|
+
background: var(--select-dark-option-selected-hover-bg, var(--select-dark-option-selected-bg, #3730a3));
|
|
2608
2650
|
color: var(--select-dark-option-selected-hover-color, var(--select-dark-option-selected-text, #e0e7ff));
|
|
2609
2651
|
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)))));
|
|
2610
2652
|
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)))));
|
|
@@ -2725,7 +2767,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2725
2767
|
});
|
|
2726
2768
|
}
|
|
2727
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
|
|
2728
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();
|
|
2729
2776
|
const target = e.target;
|
|
2730
2777
|
if (!this._config.enabled)
|
|
2731
2778
|
return;
|
|
@@ -2733,10 +2780,23 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2733
2780
|
return;
|
|
2734
2781
|
if (target && target.closest('.clear-control-button'))
|
|
2735
2782
|
return;
|
|
2736
|
-
|
|
2783
|
+
const wasClosed = !this._state.isOpen;
|
|
2784
|
+
if (wasClosed) {
|
|
2737
2785
|
this._handleOpen();
|
|
2738
2786
|
}
|
|
2787
|
+
// Focus the input (do not prevent default behavior)
|
|
2739
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
|
+
}
|
|
2740
2800
|
});
|
|
2741
2801
|
// Input container click - prevent event from reaching document listener
|
|
2742
2802
|
this._container.addEventListener('click', (e) => {
|
|
@@ -2744,6 +2804,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2744
2804
|
});
|
|
2745
2805
|
// Input focus/blur
|
|
2746
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.
|
|
2747
2813
|
this._input.addEventListener('blur', (e) => {
|
|
2748
2814
|
const related = e.relatedTarget;
|
|
2749
2815
|
if (related && (this._shadow.contains(related) || this._container.contains(related))) {
|
|
@@ -2751,6 +2817,10 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2751
2817
|
}
|
|
2752
2818
|
// Delay to allow option click/focus transitions
|
|
2753
2819
|
setTimeout(() => {
|
|
2820
|
+
if (this._suppressBlurClose) {
|
|
2821
|
+
// another pointerdown inside options is in progress; keep open
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2754
2824
|
const active = document.activeElement;
|
|
2755
2825
|
if (active && (this._shadow.contains(active) || this._container.contains(active))) {
|
|
2756
2826
|
return;
|
|
@@ -2765,11 +2835,33 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2765
2835
|
const query = e.target.value;
|
|
2766
2836
|
this._handleSearch(query);
|
|
2767
2837
|
});
|
|
2768
|
-
//
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
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
|
+
}
|
|
2773
2865
|
if (option && !option.hasAttribute('aria-disabled')) {
|
|
2774
2866
|
const indexStr = option.getAttribute('data-sm-index') ?? option.getAttribute('data-index');
|
|
2775
2867
|
const index = Number(indexStr);
|
|
@@ -2780,13 +2872,45 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2780
2872
|
});
|
|
2781
2873
|
}
|
|
2782
2874
|
}
|
|
2783
|
-
}
|
|
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);
|
|
2784
2880
|
// Keyboard navigation
|
|
2785
2881
|
this._input.addEventListener('keydown', (e) => this._handleKeydown(e));
|
|
2786
|
-
// Click outside to close
|
|
2882
|
+
// Click outside to close — robust detection across shadow DOM and custom renderers
|
|
2787
2883
|
document.addEventListener('pointerdown', (e) => {
|
|
2788
2884
|
const path = (e.composedPath && e.composedPath()) || [];
|
|
2789
|
-
|
|
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
|
+
}
|
|
2790
2914
|
if (!clickedInside) {
|
|
2791
2915
|
this._handleClose();
|
|
2792
2916
|
}
|
|
@@ -3250,13 +3374,14 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3250
3374
|
// FIX: Do not rely on this._optionsContainer.children[index] because filtering changes the children
|
|
3251
3375
|
// Instead, use the index to update state directly
|
|
3252
3376
|
const item = this._state.loadedItems[index];
|
|
3377
|
+
// Debug: log selection attempt
|
|
3253
3378
|
if (!item)
|
|
3254
3379
|
return;
|
|
3380
|
+
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3255
3381
|
// Keep active/focus styling aligned with the most recently interacted option.
|
|
3256
3382
|
// Without this, a previously selected item may retain active classes/styles
|
|
3257
3383
|
// after selecting a different option.
|
|
3258
3384
|
this._state.activeIndex = index;
|
|
3259
|
-
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3260
3385
|
if (this._config.selection.mode === 'single') {
|
|
3261
3386
|
// Single select: clear previous and select new
|
|
3262
3387
|
const wasSelected = this._state.selectedIndices.has(index);
|
|
@@ -3487,6 +3612,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3487
3612
|
const getValue = this._config.serverSide.getValueFromItem || ((item) => item?.value ?? item);
|
|
3488
3613
|
const selectedValues = selectedItems.map(getValue);
|
|
3489
3614
|
const selectedIndices = Array.from(this._state.selectedIndices);
|
|
3615
|
+
// Debug: log change payload
|
|
3490
3616
|
this._emit('change', { selectedItems, selectedValues, selectedIndices });
|
|
3491
3617
|
this._config.callbacks.onChange?.(selectedItems, selectedValues);
|
|
3492
3618
|
}
|
|
@@ -3810,19 +3936,6 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3810
3936
|
const header = document.createElement('div');
|
|
3811
3937
|
header.className = 'group-header';
|
|
3812
3938
|
header.textContent = group.label;
|
|
3813
|
-
Object.assign(header.style, {
|
|
3814
|
-
padding: '8px 12px',
|
|
3815
|
-
fontWeight: '600',
|
|
3816
|
-
color: '#6b7280',
|
|
3817
|
-
backgroundColor: '#f3f4f6',
|
|
3818
|
-
fontSize: '12px',
|
|
3819
|
-
textTransform: 'uppercase',
|
|
3820
|
-
letterSpacing: '0.05em',
|
|
3821
|
-
position: 'sticky',
|
|
3822
|
-
top: '0',
|
|
3823
|
-
zIndex: '1',
|
|
3824
|
-
borderBottom: '1px solid #e5e7eb'
|
|
3825
|
-
});
|
|
3826
3939
|
this._optionsContainer.appendChild(header);
|
|
3827
3940
|
group.options.forEach(item => {
|
|
3828
3941
|
// Find original index for correct ID generation and selection
|