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