@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.js CHANGED
@@ -1349,6 +1349,7 @@ const defaultConfig = {
1349
1349
  maxSelections: 0,
1350
1350
  showRemoveButton: true,
1351
1351
  closeOnSelect: true,
1352
+ toggleOnTriggerClick: true,
1352
1353
  },
1353
1354
  scrollToSelected: {
1354
1355
  enabled: true,
@@ -1881,6 +1882,8 @@ if (!customElements.get('select-option')) {
1881
1882
  * Enhanced Select Component
1882
1883
  * Implements all advanced features: infinite scroll, load more, busy state,
1883
1884
  * server-side selection, and full customization
1885
+ *
1886
+ * ✨ Redesigned with formal elegance, refined microinteractions, and polished UX
1884
1887
  */
1885
1888
  class EnhancedSelect extends HTMLElement {
1886
1889
  get classMap() {
@@ -1928,6 +1931,8 @@ class EnhancedSelect extends HTMLElement {
1928
1931
  this._globalStylesObserver = null;
1929
1932
  this._globalStylesContainer = null;
1930
1933
  this._tracking = { events: [], styles: [], limitations: [] };
1934
+ this._suppressBlurClose = false;
1935
+ this._renderCycleId = 0;
1931
1936
  this._shadow = this.attachShadow({ mode: 'open' });
1932
1937
  this._uniqueId = `enhanced-select-${Math.random().toString(36).substr(2, 9)}`;
1933
1938
  this._rendererHelpers = this._buildRendererHelpers();
@@ -2002,6 +2007,7 @@ class EnhancedSelect extends HTMLElement {
2002
2007
  if (this._boundArrowClick && this._arrowContainer) {
2003
2008
  this._arrowContainer.removeEventListener('click', this._boundArrowClick);
2004
2009
  }
2010
+ this._renderCycleId += 1;
2005
2011
  this._teardownGlobalStylesMirroring();
2006
2012
  }
2007
2013
  _setGlobalStylesMirroring(enabled) {
@@ -2105,8 +2111,48 @@ class EnhancedSelect extends HTMLElement {
2105
2111
  const container = document.createElement('div');
2106
2112
  container.className = 'input-container';
2107
2113
  container.setAttribute('part', 'button');
2114
+ const inputStyles = this._config.styles.input;
2115
+ if (inputStyles && !this._config.styles.container) {
2116
+ const shellStyleKeys = [
2117
+ 'background',
2118
+ 'backgroundColor',
2119
+ 'border',
2120
+ 'borderColor',
2121
+ 'borderStyle',
2122
+ 'borderWidth',
2123
+ 'borderRadius',
2124
+ 'boxShadow',
2125
+ 'padding',
2126
+ 'height',
2127
+ 'minHeight',
2128
+ 'maxHeight',
2129
+ ];
2130
+ for (const key of shellStyleKeys) {
2131
+ const value = inputStyles[key];
2132
+ if (value != null && value !== '') {
2133
+ container.style[key] = value;
2134
+ }
2135
+ }
2136
+ }
2108
2137
  return container;
2109
2138
  }
2139
+ _syncInputContainerMode() {
2140
+ if (!this._inputContainer || !this._input)
2141
+ return;
2142
+ const isMulti = this._config.selection.mode === 'multi';
2143
+ this._inputContainer.classList.toggle('input-container--multi', isMulti);
2144
+ this._inputContainer.classList.toggle('input-container--single', !isMulti);
2145
+ if (isMulti) {
2146
+ this._input.style.flex = '1 0 var(--select-multi-input-min-width, 96px)';
2147
+ this._input.style.width = 'auto';
2148
+ this._input.style.minWidth = 'var(--select-multi-input-min-width, 96px)';
2149
+ }
2150
+ else {
2151
+ this._input.style.flex = '1 1 auto';
2152
+ this._input.style.width = '100%';
2153
+ this._input.style.minWidth = '0';
2154
+ }
2155
+ }
2110
2156
  _createInput() {
2111
2157
  const input = document.createElement('input');
2112
2158
  input.setAttribute('part', 'input');
@@ -2116,6 +2162,35 @@ class EnhancedSelect extends HTMLElement {
2116
2162
  input.placeholder = this._config.placeholder || 'Select an option...';
2117
2163
  input.disabled = !this._config.enabled;
2118
2164
  input.readOnly = !this._config.searchable;
2165
+ // Apply a direct inline reset so the input is only the writable text layer,
2166
+ // while the `.input-container` remains the sole visible control shell.
2167
+ input.style.all = 'unset';
2168
+ input.style.display = 'block';
2169
+ input.style.flex = '1 1 auto';
2170
+ input.style.width = '100%';
2171
+ input.style.maxWidth = '100%';
2172
+ input.style.minWidth = '0';
2173
+ input.style.minInlineSize = '0';
2174
+ input.style.minHeight = '0';
2175
+ input.style.padding = '0';
2176
+ input.style.margin = '0';
2177
+ input.style.border = '0';
2178
+ input.style.background = 'transparent';
2179
+ input.style.boxSizing = 'border-box';
2180
+ input.style.outline = 'none';
2181
+ input.style.font = 'inherit';
2182
+ input.style.fontFamily = 'inherit';
2183
+ input.style.lineHeight = 'inherit';
2184
+ input.style.color = 'inherit';
2185
+ input.style.alignSelf = 'center';
2186
+ input.style.appearance = 'none';
2187
+ input.style.webkitAppearance = 'none';
2188
+ input.style.boxShadow = 'none';
2189
+ input.style.borderRadius = '0';
2190
+ input.style.overflow = 'hidden';
2191
+ input.style.textOverflow = 'ellipsis';
2192
+ input.style.whiteSpace = 'nowrap';
2193
+ input.style.cursor = this._config.searchable ? 'text' : 'default';
2119
2194
  // Update readonly when input is focused if searchable
2120
2195
  input.addEventListener('focus', () => {
2121
2196
  if (this._config.searchable) {
@@ -2126,7 +2201,22 @@ class EnhancedSelect extends HTMLElement {
2126
2201
  input.className += ' ' + this._config.styles.classNames.input;
2127
2202
  }
2128
2203
  if (this._config.styles.input) {
2129
- Object.assign(input.style, this._config.styles.input);
2204
+ const inputStyles = { ...this._config.styles.input };
2205
+ // Route shell-like styling to .input-container so the control appears as
2206
+ // a single styled input instead of two visually separate layers.
2207
+ delete inputStyles.background;
2208
+ delete inputStyles.backgroundColor;
2209
+ delete inputStyles.border;
2210
+ delete inputStyles.borderColor;
2211
+ delete inputStyles.borderStyle;
2212
+ delete inputStyles.borderWidth;
2213
+ delete inputStyles.borderRadius;
2214
+ delete inputStyles.boxShadow;
2215
+ delete inputStyles.padding;
2216
+ delete inputStyles.height;
2217
+ delete inputStyles.minHeight;
2218
+ delete inputStyles.maxHeight;
2219
+ Object.assign(input.style, inputStyles);
2130
2220
  }
2131
2221
  input.setAttribute('role', 'combobox');
2132
2222
  input.setAttribute('aria-expanded', 'false');
@@ -2170,7 +2260,7 @@ class EnhancedSelect extends HTMLElement {
2170
2260
  container.className = 'dropdown-arrow-container';
2171
2261
  container.innerHTML = `
2172
2262
  <svg class="dropdown-arrow" part="arrow" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2173
- <path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2263
+ <path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
2174
2264
  </svg>
2175
2265
  `;
2176
2266
  return container;
@@ -2183,7 +2273,7 @@ class EnhancedSelect extends HTMLElement {
2183
2273
  const icon = document.createElement('span');
2184
2274
  icon.className = 'clear-control-icon';
2185
2275
  icon.setAttribute('part', 'clear-icon');
2186
- icon.textContent = this._config.clearControl.icon || '×';
2276
+ 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>`;
2187
2277
  button.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
2188
2278
  button.appendChild(icon);
2189
2279
  this._clearControlIcon = icon;
@@ -2209,18 +2299,229 @@ class EnhancedSelect extends HTMLElement {
2209
2299
  this._dropdown.id = listboxId;
2210
2300
  this._input.setAttribute('aria-controls', listboxId);
2211
2301
  this._input.setAttribute('aria-owns', listboxId);
2302
+ this._syncInputContainerMode();
2212
2303
  this._syncClearControlState();
2213
2304
  }
2214
2305
  _initializeStyles() {
2215
2306
  const style = document.createElement('style');
2216
2307
  style.textContent = `
2308
+ /* ═══════════════════════════════════════════════════════════════════════════
2309
+ ELEGANT SELECT COMPONENT — Refined Design System
2310
+ Formal aesthetics with sophisticated microinteractions
2311
+ ═══════════════════════════════════════════════════════════════════════════ */
2312
+
2217
2313
  :host {
2314
+ --select-primary: #1a1a2e;
2315
+ --select-primary-light: #16213e;
2316
+ --select-accent: #0f3460;
2317
+ --select-accent-hover: #e94560;
2318
+ --select-surface: #ffffff;
2319
+ --select-surface-elevated: #fafbfc;
2320
+ --select-border: #e1e5eb;
2321
+ --select-border-focus: #0f3460;
2322
+ --select-text: #1a1a2e;
2323
+ --select-text-muted: #6b7280;
2324
+ --select-text-placeholder: #9ca3af;
2325
+ --select-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
2326
+ --select-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
2327
+ --select-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
2328
+ --select-shadow-focus: 0 0 0 3px rgba(15, 52, 96, 0.12);
2329
+ --select-radius-sm: 6px;
2330
+ --select-radius-md: 10px;
2331
+ --select-radius-lg: 14px;
2332
+ --select-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
2333
+ --select-transition-smooth: 250ms cubic-bezier(0.4, 0, 0.2, 1);
2334
+ --select-transition-bounce: 350ms cubic-bezier(0.34, 1.56, 0.64, 1);
2335
+ --select-badge-animation: badgeEnter 300ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
2336
+ --select-badge-enter-from-opacity: 0;
2337
+ --select-badge-enter-from-transform: scale(0.8) translateY(-4px);
2338
+ --select-badge-enter-to-opacity: 1;
2339
+ --select-badge-enter-to-transform: scale(1) translateY(0);
2340
+ --select-badge-hover-transform: scale(1.02);
2341
+ --select-badge-letter-spacing: 0.01em;
2342
+ --select-badge-hover-shadow: var(--select-shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.15);
2343
+ --select-badge-remove-focus-outline: 2px solid rgba(255, 255, 255, 0.5);
2344
+ --select-badge-remove-focus-offset: 1px;
2345
+ --select-badge-remove-hover-transform: scale(1.15) rotate(90deg);
2346
+ --select-badge-remove-active-transform: scale(0.95) rotate(90deg);
2347
+ --select-input-hover-border: var(--select-border-focus);
2348
+ --select-input-hover-shadow: var(--select-shadow-sm), 0 0 0 1px rgba(15, 52, 96, 0.05);
2349
+ --select-input-font-weight: 450;
2350
+ --select-input-letter-spacing: 0.01em;
2351
+ --select-input-disabled-opacity: 0.6;
2352
+ --select-separator-opacity: 0.6;
2353
+ --select-separator-active-opacity: 1;
2354
+ --select-separator-dark-bg: linear-gradient(
2355
+ to bottom,
2356
+ transparent 0%,
2357
+ var(--select-border) 20%,
2358
+ var(--select-border) 80%,
2359
+ transparent 100%
2360
+ );
2361
+ --select-arrow-open-transform: rotate(180deg);
2362
+ --select-clear-button-hover-transform: translateY(-50%) scale(1.1);
2363
+ --select-clear-button-active-transform: translateY(-50%) scale(0.95);
2364
+ --select-clear-button-focus-offset: 2px;
2365
+ --select-clear-icon-size: 14px;
2366
+ --select-dropdown-top: calc(100% + 6px);
2367
+ --select-dropdown-animation: dropdownEnter 200ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
2368
+ --select-dropdown-enter-from-opacity: 0;
2369
+ --select-dropdown-enter-from-transform: translateY(-8px) scale(0.98);
2370
+ --select-dropdown-enter-to-opacity: 1;
2371
+ --select-dropdown-enter-to-transform: translateY(0) scale(1);
2372
+ --select-dropdown-scroll-behavior: smooth;
2373
+ --select-dropdown-transform-origin: top center;
2374
+ --select-scrollbar-width: 6px;
2375
+ --select-scrollbar-thumb-radius: 3px;
2376
+ --select-option-hover-transform: translateX(2px);
2377
+ --select-option-font-weight: 450;
2378
+ --select-option-margin: 2px 0;
2379
+ --select-option-active-outline-offset: -2px;
2380
+ --select-option-selected-active-outline-offset: -2px;
2381
+ --select-option-selected-indicator-width: 3px;
2382
+ --select-option-selected-indicator-height: 60%;
2383
+ --select-option-selected-indicator-bg: var(--select-accent);
2384
+ --select-option-selected-indicator-radius: 0 2px 2px 0;
2385
+ --select-option-selected-indicator-left: 0;
2386
+ --select-option-selected-indicator-top: 50%;
2387
+ --select-option-selected-indicator-transform: translateY(-50%);
2388
+ --select-option-selected-indicator-animation: selectedIndicator 200ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
2389
+ --select-option-selected-indicator-from-height: 0;
2390
+ --select-option-selected-indicator-to-height: 60%;
2391
+ --select-option-selected-indicator-from-opacity: 0;
2392
+ --select-option-selected-indicator-to-opacity: 1;
2393
+ --select-option-pressed-transform: translateX(2px) scale(0.99);
2394
+ --select-option-selected-pressed-transform: scale(0.99);
2395
+ --select-button-hover-transform: translateY(-1px);
2396
+ --select-button-active-transform: translateY(0) scale(0.98);
2397
+ --select-button-font-weight: 550;
2398
+ --select-button-letter-spacing: 0.02em;
2399
+ --select-spinner-animation: spin 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
2400
+ --select-searching-spinner-active-color: var(--select-accent);
2401
+ --select-badge-remove-margin-left: 2px;
2402
+ --select-badge-remove-radius: 50%;
2403
+ --select-badge-remove-font-weight: 600;
2404
+ --select-empty-gap: 8px;
2405
+ --select-searching-gap: 8px;
2406
+ --select-searching-spinner-size: 24px;
2407
+ --select-searching-spinner-border: 2px solid var(--select-border);
2408
+ --select-searching-spinner-animation: var(--select-spinner-animation);
2409
+ --select-group-header-separator-margin-top: 8px;
2410
+ --select-group-header-separator-padding-top: 14px;
2411
+ --select-group-header-separator-border-top: 1px solid var(--select-border);
2412
+ --select-reduced-motion-duration: 0.01ms;
2413
+ --select-reduced-motion-iteration-count: 1;
2414
+ --select-high-contrast-border-width: 2px;
2415
+ --select-high-contrast-indicator-width: 4px;
2416
+ --select-high-contrast-focus-outline-width: 3px;
2417
+ --select-high-contrast-focus-outline-color: Highlight;
2418
+ --select-touch-target-min-height: 44px;
2419
+ --select-badge-dark-bg: linear-gradient(135deg, var(--select-accent) 0%, #4f46e5 100%);
2420
+
2218
2421
  display: block;
2219
2422
  position: relative;
2220
2423
  width: var(--select-width, 100%);
2221
2424
  height: var(--select-height, auto);
2425
+ font-family: var(--select-font-family, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
2426
+ }
2427
+
2428
+ /* ─────────────────────────────────────────────────────────────────────────
2429
+ Selection Badges — Refined pill design with elegant interactions
2430
+ ───────────────────────────────────────────────────────────────────────── */
2431
+
2432
+ .selection-badge {
2433
+ display: inline-flex;
2434
+ align-items: center;
2435
+ gap: var(--select-badge-gap, 6px);
2436
+ flex: 0 1 auto;
2437
+ min-height: var(--select-badge-min-height, 26px);
2438
+ padding: var(--select-badge-padding, 4px 10px 4px 12px);
2439
+ margin: var(--select-badge-margin, 3px);
2440
+ background: var(--select-badge-bg, linear-gradient(135deg, var(--select-accent) 0%, var(--select-primary-light) 100%));
2441
+ color: var(--select-badge-color, #ffffff);
2442
+ border: var(--select-badge-border, none);
2443
+ border-radius: var(--select-badge-border-radius, 999px);
2444
+ box-shadow: var(--select-badge-shadow, var(--select-shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.1));
2445
+ box-sizing: border-box;
2446
+ font-size: var(--select-badge-font-size, 13px);
2447
+ font-weight: var(--select-badge-font-weight, 500);
2448
+ line-height: var(--select-badge-line-height, 1.2);
2449
+ letter-spacing: var(--select-badge-letter-spacing);
2450
+ max-width: var(--select-badge-max-width, 100%);
2451
+ overflow: hidden;
2452
+ transform: scale(1);
2453
+ transition:
2454
+ transform var(--select-transition-bounce),
2455
+ box-shadow var(--select-transition-fast),
2456
+ background var(--select-transition-fast);
2457
+ animation: var(--select-badge-animation);
2458
+ }
2459
+
2460
+ @keyframes badgeEnter {
2461
+ 0% {
2462
+ opacity: var(--select-badge-enter-from-opacity);
2463
+ transform: var(--select-badge-enter-from-transform);
2464
+ }
2465
+ 100% {
2466
+ opacity: var(--select-badge-enter-to-opacity);
2467
+ transform: var(--select-badge-enter-to-transform);
2468
+ }
2222
2469
  }
2223
2470
 
2471
+ .selection-badge:hover {
2472
+ box-shadow: var(--select-badge-hover-shadow);
2473
+ transform: var(--select-badge-hover-transform);
2474
+ }
2475
+
2476
+ .selection-badge-label {
2477
+ display: block;
2478
+ min-width: 0;
2479
+ overflow: hidden;
2480
+ text-overflow: ellipsis;
2481
+ white-space: nowrap;
2482
+ line-height: var(--select-badge-line-height, 1.2);
2483
+ }
2484
+
2485
+ .badge-remove {
2486
+ display: inline-flex;
2487
+ align-items: center;
2488
+ justify-content: center;
2489
+ width: var(--select-badge-remove-size, 18px);
2490
+ height: var(--select-badge-remove-size, 18px);
2491
+ padding: 0;
2492
+ margin-left: var(--select-badge-remove-margin-left);
2493
+ background: var(--select-badge-remove-bg, rgba(255, 255, 255, 0.2));
2494
+ border: var(--select-badge-remove-border, none);
2495
+ border-radius: var(--select-badge-remove-radius);
2496
+ color: var(--select-badge-remove-color, #ffffff);
2497
+ font-size: var(--select-badge-remove-font-size, 11px);
2498
+ font-weight: var(--select-badge-remove-font-weight);
2499
+ line-height: 1;
2500
+ cursor: pointer;
2501
+ flex: 0 0 auto;
2502
+ transition:
2503
+ background var(--select-transition-fast),
2504
+ transform var(--select-transition-bounce);
2505
+ }
2506
+
2507
+ .badge-remove:hover {
2508
+ background: var(--select-badge-remove-hover-bg, rgba(233, 69, 96, 0.9));
2509
+ transform: var(--select-badge-remove-hover-transform);
2510
+ }
2511
+
2512
+ .badge-remove:focus-visible {
2513
+ outline: var(--select-badge-remove-focus-outline);
2514
+ outline-offset: var(--select-badge-remove-focus-offset);
2515
+ }
2516
+
2517
+ .badge-remove:active {
2518
+ transform: var(--select-badge-remove-active-transform);
2519
+ }
2520
+
2521
+ /* ─────────────────────────────────────────────────────────────────────────
2522
+ Input Container — Sophisticated control shell
2523
+ ───────────��───────────────────────────────────────────────────────────── */
2524
+
2224
2525
  .select-container {
2225
2526
  position: relative;
2226
2527
  width: 100%;
@@ -2231,102 +2532,143 @@ class EnhancedSelect extends HTMLElement {
2231
2532
  width: 100%;
2232
2533
  display: flex;
2233
2534
  align-items: center;
2234
- flex-wrap: wrap;
2535
+ flex-wrap: nowrap;
2235
2536
  gap: var(--select-input-gap, 6px);
2236
- padding: var(--select-input-padding, 6px 52px 6px 8px);
2537
+ padding: var(--select-input-padding, 10px 52px 10px 14px);
2237
2538
  height: var(--select-input-height, auto);
2238
- min-height: var(--select-input-min-height, 44px);
2539
+ min-height: var(--select-input-min-height, 48px);
2239
2540
  max-height: var(--select-input-max-height, 160px);
2240
2541
  overflow-y: var(--select-input-overflow-y, auto);
2241
- align-content: flex-start;
2242
- background: var(--select-input-bg, var(--select-bg, white));
2243
- border: var(--select-input-border, 1px solid var(--select-border-color, #d1d5db));
2244
- border-radius: var(--select-input-border-radius, 6px);
2542
+ align-content: center;
2543
+ background: var(--select-input-bg, var(--select-surface));
2544
+ border: var(--select-input-border, 1.5px solid var(--select-border));
2545
+ border-radius: var(--select-input-border-radius, var(--select-radius-md));
2546
+ box-shadow: var(--select-shadow-sm);
2245
2547
  box-sizing: border-box;
2246
- transition: all 0.2s ease;
2548
+ transition:
2549
+ border-color var(--select-transition-fast),
2550
+ box-shadow var(--select-transition-smooth),
2551
+ transform var(--select-transition-fast);
2552
+ }
2553
+
2554
+ .input-container:hover {
2555
+ border-color: var(--select-input-hover-border);
2556
+ box-shadow: var(--select-input-hover-shadow);
2557
+ }
2558
+
2559
+ .input-container.input-container--single {
2560
+ flex-wrap: nowrap;
2561
+ align-content: center;
2562
+ }
2563
+
2564
+ .input-container.input-container--multi {
2565
+ flex-wrap: wrap;
2566
+ align-content: flex-start;
2247
2567
  }
2248
2568
 
2249
2569
  .input-container:focus-within {
2250
- border-color: var(--select-input-focus-border, var(--select-border-focus-color, #667eea));
2251
- box-shadow: var(--select-input-focus-shadow, 0 0 0 3px rgba(102, 126, 234, 0.1));
2570
+ border-color: var(--select-input-focus-border, var(--select-border-focus));
2571
+ box-shadow: var(--select-shadow-focus), var(--select-shadow-sm);
2252
2572
  }
2253
2573
 
2254
- /* Gradient separator before arrow */
2574
+ /* Elegant separator line before arrow */
2255
2575
  .input-container::after {
2256
2576
  content: '';
2257
2577
  position: absolute;
2258
2578
  top: 50%;
2259
- right: var(--select-separator-position, var(--select-seperator-position, 40px));
2579
+ right: var(--select-separator-position, 42px);
2260
2580
  transform: translateY(-50%);
2261
- width: var(--select-separator-width, var(--select-seperator-width, 1px));
2262
- height: var(--select-separator-height, var(--select-seperator-height, 60%));
2263
- background: var(--select-separator-bg, var(--select-seperator-bg, var(--select-separator-gradient, var(--select-seperator-gradient, linear-gradient(
2581
+ width: var(--select-separator-width, 1px);
2582
+ height: var(--select-separator-height, 50%);
2583
+ background: var(--select-separator-bg, linear-gradient(
2264
2584
  to bottom,
2265
2585
  transparent 0%,
2266
- rgba(0, 0, 0, 0.1) 20%,
2267
- rgba(0, 0, 0, 0.1) 80%,
2586
+ var(--select-border) 20%,
2587
+ var(--select-border) 80%,
2268
2588
  transparent 100%
2269
- ))));
2589
+ ));
2270
2590
  pointer-events: none;
2271
2591
  z-index: 1;
2592
+ opacity: var(--select-separator-opacity);
2593
+ transition: opacity var(--select-transition-fast);
2272
2594
  }
2273
2595
 
2596
+ .input-container:hover::after,
2597
+ .input-container:focus-within::after {
2598
+ opacity: var(--select-separator-active-opacity);
2599
+ }
2600
+
2601
+ /* ─────────────────────────────────────────────────────────────────────────
2602
+ Dropdown Arrow — Smooth rotation with refined styling
2603
+ ───────────────────────────────────────────────────────────────────────── */
2604
+
2274
2605
  .dropdown-arrow-container {
2275
2606
  position: absolute;
2276
2607
  top: 0;
2277
2608
  right: 0;
2278
2609
  bottom: 0;
2279
- width: var(--select-arrow-width, 40px);
2280
- /* allow explicit height override even though container normally stretches */
2610
+ width: var(--select-arrow-width, 42px);
2281
2611
  height: var(--select-arrow-height, auto);
2282
2612
  display: flex;
2283
2613
  align-items: center;
2284
2614
  justify-content: center;
2285
2615
  cursor: pointer;
2286
- transition: background-color 0.2s ease;
2287
- border-radius: var(--select-arrow-border-radius, 0 4px 4px 0);
2616
+ border-radius: var(--select-arrow-border-radius, 0 var(--select-radius-md) var(--select-radius-md) 0);
2288
2617
  z-index: 2;
2618
+ transition: background-color var(--select-transition-fast);
2289
2619
  }
2290
2620
 
2291
2621
  .input-container.has-clear-control {
2292
- padding: var(--select-input-padding-with-clear, 6px 84px 6px 8px);
2622
+ padding: var(--select-input-padding-with-clear, 10px 84px 10px 14px);
2293
2623
  }
2294
2624
 
2295
2625
  .input-container.has-clear-control::after {
2296
- right: var(--select-separator-position-with-clear, var(--select-seperator-position-with-clear, 72px));
2626
+ right: var(--select-separator-position-with-clear, 74px);
2297
2627
  }
2298
2628
 
2299
2629
  .dropdown-arrow-container.with-clear-control {
2300
- right: var(--select-arrow-right-with-clear, 32px);
2630
+ right: var(--select-arrow-right-with-clear, 34px);
2301
2631
  }
2302
2632
 
2633
+ /* ─────────────────────────────────────────────────────────────────────────
2634
+ Clear Control — Minimal and elegant dismiss button
2635
+ ───────────────────────────────────────────────────────────────────────── */
2636
+
2303
2637
  .clear-control-button {
2304
2638
  position: absolute;
2305
2639
  top: 50%;
2306
- right: var(--select-clear-button-right, 6px);
2640
+ right: var(--select-clear-button-right, 8px);
2307
2641
  transform: translateY(-50%);
2308
- width: var(--select-clear-button-size, 24px);
2309
- height: var(--select-clear-button-size, 24px);
2642
+ width: var(--select-clear-button-size, 26px);
2643
+ height: var(--select-clear-button-size, 26px);
2310
2644
  border: var(--select-clear-button-border, none);
2311
- border-radius: var(--select-clear-button-radius, 999px);
2645
+ border-radius: var(--select-clear-button-radius, 50%);
2312
2646
  background: var(--select-clear-button-bg, transparent);
2313
- color: var(--select-clear-button-color, #6b7280);
2647
+ color: var(--select-clear-button-color, var(--select-text-muted));
2314
2648
  display: inline-flex;
2315
2649
  align-items: center;
2316
2650
  justify-content: center;
2317
2651
  cursor: pointer;
2318
2652
  z-index: 3;
2319
- transition: var(--select-clear-button-transition, all 0.2s ease);
2653
+ transition:
2654
+ background var(--select-transition-fast),
2655
+ color var(--select-transition-fast),
2656
+ transform var(--select-transition-bounce);
2320
2657
  }
2321
2658
 
2322
2659
  .clear-control-button:hover {
2323
- background: var(--select-clear-button-hover-bg, rgba(0, 0, 0, 0.06));
2324
- color: var(--select-clear-button-hover-color, #111827);
2660
+ background: var(--select-clear-button-hover-bg, rgba(233, 69, 96, 0.1));
2661
+ color: var(--select-clear-button-hover-color, var(--select-accent-hover));
2662
+ transform: var(--select-clear-button-hover-transform);
2663
+ }
2664
+
2665
+ .clear-control-button:active {
2666
+ transform: var(--select-clear-button-active-transform);
2325
2667
  }
2326
2668
 
2327
2669
  .clear-control-button:focus-visible {
2328
- outline: var(--select-clear-button-focus-outline, 2px solid rgba(102, 126, 234, 0.55));
2329
- outline-offset: 1px;
2670
+ outline: var(--select-clear-button-focus-outline, 2px solid var(--select-border-focus));
2671
+ outline-offset: var(--select-clear-button-focus-offset);
2330
2672
  }
2331
2673
 
2332
2674
  .clear-control-button[hidden] {
@@ -2334,260 +2676,332 @@ class EnhancedSelect extends HTMLElement {
2334
2676
  }
2335
2677
 
2336
2678
  .clear-control-icon {
2337
- font-size: var(--select-clear-icon-size, 16px);
2338
- line-height: 1;
2339
- font-weight: var(--select-clear-icon-weight, 500);
2679
+ width: var(--select-clear-icon-size);
2680
+ height: var(--select-clear-icon-size);
2340
2681
  pointer-events: none;
2341
2682
  }
2342
2683
 
2343
- .dropdown-arrow-container:hover {
2344
- background-color: var(--select-arrow-hover-bg, rgba(102, 126, 234, 0.08));
2684
+ .clear-control-icon svg {
2685
+ width: 100%;
2686
+ height: 100%;
2345
2687
  }
2346
2688
 
2347
- .dropdown-arrow:hover {
2348
- /* legacy alias --select-arrow-hover for icon color */
2349
- color: var(--select-arrow-hover, var(--select-arrow-hover-color, #667eea));
2689
+ .dropdown-arrow-container:hover {
2690
+ background-color: var(--select-arrow-hover-bg, rgba(15, 52, 96, 0.05));
2350
2691
  }
2351
2692
 
2352
2693
  .dropdown-arrow {
2353
- width: var(--select-arrow-width, var(--select-arrow-size, 16px));
2354
- height: var(--select-arrow-height, var(--select-arrow-size, 16px));
2355
- color: var(--select-arrow-color, #667eea);
2356
- transition: transform 0.2s ease, color 0.2s ease;
2357
- transform: translateY(0);
2694
+ width: var(--select-arrow-size, 18px);
2695
+ height: var(--select-arrow-size, 18px);
2696
+ color: var(--select-arrow-color, var(--select-text-muted));
2697
+ transition:
2698
+ transform var(--select-transition-smooth),
2699
+ color var(--select-transition-fast);
2700
+ transform-origin: center;
2358
2701
  }
2359
2702
 
2360
2703
  .dropdown-arrow path {
2361
- stroke-width: var(--select-arrow-stroke-width, 2);
2704
+ stroke-width: var(--select-arrow-stroke-width, 1.5);
2362
2705
  }
2363
2706
 
2364
2707
  .dropdown-arrow-container:hover .dropdown-arrow {
2365
- color: var(--select-arrow-hover-color, #667eea);
2708
+ color: var(--select-arrow-hover-color, var(--select-accent));
2366
2709
  }
2367
2710
 
2368
2711
  .dropdown-arrow.open {
2369
- transform: rotate(180deg);
2712
+ transform: var(--select-arrow-open-transform);
2370
2713
  }
2371
2714
 
2372
- .select-input {
2373
- flex: 1;
2374
- width: var(--select-input-width, auto);
2375
- min-width: var(--select-input-min-width, 120px);
2376
- padding: var(--select-input-field-padding, 4px);
2377
- border: none;
2378
- font-size: var(--select-input-font-size, 14px);
2715
+ /* ─────────────────────────────────────────────────────────────────────────
2716
+ Input Field — Clean text entry
2717
+ ───────────────────────────────────────────────────────────────────────── */
2718
+
2719
+ .input-container > .select-input,
2720
+ input.select-input,
2721
+ .select-input[type="text"] {
2722
+ display: block;
2723
+ flex: 1 1 auto;
2724
+ width: var(--select-input-width, 100%);
2725
+ max-width: 100%;
2726
+ min-width: var(--select-input-min-width, 0);
2727
+ min-inline-size: 0;
2728
+ min-height: 0;
2729
+ padding: var(--select-input-field-padding, 0) !important;
2730
+ margin: 0 !important;
2731
+ border: 0 !important;
2732
+ font-size: var(--select-input-font-size, 15px);
2379
2733
  line-height: var(--select-input-line-height, 1.5);
2380
- color: var(--select-input-color, var(--select-text-color, #1f2937));
2381
- background: transparent;
2734
+ color: var(--select-input-color, var(--select-text));
2735
+ background: transparent !important;
2382
2736
  box-sizing: border-box;
2383
- outline: none;
2384
- font-family: var(--select-font-family, inherit);
2385
- }
2386
-
2387
- .select-input::placeholder {
2388
- color: var(--select-input-placeholder-color, var(--select-placeholder-color, #9ca3af));
2389
- }
2390
-
2391
- .selection-badge {
2392
- display: inline-flex;
2393
- align-items: center;
2394
- gap: var(--select-badge-gap, 4px);
2395
- padding: var(--select-badge-padding, 4px 8px);
2396
- margin: var(--select-badge-margin, 2px);
2397
- background: var(--select-badge-bg, #667eea);
2398
- color: var(--select-badge-color, white);
2399
- border-radius: var(--select-badge-border-radius, 4px);
2400
- font-size: var(--select-badge-font-size, 13px);
2401
- line-height: 1;
2402
- max-width: var(--select-badge-max-width, 100%);
2403
- white-space: nowrap;
2737
+ outline: none !important;
2738
+ font-weight: var(--select-input-font-weight);
2739
+ letter-spacing: var(--select-input-letter-spacing);
2740
+ align-self: center;
2741
+ appearance: none !important;
2742
+ -webkit-appearance: none !important;
2743
+ box-shadow: none !important;
2744
+ border-radius: 0 !important;
2404
2745
  overflow: hidden;
2405
2746
  text-overflow: ellipsis;
2747
+ white-space: nowrap;
2406
2748
  }
2407
2749
 
2408
- .badge-remove {
2409
- display: inline-flex;
2410
- align-items: center;
2411
- justify-content: center;
2412
- width: var(--select-badge-remove-size, 16px);
2413
- height: var(--select-badge-remove-size, 16px);
2414
- padding: 0;
2415
- margin-left: 4px;
2416
- background: var(--select-badge-remove-bg, rgba(255, 255, 255, 0.3));
2417
- border: none;
2418
- border-radius: 50%;
2419
- color: var(--select-badge-remove-color, white);
2420
- font-size: var(--select-badge-remove-font-size, 16px);
2421
- line-height: 1;
2422
- cursor: pointer;
2423
- transition: background 0.2s;
2750
+ .select-input::placeholder {
2751
+ color: var(--select-input-placeholder-color, var(--select-text-placeholder));
2752
+ font-weight: 400;
2424
2753
  }
2425
-
2426
- .badge-remove:hover {
2427
- background: var(--select-badge-remove-hover-bg, rgba(255, 255, 255, 0.5));
2754
+
2755
+ .input-container.input-container--multi > .select-input,
2756
+ .input-container.input-container--multi > input.select-input,
2757
+ .input-container.input-container--multi > .select-input[type="text"] {
2758
+ width: auto;
2759
+ min-width: var(--select-multi-input-min-width, 96px);
2760
+ flex: 1 0 var(--select-multi-input-min-width, 96px);
2428
2761
  }
2429
2762
 
2430
- .badge-remove:focus-visible {
2431
- outline: 2px solid var(--select-badge-remove-focus-outline, rgba(255, 255, 255, 0.8));
2432
- outline-offset: 2px;
2763
+ .input-container.input-container--single > .select-input,
2764
+ .input-container.input-container--single > input.select-input,
2765
+ .input-container.input-container--single > .select-input[type="text"] {
2766
+ width: var(--select-input-width, 100%);
2767
+ min-width: 0;
2768
+ flex: 1 1 auto;
2433
2769
  }
2434
2770
 
2435
2771
  .select-input:disabled {
2436
2772
  background-color: var(--select-disabled-bg, #f5f5f5);
2437
2773
  cursor: not-allowed;
2774
+ opacity: var(--select-input-disabled-opacity);
2438
2775
  }
2439
2776
 
2777
+ /* ─────────────────────────────────────────────────────────────────────────
2778
+ Dropdown Panel — Elegant floating container
2779
+ ───────────────────────────────────────────────────────────────────────── */
2780
+
2440
2781
  .select-dropdown {
2441
2782
  position: absolute;
2442
- scroll-behavior: smooth;
2443
- top: 100%;
2783
+ scroll-behavior: var(--select-dropdown-scroll-behavior);
2784
+ top: var(--select-dropdown-top);
2444
2785
  left: 0;
2445
2786
  right: 0;
2446
- margin-top: var(--select-dropdown-margin-top, 4px);
2447
- max-height: var(--select-dropdown-max-height, 300px);
2787
+ max-height: var(--select-dropdown-max-height, 320px);
2448
2788
  overflow: hidden;
2449
- background: var(--select-dropdown-bg, var(--select-bg, white));
2450
- border: 1px solid var(--select-dropdown-border, #ccc);
2451
- border-radius: var(--select-dropdown-border-radius, 4px);
2452
- box-shadow: var(--select-dropdown-shadow, 0 4px 6px rgba(0,0,0,0.1));
2789
+ background: var(--select-dropdown-bg, var(--select-surface));
2790
+ border: 1px solid var(--select-dropdown-border, var(--select-border));
2791
+ border-radius: var(--select-dropdown-border-radius, var(--select-radius-lg));
2792
+ box-shadow: var(--select-dropdown-shadow, var(--select-shadow-lg));
2453
2793
  z-index: var(--select-dropdown-z-index, 1000);
2794
+ transform-origin: var(--select-dropdown-transform-origin);
2795
+ animation: var(--select-dropdown-animation);
2796
+ }
2797
+
2798
+ @keyframes dropdownEnter {
2799
+ 0% {
2800
+ opacity: var(--select-dropdown-enter-from-opacity);
2801
+ transform: var(--select-dropdown-enter-from-transform);
2802
+ }
2803
+ 100% {
2804
+ opacity: var(--select-dropdown-enter-to-opacity);
2805
+ transform: var(--select-dropdown-enter-to-transform);
2806
+ }
2454
2807
  }
2455
2808
 
2456
2809
  .options-container {
2457
2810
  position: relative;
2458
- max-height: var(--select-options-max-height, 300px);
2811
+ max-height: var(--select-options-max-height, 320px);
2459
2812
  overflow: auto;
2460
- transition: opacity 0.2s ease-in-out;
2461
- background: var(--select-options-bg, var(--select-dropdown-bg, var(--select-bg, white)));
2813
+ padding: var(--select-options-padding, 6px);
2814
+ background: var(--select-options-bg, var(--select-surface));
2815
+
2816
+ /* Custom scrollbar styling */
2817
+ scrollbar-width: thin;
2818
+ scrollbar-color: var(--select-border) transparent;
2819
+ }
2820
+
2821
+ .options-container::-webkit-scrollbar {
2822
+ width: var(--select-scrollbar-width);
2823
+ }
2824
+
2825
+ .options-container::-webkit-scrollbar-track {
2826
+ background: transparent;
2827
+ }
2828
+
2829
+ .options-container::-webkit-scrollbar-thumb {
2830
+ background: var(--select-border);
2831
+ border-radius: var(--select-scrollbar-thumb-radius);
2462
2832
  }
2833
+
2834
+ .options-container::-webkit-scrollbar-thumb:hover {
2835
+ background: var(--select-text-muted);
2836
+ }
2837
+
2838
+ /* ─────────────────────────────────────────────────────────────────────────
2839
+ Group Headers — Refined section dividers
2840
+ ───────────────────────────────────────────────────────────────────────── */
2463
2841
 
2464
2842
  .group-header {
2465
- padding: var(--select-group-header-padding, 8px 12px);
2843
+ padding: var(--select-group-header-padding, 10px 12px 6px);
2466
2844
  font-weight: var(--select-group-header-weight, 600);
2467
- color: var(--select-group-header-color, #6b7280);
2468
- background-color: var(--select-group-header-bg, #f3f4f6);
2845
+ color: var(--select-group-header-color, var(--select-text-muted));
2846
+ background-color: var(--select-group-header-bg, var(--select-surface));
2469
2847
  text-align: var(--select-group-header-text-align, left);
2470
- font-size: var(--select-group-header-font-size, 12px);
2848
+ font-size: var(--select-group-header-font-size, 11px);
2471
2849
  text-transform: var(--select-group-header-text-transform, uppercase);
2472
- letter-spacing: var(--select-group-header-letter-spacing, 0.05em);
2850
+ letter-spacing: var(--select-group-header-letter-spacing, 0.08em);
2473
2851
  position: sticky;
2474
2852
  top: 0;
2475
2853
  z-index: 1;
2476
- border-bottom: var(--select-group-header-border-bottom, 1px solid #e5e7eb);
2854
+ border-bottom: var(--select-group-header-border-bottom, none);
2855
+ }
2856
+
2857
+ .group-header:not(:first-child) {
2858
+ margin-top: var(--select-group-header-separator-margin-top);
2859
+ padding-top: var(--select-group-header-separator-padding-top);
2860
+ border-top: var(--select-group-header-separator-border-top);
2477
2861
  }
2478
2862
 
2863
+ /* ─────────────────────────────────────────────────────────────────────────
2864
+ Options — Elegant hover and selection states
2865
+ ───────────────────────────────────────────────────────────────────────── */
2866
+
2479
2867
  .option {
2480
- padding: var(--select-option-padding, 8px 12px);
2868
+ position: relative;
2869
+ padding: var(--select-option-padding, 10px 14px);
2481
2870
  cursor: pointer;
2482
- color: var(--select-option-color, var(--select-text-color, #1f2937));
2483
- background: var(--select-option-bg, var(--select-dropdown-bg, var(--select-bg, white)));
2484
- transition: var(--select-option-transition, background-color 0.15s ease);
2871
+ color: var(--select-option-color, var(--select-text));
2872
+ background: var(--select-option-bg, transparent);
2873
+ transition:
2874
+ background var(--select-transition-fast),
2875
+ color var(--select-transition-fast),
2876
+ transform var(--select-transition-fast),
2877
+ box-shadow var(--select-transition-fast);
2485
2878
  user-select: none;
2486
2879
  font-size: var(--select-option-font-size, 14px);
2880
+ font-weight: var(--select-option-font-weight);
2487
2881
  line-height: var(--select-option-line-height, 1.5);
2488
- border: var(--select-option-border, none);
2489
- border-bottom: var(--select-option-border-bottom, none);
2490
- border-radius: var(--select-option-border-radius, 0);
2491
- box-shadow: var(--select-option-shadow, none);
2492
- transform: var(--select-option-transform, none);
2882
+ border-radius: var(--select-option-border-radius, var(--select-radius-sm));
2883
+ margin: var(--select-option-margin);
2493
2884
  }
2494
2885
 
2495
2886
  .option:hover {
2496
- background: var(--select-option-hover-bg, #f3f4f6);
2497
- color: var(--select-option-hover-color, #1f2937);
2887
+ background: var(--select-option-hover-bg, var(--select-surface-elevated));
2888
+ color: var(--select-option-hover-color, var(--select-text));
2889
+ transform: var(--select-option-hover-transform);
2498
2890
  }
2499
2891
 
2500
2892
  .option.selected {
2501
- background: var(--select-option-selected-bg, #e0e7ff);
2502
- color: var(--select-option-selected-color, #4338ca);
2503
- font-weight: var(--select-option-selected-weight, 500);
2504
- border: var(--select-option-selected-border, var(--select-option-border, none));
2505
- border-bottom: var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none));
2506
- border-radius: var(--select-option-selected-border-radius, var(--select-option-border-radius, 0));
2507
- box-shadow: var(--select-option-selected-shadow, var(--select-option-shadow, none));
2508
- transform: var(--select-option-selected-transform, var(--select-option-transform, none));
2893
+ background: var(--select-option-selected-bg, linear-gradient(135deg, rgba(15, 52, 96, 0.08) 0%, rgba(15, 52, 96, 0.04) 100%));
2894
+ color: var(--select-option-selected-color, var(--select-accent));
2895
+ font-weight: var(--select-option-selected-weight, 550);
2896
+ }
2897
+
2898
+ .option.selected::before {
2899
+ content: '';
2900
+ position: absolute;
2901
+ left: var(--select-option-selected-indicator-left);
2902
+ top: var(--select-option-selected-indicator-top);
2903
+ transform: var(--select-option-selected-indicator-transform);
2904
+ width: var(--select-option-selected-indicator-width);
2905
+ height: var(--select-option-selected-indicator-height);
2906
+ background: var(--select-option-selected-indicator-bg);
2907
+ border-radius: var(--select-option-selected-indicator-radius);
2908
+ animation: var(--select-option-selected-indicator-animation);
2909
+ }
2910
+
2911
+ @keyframes selectedIndicator {
2912
+ 0% {
2913
+ height: var(--select-option-selected-indicator-from-height);
2914
+ opacity: var(--select-option-selected-indicator-from-opacity);
2915
+ }
2916
+ 100% {
2917
+ height: var(--select-option-selected-indicator-to-height);
2918
+ opacity: var(--select-option-selected-indicator-to-opacity);
2919
+ }
2509
2920
  }
2510
2921
 
2511
2922
  .option.selected:hover {
2512
- background: var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e0e7ff));
2513
- color: var(--select-option-selected-hover-color, var(--select-option-selected-color, #4338ca));
2514
- border: var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)));
2515
- border-bottom: var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2516
- box-shadow: var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2517
- transform: var(--select-option-selected-hover-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2923
+ background: var(--select-option-selected-hover-bg, linear-gradient(135deg, rgba(15, 52, 96, 0.12) 0%, rgba(15, 52, 96, 0.06) 100%));
2518
2924
  }
2519
2925
 
2520
2926
  .option.active:not(.selected) {
2521
- background: var(--select-option-active-bg, #f3f4f6);
2522
- color: var(--select-option-active-color, #1f2937);
2523
- outline: var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45));
2524
- outline-offset: -2px;
2927
+ background: var(--select-option-active-bg, var(--select-surface-elevated));
2928
+ outline: var(--select-option-active-outline, 2px solid rgba(15, 52, 96, 0.3));
2929
+ outline-offset: var(--select-option-active-outline-offset);
2525
2930
  }
2526
2931
 
2527
2932
  .option.selected.active {
2528
- background: var(--select-option-selected-active-bg, var(--select-option-selected-bg, #e0e7ff));
2529
- color: var(--select-option-selected-active-color, var(--select-option-selected-color, #4338ca));
2530
- border: var(--select-option-selected-active-border, var(--select-option-selected-border, var(--select-option-border, none)));
2531
- border-bottom: var(--select-option-selected-active-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2532
- box-shadow: var(--select-option-selected-active-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2533
- transform: var(--select-option-selected-active-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2534
- outline: var(--select-option-selected-active-outline, var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45)));
2535
- outline-offset: -2px;
2933
+ outline: var(--select-option-selected-active-outline, 2px solid rgba(15, 52, 96, 0.4));
2934
+ outline-offset: var(--select-option-selected-active-outline-offset);
2536
2935
  }
2537
2936
 
2538
2937
  .option:active:not(.selected) {
2539
- background: var(--select-option-pressed-bg, #e5e7eb);
2938
+ background: var(--select-option-pressed-bg, rgba(15, 52, 96, 0.08));
2939
+ transform: var(--select-option-pressed-transform);
2540
2940
  }
2541
2941
 
2542
2942
  .option.selected:active {
2543
- background: var(--select-option-selected-pressed-bg, var(--select-option-selected-hover-bg, var(--select-option-selected-bg, #e0e7ff)));
2943
+ transform: var(--select-option-selected-pressed-transform);
2544
2944
  }
2545
2945
 
2946
+ /* ─────────────────────────────────────────────────────────────────────────
2947
+ Load More & Busy States — Refined feedback indicators
2948
+ ───────────────────────────────────────────────────────────────────────── */
2949
+
2546
2950
  .load-more-container {
2547
2951
  padding: var(--select-load-more-padding, 12px);
2548
2952
  text-align: center;
2549
- border-top: var(--select-divider-border, 1px solid #e0e0e0);
2550
- background: var(--select-load-more-bg, white);
2551
2953
  }
2552
2954
 
2553
2955
  .load-more-button {
2554
- padding: var(--select-button-padding, 8px 16px);
2555
- border: var(--select-button-border, 1px solid #1976d2);
2556
- background: var(--select-button-bg, white);
2557
- color: var(--select-button-color, #1976d2);
2558
- border-radius: var(--select-button-border-radius, 4px);
2956
+ padding: var(--select-button-padding, 10px 20px);
2957
+ border: var(--select-button-border, 1.5px solid var(--select-border));
2958
+ background: var(--select-button-bg, transparent);
2959
+ color: var(--select-button-color, var(--select-accent));
2960
+ border-radius: var(--select-button-border-radius, var(--select-radius-md));
2559
2961
  cursor: pointer;
2560
- font-size: var(--select-button-font-size, 14px);
2561
- font-family: var(--select-font-family, inherit);
2562
- transition: all 0.2s ease;
2962
+ font-size: var(--select-button-font-size, 13px);
2963
+ font-weight: var(--select-button-font-weight);
2964
+ letter-spacing: var(--select-button-letter-spacing);
2965
+ transition:
2966
+ background var(--select-transition-fast),
2967
+ border-color var(--select-transition-fast),
2968
+ color var(--select-transition-fast),
2969
+ transform var(--select-transition-bounce);
2563
2970
  }
2564
2971
 
2565
2972
  .load-more-button:hover {
2566
- background: var(--select-button-hover-bg, #1976d2);
2973
+ background: var(--select-button-hover-bg, var(--select-accent));
2974
+ border-color: var(--select-accent);
2567
2975
  color: var(--select-button-hover-color, white);
2976
+ transform: var(--select-button-hover-transform);
2977
+ }
2978
+
2979
+ .load-more-button:active {
2980
+ transform: var(--select-button-active-transform);
2568
2981
  }
2569
2982
 
2570
2983
  .load-more-button:disabled {
2571
2984
  opacity: var(--select-button-disabled-opacity, 0.5);
2572
2985
  cursor: not-allowed;
2986
+ transform: none;
2573
2987
  }
2574
2988
 
2575
2989
  .busy-bucket {
2576
- padding: var(--select-busy-padding, 16px);
2990
+ padding: var(--select-busy-padding, 20px);
2577
2991
  text-align: center;
2578
- color: var(--select-busy-color, #666);
2579
- background: var(--select-busy-bg, white);
2580
- font-size: var(--select-busy-font-size, 14px);
2992
+ color: var(--select-busy-color, var(--select-text-muted));
2993
+ background: var(--select-busy-bg, transparent);
2994
+ font-size: var(--select-busy-font-size, 13px);
2581
2995
  }
2582
2996
 
2583
2997
  .spinner {
2584
2998
  display: inline-block;
2585
- width: var(--select-spinner-size, 20px);
2586
- height: var(--select-spinner-size, 20px);
2587
- border: var(--select-spinner-border, 2px solid #ccc);
2588
- border-top-color: var(--select-spinner-active-color, #1976d2);
2999
+ width: var(--select-spinner-size, 22px);
3000
+ height: var(--select-spinner-size, 22px);
3001
+ border: var(--select-spinner-border, 2px solid var(--select-border));
3002
+ border-top-color: var(--select-spinner-active-color, var(--select-accent));
2589
3003
  border-radius: 50%;
2590
- animation: spin 0.6s linear infinite;
3004
+ animation: var(--select-spinner-animation);
2591
3005
  }
2592
3006
 
2593
3007
  @keyframes spin {
@@ -2595,61 +3009,97 @@ class EnhancedSelect extends HTMLElement {
2595
3009
  }
2596
3010
 
2597
3011
  .empty-state {
2598
- padding: var(--select-empty-padding, 24px);
3012
+ padding: var(--select-empty-padding, 32px 24px);
2599
3013
  text-align: center;
2600
- color: var(--select-empty-color, #6b7280);
3014
+ color: var(--select-empty-color, var(--select-text-muted));
2601
3015
  font-size: var(--select-empty-font-size, 14px);
2602
- background: var(--select-empty-bg, white);
3016
+ background: var(--select-empty-bg, transparent);
2603
3017
  display: flex;
2604
3018
  flex-direction: column;
2605
3019
  align-items: center;
2606
3020
  justify-content: center;
2607
- gap: 6px;
2608
- min-height: var(--select-empty-min-height, 72px);
3021
+ gap: var(--select-empty-gap);
2609
3022
  }
2610
3023
 
2611
3024
  .searching-state {
2612
- padding: var(--select-searching-padding, 24px);
3025
+ padding: var(--select-searching-padding, 32px 24px);
2613
3026
  text-align: center;
2614
- color: var(--select-searching-color, #667eea);
3027
+ color: var(--select-searching-color, var(--select-accent));
2615
3028
  font-size: var(--select-searching-font-size, 14px);
2616
- font-style: italic;
2617
- background: var(--select-searching-bg, white);
2618
- animation: pulse 1.5s ease-in-out infinite;
3029
+ background: var(--select-searching-bg, transparent);
2619
3030
  display: flex;
2620
3031
  flex-direction: column;
2621
3032
  align-items: center;
2622
3033
  justify-content: center;
2623
- gap: 6px;
2624
- min-height: var(--select-searching-min-height, 72px);
3034
+ gap: var(--select-searching-gap);
2625
3035
  }
2626
3036
 
2627
- @keyframes pulse {
2628
- 0%, 100% { opacity: 1; }
2629
- 50% { opacity: 0.5; }
3037
+ .searching-state::before {
3038
+ content: '';
3039
+ width: var(--select-searching-spinner-size);
3040
+ height: var(--select-searching-spinner-size);
3041
+ border: var(--select-searching-spinner-border);
3042
+ border-top-color: var(--select-searching-spinner-active-color);
3043
+ border-radius: 50%;
3044
+ animation: var(--select-searching-spinner-animation);
2630
3045
  }
2631
3046
 
2632
- /* Error states */
3047
+ /* ─────────────────────────────────────────────────────────────────────────
3048
+ Error States — Clear visual feedback
3049
+ ───────────────────────────────────────────────────────────────────────── */
3050
+
2633
3051
  .select-input[aria-invalid="true"] {
2634
- border-color: var(--select-error-border, #dc2626);
3052
+ border-color: var(--select-error-border, #e94560);
2635
3053
  }
2636
3054
 
2637
3055
  .select-input[aria-invalid="true"]:focus {
2638
- border-color: var(--select-error-border, #dc2626);
2639
- box-shadow: 0 0 0 2px var(--select-error-shadow, rgba(220, 38, 38, 0.1));
2640
- outline-color: var(--select-error-border, #dc2626);
3056
+ border-color: var(--select-error-border, #e94560);
3057
+ box-shadow: 0 0 0 3px var(--select-error-shadow, rgba(233, 69, 96, 0.15));
2641
3058
  }
2642
3059
 
2643
- /* Accessibility: Reduced motion */
3060
+ /* ──────────────────────────────────────────────────��──────────────────────
3061
+ Accessibility — Reduced motion & High contrast
3062
+ ───────────────────────────────────────────────────────────────────────── */
3063
+
2644
3064
  @media (prefers-reduced-motion: reduce) {
2645
- * {
2646
- animation-duration: 0.01ms !important;
2647
- animation-iteration-count: 1 !important;
2648
- transition-duration: 0.01ms !important;
3065
+ *,
3066
+ *::before,
3067
+ *::after {
3068
+ animation-duration: var(--select-reduced-motion-duration) !important;
3069
+ animation-iteration-count: var(--select-reduced-motion-iteration-count) !important;
3070
+ transition-duration: var(--select-reduced-motion-duration) !important;
3071
+ }
3072
+
3073
+ .dropdown-arrow.open {
3074
+ transform: var(--select-arrow-open-transform);
2649
3075
  }
2650
3076
  }
2651
3077
 
2652
- /* Dark mode - Opt-in via class, data attribute, or ancestor context */
3078
+ @media (prefers-contrast: high) {
3079
+ .select-input:focus {
3080
+ outline-width: var(--select-high-contrast-focus-outline-width);
3081
+ outline-color: var(--select-high-contrast-focus-outline-color);
3082
+ }
3083
+
3084
+ .input-container {
3085
+ border-width: var(--select-high-contrast-border-width);
3086
+ }
3087
+
3088
+ .option.selected::before {
3089
+ width: var(--select-high-contrast-indicator-width);
3090
+ }
3091
+ }
3092
+
3093
+ /* Touch targets (WCAG 2.5.5) */
3094
+ .load-more-button,
3095
+ select-option {
3096
+ min-height: var(--select-touch-target-min-height);
3097
+ }
3098
+
3099
+ /* ─────────────────────────────────────────────────────────────────────────
3100
+ Dark Mode — Elegant dark theme
3101
+ ───────────────────────────────────────────────────────────────────────── */
3102
+
2653
3103
  :host(.dark-mode),
2654
3104
  :host([dark-mode]),
2655
3105
  :host([darkmode]),
@@ -2661,139 +3111,129 @@ class EnhancedSelect extends HTMLElement {
2661
3111
  :host-context([darkmode]),
2662
3112
  :host-context([data-theme="dark"]),
2663
3113
  :host-context([theme="dark"]) {
2664
- /* map dark tokens to base option tokens so nested <select-option>
2665
- components also pick up dark mode via inherited CSS variables */
2666
- --select-option-bg: var(--select-dark-option-bg, #1f2937);
2667
- --select-option-color: var(--select-dark-option-color, #f9fafb);
2668
- --select-option-hover-bg: var(--select-dark-option-hover-bg, #374151);
2669
- --select-option-hover-color: var(--select-dark-option-hover-color, #f9fafb);
2670
- --select-option-selected-bg: var(--select-dark-option-selected-bg, #3730a3);
2671
- --select-option-selected-color: var(--select-dark-option-selected-text, #e0e7ff);
2672
-
2673
- .input-container {
2674
- background: var(--select-dark-bg, #1f2937);
2675
- border-color: var(--select-dark-border, #4b5563);
2676
- }
2677
-
2678
- .select-input {
2679
- color: var(--select-dark-text, #f9fafb);
2680
- }
2681
-
2682
- .select-input::placeholder {
2683
- color: var(--select-dark-placeholder, #6b7280);
2684
- }
2685
-
2686
- .select-dropdown {
2687
- background: var(--select-dark-dropdown-bg, #1f2937);
2688
- border-color: var(--select-dark-dropdown-border, #4b5563);
2689
- }
3114
+ --select-primary: #e5e5e5;
3115
+ --select-primary-light: #2a2a3e;
3116
+ --select-accent: #6366f1;
3117
+ --select-accent-hover: #f43f5e;
3118
+ --select-surface: #1a1a2e;
3119
+ --select-surface-elevated: #252540;
3120
+ --select-border: #3f3f5a;
3121
+ --select-border-focus: #6366f1;
3122
+ --select-text: #f5f5f5;
3123
+ --select-text-muted: #9ca3af;
3124
+ --select-text-placeholder: #6b7280;
3125
+ --select-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
3126
+ --select-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
3127
+ --select-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
3128
+ --select-shadow-focus: 0 0 0 3px rgba(99, 102, 241, 0.25);
2690
3129
 
2691
- .options-container {
2692
- background: var(--select-dark-options-bg, #1f2937);
2693
- }
2694
-
2695
- .option {
2696
- color: var(--select-dark-option-color, #f9fafb);
2697
- background: var(--select-dark-option-bg, #1f2937);
2698
- }
2699
-
2700
- .option:hover {
2701
- background: var(--select-dark-option-hover-bg, #374151);
2702
- color: var(--select-dark-option-hover-color, #f9fafb);
2703
- }
2704
-
2705
- .option.selected {
2706
- background: var(--select-dark-option-selected-bg, #3730a3);
2707
- color: var(--select-dark-option-selected-text, #e0e7ff);
2708
- border: var(--select-dark-option-selected-border, var(--select-option-selected-border, var(--select-option-border, none)));
2709
- border-bottom: var(--select-dark-option-selected-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
2710
- box-shadow: var(--select-dark-option-selected-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
2711
- transform: var(--select-dark-option-selected-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
2712
- }
2713
-
2714
- .option.selected:hover {
2715
- background: var(--select-dark-option-selected-hover-bg, var(--select-dark-option-selected-bg, #3730a3));
2716
- color: var(--select-dark-option-selected-hover-color, var(--select-dark-option-selected-text, #e0e7ff));
2717
- 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)))));
2718
- 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)))));
2719
- 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)))));
2720
- 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)))));
2721
- }
2722
-
2723
- .option.active:not(.selected) {
2724
- background-color: var(--select-dark-option-active-bg, #374151);
2725
- color: var(--select-dark-option-active-color, #f9fafb);
2726
- outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
2727
- }
2728
-
2729
- /* Group header in dark mode */
2730
- .group-header {
2731
- color: var(--select-dark-group-header-color, var(--select-group-header-color, #6b7280));
2732
- background-color: var(--select-dark-group-header-bg, var(--select-group-header-bg, #374151));
2733
- }
3130
+ --select-option-bg: transparent;
3131
+ --select-option-color: var(--select-text);
3132
+ --select-option-hover-bg: var(--select-surface-elevated);
3133
+ --select-option-hover-color: var(--select-text);
3134
+ --select-option-selected-bg: linear-gradient(135deg, rgba(99, 102, 241, 0.15) 0%, rgba(99, 102, 241, 0.08) 100%);
3135
+ --select-option-selected-color: #a5b4fc;
3136
+ }
2734
3137
 
2735
- .option.selected.active {
2736
- background-color: var(--select-dark-option-selected-active-bg, var(--select-dark-option-selected-bg, #3730a3));
2737
- color: var(--select-dark-option-selected-active-color, var(--select-dark-option-selected-text, #e0e7ff));
2738
- 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)))));
2739
- 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)))));
2740
- 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)))));
2741
- 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)))));
2742
- 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)))));
2743
- outline-offset: -2px;
2744
- }
3138
+ :host(.dark-mode) .input-container,
3139
+ :host([dark-mode]) .input-container,
3140
+ :host([darkmode]) .input-container,
3141
+ :host([data-theme="dark"]) .input-container,
3142
+ :host([theme="dark"]) .input-container,
3143
+ :host-context(.dark-mode) .input-container,
3144
+ :host-context(.dark) .input-container,
3145
+ :host-context([dark-mode]) .input-container,
3146
+ :host-context([darkmode]) .input-container,
3147
+ :host-context([data-theme="dark"]) .input-container,
3148
+ :host-context([theme="dark"]) .input-container {
3149
+ background: var(--select-surface);
3150
+ border-color: var(--select-border);
3151
+ }
2745
3152
 
2746
- .selection-badge {
2747
- background: var(--select-dark-badge-bg, #4f46e5);
2748
- color: var(--select-dark-badge-color, #eef2ff);
2749
- }
3153
+ :host(.dark-mode) .input-container::after,
3154
+ :host([dark-mode]) .input-container::after,
3155
+ :host([darkmode]) .input-container::after,
3156
+ :host([data-theme="dark"]) .input-container::after,
3157
+ :host([theme="dark"]) .input-container::after,
3158
+ :host-context(.dark-mode) .input-container::after,
3159
+ :host-context(.dark) .input-container::after,
3160
+ :host-context([dark-mode]) .input-container::after,
3161
+ :host-context([darkmode]) .input-container::after,
3162
+ :host-context([data-theme="dark"]) .input-container::after,
3163
+ :host-context([theme="dark"]) .input-container::after {
3164
+ background: var(--select-separator-dark-bg);
3165
+ }
2750
3166
 
2751
- .badge-remove {
2752
- background: var(--select-dark-badge-remove-bg, rgba(255, 255, 255, 0.15));
2753
- color: var(--select-dark-badge-remove-color, #e5e7eb);
2754
- }
3167
+ :host(.dark-mode) .select-dropdown,
3168
+ :host([dark-mode]) .select-dropdown,
3169
+ :host([darkmode]) .select-dropdown,
3170
+ :host([data-theme="dark"]) .select-dropdown,
3171
+ :host([theme="dark"]) .select-dropdown,
3172
+ :host-context(.dark-mode) .select-dropdown,
3173
+ :host-context(.dark) .select-dropdown,
3174
+ :host-context([dark-mode]) .select-dropdown,
3175
+ :host-context([darkmode]) .select-dropdown,
3176
+ :host-context([data-theme="dark"]) .select-dropdown,
3177
+ :host-context([theme="dark"]) .select-dropdown {
3178
+ background: var(--select-surface);
3179
+ border-color: var(--select-border);
3180
+ }
2755
3181
 
2756
- .badge-remove:hover {
2757
- background: var(--select-dark-badge-remove-hover-bg, rgba(255, 255, 255, 0.3));
2758
- }
2759
-
2760
- .busy-bucket,
2761
- .empty-state {
2762
- color: var(--select-dark-busy-color, #9ca3af);
2763
- background: var(--select-dark-empty-bg, #111827);
2764
- }
3182
+ :host(.dark-mode) .options-container,
3183
+ :host([dark-mode]) .options-container,
3184
+ :host([darkmode]) .options-container,
3185
+ :host([data-theme="dark"]) .options-container,
3186
+ :host([theme="dark"]) .options-container,
3187
+ :host-context(.dark-mode) .options-container,
3188
+ :host-context(.dark) .options-container,
3189
+ :host-context([dark-mode]) .options-container,
3190
+ :host-context([darkmode]) .options-container,
3191
+ :host-context([data-theme="dark"]) .options-container,
3192
+ :host-context([theme="dark"]) .options-container {
3193
+ background: var(--select-surface);
3194
+ scrollbar-color: var(--select-border) transparent;
3195
+ }
2765
3196
 
2766
- .searching-state {
2767
- background: var(--select-dark-searching-bg, #111827);
2768
- }
2769
-
2770
- .input-container::after {
2771
- background: linear-gradient(
2772
- to bottom,
2773
- transparent 0%,
2774
- rgba(255, 255, 255, 0.1) 20%,
2775
- rgba(255, 255, 255, 0.1) 80%,
2776
- transparent 100%
2777
- );
2778
- }
3197
+ :host(.dark-mode) .selection-badge,
3198
+ :host([dark-mode]) .selection-badge,
3199
+ :host([darkmode]) .selection-badge,
3200
+ :host([data-theme="dark"]) .selection-badge,
3201
+ :host([theme="dark"]) .selection-badge,
3202
+ :host-context(.dark-mode) .selection-badge,
3203
+ :host-context(.dark) .selection-badge,
3204
+ :host-context([dark-mode]) .selection-badge,
3205
+ :host-context([darkmode]) .selection-badge,
3206
+ :host-context([data-theme="dark"]) .selection-badge,
3207
+ :host-context([theme="dark"]) .selection-badge {
3208
+ background: var(--select-badge-dark-bg);
2779
3209
  }
2780
-
2781
- /* Accessibility: High contrast mode */
2782
- @media (prefers-contrast: high) {
2783
- .select-input:focus {
2784
- outline-width: 3px;
2785
- outline-color: Highlight;
2786
- }
2787
-
2788
- .select-input {
2789
- border-width: 2px;
2790
- }
3210
+
3211
+ :host(.dark-mode) .group-header:not(:first-child),
3212
+ :host([dark-mode]) .group-header:not(:first-child),
3213
+ :host([darkmode]) .group-header:not(:first-child),
3214
+ :host([data-theme="dark"]) .group-header:not(:first-child),
3215
+ :host([theme="dark"]) .group-header:not(:first-child),
3216
+ :host-context(.dark-mode) .group-header:not(:first-child),
3217
+ :host-context(.dark) .group-header:not(:first-child),
3218
+ :host-context([dark-mode]) .group-header:not(:first-child),
3219
+ :host-context([darkmode]) .group-header:not(:first-child),
3220
+ :host-context([data-theme="dark"]) .group-header:not(:first-child),
3221
+ :host-context([theme="dark"]) .group-header:not(:first-child) {
3222
+ border-top-color: var(--select-border);
2791
3223
  }
2792
-
2793
- /* Touch targets (WCAG 2.5.5) */
2794
- .load-more-button,
2795
- select-option {
2796
- min-height: 44px;
3224
+
3225
+ :host(.dark-mode) .option.selected::before,
3226
+ :host([dark-mode]) .option.selected::before,
3227
+ :host([darkmode]) .option.selected::before,
3228
+ :host([data-theme="dark"]) .option.selected::before,
3229
+ :host([theme="dark"]) .option.selected::before,
3230
+ :host-context(.dark-mode) .option.selected::before,
3231
+ :host-context(.dark) .option.selected::before,
3232
+ :host-context([dark-mode]) .option.selected::before,
3233
+ :host-context([darkmode]) .option.selected::before,
3234
+ :host-context([data-theme="dark"]) .option.selected::before,
3235
+ :host-context([theme="dark"]) .option.selected::before {
3236
+ background: var(--select-accent);
2797
3237
  }
2798
3238
  `;
2799
3239
  // Insert as first child to ensure styles are processed first
@@ -2813,10 +3253,10 @@ class EnhancedSelect extends HTMLElement {
2813
3253
  // delegate to the existing open/close helpers so we don't accidentally
2814
3254
  // drift out of sync with the logic in those methods (focus, events,
2815
3255
  // scroll-to-selected, etc.)
2816
- if (this._state.isOpen) {
3256
+ if (this._state.isOpen && this._config.selection.toggleOnTriggerClick !== false) {
2817
3257
  this._handleClose();
2818
3258
  }
2819
- else {
3259
+ else if (!this._state.isOpen) {
2820
3260
  this._handleOpen();
2821
3261
  }
2822
3262
  };
@@ -2839,7 +3279,7 @@ class EnhancedSelect extends HTMLElement {
2839
3279
  this._inputContainer.addEventListener('pointerdown', (e) => {
2840
3280
  // Prevent propagation to document click listener but do NOT preventDefault.
2841
3281
  // Allow default so browser events (click) on newly opened options still fire.
2842
- e.stopPropagation();
3282
+ // 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.
2843
3283
  const target = e.target;
2844
3284
  if (!this._config.enabled)
2845
3285
  return;
@@ -2847,21 +3287,23 @@ class EnhancedSelect extends HTMLElement {
2847
3287
  return;
2848
3288
  if (target && target.closest('.clear-control-button'))
2849
3289
  return;
3290
+ // If we clicked the container, but not the input itself, we must prevent default
3291
+ // otherwise the browser moves focus from our input to the body, immediately triggering blur.
3292
+ if (target && !target.matches('.select-input')) {
3293
+ e.preventDefault();
3294
+ }
3295
+ const clickedInput = Boolean(target && target.matches('.select-input'));
3296
+ if (this._state.isOpen &&
3297
+ this._config.selection.toggleOnTriggerClick !== false &&
3298
+ !(clickedInput && this._config.searchable)) {
3299
+ this._handleClose();
3300
+ return;
3301
+ }
2850
3302
  const wasClosed = !this._state.isOpen;
2851
3303
  if (wasClosed) {
2852
3304
  this._handleOpen();
2853
3305
  }
2854
- else {
2855
- // Keep open while interacting directly with the input so users can
2856
- // place cursor/type without accidental collapse.
2857
- if (target === this._input) {
2858
- this._input.focus();
2859
- return;
2860
- }
2861
- // clicking other parts of the input container while open toggles close
2862
- this._handleClose();
2863
- }
2864
- // Focus the input (do not prevent default behavior)
3306
+ // Focus the input (do not prevent default behavior for input itself)
2865
3307
  this._input.focus();
2866
3308
  // If we just opened the dropdown, transfer pointer capture to the
2867
3309
  // options container so the subsequent pointerup lands there instead of
@@ -2903,7 +3345,7 @@ class EnhancedSelect extends HTMLElement {
2903
3345
  return;
2904
3346
  }
2905
3347
  this._handleClose();
2906
- }, 0);
3348
+ }, 150);
2907
3349
  });
2908
3350
  // Input search
2909
3351
  this._input.addEventListener('input', (e) => {
@@ -2920,7 +3362,7 @@ class EnhancedSelect extends HTMLElement {
2920
3362
  this._suppressBlurClose = true;
2921
3363
  setTimeout(() => {
2922
3364
  this._suppressBlurClose = false;
2923
- }, 0);
3365
+ }, 150); // Increased timeout to ensure click finishes before blur checks
2924
3366
  });
2925
3367
  // Delegated click listener for improved event handling (robust across shadow DOM)
2926
3368
  const handleOptionEvent = (e) => {
@@ -2951,9 +3393,6 @@ class EnhancedSelect extends HTMLElement {
2951
3393
  }
2952
3394
  };
2953
3395
  this._optionsContainer.addEventListener('click', handleOptionEvent);
2954
- // also watch pointerup to catch cases where the pointerdown started outside
2955
- // (e.g. on the input) and the click never fires
2956
- this._optionsContainer.addEventListener('pointerup', handleOptionEvent);
2957
3396
  // Keyboard navigation
2958
3397
  this._input.addEventListener('keydown', (e) => this._handleKeydown(e));
2959
3398
  // Click outside to close — robust detection across shadow DOM and custom renderers
@@ -3051,17 +3490,6 @@ class EnhancedSelect extends HTMLElement {
3051
3490
  this._dropdown.style.display = 'block';
3052
3491
  this._input.setAttribute('aria-expanded', 'true');
3053
3492
  this._updateArrowRotation();
3054
- // Clear search query when opening to show all options
3055
- // This ensures we can scroll to selected item
3056
- if (this._config.searchable) {
3057
- this._state.searchQuery = '';
3058
- // Don't clear input value if it represents selection
3059
- // But if we want to search, we might want to clear it?
3060
- // Standard behavior: input keeps value (label), but dropdown shows all options
3061
- // until user types.
3062
- // However, our filtering logic uses _state.searchQuery.
3063
- // So clearing it here resets the filter.
3064
- }
3065
3493
  // Render options when opening
3066
3494
  this._renderOptions();
3067
3495
  this._setInitialActiveOption();
@@ -3307,24 +3735,24 @@ class EnhancedSelect extends HTMLElement {
3307
3735
  }
3308
3736
  }
3309
3737
  _setActive(index) {
3310
- const options = Array.from(this._optionsContainer.children);
3311
3738
  // Clear previous active state
3312
- if (this._state.activeIndex >= 0 && options[this._state.activeIndex]) {
3313
- const prevOption = options[this._state.activeIndex];
3314
- // Check if it's a custom SelectOption or a lightweight DOM element
3315
- if ('setActive' in prevOption && typeof prevOption.setActive === 'function') {
3316
- prevOption.setActive(false);
3317
- }
3318
- else {
3319
- // Lightweight option - remove active class
3320
- prevOption.classList.remove('smilodon-option--active');
3321
- prevOption.setAttribute('aria-selected', 'false');
3739
+ if (this._state.activeIndex >= 0) {
3740
+ const prevOption = this._getOptionElementByIndex(this._state.activeIndex);
3741
+ if (prevOption) {
3742
+ // Check if it's a custom SelectOption or a lightweight DOM element
3743
+ if ('setActive' in prevOption && typeof prevOption.setActive === 'function') {
3744
+ prevOption.setActive(false);
3745
+ }
3746
+ else {
3747
+ // Lightweight option - remove active class
3748
+ prevOption.classList.remove('smilodon-option--active');
3749
+ }
3322
3750
  }
3323
3751
  }
3324
3752
  this._state.activeIndex = index;
3325
3753
  // Set new active state
3326
- if (options[index]) {
3327
- const option = options[index];
3754
+ const option = this._getOptionElementByIndex(index);
3755
+ if (option) {
3328
3756
  // Check if it's a custom SelectOption or a lightweight DOM element
3329
3757
  if ('setActive' in option && typeof option.setActive === 'function') {
3330
3758
  option.setActive(true);
@@ -3332,13 +3760,13 @@ class EnhancedSelect extends HTMLElement {
3332
3760
  else {
3333
3761
  // Lightweight option - add active class
3334
3762
  option.classList.add('smilodon-option--active');
3335
- option.setAttribute('aria-selected', 'true');
3336
3763
  }
3337
3764
  if (typeof option.scrollIntoView === 'function') {
3765
+ // Don't scroll wildly when just opening with pre-selections
3338
3766
  option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
3339
3767
  }
3340
3768
  // Announce position for screen readers
3341
- const total = options.length;
3769
+ const total = this._state.loadedItems.length;
3342
3770
  this._announce(`Item ${index + 1} of ${total}`);
3343
3771
  // Update aria-activedescendant using the actual option id when available
3344
3772
  const optionId = option.id || `${this._uniqueId}-option-${index}`;
@@ -3461,9 +3889,9 @@ class EnhancedSelect extends HTMLElement {
3461
3889
  // FIX: Do not rely on this._optionsContainer.children[index] because filtering changes the children
3462
3890
  // Instead, use the index to update state directly
3463
3891
  const item = this._state.loadedItems[index];
3464
- // Debug: log selection attempt
3465
- if (!item)
3892
+ if (!item) {
3466
3893
  return;
3894
+ }
3467
3895
  const isCurrentlySelected = this._state.selectedIndices.has(index);
3468
3896
  // Keep active/focus styling aligned with the most recently interacted option.
3469
3897
  // Without this, a previously selected item may retain active classes/styles
@@ -3474,7 +3902,7 @@ class EnhancedSelect extends HTMLElement {
3474
3902
  const wasSelected = this._state.selectedIndices.has(index);
3475
3903
  this._state.selectedIndices.clear();
3476
3904
  this._state.selectedItems.clear();
3477
- if (!wasSelected) {
3905
+ if (!wasSelected || !this._config.selection.allowDeselect) {
3478
3906
  // Select this option
3479
3907
  this._state.selectedIndices.add(index);
3480
3908
  this._state.selectedItems.set(index, item);
@@ -3536,16 +3964,34 @@ class EnhancedSelect extends HTMLElement {
3536
3964
  });
3537
3965
  }
3538
3966
  _handleOptionRemove(index) {
3967
+ const item = this._state.selectedItems.get(index);
3539
3968
  const option = this._getOptionElementByIndex(index);
3540
- if (!option)
3541
- return;
3542
3969
  this._state.selectedIndices.delete(index);
3543
3970
  this._state.selectedItems.delete(index);
3544
- option.setSelected(false);
3971
+ if (option && 'setSelected' in option && typeof option.setSelected === 'function') {
3972
+ option.setSelected(false);
3973
+ }
3974
+ else if (option) {
3975
+ option.classList.remove('selected', 'sm-selected', 'smilodon-option--selected');
3976
+ option.setAttribute('aria-selected', 'false');
3977
+ const stateTokens = (option.dataset.smState || '')
3978
+ .split(' ')
3979
+ .map(token => token.trim())
3980
+ .filter(token => token && token !== 'selected');
3981
+ if (stateTokens.length > 0) {
3982
+ option.dataset.smState = stateTokens.join(' ');
3983
+ }
3984
+ else {
3985
+ delete option.dataset.smState;
3986
+ }
3987
+ }
3545
3988
  this._updateInputDisplay();
3989
+ this._renderOptions();
3546
3990
  this._emitChange();
3547
- const config = option.getConfig();
3548
- this._emit('remove', { item: config.item, index });
3991
+ const config = option && 'getConfig' in option && typeof option.getConfig === 'function'
3992
+ ? option.getConfig()
3993
+ : undefined;
3994
+ this._emit('remove', { item: config?.item ?? item, index });
3549
3995
  }
3550
3996
  _updateInputDisplay() {
3551
3997
  const selectedItems = Array.from(this._state.selectedItems.values());
@@ -3573,22 +4019,29 @@ class EnhancedSelect extends HTMLElement {
3573
4019
  const badge = document.createElement('span');
3574
4020
  badge.className = 'selection-badge';
3575
4021
  badge.setAttribute('part', 'chip');
3576
- badge.textContent = getLabel(item);
4022
+ const badgeLabel = document.createElement('span');
4023
+ badgeLabel.className = 'selection-badge-label';
4024
+ badgeLabel.textContent = getLabel(item);
4025
+ badge.appendChild(badgeLabel);
3577
4026
  // Add remove button to badge
3578
- const removeBtn = document.createElement('button');
3579
- removeBtn.className = 'badge-remove';
3580
- removeBtn.setAttribute('part', 'chip-remove');
3581
- removeBtn.innerHTML = '×';
3582
- removeBtn.setAttribute('aria-label', `Remove ${getLabel(item)}`);
3583
- removeBtn.addEventListener('click', (e) => {
3584
- e.stopPropagation();
3585
- this._state.selectedIndices.delete(index);
3586
- this._state.selectedItems.delete(index);
3587
- this._updateInputDisplay();
3588
- this._renderOptions();
3589
- this._emitChange();
3590
- });
3591
- badge.appendChild(removeBtn);
4027
+ if (this._config.selection.showRemoveButton !== false) {
4028
+ const removeBtn = document.createElement('button');
4029
+ removeBtn.type = 'button';
4030
+ removeBtn.className = 'badge-remove';
4031
+ removeBtn.setAttribute('part', 'chip-remove');
4032
+ removeBtn.innerHTML = '×';
4033
+ removeBtn.setAttribute('aria-label', `Remove ${getLabel(item)}`);
4034
+ removeBtn.addEventListener('pointerdown', (e) => {
4035
+ e.stopPropagation();
4036
+ e.preventDefault();
4037
+ });
4038
+ removeBtn.addEventListener('click', (e) => {
4039
+ e.stopPropagation();
4040
+ e.preventDefault();
4041
+ this._handleOptionRemove(index);
4042
+ });
4043
+ badge.appendChild(removeBtn);
4044
+ }
3592
4045
  this._inputContainer.insertBefore(badge, this._input);
3593
4046
  });
3594
4047
  }
@@ -3640,10 +4093,12 @@ class EnhancedSelect extends HTMLElement {
3640
4093
  const option = this._getOptionElementByIndex(targetIndex);
3641
4094
  if (option) {
3642
4095
  // Use smooth scrolling with center alignment for better UX
3643
- option.scrollIntoView({
3644
- block: this._config.scrollToSelected.block || 'center',
3645
- behavior: 'smooth',
3646
- });
4096
+ if (typeof option.scrollIntoView === 'function') {
4097
+ option.scrollIntoView({
4098
+ block: this._config.scrollToSelected.block || 'center',
4099
+ behavior: 'smooth',
4100
+ });
4101
+ }
3647
4102
  // Also set it as active for keyboard navigation
3648
4103
  this._setActive(targetIndex);
3649
4104
  }
@@ -3855,7 +4310,6 @@ class EnhancedSelect extends HTMLElement {
3855
4310
  const getValue = this._config.serverSide.getValueFromItem || ((item) => item?.value ?? item);
3856
4311
  const selectedValues = selectedItems.map(getValue);
3857
4312
  const selectedIndices = Array.from(this._state.selectedIndices);
3858
- // Debug: log change payload
3859
4313
  this._emit('change', { selectedItems, selectedValues, selectedIndices });
3860
4314
  this._config.callbacks.onChange?.(selectedItems, selectedValues);
3861
4315
  }
@@ -4059,7 +4513,7 @@ class EnhancedSelect extends HTMLElement {
4059
4513
  this._clearControl.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
4060
4514
  }
4061
4515
  if (this._clearControlIcon) {
4062
- this._clearControlIcon.textContent = this._config.clearControl.icon || '×';
4516
+ 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>`;
4063
4517
  }
4064
4518
  if (this._dropdown) {
4065
4519
  if (this._config.selection.mode === 'multi') {
@@ -4069,6 +4523,7 @@ class EnhancedSelect extends HTMLElement {
4069
4523
  this._dropdown.removeAttribute('aria-multiselectable');
4070
4524
  }
4071
4525
  }
4526
+ this._syncInputContainerMode();
4072
4527
  // Re-initialize observers in case infinite scroll was enabled/disabled
4073
4528
  this._initializeObservers();
4074
4529
  this._syncClearControlState();
@@ -4174,6 +4629,8 @@ class EnhancedSelect extends HTMLElement {
4174
4629
  * Render options based on current state
4175
4630
  */
4176
4631
  _renderOptions() {
4632
+ this._renderCycleId += 1;
4633
+ const renderCycleId = this._renderCycleId;
4177
4634
  // Cleanup observer
4178
4635
  if (this._loadMoreTrigger && this._intersectionObserver) {
4179
4636
  this._intersectionObserver.unobserve(this._loadMoreTrigger);
@@ -4238,23 +4695,21 @@ class EnhancedSelect extends HTMLElement {
4238
4695
  }
4239
4696
  else {
4240
4697
  // Normal rendering (flat list or filtered)
4241
- let hasRenderedItems = false;
4698
+ const filteredIndices = [];
4242
4699
  this._state.loadedItems.forEach((item, index) => {
4243
- // Apply filter if query exists
4244
4700
  if (query) {
4245
4701
  try {
4246
4702
  const label = String(getLabel(item)).toLowerCase();
4247
4703
  if (!label.includes(query))
4248
4704
  return;
4249
4705
  }
4250
- catch (e) {
4706
+ catch (_e) {
4251
4707
  return;
4252
4708
  }
4253
4709
  }
4254
- hasRenderedItems = true;
4255
- this._renderSingleOption(item, index, getValue, getLabel);
4710
+ filteredIndices.push(index);
4256
4711
  });
4257
- if (!hasRenderedItems && !this._state.isBusy) {
4712
+ if (filteredIndices.length === 0 && !this._state.isBusy) {
4258
4713
  const empty = document.createElement('div');
4259
4714
  empty.setAttribute('part', 'no-results');
4260
4715
  empty.className = 'empty-state';
@@ -4266,6 +4721,54 @@ class EnhancedSelect extends HTMLElement {
4266
4721
  }
4267
4722
  this._optionsContainer.appendChild(empty);
4268
4723
  }
4724
+ else {
4725
+ const shouldIncrementalRender = this._config.virtualize !== false
4726
+ && this._state.groupedItems.length === 0
4727
+ && filteredIndices.length > 300;
4728
+ if (shouldIncrementalRender) {
4729
+ const chunkSize = 80;
4730
+ let cursor = 0;
4731
+ let maxRenderTarget = 0;
4732
+ if (this._state.selectedIndices.size > 0 && this._config.scrollToSelected.enabled) {
4733
+ const indices = Array.from(this._state.selectedIndices).sort((a, b) => a - b);
4734
+ const targetIndex = this._config.scrollToSelected.multiSelectTarget === 'first' ? indices[0] : indices[indices.length - 1];
4735
+ const filteredPos = filteredIndices.indexOf(targetIndex);
4736
+ if (filteredPos !== -1) {
4737
+ maxRenderTarget = filteredPos + 20; // Ensure we render up to the selection
4738
+ }
4739
+ }
4740
+ const renderChunk = () => {
4741
+ if (renderCycleId !== this._renderCycleId)
4742
+ return;
4743
+ const fragment = document.createDocumentFragment();
4744
+ const chunkEnd = Math.min(Math.max(cursor + chunkSize, maxRenderTarget), filteredIndices.length);
4745
+ maxRenderTarget = 0; // Reset after fast-forwarding
4746
+ for (; cursor < chunkEnd; cursor += 1) {
4747
+ const itemIndex = filteredIndices[cursor];
4748
+ const item = this._state.loadedItems[itemIndex];
4749
+ this._renderSingleOption(item, itemIndex, getValue, getLabel, fragment);
4750
+ }
4751
+ this._optionsContainer.appendChild(fragment);
4752
+ if (cursor < filteredIndices.length) {
4753
+ requestAnimationFrame(renderChunk);
4754
+ }
4755
+ else {
4756
+ if (renderCycleId !== this._renderCycleId)
4757
+ return;
4758
+ if (!this._state.isBusy && (this._config.loadMore.enabled || this._config.infiniteScroll.enabled) && this._state.loadedItems.length > 0) {
4759
+ this._addLoadMoreTrigger();
4760
+ }
4761
+ this._finalizePerfMarks();
4762
+ }
4763
+ };
4764
+ renderChunk();
4765
+ return;
4766
+ }
4767
+ filteredIndices.forEach((itemIndex) => {
4768
+ const item = this._state.loadedItems[itemIndex];
4769
+ this._renderSingleOption(item, itemIndex, getValue, getLabel);
4770
+ });
4771
+ }
4269
4772
  }
4270
4773
  // Append Busy Indicator if busy
4271
4774
  if (this._state.isBusy && this._config.busyBucket.enabled) {
@@ -4290,7 +4793,7 @@ class EnhancedSelect extends HTMLElement {
4290
4793
  }
4291
4794
  this._finalizePerfMarks();
4292
4795
  }
4293
- _renderSingleOption(item, index, getValue, getLabel) {
4796
+ _renderSingleOption(item, index, getValue, getLabel, targetContainer = this._optionsContainer) {
4294
4797
  const isSelected = this._state.selectedIndices.has(index);
4295
4798
  const isDisabled = Boolean(item?.disabled);
4296
4799
  const optionId = `${this._uniqueId}-option-${index}`;
@@ -4306,7 +4809,7 @@ class EnhancedSelect extends HTMLElement {
4306
4809
  disabled: isDisabled,
4307
4810
  id: optionId,
4308
4811
  });
4309
- this._optionsContainer.appendChild(optionElement);
4812
+ targetContainer.appendChild(optionElement);
4310
4813
  return;
4311
4814
  }
4312
4815
  const option = new SelectOption({
@@ -4335,20 +4838,16 @@ class EnhancedSelect extends HTMLElement {
4335
4838
  option.dataset.smValue = String(val);
4336
4839
  }
4337
4840
  option.id = option.id || optionId;
4338
- option.addEventListener('click', (e) => {
4339
- e.stopPropagation(); // Prevent duplicate handling by delegation
4340
- const mouseEvent = e;
4341
- this._selectOption(index, {
4342
- shiftKey: mouseEvent.shiftKey,
4343
- toggleKey: mouseEvent.ctrlKey || mouseEvent.metaKey,
4344
- });
4345
- });
4841
+ // Do NOT bind a native click listener here for SelectOption elements.
4842
+ // Like custom rendered options, they are fully handled by the `handleOptionEvent` delegator
4843
+ // on `this._optionsContainer` (line 1221).
4844
+ // Adding this listener causes the double-click toggle bug since both fire on selection!
4346
4845
  option.addEventListener('optionRemove', (event) => {
4347
4846
  const detail = event.detail;
4348
4847
  const targetIndex = detail?.index ?? index;
4349
4848
  this._handleOptionRemove(targetIndex);
4350
4849
  });
4351
- this._optionsContainer.appendChild(option);
4850
+ targetContainer.appendChild(option);
4352
4851
  }
4353
4852
  _normalizeCustomOptionElement(element, meta) {
4354
4853
  const optionEl = element instanceof HTMLElement ? element : document.createElement('div');
@@ -4435,35 +4934,9 @@ class EnhancedSelect extends HTMLElement {
4435
4934
  optionEl.tabIndex = -1;
4436
4935
  }
4437
4936
  if (!this._customOptionBoundElements.has(optionEl)) {
4438
- optionEl.addEventListener('click', (e) => {
4439
- e.stopPropagation();
4440
- const current = e.currentTarget;
4441
- if (current.getAttribute('aria-disabled') === 'true')
4442
- return;
4443
- const parsedIndex = Number(current.dataset.index);
4444
- if (!Number.isFinite(parsedIndex))
4445
- return;
4446
- const mouseEvent = e;
4447
- this._selectOption(parsedIndex, {
4448
- shiftKey: mouseEvent.shiftKey,
4449
- toggleKey: mouseEvent.ctrlKey || mouseEvent.metaKey,
4450
- });
4451
- });
4452
- optionEl.addEventListener('keydown', (e) => {
4453
- if (e.key !== 'Enter' && e.key !== ' ')
4454
- return;
4455
- const current = e.currentTarget;
4456
- if (current.getAttribute('aria-disabled') === 'true')
4457
- return;
4458
- const parsedIndex = Number(current.dataset.index);
4459
- if (!Number.isFinite(parsedIndex))
4460
- return;
4461
- e.preventDefault();
4462
- this._selectOption(parsedIndex, {
4463
- shiftKey: e.shiftKey,
4464
- toggleKey: e.ctrlKey || e.metaKey,
4465
- });
4466
- });
4937
+ // Intentionally NOT binding native option click listeners for custom options!
4938
+ // All option interactions are globally handled by _optionsContainer.addEventListener('click', handleOptionEvent);
4939
+ // Re-attaching here causes the double-click toggle bug if a child component fails to stopPropagation.
4467
4940
  this._customOptionBoundElements.add(optionEl);
4468
4941
  }
4469
4942
  return optionEl;