@smilodon/core 1.4.3 → 1.4.5

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.umd.js CHANGED
@@ -1398,6 +1398,14 @@
1398
1398
  expandLabel: 'Show more',
1399
1399
  collapseLabel: 'Show less',
1400
1400
  },
1401
+ clearControl: {
1402
+ enabled: false,
1403
+ clearSelection: true,
1404
+ clearSearch: true,
1405
+ hideWhenEmpty: true,
1406
+ ariaLabel: 'Clear selection and search',
1407
+ icon: '×',
1408
+ },
1401
1409
  callbacks: {},
1402
1410
  enabled: true,
1403
1411
  searchable: false,
@@ -1502,10 +1510,15 @@
1502
1510
  display: flex;
1503
1511
  align-items: center;
1504
1512
  justify-content: space-between;
1505
- padding: 8px 12px;
1513
+ padding: var(--select-option-padding, 8px 12px);
1506
1514
  cursor: pointer;
1507
1515
  user-select: none;
1508
- transition: background-color 0.2s ease;
1516
+ transition: var(--select-option-transition, background-color 0.2s ease);
1517
+ border: var(--select-option-border, none);
1518
+ border-bottom: var(--select-option-border-bottom, none);
1519
+ border-radius: var(--select-option-border-radius, 0);
1520
+ box-shadow: var(--select-option-shadow, none);
1521
+ transform: var(--select-option-transform, none);
1509
1522
  }
1510
1523
 
1511
1524
  .option-container:hover {
@@ -1515,6 +1528,20 @@
1515
1528
  .option-container.selected {
1516
1529
  background-color: var(--select-option-selected-bg, #e3f2fd);
1517
1530
  color: var(--select-option-selected-color, #1976d2);
1531
+ border: var(--select-option-selected-border, var(--select-option-border, none));
1532
+ border-bottom: var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none));
1533
+ border-radius: var(--select-option-selected-border-radius, var(--select-option-border-radius, 0));
1534
+ box-shadow: var(--select-option-selected-shadow, var(--select-option-shadow, none));
1535
+ transform: var(--select-option-selected-transform, var(--select-option-transform, none));
1536
+ }
1537
+
1538
+ .option-container.selected:hover {
1539
+ background-color: var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e3f2fd));
1540
+ color: var(--select-option-selected-hover-color, var(--select-option-selected-color, #1976d2));
1541
+ border: var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)));
1542
+ border-bottom: var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
1543
+ box-shadow: var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
1544
+ transform: var(--select-option-selected-hover-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
1518
1545
  }
1519
1546
 
