@smilodon/core 1.4.11 → 1.4.13

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 CHANGED
@@ -1351,6 +1351,7 @@ const defaultConfig = {
1351
1351
  maxSelections: 0,
1352
1352
  showRemoveButton: true,
1353
1353
  closeOnSelect: true,
1354
+ toggleOnTriggerClick: true,
1354
1355
  },
1355
1356
  scrollToSelected: {
1356
1357
  enabled: true,
@@ -1403,6 +1404,18 @@ const defaultConfig = {
1403
1404
  icon: '×',
1404
1405
  },
1405
1406
  callbacks: {},
1407
+ tracking: {
1408
+ enabled: false,
1409
+ events: true,
1410
+ styling: true,
1411
+ limitations: true,
1412
+ emitDiagnostics: false,
1413
+ maxEntries: 200,
1414
+ },
1415
+ limitations: {
1416
+ policies: {},
1417
+ autoMitigateRuntimeModeSwitch: true,
1418
+ },
1406
1419
  enabled: true,
1407
1420
  searchable: false,
1408
1421
  placeholder: 'Select an option...',
@@ -1871,6 +1884,8 @@ if (!customElements.get('select-option')) {
1871
1884
  * Enhanced Select Component
1872
1885
  * Implements all advanced features: infinite scroll, load more, busy state,
1873
1886
  * server-side selection, and full customization
1887
+ *
1888
+ * ✨ Redesigned with formal elegance, refined microinteractions, and polished UX
1874
1889
  */
1875
1890
  class EnhancedSelect extends HTMLElement {
1876
1891
  get classMap() {
@@ -1879,6 +1894,10 @@ class EnhancedSelect extends HTMLElement {
1879
1894
  set classMap(map) {
1880
1895
  this._classMap = map;
1881
1896
  this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map || this._groupHeaderRenderer));
1897
+ this._track('style', 'classMapChanged', {
1898
+ hasClassMap: Boolean(map),
1899
+ keys: map ? Object.keys(map) : [],
1900
+ });
1882
1901
  if (!this.isConnected)
1883
1902
  return;
1884
1903
  this._renderOptions();
@@ -1894,6 +1913,7 @@ class EnhancedSelect extends HTMLElement {
1894
1913
  set groupHeaderRenderer(renderer) {
1895
1914
  this._groupHeaderRenderer = renderer;
1896
1915
  this._setGlobalStylesMirroring(Boolean(this._optionRenderer || this._classMap || renderer));
1916
+ this._track('style', 'groupHeaderRendererChanged', { enabled: Boolean(renderer) });
1897
1917
  if (!this.isConnected)
1898
1918
  return;
1899
1919
  this._renderOptions();
@@ -1912,6 +1932,9 @@ class EnhancedSelect extends HTMLElement {
1912
1932
  this._mirrorGlobalStylesForCustomOptions = false;
1913
1933
  this._globalStylesObserver = null;
1914
1934
  this._globalStylesContainer = null;
1935
+ this._tracking = { events: [], styles: [], limitations: [] };
1936
+ this._suppressBlurClose = false;
1937
+ this._renderCycleId = 0;
1915
1938
  this._shadow = this.attachShadow({ mode: 'open' });
1916
1939
  this._uniqueId = `enhanced-select-${Math.random().toString(36).substr(2, 9)}`;
1917
1940
  this._rendererHelpers = this._buildRendererHelpers();
@@ -1986,6 +2009,7 @@ class EnhancedSelect extends HTMLElement {
1986
2009
  if (this._boundArrowClick && this._arrowContainer) {
1987
2010
  this._arrowContainer.removeEventListener('click', this._boundArrowClick);
1988
2011
  }
2012
+ this._renderCycleId += 1;
1989
2013
  this._teardownGlobalStylesMirroring();
1990
2014
  }
1991
2015
  _setGlobalStylesMirroring(enabled) {
@@ -1996,6 +2020,7 @@ class EnhancedSelect extends HTMLElement {
1996
2020
  return;
1997
2021
  }
1998
2022
  this._mirrorGlobalStylesForCustomOptions = enabled;
2023
+ this._track('style', 'globalStylesMirroringChanged', { enabled });
1999
2024
  if (enabled) {
2000
2025
  this._setupGlobalStylesMirroring();
2001
2026
  }
@@ -2088,8 +2113,48 @@ class EnhancedSelect extends HTMLElement {
2088
2113
  const container = document.createElement('div');
2089
2114
  container.className = 'input-container';
2090
2115
  container.setAttribute('part', 'button');
2116
+ const inputStyles = this._config.styles.input;
2117
+ if (inputStyles && !this._config.styles.container) {
2118
+ const shellStyleKeys = [
2119
+ 'background',
2120
+ 'backgroundColor',
2121
+ 'border',
2122
+ 'borderColor',
2123
+ 'borderStyle',
2124
+ 'borderWidth',
2125
+ 'borderRadius',
2126
+ 'boxShadow',
2127
+ 'padding',
2128
+ 'height',
2129
+ 'minHeight',
2130
+ 'maxHeight',
2131
+ ];
2132
+ for (const key of shellStyleKeys) {
2133
+ const value = inputStyles[key];
2134
+ if (value != null && value !== '') {
2135
+ container.style[key] = value;
2136
+ }
2137
+ }
2138
+ }
2091
2139
  return container;
2092
2140
  }
2141
+ _syncInputContainerMode() {
2142
+ if (!this._inputContainer || !this._input)
2143
+ return;
2144
+ const isMulti = this._config.selection.mode === 'multi';
2145
+ this._inputContainer.classList.toggle('input-container--multi', isMulti);
2146
+ this._inputContainer.classList.toggle('input-container--single', !isMulti);
2147
+ if (isMulti) {
2148
+ this._input.style.flex = '1 0 var(--select-multi-input-min-width, 96px)';
2149
+ this._input.style.width = 'auto';
2150
+ this._input.style.minWidth = 'var(--select-multi-input-min-width, 96px)';
2151
+ }
2152
+ else {
2153
+ this._input.style.flex = '1 1 auto';
2154
+ this._input.style.width = '100%';
2155
+ this._input.style.minWidth = '0';
2156
+ }
2157
+ }
2093
2158
  _createInput() {
2094
2159
  const input = document.createElement('input');
2095
2160
  input.setAttribute('part', 'input');
@@ -2099,6 +2164,35 @@ class EnhancedSelect extends HTMLElement {
2099
2164
  input.placeholder = this._config.placeholder || 'Select an option...';
2100
2165
  input.disabled = !this._config.enabled;
2101
2166
  input.readOnly = !this._config.searchable;
2167
+ // Apply a direct inline reset so the input is only the writable text layer,
2168
+ // while the `.input-container` remains the sole visible control shell.
2169
+ input.style.all = 'unset';
2170
+ input.style.display = 'block';
2171
+ input.style.flex = '1 1 auto';
2172
+ input.style.width = '100%';
2173
+ input.style.maxWidth = '100%';
2174
+ input.style.minWidth = '0';
2175
+ input.style.minInlineSize = '0';
2176
+ input.style.minHeight = '0';
2177
+ input.style.padding = '0';
2178
+ input.style.margin = '0';
2179
+ input.style.border = '0';
2180
+ input.style.background = 'transparent';
2181
+ input.style.boxSizing = 'border-box';
2182
+ input.style.outline = 'none';
2183
+ input.style.font = 'inherit';
2184
+ input.style.fontFamily = 'inherit';
2185
+ input.style.lineHeight = 'inherit';
2186
+ input.style.color = 'inherit';
2187
+ input.style.alignSelf = 'center';
2188
+ input.style.appearance = 'none';
2189
+ input.style.webkitAppearance = 'none';
2190
+ input.style.boxShadow = 'none';
2191
+ input.style.borderRadius = '0';
2192
+ input.style.overflow = 'hidden';
2193
+ input.style.textOverflow = 'ellipsis';
2194
+ input.style.whiteSpace = 'nowrap';
2195
+ input.style.cursor = this._config.searchable ? 'text' : 'default';
2102
2196
  // Update readonly when input is focused if searchable
2103
2197
  input.addEventListener('focus', () => {
2104
2198
  if (this._config.searchable) {
@@ -2109,7 +2203,22 @@ class EnhancedSelect extends HTMLElement {
2109
2203
  input.className += ' ' + this._config.styles.classNames.input;
2110
2204
  }
2111
2205
  if (this._config.styles.input) {
2112
- Object.assign(input.style, this._config.styles.input);
2206
+ const inputStyles = { ...this._config.styles.input };
2207
+ // Route shell-like styling to .input-container so the control appears as
2208
+ // a single styled input instead of two visually separate layers.
2209
+ delete inputStyles.background;
2210
+ delete inputStyles.backgroundColor;
2211
+ delete inputStyles.border;
2212
+ delete inputStyles.borderColor;
2213
+ delete inputStyles.borderStyle;
2214
+ delete inputStyles.borderWidth;
2215
+ delete inputStyles.borderRadius;
2216
+ delete inputStyles.boxShadow;
2217
+ delete inputStyles.padding;
2218
+ delete inputStyles.height;
2219
+ delete inputStyles.minHeight;
2220
+ delete inputStyles.maxHeight;
2221
+ Object.assign(input.style, inputStyles);
2113
2222
  }
2114
2223
  input.setAttribute('role', 'combobox');
2115
2224
  input.setAttribute('aria-expanded', 'false');
@@ -2153,7 +2262,7 @@ class EnhancedSelect extends HTMLElement {
2153
2262
  container.className = 'dropdown-arrow-container';
2154
2263
  container.innerHTML = `
2155
2264
  <svg class="dropdown-arrow" part="arrow" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2156
- <path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2265
+ <path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
2157
2266
  </svg>
2158
2267
  `;
2159
2268
  return container;
@@ -2166,7 +2275,7 @@ class EnhancedSelect extends HTMLElement {
2166
2275
  const icon = document.createElement('span');
2167
2276
  icon.className = 'clear-control-icon';
2168
2277
  icon.setAttribute('part', 'clear-icon');
2169
- icon.textContent = this._config.clearControl.icon || '×';
2278
+ icon.innerHTML = `<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 4L4 12M4 4L12 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
2170
2279
  button.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
2171
2280
  button.appendChild(icon);
2172
2281
  this._clearControlIcon = icon;
@@ -2192,18 +2301,229 @@ class EnhancedSelect extends HTMLElement {
2192
2301
  this._dropdown.id = listboxId;
2193
2302
  this._input.setAttribute('aria-controls', listboxId);
2194
2303
  this._input.setAttribute('aria-owns', listboxId);
2304
+ this._syncInputContainerMode();
2195
2305
  this._syncClearControlState();
2196
2306
  }
2197
2307
  _initializeStyles() {
2198
2308
  const style = document.createElement('style');
2199
2309
  style.textContent = `
2310
+ /* ═══════════════════════════════════════════════════════════════════════════
2311
+ ELEGANT SELECT COMPONENT — Refined Design System
2312
+ Formal aesthetics with sophisticated microinteractions
2313
+ ═══════════════════════════════════════════════════════════════════════════ */
2314
+
2200
2315
  :host {
2316
+ --select-primary: #1a1a2e;
2317
+ --select-primary-light: #16213e;
2318
+ --select-accent: #0f3460;
2319
+ --select-accent-hover: #e94560;
2320
+ --select-surface: #ffffff;
2321
+ --select-surface-elevated: #fafbfc;
2322
+ --select-border: #e1e5eb;
2323
+ --select-border-focus: #0f3460;
2324
+ --select-text: #1a1a2e;
2325
+ --select-text-muted: #6b7280;
2326
+ --select-text-placeholder: #9ca3af;
2327
+ --select-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
2328
+ --select-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
2329
+ --select-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
2330
+ --select-shadow-focus: 0 0 0 3px rgba(15, 52, 96, 0.12);
2331
+ --select-radius-sm: 6px;
2332
+ --select-radius-md: 10px;
2333
+ --select-radius-lg: 14px;
2334
+ --select-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
2335
+ --select-transition-smooth: 250ms cubic-bezier(0.4, 0, 0.2, 1);
2336
+ --select-transition-bounce: 350ms cubic-bezier(0.34, 1.56, 0.64, 1);
2337
+ --select-badge-animation: badgeEnter 300ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
2338
+ --select-badge-enter-from-opacity: 0;
2339
+ --select-badge-enter-from-transform: scale(0.8) translateY(-4px);
2340
+ --select-badge-enter-to-opacity: 1;
2341
+ --select-badge-enter-to-transform: scale(1) translateY(0);
2342
+ --select-badge-hover-transform: scale(1.02);
2343
+ --select-badge-letter-spacing: 0.01em;
2344
+ --select-badge-hover-shadow: var(--select-shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.15);
2345
+ --select-badge-remove-focus-outline: 2px solid rgba(255, 255, 255, 0.5);
2346
+ --select-badge-remove-focus-offset: 1px;
2347
+ --select-badge-remove-hover-transform: scale(1.15) rotate(90deg);
2348
+ --select-badge-remove-active-transform: scale(0.95) rotate(90deg);
2349
+ --select-input-hover-border: var(--select-border-focus);
2350
+ --select-input-hover-shadow: var(--select-shadow-sm), 0 0 0 1px rgba(15, 52, 96, 0.05);
2351
+ --select-input-font-weight: 450;
2352
+ --select-input-letter-spacing: 0.01em;
2353
+ --select-input-disabled-opacity: 0.6;
2354
+ --select-separator-opacity: 0.6;
2355
+ --select-separator-active-opacity: 1;
2356
+ --select-separator-dark-bg: linear-gradient(
2357
+ to bottom,
2358
+ transparent 0%,
2359
+ var(--select-border) 20%,
2360
+ var(--select-border) 80%,
2361
+ transparent 100%
2362
+ );
2363
+ --select-arrow-open-transform: rotate(180deg);
2364
+ --select-clear-button-hover-transform: translateY(-50%) scale(1.1);
2365
+ --select-clear-button-active-transform: translateY(-50%) scale(0.95);
2366
+ --select-clear-button-focus-offset: 2px;
2367
+ --select-clear-icon-size: 14px;
2368
+ --select-dropdown-top: calc(100% + 6px);
2369
+ --select-dropdown-animation: dropdownEnter 200ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
2370
+ --select-dropdown-enter-from-opacity: 0;
2371
+ --select-dropdown-enter-from-transform: translateY(-8px) scale(0.98);
2372
+ --select-dropdown-enter-to-opacity: 1;
2373
+ --select-dropdown-enter-to-transform: translateY(0) scale(1);
2374
+ --select-dropdown-scroll-behavior: smooth;
2375
+ --select-dropdown-transform-origin: top center;
2376
+ --select-scrollbar-width: 6px;
2377
+ --select-scrollbar-thumb-radius: 3px;
2378
+ --select-option-hover-transform: translateX(2px);
2379
+ --select-option-font-weight: 450;
2380
+ --select-option-margin: 2px 0;
2381
+ --select-option-active-outline-offset: -2px;
2382
+ --select-option-selected-active-outline-offset: -2px;
2383
+ --select-option-selected-indicator-width: 3px;
2384
+ --select-option-selected-indicator-height: 60%;
2385
+ --select-option-selected-indicator-bg: var(--select-accent);
2386
+ --select-option-selected-indicator-radius: 0 2px 2px 0;
2387
+ --select-option-selected-indicator-left: 0;
2388
+ --select-option-selected-indicator-top: 50%;
2389
+ --select-option-selected-indicator-transform: translateY(-50%);
2390
+ --select-option-selected-indicator-animation: selectedIndicator 200ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
2391
+ --select-option-selected-indicator-from-height: 0;
2392
+ --select-option-selected-indicator-to-height: 60%;
2393
+ --select-option-selected-indicator-from-opacity: 0;
2394
+ --select-option-selected-indicator-to-opacity: 1;
2395
+ --select-option-pressed-transform: translateX(2px) scale(0.99);
2396
+ --select-option-selected-pressed-transform: scale(0.99);
2397
+ --select-button-hover-transform: translateY(-1px);
2398
+ --select-button-active-transform: translateY(0) scale(0.98);
2399
+ --select-button-font-weight: 550;
2400
+ --select-button-letter-spacing: 0.02em;
2401
+ --select-spinner-animation: spin 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
2402
+ --select-searching-spinner-active-color: var(--select-accent);
2403
+ --select-badge-remove-margin-left: 2px;
2404
+ --select-badge-remove-radius: 50%;
2405
+ --select-badge-remove-font-weight: 600;
2406
+ --select-empty-gap: 8px;
2407
+ --select-searching-gap: 8px;
2408
+ --select-searching-spinner-size: 24px;
2409
+ --select-searching-spinner-border: 2px solid var(--select-border);
2410
+ --select-searching-spinner-animation: var(--select-spinner-animation);
2411
+ --select-group-header-separator-margin-top: 8px;
2412
+ --select-group-header-separator-padding-top: 14px;
2413
+ --select-group-header-separator-border-top: 1px solid var(--select-border);
2414
+ --select-reduced-motion-duration: 0.01ms;
2415
+ --select-reduced-motion-iteration-count: 1;
2416
+ --select-high-contrast-border-width: 2px;
2417
+ --select-high-contrast-indicator-width: 4px;
2418
+ --select-high-contrast-focus-outline-width: 3px;
2419
+ --select-high-contrast-focus-outline-color: Highlight;
2420
+ --select-touch-target-min-height: 44px;
2421
+ --select-badge-dark-bg: linear-gradient(135deg, var(--select-accent) 0%, #4f46e5 100%);
2422
+
2201
2423
  display: block;
2202
2424
  position: relative;
2203
2425
  width: var(--select-width, 100%);
2204
2426
  height: var(--select-height, auto);
2427
+ font-family: var(--select-font-family, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
2428
+ }
2429
+
2430
+ /* ─────────────────────────────────────────────────────────────────────────
2431
+ Selection Badges — Refined pill design with elegant interactions
2432
+ ───────────────────────────────────────────────────────────────────────── */
2433
+
2434
+ .selection-badge {
2435
+ display: inline-flex;
2436
+ align-items: center;
2437
+ gap: var(--select-badge-gap, 6px);
2438
+ flex: 0 1 auto;
2439
+ min-height: var(--select-badge-min-height, 26px);
2440
+ padding: var(--select-badge-padding, 4px 10px 4px 12px);
2441
+ margin: var(--select-badge-margin, 3px);
2442
+ background: var(--select-badge-bg, linear-gradient(135deg, var(--select-accent) 0%, var(--select-primary-light) 100%));
2443
+ color: var(--select-badge-color, #ffffff);
2444
+ border: var(--select-badge-border, none);
2445
+ border-radius: var(--select-badge-border-radius, 999px);
2446
+ box-shadow: var(--select-badge-shadow, var(--select-shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.1));
2447
+ box-sizing: border-box;
2448
+ font-size: var(--select-badge-font-size, 13px);
2449
+ font-weight: var(--select-badge-font-weight, 500);
2450
+ line-height: var(--select-badge-line-height, 1.2);
2451
+ letter-spacing: var(--select-badge-letter-spacing);
2452
+ max-width: var(--select-badge-max-width, 100%);
2453
+ overflow: hidden;
2454
+ transform: scale(1);
2455
+ transition:
2456
+ transform var(--select-transition-bounce),
2457
+ box-shadow var(--select-transition-fast),
2458
+ background var(--select-transition-fast);
2459
+ animation: var(--select-badge-animation);
2460
+ }
2461
+
2462
+ @keyframes badgeEnter {
2463
+ 0% {
2464
+ opacity: var(--select-badge-enter-from-opacity);
2465
+ transform: var(--select-badge-enter-from-transform);
2466
+ }
2467
+ 100% {
2468
+ opacity: var(--select-badge-enter-to-opacity);
2469
+ transform: var(--select-badge-enter-to-transform);
2470
+ }
2205
2471
  }
2206
2472
 
2473
+ .selection-badge:hover {
2474
+ box-shadow: var(--select-badge-hover-shadow);
2475
+ transform: var(--select-badge-hover-transform);
2476
+ }
2477
+
2478
+ .selection-badge-label {
2479
+ display: block;
2480
+ min-width: 0;
2481
+ overflow: hidden;
2482
+ text-overflow: ellipsis;
2483
+ white-space: nowrap;
2484
+ line-height: var(--select-badge-line-height, 1.2);
2485
+ }
2486
+
2487
+ .badge-remove {
2488
+ display: inline-flex;
2489
+ align-items: center;
2490
+ justify-content: center;
2491
+ width: var(--select-badge-remove-size, 18px);
2492
+ height: var(--select-badge-remove-size, 18px);
2493
+ padding: 0;
2494
+ margin-left: var(--select-badge-remove-margin-left);
2495
+ background: var(--select-badge-remove-bg, rgba(255, 255, 255, 0.2));
2496
+ border: var(--select-badge-remove-border, none);
2497
+ border-radius: var(--select-badge-remove-radius);
2498
+ color: var(--select-badge-remove-color, #ffffff);
2499
+ font-size: var(--select-badge-remove-font-size, 11px);
2500
+ font-weight: var(--select-badge-remove-font-weight);
2501
+ line-height: 1;
2502
+ cursor: pointer;
2503
+ flex: 0 0 auto;
2504
+ transition:
2505
+ background var(--select-transition-fast),
2506
+ transform var(--select-transition-bounce);
2507
+ }
2508
+
2509
+ .badge-remove:hover {
2510
+ background: var(--select-badge-remove-hover-bg, rgba(233, 69, 96, 0.9));
2511
+ transform: var(--select-badge-remove-hover-transform);
2512
+ }
2513
+
2514
+ .badge-remove:focus-visible {
2515
+ outline: var(--select-badge-remove-focus-outline);
2516
+ outline-offset: var(--select-badge-remove-focus-offset);
2517
+ }
2518
+
2519
+ .badge-remove:active {
2520
+ transform: var(--select-badge-remove-active-transform);
2521
+ }
2522
+
2523
+ /* ─────────────────────────────────────────────────────────────────────────
2524
+ Input Container — Sophisticated control shell
2525
+ ───────────��───────────────────────────────────────────────────────────── */
2526
+
2207
2527
  .select-container {
2208
2528
  position: relative;
2209
2529
  width: 100%;
@@ -2214,102 +2534,143 @@ class EnhancedSelect extends HTMLElement {
2214
2534
  width: 100%;
2215
2535
  display: flex;
2216
2536
  align-items: center;
2217
- flex-wrap: wrap;
2537
+ flex-wrap: nowrap;
2218
2538
  gap: var(--select-input-gap, 6px);
2219
- padding: var(--select-input-padding, 6px 52px 6px 8px);
2539
+ padding: var(--select-input-padding, 10px 52px 10px 14px);
2220
2540
  height: var(--select-input-height, auto);
2221
- min-height: var(--select-input-min-height, 44px);
2541
+ min-height: var(--select-input-min-height, 48px);
2222
2542
  max-height: var(--select-input-max-height, 160px);
2223
2543
  overflow-y: var(--select-input-overflow-y, auto);
2224
- align-content: flex-start;
2225
- background: var(--select-input-bg, var(--select-bg, white));
2226
- border: var(--select-input-border, 1px solid var(--select-border-color, #d1d5db));
2227
- border-radius: var(--select-input-border-radius, 6px);
2544
+ align-content: center;
2545
+ background: var(--select-input-bg, var(--select-surface));
2546
+ border: var(--select-input-border, 1.5px solid var(--select-border));
2547
+ border-radius: var(--select-input-border-radius, var(--select-radius-md));
2548
+ box-shadow: var(--select-shadow-sm);
2228
2549
  box-sizing: border-box;
2229
- transition: all 0.2s ease;
2550
+ transition:
2551
+ border-color var(--select-transition-fast),
2552
+ box-shadow var(--select-transition-smooth),
2553
+ transform var(--select-transition-fast);
2554
+ }
2555
+
2556
+ .input-container:hover {
2557
+ border-color: var(--select-input-hover-border);
2558
+ box-shadow: var(--select-input-hover-shadow);
2559
+ }
2560
+
2561
+ .input-container.input-container--single {
2562
+ flex-wrap: nowrap;
2563
+ align-content: center;
2564
+ }
2565
+
2566
+ .input-container.input-container--multi {
2567
+ flex-wrap: wrap;
2568
+ align-content: flex-start;
2230
2569
  }
2231
2570
 
2232
2571
  .input-container:focus-within {
2233
- border-color: var(--select-input-focus-border, var(--select-border-focus-color, #667eea));
2234
- box-shadow: var(--select-input-focus-shadow, 0 0 0 3px rgba(102, 126, 234, 0.1));
2572
+ border-color: var(--select-input-focus-border, var(--select-border-focus));
2573
+ box-shadow: var(--select-shadow-focus), var(--select-shadow-sm);
2235
2574
  }
2236
2575
 
2237
- /* Gradient separator before arrow */
2576
+ /* Elegant separator line before arrow */
2238
2577
  .input-container::after {
2239
2578
  content: '';
2240
2579
  position: absolute;
2241
2580
  top: 50%;
2242
- right: var(--select-separator-position, var(--select-seperator-position, 40px));
2581
+ right: var(--select-separator-position, 42px);
2243
2582
  transform: translateY(-50%);
2244
- width: var(--select-separator-width, var(--select-seperator-width, 1px));
2245
- height: var(--select-separator-height, var(--select-seperator-height, 60%));
2246
- background: var(--select-separator-bg, var(--select-seperator-bg, var(--select-separator-gradient, var(--select-seperator-gradient, linear-gradient(
2583
+ width: var(--select-separator-width, 1px);
2584
+ height: var(--select-separator-height, 50%);
2585
+ background: var(--select-separator-bg, linear-gradient(
2247
2586
  to bottom,
2248
2587
  transparent 0%,
2249
- rgba(0, 0, 0, 0.1) 20%,
2250
- rgba(0, 0, 0, 0.1) 80%,
2588
+ var(--select-border) 20%,
2589
+ var(--select-border) 80%,
2251
2590
  transparent 100%
2252
- ))));
2591
+ ));
2253
2592
  pointer-events: none;
2254
2593
  z-index: 1;
2594
+ opacity: var(--select-separator-opacity);
2595
+ transition: opacity var(--select-transition-fast);
2596
+ }
2597
+
2598
+ .input-container:hover::after,
2599
+ .input-container:focus-within::after {
2600
+ opacity: var(--select-separator-active-opacity);
2255
2601
  }
2256
2602
 
2603
+ /* ─────────────────────────────────────────────────────────────────────────
2604
+ Dropdown Arrow — Smooth rotation with refined styling
2605
+ ───────────────────────────────────────────────────────────────────────── */
2606
+
2257
2607
  .dropdown-arrow-container {
2258
2608
  position: absolute;
2259
2609
  top: 0;
2260
2610
  right: 0;
2261
2611
  bottom: 0;
2262
- width: var(--select-arrow-width, 40px);
2263
- /* allow explicit height override even though container normally stretches */
2612
+ width: var(--select-arrow-width, 42px);
2264
2613
  height: var(--select-arrow-height, auto);
2265
2614
  display: flex;
2266
2615
  align-items: center;
2267
2616
  justify-content: center;
2268
2617
  cursor: pointer;
2269
- transition: background-color 0.2s ease;
2270
- border-radius: var(--select-arrow-border-radius, 0 4px 4px 0);
2618
+ border-radius: var(--select-arrow-border-radius, 0 var(--select-radius-md) var(--select-radius-md) 0);
2271
2619
  z-index: 2;
2620
+ transition: background-color var(--select-transition-fast);
2272
2621
  }
2273
2622
 
2274
2623
  .input-container.has-clear-control {
2275
- padding: var(--select-input-padding-with-clear, 6px 84px 6px 8px);
2624
+ padding: var(--select-input-padding-with-clear, 10px 84px 10px 14px);
2276
2625
  }
2277
2626
 
2278
2627
  .input-container.has-clear-control::after {
2279
- right: var(--select-separator-position-with-clear, var(--select-seperator-position-with-clear, 72px));
2628
+ right: var(--select-separator-position-with-clear, 74px);
2280
2629
  }
2281
2630
 
2282
2631
  .dropdown-arrow-container.with-clear-control {
2283
- right: var(--select-arrow-right-with-clear, 32px);
2632
+ right: var(--select-arrow-right-with-clear, 34px);
2284
2633
  }
2285
2634
 
2635
+ /* ─────────────────────────────────────────────────────────────────────────
2636
+ Clear Control — Minimal and elegant dismiss button
2637
+ ───────────────────────────────────────────────────────────────────────── */
2638
+
2286
2639
  .clear-control-button {
2287
2640
  position: absolute;
2288
2641
  top: 50%;
2289
- right: var(--select-clear-button-right, 6px);
2642
+ right: var(--select-clear-button-right, 8px);
2290
2643
  transform: translateY(-50%);
2291
- width: var(--select-clear-button-size, 24px);
2292
- height: var(--select-clear-button-size, 24px);
2644
+ width: var(--select-clear-button-size, 26px);
2645
+ height: var(--select-clear-button-size, 26px);
2293
2646
  border: var(--select-clear-button-border, none);
2294
- border-radius: var(--select-clear-button-radius, 999px);
2647
+ border-radius: var(--select-clear-button-radius, 50%);
2295
2648
  background: var(--select-clear-button-bg, transparent);
2296
- color: var(--select-clear-button-color, #6b7280);
2649
+ color: var(--select-clear-button-color, var(--select-text-muted));
2297
2650
  display: inline-flex;
2298
2651
  align-items: center;
2299
2652
  justify-content: center;
2300
2653
  cursor: pointer;
2301
2654
  z-index: 3;
2302
- transition: var(--select-clear-button-transition, all 0.2s ease);
2655
+ transition:
2656
+ background var(--select-transition-fast),
2657
+ color var(--select-transition-fast),
2658
+ transform var(--select-transition-bounce);
2303
2659
  }
2304
2660
 
2305
2661
  .clear-control-button:hover {
2306
- background: var(--select-clear-button-hover-bg, rgba(0, 0, 0, 0.06));
2307
- color: var(--select-clear-button-hover-color, #111827);
2662
+ background: var(--select-clear-button-hover-bg, rgba(233, 69, 96, 0.1));
2663
+ color: var(--select-clear-button-hover-color, var(--select-accent-hover));
2664
+ transform: var(--select-clear-button-hover-transform);
2665
+ }
2666
+
2667
+ .clear-control-button:active {
2668
+ transform: var(--select-clear-button-active-transform);
2308
2669
  }
2309
2670
 
2310
2671
  .clear-control-button:focus-visible {
2311
- outline: var(--select-clear-button-focus-outline, 2px solid rgba(102, 126, 234, 0.55));
2312
- outline-offset: 1px;
2672
+ outline: var(--select-clear-button-focus-outline, 2px solid var(--select-border-focus));
2673
+ outline-offset: var(--select-clear-button-focus-offset);
2313
2674
  }
2314
2675
 
2315
2676
  .clear-control-button[hidden] {
@@ -2317,260 +2678,332 @@ class EnhancedSelect extends HTMLElement {
2317
2678
  }
2318
2679
 
2319
2680
  .clear-control-icon {
2320
- font-size: var(--select-clear-icon-size, 16px);
2321
- line-height: 1;
2322
- font-weight: var(--select-clear-icon-weight, 500);
2681
+ width: var(--select-clear-icon-size);
2682
+ height: var(--select-clear-icon-size);
2323
2683
  pointer-events: none;
2324
2684
  }
2325
2685
 
2326
- .dropdown-arrow-container:hover {
2327
- background-color: var(--select-arrow-hover-bg, rgba(102, 126, 234, 0.08));
2686
+ .clear-control-icon svg {
2687
+ width: 100%;
2688
+ height: 100%;
2328
2689
  }
2329
2690
 
2330
- .dropdown-arrow:hover {
2331
- /* legacy alias --select-arrow-hover for icon color */
2332
- color: var(--select-arrow-hover, var(--select-arrow-hover-color, #667eea));
2691
+ .dropdown-arrow-container:hover {
2692
+ background-color: var(--select-arrow-hover-bg, rgba(15, 52, 96, 0.05));
2333
2693
  }
2334
2694
 
2335
2695
  .dropdown-arrow {
2336
- width: var(--select-arrow-width, var(--select-arrow-size, 16px));
2337
- height: var(--select-arrow-height, var(--select-arrow-size, 16px));
2338
- color: var(--select-arrow-color, #667eea);
2339
- transition: transform 0.2s ease, color 0.2s ease;
2340
- transform: translateY(0);
2696
+ width: var(--select-arrow-size, 18px);
2697
+ height: var(--select-arrow-size, 18px);
2698
+ color: var(--select-arrow-color, var(--select-text-muted));
2699
+ transition:
2700
+ transform var(--select-transition-smooth),
2701
+ color var(--select-transition-fast);
2702
+ transform-origin: center;
2341
2703
  }
2342
2704
 
2343
2705
  .dropdown-arrow path {
2344
- stroke-width: var(--select-arrow-stroke-width, 2);
2706
+ stroke-width: var(--select-arrow-stroke-width, 1.5);
2345
2707
  }
2346
2708
 
2347
2709
  .dropdown-arrow-container:hover .dropdown-arrow {
2348
- color: var(--select-arrow-hover-color, #667eea);
2710
+ color: var(--select-arrow-hover-color, var(--select-accent));
2349
2711
  }
2350
2712
 
2351
2713
  .dropdown-arrow.open {
2352
- transform: rotate(180deg);
2714
+ transform: var(--select-arrow-open-transform);
2353
2715
  }
2354
2716
 
2355
- .select-input {
2356
- flex: 1;
2357
- width: var(--select-input-width, auto);
2358
- min-width: var(--select-input-min-width, 120px);
2359
- padding: var(--select-input-field-padding, 4px);
2360
- border: none;
2361
- font-size: var(--select-input-font-size, 14px);
2717
+ /* ─────────────────────────────────────────────────────────────────────────
2718
+ Input Field — Clean text entry
2719
+ ───────────────────────────────────────────────────────────────────────── */
2720
+
2721
+ .input-container > .select-input,
2722
+ input.select-input,
2723
+ .select-input[type="text"] {
2724
+ display: block;
2725
+ flex: 1 1 auto;
2726
+ width: var(--select-input-width, 100%);
2727
+ max-width: 100%;
2728
+ min-width: var(--select-input-min-width, 0);
2729
+ min-inline-size: 0;
2730
+ min-height: 0;
2731
+ padding: var(--select-input-field-padding, 0) !important;
2732
+ margin: 0 !important;
2733
+ border: 0 !important;
2734
+ font-size: var(--select-input-font-size, 15px);
2362
2735
  line-height: var(--select-input-line-height, 1.5);
2363
- color: var(--select-input-color, var(--select-text-color, #1f2937));
2364
- background: transparent;
2736
+ color: var(--select-input-color, var(--select-text));
2737
+ background: transparent !important;
2365
2738
  box-sizing: border-box;
2366
- outline: none;
2367
- font-family: var(--select-font-family, inherit);
2368
- }
2369
-
2370
- .select-input::placeholder {
2371
- color: var(--select-input-placeholder-color, var(--select-placeholder-color, #9ca3af));
2372
- }
2373
-
2374
- .selection-badge {
2375
- display: inline-flex;
2376
- align-items: center;
2377
- gap: var(--select-badge-gap, 4px);
2378
- padding: var(--select-badge-padding, 4px 8px);
2379
- margin: var(--select-badge-margin, 2px);
2380
- background: var(--select-badge-bg, #667eea);
2381
- color: var(--select-badge-color, white);
2382
- border-radius: var(--select-badge-border-radius, 4px);
2383
- font-size: var(--select-badge-font-size, 13px);
2384
- line-height: 1;
2385
- max-width: var(--select-badge-max-width, 100%);
2386
- white-space: nowrap;
2739
+ outline: none !important;
2740
+ font-weight: var(--select-input-font-weight);
2741
+ letter-spacing: var(--select-input-letter-spacing);
2742
+ align-self: center;
2743
+ appearance: none !important;
2744
+ -webkit-appearance: none !important;
2745
+ box-shadow: none !important;
2746
+ border-radius: 0 !important;
2387
2747
  overflow: hidden;
2388
2748
  text-overflow: ellipsis;
2749
+ white-space: nowrap;
2389
2750
  }
2390
2751
 
2391
- .badge-remove {
2392
- display: inline-flex;
2393
- align-items: center;
2394
- justify-content: center;
2395
- width: var(--select-badge-remove-size, 16px);
2396
- height: var(--select-badge-remove-size, 16px);
2397
- padding: 0;
2398
- margin-left: 4px;
2399
- background: var(--select-badge-remove-bg, rgba(255, 255, 255, 0.3));
2400
- border: none;
2401
- border-radius: 50%;
2402
- color: var(--select-badge-remove-color, white);
2403
- font-size: var(--select-badge-remove-font-size, 16px);
2404
- line-height: 1;
2405
- cursor: pointer;
2406
- transition: background 0.2s;
2752
+ .select-input::placeholder {
2753
+ color: var(--select-input-placeholder-color, var(--select-text-placeholder));
2754
+ font-weight: 400;
2407
2755
  }
2408
-
2409
- .badge-remove:hover {
2410
- background: var(--select-badge-remove-hover-bg, rgba(255, 255, 255, 0.5));
2756
+
2757
+ .input-container.input-container--multi > .select-input,
2758
+ .input-container.input-container--multi > input.select-input,
2759
+ .input-container.input-container--multi > .select-input[type="text"] {
2760
+ width: auto;
2761
+ min-width: var(--select-multi-input-min-width, 96px);
2762
+ flex: 1 0 var(--select-multi-input-min-width, 96px);
2411
2763
  }
2412
2764
 
2413
- .badge-remove:focus-visible {
2414
- outline: 2px solid var(--select-badge-remove-focus-outline, rgba(255, 255, 255, 0.8));
2415
- outline-offset: 2px;
2765
+ .input-container.input-container--single > .select-input,
2766
+ .input-container.input-container--single > input.select-input,
2767
+ .input-container.input-container--single > .select-input[type="text"] {
2768
+ width: var(--select-input-width, 100%);
2769
+ min-width: 0;
2770
+ flex: 1 1 auto;
2416
2771
  }
2417
2772
 
2418
2773
  .select-input:disabled {
2419
2774
  background-color: var(--select-disabled-bg, #f5f5f5);
2420
2775
  cursor: not-allowed;
2776
+ opacity: var(--select-input-disabled-opacity);
2421
2777
  }
2422
2778
 
2779
+ /* ─────────────────────────────────────────────────────────────────────────
2780
+ Dropdown Panel — Elegant floating container
2781
+ ───────────────────────────────────────────────────────────────────────── */
2782
+
2423
2783
  .select-dropdown {
2424
2784
  position: absolute;
2425
- scroll-behavior: smooth;
2426
- top: 100%;
2785
+ scroll-behavior: var(--select-dropdown-scroll-behavior);
2786
+ top: var(--select-dropdown-top);
2427
2787
  left: 0;
2428
2788
  right: 0;
2429
- margin-top: var(--select-dropdown-margin-top, 4px);
2430
- max-height: var(--select-dropdown-max-height, 300px);
2789
+ max-height: var(--select-dropdown-max-height, 320px);
2431
2790
  overflow: hidden;
2432
- background: var(--select-dropdown-bg, var(--select-bg, white));
2433
- border: 1px solid var(--select-dropdown-border, #ccc);
2434
- border-radius: var(--select-dropdown-border-radius, 4px);
2435
- box-shadow: var(--select-dropdown-shadow, 0 4px 6px rgba(0,0,0,0.1));
2791
+ background: var(--select-dropdown-bg, var(--select-surface));
2792
+ border: 1px solid var(--select-dropdown-border, var(--select-border));
2793
+ border-radius: var(--select-dropdown-border-radius, var(--select-radius-lg));
2794
+ box-shadow: var(--select-dropdown-shadow, var(--select-shadow-lg));
2436
2795
  z-index: var(--select-dropdown-z-index, 1000);
2796
+ transform-origin: var(--select-dropdown-transform-origin);
2797
+ animation: var(--select-dropdown-animation);
2798
+ }
2799
+
2800
+ @keyframes dropdownEnter {
2801
+ 0% {
2802
+ opacity: var(--select-dropdown-enter-from-opacity);
2803
+ transform: var(--select-dropdown-enter-from-transform);
2804
+ }
2805
+ 100% {
2806
+ opacity: var(--select-dropdown-enter-to-opacity);
2807
+ transform: var(--select-dropdown-enter-to-transform);
2808
+ }
2437
2809
  }
2438
2810
 
2439
2811
  .options-container {
2440
2812
  position: relative;
2441
- max-height: var(--select-options-max-height, 300px);
2813
+ max-height: var(--select-options-max-height, 320px);
2442
2814
  overflow: auto;
2443
- transition: opacity 0.2s ease-in-out;
2444
- background: var(--select-options-bg, var(--select-dropdown-bg, var(--select-bg, white)));
2815
+ padding: var(--select-options-padding, 6px);
2816
+ background: var(--select-options-bg, var(--select-surface));
2817
+
2818
+ /* Custom scrollbar styling */
2819
+ scrollbar-width: thin;
2820
+ scrollbar-color: var(--select-border) transparent;
2821
+ }
2822
+
2823
+ .options-container::-webkit-scrollbar {
2824
+ width: var(--select-scrollbar-width);
2825
+ }
2826
+
2827
+ .options-container::-webkit-scrollbar-track {
2828
+ background: transparent;
2829
+ }
2830
+
2831
+ .options-container::-webkit-scrollbar-thumb {
2832
+ background: var(--select-border);
2833
+ border-radius: var(--select-scrollbar-thumb-radius);
2445
2834
  }
2835
+
2836
+ .options-container::-webkit-scrollbar-thumb:hover {
2837
+ background: var(--select-text-muted);
2838
+ }
2839
+
2840
+ /* ─────────────────────────────────────────────────────────────────────────
2841
+ Group Headers — Refined section dividers
2842
+ ───────────────────────────────────────────────────────────────────────── */
2446
2843
 
2447
2844
  .group-header {
2448
- padding: var(--select-group-header-padding, 8px 12px);
2845
+ padding: var(--select-group-header-padding, 10px 12px 6px);
2449
2846
  font-weight: var(--select-group-header-weight, 600);
2450
- color: var(--select-group-header-color, #6b7280);
2451
- background-color: var(--select-group-header-bg, #f3f4f6);
2847
+ color: var(--select-group-header-color, var(--select-text-muted));
2848
+ background-color: var(--select-group-header-bg, var(--select-surface));
2452
2849
  text-align: var(--select-group-header-text-align, left);
2453
- font-size: var(--select-group-header-font-size, 12px);
2850
+ font-size: var(--select-group-header-font-size, 11px);
2454
2851
  text-transform: var(--select-group-header-text-transform, uppercase);
2455
- letter-spacing: var(--select-group-header-letter-spacing, 0.05em);
2852
+ letter-spacing: var(--select-group-header-letter-spacing, 0.08em);
2456
2853
  position: sticky;
2457
2854
  top: 0;
2458
2855
  z-index: 1;
2459
- border-bottom: var(--select-group-header-border-bottom, 1px solid #e5e7eb);
2856
+ border-bottom: var(--select-group-header-border-bottom, none);
2857
+ }
2858
+
2859
+ .group-header:not(:first-child) {
2860
+ margin-top: var(--select-group-header-separator-margin-top);
2861
+ padding-top: var(--select-group-header-separator-padding-top);
2862
+ border-top: var(--select-group-header-separator-border-top);
2460
2863
  }
2461
2864
 
2865
+ /* ─────────────────────────────────────────────────────────────────────────
2866
+ Options — Elegant hover and selection states
2867
+ ───────────────────────────────────────────────────────────────────────── */
2868
+
2462
2869
  .option {
2463
- padding: var(--select-option-padding, 8px 12px);
2870
+ position: relative;
2871
+ padding: var(--select-option-padding, 10px 14px);
2464
2872
  cursor: pointer;
2465
- color: var(--select-option-color, var(--select-text-color, #1f2937));
2466
- background: var(--select-option-bg, var(--select-dropdown-bg, var(--select-bg, white)));
2467
- transition: var(--select-option-transition, background-color 0.15s ease);
2873
+ color: var(--select-option-color, var(--select-text));
2874
+ background: var(--select-option-bg, transparent);
2875
+ transition:
2876
+ background var(--select-transition-fast),
2877
+ color var(--select-transition-fast),
2878
+ transform var(--select-transition-fast),
2879
+ box-shadow var(--select-transition-fast);
2468
2880
  user-select: none;
2469
2881
  font-size: var(--select-option-font-size, 14px);
2882
+ font-weight: var(--select-option-font-weight);
2470
2883
  line-height: var(--select-option-line-height, 1.5);
2471
- border: var(--select-option-border, none);
2472
- border-bottom: var(--select-option-border-bottom, none);
2473
- border-radius: var(--select-option-border-radius, 0);
2474
- box-shadow: var(--select-option-shadow, none);
2475
- transform: var(--select-option-transform, none);
2884
+ border-radius: var(--select-option-border-radius, var(--select-radius-sm));
2885
+ margin: var(--select-option-margin);
2476
2886
  }
2477
2887
 
2478
2888
  .option:hover {
2479
- background: var(--select-option-hover-bg, #f3f4f6);
2480
- color: var(--select-option-hover-color, #1f2937);
2889
+ background: var(--select-option-hover-bg, var(--select-surface-elevated));
2890
+ color: var(--select-option-hover-color, var(--select-text));
2891
+ transform: var(--select-option-hover-transform);
2481
2892
  }
2482
2893
 
2483
2894
  .option.selected {
2484
- background: var(--select-option-selected-bg, #e0e7ff);
2485
- color: var(--select-option-selected-color, #4338ca);
2486
- font-weight: var(--select-option-selected-weight, 500);
2487
- border: var(--select-option-selected-border, var(--select-option-border, none));
2488
- border-bottom: var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none));
2489
- border-radius: var(--select-option-selected-border-radius, var(--select-option-border-radius, 0));
2490
- box-shadow: var(--select-option-selected-shadow, var(--select-option-shadow, none));
2491
- transform: var(--select-option-selected-transform, var(--select-option-transform, none));
2895
+ background: var(--select-option-selected-bg, linear-gradient(135deg, rgba(15, 52, 96, 0.08) 0%, rgba(15, 52, 96, 0.04) 100%));
2896
+ color: var(--select-option-selected-color, var(--select-accent));
2897
+ font-weight: var(--select-option-selected-weight, 550);
2898
+ }
2899
+
2900
+ .option.selected::before {
2901
+ content: '';
2902
+ position: absolute;
2903
+ left: var(--select-option-selected-indicator-left);
2904
+ top: var(--select-option-selected-indicator-top);
2905
+ transform: var(--select-option-selected-indicator-transform);
2906
+ width: var(--select-option-selected-indicator-width);
2907
+ height: var(--select-option-selected-indicator-height);
2908
+ background: var(--select-option-selected-indicator-bg);
2909
+ border-radius: var(--select-option-selected-indicator-radius);
2910
+ animation: var(--select-option-selected-indicator-animation);
2911
+ }
2912
+
2913
+ @keyframes selectedIndicator {
2914
+ 0% {
2915
+ height: var(--select-option-selected-indicator-from-height);
2916
+ opacity: var(--select-option-selected-indicator-from-opacity);
2917
+ }
2918
+ 100% {
2919
+ height: var(--select-option-selected-indicator-to-height);
2920
+ opacity: var(--select-option-selected-indicator-to-opacity);
2921
+ }
2492
2922
  }
2493
2923
 
2494
2924
  .option.selected:hover {
2495
- background: var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e0e7ff));
2496
- color: var(--select-option-selected-hover-color, var(--select-option-selected-color, #4338ca));
2497
- border: var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)));
2498
- border-bottom: var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2499
- box-shadow: var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2500
- transform: var(--select-option-selected-hover-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2925
+ background: var(--select-option-selected-hover-bg, linear-gradient(135deg, rgba(15, 52, 96, 0.12) 0%, rgba(15, 52, 96, 0.06) 100%));
2501
2926
  }
2502
2927
 
2503
2928
  .option.active:not(.selected) {
2504
- background: var(--select-option-active-bg, #f3f4f6);
2505
- color: var(--select-option-active-color, #1f2937);
2506
- outline: var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45));
2507
- outline-offset: -2px;
2929
+ background: var(--select-option-active-bg, var(--select-surface-elevated));
2930
+ outline: var(--select-option-active-outline, 2px solid rgba(15, 52, 96, 0.3));
2931
+ outline-offset: var(--select-option-active-outline-offset);
2508
2932
  }
2509
2933
 
2510
2934
  .option.selected.active {
2511
- background: var(--select-option-selected-active-bg, var(--select-option-selected-bg, #e0e7ff));
2512
- color: var(--select-option-selected-active-color, var(--select-option-selected-color, #4338ca));
2513
- border: var(--select-option-selected-active-border, var(--select-option-selected-border, var(--select-option-border, none)));
2514
- border-bottom: var(--select-option-selected-active-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2515
- box-shadow: var(--select-option-selected-active-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2516
- transform: var(--select-option-selected-active-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2517
- outline: var(--select-option-selected-active-outline, var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45)));
2518
- outline-offset: -2px;
2935
+ outline: var(--select-option-selected-active-outline, 2px solid rgba(15, 52, 96, 0.4));
2936
+ outline-offset: var(--select-option-selected-active-outline-offset);
2519
2937
  }
2520
2938
 
2521
2939
  .option:active:not(.selected) {
2522
- background: var(--select-option-pressed-bg, #e5e7eb);
2940
+ background: var(--select-option-pressed-bg, rgba(15, 52, 96, 0.08));
2941
+ transform: var(--select-option-pressed-transform);
2523
2942
  }
2524
2943
 
2525
2944
  .option.selected:active {
2526
- background: var(--select-option-selected-pressed-bg, var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e0e7ff)));
2945
+ transform: var(--select-option-selected-pressed-transform);
2527
2946
  }
2528
2947
 
2948
+ /* ─────────────────────────────────────────────────────────────────────────
2949
+ Load More & Busy States — Refined feedback indicators
2950
+ ───────────────────────────────────────────────────────────────────────── */
2951
+
2529
2952
  .load-more-container {
2530
2953
  padding: var(--select-load-more-padding, 12px);
2531
2954
  text-align: center;
2532
- border-top: var(--select-divider-border, 1px solid #e0e0e0);
2533
- background: var(--select-load-more-bg, white);
2534
2955
  }
2535
2956
 
2536
2957
  .load-more-button {
2537
- padding: var(--select-button-padding, 8px 16px);
2538
- border: var(--select-button-border, 1px solid #1976d2);
2539
- background: var(--select-button-bg, white);
2540
- color: var(--select-button-color, #1976d2);
2541
- border-radius: var(--select-button-border-radius, 4px);
2958
+ padding: var(--select-button-padding, 10px 20px);
2959
+ border: var(--select-button-border, 1.5px solid var(--select-border));
2960
+ background: var(--select-button-bg, transparent);
2961
+ color: var(--select-button-color, var(--select-accent));
2962
+ border-radius: var(--select-button-border-radius, var(--select-radius-md));
2542
2963
  cursor: pointer;
2543
- font-size: var(--select-button-font-size, 14px);
2544
- font-family: var(--select-font-family, inherit);
2545
- transition: all 0.2s ease;
2964
+ font-size: var(--select-button-font-size, 13px);
2965
+ font-weight: var(--select-button-font-weight);
2966
+ letter-spacing: var(--select-button-letter-spacing);
2967
+ transition:
2968
+ background var(--select-transition-fast),
2969
+ border-color var(--select-transition-fast),
2970
+ color var(--select-transition-fast),
2971
+ transform var(--select-transition-bounce);
2546
2972
  }
2547
2973
 
2548
2974
  .load-more-button:hover {
2549
- background: var(--select-button-hover-bg, #1976d2);
2975
+ background: var(--select-button-hover-bg, var(--select-accent));
2976
+ border-color: var(--select-accent);
2550
2977
  color: var(--select-button-hover-color, white);
2978
+ transform: var(--select-button-hover-transform);
2979
+ }
2980
+
2981
+ .load-more-button:active {
2982
+ transform: var(--select-button-active-transform);
2551
2983
  }
2552
2984
 
2553
2985
  .load-more-button:disabled {
2554
2986
  opacity: var(--select-button-disabled-opacity, 0.5);
2555
2987
  cursor: not-allowed;
2988
+ transform: none;
2556
2989
  }
2557
2990
 
2558
2991
  .busy-bucket {
2559
- padding: var(--select-busy-padding, 16px);
2992
+ padding: var(--select-busy-padding, 20px);
2560
2993
  text-align: center;
2561
- color: var(--select-busy-color, #666);
2562
- background: var(--select-busy-bg, white);
2563
- font-size: var(--select-busy-font-size, 14px);
2994
+ color: var(--select-busy-color, var(--select-text-muted));
2995
+ background: var(--select-busy-bg, transparent);
2996
+ font-size: var(--select-busy-font-size, 13px);
2564
2997
  }
2565
2998
 
2566
2999
  .spinner {
2567
3000
  display: inline-block;
2568
- width: var(--select-spinner-size, 20px);
2569
- height: var(--select-spinner-size, 20px);
2570
- border: var(--select-spinner-border, 2px solid #ccc);
2571
- border-top-color: var(--select-spinner-active-color, #1976d2);
3001
+ width: var(--select-spinner-size, 22px);
3002
+ height: var(--select-spinner-size, 22px);
3003
+ border: var(--select-spinner-border, 2px solid var(--select-border));
3004
+ border-top-color: var(--select-spinner-active-color, var(--select-accent));
2572
3005
  border-radius: 50%;
2573
- animation: spin 0.6s linear infinite;
3006
+ animation: var(--select-spinner-animation);
2574
3007
  }
2575
3008
 
2576
3009
  @keyframes spin {
@@ -2578,61 +3011,97 @@ class EnhancedSelect extends HTMLElement {
2578
3011
  }
2579
3012
 
2580
3013
  .empty-state {
2581
- padding: var(--select-empty-padding, 24px);
3014
+ padding: var(--select-empty-padding, 32px 24px);
2582
3015
  text-align: center;
2583
- color: var(--select-empty-color, #6b7280);
3016
+ color: var(--select-empty-color, var(--select-text-muted));
2584
3017
  font-size: var(--select-empty-font-size, 14px);
2585
- background: var(--select-empty-bg, white);
3018
+ background: var(--select-empty-bg, transparent);
2586
3019
  display: flex;
2587
3020
  flex-direction: column;
2588
3021
  align-items: center;
2589
3022
  justify-content: center;
2590
- gap: 6px;
2591
- min-height: var(--select-empty-min-height, 72px);
3023
+ gap: var(--select-empty-gap);
2592
3024
  }
2593
3025
 
2594
3026
  .searching-state {
2595
- padding: var(--select-searching-padding, 24px);
3027
+ padding: var(--select-searching-padding, 32px 24px);
2596
3028
  text-align: center;
2597
- color: var(--select-searching-color, #667eea);
3029
+ color: var(--select-searching-color, var(--select-accent));
2598
3030
  font-size: var(--select-searching-font-size, 14px);
2599
- font-style: italic;
2600
- background: var(--select-searching-bg, white);
2601
- animation: pulse 1.5s ease-in-out infinite;
3031
+ background: var(--select-searching-bg, transparent);
2602
3032
  display: flex;
2603
3033
  flex-direction: column;
2604
3034
  align-items: center;
2605
3035
  justify-content: center;
2606
- gap: 6px;
2607
- min-height: var(--select-searching-min-height, 72px);
3036
+ gap: var(--select-searching-gap);
2608
3037
  }
2609
3038
 
2610
- @keyframes pulse {
2611
- 0%, 100% { opacity: 1; }
2612
- 50% { opacity: 0.5; }
3039
+ .searching-state::before {
3040
+ content: '';
3041
+ width: var(--select-searching-spinner-size);
3042
+ height: var(--select-searching-spinner-size);
3043
+ border: var(--select-searching-spinner-border);
3044
+ border-top-color: var(--select-searching-spinner-active-color);
3045
+ border-radius: 50%;
3046
+ animation: var(--select-searching-spinner-animation);
2613
3047
  }
2614
3048
 
2615
- /* Error states */
3049
+ /* ─────────────────────────────────────────────────────────────────────────
3050
+ Error States — Clear visual feedback
3051
+ ───────────────────────────────────────────────────────────────────────── */
3052
+
2616
3053
  .select-input[aria-invalid="true"] {
2617
- border-color: var(--select-error-border, #dc2626);
3054
+ border-color: var(--select-error-border, #e94560);
2618
3055
  }
2619
3056
 
2620
3057
  .select-input[aria-invalid="true"]:focus {
2621
- border-color: var(--select-error-border, #dc2626);
2622
- box-shadow: 0 0 0 2px var(--select-error-shadow, rgba(220, 38, 38, 0.1));
2623
- outline-color: var(--select-error-border, #dc2626);
3058
+ border-color: var(--select-error-border, #e94560);
3059
+ box-shadow: 0 0 0 3px var(--select-error-shadow, rgba(233, 69, 96, 0.15));
2624
3060
  }
2625
3061
 
2626
- /* Accessibility: Reduced motion */
3062
+ /* ──────────────────────────────────────────────────��──────────────────────
3063
+ Accessibility — Reduced motion & High contrast
3064
+ ───────────────────────────────────────────────────────────────────────── */
3065
+
2627
3066
  @media (prefers-reduced-motion: reduce) {
2628
- * {
2629
- animation-duration: 0.01ms !important;
2630
- animation-iteration-count: 1 !important;
2631
- transition-duration: 0.01ms !important;
3067
+ *,
3068
+ *::before,
3069
+ *::after {
3070
+ animation-duration: var(--select-reduced-motion-duration) !important;
3071
+ animation-iteration-count: var(--select-reduced-motion-iteration-count) !important;
3072
+ transition-duration: var(--select-reduced-motion-duration) !important;
3073
+ }
3074
+
3075
+ .dropdown-arrow.open {
3076
+ transform: var(--select-arrow-open-transform);
2632
3077
  }
2633
3078
  }
2634
3079
 
2635
- /* Dark mode - Opt-in via class, data attribute, or ancestor context */
3080
+ @media (prefers-contrast: high) {
3081
+ .select-input:focus {
3082
+ outline-width: var(--select-high-contrast-focus-outline-width);
3083
+ outline-color: var(--select-high-contrast-focus-outline-color);
3084
+ }
3085
+
3086
+ .input-container {
3087
+ border-width: var(--select-high-contrast-border-width);
3088
+ }
3089
+
3090
+ .option.selected::before {
3091
+ width: var(--select-high-contrast-indicator-width);
3092
+ }
3093
+ }
3094
+
3095
+ /* Touch targets (WCAG 2.5.5) */
3096
+ .load-more-button,
3097
+ select-option {
3098
+ min-height: var(--select-touch-target-min-height);
3099
+ }
3100
+
3101
+ /* ─────────────────────────────────────────────────────────────────────────
3102
+ Dark Mode — Elegant dark theme
3103
+ ───────────────────────────────────────────────────────────────────────── */
3104
+
2636
3105
  :host(.dark-mode),
2637
3106
  :host([dark-mode]),
2638
3107
  :host([darkmode]),
@@ -2644,139 +3113,129 @@ class EnhancedSelect extends HTMLElement {
2644
3113
  :host-context([darkmode]),
2645
3114
  :host-context([data-theme="dark"]),
2646
3115
  :host-context([theme="dark"]) {
2647
- /* map dark tokens to base option tokens so nested <select-option>
2648
- components also pick up dark mode via inherited CSS variables */
2649
- --select-option-bg: var(--select-dark-option-bg, #1f2937);
2650
- --select-option-color: var(--select-dark-option-color, #f9fafb);
2651
- --select-option-hover-bg: var(--select-dark-option-hover-bg, #374151);
2652
- --select-option-hover-color: var(--select-dark-option-hover-color, #f9fafb);
2653
- --select-option-selected-bg: var(--select-dark-option-selected-bg, #3730a3);
2654
- --select-option-selected-color: var(--select-dark-option-selected-text, #e0e7ff);
2655
-
2656
- .input-container {
2657
- background: var(--select-dark-bg, #1f2937);
2658
- border-color: var(--select-dark-border, #4b5563);
2659
- }
2660
-
2661
- .select-input {
2662
- color: var(--select-dark-text, #f9fafb);
2663
- }
2664
-
2665
- .select-input::placeholder {
2666
- color: var(--select-dark-placeholder, #6b7280);
2667
- }
2668
-
2669
- .select-dropdown {
2670
- background: var(--select-dark-dropdown-bg, #1f2937);
2671
- border-color: var(--select-dark-dropdown-border, #4b5563);
2672
- }
2673
-
2674
- .options-container {
2675
- background: var(--select-dark-options-bg, #1f2937);
2676
- }
2677
-
2678
- .option {
2679
- color: var(--select-dark-option-color, #f9fafb);
2680
- background: var(--select-dark-option-bg, #1f2937);
2681
- }
3116
+ --select-primary: #e5e5e5;
3117
+ --select-primary-light: #2a2a3e;
3118
+ --select-accent: #6366f1;
3119
+ --select-accent-hover: #f43f5e;
3120
+ --select-surface: #1a1a2e;
3121
+ --select-surface-elevated: #252540;
3122
+ --select-border: #3f3f5a;
3123
+ --select-border-focus: #6366f1;
3124
+ --select-text: #f5f5f5;
3125
+ --select-text-muted: #9ca3af;
3126
+ --select-text-placeholder: #6b7280;
3127
+ --select-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
3128
+ --select-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
3129
+ --select-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
3130
+ --select-shadow-focus: 0 0 0 3px rgba(99, 102, 241, 0.25);
2682
3131
 
2683
- .option:hover {
2684
- background: var(--select-dark-option-hover-bg, #374151);
2685
- color: var(--select-dark-option-hover-color, #f9fafb);
2686
- }
2687
-
2688
- .option.selected {
2689
- background: var(--select-dark-option-selected-bg, #3730a3);
2690
- color: var(--select-dark-option-selected-text, #e0e7ff);
2691
- border: var(--select-dark-option-selected-border, var(--select-option-selected-border, var(--select-option-border, none)));
2692
- border-bottom: var(--select-dark-option-selected-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2693
- box-shadow: var(--select-dark-option-selected-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2694
- transform: var(--select-dark-option-selected-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2695
- }
2696
-
2697
- .option.selected:hover {
2698
- background: var(--select-dark-option-selected-hover-bg, var(--select-dark-option-selected-bg, #3730a3));
2699
- color: var(--select-dark-option-selected-hover-color, var(--select-dark-option-selected-text, #e0e7ff));
2700
- 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)))));
2701
- 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)))));
2702
- 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)))));
2703
- 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)))));
2704
- }
2705
-
2706
- .option.active:not(.selected) {
2707
- background-color: var(--select-dark-option-active-bg, #374151);
2708
- color: var(--select-dark-option-active-color, #f9fafb);
2709
- outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
2710
- }
2711
-
2712
- /* Group header in dark mode */
2713
- .group-header {
2714
- color: var(--select-dark-group-header-color, var(--select-group-header-color, #6b7280));
2715
- background-color: var(--select-dark-group-header-bg, var(--select-group-header-bg, #374151));
2716
- }
3132
+ --select-option-bg: transparent;
3133
+ --select-option-color: var(--select-text);
3134
+ --select-option-hover-bg: var(--select-surface-elevated);
3135
+ --select-option-hover-color: var(--select-text);
3136
+ --select-option-selected-bg: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(99, 102, 241, 0.08) 100%);
3137
+ --select-option-selected-color: #a5b4fc;
3138
+ }
2717
3139
 
2718
- .option.selected.active {
2719
- background-color: var(--select-dark-option-selected-active-bg, var(--select-dark-option-selected-bg, #3730a3));
2720
- color: var(--select-dark-option-selected-active-color, var(--select-dark-option-selected-text, #e0e7ff));
2721
- 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)))));
2722
- 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)))));
2723
- 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)))));
2724
- 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)))));
2725
- 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)))));
2726
- outline-offset: -2px;
2727
- }
3140
+ :host(.dark-mode) .input-container,
3141
+ :host([dark-mode]) .input-container,
3142
+ :host([darkmode]) .input-container,
3143
+ :host([data-theme="dark"]) .input-container,
3144
+ :host([theme="dark"]) .input-container,
3145
+ :host-context(.dark-mode) .input-container,
3146
+ :host-context(.dark) .input-container,
3147
+ :host-context([dark-mode]) .input-container,
3148
+ :host-context([darkmode]) .input-container,
3149
+ :host-context([data-theme="dark"]) .input-container,
3150
+ :host-context([theme="dark"]) .input-container {
3151
+ background: var(--select-surface);
3152
+ border-color: var(--select-border);
3153
+ }
2728
3154
 
2729
- .selection-badge {
2730
- background: var(--select-dark-badge-bg, #4f46e5);
2731
- color: var(--select-dark-badge-color, #eef2ff);
2732
- }
3155
+ :host(.dark-mode) .input-container::after,
3156
+ :host([dark-mode]) .input-container::after,
3157
+ :host([darkmode]) .input-container::after,
3158
+ :host([data-theme="dark"]) .input-container::after,
3159
+ :host([theme="dark"]) .input-container::after,
3160
+ :host-context(.dark-mode) .input-container::after,
3161
+ :host-context(.dark) .input-container::after,
3162
+ :host-context([dark-mode]) .input-container::after,
3163
+ :host-context([darkmode]) .input-container::after,
3164
+ :host-context([data-theme="dark"]) .input-container::after,
3165
+ :host-context([theme="dark"]) .input-container::after {
3166
+ background: var(--select-separator-dark-bg);
3167
+ }
2733
3168
 
2734
- .badge-remove {
2735
- background: var(--select-dark-badge-remove-bg, rgba(255, 255, 255, 0.15));
2736
- color: var(--select-dark-badge-remove-color, #e5e7eb);
2737
- }
3169
+ :host(.dark-mode) .select-dropdown,
3170
+ :host([dark-mode]) .select-dropdown,
3171
+ :host([darkmode]) .select-dropdown,
3172
+ :host([data-theme="dark"]) .select-dropdown,
3173
+ :host([theme="dark"]) .select-dropdown,
3174
+ :host-context(.dark-mode) .select-dropdown,
3175
+ :host-context(.dark) .select-dropdown,
3176
+ :host-context([dark-mode]) .select-dropdown,
3177
+ :host-context([darkmode]) .select-dropdown,
3178
+ :host-context([data-theme="dark"]) .select-dropdown,
3179
+ :host-context([theme="dark"]) .select-dropdown {
3180
+ background: var(--select-surface);
3181
+ border-color: var(--select-border);
3182
+ }
2738
3183
 
2739
- .badge-remove:hover {
2740
- background: var(--select-dark-badge-remove-hover-bg, rgba(255, 255, 255, 0.3));
2741
- }
2742
-
2743
- .busy-bucket,
2744
- .empty-state {
2745
- color: var(--select-dark-busy-color, #9ca3af);
2746
- background: var(--select-dark-empty-bg, #111827);
2747
- }
3184
+ :host(.dark-mode) .options-container,
3185
+ :host([dark-mode]) .options-container,
3186
+ :host([darkmode]) .options-container,
3187
+ :host([data-theme="dark"]) .options-container,
3188
+ :host([theme="dark"]) .options-container,
3189
+ :host-context(.dark-mode) .options-container,
3190
+ :host-context(.dark) .options-container,
3191
+ :host-context([dark-mode]) .options-container,
3192
+ :host-context([darkmode]) .options-container,
3193
+ :host-context([data-theme="dark"]) .options-container,
3194
+ :host-context([theme="dark"]) .options-container {
3195
+ background: var(--select-surface);
3196
+ scrollbar-color: var(--select-border) transparent;
3197
+ }
2748
3198
 
2749
- .searching-state {
2750
- background: var(--select-dark-searching-bg, #111827);
2751
- }
2752
-
2753
- .input-container::after {
2754
- background: linear-gradient(
2755
- to bottom,
2756
- transparent 0%,
2757
- rgba(255, 255, 255, 0.1) 20%,
2758
- rgba(255, 255, 255, 0.1) 80%,
2759
- transparent 100%
2760
- );
2761
- }
3199
+ :host(.dark-mode) .selection-badge,
3200
+ :host([dark-mode]) .selection-badge,
3201
+ :host([darkmode]) .selection-badge,
3202
+ :host([data-theme="dark"]) .selection-badge,
3203
+ :host([theme="dark"]) .selection-badge,
3204
+ :host-context(.dark-mode) .selection-badge,
3205
+ :host-context(.dark) .selection-badge,
3206
+ :host-context([dark-mode]) .selection-badge,
3207
+ :host-context([darkmode]) .selection-badge,
3208
+ :host-context([data-theme="dark"]) .selection-badge,
3209
+ :host-context([theme="dark"]) .selection-badge {
3210
+ background: var(--select-badge-dark-bg);
2762
3211
  }
2763
-
2764
- /* Accessibility: High contrast mode */
2765
- @media (prefers-contrast: high) {
2766
- .select-input:focus {
2767
- outline-width: 3px;
2768
- outline-color: Highlight;
2769
- }
2770
-
2771
- .select-input {
2772
- border-width: 2px;
2773
- }
3212
+
3213
+ :host(.dark-mode) .group-header:not(:first-child),
3214
+ :host([dark-mode]) .group-header:not(:first-child),
3215
+ :host([darkmode]) .group-header:not(:first-child),
3216
+ :host([data-theme="dark"]) .group-header:not(:first-child),
3217
+ :host([theme="dark"]) .group-header:not(:first-child),
3218
+ :host-context(.dark-mode) .group-header:not(:first-child),
3219
+ :host-context(.dark) .group-header:not(:first-child),
3220
+ :host-context([dark-mode]) .group-header:not(:first-child),
3221
+ :host-context([darkmode]) .group-header:not(:first-child),
3222
+ :host-context([data-theme="dark"]) .group-header:not(:first-child),
3223
+ :host-context([theme="dark"]) .group-header:not(:first-child) {
3224
+ border-top-color: var(--select-border);
2774
3225
  }
2775
-
2776
- /* Touch targets (WCAG 2.5.5) */
2777
- .load-more-button,
2778
- select-option {
2779
- min-height: 44px;
3226
+
3227
+ :host(.dark-mode) .option.selected::before,
3228
+ :host([dark-mode]) .option.selected::before,
3229
+ :host([darkmode]) .option.selected::before,
3230
+ :host([data-theme="dark"]) .option.selected::before,
3231
+ :host([theme="dark"]) .option.selected::before,
3232
+ :host-context(.dark-mode) .option.selected::before,
3233
+ :host-context(.dark) .option.selected::before,
3234
+ :host-context([dark-mode]) .option.selected::before,
3235
+ :host-context([darkmode]) .option.selected::before,
3236
+ :host-context([data-theme="dark"]) .option.selected::before,
3237
+ :host-context([theme="dark"]) .option.selected::before {
3238
+ background: var(--select-accent);
2780
3239
  }
2781
3240
  `;
2782
3241
  // Insert as first child to ensure styles are processed first
@@ -2796,10 +3255,10 @@ class EnhancedSelect extends HTMLElement {
2796
3255
  // delegate to the existing open/close helpers so we don't accidentally
2797
3256
  // drift out of sync with the logic in those methods (focus, events,
2798
3257
  // scroll-to-selected, etc.)
2799
- if (this._state.isOpen) {
3258
+ if (this._state.isOpen && this._config.selection.toggleOnTriggerClick !== false) {
2800
3259
  this._handleClose();
2801
3260
  }
2802
- else {
3261
+ else if (!this._state.isOpen) {
2803
3262
  this._handleOpen();
2804
3263
  }
2805
3264
  };
@@ -2822,7 +3281,7 @@ class EnhancedSelect extends HTMLElement {
2822
3281
  this._inputContainer.addEventListener('pointerdown', (e) => {
2823
3282
  // Prevent propagation to document click listener but do NOT preventDefault.
2824
3283
  // Allow default so browser events (click) on newly opened options still fire.
2825
- e.stopPropagation();
3284
+ // e.stopPropagation(); // BUG: By stopping propagation here, the document click listener doesn't see it, which is fine for not closing it. But be very careful.
2826
3285
  const target = e.target;
2827
3286
  if (!this._config.enabled)
2828
3287
  return;
@@ -2830,21 +3289,23 @@ class EnhancedSelect extends HTMLElement {
2830
3289
  return;
2831
3290
  if (target && target.closest('.clear-control-button'))
2832
3291
  return;
3292
+ // If we clicked the container, but not the input itself, we must prevent default
3293
+ // otherwise the browser moves focus from our input to the body, immediately triggering blur.
3294
+ if (target && !target.matches('.select-input')) {
3295
+ e.preventDefault();
3296
+ }
3297
+ const clickedInput = Boolean(target && target.matches('.select-input'));
3298
+ if (this._state.isOpen &&
3299
+ this._config.selection.toggleOnTriggerClick !== false &&
3300
+ !(clickedInput && this._config.searchable)) {
3301
+ this._handleClose();
3302
+ return;
3303
+ }
2833
3304
  const wasClosed = !this._state.isOpen;
2834
3305
  if (wasClosed) {
2835
3306
  this._handleOpen();
2836
3307
  }
2837
- else {
2838
- // Keep open while interacting directly with the input so users can
2839
- // place cursor/type without accidental collapse.
2840
- if (target === this._input) {
2841
- this._input.focus();
2842
- return;
2843
- }
2844
- // clicking other parts of the input container while open toggles close
2845
- this._handleClose();
2846
- }
2847
- // Focus the input (do not prevent default behavior)
3308
+ // Focus the input (do not prevent default behavior for input itself)
2848
3309
  this._input.focus();
2849
3310
  // If we just opened the dropdown, transfer pointer capture to the
2850
3311
  // options container so the subsequent pointerup lands there instead of
@@ -2886,7 +3347,7 @@ class EnhancedSelect extends HTMLElement {
2886
3347
  return;
2887
3348
  }
2888
3349
  this._handleClose();
2889
- }, 0);
3350
+ }, 150);
2890
3351
  });
2891
3352
  // Input search
2892
3353
  this._input.addEventListener('input', (e) => {
@@ -2903,7 +3364,7 @@ class EnhancedSelect extends HTMLElement {
2903
3364
  this._suppressBlurClose = true;
2904
3365
  setTimeout(() => {
2905
3366
  this._suppressBlurClose = false;
2906
- }, 0);
3367
+ }, 150); // Increased timeout to ensure click finishes before blur checks
2907
3368
  });
2908
3369
  // Delegated click listener for improved event handling (robust across shadow DOM)
2909
3370
  const handleOptionEvent = (e) => {
@@ -2934,9 +3395,6 @@ class EnhancedSelect extends HTMLElement {
2934
3395
  }
2935
3396
  };
2936
3397
  this._optionsContainer.addEventListener('click', handleOptionEvent);
2937
- // also watch pointerup to catch cases where the pointerdown started outside
2938
- // (e.g. on the input) and the click never fires
2939
- this._optionsContainer.addEventListener('pointerup', handleOptionEvent);
2940
3398
  // Keyboard navigation
2941
3399
  this._input.addEventListener('keydown', (e) => this._handleKeydown(e));
2942
3400
  // Click outside to close — robust detection across shadow DOM and custom renderers
@@ -3034,17 +3492,6 @@ class EnhancedSelect extends HTMLElement {
3034
3492
  this._dropdown.style.display = 'block';
3035
3493
  this._input.setAttribute('aria-expanded', 'true');
3036
3494
  this._updateArrowRotation();
3037
- // Clear search query when opening to show all options
3038
- // This ensures we can scroll to selected item
3039
- if (this._config.searchable) {
3040
- this._state.searchQuery = '';
3041
- // Don't clear input value if it represents selection
3042
- // But if we want to search, we might want to clear it?
3043
- // Standard behavior: input keeps value (label), but dropdown shows all options
3044
- // until user types.
3045
- // However, our filtering logic uses _state.searchQuery.
3046
- // So clearing it here resets the filter.
3047
- }
3048
3495
  // Render options when opening
3049
3496
  this._renderOptions();
3050
3497
  this._setInitialActiveOption();
@@ -3290,24 +3737,24 @@ class EnhancedSelect extends HTMLElement {
3290
3737
  }
3291
3738
  }
3292
3739
  _setActive(index) {
3293
- const options = Array.from(this._optionsContainer.children);
3294
3740
  // Clear previous active state
3295
- if (this._state.activeIndex >= 0 && options[this._state.activeIndex]) {
3296
- const prevOption = options[this._state.activeIndex];
3297
- // Check if it's a custom SelectOption or a lightweight DOM element
3298
- if ('setActive' in prevOption && typeof prevOption.setActive === 'function') {
3299
- prevOption.setActive(false);
3300
- }
3301
- else {
3302
- // Lightweight option - remove active class
3303
- prevOption.classList.remove('smilodon-option--active');
3304
- prevOption.setAttribute('aria-selected', 'false');
3741
+ if (this._state.activeIndex >= 0) {
3742
+ const prevOption = this._getOptionElementByIndex(this._state.activeIndex);
3743
+ if (prevOption) {
3744
+ // Check if it's a custom SelectOption or a lightweight DOM element
3745
+ if ('setActive' in prevOption && typeof prevOption.setActive === 'function') {
3746
+ prevOption.setActive(false);
3747
+ }
3748
+ else {
3749
+ // Lightweight option - remove active class
3750
+ prevOption.classList.remove('smilodon-option--active');
3751
+ }
3305
3752
  }
3306
3753
  }
3307
3754
  this._state.activeIndex = index;
3308
3755
  // Set new active state
3309
- if (options[index]) {
3310
- const option = options[index];
3756
+ const option = this._getOptionElementByIndex(index);
3757
+ if (option) {
3311
3758
  // Check if it's a custom SelectOption or a lightweight DOM element
3312
3759
  if ('setActive' in option && typeof option.setActive === 'function') {
3313
3760
  option.setActive(true);
@@ -3315,13 +3762,13 @@ class EnhancedSelect extends HTMLElement {
3315
3762
  else {
3316
3763
  // Lightweight option - add active class
3317
3764
  option.classList.add('smilodon-option--active');
3318
- option.setAttribute('aria-selected', 'true');
3319
3765
  }
3320
3766
  if (typeof option.scrollIntoView === 'function') {
3767
+ // Don't scroll wildly when just opening with pre-selections
3321
3768
  option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3322
3769
  }
3323
3770
  // Announce position for screen readers
3324
- const total = options.length;
3771
+ const total = this._state.loadedItems.length;
3325
3772
  this._announce(`Item ${index + 1} of ${total}`);
3326
3773
  // Update aria-activedescendant using the actual option id when available
3327
3774
  const optionId = option.id || `${this._uniqueId}-option-${index}`;
@@ -3444,9 +3891,9 @@ class EnhancedSelect extends HTMLElement {
3444
3891
  // FIX: Do not rely on this._optionsContainer.children[index] because filtering changes the children
3445
3892
  // Instead, use the index to update state directly
3446
3893
  const item = this._state.loadedItems[index];
3447
- // Debug: log selection attempt
3448
- if (!item)
3894
+ if (!item) {
3449
3895
  return;
3896
+ }
3450
3897
  const isCurrentlySelected = this._state.selectedIndices.has(index);
3451
3898
  // Keep active/focus styling aligned with the most recently interacted option.
3452
3899
  // Without this, a previously selected item may retain active classes/styles
@@ -3457,7 +3904,7 @@ class EnhancedSelect extends HTMLElement {
3457
3904
  const wasSelected = this._state.selectedIndices.has(index);
3458
3905
  this._state.selectedIndices.clear();
3459
3906
  this._state.selectedItems.clear();
3460
- if (!wasSelected) {
3907
+ if (!wasSelected || !this._config.selection.allowDeselect) {
3461
3908
  // Select this option
3462
3909
  this._state.selectedIndices.add(index);
3463
3910
  this._state.selectedItems.set(index, item);
@@ -3519,16 +3966,34 @@ class EnhancedSelect extends HTMLElement {
3519
3966
  });
3520
3967
  }
3521
3968
  _handleOptionRemove(index) {
3969
+ const item = this._state.selectedItems.get(index);
3522
3970
  const option = this._getOptionElementByIndex(index);
3523
- if (!option)
3524
- return;
3525
3971
  this._state.selectedIndices.delete(index);
3526
3972
  this._state.selectedItems.delete(index);
3527
- option.setSelected(false);
3973
+ if (option && 'setSelected' in option && typeof option.setSelected === 'function') {
3974
+ option.setSelected(false);
3975
+ }
3976
+ else if (option) {
3977
+ option.classList.remove('selected', 'sm-selected', 'smilodon-option--selected');
3978
+ option.setAttribute('aria-selected', 'false');
3979
+ const stateTokens = (option.dataset.smState || '')
3980
+ .split(' ')
3981
+ .map(token => token.trim())
3982
+ .filter(token => token && token !== 'selected');
3983
+ if (stateTokens.length > 0) {
3984
+ option.dataset.smState = stateTokens.join(' ');
3985
+ }
3986
+ else {
3987
+ delete option.dataset.smState;
3988
+ }
3989
+ }
3528
3990
  this._updateInputDisplay();
3991
+ this._renderOptions();
3529
3992
  this._emitChange();
3530
- const config = option.getConfig();
3531
- this._emit('remove', { item: config.item, index });
3993
+ const config = option && 'getConfig' in option && typeof option.getConfig === 'function'
3994
+ ? option.getConfig()
3995
+ : undefined;
3996
+ this._emit('remove', { item: config?.item ?? item, index });
3532
3997
  }
3533
3998
  _updateInputDisplay() {
3534
3999
  const selectedItems = Array.from(this._state.selectedItems.values());
@@ -3556,22 +4021,29 @@ class EnhancedSelect extends HTMLElement {
3556
4021
  const badge = document.createElement('span');
3557
4022
  badge.className = 'selection-badge';
3558
4023
  badge.setAttribute('part', 'chip');
3559
- badge.textContent = getLabel(item);
4024
+ const badgeLabel = document.createElement('span');
4025
+ badgeLabel.className = 'selection-badge-label';
4026
+ badgeLabel.textContent = getLabel(item);
4027
+ badge.appendChild(badgeLabel);
3560
4028
  // Add remove button to badge
3561
- const removeBtn = document.createElement('button');
3562
- removeBtn.className = 'badge-remove';
3563
- removeBtn.setAttribute('part', 'chip-remove');
3564
- removeBtn.innerHTML = '×';
3565
- removeBtn.setAttribute('aria-label', `Remove ${getLabel(item)}`);
3566
- removeBtn.addEventListener('click', (e) => {
3567
- e.stopPropagation();
3568
- this._state.selectedIndices.delete(index);
3569
- this._state.selectedItems.delete(index);
3570
- this._updateInputDisplay();
3571
- this._renderOptions();
3572
- this._emitChange();
3573
- });
3574
- badge.appendChild(removeBtn);
4029
+ if (this._config.selection.showRemoveButton !== false) {
4030
+ const removeBtn = document.createElement('button');
4031
+ removeBtn.type = 'button';
4032
+ removeBtn.className = 'badge-remove';
4033
+ removeBtn.setAttribute('part', 'chip-remove');
4034
+ removeBtn.innerHTML = '×';
4035
+ removeBtn.setAttribute('aria-label', `Remove ${getLabel(item)}`);
4036
+ removeBtn.addEventListener('pointerdown', (e) => {
4037
+ e.stopPropagation();
4038
+ e.preventDefault();
4039
+ });
4040
+ removeBtn.addEventListener('click', (e) => {
4041
+ e.stopPropagation();
4042
+ e.preventDefault();
4043
+ this._handleOptionRemove(index);
4044
+ });
4045
+ badge.appendChild(removeBtn);
4046
+ }
3575
4047
  this._inputContainer.insertBefore(badge, this._input);
3576
4048
  });
3577
4049
  }
@@ -3623,10 +4095,12 @@ class EnhancedSelect extends HTMLElement {
3623
4095
  const option = this._getOptionElementByIndex(targetIndex);
3624
4096
  if (option) {
3625
4097
  // Use smooth scrolling with center alignment for better UX
3626
- option.scrollIntoView({
3627
- block: this._config.scrollToSelected.block || 'center',
3628
- behavior: 'smooth',
3629
- });
4098
+ if (typeof option.scrollIntoView === 'function') {
4099
+ option.scrollIntoView({
4100
+ block: this._config.scrollToSelected.block || 'center',
4101
+ behavior: 'smooth',
4102
+ });
4103
+ }
3630
4104
  // Also set it as active for keyboard navigation
3631
4105
  this._setActive(targetIndex);
3632
4106
  }
@@ -3676,13 +4150,168 @@ class EnhancedSelect extends HTMLElement {
3676
4150
  }
3677
4151
  _emit(name, detail) {
3678
4152
  this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
4153
+ if (name !== 'diagnostic') {
4154
+ this._track('event', String(name), detail);
4155
+ }
4156
+ }
4157
+ _track(source, name, detail) {
4158
+ const cfg = this._config.tracking;
4159
+ if (!cfg?.enabled)
4160
+ return;
4161
+ if (source === 'event' && !cfg.events)
4162
+ return;
4163
+ if (source === 'style' && !cfg.styling)
4164
+ return;
4165
+ if (source === 'limitation' && !cfg.limitations)
4166
+ return;
4167
+ const entry = {
4168
+ timestamp: Date.now(),
4169
+ source,
4170
+ name,
4171
+ detail,
4172
+ };
4173
+ const bucket = source === 'event'
4174
+ ? this._tracking.events
4175
+ : source === 'style'
4176
+ ? this._tracking.styles
4177
+ : this._tracking.limitations;
4178
+ bucket.push(entry);
4179
+ const maxEntries = Math.max(10, cfg.maxEntries || 200);
4180
+ if (bucket.length > maxEntries) {
4181
+ bucket.splice(0, bucket.length - maxEntries);
4182
+ }
4183
+ if (cfg.emitDiagnostics) {
4184
+ this.dispatchEvent(new CustomEvent('diagnostic', {
4185
+ detail: entry,
4186
+ bubbles: true,
4187
+ composed: true,
4188
+ }));
4189
+ }
4190
+ }
4191
+ _getKnownLimitationDefinitions() {
4192
+ return [
4193
+ {
4194
+ id: 'variableItemHeight',
4195
+ title: 'Variable item height',
4196
+ description: 'Virtualization assumes fixed or estimated item heights; fully dynamic heights are not yet supported.',
4197
+ workaround: 'Use consistent item heights or set estimatedItemHeight to your dominant row size.',
4198
+ },
4199
+ {
4200
+ id: 'builtInFetchPaginationApi',
4201
+ title: 'Built-in fetch/pagination API',
4202
+ description: 'Core does not include a built-in fetchUrl/searchUrl pagination transport.',
4203
+ workaround: 'Use onSearch/onLoadMore callbacks and update data via setItems().',
4204
+ },
4205
+ {
4206
+ id: 'virtualizationOverheadSmallLists',
4207
+ title: 'Virtualization overhead for small lists',
4208
+ description: 'Virtualization can add slight overhead on very small lists.',
4209
+ workaround: 'Disable virtualization for tiny datasets when micro-latency is critical.',
4210
+ },
4211
+ {
4212
+ id: 'runtimeModeSwitching',
4213
+ title: 'Runtime single/multi mode switching',
4214
+ description: 'Switching between single and multi mode can require state reset for consistency.',
4215
+ workaround: 'Enable autoMitigateRuntimeModeSwitch or recreate/reset component state when toggling modes.',
4216
+ },
4217
+ {
4218
+ id: 'legacyBrowserSupport',
4219
+ title: 'Legacy browser support',
4220
+ description: 'Official support targets modern evergreen browsers.',
4221
+ },
4222
+ {
4223
+ id: 'webkitArchLinux',
4224
+ title: 'Playwright WebKit on Arch-based Linux',
4225
+ description: 'Native WebKit Playwright bundle depends on unavailable legacy system libraries on Arch-based distros.',
4226
+ workaround: 'Run WebKit E2E tests via Playwright Docker image.',
4227
+ },
4228
+ ];
4229
+ }
4230
+ _evaluateLimitationStatus(id) {
4231
+ const policyMode = this._config.limitations?.policies?.[id]?.mode ?? 'default';
4232
+ if (policyMode === 'suppress')
4233
+ return 'suppressed';
4234
+ if (id === 'runtimeModeSwitching' && this._config.limitations?.autoMitigateRuntimeModeSwitch) {
4235
+ return 'mitigated';
4236
+ }
4237
+ return 'active';
4238
+ }
4239
+ getKnownLimitations() {
4240
+ return this._getKnownLimitationDefinitions().map((limitation) => {
4241
+ const mode = this._config.limitations?.policies?.[limitation.id]?.mode ?? 'default';
4242
+ return {
4243
+ ...limitation,
4244
+ mode,
4245
+ status: this._evaluateLimitationStatus(limitation.id),
4246
+ };
4247
+ });
4248
+ }
4249
+ setLimitationPolicies(policies) {
4250
+ const next = {
4251
+ ...(this._config.limitations?.policies || {}),
4252
+ ...policies,
4253
+ };
4254
+ this.updateConfig({
4255
+ limitations: {
4256
+ ...(this._config.limitations || { autoMitigateRuntimeModeSwitch: true, policies: {} }),
4257
+ policies: next,
4258
+ },
4259
+ });
4260
+ this._track('limitation', 'policiesUpdated', { policies: next });
4261
+ }
4262
+ getTrackingSnapshot() {
4263
+ return {
4264
+ events: [...this._tracking.events],
4265
+ styles: [...this._tracking.styles],
4266
+ limitations: [...this._tracking.limitations],
4267
+ };
4268
+ }
4269
+ clearTracking(source) {
4270
+ if (!source || source === 'all') {
4271
+ this._tracking.events = [];
4272
+ this._tracking.styles = [];
4273
+ this._tracking.limitations = [];
4274
+ return;
4275
+ }
4276
+ if (source === 'event')
4277
+ this._tracking.events = [];
4278
+ if (source === 'style')
4279
+ this._tracking.styles = [];
4280
+ if (source === 'limitation')
4281
+ this._tracking.limitations = [];
4282
+ }
4283
+ getCapabilities() {
4284
+ return {
4285
+ styling: {
4286
+ classMap: true,
4287
+ optionRenderer: true,
4288
+ groupHeaderRenderer: true,
4289
+ cssCustomProperties: true,
4290
+ shadowParts: true,
4291
+ globalStyleMirroring: true,
4292
+ },
4293
+ events: {
4294
+ emitted: ['select', 'open', 'close', 'search', 'change', 'loadMore', 'remove', 'clear', 'error', 'diagnostic'],
4295
+ diagnosticEvent: true,
4296
+ },
4297
+ functionality: {
4298
+ multiSelect: true,
4299
+ searchable: true,
4300
+ infiniteScroll: true,
4301
+ loadMore: true,
4302
+ clearControl: true,
4303
+ groupedItems: true,
4304
+ serverSideSelection: true,
4305
+ runtimeModeSwitchMitigation: Boolean(this._config.limitations?.autoMitigateRuntimeModeSwitch),
4306
+ },
4307
+ limitations: this.getKnownLimitations(),
4308
+ };
3679
4309
  }
3680
4310
  _emitChange() {
3681
4311
  const selectedItems = Array.from(this._state.selectedItems.values());
3682
4312
  const getValue = this._config.serverSide.getValueFromItem || ((item) => item?.value ?? item);
3683
4313
  const selectedValues = selectedItems.map(getValue);
3684
4314
  const selectedIndices = Array.from(this._state.selectedIndices);
3685
- // Debug: log change payload
3686
4315
  this._emit('change', { selectedItems, selectedValues, selectedIndices });
3687
4316
  this._config.callbacks.onChange?.(selectedItems, selectedValues);
3688
4317
  }
@@ -3693,6 +4322,7 @@ class EnhancedSelect extends HTMLElement {
3693
4322
  set optionRenderer(renderer) {
3694
4323
  this._optionRenderer = renderer;
3695
4324
  this._setGlobalStylesMirroring(Boolean(renderer || this._classMap));
4325
+ this._track('style', 'optionRendererChanged', { enabled: Boolean(renderer) });
3696
4326
  this._renderOptions();
3697
4327
  }
3698
4328
  /**
@@ -3857,7 +4487,22 @@ class EnhancedSelect extends HTMLElement {
3857
4487
  * Update component configuration
3858
4488
  */
3859
4489
  updateConfig(config) {
3860
- this._config = selectConfig.mergeWithComponentConfig(config);
4490
+ const previousMode = this._config.selection.mode;
4491
+ this._config = this._mergeConfig(this._config, config);
4492
+ if (previousMode !== this._config.selection.mode &&
4493
+ this._config.limitations?.autoMitigateRuntimeModeSwitch) {
4494
+ this.clear();
4495
+ this._track('limitation', 'runtimeModeSwitchMitigated', {
4496
+ from: previousMode,
4497
+ to: this._config.selection.mode,
4498
+ });
4499
+ }
4500
+ else if (previousMode !== this._config.selection.mode) {
4501
+ this._track('limitation', 'runtimeModeSwitchDetected', {
4502
+ from: previousMode,
4503
+ to: this._config.selection.mode,
4504
+ });
4505
+ }
3861
4506
  // Update input state based on new config
3862
4507
  if (this._input) {
3863
4508
  this._input.readOnly = !this._config.searchable;
@@ -3870,7 +4515,7 @@ class EnhancedSelect extends HTMLElement {
3870
4515
  this._clearControl.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
3871
4516
  }
3872
4517
  if (this._clearControlIcon) {
3873
- this._clearControlIcon.textContent = this._config.clearControl.icon || '×';
4518
+ this._clearControlIcon.innerHTML = `<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 4L4 12M4 4L12 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
3874
4519
  }
3875
4520
  if (this._dropdown) {
3876
4521
  if (this._config.selection.mode === 'multi') {
@@ -3880,11 +4525,28 @@ class EnhancedSelect extends HTMLElement {
3880
4525
  this._dropdown.removeAttribute('aria-multiselectable');
3881
4526
  }
3882
4527
  }
4528
+ this._syncInputContainerMode();
3883
4529
  // Re-initialize observers in case infinite scroll was enabled/disabled
3884
4530
  this._initializeObservers();
3885
4531
  this._syncClearControlState();
3886
4532
  this._renderOptions();
3887
4533
  }
4534
+ _mergeConfig(target, source) {
4535
+ const result = { ...target };
4536
+ for (const key in source) {
4537
+ if (!Object.prototype.hasOwnProperty.call(source, key))
4538
+ continue;
4539
+ const sourceValue = source[key];
4540
+ const targetValue = result[key];
4541
+ if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue)) {
4542
+ result[key] = this._mergeConfig(targetValue && typeof targetValue === 'object' ? targetValue : {}, sourceValue);
4543
+ }
4544
+ else {
4545
+ result[key] = sourceValue;
4546
+ }
4547
+ }
4548
+ return result;
4549
+ }
3888
4550
  _handleClearControlClick() {
3889
4551
  const shouldClearSelection = this._config.clearControl.clearSelection !== false;
3890
4552
  const shouldClearSearch = this._config.clearControl.clearSearch !== false;
@@ -3969,6 +4631,8 @@ class EnhancedSelect extends HTMLElement {
3969
4631
  * Render options based on current state
3970
4632
  */
3971
4633
  _renderOptions() {
4634
+ this._renderCycleId += 1;
4635
+ const renderCycleId = this._renderCycleId;
3972
4636
  // Cleanup observer
3973
4637
  if (this._loadMoreTrigger && this._intersectionObserver) {
3974
4638
  this._intersectionObserver.unobserve(this._loadMoreTrigger);
@@ -4033,23 +4697,21 @@ class EnhancedSelect extends HTMLElement {
4033
4697
  }
4034
4698
  else {
4035
4699
  // Normal rendering (flat list or filtered)
4036
- let hasRenderedItems = false;
4700
+ const filteredIndices = [];
4037
4701
  this._state.loadedItems.forEach((item, index) => {
4038
- // Apply filter if query exists
4039
4702
  if (query) {
4040
4703
  try {
4041
4704
  const label = String(getLabel(item)).toLowerCase();
4042
4705
  if (!label.includes(query))
4043
4706
  return;
4044
4707
  }
4045
- catch (e) {
4708
+ catch (_e) {
4046
4709
  return;
4047
4710
  }
4048
4711
  }
4049
- hasRenderedItems = true;
4050
- this._renderSingleOption(item, index, getValue, getLabel);
4712
+ filteredIndices.push(index);
4051
4713
  });
4052
- if (!hasRenderedItems && !this._state.isBusy) {
4714
+ if (filteredIndices.length === 0 && !this._state.isBusy) {
4053
4715
  const empty = document.createElement('div');
4054
4716
  empty.setAttribute('part', 'no-results');
4055
4717
  empty.className = 'empty-state';
@@ -4061,6 +4723,54 @@ class EnhancedSelect extends HTMLElement {
4061
4723
  }
4062
4724
  this._optionsContainer.appendChild(empty);
4063
4725
  }
4726
+ else {
4727
+ const shouldIncrementalRender = this._config.virtualize !== false
4728
+ && this._state.groupedItems.length === 0
4729
+ && filteredIndices.length > 300;
4730
+ if (shouldIncrementalRender) {
4731
+ const chunkSize = 80;
4732
+ let cursor = 0;
4733
+ let maxRenderTarget = 0;
4734
+ if (this._state.selectedIndices.size > 0 && this._config.scrollToSelected.enabled) {
4735
+ const indices = Array.from(this._state.selectedIndices).sort((a, b) => a - b);
4736
+ const targetIndex = this._config.scrollToSelected.multiSelectTarget === 'first' ? indices[0] : indices[indices.length - 1];
4737
+ const filteredPos = filteredIndices.indexOf(targetIndex);
4738
+ if (filteredPos !== -1) {
4739
+ maxRenderTarget = filteredPos + 20; // Ensure we render up to the selection
4740
+ }
4741
+ }
4742
+ const renderChunk = () => {
4743
+ if (renderCycleId !== this._renderCycleId)
4744
+ return;
4745
+ const fragment = document.createDocumentFragment();
4746
+ const chunkEnd = Math.min(Math.max(cursor + chunkSize, maxRenderTarget), filteredIndices.length);
4747
+ maxRenderTarget = 0; // Reset after fast-forwarding
4748
+ for (; cursor < chunkEnd; cursor += 1) {
4749
+ const itemIndex = filteredIndices[cursor];
4750
+ const item = this._state.loadedItems[itemIndex];
4751
+ this._renderSingleOption(item, itemIndex, getValue, getLabel, fragment);
4752
+ }
4753
+ this._optionsContainer.appendChild(fragment);
4754
+ if (cursor < filteredIndices.length) {
4755
+ requestAnimationFrame(renderChunk);
4756
+ }
4757
+ else {
4758
+ if (renderCycleId !== this._renderCycleId)
4759
+ return;
4760
+ if (!this._state.isBusy && (this._config.loadMore.enabled || this._config.infiniteScroll.enabled) && this._state.loadedItems.length > 0) {
4761
+ this._addLoadMoreTrigger();
4762
+ }
4763
+ this._finalizePerfMarks();
4764
+ }
4765
+ };
4766
+ renderChunk();
4767
+ return;
4768
+ }
4769
+ filteredIndices.forEach((itemIndex) => {
4770
+ const item = this._state.loadedItems[itemIndex];
4771
+ this._renderSingleOption(item, itemIndex, getValue, getLabel);
4772
+ });
4773
+ }
4064
4774
  }
4065
4775
  // Append Busy Indicator if busy
4066
4776
  if (this._state.isBusy && this._config.busyBucket.enabled) {
@@ -4085,7 +4795,7 @@ class EnhancedSelect extends HTMLElement {
4085
4795
  }
4086
4796
  this._finalizePerfMarks();
4087
4797
  }
4088
- _renderSingleOption(item, index, getValue, getLabel) {
4798
+ _renderSingleOption(item, index, getValue, getLabel, targetContainer = this._optionsContainer) {
4089
4799
  const isSelected = this._state.selectedIndices.has(index);
4090
4800
  const isDisabled = Boolean(item?.disabled);
4091
4801
  const optionId = `${this._uniqueId}-option-${index}`;
@@ -4101,7 +4811,7 @@ class EnhancedSelect extends HTMLElement {
4101
4811
  disabled: isDisabled,
4102
4812
  id: optionId,
4103
4813
  });
4104
- this._optionsContainer.appendChild(optionElement);
4814
+ targetContainer.appendChild(optionElement);
4105
4815
  return;
4106
4816
  }
4107
4817
  const option = new SelectOption({
@@ -4130,20 +4840,16 @@ class EnhancedSelect extends HTMLElement {
4130
4840
  option.dataset.smValue = String(val);
4131
4841
  }
4132
4842
  option.id = option.id || optionId;
4133
- option.addEventListener('click', (e) => {
4134
- e.stopPropagation(); // Prevent duplicate handling by delegation
4135
- const mouseEvent = e;
4136
- this._selectOption(index, {
4137
- shiftKey: mouseEvent.shiftKey,
4138
- toggleKey: mouseEvent.ctrlKey || mouseEvent.metaKey,
4139
- });
4140
- });
4843
+ // Do NOT bind a native click listener here for SelectOption elements.
4844
+ // Like custom rendered options, they are fully handled by the `handleOptionEvent` delegator
4845
+ // on `this._optionsContainer` (line 1221).
4846
+ // Adding this listener causes the double-click toggle bug since both fire on selection!
4141
4847
  option.addEventListener('optionRemove', (event) => {
4142
4848
  const detail = event.detail;
4143
4849
  const targetIndex = detail?.index ?? index;
4144
4850
  this._handleOptionRemove(targetIndex);
4145
4851
  });
4146
- this._optionsContainer.appendChild(option);
4852
+ targetContainer.appendChild(option);
4147
4853
  }
4148
4854
  _normalizeCustomOptionElement(element, meta) {
4149
4855
  const optionEl = element instanceof HTMLElement ? element : document.createElement('div');
@@ -4230,35 +4936,9 @@ class EnhancedSelect extends HTMLElement {
4230
4936
  optionEl.tabIndex = -1;
4231
4937
  }
4232
4938
  if (!this._customOptionBoundElements.has(optionEl)) {
4233
- optionEl.addEventListener('click', (e) => {
4234
- e.stopPropagation();
4235
- const current = e.currentTarget;
4236
- if (current.getAttribute('aria-disabled') === 'true')
4237
- return;
4238
- const parsedIndex = Number(current.dataset.index);
4239
- if (!Number.isFinite(parsedIndex))
4240
- return;
4241
- const mouseEvent = e;
4242
- this._selectOption(parsedIndex, {
4243
- shiftKey: mouseEvent.shiftKey,
4244
- toggleKey: mouseEvent.ctrlKey || mouseEvent.metaKey,
4245
- });
4246
- });
4247
- optionEl.addEventListener('keydown', (e) => {
4248
- if (e.key !== 'Enter' && e.key !== ' ')
4249
- return;
4250
- const current = e.currentTarget;
4251
- if (current.getAttribute('aria-disabled') === 'true')
4252
- return;
4253
- const parsedIndex = Number(current.dataset.index);
4254
- if (!Number.isFinite(parsedIndex))
4255
- return;
4256
- e.preventDefault();
4257
- this._selectOption(parsedIndex, {
4258
- shiftKey: e.shiftKey,
4259
- toggleKey: e.ctrlKey || e.metaKey,
4260
- });
4261
- });
4939
+ // Intentionally NOT binding native option click listeners for custom options!
4940
+ // All option interactions are globally handled by _optionsContainer.addEventListener('click', handleOptionEvent);
4941
+ // Re-attaching here causes the double-click toggle bug if a child component fails to stopPropagation.
4262
4942
  this._customOptionBoundElements.add(optionEl);
4263
4943
  }
4264
4944
  return optionEl;