@smilodon/core 1.4.11 → 1.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -15
- package/dist/index.cjs +1134 -454
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1134 -454
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.umd.js +1134 -454
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/dist/types/src/components/enhanced-select.d.ts +16 -1
- package/dist/types/src/config/global-config.d.ts +27 -0
- package/dist/types/src/types.d.ts +58 -0
- package/dist/types/tests/capabilities-tracking.spec.d.ts +1 -0
- package/package.json +1 -1
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,
|
|
@@ -1401,6 +1402,18 @@ const defaultConfig = {
|
|
|
1401
1402
|
icon: '×',
|
|
1402
1403
|
},
|
|
1403
1404
|
callbacks: {},
|
|
1405
|
+
tracking: {
|
|
1406
|
+
enabled: false,
|
|
1407
|
+
events: true,
|
|
1408
|
+
styling: true,
|
|
1409
|
+
limitations: true,
|
|
1410
|
+
emitDiagnostics: false,
|
|
1411
|
+
maxEntries: 200,
|
|
1412
|
+
},
|
|
1413
|
+
limitations: {
|
|
1414
|
+
policies: {},
|
|
1415
|
+
autoMitigateRuntimeModeSwitch: true,
|
|
1416
|
+
},
|
|
1404
1417
|
enabled: true,
|
|
1405
1418
|
searchable: false,
|
|
1406
1419
|
placeholder: 'Select an option...',
|
|
@@ -1869,6 +1882,8 @@ if (!customElements.get('select-option')) {
|
|
|
1869
1882
|
* Enhanced Select Component
|
|
1870
1883
|
* Implements all advanced features: infinite scroll, load more, busy state,
|
|
1871
1884
|
* server-side selection, and full customization
|
|
1885
|
+
*
|
|
1886
|
+
* ✨ Redesigned with formal elegance, refined microinteractions, and polished UX
|
|
1872
1887
|
*/
|
|
1873
1888
|
class EnhancedSelect extends HTMLElement {
|
|
1874
1889
|
get classMap() {
|
|
@@ -1877,6 +1892,10 @@ class EnhancedSelect extends HTMLElement {
|
|
|
1877
1892
|
set classMap(map) {
|
|
1878
1893
|
this._classMap = map;
|
|
1879
1894
|
this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map || this._groupHeaderRenderer));
|
|
1895
|
+
this._track('style', 'classMapChanged', {
|
|
1896
|
+
hasClassMap: Boolean(map),
|
|
1897
|
+
keys: map ? Object.keys(map) : [],
|
|
1898
|
+
});
|
|
1880
1899
|
if (!this.isConnected)
|
|
1881
1900
|
return;
|
|
1882
1901
|
this._renderOptions();
|
|
@@ -1892,6 +1911,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
1892
1911
|
set groupHeaderRenderer(renderer) {
|
|
1893
1912
|
this._groupHeaderRenderer = renderer;
|
|
1894
1913
|
this._setGlobalStylesMirroring(Boolean(this._optionRenderer || this._classMap || renderer));
|
|
1914
|
+
this._track('style', 'groupHeaderRendererChanged', { enabled: Boolean(renderer) });
|
|
1895
1915
|
if (!this.isConnected)
|
|
1896
1916
|
return;
|
|
1897
1917
|
this._renderOptions();
|
|
@@ -1910,6 +1930,9 @@ class EnhancedSelect extends HTMLElement {
|
|
|
1910
1930
|
this._mirrorGlobalStylesForCustomOptions = false;
|
|
1911
1931
|
this._globalStylesObserver = null;
|
|
1912
1932
|
this._globalStylesContainer = null;
|
|
1933
|
+
this._tracking = { events: [], styles: [], limitations: [] };
|
|
1934
|
+
this._suppressBlurClose = false;
|
|
1935
|
+
this._renderCycleId = 0;
|
|
1913
1936
|
this._shadow = this.attachShadow({ mode: 'open' });
|
|
1914
1937
|
this._uniqueId = `enhanced-select-${Math.random().toString(36).substr(2, 9)}`;
|
|
1915
1938
|
this._rendererHelpers = this._buildRendererHelpers();
|
|
@@ -1984,6 +2007,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
1984
2007
|
if (this._boundArrowClick && this._arrowContainer) {
|
|
1985
2008
|
this._arrowContainer.removeEventListener('click', this._boundArrowClick);
|
|
1986
2009
|
}
|
|
2010
|
+
this._renderCycleId += 1;
|
|
1987
2011
|
this._teardownGlobalStylesMirroring();
|
|
1988
2012
|
}
|
|
1989
2013
|
_setGlobalStylesMirroring(enabled) {
|
|
@@ -1994,6 +2018,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
1994
2018
|
return;
|
|
1995
2019
|
}
|
|
1996
2020
|
this._mirrorGlobalStylesForCustomOptions = enabled;
|
|
2021
|
+
this._track('style', 'globalStylesMirroringChanged', { enabled });
|
|
1997
2022
|
if (enabled) {
|
|
1998
2023
|
this._setupGlobalStylesMirroring();
|
|
1999
2024
|
}
|
|
@@ -2086,8 +2111,48 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2086
2111
|
const container = document.createElement('div');
|
|
2087
2112
|
container.className = 'input-container';
|
|
2088
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
|
+
}
|
|
2089
2137
|
return container;
|
|
2090
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
|
+
}
|
|
2091
2156
|
_createInput() {
|
|
2092
2157
|
const input = document.createElement('input');
|
|
2093
2158
|
input.setAttribute('part', 'input');
|
|
@@ -2097,6 +2162,35 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2097
2162
|
input.placeholder = this._config.placeholder || 'Select an option...';
|
|
2098
2163
|
input.disabled = !this._config.enabled;
|
|
2099
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';
|
|
2100
2194
|
// Update readonly when input is focused if searchable
|
|
2101
2195
|
input.addEventListener('focus', () => {
|
|
2102
2196
|
if (this._config.searchable) {
|
|
@@ -2107,7 +2201,22 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2107
2201
|
input.className += ' ' + this._config.styles.classNames.input;
|
|
2108
2202
|
}
|
|
2109
2203
|
if (this._config.styles.input) {
|
|
2110
|
-
|
|
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);
|
|
2111
2220
|
}
|
|
2112
2221
|
input.setAttribute('role', 'combobox');
|
|
2113
2222
|
input.setAttribute('aria-expanded', 'false');
|
|
@@ -2151,7 +2260,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2151
2260
|
container.className = 'dropdown-arrow-container';
|
|
2152
2261
|
container.innerHTML = `
|
|
2153
2262
|
<svg class="dropdown-arrow" part="arrow" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2154
|
-
<path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="
|
|
2263
|
+
<path d="M4 6L8 10L12 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
2155
2264
|
</svg>
|
|
2156
2265
|
`;
|
|
2157
2266
|
return container;
|
|
@@ -2164,7 +2273,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2164
2273
|
const icon = document.createElement('span');
|
|
2165
2274
|
icon.className = 'clear-control-icon';
|
|
2166
2275
|
icon.setAttribute('part', 'clear-icon');
|
|
2167
|
-
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>`;
|
|
2168
2277
|
button.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
|
|
2169
2278
|
button.appendChild(icon);
|
|
2170
2279
|
this._clearControlIcon = icon;
|
|
@@ -2190,18 +2299,229 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2190
2299
|
this._dropdown.id = listboxId;
|
|
2191
2300
|
this._input.setAttribute('aria-controls', listboxId);
|
|
2192
2301
|
this._input.setAttribute('aria-owns', listboxId);
|
|
2302
|
+
this._syncInputContainerMode();
|
|
2193
2303
|
this._syncClearControlState();
|
|
2194
2304
|
}
|
|
2195
2305
|
_initializeStyles() {
|
|
2196
2306
|
const style = document.createElement('style');
|
|
2197
2307
|
style.textContent = `
|
|
2308
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
2309
|
+
ELEGANT SELECT COMPONENT — Refined Design System
|
|
2310
|
+
Formal aesthetics with sophisticated microinteractions
|
|
2311
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
2312
|
+
|
|
2198
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
|
+
|
|
2199
2421
|
display: block;
|
|
2200
2422
|
position: relative;
|
|
2201
2423
|
width: var(--select-width, 100%);
|
|
2202
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
|
+
}
|
|
2203
2469
|
}
|
|
2204
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
|
+
|
|
2205
2525
|
.select-container {
|
|
2206
2526
|
position: relative;
|
|
2207
2527
|
width: 100%;
|
|
@@ -2212,102 +2532,143 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2212
2532
|
width: 100%;
|
|
2213
2533
|
display: flex;
|
|
2214
2534
|
align-items: center;
|
|
2215
|
-
flex-wrap:
|
|
2535
|
+
flex-wrap: nowrap;
|
|
2216
2536
|
gap: var(--select-input-gap, 6px);
|
|
2217
|
-
padding: var(--select-input-padding,
|
|
2537
|
+
padding: var(--select-input-padding, 10px 52px 10px 14px);
|
|
2218
2538
|
height: var(--select-input-height, auto);
|
|
2219
|
-
min-height: var(--select-input-min-height,
|
|
2539
|
+
min-height: var(--select-input-min-height, 48px);
|
|
2220
2540
|
max-height: var(--select-input-max-height, 160px);
|
|
2221
2541
|
overflow-y: var(--select-input-overflow-y, auto);
|
|
2222
|
-
align-content:
|
|
2223
|
-
background: var(--select-input-bg, var(--select-
|
|
2224
|
-
border: var(--select-input-border,
|
|
2225
|
-
border-radius: var(--select-input-border-radius,
|
|
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);
|
|
2226
2547
|
box-sizing: border-box;
|
|
2227
|
-
transition:
|
|
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;
|
|
2228
2567
|
}
|
|
2229
2568
|
|
|
2230
2569
|
.input-container:focus-within {
|
|
2231
|
-
border-color: var(--select-input-focus-border, var(--select-border-focus
|
|
2232
|
-
box-shadow: var(--select-
|
|
2570
|
+
border-color: var(--select-input-focus-border, var(--select-border-focus));
|
|
2571
|
+
box-shadow: var(--select-shadow-focus), var(--select-shadow-sm);
|
|
2233
2572
|
}
|
|
2234
2573
|
|
|
2235
|
-
/*
|
|
2574
|
+
/* Elegant separator line before arrow */
|
|
2236
2575
|
.input-container::after {
|
|
2237
2576
|
content: '';
|
|
2238
2577
|
position: absolute;
|
|
2239
2578
|
top: 50%;
|
|
2240
|
-
right: var(--select-separator-position,
|
|
2579
|
+
right: var(--select-separator-position, 42px);
|
|
2241
2580
|
transform: translateY(-50%);
|
|
2242
|
-
width: var(--select-separator-width,
|
|
2243
|
-
height: var(--select-separator-height,
|
|
2244
|
-
background: var(--select-separator-bg,
|
|
2581
|
+
width: var(--select-separator-width, 1px);
|
|
2582
|
+
height: var(--select-separator-height, 50%);
|
|
2583
|
+
background: var(--select-separator-bg, linear-gradient(
|
|
2245
2584
|
to bottom,
|
|
2246
2585
|
transparent 0%,
|
|
2247
|
-
|
|
2248
|
-
|
|
2586
|
+
var(--select-border) 20%,
|
|
2587
|
+
var(--select-border) 80%,
|
|
2249
2588
|
transparent 100%
|
|
2250
|
-
))
|
|
2589
|
+
));
|
|
2251
2590
|
pointer-events: none;
|
|
2252
2591
|
z-index: 1;
|
|
2592
|
+
opacity: var(--select-separator-opacity);
|
|
2593
|
+
transition: opacity var(--select-transition-fast);
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
.input-container:hover::after,
|
|
2597
|
+
.input-container:focus-within::after {
|
|
2598
|
+
opacity: var(--select-separator-active-opacity);
|
|
2253
2599
|
}
|
|
2254
2600
|
|
|
2601
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
2602
|
+
Dropdown Arrow — Smooth rotation with refined styling
|
|
2603
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
2604
|
+
|
|
2255
2605
|
.dropdown-arrow-container {
|
|
2256
2606
|
position: absolute;
|
|
2257
2607
|
top: 0;
|
|
2258
2608
|
right: 0;
|
|
2259
2609
|
bottom: 0;
|
|
2260
|
-
width: var(--select-arrow-width,
|
|
2261
|
-
/* allow explicit height override even though container normally stretches */
|
|
2610
|
+
width: var(--select-arrow-width, 42px);
|
|
2262
2611
|
height: var(--select-arrow-height, auto);
|
|
2263
2612
|
display: flex;
|
|
2264
2613
|
align-items: center;
|
|
2265
2614
|
justify-content: center;
|
|
2266
2615
|
cursor: pointer;
|
|
2267
|
-
|
|
2268
|
-
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);
|
|
2269
2617
|
z-index: 2;
|
|
2618
|
+
transition: background-color var(--select-transition-fast);
|
|
2270
2619
|
}
|
|
2271
2620
|
|
|
2272
2621
|
.input-container.has-clear-control {
|
|
2273
|
-
padding: var(--select-input-padding-with-clear,
|
|
2622
|
+
padding: var(--select-input-padding-with-clear, 10px 84px 10px 14px);
|
|
2274
2623
|
}
|
|
2275
2624
|
|
|
2276
2625
|
.input-container.has-clear-control::after {
|
|
2277
|
-
right: var(--select-separator-position-with-clear,
|
|
2626
|
+
right: var(--select-separator-position-with-clear, 74px);
|
|
2278
2627
|
}
|
|
2279
2628
|
|
|
2280
2629
|
.dropdown-arrow-container.with-clear-control {
|
|
2281
|
-
right: var(--select-arrow-right-with-clear,
|
|
2630
|
+
right: var(--select-arrow-right-with-clear, 34px);
|
|
2282
2631
|
}
|
|
2283
2632
|
|
|
2633
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
2634
|
+
Clear Control — Minimal and elegant dismiss button
|
|
2635
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
2636
|
+
|
|
2284
2637
|
.clear-control-button {
|
|
2285
2638
|
position: absolute;
|
|
2286
2639
|
top: 50%;
|
|
2287
|
-
right: var(--select-clear-button-right,
|
|
2640
|
+
right: var(--select-clear-button-right, 8px);
|
|
2288
2641
|
transform: translateY(-50%);
|
|
2289
|
-
width: var(--select-clear-button-size,
|
|
2290
|
-
height: var(--select-clear-button-size,
|
|
2642
|
+
width: var(--select-clear-button-size, 26px);
|
|
2643
|
+
height: var(--select-clear-button-size, 26px);
|
|
2291
2644
|
border: var(--select-clear-button-border, none);
|
|
2292
|
-
border-radius: var(--select-clear-button-radius,
|
|
2645
|
+
border-radius: var(--select-clear-button-radius, 50%);
|
|
2293
2646
|
background: var(--select-clear-button-bg, transparent);
|
|
2294
|
-
color: var(--select-clear-button-color,
|
|
2647
|
+
color: var(--select-clear-button-color, var(--select-text-muted));
|
|
2295
2648
|
display: inline-flex;
|
|
2296
2649
|
align-items: center;
|
|
2297
2650
|
justify-content: center;
|
|
2298
2651
|
cursor: pointer;
|
|
2299
2652
|
z-index: 3;
|
|
2300
|
-
transition:
|
|
2653
|
+
transition:
|
|
2654
|
+
background var(--select-transition-fast),
|
|
2655
|
+
color var(--select-transition-fast),
|
|
2656
|
+
transform var(--select-transition-bounce);
|
|
2301
2657
|
}
|
|
2302
2658
|
|
|
2303
2659
|
.clear-control-button:hover {
|
|
2304
|
-
background: var(--select-clear-button-hover-bg, rgba(
|
|
2305
|
-
color: var(--select-clear-button-hover-color,
|
|
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);
|
|
2306
2667
|
}
|
|
2307
2668
|
|
|
2308
2669
|
.clear-control-button:focus-visible {
|
|
2309
|
-
outline: var(--select-clear-button-focus-outline, 2px solid
|
|
2310
|
-
outline-offset:
|
|
2670
|
+
outline: var(--select-clear-button-focus-outline, 2px solid var(--select-border-focus));
|
|
2671
|
+
outline-offset: var(--select-clear-button-focus-offset);
|
|
2311
2672
|
}
|
|
2312
2673
|
|
|
2313
2674
|
.clear-control-button[hidden] {
|
|
@@ -2315,260 +2676,332 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2315
2676
|
}
|
|
2316
2677
|
|
|
2317
2678
|
.clear-control-icon {
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
font-weight: var(--select-clear-icon-weight, 500);
|
|
2679
|
+
width: var(--select-clear-icon-size);
|
|
2680
|
+
height: var(--select-clear-icon-size);
|
|
2321
2681
|
pointer-events: none;
|
|
2322
2682
|
}
|
|
2323
2683
|
|
|
2324
|
-
.
|
|
2325
|
-
|
|
2684
|
+
.clear-control-icon svg {
|
|
2685
|
+
width: 100%;
|
|
2686
|
+
height: 100%;
|
|
2326
2687
|
}
|
|
2327
2688
|
|
|
2328
|
-
.dropdown-arrow:hover {
|
|
2329
|
-
|
|
2330
|
-
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));
|
|
2331
2691
|
}
|
|
2332
2692
|
|
|
2333
2693
|
.dropdown-arrow {
|
|
2334
|
-
width: var(--select-arrow-
|
|
2335
|
-
height: var(--select-arrow-
|
|
2336
|
-
color: var(--select-arrow-color,
|
|
2337
|
-
transition:
|
|
2338
|
-
|
|
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;
|
|
2339
2701
|
}
|
|
2340
2702
|
|
|
2341
2703
|
.dropdown-arrow path {
|
|
2342
|
-
stroke-width: var(--select-arrow-stroke-width,
|
|
2704
|
+
stroke-width: var(--select-arrow-stroke-width, 1.5);
|
|
2343
2705
|
}
|
|
2344
2706
|
|
|
2345
2707
|
.dropdown-arrow-container:hover .dropdown-arrow {
|
|
2346
|
-
color: var(--select-arrow-hover-color,
|
|
2708
|
+
color: var(--select-arrow-hover-color, var(--select-accent));
|
|
2347
2709
|
}
|
|
2348
2710
|
|
|
2349
2711
|
.dropdown-arrow.open {
|
|
2350
|
-
transform:
|
|
2712
|
+
transform: var(--select-arrow-open-transform);
|
|
2351
2713
|
}
|
|
2352
2714
|
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
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);
|
|
2360
2733
|
line-height: var(--select-input-line-height, 1.5);
|
|
2361
|
-
color: var(--select-input-color, var(--select-text
|
|
2362
|
-
background: transparent;
|
|
2734
|
+
color: var(--select-input-color, var(--select-text));
|
|
2735
|
+
background: transparent !important;
|
|
2363
2736
|
box-sizing: border-box;
|
|
2364
|
-
outline: none;
|
|
2365
|
-
font-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
.selection-badge {
|
|
2373
|
-
display: inline-flex;
|
|
2374
|
-
align-items: center;
|
|
2375
|
-
gap: var(--select-badge-gap, 4px);
|
|
2376
|
-
padding: var(--select-badge-padding, 4px 8px);
|
|
2377
|
-
margin: var(--select-badge-margin, 2px);
|
|
2378
|
-
background: var(--select-badge-bg, #667eea);
|
|
2379
|
-
color: var(--select-badge-color, white);
|
|
2380
|
-
border-radius: var(--select-badge-border-radius, 4px);
|
|
2381
|
-
font-size: var(--select-badge-font-size, 13px);
|
|
2382
|
-
line-height: 1;
|
|
2383
|
-
max-width: var(--select-badge-max-width, 100%);
|
|
2384
|
-
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;
|
|
2385
2745
|
overflow: hidden;
|
|
2386
2746
|
text-overflow: ellipsis;
|
|
2747
|
+
white-space: nowrap;
|
|
2387
2748
|
}
|
|
2388
2749
|
|
|
2389
|
-
.
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
justify-content: center;
|
|
2393
|
-
width: var(--select-badge-remove-size, 16px);
|
|
2394
|
-
height: var(--select-badge-remove-size, 16px);
|
|
2395
|
-
padding: 0;
|
|
2396
|
-
margin-left: 4px;
|
|
2397
|
-
background: var(--select-badge-remove-bg, rgba(255, 255, 255, 0.3));
|
|
2398
|
-
border: none;
|
|
2399
|
-
border-radius: 50%;
|
|
2400
|
-
color: var(--select-badge-remove-color, white);
|
|
2401
|
-
font-size: var(--select-badge-remove-font-size, 16px);
|
|
2402
|
-
line-height: 1;
|
|
2403
|
-
cursor: pointer;
|
|
2404
|
-
transition: background 0.2s;
|
|
2750
|
+
.select-input::placeholder {
|
|
2751
|
+
color: var(--select-input-placeholder-color, var(--select-text-placeholder));
|
|
2752
|
+
font-weight: 400;
|
|
2405
2753
|
}
|
|
2406
|
-
|
|
2407
|
-
.
|
|
2408
|
-
|
|
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);
|
|
2409
2761
|
}
|
|
2410
2762
|
|
|
2411
|
-
.
|
|
2412
|
-
|
|
2413
|
-
|
|
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;
|
|
2414
2769
|
}
|
|
2415
2770
|
|
|
2416
2771
|
.select-input:disabled {
|
|
2417
2772
|
background-color: var(--select-disabled-bg, #f5f5f5);
|
|
2418
2773
|
cursor: not-allowed;
|
|
2774
|
+
opacity: var(--select-input-disabled-opacity);
|
|
2419
2775
|
}
|
|
2420
2776
|
|
|
2777
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
2778
|
+
Dropdown Panel — Elegant floating container
|
|
2779
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
2780
|
+
|
|
2421
2781
|
.select-dropdown {
|
|
2422
2782
|
position: absolute;
|
|
2423
|
-
scroll-behavior:
|
|
2424
|
-
top:
|
|
2783
|
+
scroll-behavior: var(--select-dropdown-scroll-behavior);
|
|
2784
|
+
top: var(--select-dropdown-top);
|
|
2425
2785
|
left: 0;
|
|
2426
2786
|
right: 0;
|
|
2427
|
-
|
|
2428
|
-
max-height: var(--select-dropdown-max-height, 300px);
|
|
2787
|
+
max-height: var(--select-dropdown-max-height, 320px);
|
|
2429
2788
|
overflow: hidden;
|
|
2430
|
-
background: var(--select-dropdown-bg, var(--select-
|
|
2431
|
-
border: 1px solid var(--select-dropdown-border,
|
|
2432
|
-
border-radius: var(--select-dropdown-border-radius,
|
|
2433
|
-
box-shadow: var(--select-dropdown-shadow,
|
|
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));
|
|
2434
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
|
+
}
|
|
2435
2807
|
}
|
|
2436
2808
|
|
|
2437
2809
|
.options-container {
|
|
2438
2810
|
position: relative;
|
|
2439
|
-
max-height: var(--select-options-max-height,
|
|
2811
|
+
max-height: var(--select-options-max-height, 320px);
|
|
2440
2812
|
overflow: auto;
|
|
2441
|
-
|
|
2442
|
-
background: var(--select-options-bg, var(--select-
|
|
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);
|
|
2443
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
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
2444
2841
|
|
|
2445
2842
|
.group-header {
|
|
2446
|
-
padding: var(--select-group-header-padding,
|
|
2843
|
+
padding: var(--select-group-header-padding, 10px 12px 6px);
|
|
2447
2844
|
font-weight: var(--select-group-header-weight, 600);
|
|
2448
|
-
color: var(--select-group-header-color,
|
|
2449
|
-
background-color: var(--select-group-header-bg,
|
|
2845
|
+
color: var(--select-group-header-color, var(--select-text-muted));
|
|
2846
|
+
background-color: var(--select-group-header-bg, var(--select-surface));
|
|
2450
2847
|
text-align: var(--select-group-header-text-align, left);
|
|
2451
|
-
font-size: var(--select-group-header-font-size,
|
|
2848
|
+
font-size: var(--select-group-header-font-size, 11px);
|
|
2452
2849
|
text-transform: var(--select-group-header-text-transform, uppercase);
|
|
2453
|
-
letter-spacing: var(--select-group-header-letter-spacing, 0.
|
|
2850
|
+
letter-spacing: var(--select-group-header-letter-spacing, 0.08em);
|
|
2454
2851
|
position: sticky;
|
|
2455
2852
|
top: 0;
|
|
2456
2853
|
z-index: 1;
|
|
2457
|
-
border-bottom: var(--select-group-header-border-bottom,
|
|
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);
|
|
2458
2861
|
}
|
|
2459
2862
|
|
|
2863
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
2864
|
+
Options — Elegant hover and selection states
|
|
2865
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
2866
|
+
|
|
2460
2867
|
.option {
|
|
2461
|
-
|
|
2868
|
+
position: relative;
|
|
2869
|
+
padding: var(--select-option-padding, 10px 14px);
|
|
2462
2870
|
cursor: pointer;
|
|
2463
|
-
color: var(--select-option-color, var(--select-text
|
|
2464
|
-
background: var(--select-option-bg,
|
|
2465
|
-
transition:
|
|
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);
|
|
2466
2878
|
user-select: none;
|
|
2467
2879
|
font-size: var(--select-option-font-size, 14px);
|
|
2880
|
+
font-weight: var(--select-option-font-weight);
|
|
2468
2881
|
line-height: var(--select-option-line-height, 1.5);
|
|
2469
|
-
border: var(--select-option-border,
|
|
2470
|
-
|
|
2471
|
-
border-radius: var(--select-option-border-radius, 0);
|
|
2472
|
-
box-shadow: var(--select-option-shadow, none);
|
|
2473
|
-
transform: var(--select-option-transform, none);
|
|
2882
|
+
border-radius: var(--select-option-border-radius, var(--select-radius-sm));
|
|
2883
|
+
margin: var(--select-option-margin);
|
|
2474
2884
|
}
|
|
2475
2885
|
|
|
2476
2886
|
.option:hover {
|
|
2477
|
-
background: var(--select-option-hover-bg,
|
|
2478
|
-
color: var(--select-option-hover-color,
|
|
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);
|
|
2479
2890
|
}
|
|
2480
2891
|
|
|
2481
2892
|
.option.selected {
|
|
2482
|
-
background: var(--select-option-selected-bg,
|
|
2483
|
-
color: var(--select-option-selected-color,
|
|
2484
|
-
font-weight: var(--select-option-selected-weight,
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
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
|
+
}
|
|
2490
2920
|
}
|
|
2491
2921
|
|
|
2492
2922
|
.option.selected:hover {
|
|
2493
|
-
background: var(--select-option-selected-hover-bg,
|
|
2494
|
-
color: var(--select-option-selected-hover-color, var(--select-option-selected-color, #4338ca));
|
|
2495
|
-
border: var(--select-option-selected-hover-border, var(--select-option-selected-border, var(--select-option-border, none)));
|
|
2496
|
-
border-bottom: var(--select-option-selected-hover-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
|
|
2497
|
-
box-shadow: var(--select-option-selected-hover-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
|
|
2498
|
-
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%));
|
|
2499
2924
|
}
|
|
2500
2925
|
|
|
2501
2926
|
.option.active:not(.selected) {
|
|
2502
|
-
background: var(--select-option-active-bg,
|
|
2503
|
-
|
|
2504
|
-
outline: var(--select-option-active-outline
|
|
2505
|
-
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);
|
|
2506
2930
|
}
|
|
2507
2931
|
|
|
2508
2932
|
.option.selected.active {
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
border: var(--select-option-selected-active-border, var(--select-option-selected-border, var(--select-option-border, none)));
|
|
2512
|
-
border-bottom: var(--select-option-selected-active-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
|
|
2513
|
-
box-shadow: var(--select-option-selected-active-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
|
|
2514
|
-
transform: var(--select-option-selected-active-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
|
|
2515
|
-
outline: var(--select-option-selected-active-outline, var(--select-option-active-outline, 2px solid rgba(99, 102, 241, 0.45)));
|
|
2516
|
-
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);
|
|
2517
2935
|
}
|
|
2518
2936
|
|
|
2519
2937
|
.option:active:not(.selected) {
|
|
2520
|
-
background: var(--select-option-pressed-bg,
|
|
2938
|
+
background: var(--select-option-pressed-bg, rgba(15, 52, 96, 0.08));
|
|
2939
|
+
transform: var(--select-option-pressed-transform);
|
|
2521
2940
|
}
|
|
2522
2941
|
|
|
2523
2942
|
.option.selected:active {
|
|
2524
|
-
|
|
2943
|
+
transform: var(--select-option-selected-pressed-transform);
|
|
2525
2944
|
}
|
|
2526
2945
|
|
|
2946
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
2947
|
+
Load More & Busy States — Refined feedback indicators
|
|
2948
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
2949
|
+
|
|
2527
2950
|
.load-more-container {
|
|
2528
2951
|
padding: var(--select-load-more-padding, 12px);
|
|
2529
2952
|
text-align: center;
|
|
2530
|
-
border-top: var(--select-divider-border, 1px solid #e0e0e0);
|
|
2531
|
-
background: var(--select-load-more-bg, white);
|
|
2532
2953
|
}
|
|
2533
2954
|
|
|
2534
2955
|
.load-more-button {
|
|
2535
|
-
padding: var(--select-button-padding,
|
|
2536
|
-
border: var(--select-button-border,
|
|
2537
|
-
background: var(--select-button-bg,
|
|
2538
|
-
color: var(--select-button-color,
|
|
2539
|
-
border-radius: var(--select-button-border-radius,
|
|
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));
|
|
2540
2961
|
cursor: pointer;
|
|
2541
|
-
font-size: var(--select-button-font-size,
|
|
2542
|
-
font-
|
|
2543
|
-
|
|
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);
|
|
2544
2970
|
}
|
|
2545
2971
|
|
|
2546
2972
|
.load-more-button:hover {
|
|
2547
|
-
background: var(--select-button-hover-bg,
|
|
2973
|
+
background: var(--select-button-hover-bg, var(--select-accent));
|
|
2974
|
+
border-color: var(--select-accent);
|
|
2548
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);
|
|
2549
2981
|
}
|
|
2550
2982
|
|
|
2551
2983
|
.load-more-button:disabled {
|
|
2552
2984
|
opacity: var(--select-button-disabled-opacity, 0.5);
|
|
2553
2985
|
cursor: not-allowed;
|
|
2986
|
+
transform: none;
|
|
2554
2987
|
}
|
|
2555
2988
|
|
|
2556
2989
|
.busy-bucket {
|
|
2557
|
-
padding: var(--select-busy-padding,
|
|
2990
|
+
padding: var(--select-busy-padding, 20px);
|
|
2558
2991
|
text-align: center;
|
|
2559
|
-
color: var(--select-busy-color,
|
|
2560
|
-
background: var(--select-busy-bg,
|
|
2561
|
-
font-size: var(--select-busy-font-size,
|
|
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);
|
|
2562
2995
|
}
|
|
2563
2996
|
|
|
2564
2997
|
.spinner {
|
|
2565
2998
|
display: inline-block;
|
|
2566
|
-
width: var(--select-spinner-size,
|
|
2567
|
-
height: var(--select-spinner-size,
|
|
2568
|
-
border: var(--select-spinner-border, 2px solid
|
|
2569
|
-
border-top-color: var(--select-spinner-active-color,
|
|
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));
|
|
2570
3003
|
border-radius: 50%;
|
|
2571
|
-
animation:
|
|
3004
|
+
animation: var(--select-spinner-animation);
|
|
2572
3005
|
}
|
|
2573
3006
|
|
|
2574
3007
|
@keyframes spin {
|
|
@@ -2576,61 +3009,97 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2576
3009
|
}
|
|
2577
3010
|
|
|
2578
3011
|
.empty-state {
|
|
2579
|
-
padding: var(--select-empty-padding, 24px);
|
|
3012
|
+
padding: var(--select-empty-padding, 32px 24px);
|
|
2580
3013
|
text-align: center;
|
|
2581
|
-
color: var(--select-empty-color,
|
|
3014
|
+
color: var(--select-empty-color, var(--select-text-muted));
|
|
2582
3015
|
font-size: var(--select-empty-font-size, 14px);
|
|
2583
|
-
background: var(--select-empty-bg,
|
|
3016
|
+
background: var(--select-empty-bg, transparent);
|
|
2584
3017
|
display: flex;
|
|
2585
3018
|
flex-direction: column;
|
|
2586
3019
|
align-items: center;
|
|
2587
3020
|
justify-content: center;
|
|
2588
|
-
gap:
|
|
2589
|
-
min-height: var(--select-empty-min-height, 72px);
|
|
3021
|
+
gap: var(--select-empty-gap);
|
|
2590
3022
|
}
|
|
2591
3023
|
|
|
2592
3024
|
.searching-state {
|
|
2593
|
-
padding: var(--select-searching-padding, 24px);
|
|
3025
|
+
padding: var(--select-searching-padding, 32px 24px);
|
|
2594
3026
|
text-align: center;
|
|
2595
|
-
color: var(--select-searching-color,
|
|
3027
|
+
color: var(--select-searching-color, var(--select-accent));
|
|
2596
3028
|
font-size: var(--select-searching-font-size, 14px);
|
|
2597
|
-
|
|
2598
|
-
background: var(--select-searching-bg, white);
|
|
2599
|
-
animation: pulse 1.5s ease-in-out infinite;
|
|
3029
|
+
background: var(--select-searching-bg, transparent);
|
|
2600
3030
|
display: flex;
|
|
2601
3031
|
flex-direction: column;
|
|
2602
3032
|
align-items: center;
|
|
2603
3033
|
justify-content: center;
|
|
2604
|
-
gap:
|
|
2605
|
-
min-height: var(--select-searching-min-height, 72px);
|
|
3034
|
+
gap: var(--select-searching-gap);
|
|
2606
3035
|
}
|
|
2607
3036
|
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
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);
|
|
2611
3045
|
}
|
|
2612
3046
|
|
|
2613
|
-
/*
|
|
3047
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
3048
|
+
Error States — Clear visual feedback
|
|
3049
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
3050
|
+
|
|
2614
3051
|
.select-input[aria-invalid="true"] {
|
|
2615
|
-
border-color: var(--select-error-border, #
|
|
3052
|
+
border-color: var(--select-error-border, #e94560);
|
|
2616
3053
|
}
|
|
2617
3054
|
|
|
2618
3055
|
.select-input[aria-invalid="true"]:focus {
|
|
2619
|
-
border-color: var(--select-error-border, #
|
|
2620
|
-
box-shadow: 0 0 0
|
|
2621
|
-
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));
|
|
2622
3058
|
}
|
|
2623
3059
|
|
|
2624
|
-
/*
|
|
3060
|
+
/* ──────────────────────────────────────────────────��──────────────────────
|
|
3061
|
+
Accessibility — Reduced motion & High contrast
|
|
3062
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
3063
|
+
|
|
2625
3064
|
@media (prefers-reduced-motion: reduce) {
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
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);
|
|
2630
3075
|
}
|
|
2631
3076
|
}
|
|
2632
3077
|
|
|
2633
|
-
|
|
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
|
+
|
|
2634
3103
|
:host(.dark-mode),
|
|
2635
3104
|
:host([dark-mode]),
|
|
2636
3105
|
:host([darkmode]),
|
|
@@ -2642,139 +3111,129 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2642
3111
|
:host-context([darkmode]),
|
|
2643
3112
|
:host-context([data-theme="dark"]),
|
|
2644
3113
|
:host-context([theme="dark"]) {
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
--select-
|
|
2648
|
-
--select-
|
|
2649
|
-
--select-
|
|
2650
|
-
--select-
|
|
2651
|
-
--select-
|
|
2652
|
-
--select-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
color: var(--select-dark-text, #f9fafb);
|
|
2661
|
-
}
|
|
2662
|
-
|
|
2663
|
-
.select-input::placeholder {
|
|
2664
|
-
color: var(--select-dark-placeholder, #6b7280);
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
.select-dropdown {
|
|
2668
|
-
background: var(--select-dark-dropdown-bg, #1f2937);
|
|
2669
|
-
border-color: var(--select-dark-dropdown-border, #4b5563);
|
|
2670
|
-
}
|
|
2671
|
-
|
|
2672
|
-
.options-container {
|
|
2673
|
-
background: var(--select-dark-options-bg, #1f2937);
|
|
2674
|
-
}
|
|
2675
|
-
|
|
2676
|
-
.option {
|
|
2677
|
-
color: var(--select-dark-option-color, #f9fafb);
|
|
2678
|
-
background: var(--select-dark-option-bg, #1f2937);
|
|
2679
|
-
}
|
|
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);
|
|
2680
3129
|
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
color: var(--select-dark-option-selected-text, #e0e7ff);
|
|
2689
|
-
border: var(--select-dark-option-selected-border, var(--select-option-selected-border, var(--select-option-border, none)));
|
|
2690
|
-
border-bottom: var(--select-dark-option-selected-border-bottom, var(--select-option-selected-border-bottom, var(--select-option-border-bottom, none)));
|
|
2691
|
-
box-shadow: var(--select-dark-option-selected-shadow, var(--select-option-selected-shadow, var(--select-option-shadow, none)));
|
|
2692
|
-
transform: var(--select-dark-option-selected-transform, var(--select-option-selected-transform, var(--select-option-transform, none)));
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
.option.selected:hover {
|
|
2696
|
-
background: var(--select-dark-option-selected-hover-bg, var(--select-dark-option-selected-bg, #3730a3));
|
|
2697
|
-
color: var(--select-dark-option-selected-hover-color, var(--select-dark-option-selected-text, #e0e7ff));
|
|
2698
|
-
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)))));
|
|
2699
|
-
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)))));
|
|
2700
|
-
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)))));
|
|
2701
|
-
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)))));
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
.option.active:not(.selected) {
|
|
2705
|
-
background-color: var(--select-dark-option-active-bg, #374151);
|
|
2706
|
-
color: var(--select-dark-option-active-color, #f9fafb);
|
|
2707
|
-
outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
/* Group header in dark mode */
|
|
2711
|
-
.group-header {
|
|
2712
|
-
color: var(--select-dark-group-header-color, var(--select-group-header-color, #6b7280));
|
|
2713
|
-
background-color: var(--select-dark-group-header-bg, var(--select-group-header-bg, #374151));
|
|
2714
|
-
}
|
|
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
|
+
}
|
|
2715
3137
|
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
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
|
+
}
|
|
2726
3152
|
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
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
|
+
}
|
|
2731
3166
|
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
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
|
+
}
|
|
2736
3181
|
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
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
|
+
}
|
|
2746
3196
|
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
}
|
|
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);
|
|
2760
3209
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
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);
|
|
2772
3223
|
}
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
.
|
|
2776
|
-
|
|
2777
|
-
|
|
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);
|
|
2778
3237
|
}
|
|
2779
3238
|
`;
|
|
2780
3239
|
// Insert as first child to ensure styles are processed first
|
|
@@ -2794,10 +3253,10 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2794
3253
|
// delegate to the existing open/close helpers so we don't accidentally
|
|
2795
3254
|
// drift out of sync with the logic in those methods (focus, events,
|
|
2796
3255
|
// scroll-to-selected, etc.)
|
|
2797
|
-
if (this._state.isOpen) {
|
|
3256
|
+
if (this._state.isOpen && this._config.selection.toggleOnTriggerClick !== false) {
|
|
2798
3257
|
this._handleClose();
|
|
2799
3258
|
}
|
|
2800
|
-
else {
|
|
3259
|
+
else if (!this._state.isOpen) {
|
|
2801
3260
|
this._handleOpen();
|
|
2802
3261
|
}
|
|
2803
3262
|
};
|
|
@@ -2820,7 +3279,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2820
3279
|
this._inputContainer.addEventListener('pointerdown', (e) => {
|
|
2821
3280
|
// Prevent propagation to document click listener but do NOT preventDefault.
|
|
2822
3281
|
// Allow default so browser events (click) on newly opened options still fire.
|
|
2823
|
-
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.
|
|
2824
3283
|
const target = e.target;
|
|
2825
3284
|
if (!this._config.enabled)
|
|
2826
3285
|
return;
|
|
@@ -2828,21 +3287,23 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2828
3287
|
return;
|
|
2829
3288
|
if (target && target.closest('.clear-control-button'))
|
|
2830
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
|
+
}
|
|
2831
3302
|
const wasClosed = !this._state.isOpen;
|
|
2832
3303
|
if (wasClosed) {
|
|
2833
3304
|
this._handleOpen();
|
|
2834
3305
|
}
|
|
2835
|
-
|
|
2836
|
-
// Keep open while interacting directly with the input so users can
|
|
2837
|
-
// place cursor/type without accidental collapse.
|
|
2838
|
-
if (target === this._input) {
|
|
2839
|
-
this._input.focus();
|
|
2840
|
-
return;
|
|
2841
|
-
}
|
|
2842
|
-
// clicking other parts of the input container while open toggles close
|
|
2843
|
-
this._handleClose();
|
|
2844
|
-
}
|
|
2845
|
-
// Focus the input (do not prevent default behavior)
|
|
3306
|
+
// Focus the input (do not prevent default behavior for input itself)
|
|
2846
3307
|
this._input.focus();
|
|
2847
3308
|
// If we just opened the dropdown, transfer pointer capture to the
|
|
2848
3309
|
// options container so the subsequent pointerup lands there instead of
|
|
@@ -2884,7 +3345,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2884
3345
|
return;
|
|
2885
3346
|
}
|
|
2886
3347
|
this._handleClose();
|
|
2887
|
-
},
|
|
3348
|
+
}, 150);
|
|
2888
3349
|
});
|
|
2889
3350
|
// Input search
|
|
2890
3351
|
this._input.addEventListener('input', (e) => {
|
|
@@ -2901,7 +3362,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2901
3362
|
this._suppressBlurClose = true;
|
|
2902
3363
|
setTimeout(() => {
|
|
2903
3364
|
this._suppressBlurClose = false;
|
|
2904
|
-
},
|
|
3365
|
+
}, 150); // Increased timeout to ensure click finishes before blur checks
|
|
2905
3366
|
});
|
|
2906
3367
|
// Delegated click listener for improved event handling (robust across shadow DOM)
|
|
2907
3368
|
const handleOptionEvent = (e) => {
|
|
@@ -2932,9 +3393,6 @@ class EnhancedSelect extends HTMLElement {
|
|
|
2932
3393
|
}
|
|
2933
3394
|
};
|
|
2934
3395
|
this._optionsContainer.addEventListener('click', handleOptionEvent);
|
|
2935
|
-
// also watch pointerup to catch cases where the pointerdown started outside
|
|
2936
|
-
// (e.g. on the input) and the click never fires
|
|
2937
|
-
this._optionsContainer.addEventListener('pointerup', handleOptionEvent);
|
|
2938
3396
|
// Keyboard navigation
|
|
2939
3397
|
this._input.addEventListener('keydown', (e) => this._handleKeydown(e));
|
|
2940
3398
|
// Click outside to close — robust detection across shadow DOM and custom renderers
|
|
@@ -3032,17 +3490,6 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3032
3490
|
this._dropdown.style.display = 'block';
|
|
3033
3491
|
this._input.setAttribute('aria-expanded', 'true');
|
|
3034
3492
|
this._updateArrowRotation();
|
|
3035
|
-
// Clear search query when opening to show all options
|
|
3036
|
-
// This ensures we can scroll to selected item
|
|
3037
|
-
if (this._config.searchable) {
|
|
3038
|
-
this._state.searchQuery = '';
|
|
3039
|
-
// Don't clear input value if it represents selection
|
|
3040
|
-
// But if we want to search, we might want to clear it?
|
|
3041
|
-
// Standard behavior: input keeps value (label), but dropdown shows all options
|
|
3042
|
-
// until user types.
|
|
3043
|
-
// However, our filtering logic uses _state.searchQuery.
|
|
3044
|
-
// So clearing it here resets the filter.
|
|
3045
|
-
}
|
|
3046
3493
|
// Render options when opening
|
|
3047
3494
|
this._renderOptions();
|
|
3048
3495
|
this._setInitialActiveOption();
|
|
@@ -3288,24 +3735,24 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3288
3735
|
}
|
|
3289
3736
|
}
|
|
3290
3737
|
_setActive(index) {
|
|
3291
|
-
const options = Array.from(this._optionsContainer.children);
|
|
3292
3738
|
// Clear previous active state
|
|
3293
|
-
if (this._state.activeIndex >= 0
|
|
3294
|
-
const prevOption =
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
prevOption.setActive
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
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
|
+
}
|
|
3303
3750
|
}
|
|
3304
3751
|
}
|
|
3305
3752
|
this._state.activeIndex = index;
|
|
3306
3753
|
// Set new active state
|
|
3307
|
-
|
|
3308
|
-
|
|
3754
|
+
const option = this._getOptionElementByIndex(index);
|
|
3755
|
+
if (option) {
|
|
3309
3756
|
// Check if it's a custom SelectOption or a lightweight DOM element
|
|
3310
3757
|
if ('setActive' in option && typeof option.setActive === 'function') {
|
|
3311
3758
|
option.setActive(true);
|
|
@@ -3313,13 +3760,13 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3313
3760
|
else {
|
|
3314
3761
|
// Lightweight option - add active class
|
|
3315
3762
|
option.classList.add('smilodon-option--active');
|
|
3316
|
-
option.setAttribute('aria-selected', 'true');
|
|
3317
3763
|
}
|
|
3318
3764
|
if (typeof option.scrollIntoView === 'function') {
|
|
3765
|
+
// Don't scroll wildly when just opening with pre-selections
|
|
3319
3766
|
option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
3320
3767
|
}
|
|
3321
3768
|
// Announce position for screen readers
|
|
3322
|
-
const total =
|
|
3769
|
+
const total = this._state.loadedItems.length;
|
|
3323
3770
|
this._announce(`Item ${index + 1} of ${total}`);
|
|
3324
3771
|
// Update aria-activedescendant using the actual option id when available
|
|
3325
3772
|
const optionId = option.id || `${this._uniqueId}-option-${index}`;
|
|
@@ -3442,9 +3889,9 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3442
3889
|
// FIX: Do not rely on this._optionsContainer.children[index] because filtering changes the children
|
|
3443
3890
|
// Instead, use the index to update state directly
|
|
3444
3891
|
const item = this._state.loadedItems[index];
|
|
3445
|
-
|
|
3446
|
-
if (!item)
|
|
3892
|
+
if (!item) {
|
|
3447
3893
|
return;
|
|
3894
|
+
}
|
|
3448
3895
|
const isCurrentlySelected = this._state.selectedIndices.has(index);
|
|
3449
3896
|
// Keep active/focus styling aligned with the most recently interacted option.
|
|
3450
3897
|
// Without this, a previously selected item may retain active classes/styles
|
|
@@ -3455,7 +3902,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3455
3902
|
const wasSelected = this._state.selectedIndices.has(index);
|
|
3456
3903
|
this._state.selectedIndices.clear();
|
|
3457
3904
|
this._state.selectedItems.clear();
|
|
3458
|
-
if (!wasSelected) {
|
|
3905
|
+
if (!wasSelected || !this._config.selection.allowDeselect) {
|
|
3459
3906
|
// Select this option
|
|
3460
3907
|
this._state.selectedIndices.add(index);
|
|
3461
3908
|
this._state.selectedItems.set(index, item);
|
|
@@ -3517,16 +3964,34 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3517
3964
|
});
|
|
3518
3965
|
}
|
|
3519
3966
|
_handleOptionRemove(index) {
|
|
3967
|
+
const item = this._state.selectedItems.get(index);
|
|
3520
3968
|
const option = this._getOptionElementByIndex(index);
|
|
3521
|
-
if (!option)
|
|
3522
|
-
return;
|
|
3523
3969
|
this._state.selectedIndices.delete(index);
|
|
3524
3970
|
this._state.selectedItems.delete(index);
|
|
3525
|
-
option.setSelected
|
|
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
|
+
}
|
|
3526
3988
|
this._updateInputDisplay();
|
|
3989
|
+
this._renderOptions();
|
|
3527
3990
|
this._emitChange();
|
|
3528
|
-
const config = option.getConfig
|
|
3529
|
-
|
|
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 });
|
|
3530
3995
|
}
|
|
3531
3996
|
_updateInputDisplay() {
|
|
3532
3997
|
const selectedItems = Array.from(this._state.selectedItems.values());
|
|
@@ -3554,22 +4019,29 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3554
4019
|
const badge = document.createElement('span');
|
|
3555
4020
|
badge.className = 'selection-badge';
|
|
3556
4021
|
badge.setAttribute('part', 'chip');
|
|
3557
|
-
|
|
4022
|
+
const badgeLabel = document.createElement('span');
|
|
4023
|
+
badgeLabel.className = 'selection-badge-label';
|
|
4024
|
+
badgeLabel.textContent = getLabel(item);
|
|
4025
|
+
badge.appendChild(badgeLabel);
|
|
3558
4026
|
// Add remove button to badge
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
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
|
+
}
|
|
3573
4045
|
this._inputContainer.insertBefore(badge, this._input);
|
|
3574
4046
|
});
|
|
3575
4047
|
}
|
|
@@ -3621,10 +4093,12 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3621
4093
|
const option = this._getOptionElementByIndex(targetIndex);
|
|
3622
4094
|
if (option) {
|
|
3623
4095
|
// Use smooth scrolling with center alignment for better UX
|
|
3624
|
-
option.scrollIntoView
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
4096
|
+
if (typeof option.scrollIntoView === 'function') {
|
|
4097
|
+
option.scrollIntoView({
|
|
4098
|
+
block: this._config.scrollToSelected.block || 'center',
|
|
4099
|
+
behavior: 'smooth',
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
3628
4102
|
// Also set it as active for keyboard navigation
|
|
3629
4103
|
this._setActive(targetIndex);
|
|
3630
4104
|
}
|
|
@@ -3674,13 +4148,168 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3674
4148
|
}
|
|
3675
4149
|
_emit(name, detail) {
|
|
3676
4150
|
this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
|
|
4151
|
+
if (name !== 'diagnostic') {
|
|
4152
|
+
this._track('event', String(name), detail);
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
_track(source, name, detail) {
|
|
4156
|
+
const cfg = this._config.tracking;
|
|
4157
|
+
if (!cfg?.enabled)
|
|
4158
|
+
return;
|
|
4159
|
+
if (source === 'event' && !cfg.events)
|
|
4160
|
+
return;
|
|
4161
|
+
if (source === 'style' && !cfg.styling)
|
|
4162
|
+
return;
|
|
4163
|
+
if (source === 'limitation' && !cfg.limitations)
|
|
4164
|
+
return;
|
|
4165
|
+
const entry = {
|
|
4166
|
+
timestamp: Date.now(),
|
|
4167
|
+
source,
|
|
4168
|
+
name,
|
|
4169
|
+
detail,
|
|
4170
|
+
};
|
|
4171
|
+
const bucket = source === 'event'
|
|
4172
|
+
? this._tracking.events
|
|
4173
|
+
: source === 'style'
|
|
4174
|
+
? this._tracking.styles
|
|
4175
|
+
: this._tracking.limitations;
|
|
4176
|
+
bucket.push(entry);
|
|
4177
|
+
const maxEntries = Math.max(10, cfg.maxEntries || 200);
|
|
4178
|
+
if (bucket.length > maxEntries) {
|
|
4179
|
+
bucket.splice(0, bucket.length - maxEntries);
|
|
4180
|
+
}
|
|
4181
|
+
if (cfg.emitDiagnostics) {
|
|
4182
|
+
this.dispatchEvent(new CustomEvent('diagnostic', {
|
|
4183
|
+
detail: entry,
|
|
4184
|
+
bubbles: true,
|
|
4185
|
+
composed: true,
|
|
4186
|
+
}));
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
_getKnownLimitationDefinitions() {
|
|
4190
|
+
return [
|
|
4191
|
+
{
|
|
4192
|
+
id: 'variableItemHeight',
|
|
4193
|
+
title: 'Variable item height',
|
|
4194
|
+
description: 'Virtualization assumes fixed or estimated item heights; fully dynamic heights are not yet supported.',
|
|
4195
|
+
workaround: 'Use consistent item heights or set estimatedItemHeight to your dominant row size.',
|
|
4196
|
+
},
|
|
4197
|
+
{
|
|
4198
|
+
id: 'builtInFetchPaginationApi',
|
|
4199
|
+
title: 'Built-in fetch/pagination API',
|
|
4200
|
+
description: 'Core does not include a built-in fetchUrl/searchUrl pagination transport.',
|
|
4201
|
+
workaround: 'Use onSearch/onLoadMore callbacks and update data via setItems().',
|
|
4202
|
+
},
|
|
4203
|
+
{
|
|
4204
|
+
id: 'virtualizationOverheadSmallLists',
|
|
4205
|
+
title: 'Virtualization overhead for small lists',
|
|
4206
|
+
description: 'Virtualization can add slight overhead on very small lists.',
|
|
4207
|
+
workaround: 'Disable virtualization for tiny datasets when micro-latency is critical.',
|
|
4208
|
+
},
|
|
4209
|
+
{
|
|
4210
|
+
id: 'runtimeModeSwitching',
|
|
4211
|
+
title: 'Runtime single/multi mode switching',
|
|
4212
|
+
description: 'Switching between single and multi mode can require state reset for consistency.',
|
|
4213
|
+
workaround: 'Enable autoMitigateRuntimeModeSwitch or recreate/reset component state when toggling modes.',
|
|
4214
|
+
},
|
|
4215
|
+
{
|
|
4216
|
+
id: 'legacyBrowserSupport',
|
|
4217
|
+
title: 'Legacy browser support',
|
|
4218
|
+
description: 'Official support targets modern evergreen browsers.',
|
|
4219
|
+
},
|
|
4220
|
+
{
|
|
4221
|
+
id: 'webkitArchLinux',
|
|
4222
|
+
title: 'Playwright WebKit on Arch-based Linux',
|
|
4223
|
+
description: 'Native WebKit Playwright bundle depends on unavailable legacy system libraries on Arch-based distros.',
|
|
4224
|
+
workaround: 'Run WebKit E2E tests via Playwright Docker image.',
|
|
4225
|
+
},
|
|
4226
|
+
];
|
|
4227
|
+
}
|
|
4228
|
+
_evaluateLimitationStatus(id) {
|
|
4229
|
+
const policyMode = this._config.limitations?.policies?.[id]?.mode ?? 'default';
|
|
4230
|
+
if (policyMode === 'suppress')
|
|
4231
|
+
return 'suppressed';
|
|
4232
|
+
if (id === 'runtimeModeSwitching' && this._config.limitations?.autoMitigateRuntimeModeSwitch) {
|
|
4233
|
+
return 'mitigated';
|
|
4234
|
+
}
|
|
4235
|
+
return 'active';
|
|
4236
|
+
}
|
|
4237
|
+
getKnownLimitations() {
|
|
4238
|
+
return this._getKnownLimitationDefinitions().map((limitation) => {
|
|
4239
|
+
const mode = this._config.limitations?.policies?.[limitation.id]?.mode ?? 'default';
|
|
4240
|
+
return {
|
|
4241
|
+
...limitation,
|
|
4242
|
+
mode,
|
|
4243
|
+
status: this._evaluateLimitationStatus(limitation.id),
|
|
4244
|
+
};
|
|
4245
|
+
});
|
|
4246
|
+
}
|
|
4247
|
+
setLimitationPolicies(policies) {
|
|
4248
|
+
const next = {
|
|
4249
|
+
...(this._config.limitations?.policies || {}),
|
|
4250
|
+
...policies,
|
|
4251
|
+
};
|
|
4252
|
+
this.updateConfig({
|
|
4253
|
+
limitations: {
|
|
4254
|
+
...(this._config.limitations || { autoMitigateRuntimeModeSwitch: true, policies: {} }),
|
|
4255
|
+
policies: next,
|
|
4256
|
+
},
|
|
4257
|
+
});
|
|
4258
|
+
this._track('limitation', 'policiesUpdated', { policies: next });
|
|
4259
|
+
}
|
|
4260
|
+
getTrackingSnapshot() {
|
|
4261
|
+
return {
|
|
4262
|
+
events: [...this._tracking.events],
|
|
4263
|
+
styles: [...this._tracking.styles],
|
|
4264
|
+
limitations: [...this._tracking.limitations],
|
|
4265
|
+
};
|
|
4266
|
+
}
|
|
4267
|
+
clearTracking(source) {
|
|
4268
|
+
if (!source || source === 'all') {
|
|
4269
|
+
this._tracking.events = [];
|
|
4270
|
+
this._tracking.styles = [];
|
|
4271
|
+
this._tracking.limitations = [];
|
|
4272
|
+
return;
|
|
4273
|
+
}
|
|
4274
|
+
if (source === 'event')
|
|
4275
|
+
this._tracking.events = [];
|
|
4276
|
+
if (source === 'style')
|
|
4277
|
+
this._tracking.styles = [];
|
|
4278
|
+
if (source === 'limitation')
|
|
4279
|
+
this._tracking.limitations = [];
|
|
4280
|
+
}
|
|
4281
|
+
getCapabilities() {
|
|
4282
|
+
return {
|
|
4283
|
+
styling: {
|
|
4284
|
+
classMap: true,
|
|
4285
|
+
optionRenderer: true,
|
|
4286
|
+
groupHeaderRenderer: true,
|
|
4287
|
+
cssCustomProperties: true,
|
|
4288
|
+
shadowParts: true,
|
|
4289
|
+
globalStyleMirroring: true,
|
|
4290
|
+
},
|
|
4291
|
+
events: {
|
|
4292
|
+
emitted: ['select', 'open', 'close', 'search', 'change', 'loadMore', 'remove', 'clear', 'error', 'diagnostic'],
|
|
4293
|
+
diagnosticEvent: true,
|
|
4294
|
+
},
|
|
4295
|
+
functionality: {
|
|
4296
|
+
multiSelect: true,
|
|
4297
|
+
searchable: true,
|
|
4298
|
+
infiniteScroll: true,
|
|
4299
|
+
loadMore: true,
|
|
4300
|
+
clearControl: true,
|
|
4301
|
+
groupedItems: true,
|
|
4302
|
+
serverSideSelection: true,
|
|
4303
|
+
runtimeModeSwitchMitigation: Boolean(this._config.limitations?.autoMitigateRuntimeModeSwitch),
|
|
4304
|
+
},
|
|
4305
|
+
limitations: this.getKnownLimitations(),
|
|
4306
|
+
};
|
|
3677
4307
|
}
|
|
3678
4308
|
_emitChange() {
|
|
3679
4309
|
const selectedItems = Array.from(this._state.selectedItems.values());
|
|
3680
4310
|
const getValue = this._config.serverSide.getValueFromItem || ((item) => item?.value ?? item);
|
|
3681
4311
|
const selectedValues = selectedItems.map(getValue);
|
|
3682
4312
|
const selectedIndices = Array.from(this._state.selectedIndices);
|
|
3683
|
-
// Debug: log change payload
|
|
3684
4313
|
this._emit('change', { selectedItems, selectedValues, selectedIndices });
|
|
3685
4314
|
this._config.callbacks.onChange?.(selectedItems, selectedValues);
|
|
3686
4315
|
}
|
|
@@ -3691,6 +4320,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3691
4320
|
set optionRenderer(renderer) {
|
|
3692
4321
|
this._optionRenderer = renderer;
|
|
3693
4322
|
this._setGlobalStylesMirroring(Boolean(renderer || this._classMap));
|
|
4323
|
+
this._track('style', 'optionRendererChanged', { enabled: Boolean(renderer) });
|
|
3694
4324
|
this._renderOptions();
|
|
3695
4325
|
}
|
|
3696
4326
|
/**
|
|
@@ -3855,7 +4485,22 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3855
4485
|
* Update component configuration
|
|
3856
4486
|
*/
|
|
3857
4487
|
updateConfig(config) {
|
|
3858
|
-
|
|
4488
|
+
const previousMode = this._config.selection.mode;
|
|
4489
|
+
this._config = this._mergeConfig(this._config, config);
|
|
4490
|
+
if (previousMode !== this._config.selection.mode &&
|
|
4491
|
+
this._config.limitations?.autoMitigateRuntimeModeSwitch) {
|
|
4492
|
+
this.clear();
|
|
4493
|
+
this._track('limitation', 'runtimeModeSwitchMitigated', {
|
|
4494
|
+
from: previousMode,
|
|
4495
|
+
to: this._config.selection.mode,
|
|
4496
|
+
});
|
|
4497
|
+
}
|
|
4498
|
+
else if (previousMode !== this._config.selection.mode) {
|
|
4499
|
+
this._track('limitation', 'runtimeModeSwitchDetected', {
|
|
4500
|
+
from: previousMode,
|
|
4501
|
+
to: this._config.selection.mode,
|
|
4502
|
+
});
|
|
4503
|
+
}
|
|
3859
4504
|
// Update input state based on new config
|
|
3860
4505
|
if (this._input) {
|
|
3861
4506
|
this._input.readOnly = !this._config.searchable;
|
|
@@ -3868,7 +4513,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3868
4513
|
this._clearControl.setAttribute('aria-label', this._config.clearControl.ariaLabel || 'Clear selection and search');
|
|
3869
4514
|
}
|
|
3870
4515
|
if (this._clearControlIcon) {
|
|
3871
|
-
this._clearControlIcon.
|
|
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>`;
|
|
3872
4517
|
}
|
|
3873
4518
|
if (this._dropdown) {
|
|
3874
4519
|
if (this._config.selection.mode === 'multi') {
|
|
@@ -3878,11 +4523,28 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3878
4523
|
this._dropdown.removeAttribute('aria-multiselectable');
|
|
3879
4524
|
}
|
|
3880
4525
|
}
|
|
4526
|
+
this._syncInputContainerMode();
|
|
3881
4527
|
// Re-initialize observers in case infinite scroll was enabled/disabled
|
|
3882
4528
|
this._initializeObservers();
|
|
3883
4529
|
this._syncClearControlState();
|
|
3884
4530
|
this._renderOptions();
|
|
3885
4531
|
}
|
|
4532
|
+
_mergeConfig(target, source) {
|
|
4533
|
+
const result = { ...target };
|
|
4534
|
+
for (const key in source) {
|
|
4535
|
+
if (!Object.prototype.hasOwnProperty.call(source, key))
|
|
4536
|
+
continue;
|
|
4537
|
+
const sourceValue = source[key];
|
|
4538
|
+
const targetValue = result[key];
|
|
4539
|
+
if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue)) {
|
|
4540
|
+
result[key] = this._mergeConfig(targetValue && typeof targetValue === 'object' ? targetValue : {}, sourceValue);
|
|
4541
|
+
}
|
|
4542
|
+
else {
|
|
4543
|
+
result[key] = sourceValue;
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
return result;
|
|
4547
|
+
}
|
|
3886
4548
|
_handleClearControlClick() {
|
|
3887
4549
|
const shouldClearSelection = this._config.clearControl.clearSelection !== false;
|
|
3888
4550
|
const shouldClearSearch = this._config.clearControl.clearSearch !== false;
|
|
@@ -3967,6 +4629,8 @@ class EnhancedSelect extends HTMLElement {
|
|
|
3967
4629
|
* Render options based on current state
|
|
3968
4630
|
*/
|
|
3969
4631
|
_renderOptions() {
|
|
4632
|
+
this._renderCycleId += 1;
|
|
4633
|
+
const renderCycleId = this._renderCycleId;
|
|
3970
4634
|
// Cleanup observer
|
|
3971
4635
|
if (this._loadMoreTrigger && this._intersectionObserver) {
|
|
3972
4636
|
this._intersectionObserver.unobserve(this._loadMoreTrigger);
|
|
@@ -4031,23 +4695,21 @@ class EnhancedSelect extends HTMLElement {
|
|
|
4031
4695
|
}
|
|
4032
4696
|
else {
|
|
4033
4697
|
// Normal rendering (flat list or filtered)
|
|
4034
|
-
|
|
4698
|
+
const filteredIndices = [];
|
|
4035
4699
|
this._state.loadedItems.forEach((item, index) => {
|
|
4036
|
-
// Apply filter if query exists
|
|
4037
4700
|
if (query) {
|
|
4038
4701
|
try {
|
|
4039
4702
|
const label = String(getLabel(item)).toLowerCase();
|
|
4040
4703
|
if (!label.includes(query))
|
|
4041
4704
|
return;
|
|
4042
4705
|
}
|
|
4043
|
-
catch (
|
|
4706
|
+
catch (_e) {
|
|
4044
4707
|
return;
|
|
4045
4708
|
}
|
|
4046
4709
|
}
|
|
4047
|
-
|
|
4048
|
-
this._renderSingleOption(item, index, getValue, getLabel);
|
|
4710
|
+
filteredIndices.push(index);
|
|
4049
4711
|
});
|
|
4050
|
-
if (
|
|
4712
|
+
if (filteredIndices.length === 0 && !this._state.isBusy) {
|
|
4051
4713
|
const empty = document.createElement('div');
|
|
4052
4714
|
empty.setAttribute('part', 'no-results');
|
|
4053
4715
|
empty.className = 'empty-state';
|
|
@@ -4059,6 +4721,54 @@ class EnhancedSelect extends HTMLElement {
|
|
|
4059
4721
|
}
|
|
4060
4722
|
this._optionsContainer.appendChild(empty);
|
|
4061
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
|
+
}
|
|
4062
4772
|
}
|
|
4063
4773
|
// Append Busy Indicator if busy
|
|
4064
4774
|
if (this._state.isBusy && this._config.busyBucket.enabled) {
|
|
@@ -4083,7 +4793,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
4083
4793
|
}
|
|
4084
4794
|
this._finalizePerfMarks();
|
|
4085
4795
|
}
|
|
4086
|
-
_renderSingleOption(item, index, getValue, getLabel) {
|
|
4796
|
+
_renderSingleOption(item, index, getValue, getLabel, targetContainer = this._optionsContainer) {
|
|
4087
4797
|
const isSelected = this._state.selectedIndices.has(index);
|
|
4088
4798
|
const isDisabled = Boolean(item?.disabled);
|
|
4089
4799
|
const optionId = `${this._uniqueId}-option-${index}`;
|
|
@@ -4099,7 +4809,7 @@ class EnhancedSelect extends HTMLElement {
|
|
|
4099
4809
|
disabled: isDisabled,
|
|
4100
4810
|
id: optionId,
|
|
4101
4811
|
});
|
|
4102
|
-
|
|
4812
|
+
targetContainer.appendChild(optionElement);
|
|
4103
4813
|
return;
|
|
4104
4814
|
}
|
|
4105
4815
|
const option = new SelectOption({
|
|
@@ -4128,20 +4838,16 @@ class EnhancedSelect extends HTMLElement {
|
|
|
4128
4838
|
option.dataset.smValue = String(val);
|
|
4129
4839
|
}
|
|
4130
4840
|
option.id = option.id || optionId;
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
shiftKey: mouseEvent.shiftKey,
|
|
4136
|
-
toggleKey: mouseEvent.ctrlKey || mouseEvent.metaKey,
|
|
4137
|
-
});
|
|
4138
|
-
});
|
|
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!
|
|
4139
4845
|
option.addEventListener('optionRemove', (event) => {
|
|
4140
4846
|
const detail = event.detail;
|
|
4141
4847
|
const targetIndex = detail?.index ?? index;
|
|
4142
4848
|
this._handleOptionRemove(targetIndex);
|
|
4143
4849
|
});
|
|
4144
|
-
|
|
4850
|
+
targetContainer.appendChild(option);
|
|
4145
4851
|
}
|
|
4146
4852
|
_normalizeCustomOptionElement(element, meta) {
|
|
4147
4853
|
const optionEl = element instanceof HTMLElement ? element : document.createElement('div');
|
|
@@ -4228,35 +4934,9 @@ class EnhancedSelect extends HTMLElement {
|
|
|
4228
4934
|
optionEl.tabIndex = -1;
|
|
4229
4935
|
}
|
|
4230
4936
|
if (!this._customOptionBoundElements.has(optionEl)) {
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
if (current.getAttribute('aria-disabled') === 'true')
|
|
4235
|
-
return;
|
|
4236
|
-
const parsedIndex = Number(current.dataset.index);
|
|
4237
|
-
if (!Number.isFinite(parsedIndex))
|
|
4238
|
-
return;
|
|
4239
|
-
const mouseEvent = e;
|
|
4240
|
-
this._selectOption(parsedIndex, {
|
|
4241
|
-
shiftKey: mouseEvent.shiftKey,
|
|
4242
|
-
toggleKey: mouseEvent.ctrlKey || mouseEvent.metaKey,
|
|
4243
|
-
});
|
|
4244
|
-
});
|
|
4245
|
-
optionEl.addEventListener('keydown', (e) => {
|
|
4246
|
-
if (e.key !== 'Enter' && e.key !== ' ')
|
|
4247
|
-
return;
|
|
4248
|
-
const current = e.currentTarget;
|
|
4249
|
-
if (current.getAttribute('aria-disabled') === 'true')
|
|
4250
|
-
return;
|
|
4251
|
-
const parsedIndex = Number(current.dataset.index);
|
|
4252
|
-
if (!Number.isFinite(parsedIndex))
|
|
4253
|
-
return;
|
|
4254
|
-
e.preventDefault();
|
|
4255
|
-
this._selectOption(parsedIndex, {
|
|
4256
|
-
shiftKey: e.shiftKey,
|
|
4257
|
-
toggleKey: e.ctrlKey || e.metaKey,
|
|
4258
|
-
});
|
|
4259
|
-
});
|
|
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.
|
|
4260
4940
|
this._customOptionBoundElements.add(optionEl);
|
|
4261
4941
|
}
|
|
4262
4942
|
return optionEl;
|