@smilodon/core 1.4.12 → 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,
@@ -1883,6 +1884,8 @@ if (!customElements.get('select-option')) {
1883
1884
  * Enhanced Select Component
1884
1885
  * Implements all advanced features: infinite scroll, load more, busy state,
1885
1886
  * server-side selection, and full customization
1887
+ *
1888
+ * ✨ Redesigned with formal elegance, refined microinteractions, and polished UX
1886
1889
  */
1887
1890
  class EnhancedSelect extends HTMLElement {
1888
1891
  get classMap() {
@@ -1930,6 +1933,8 @@ class EnhancedSelect extends HTMLElement {
1930
1933
  this._globalStylesObserver = null;
1931
1934
  this._globalStylesContainer = null;
1932
1935
  this._tracking = { events: [], styles: [], limitations: [] };
1936
+ this._suppressBlurClose = false;
1937
+ this._renderCycleId = 0;
1933
1938
  this._shadow = this.attachShadow({ mode: 'open' });
1934
1939
  this._uniqueId = `enhanced-select-${Math.random().toString(36).substr(2, 9)}`;
1935
1940
  this._rendererHelpers = this._buildRendererHelpers();
@@ -2004,6 +2009,7 @@ class EnhancedSelect extends HTMLElement {
2004
2009
  if (this._boundArrowClick && this._arrowContainer) {
2005
2010
  this._arrowContainer.removeEventListener('click', this._boundArrowClick);
2006
2011
  }
2012
+ this._renderCycleId += 1;
2007
2013
  this._teardownGlobalStylesMirroring();
2008
2014
  }
2009
2015
  _setGlobalStylesMirroring(enabled) {
@@ -2107,8 +2113,48 @@ class EnhancedSelect extends HTMLElement {
2107
2113
  const container = document.createElement('div');
2108
2114
  container.className = 'input-container';
2109
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
+ }
2110
2139
  return container;
2111
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
+ }
2112
2158
  _createInput() {
2113
2159
  const input = document.createElement('input');
2114
2160
  input.setAttribute('part', 'input');
@@ -2118,6 +2164,35 @@ class EnhancedSelect extends HTMLElement {
2118
2164
  input.placeholder = this._config.placeholder || 'Select an option...';
2119
2165
  input.disabled = !this._config.enabled;
2120
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';
2121
2196
  // Update readonly when input is focused if searchable
2122
2197
  input.addEventListener('focus', () => {
2123
2198
  if (this._config.searchable) {
@@ -2128,7 +2203,22 @@ class EnhancedSelect extends HTMLElement {
2128
2203
  input.className += ' ' + this._config.styles.classNames.input;
2129
2204
  }
2130
2205
  if (this._config.styles.input) {
2131
- 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);
2132
2222
  }
2133
2223
  input.setAttribute('role', 'combobox');
2134
2224
  input.setAttribute('aria-expanded', 'false');
@@ -2172,7 +2262,7 @@ class EnhancedSelect extends HTMLElement {
2172
2262
  container.className = 'dropdown-arrow-container';
2173
2263
  container.innerHTML = `
2174
2264
  <svg class="dropdown-arrow" part="arrow" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2175
- <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"/>
2176
2266
  </svg>
2177
2267
  `;
2178
2268
  return container;
@@ -2185,7 +2275,7 @@ class EnhancedSelect extends HTMLElement {
2185
2275
  const icon = document.createElement('span');
2186
2276
  icon.className = 'clear-control-icon';
2187
2277
  icon.setAttribute('part', 'clear-icon');
2188
- 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>`;
2189
2279
  button.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
2190
2280
  button.appendChild(icon);
2191
2281
  this._clearControlIcon = icon;
@@ -2211,18 +2301,229 @@ class EnhancedSelect extends HTMLElement {
2211
2301
  this._dropdown.id = listboxId;
2212
2302
  this._input.setAttribute('aria-controls', listboxId);
2213
2303
  this._input.setAttribute('aria-owns', listboxId);
2304
+ this._syncInputContainerMode();
2214
2305
  this._syncClearControlState();
2215
2306
  }
2216
2307
  _initializeStyles() {
2217
2308
  const style = document.createElement('style');
2218
2309
  style.textContent = `
2310
+ /* ═══════════════════════════════════════════════════════════════════════════
2311
+ ELEGANT SELECT COMPONENT — Refined Design System
2312
+ Formal aesthetics with sophisticated microinteractions
2313
+ ═══════════════════════════════════════════════════════════════════════════ */
2314
+
2219
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
+
2220
2423
  display: block;
2221
2424
  position: relative;
2222
2425
  width: var(--select-width, 100%);
2223
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
+ }
2224
2471
  }
2225
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
+
2226
2527
  .select-container {
2227
2528
  position: relative;
2228
2529
  width: 100%;
@@ -2233,102 +2534,143 @@ class EnhancedSelect extends HTMLElement {
2233
2534
  width: 100%;
2234
2535
  display: flex;
2235
2536
  align-items: center;
2236
- flex-wrap: wrap;
2537
+ flex-wrap: nowrap;
2237
2538
  gap: var(--select-input-gap, 6px);
2238
- padding: var(--select-input-padding, 6px 52px 6px 8px);
2539
+ padding: var(--select-input-padding, 10px 52px 10px 14px);
2239
2540
  height: var(--select-input-height, auto);
2240
- min-height: var(--select-input-min-height, 44px);
2541
+ min-height: var(--select-input-min-height, 48px);
2241
2542
  max-height: var(--select-input-max-height, 160px);
2242
2543
  overflow-y: var(--select-input-overflow-y, auto);
2243
- align-content: flex-start;
2244
- background: var(--select-input-bg, var(--select-bg, white));
2245
- border: var(--select-input-border, 1px solid var(--select-border-color, #d1d5db));
2246
- 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);
2247
2549
  box-sizing: border-box;
2248
- 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;
2249
2569
  }
2250
2570
 
2251
2571
  .input-container:focus-within {
2252
- border-color: var(--select-input-focus-border, var(--select-border-focus-color, #667eea));
2253
- 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);
2254
2574
  }
2255
2575
 
2256
- /* Gradient separator before arrow */
2576
+ /* Elegant separator line before arrow */
2257
2577
  .input-container::after {
2258
2578
  content: '';
2259
2579
  position: absolute;
2260
2580
  top: 50%;
2261
- right: var(--select-separator-position, var(--select-seperator-position, 40px));
2581
+ right: var(--select-separator-position, 42px);
2262
2582
  transform: translateY(-50%);
2263
- width: var(--select-separator-width, var(--select-seperator-width, 1px));
2264
- height: var(--select-separator-height, var(--select-seperator-height, 60%));
2265
- 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(
2266
2586
  to bottom,
2267
2587
  transparent 0%,
2268
- rgba(0, 0, 0, 0.1) 20%,
2269
- rgba(0, 0, 0, 0.1) 80%,
2588
+ var(--select-border) 20%,
2589
+ var(--select-border) 80%,
2270
2590
  transparent 100%
2271
- ))));
2591
+ ));
2272
2592
  pointer-events: none;
2273
2593
  z-index: 1;
2594
+ opacity: var(--select-separator-opacity);
2595
+ transition: opacity var(--select-transition-fast);
2274
2596
  }
2275
2597
 
2598
+ .input-container:hover::after,
2599
+ .input-container:focus-within::after {
2600
+ opacity: var(--select-separator-active-opacity);
2601
+ }
2602
+
2603
+ /* ─────────────────────────────────────────────────────────────────────────
2604
+ Dropdown Arrow — Smooth rotation with refined styling
2605
+ ───────────────────────────────────────────────────────────────────────── */
2606
+
2276
2607
  .dropdown-arrow-container {
2277
2608
  position: absolute;
2278
2609
  top: 0;
2279
2610
  right: 0;
2280
2611
  bottom: 0;
2281
- width: var(--select-arrow-width, 40px);
2282
- /* allow explicit height override even though container normally stretches */
2612
+ width: var(--select-arrow-width, 42px);
2283
2613
  height: var(--select-arrow-height, auto);
2284
2614
  display: flex;
2285
2615
  align-items: center;
2286
2616
  justify-content: center;
2287
2617
  cursor: pointer;
2288
- transition: background-color 0.2s ease;
2289
- 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);
2290
2619
  z-index: 2;
2620
+ transition: background-color var(--select-transition-fast);
2291
2621
  }
2292
2622
 
2293
2623
  .input-container.has-clear-control {
2294
- padding: var(--select-input-padding-with-clear, 6px 84px 6px 8px);
2624
+ padding: var(--select-input-padding-with-clear, 10px 84px 10px 14px);
2295
2625
  }
2296
2626
 
2297
2627
  .input-container.has-clear-control::after {
2298
- right: var(--select-separator-position-with-clear, var(--select-seperator-position-with-clear, 72px));
2628
+ right: var(--select-separator-position-with-clear, 74px);
2299
2629
  }
2300
2630
 
2301
2631
  .dropdown-arrow-container.with-clear-control {
2302
- right: var(--select-arrow-right-with-clear, 32px);
2632
+ right: var(--select-arrow-right-with-clear, 34px);
2303
2633
  }
2304
2634
 
2635
+ /* ─────────────────────────────────────────────────────────────────────────
2636
+ Clear Control — Minimal and elegant dismiss button
2637
+ ───────────────────────────────────────────────────────────────────────── */
2638
+
2305
2639
  .clear-control-button {
2306
2640
  position: absolute;
2307
2641
  top: 50%;
2308
- right: var(--select-clear-button-right, 6px);
2642
+ right: var(--select-clear-button-right, 8px);
2309
2643
  transform: translateY(-50%);
2310
- width: var(--select-clear-button-size, 24px);
2311
- height: var(--select-clear-button-size, 24px);
2644
+ width: var(--select-clear-button-size, 26px);
2645
+ height: var(--select-clear-button-size, 26px);
2312
2646
  border: var(--select-clear-button-border, none);
2313
- border-radius: var(--select-clear-button-radius, 999px);
2647
+ border-radius: var(--select-clear-button-radius, 50%);
2314
2648
  background: var(--select-clear-button-bg, transparent);
2315
- color: var(--select-clear-button-color, #6b7280);
2649
+ color: var(--select-clear-button-color, var(--select-text-muted));
2316
2650
  display: inline-flex;
2317
2651
  align-items: center;
2318
2652
  justify-content: center;
2319
2653
  cursor: pointer;
2320
2654
  z-index: 3;
2321
- 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);
2322
2659
  }
2323
2660
 
2324
2661
  .clear-control-button:hover {
2325
- background: var(--select-clear-button-hover-bg, rgba(0, 0, 0, 0.06));
2326
- 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);
2327
2669
  }
2328
2670
 
2329
2671
  .clear-control-button:focus-visible {
2330
- outline: var(--select-clear-button-focus-outline, 2px solid rgba(102, 126, 234, 0.55));
2331
- 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);
2332
2674
  }
2333
2675
 
2334
2676
  .clear-control-button[hidden] {
@@ -2336,260 +2678,332 @@ class EnhancedSelect extends HTMLElement {
2336
2678
  }
2337
2679
 
2338
2680
  .clear-control-icon {
2339
- font-size: var(--select-clear-icon-size, 16px);
2340
- line-height: 1;
2341
- font-weight: var(--select-clear-icon-weight, 500);
2681
+ width: var(--select-clear-icon-size);
2682
+ height: var(--select-clear-icon-size);
2342
2683
  pointer-events: none;
2343
2684
  }
2344
2685
 
2345
- .dropdown-arrow-container:hover {
2346
- background-color: var(--select-arrow-hover-bg, rgba(102, 126, 234, 0.08));
2686
+ .clear-control-icon svg {
2687
+ width: 100%;
2688
+ height: 100%;
2347
2689
  }
2348
2690
 
2349
- .dropdown-arrow:hover {
2350
- /* legacy alias --select-arrow-hover for icon color */
2351
- 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));
2352
2693
  }
2353
2694
 
2354
2695
  .dropdown-arrow {
2355
- width: var(--select-arrow-width, var(--select-arrow-size, 16px));
2356
- height: var(--select-arrow-height, var(--select-arrow-size, 16px));
2357
- color: var(--select-arrow-color, #667eea);
2358
- transition: transform 0.2s ease, color 0.2s ease;
2359
- 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;
2360
2703
  }
2361
2704
 
2362
2705
  .dropdown-arrow path {
2363
- stroke-width: var(--select-arrow-stroke-width, 2);
2706
+ stroke-width: var(--select-arrow-stroke-width, 1.5);
2364
2707
  }
2365
2708
 
2366
2709
  .dropdown-arrow-container:hover .dropdown-arrow {
2367
- color: var(--select-arrow-hover-color, #667eea);
2710
+ color: var(--select-arrow-hover-color, var(--select-accent));
2368
2711
  }
2369
2712
 
2370
2713
  .dropdown-arrow.open {
2371
- transform: rotate(180deg);
2714
+ transform: var(--select-arrow-open-transform);
2372
2715
  }
2373
2716
 
2374
- .select-input {
2375
- flex: 1;
2376
- width: var(--select-input-width, auto);
2377
- min-width: var(--select-input-min-width, 120px);
2378
- padding: var(--select-input-field-padding, 4px);
2379
- border: none;
2380
- 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);
2381
2735
  line-height: var(--select-input-line-height, 1.5);
2382
- color: var(--select-input-color, var(--select-text-color, #1f2937));
2383
- background: transparent;
2736
+ color: var(--select-input-color, var(--select-text));
2737
+ background: transparent !important;
2384
2738
  box-sizing: border-box;
2385
- outline: none;
2386
- font-family: var(--select-font-family, inherit);
2387
- }
2388
-
2389
- .select-input::placeholder {
2390
- color: var(--select-input-placeholder-color, var(--select-placeholder-color, #9ca3af));
2391
- }
2392
-
2393
- .selection-badge {
2394
- display: inline-flex;
2395
- align-items: center;
2396
- gap: var(--select-badge-gap, 4px);
2397
- padding: var(--select-badge-padding, 4px 8px);
2398
- margin: var(--select-badge-margin, 2px);
2399
- background: var(--select-badge-bg, #667eea);
2400
- color: var(--select-badge-color, white);
2401
- border-radius: var(--select-badge-border-radius, 4px);
2402
- font-size: var(--select-badge-font-size, 13px);
2403
- line-height: 1;
2404
- max-width: var(--select-badge-max-width, 100%);
2405
- 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;
2406
2747
  overflow: hidden;
2407
2748
  text-overflow: ellipsis;
2749
+ white-space: nowrap;
2408
2750
  }
2409
2751
 
2410
- .badge-remove {
2411
- display: inline-flex;
2412
- align-items: center;
2413
- justify-content: center;
2414
- width: var(--select-badge-remove-size, 16px);
2415
- height: var(--select-badge-remove-size, 16px);
2416
- padding: 0;
2417
- margin-left: 4px;
2418
- background: var(--select-badge-remove-bg, rgba(255, 255, 255, 0.3));
2419
- border: none;
2420
- border-radius: 50%;
2421
- color: var(--select-badge-remove-color, white);
2422
- font-size: var(--select-badge-remove-font-size, 16px);
2423
- line-height: 1;
2424
- cursor: pointer;
2425
- transition: background 0.2s;
2752
+ .select-input::placeholder {
2753
+ color: var(--select-input-placeholder-color, var(--select-text-placeholder));
2754
+ font-weight: 400;
2426
2755
  }
2427
-
2428
- .badge-remove:hover {
2429
- 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);
2430
2763
  }
2431
2764
 
2432
- .badge-remove:focus-visible {
2433
- outline: 2px solid var(--select-badge-remove-focus-outline, rgba(255, 255, 255, 0.8));
2434
- 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;
2435
2771
  }
2436
2772
 
2437
2773
  .select-input:disabled {
2438
2774
  background-color: var(--select-disabled-bg, #f5f5f5);
2439
2775
  cursor: not-allowed;
2776
+ opacity: var(--select-input-disabled-opacity);
2440
2777
  }
2441
2778
 
2779
+ /* ─────────────────────────────────────────────────────────────────────────
2780
+ Dropdown Panel — Elegant floating container
2781
+ ───────────────────────────────────────────────────────────────────────── */
2782
+
2442
2783
  .select-dropdown {
2443
2784
  position: absolute;
2444
- scroll-behavior: smooth;
2445
- top: 100%;
2785
+ scroll-behavior: var(--select-dropdown-scroll-behavior);
2786
+ top: var(--select-dropdown-top);
2446
2787
  left: 0;
2447
2788
  right: 0;
2448
- margin-top: var(--select-dropdown-margin-top, 4px);
2449
- max-height: var(--select-dropdown-max-height, 300px);
2789
+ max-height: var(--select-dropdown-max-height, 320px);
2450
2790
  overflow: hidden;
2451
- background: var(--select-dropdown-bg, var(--select-bg, white));
2452
- border: 1px solid var(--select-dropdown-border, #ccc);
2453
- border-radius: var(--select-dropdown-border-radius, 4px);
2454
- 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));
2455
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
+ }
2456
2809
  }
2457
2810
 
2458
2811
  .options-container {
2459
2812
  position: relative;
2460
- max-height: var(--select-options-max-height, 300px);
2813
+ max-height: var(--select-options-max-height, 320px);
2461
2814
  overflow: auto;
2462
- transition: opacity 0.2s ease-in-out;
2463
- 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);
2464
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
+ ───────────────────────────────────────────────────────────────────────── */
2465
2843
 
2466
2844
  .group-header {
2467
- padding: var(--select-group-header-padding, 8px 12px);
2845
+ padding: var(--select-group-header-padding, 10px 12px 6px);
2468
2846
  font-weight: var(--select-group-header-weight, 600);
2469
- color: var(--select-group-header-color, #6b7280);
2470
- 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));
2471
2849
  text-align: var(--select-group-header-text-align, left);
2472
- font-size: var(--select-group-header-font-size, 12px);
2850
+ font-size: var(--select-group-header-font-size, 11px);
2473
2851
  text-transform: var(--select-group-header-text-transform, uppercase);
2474
- letter-spacing: var(--select-group-header-letter-spacing, 0.05em);
2852
+ letter-spacing: var(--select-group-header-letter-spacing, 0.08em);
2475
2853
  position: sticky;
2476
2854
  top: 0;
2477
2855
  z-index: 1;
2478
- 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);
2479
2863
  }
2480
2864
 
2865
+ /* ─────────────────────────────────────────────────────────────────────────
2866
+ Options — Elegant hover and selection states
2867
+ ───────────────────────────────────────────────────────────────────────── */
2868
+
2481
2869
  .option {
2482
- padding: var(--select-option-padding, 8px 12px);
2870
+ position: relative;
2871
+ padding: var(--select-option-padding, 10px 14px);
2483
2872
  cursor: pointer;
2484
- color: var(--select-option-color, var(--select-text-color, #1f2937));
2485
- background: var(--select-option-bg, var(--select-dropdown-bg, var(--select-bg, white)));
2486
- 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);
2487
2880
  user-select: none;
2488
2881
  font-size: var(--select-option-font-size, 14px);
2882
+ font-weight: var(--select-option-font-weight);
2489
2883
  line-height: var(--select-option-line-height, 1.5);
2490
- border: var(--select-option-border, none);
2491
- border-bottom: var(--select-option-border-bottom, none);
2492
- border-radius: var(--select-option-border-radius, 0);
2493
- box-shadow: var(--select-option-shadow, none);
2494
- transform: var(--select-option-transform, none);
2884
+ border-radius: var(--select-option-border-radius, var(--select-radius-sm));
2885
+ margin: var(--select-option-margin);
2495
2886
  }
2496
2887
 
2497
2888
  .option:hover {
2498
- background: var(--select-option-hover-bg, #f3f4f6);
2499
- 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);
2500
2892
  }
2501
2893
 
2502
2894
  .option.selected {
2503
- background: var(--select-option-selected-bg, #e0e7ff);
2504
- color: var(--select-option-selected-color, #4338ca);
2505
- font-weight: var(--select-option-selected-weight, 500);
2506
- border: var(--select-option-selected-border, var(--select-option-border, none));
2507
- border-bottom: var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none));
2508
- border-radius: var(--select-option-selected-border-radius, var(--select-option-border-radius, 0));
2509
- box-shadow: var(--select-option-selected-shadow, var(--select-option-shadow, none));
2510
- 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
+ }
2511
2922
  }
2512
2923
 
2513
2924
  .option.selected:hover {
2514
- background: var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e0e7ff));
2515
- color: var(--select-option-selected-hover-color, var(--select-option-selected-color, #4338ca));
2516
- border: var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)));
2517
- border-bottom: var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2518
- box-shadow: var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2519
- 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%));
2520
2926
  }
2521
2927
 
2522
2928
  .option.active:not(.selected) {
2523
- background: var(--select-option-active-bg, #f3f4f6);
2524
- color: var(--select-option-active-color, #1f2937);
2525
- outline: var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45));
2526
- 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);
2527
2932
  }
