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