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