2528
2933
 
2529
2934
  .option.selected.active {
2530
- background: var(--select-option-selected-active-bg, var(--select-option-selected-bg, #e0e7ff));
2531
- color: var(--select-option-selected-active-color, var(--select-option-selected-color, #4338ca));
2532
- border: var(--select-option-selected-active-border, var(--select-option-selected-border, var(--select-option-border, none)));
2533
- border-bottom: var(--select-option-selected-active-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2534
- box-shadow: var(--select-option-selected-active-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2535
- transform: var(--select-option-selected-active-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2536
- outline: var(--select-option-selected-active-outline, var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45)));
2537
- 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);
2538
2937
  }
2539
2938
 
2540
2939
  .option:active:not(.selected) {
2541
- 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);
2542
2942
  }
2543
2943
 
2544
2944
  .option.selected:active {
2545
- 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);
2546
2946
  }
2547
2947
 
2948
+ /* ─────────────────────────────────────────────────────────────────────────
2949
+ Load More & Busy States — Refined feedback indicators
2950
+ ───────────────────────────────────────────────────────────────────────── */
2951
+
2548
2952
  .load-more-container {
2549
2953
  padding: var(--select-load-more-padding, 12px);
2550
2954
  text-align: center;
2551
- border-top: var(--select-divider-border, 1px solid #e0e0e0);
2552
- background: var(--select-load-more-bg, white);
2553
2955
  }
2554
2956
 
2555
2957
  .load-more-button {
2556
- padding: var(--select-button-padding, 8px 16px);
2557
- border: var(--select-button-border, 1px solid #1976d2);
2558
- background: var(--select-button-bg, white);
2559
- color: var(--select-button-color, #1976d2);
2560
- 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));
2561
2963
  cursor: pointer;
2562
- font-size: var(--select-button-font-size, 14px);
2563
- font-family: var(--select-font-family, inherit);
2564
- 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);
2565
2972
  }
2566
2973
 
2567
2974
  .load-more-button:hover {
2568
- background: var(--select-button-hover-bg, #1976d2);
2975
+ background: var(--select-button-hover-bg, var(--select-accent));
2976
+ border-color: var(--select-accent);
2569
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);
2570
2983
  }
2571
2984
 
2572
2985
  .load-more-button:disabled {
2573
2986
  opacity: var(--select-button-disabled-opacity, 0.5);
2574
2987
  cursor: not-allowed;
2988
+ transform: none;
2575
2989
  }
2576
2990
 
2577
2991
  .busy-bucket {
2578
- padding: var(--select-busy-padding, 16px);
2992
+ padding: var(--select-busy-padding, 20px);
2579
2993
  text-align: center;
2580
- color: var(--select-busy-color, #666);
2581
- background: var(--select-busy-bg, white);
2582
- 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);
2583
2997
  }
2584
2998
 
2585
2999
  .spinner {
2586
3000
  display: inline-block;
2587
- width: var(--select-spinner-size, 20px);
2588
- height: var(--select-spinner-size, 20px);
2589
- border: var(--select-spinner-border, 2px solid #ccc);
2590
- 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));
2591
3005
  border-radius: 50%;
2592
- animation: spin 0.6s linear infinite;
3006
+ animation: var(--select-spinner-animation);
2593
3007
  }
2594
3008
 
2595
3009
  @keyframes spin {
@@ -2597,61 +3011,97 @@ class EnhancedSelect extends HTMLElement {
2597
3011
  }
2598
3012
 
2599
3013
  .empty-state {
2600
- padding: var(--select-empty-padding, 24px);
3014
+ padding: var(--select-empty-padding, 32px 24px);
2601
3015
  text-align: center;
2602
- color: var(--select-empty-color, #6b7280);
3016
+ color: var(--select-empty-color, var(--select-text-muted));
2603
3017
  font-size: var(--select-empty-font-size, 14px);
2604
- background: var(--select-empty-bg, white);
3018
+ background: var(--select-empty-bg, transparent);
2605
3019
  display: flex;
2606
3020
  flex-direction: column;
2607
3021
  align-items: center;
2608
3022
  justify-content: center;
2609
- gap: 6px;
2610
- min-height: var(--select-empty-min-height, 72px);
3023
+ gap: var(--select-empty-gap);
2611
3024
  }
2612
3025
 
2613
3026
  .searching-state {
2614
- padding: var(--select-searching-padding, 24px);
3027
+ padding: var(--select-searching-padding, 32px 24px);
2615
3028
  text-align: center;
2616
- color: var(--select-searching-color, #667eea);
3029
+ color: var(--select-searching-color, var(--select-accent));
2617
3030
  font-size: var(--select-searching-font-size, 14px);
2618
- font-style: italic;
2619
- background: var(--select-searching-bg, white);
2620
- animation: pulse 1.5s ease-in-out infinite;
3031
+ background: var(--select-searching-bg, transparent);
2621
3032
  display: flex;
2622
3033
  flex-direction: column;
2623
3034
  align-items: center;
2624
3035
  justify-content: center;
2625
- gap: 6px;
2626
- min-height: var(--select-searching-min-height, 72px);
3036
+ gap: var(--select-searching-gap);
2627
3037
  }
2628
3038
 
2629
- @keyframes pulse {
2630
- 0%, 100% { opacity: 1; }
2631
- 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);
2632
3047
  }
2633
3048
 
2634
- /* Error states */
3049
+ /* ─────────────────────────────────────────────────────────────────────────
3050
+ Error States — Clear visual feedback
3051
+ ───────────────────────────────────────────────────────────────────────── */
3052
+
2635
3053
  .select-input[aria-invalid="true"] {
2636
- border-color: var(--select-error-border, #dc2626);
3054
+ border-color: var(--select-error-border, #e94560);
2637
3055
  }
2638
3056
 
2639
3057
  .select-input[aria-invalid="true"]:focus {
2640
- border-color: var(--select-error-border, #dc2626);
2641
- box-shadow: 0 0 0 2px var(--select-error-shadow, rgba(220, 38, 38, 0.1));
2642
- 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));
2643
3060
  }
2644
3061
 
2645
- /* Accessibility: Reduced motion */
3062
+ /* ──────────────────────────────────────────────────��──────────────────────
3063
+ Accessibility — Reduced motion & High contrast
3064
+ ───────────────────────────────────────────────────────────────────────── */
3065
+
2646
3066
  @media (prefers-reduced-motion: reduce) {
2647
- * {
2648
- animation-duration: 0.01ms !important;
2649
- animation-iteration-count: 1 !important;
2650
- 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);
2651
3077
  }
2652
3078
  }
2653
3079
 
2654
- /* 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
+
2655
3105
  :host(.dark-mode),
2656
3106
  :host([dark-mode]),
2657
3107
  :host([darkmode]),
@@ -2663,139 +3113,129 @@ class EnhancedSelect extends HTMLElement {
2663
3113
  :host-context([darkmode]),
2664
3114
  :host-context([data-theme="dark"]),
2665
3115
  :host-context([theme="dark"]) {
2666
- /* map dark tokens to base option tokens so nested <select-option>
2667
- components also pick up dark mode via inherited CSS variables */
2668
- --select-option-bg: var(--select-dark-option-bg, #1f2937);
2669
- --select-option-color: var(--select-dark-option-color, #f9fafb);
2670
- --select-option-hover-bg: var(--select-dark-option-hover-bg, #374151);
2671
- --select-option-hover-color: var(--select-dark-option-hover-color, #f9fafb);
2672
- --select-option-selected-bg: var(--select-dark-option-selected-bg, #3730a3);
2673
- --select-option-selected-color: var(--select-dark-option-selected-text, #e0e7ff);
2674
-
2675
- .input-container {
2676
- background: var(--select-dark-bg, #1f2937);
2677
- border-color: var(--select-dark-border, #4b5563);
2678
- }
2679
-
2680
- .select-input {
2681
- color: var(--select-dark-text, #f9fafb);
2682
- }
2683
-
2684
- .select-input::placeholder {
2685
- color: var(--select-dark-placeholder, #6b7280);
2686
- }
2687
-
2688
- .select-dropdown {
2689
- background: var(--select-dark-dropdown-bg, #1f2937);
2690
- border-color: var(--select-dark-dropdown-border, #4b5563);
2691
- }
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);
2692
3131
 
2693
- .options-container {
2694
- background: var(--select-dark-options-bg, #1f2937);
2695
- }
2696
-
2697
- .option {
2698
- color: var(--select-dark-option-color, #f9fafb);
2699
- background: var(--select-dark-option-bg, #1f2937);
2700
- }
2701
-
2702
- .option:hover {
2703
- background: var(--select-dark-option-hover-bg, #374151);
2704
- color: var(--select-dark-option-hover-color, #f9fafb);
2705
- }
2706
-
2707
- .option.selected {
2708
- background: var(--select-dark-option-selected-bg, #3730a3);
2709
- color: var(--select-dark-option-selected-text, #e0e7ff);
2710
- border: var(--select-dark-option-selected-border, var(--select-option-selected-border, var(--select-option-border, none)));
2711
- border-bottom: var(--select-dark-option-selected-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2712
- box-shadow: var(--select-dark-option-selected-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2713
- transform: var(--select-dark-option-selected-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2714
- }
2715
-
2716
- .option.selected:hover {
2717
- background: var(--select-dark-option-selected-hover-bg, var(--select-dark-option-selected-bg, #3730a3));
2718
- color: var(--select-dark-option-selected-hover-color, var(--select-dark-option-selected-text, #e0e7ff));
2719
- 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)))));
2720
- 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)))));
2721
- 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)))));
2722
- 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)))));
2723
- }
2724
-
2725
- .option.active:not(.selected) {
2726
- background-color: var(--select-dark-option-active-bg, #374151);
2727
- color: var(--select-dark-option-active-color, #f9fafb);
2728
- outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
2729
- }
2730
-
2731
- /* Group header in dark mode */
2732
- .group-header {
2733
- color: var(--select-dark-group-header-color, var(--select-group-header-color, #6b7280));
2734
- background-color: var(--select-dark-group-header-bg, var(--select-group-header-bg, #374151));
2735
- }
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
+ }
2736
3139
 
2737
- .option.selected.active {
2738
- background-color: var(--select-dark-option-selected-active-bg, var(--select-dark-option-selected-bg, #3730a3));
2739
- color: var(--select-dark-option-selected-active-color, var(--select-dark-option-selected-text, #e0e7ff));
2740
- 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)))));
2741
- 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)))));
2742
- 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)))));
2743
- 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)))));
2744
- 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)))));
2745
- outline-offset: -2px;
2746
- }
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
+ }
2747
3154
 