1520
1547
  .option-container.active {
@@ -1822,6 +1849,16 @@
1822
1849
  * server-side selection, and full customization
1823
1850
  */
1824
1851
  class EnhancedSelect extends HTMLElement {
1852
+ get classMap() {
1853
+ return this._classMap;
1854
+ }
1855
+ set classMap(map) {
1856
+ this._classMap = map;
1857
+ this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map));
1858
+ if (!this.isConnected)
1859
+ return;
1860
+ this._renderOptions();
1861
+ }
1825
1862
  constructor() {
1826
1863
  super();
1827
1864
  this._pageCache = {};
@@ -1865,6 +1902,7 @@
1865
1902
  this._inputContainer = this._createInputContainer();
1866
1903
  this._input = this._createInput();
1867
1904
  this._arrowContainer = this._createArrowContainer();
1905
+ this._clearControl = this._createClearControl();
1868
1906
  this._dropdown = this._createDropdown();
1869
1907
  this._optionsContainer = this._createOptionsContainer();
1870
1908
  this._liveRegion = this._createLiveRegion();
@@ -2078,8 +2116,25 @@
2078
2116
  `;
2079
2117
  return container;
2080
2118
  }
2119
+ _createClearControl() {
2120
+ const button = document.createElement('button');
2121
+ button.type = 'button';
2122
+ button.className = 'clear-control-button';
2123
+ button.setAttribute('part', 'clear-button');
2124
+ const icon = document.createElement('span');
2125
+ icon.className = 'clear-control-icon';
2126
+ icon.setAttribute('part', 'clear-icon');
2127
+ icon.textContent = this._config.clearControl.icon || '×';
2128
+ button.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
2129
+ button.appendChild(icon);
2130
+ this._clearControlIcon = icon;
2131
+ return button;
2132
+ }
2081
2133
  _assembleDOM() {
2082
2134
  this._inputContainer.appendChild(this._input);
2135
+ if (this._clearControl) {
2136
+ this._inputContainer.appendChild(this._clearControl);
2137
+ }
2083
2138
  if (this._arrowContainer) {
2084
2139
  this._inputContainer.appendChild(this._arrowContainer);
2085
2140
  }
@@ -2095,6 +2150,7 @@
2095
2150
  this._dropdown.id = listboxId;
2096
2151
  this._input.setAttribute('aria-controls', listboxId);
2097
2152
  this._input.setAttribute('aria-owns', listboxId);
2153
+ this._syncClearControlState();
2098
2154
  }
2099
2155
  _initializeStyles() {
2100
2156
  const style = document.createElement('style');
@@ -2168,6 +2224,58 @@
2168
2224
  border-radius: var(--select-arrow-border-radius, 0 4px 4px 0);
2169
2225
  z-index: 2;
2170
2226
  }
2227
+
2228
+ .input-container.has-clear-control {
2229
+ padding: var(--select-input-padding-with-clear, 6px 84px 6px 8px);
2230
+ }
2231
+
2232
+ .input-container.has-clear-control::after {
2233
+ right: var(--select-separator-position-with-clear, 72px);
2234
+ }
2235
+
2236
+ .dropdown-arrow-container.with-clear-control {
2237
+ right: var(--select-arrow-right-with-clear, 32px);
2238
+ }
2239
+
2240
+ .clear-control-button {
2241
+ position: absolute;
2242
+ top: 50%;
2243
+ right: var(--select-clear-button-right, 6px);
2244
+ transform: translateY(-50%);
2245
+ width: var(--select-clear-button-size, 24px);
2246
+ height: var(--select-clear-button-size, 24px);
2247
+ border: var(--select-clear-button-border, none);
2248
+ border-radius: var(--select-clear-button-radius, 999px);
2249
+ background: var(--select-clear-button-bg, transparent);
2250
+ color: var(--select-clear-button-color, #6b7280);
2251
+ display: inline-flex;
2252
+ align-items: center;
2253
+ justify-content: center;
2254
+ cursor: pointer;
2255
+ z-index: 3;
2256
+ transition: var(--select-clear-button-transition, all 0.2s ease);
2257
+ }
2258
+
2259
+ .clear-control-button:hover {
2260
+ background: var(--select-clear-button-hover-bg, rgba(0, 0, 0, 0.06));
2261
+ color: var(--select-clear-button-hover-color, #111827);
2262
+ }
2263
+
2264
+ .clear-control-button:focus-visible {
2265
+ outline: var(--select-clear-button-focus-outline, 2px solid rgba(102, 126, 234, 0.55));
2266
+ outline-offset: 1px;
2267
+ }
2268
+
2269
+ .clear-control-button[hidden] {
2270
+ display: none;
2271
+ }
2272
+
2273
+ .clear-control-icon {
2274
+ font-size: var(--select-clear-icon-size, 16px);
2275
+ line-height: 1;
2276
+ font-weight: var(--select-clear-icon-weight, 500);
2277
+ pointer-events: none;
2278
+ }
2171
2279
 
2172
2280
  .dropdown-arrow-container:hover {
2173
2281
  background-color: var(--select-arrow-hover-bg, rgba(102, 126, 234, 0.08));
@@ -2295,6 +2403,9 @@
2295
2403
  line-height: var(--select-option-line-height, 1.5);
2296
2404
  border: var(--select-option-border, none);
2297
2405
  border-bottom: var(--select-option-border-bottom, none);
2406
+ border-radius: var(--select-option-border-radius, 0);
2407
+ box-shadow: var(--select-option-shadow, none);
2408
+ transform: var(--select-option-transform, none);
2298
2409
  }
2299
2410
 
2300
2411
  .option:hover {
@@ -2306,18 +2417,47 @@
2306
2417
  background-color: var(--select-option-selected-bg, #e0e7ff);
2307
2418
  color: var(--select-option-selected-color, #4338ca);
2308
2419
  font-weight: var(--select-option-selected-weight, 500);
2420
+ border: var(--select-option-selected-border, var(--select-option-border, none));
2421
+ border-bottom: var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none));
2422
+ border-radius: var(--select-option-selected-border-radius, var(--select-option-border-radius, 0));
2423
+ box-shadow: var(--select-option-selected-shadow, var(--select-option-shadow, none));
2424
+ transform: var(--select-option-selected-transform, var(--select-option-transform, none));
2425
+ }
2426
+
2427
+ .option.selected:hover {
2428
+ background-color: var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e0e7ff));
2429
+ color: var(--select-option-selected-hover-color, var(--select-option-selected-color, #4338ca));
2430
+ border: var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)));
2431
+ border-bottom: var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2432
+ box-shadow: var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2433
+ transform: var(--select-option-selected-hover-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2309
2434
  }
2310
2435
 
2311
- .option.active {
2436
+ .option.active:not(.selected) {
2312
2437
  background-color: var(--select-option-active-bg, #f3f4f6);
2313
2438
  color: var(--select-option-active-color, #1f2937);
2314
2439
  outline: var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45));
2315
2440
  outline-offset: -2px;
2316
2441
  }
2317
2442
 
2318
- .option:active {
2443
+ .option.selected.active {
2444
+ background-color: var(--select-option-selected-active-bg, var(--select-option-selected-bg, #e0e7ff));
2445
+ color: var(--select-option-selected-active-color, var(--select-option-selected-color, #4338ca));
2446
+ border: var(--select-option-selected-active-border, var(--select-option-selected-border, var(--select-option-border, none)));
2447
+ border-bottom: var(--select-option-selected-active-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2448
+ box-shadow: var(--select-option-selected-active-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2449
+ transform: var(--select-option-selected-active-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2450
+ outline: var(--select-option-selected-active-outline, var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45)));
2451
+ outline-offset: -2px;
2452
+ }
2453
+
2454
+ .option:active:not(.selected) {
2319
2455
  background-color: var(--select-option-pressed-bg, #e5e7eb);
2320
2456
  }
2457
+
2458
+ .option.selected:active {
2459
+ background-color: var(--select-option-selected-pressed-bg, var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e0e7ff)));
2460
+ }
2321
2461
 
2322
2462
  .load-more-container {
2323
2463
  padding: var(--select-load-more-padding, 12px);
@@ -2463,14 +2603,38 @@
2463
2603
  .option.selected {
2464
2604
  background-color: var(--select-dark-option-selected-bg, #3730a3);
2465
2605
  color: var(--select-dark-option-selected-text, #e0e7ff);
2606
+ border: var(--select-dark-option-selected-border, var(--select-option-selected-border, var(--select-option-border, none)));
2607
+ border-bottom: var(--select-dark-option-selected-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2608
+ box-shadow: var(--select-dark-option-selected-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2609
+ transform: var(--select-dark-option-selected-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2610
+ }
2611
+
2612
+ .option.selected:hover {
2613
+ background-color: var(--select-dark-option-selected-hover-bg, var(--select-dark-option-selected-bg, #3730a3));
2614
+ color: var(--select-dark-option-selected-hover-color, var(--select-dark-option-selected-text, #e0e7ff));
2615
+ 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
+ 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)))));
2617
+ box-shadow: var(--select-dark-option-selected-hover-shadow, var(--select-dark-option-selected-shadow, var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)))));
2618
+ transform: var(--select-dark-option-selected-hover-transform, var(--select-dark-option-selected-transform, var(--select-option-selected-hover-transform, var(--select-option-selected-transform, var(--select-option-transform, none)))));
2466
2619
  }
2467
2620
 
2468
- .option.active {
2621
+ .option.active:not(.selected) {
2469
2622
  background-color: var(--select-dark-option-active-bg, #374151);
2470
2623
  color: var(--select-dark-option-active-color, #f9fafb);
2471
2624
  outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
2472
2625
  }
2473
2626
 
2627
+ .option.selected.active {
2628
+ background-color: var(--select-dark-option-selected-active-bg, var(--select-dark-option-selected-bg, #3730a3));
2629
+ color: var(--select-dark-option-selected-active-color, var(--select-dark-option-selected-text, #e0e7ff));
2630
+ border: var(--select-dark-option-selected-active-border, var(--select-dark-option-selected-border, var(--select-option-selected-active-border, var(--select-option-selected-border, var(--select-option-border, none)))));
2631
+ border-bottom: var(--select-dark-option-selected-active-border-bottom, var(--select-dark-option-selected-border-bottom, var(--select-option-selected-active-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)))));
2632
+ box-shadow: var(--select-dark-option-selected-active-shadow, var(--select-dark-option-selected-shadow, var(--select-option-selected-active-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)))));
2633
+ transform: var(--select-dark-option-selected-active-transform, var(--select-dark-option-selected-transform, var(--select-option-selected-active-transform, var(--select-option-selected-transform, var(--select-option-transform, none)))));
2634
+ outline: var(--select-dark-option-selected-active-outline, var(--select-dark-option-active-outline, var(--select-option-selected-active-outline, var(--select-option-active-outline, 2px solid rgba(129, 140, 248, 0.55)))));
2635
+ outline-offset: -2px;
2636
+ }
2637
+
2474
2638
  .selection-badge {
2475
2639
  background: var(--select-dark-badge-bg, #4f46e5);
2476
2640
  color: var(--select-dark-badge-color, #eef2ff);
@@ -2555,6 +2719,17 @@
2555
2719
  };
2556
2720
  this._arrowContainer.addEventListener('click', this._boundArrowClick);
2557
2721
  }
2722
+ if (this._clearControl) {
2723
+ this._clearControl.addEventListener('pointerdown', (e) => {
2724
+ e.stopPropagation();
2725
+ e.preventDefault();
2726
+ });
2727
+ this._clearControl.addEventListener('click', (e) => {
2728
+ e.stopPropagation();
2729
+ e.preventDefault();
2730
+ this._handleClearControlClick();
2731
+ });
2732
+ }
2558
2733
  // Input container click - focus input and open dropdown
2559
2734
  this._inputContainer.addEventListener('pointerdown', (e) => {
2560
2735
  const target = e.target;
@@ -2562,6 +2737,8 @@
2562
2737
  return;
2563
2738
  if (target && target.closest('.dropdown-arrow-container'))
2564
2739
  return;
2740
+ if (target && target.closest('.clear-control-button'))
2741
+ return;
2565
2742
  if (!this._state.isOpen) {
2566
2743
  this._handleOpen();
2567
2744
  }
@@ -2774,6 +2951,7 @@
2774
2951
  }
2775
2952
  _handleSearch(query) {
2776
2953
  this._state.searchQuery = query;
2954
+ this._syncClearControlState();
2777
2955
  if (query.length > 0) {
2778
2956
  this._perfMark('smilodon-search-input-last');
2779
2957
  this._pendingSearchRenderMark = true;
@@ -2951,7 +3129,9 @@
2951
3129
  option.classList.add('smilodon-option--active');
2952
3130
  option.setAttribute('aria-selected', 'true');
2953
3131
  }
2954
- option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3132
+ if (typeof option.scrollIntoView === 'function') {
3133
+ option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3134
+ }
2955
3135
  // Announce position for screen readers
2956
3136
  const total = options.length;
2957
3137
  this._announce(`Item ${index + 1} of ${total}`);
@@ -3078,6 +3258,10 @@
3078
3258
  const item = this._state.loadedItems[index];
3079
3259
  if (!item)
3080
3260
  return;
3261
+ // Keep active/focus styling aligned with the most recently interacted option.
3262
+ // Without this, a previously selected item may retain active classes/styles
3263
+ // after selecting a different option.
3264
+ this._state.activeIndex = index;
3081
3265
  const isCurrentlySelected = this._state.selectedIndices.has(index);
3082
3266
  if (this._config.selection.mode === 'single') {
3083
3267
  // Single select: clear previous and select new
@@ -3202,6 +3386,7 @@
3202
3386
  this._inputContainer.insertBefore(badge, this._input);
3203
3387
  });
3204
3388
  }
3389
+ this._syncClearControlState();
3205
3390
  }
3206
3391
  _renderOptionsWithAnimation() {
3207
3392
  // Add fade-out animation
@@ -3317,7 +3502,7 @@
3317
3502
  }
3318
3503
  set optionRenderer(renderer) {
3319
3504
  this._optionRenderer = renderer;
3320
- this._setGlobalStylesMirroring(Boolean(renderer));
3505
+ this._setGlobalStylesMirroring(Boolean(renderer || this._classMap));
3321
3506
  this._renderOptions();
3322
3507
  }
3323
3508
  /**
@@ -3450,6 +3635,21 @@
3450
3635
  this._renderOptions();
3451
3636
  this._updateInputDisplay();
3452
3637
  this._emitChange();
3638
+ this._syncClearControlState();
3639
+ }
3640
+ /**
3641
+ * Clear search query and restore full option list
3642
+ */
3643
+ clearSearch() {
3644
+ this._state.searchQuery = '';
3645
+ if (this._config.searchable) {
3646
+ this._input.value = '';
3647
+ if (this._state.selectedIndices.size > 0) {
3648
+ this._updateInputDisplay();
3649
+ }
3650
+ }
3651
+ this._renderOptions();
3652
+ this._syncClearControlState();
3453
3653
  }
3454
3654
  /**
3455
3655
  * Open dropdown
@@ -3476,6 +3676,12 @@
3476
3676
  this._input.placeholder = this._config.placeholder || 'Select an option...';
3477
3677
  }
3478
3678
  }
3679
+ if (this._clearControl) {
3680
+ this._clearControl.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
3681
+ }
3682
+ if (this._clearControlIcon) {
3683
+ this._clearControlIcon.textContent = this._config.clearControl.icon || '×';
3684
+ }
3479
3685
  if (this._dropdown) {
3480
3686
  if (this._config.selection.mode === 'multi') {
3481
3687
  this._dropdown.setAttribute('aria-multiselectable', 'true');
@@ -3486,8 +3692,47 @@
3486
3692
  }
3487
3693
  // Re-initialize observers in case infinite scroll was enabled/disabled
3488
3694
  this._initializeObservers();
3695
+ this._syncClearControlState();
3489
3696
  this._renderOptions();
3490
3697
  }
3698
+ _handleClearControlClick() {
3699
+ const shouldClearSelection = this._config.clearControl.clearSelection !== false;
3700
+ const shouldClearSearch = this._config.clearControl.clearSearch !== false;
3701
+ const hadSelection = this._state.selectedIndices.size > 0;
3702
+ const hadSearch = this._state.searchQuery.length > 0;
3703
+ if (shouldClearSelection && hadSelection) {
3704
+ this.clear();
3705
+ }
3706
+ if (shouldClearSearch && hadSearch) {
3707
+ this.clearSearch();
3708
+ }
3709
+ this._emit('clear', {
3710
+ clearedSelection: shouldClearSelection && hadSelection,
3711
+ clearedSearch: shouldClearSearch && hadSearch,
3712
+ });
3713
+ this._config.callbacks.onClear?.({
3714
+ clearedSelection: shouldClearSelection && hadSelection,
3715
+ clearedSearch: shouldClearSearch && hadSearch,
3716
+ });
3717
+ this._input.focus();
3718
+ }
3719
+ _syncClearControlState() {
3720
+ if (!this._clearControl || !this._inputContainer || !this._arrowContainer)
3721
+ return;
3722
+ const enabled = this._config.clearControl.enabled === true;
3723
+ const hasSelection = this._state.selectedIndices.size > 0;
3724
+ const hasSearch = this._state.searchQuery.length > 0;
3725
+ const clearSelection = this._config.clearControl.clearSelection !== false;
3726
+ const clearSearch = this._config.clearControl.clearSearch !== false;
3727
+ const hasSomethingToClear = (clearSelection && hasSelection) || (clearSearch && hasSearch);
3728
+ const hideWhenEmpty = this._config.clearControl.hideWhenEmpty !== false;
3729
+ const visible = enabled && (!hideWhenEmpty || hasSomethingToClear);
3730
+ this._inputContainer.classList.toggle('has-clear-control', enabled);
3731
+ this._arrowContainer.classList.toggle('with-clear-control', enabled);
3732
+ this._clearControl.hidden = !visible;
3733
+ this._clearControl.disabled = !hasSomethingToClear;
3734
+ this._clearControl.setAttribute('aria-hidden', String(!visible));
3735
+ }
3491
3736
  /**
3492
3737
  * Set error state
3493
3738
  */
@@ -4775,10 +5020,17 @@
4775
5020
  color: var(--ns-item-selected-color, #0066cc);
4776
5021
  }
4777
5022
 
4778
- .ns-item[data-active="true"] {
5023
+ .ns-item[data-active="true"]:not([aria-selected="true"]) {
4779
5024
  background: var(--ns-item-active-bg, rgba(0, 102, 204, 0.2));
4780
5025
  }
4781
5026
 
5027
+ .ns-item[aria-selected="true"][data-active="true"] {
5028
+ background: var(--ns-item-selected-active-bg, var(--ns-item-selected-bg, rgba(0, 102, 204, 0.1)));
5029
+ color: var(--ns-item-selected-active-color, var(--ns-item-selected-color, #0066cc));
5030
+ outline: var(--ns-item-selected-active-outline, 2px solid #0066cc);
5031
+ outline-offset: -2px;
5032
+ }
5033
+
4782
5034
  .ns-item[aria-disabled="true"] {
4783
5035
  opacity: 0.5;
4784
5036
  cursor: not-allowed;