2748
- .selection-badge {
2749
- background: var(--select-dark-badge-bg, #4f46e5);
2750
- color: var(--select-dark-badge-color, #eef2ff);
2751
- }
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
+ }
2752
3168
 
2753
- .badge-remove {
2754
- background: var(--select-dark-badge-remove-bg, rgba(255, 255, 255, 0.15));
2755
- color: var(--select-dark-badge-remove-color, #e5e7eb);
2756
- }
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
+ }
2757
3183
 
2758
- .badge-remove:hover {
2759
- background: var(--select-dark-badge-remove-hover-bg, rgba(255, 255, 255, 0.3));
2760
- }
2761
-
2762
- .busy-bucket,
2763
- .empty-state {
2764
- color: var(--select-dark-busy-color, #9ca3af);
2765
- background: var(--select-dark-empty-bg, #111827);
2766
- }
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
+ }
2767
3198
 
2768
- .searching-state {
2769
- background: var(--select-dark-searching-bg, #111827);
2770
- }
2771
-
2772
- .input-container::after {
2773
- background: linear-gradient(
2774
- to bottom,
2775
- transparent 0%,
2776
- rgba(255, 255, 255, 0.1) 20%,
2777
- rgba(255, 255, 255, 0.1) 80%,
2778
- transparent 100%
2779
- );
2780
- }
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);
2781
3211
  }
2782
-
2783
- /* Accessibility: High contrast mode */
2784
- @media (prefers-contrast: high) {
2785
- .select-input:focus {
2786
- outline-width: 3px;
2787
- outline-color: Highlight;
2788
- }
2789
-
2790
- .select-input {
2791
- border-width: 2px;
2792
- }
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);
2793
3225
  }
2794
-
2795
- /* Touch targets (WCAG 2.5.5) */
2796
- .load-more-button,
2797
- select-option {
2798
- 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);
2799
3239
  }
2800
3240
  `;
2801
3241
  // Insert as first child to ensure styles are processed first
@@ -2815,10 +3255,10 @@ class EnhancedSelect extends HTMLElement {
2815
3255
  // delegate to the existing open/close helpers so we don't accidentally
2816
3256
  // drift out of sync with the logic in those methods (focus, events,
2817
3257
  // scroll-to-selected, etc.)
2818
- if (this._state.isOpen) {
3258
+ if (this._state.isOpen && this._config.selection.toggleOnTriggerClick !== false) {
2819
3259
  this._handleClose();
2820
3260
  }
2821
- else {
3261
+ else if (!this._state.isOpen) {
2822
3262
  this._handleOpen();
2823
3263
  }
2824
3264
  };
@@ -2841,7 +3281,7 @@ class EnhancedSelect extends HTMLElement {
2841
3281
  this._inputContainer.addEventListener('pointerdown', (e) => {
2842
3282
  // Prevent propagation to document click listener but do NOT preventDefault.
2843
3283
  // Allow default so browser events (click) on newly opened options still fire.
2844
- 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.
2845
3285
  const target = e.target;
2846
3286
  if (!this._config.enabled)
2847
3287
  return;
@@ -2849,21 +3289,23 @@ class EnhancedSelect extends HTMLElement {
2849
3289
  return;
2850
3290
  if (target && target.closest('.clear-control-button'))
2851
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
+ }
2852
3304
  const wasClosed = !this._state.isOpen;
2853
3305
  if (wasClosed) {
2854
3306
  this._handleOpen();
2855
3307
  }
2856
- else {
2857
- // Keep open while interacting directly with the input so users can
2858
- // place cursor/type without accidental collapse.
2859
- if (target === this._input) {
2860
- this._input.focus();
2861
- return;
2862
- }
2863
- // clicking other parts of the input container while open toggles close
2864
- this._handleClose();
2865
- }
2866
- // Focus the input (do not prevent default behavior)
3308
+ // Focus the input (do not prevent default behavior for input itself)
2867
3309
  this._input.focus();
2868
3310
  // If we just opened the dropdown, transfer pointer capture to the
2869
3311
  // options container so the subsequent pointerup lands there instead of
@@ -2905,7 +3347,7 @@ class EnhancedSelect extends HTMLElement {
2905
3347
  return;
2906
3348
  }
2907
3349
  this._handleClose();
2908
- }, 0);
3350
+ }, 150);
2909
3351
  });
2910
3352
  // Input search
2911
3353
  this._input.addEventListener('input', (e) => {
@@ -2922,7 +3364,7 @@ class EnhancedSelect extends HTMLElement {
2922
3364
  this._suppressBlurClose = true;
2923
3365
  setTimeout(() => {
2924
3366
  this._suppressBlurClose = false;
2925
- }, 0);
3367
+ }, 150); // Increased timeout to ensure click finishes before blur checks
2926
3368
  });
2927
3369
  // Delegated click listener for improved event handling (robust across shadow DOM)
2928
3370
  const handleOptionEvent = (e) => {
@@ -2953,9 +3395,6 @@ class EnhancedSelect extends HTMLElement {
2953
3395
  }
2954
3396
  };
2955
3397
  this._optionsContainer.addEventListener('click', handleOptionEvent);
2956
- // also watch pointerup to catch cases where the pointerdown started outside
2957
- // (e.g. on the input) and the click never fires
2958
- this._optionsContainer.addEventListener('pointerup', handleOptionEvent);
2959
3398
  // Keyboard navigation
2960
3399
  this._input.addEventListener('keydown', (e) => this._handleKeydown(e));
2961
3400
  // Click outside to close — robust detection across shadow DOM and custom renderers
@@ -3053,17 +3492,6 @@ class EnhancedSelect extends HTMLElement {
3053
3492
  this._dropdown.style.display = 'block';
3054
3493
  this._input.setAttribute('aria-expanded', 'true');
3055
3494
  this._updateArrowRotation();
3056
- // Clear search query when opening to show all options
3057
- // This ensures we can scroll to selected item
3058
- if (this._config.searchable) {
3059
- this._state.searchQuery = '';
3060
- // Don't clear input value if it represents selection
3061
- // But if we want to search, we might want to clear it?
3062
- // Standard behavior: input keeps value (label), but dropdown shows all options
3063
- // until user types.
3064
- // However, our filtering logic uses _state.searchQuery.
3065
- // So clearing it here resets the filter.
3066
- }
3067
3495
  // Render options when opening
3068
3496
  this._renderOptions();
3069
3497
  this._setInitialActiveOption();
@@ -3309,24 +3737,24 @@ class EnhancedSelect extends HTMLElement {
3309
3737
  }
3310
3738
  }
3311
3739
  _setActive(index) {
3312
- const options = Array.from(this._optionsContainer.children);
3313
3740
  // Clear previous active state
3314
- if (this._state.activeIndex >= 0 && options[this._state.activeIndex]) {
3315
- const prevOption = options[this._state.activeIndex];
3316
- // Check if it's a custom SelectOption or a lightweight DOM element
3317
- if ('setActive' in prevOption && typeof prevOption.setActive === 'function') {
3318
- prevOption.setActive(false);
3319
- }
3320
- else {
3321
- // Lightweight option - remove active class
3322
- prevOption.classList.remove('smilodon-option--active');
3323
- 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
+ }
3324
3752
  }
3325
3753
  }
3326
3754
  this._state.activeIndex = index;
3327
3755
  // Set new active state
3328
- if (options[index]) {
3329
- const option = options[index];
3756
+ const option = this._getOptionElementByIndex(index);
3757
+ if (option) {
3330
3758
  // Check if it's a custom SelectOption or a lightweight DOM element
3331
3759
  if ('setActive' in option && typeof option.setActive === 'function') {
3332
3760
  option.setActive(true);
@@ -3334,13 +3762,13 @@ class EnhancedSelect extends HTMLElement {
3334
3762
  else {
3335
3763
  // Lightweight option - add active class
3336
3764
  option.classList.add('smilodon-option--active');
3337
- option.setAttribute('aria-selected', 'true');
3338
3765
  }
3339
3766
  if (typeof option.scrollIntoView === 'function') {
3767
+ // Don't scroll wildly when just opening with pre-selections
3340
3768
  option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3341
3769
  }
3342
3770
  // Announce position for screen readers
3343
- const total = options.length;
3771
+ const total = this._state.loadedItems.length;
3344
3772
  this._announce(`Item ${index + 1} of ${total}`);
3345
3773
  // Update aria-activedescendant using the actual option id when available
3346
3774
  const optionId = option.id || `${this._uniqueId}-option-${index}`;
@@ -3463,9 +3891,9 @@ class EnhancedSelect extends HTMLElement {
3463
3891
  // FIX: Do not rely on this._optionsContainer.children[index] because filtering changes the children
3464
3892
  // Instead, use the index to update state directly
3465
3893
  const item = this._state.loadedItems[index];
3466
- // Debug: log selection attempt
3467
- if (!item)
3894
+ if (!item) {
3468
3895
  return;
3896
+ }
3469
3897
  const isCurrentlySelected = this._state.selectedIndices.has(index);
3470
3898
  // Keep active/focus styling aligned with the most recently interacted option.
3471
3899
  // Without this, a previously selected item may retain active classes/styles
@@ -3476,7 +3904,7 @@ class EnhancedSelect extends HTMLElement {
3476
3904
  const wasSelected = this._state.selectedIndices.has(index);
3477
3905
  this._state.selectedIndices.clear();
3478
3906
  this._state.selectedItems.clear();
3479
- if (!wasSelected) {
3907
+ if (!wasSelected || !this._config.selection.allowDeselect) {
3480
3908
  // Select this option
3481
3909
  this._state.selectedIndices.add(index);
3482
3910
  this._state.selectedItems.set(index, item);
@@ -3538,16 +3966,34 @@ class EnhancedSelect extends HTMLElement {
3538
3966
  });
3539
3967
  }
3540
3968
  _handleOptionRemove(index) {
3969
+ const item = this._state.selectedItems.get(index);
3541
3970
  const option = this._getOptionElementByIndex(index);
3542
- if (!option)
3543
- return;
3544
3971
  this._state.selectedIndices.delete(index);
3545
3972
  this._state.selectedItems.delete(index);
3546
- 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
+ }
3547
3990
  this._updateInputDisplay();
3991
+ this._renderOptions();
3548
3992
  this._emitChange();
3549
- const config = option.getConfig();
3550
- 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 });
3551
3997
  }
3552
3998
  _updateInputDisplay() {
3553
3999
  const selectedItems = Array.from(this._state.selectedItems.values());
@@ -3575,22 +4021,29 @@ class EnhancedSelect extends HTMLElement {
3575
4021
  const badge = document.createElement('span');
3576
4022
  badge.className = 'selection-badge';
3577
4023
  badge.setAttribute('part', 'chip');
3578
- 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);
3579
4028
  // Add remove button to badge
3580
- const removeBtn = document.createElement('button');
3581
- removeBtn.className = 'badge-remove';
3582
- removeBtn.setAttribute('part', 'chip-remove');
3583
- removeBtn.innerHTML = '×';
3584
- removeBtn.setAttribute('aria-label', `Remove ${getLabel(item)}`);
3585
- removeBtn.addEventListener('click', (e) => {
3586
- e.stopPropagation();
3587
- this._state.selectedIndices.delete(index);
3588
- this._state.selectedItems.delete(index);
3589
- this._updateInputDisplay();
3590
- this._renderOptions();
3591
- this._emitChange();
3592
- });
3593
- 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
+ }
3594
4047
  this._inputContainer.insertBefore(badge, this._input);
3595
4048
  });
3596
4049
  }
@@ -3642,10 +4095,12 @@ class EnhancedSelect extends HTMLElement {
3642
4095
  const option = this._getOptionElementByIndex(targetIndex);
3643
4096
  if (option) {
3644
4097
  // Use smooth scrolling with center alignment for better UX
3645
- option.scrollIntoView({
3646
- block: this._config.scrollToSelected.block || 'center',
3647
- behavior: 'smooth',
3648
- });
4098
+ if (typeof option.scrollIntoView === 'function') {
4099
+ option.scrollIntoView({
4100
+ block: this._config.scrollToSelected.block || 'center',
4101
+ behavior: 'smooth',
4102
+ });
4103
+ }
3649
4104
  // Also set it as active for keyboard navigation
3650
4105
  this._setActive(targetIndex);
3651
4106
  }
@@ -3857,7 +4312,6 @@ class EnhancedSelect extends HTMLElement {
3857
4312
  const getValue = this._config.serverSide.getValueFromItem || ((item) => item?.value ?? item);
3858
4313
  const selectedValues = selectedItems.map(getValue);
3859
4314
  const selectedIndices = Array.from(this._state.selectedIndices);
3860
- // Debug: log change payload
3861
4315
  this._emit('change', { selectedItems, selectedValues, selectedIndices });
3862
4316
  this._config.callbacks.onChange?.(selectedItems, selectedValues);
3863
4317
  }
@@ -4061,7 +4515,7 @@ class EnhancedSelect extends HTMLElement {
4061
4515
  this._clearControl.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
4062
4516
  }
4063
4517
  if (this._clearControlIcon) {
4064
- 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>`;
4065
4519
  }
4066
4520
  if (this._dropdown) {
4067
4521
  if (this._config.selection.mode === 'multi') {
@@ -4071,6 +4525,7 @@ class EnhancedSelect extends HTMLElement {
4071
4525
  this._dropdown.removeAttribute('aria-multiselectable');
4072
4526
  }
4073
4527
  }
4528
+ this._syncInputContainerMode();
4074
4529
  // Re-initialize observers in case infinite scroll was enabled/disabled
4075
4530
  this._initializeObservers();
4076
4531
  this._syncClearControlState();
@@ -4176,6 +4631,8 @@ class EnhancedSelect extends HTMLElement {
4176
4631
  * Render options based on current state
4177
4632
  */
4178
4633
  _renderOptions() {
4634
+ this._renderCycleId += 1;
4635
+ const renderCycleId = this._renderCycleId;
4179
4636
  // Cleanup observer
4180
4637
  if (this._loadMoreTrigger && this._intersectionObserver) {
4181
4638
  this._intersectionObserver.unobserve(this._loadMoreTrigger);
@@ -4240,23 +4697,21 @@ class EnhancedSelect extends HTMLElement {
4240
4697
  }
4241
4698
  else {
4242
4699
  // Normal rendering (flat list or filtered)
4243
- let hasRenderedItems = false;
4700
+ const filteredIndices = [];
4244
4701
  this._state.loadedItems.forEach((item, index) => {
4245
- // Apply filter if query exists
4246
4702
  if (query) {
4247
4703
  try {
4248
4704
  const label = String(getLabel(item)).toLowerCase();
4249
4705
  if (!label.includes(query))
4250
4706
  return;
4251
4707
  }
4252
- catch (e) {
4708
+ catch (_e) {
4253
4709
  return;
4254
4710
  }
4255
4711
  }
4256
- hasRenderedItems = true;
4257
- this._renderSingleOption(item, index, getValue, getLabel);
4712
+ filteredIndices.push(index);
4258
4713
  });
4259
- if (!hasRenderedItems && !this._state.isBusy) {
4714
+ if (filteredIndices.length === 0 && !this._state.isBusy) {
4260
4715
  const empty = document.createElement('div');
4261
4716
  empty.setAttribute('part', 'no-results');
4262
4717
  empty.className = 'empty-state';
@@ -4268,6 +4723,54 @@ class EnhancedSelect extends HTMLElement {
4268
4723
  }
4269
4724
  this._optionsContainer.appendChild(empty);
4270
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
+ }
4271
4774
  }
4272
4775
  // Append Busy Indicator if busy
4273
4776
  if (this._state.isBusy && this._config.busyBucket.enabled) {
@@ -4292,7 +4795,7 @@ class EnhancedSelect extends HTMLElement {
4292
4795
  }
4293
4796
  this._finalizePerfMarks();
4294
4797
  }
4295
- _renderSingleOption(item, index, getValue, getLabel) {
4798
+ _renderSingleOption(item, index, getValue, getLabel, targetContainer = this._optionsContainer) {
4296
4799
  const isSelected = this._state.selectedIndices.has(index);
4297
4800
  const isDisabled = Boolean(item?.disabled);
4298
4801
  const optionId = `${this._uniqueId}-option-${index}`;
@@ -4308,7 +4811,7 @@ class EnhancedSelect extends HTMLElement {
4308
4811
  disabled: isDisabled,
4309
4812
  id: optionId,
4310
4813
  });
4311
- this._optionsContainer.appendChild(optionElement);
4814
+ targetContainer.appendChild(optionElement);
4312
4815
  return;
4313
4816
  }
4314
4817
  const option = new SelectOption({
@@ -4337,20 +4840,16 @@ class EnhancedSelect extends HTMLElement {
4337
4840
  option.dataset.smValue = String(val);
4338
4841
  }
4339
4842
  option.id = option.id || optionId;
4340
- option.addEventListener('click', (e) => {
4341
- e.stopPropagation(); // Prevent duplicate handling by delegation
4342
- const mouseEvent = e;
4343
- this._selectOption(index, {
4344
- shiftKey: mouseEvent.shiftKey,
4345
- toggleKey: mouseEvent.ctrlKey || mouseEvent.metaKey,
4346
- });
4347
- });
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!
4348
4847
  option.addEventListener('optionRemove', (event) => {
4349
4848
  const detail = event.detail;
4350
4849
  const targetIndex = detail?.index ?? index;
4351
4850
  this._handleOptionRemove(targetIndex);
4352
4851
  });
4353
- this._optionsContainer.appendChild(option);
4852
+ targetContainer.appendChild(option);
4354
4853
  }
4355
4854
  _normalizeCustomOptionElement(element, meta) {
4356
4855
  const optionEl = element instanceof HTMLElement ? element : document.createElement('div');
@@ -4437,35 +4936,9 @@ class EnhancedSelect extends HTMLElement {
4437
4936
  optionEl.tabIndex = -1;
4438
4937
  }
4439
4938
  if (!this._customOptionBoundElements.has(optionEl)) {
4440
- optionEl.addEventListener('click', (e) => {
4441
- e.stopPropagation();
4442
- const current = e.currentTarget;
4443
- if (current.getAttribute('aria-disabled') === 'true')
4444
- return;
4445
- const parsedIndex = Number(current.dataset.index);
4446
- if (!Number.isFinite(parsedIndex))
4447
- return;
4448
- const mouseEvent = e;
4449
- this._selectOption(parsedIndex, {
4450
- shiftKey: mouseEvent.shiftKey,
4451
- toggleKey: mouseEvent.ctrlKey || mouseEvent.metaKey,
4452
- });
4453
- });
4454
- optionEl.addEventListener('keydown', (e) => {
4455
- if (e.key !== 'Enter' && e.key !== ' ')
4456
- return;
4457
- const current = e.currentTarget;
4458
- if (current.getAttribute('aria-disabled') === 'true')
4459
- return;
4460
- const parsedIndex = Number(current.dataset.index);
4461
- if (!Number.isFinite(parsedIndex))
4462
- return;
4463
- e.preventDefault();
4464
- this._selectOption(parsedIndex, {
4465
- shiftKey: e.shiftKey,
4466
- toggleKey: e.ctrlKey || e.metaKey,
4467
- });
4468
- });
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.
4469
4942
  this._customOptionBoundElements.add(optionEl);
4470
4943
  }
4471
4944
  return optionEl;