thekselect 1.0.0

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.
@@ -0,0 +1,1171 @@
1
+ (function(global, factory) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ThekSelect = {}));
3
+ })(this, function(exports) {
4
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
5
+ //#region src/core/state.ts
6
+ var StateManager = class {
7
+ state;
8
+ listeners = /* @__PURE__ */ new Set();
9
+ constructor(initialState) {
10
+ this.state = { ...initialState };
11
+ }
12
+ getState() {
13
+ return { ...this.state };
14
+ }
15
+ setState(newState) {
16
+ const oldState = this.state;
17
+ this.state = {
18
+ ...this.state,
19
+ ...newState
20
+ };
21
+ if (Object.keys(newState).some((key) => {
22
+ const val = newState[key];
23
+ const oldVal = oldState[key];
24
+ if (Array.isArray(val) && Array.isArray(oldVal)) return val.length !== oldVal.length || val.some((item, index) => item !== oldVal[index]);
25
+ return val !== oldVal;
26
+ })) this.notify();
27
+ }
28
+ subscribe(listener) {
29
+ this.listeners.add(listener);
30
+ return () => this.listeners.delete(listener);
31
+ }
32
+ notify() {
33
+ const currentState = this.getState();
34
+ this.listeners.forEach((listener) => listener(currentState));
35
+ }
36
+ };
37
+ //#endregion
38
+ //#region src/utils/debounce.ts
39
+ function debounce(fn, delay) {
40
+ let timeoutId = null;
41
+ const debounced = ((...args) => {
42
+ if (timeoutId !== null) clearTimeout(timeoutId);
43
+ timeoutId = setTimeout(() => {
44
+ timeoutId = null;
45
+ fn(...args);
46
+ }, delay);
47
+ });
48
+ debounced.cancel = () => {
49
+ if (timeoutId !== null) {
50
+ clearTimeout(timeoutId);
51
+ timeoutId = null;
52
+ }
53
+ };
54
+ return debounced;
55
+ }
56
+ //#endregion
57
+ //#region src/utils/dom.ts
58
+ function generateId(prefix = "thek") {
59
+ return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
60
+ }
61
+ //#endregion
62
+ //#region src/utils/styles.ts
63
+ var BASE_STYLES = `:root {
64
+ --thek-primary: #0f172a;
65
+ --thek-primary-light: #f1f5f9;
66
+ --thek-bg-surface: #ffffff;
67
+ --thek-bg-panel: #f8fafc;
68
+ --thek-bg-subtle: #f1f5f9;
69
+ --thek-border: #e2e8f0;
70
+ --thek-border-strong: #cbd5e1;
71
+ --thek-text-main: #0f172a;
72
+ --thek-text-muted: #64748b;
73
+ --thek-text-inverse: #ffffff;
74
+ --thek-danger: #ef4444;
75
+ --thek-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
76
+ --thek-input-height: 40px;
77
+ --thek-height-sm: 32px;
78
+ --thek-height-md: 40px;
79
+ --thek-height-lg: 48px;
80
+ --thek-item-padding: 8px 10px;
81
+ --thek-font-family: inherit;
82
+ --thek-border-radius: 8px;
83
+ }
84
+
85
+ @media (prefers-color-scheme: dark) {
86
+ :root {
87
+ --thek-primary: #38bdf8;
88
+ --thek-primary-light: rgba(56, 189, 248, 0.15);
89
+ --thek-bg-surface: #0f172a;
90
+ --thek-bg-panel: #334155;
91
+ --thek-bg-subtle: #475569;
92
+ --thek-border: #334155;
93
+ --thek-border-strong: #475569;
94
+ --thek-text-main: #f8fafc;
95
+ --thek-text-muted: #94a3b8;
96
+ --thek-text-inverse: #0f172a;
97
+ --thek-danger: #f43f5e;
98
+ --thek-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
99
+ }
100
+ }
101
+
102
+ [data-theme='dark'] {
103
+ --thek-primary: #38bdf8;
104
+ --thek-primary-light: rgba(56, 189, 248, 0.15);
105
+ --thek-bg-surface: #0f172a;
106
+ --thek-bg-panel: #334155;
107
+ --thek-bg-subtle: #475569;
108
+ --thek-border: #334155;
109
+ --thek-border-strong: #475569;
110
+ --thek-text-main: #f8fafc;
111
+ --thek-text-muted: #94a3b8;
112
+ --thek-text-inverse: #0f172a;
113
+ --thek-danger: #f43f5e;
114
+ --thek-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
115
+ }
116
+
117
+ .thek-select {
118
+ position: relative;
119
+ width: 100%;
120
+ font-family: var(--thek-font-family);
121
+ box-sizing: border-box;
122
+ }
123
+
124
+ .thek-select *, .thek-dropdown * {
125
+ box-sizing: border-box;
126
+ }
127
+
128
+ .thek-control {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: space-between;
132
+ padding: 4px 12px;
133
+ cursor: pointer;
134
+ min-height: var(--thek-input-height);
135
+ background-color: var(--thek-bg-surface);
136
+ color: var(--thek-text-main);
137
+ border: 1px solid var(--thek-border);
138
+ border-radius: var(--thek-border-radius);
139
+ transition: all 0.2s ease;
140
+ }
141
+
142
+ .thek-control:hover {
143
+ border-color: var(--thek-border-strong);
144
+ }
145
+
146
+ .thek-select.thek-open .thek-control {
147
+ border-color: var(--thek-primary);
148
+ box-shadow: 0 0 0 2px var(--thek-primary-light);
149
+ }
150
+
151
+ .thek-placeholder {
152
+ color: var(--thek-text-muted);
153
+ margin-left: 2px;
154
+ font-size: 0.95em;
155
+ }
156
+
157
+ .thek-indicators {
158
+ display: flex;
159
+ align-items: center;
160
+ padding-left: 12px;
161
+ color: var(--thek-text-muted);
162
+ }
163
+
164
+ .thek-arrow {
165
+ font-size: 0.8em;
166
+ transition: transform 0.2s ease;
167
+ }
168
+
169
+ .thek-open .thek-arrow {
170
+ transform: rotate(180deg);
171
+ }
172
+
173
+ .thek-selection {
174
+ display: flex;
175
+ flex-wrap: nowrap;
176
+ overflow: hidden;
177
+ gap: 6px;
178
+ flex: 1;
179
+ -webkit-mask-image: linear-gradient(to right, black 90%, transparent 100%);
180
+ mask-image: linear-gradient(to right, black 90%, transparent 100%);
181
+ }
182
+
183
+ .thek-summary-text {
184
+ font-size: 0.9em;
185
+ color: var(--thek-text-main);
186
+ white-space: nowrap;
187
+ font-weight: 500;
188
+ }
189
+
190
+ .thek-tag {
191
+ flex-shrink: 0;
192
+ background-color: var(--thek-bg-panel);
193
+ border: 1px solid var(--thek-border);
194
+ color: var(--thek-text-main);
195
+ border-radius: calc(var(--thek-border-radius) - 2px);
196
+ padding: 2px 8px;
197
+ font-size: 0.8em;
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 6px;
201
+ font-weight: 500;
202
+ transition: background-color 0.1s;
203
+ }
204
+
205
+ .thek-tag:hover {
206
+ background-color: var(--thek-bg-subtle);
207
+ }
208
+
209
+ .thek-tag-remove {
210
+ cursor: pointer;
211
+ color: var(--thek-text-muted);
212
+ font-size: 1.1em;
213
+ line-height: 1;
214
+ }
215
+
216
+ .thek-tag-remove:hover {
217
+ color: var(--thek-danger);
218
+ }
219
+
220
+ .thek-dropdown {
221
+ background-color: var(--thek-bg-surface);
222
+ border: 1px solid var(--thek-border);
223
+ border-radius: var(--thek-border-radius);
224
+ box-shadow: var(--thek-shadow);
225
+ overflow: hidden;
226
+ box-sizing: border-box;
227
+ margin-top: 4px;
228
+ animation: thek-fade-in 0.15s ease-out;
229
+ }
230
+
231
+ @keyframes thek-fade-in {
232
+ from { opacity: 0; transform: translateY(-4px); }
233
+ to { opacity: 1; transform: translateY(0); }
234
+ }
235
+
236
+ .thek-search-wrapper {
237
+ padding: 10px;
238
+ border-bottom: 1px solid var(--thek-border);
239
+ position: relative;
240
+ background-color: var(--thek-bg-surface);
241
+ }
242
+
243
+ .thek-input {
244
+ width: 100%;
245
+ border: 1px solid var(--thek-border);
246
+ background: var(--thek-bg-surface);
247
+ padding: 8px 12px 8px 34px;
248
+ border-radius: calc(var(--thek-border-radius) - 2px);
249
+ color: var(--thek-text-main);
250
+ outline: none;
251
+ font-size: 0.9em;
252
+ transition: border-color 0.2s;
253
+ }
254
+
255
+ .thek-input:focus {
256
+ border-color: var(--thek-primary);
257
+ }
258
+
259
+ .thek-search-icon {
260
+ position: absolute;
261
+ left: 22px;
262
+ top: 50%;
263
+ transform: translateY(-50%);
264
+ color: var(--thek-text-muted);
265
+ font-size: 0.85em;
266
+ }
267
+
268
+ .thek-options {
269
+ list-style: none;
270
+ margin: 0;
271
+ padding: 6px;
272
+ max-height: 240px;
273
+ overflow-y: auto;
274
+ }
275
+
276
+ .thek-option {
277
+ padding: var(--thek-item-padding);
278
+ border-radius: 6px;
279
+ cursor: pointer;
280
+ display: flex;
281
+ align-items: center;
282
+ gap: 10px;
283
+ transition: all 0.1s ease;
284
+ color: var(--thek-text-main);
285
+ font-size: 0.95em;
286
+ margin-bottom: 2px;
287
+ }
288
+
289
+ .thek-option:last-child {
290
+ margin-bottom: 0;
291
+ }
292
+
293
+ .thek-option:hover {
294
+ background-color: var(--thek-bg-panel);
295
+ }
296
+
297
+ .thek-option.thek-focused {
298
+ background-color: var(--thek-bg-panel);
299
+ }
300
+
301
+ .thek-option.thek-selected {
302
+ background-color: var(--thek-primary-light);
303
+ color: var(--thek-primary);
304
+ font-weight: 500;
305
+ }
306
+
307
+ .thek-checkbox {
308
+ width: 1.2em;
309
+ height: 1.2em;
310
+ border: 1.5px solid var(--thek-border-strong);
311
+ border-radius: 4px;
312
+ display: flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ background: var(--thek-bg-surface);
316
+ font-size: 0.7em;
317
+ flex-shrink: 0;
318
+ transition: all 0.2s;
319
+ }
320
+
321
+ .thek-option.thek-selected .thek-checkbox {
322
+ background-color: var(--thek-primary);
323
+ border-color: var(--thek-primary);
324
+ color: var(--thek-text-inverse);
325
+ }
326
+
327
+ .thek-option.thek-disabled {
328
+ opacity: 0.4;
329
+ cursor: not-allowed;
330
+ background-color: transparent;
331
+ }
332
+
333
+ .thek-no-results, .thek-loading {
334
+ padding: 20px 12px;
335
+ text-align: center;
336
+ color: var(--thek-text-muted);
337
+ font-size: 0.9em;
338
+ }
339
+
340
+ .thek-disabled .thek-control {
341
+ background-color: var(--thek-bg-subtle);
342
+ cursor: not-allowed;
343
+ opacity: 0.7;
344
+ border-color: var(--thek-border);
345
+ }
346
+ `;
347
+ var injected = false;
348
+ function injectStyles() {
349
+ if (injected || typeof document === "undefined") return;
350
+ const style = document.createElement("style");
351
+ style.id = "thekselect-base-styles";
352
+ style.textContent = BASE_STYLES;
353
+ document.head.appendChild(style);
354
+ injected = true;
355
+ }
356
+ //#endregion
357
+ //#region src/core/dom-renderer.ts
358
+ var DomRenderer = class {
359
+ wrapper;
360
+ control;
361
+ selectionContainer;
362
+ indicatorsContainer;
363
+ placeholderElement;
364
+ input;
365
+ dropdown;
366
+ optionsList;
367
+ lastState = null;
368
+ lastFilteredOptions = [];
369
+ constructor(config, id, callbacks) {
370
+ this.config = config;
371
+ this.id = id;
372
+ this.callbacks = callbacks;
373
+ }
374
+ normalizeHeight(value) {
375
+ if (typeof value === "number") return `${value}px`;
376
+ const trimmed = value.trim();
377
+ if (/^\d+(\.\d+)?$/.test(trimmed)) return `${trimmed}px`;
378
+ return trimmed;
379
+ }
380
+ applyHeight(height) {
381
+ const resolved = this.normalizeHeight(height);
382
+ this.wrapper.style.setProperty("--thek-input-height", resolved);
383
+ this.dropdown.style.setProperty("--thek-input-height", resolved);
384
+ }
385
+ createDom() {
386
+ this.wrapper = document.createElement("div");
387
+ this.wrapper.className = "thek-select";
388
+ if (this.config.disabled) this.wrapper.classList.add("thek-disabled");
389
+ if (this.config.multiple) this.wrapper.classList.add("thek-multiple");
390
+ this.control = document.createElement("div");
391
+ this.control.className = "thek-control";
392
+ this.control.setAttribute("role", "combobox");
393
+ this.control.setAttribute("aria-expanded", "false");
394
+ this.control.setAttribute("aria-haspopup", "listbox");
395
+ this.control.setAttribute("aria-controls", `${this.id}-list`);
396
+ this.selectionContainer = document.createElement("div");
397
+ this.selectionContainer.className = "thek-selection";
398
+ this.placeholderElement = document.createElement("span");
399
+ this.placeholderElement.className = "thek-placeholder";
400
+ this.placeholderElement.textContent = this.config.placeholder;
401
+ this.indicatorsContainer = document.createElement("div");
402
+ this.indicatorsContainer.className = "thek-indicators";
403
+ this.indicatorsContainer.innerHTML = "<i class=\"fa-solid fa-chevron-down thek-arrow\"></i>";
404
+ this.control.appendChild(this.selectionContainer);
405
+ this.control.appendChild(this.placeholderElement);
406
+ this.control.appendChild(this.indicatorsContainer);
407
+ this.dropdown = document.createElement("div");
408
+ this.dropdown.className = "thek-dropdown";
409
+ this.dropdown.hidden = true;
410
+ if (this.config.searchable) {
411
+ const searchWrapper = document.createElement("div");
412
+ searchWrapper.className = "thek-search-wrapper";
413
+ searchWrapper.innerHTML = "<i class=\"fa-solid fa-magnifying-glass thek-search-icon\"></i>";
414
+ this.input = document.createElement("input");
415
+ this.input.className = "thek-input";
416
+ this.input.type = "text";
417
+ this.input.autocomplete = "off";
418
+ this.input.placeholder = "Search...";
419
+ this.input.setAttribute("aria-autocomplete", "list");
420
+ searchWrapper.appendChild(this.input);
421
+ this.dropdown.appendChild(searchWrapper);
422
+ } else {
423
+ this.input = document.createElement("input");
424
+ this.input.type = "hidden";
425
+ }
426
+ this.optionsList = document.createElement("ul");
427
+ this.optionsList.className = "thek-options";
428
+ this.optionsList.id = `${this.id}-list`;
429
+ this.optionsList.setAttribute("role", "listbox");
430
+ this.optionsList.addEventListener("scroll", () => this.handleOptionsScroll());
431
+ this.optionsList.addEventListener("wheel", (e) => this.handleOptionsWheel(e), { passive: false });
432
+ this.dropdown.appendChild(this.optionsList);
433
+ this.wrapper.appendChild(this.control);
434
+ document.body.appendChild(this.dropdown);
435
+ this.applyHeight(this.config.height);
436
+ }
437
+ render(state, filteredOptions) {
438
+ this.lastState = state;
439
+ this.lastFilteredOptions = filteredOptions;
440
+ this.control.setAttribute("aria-expanded", state.isOpen.toString());
441
+ this.dropdown.hidden = !state.isOpen;
442
+ this.wrapper.classList.toggle("thek-open", state.isOpen);
443
+ if (state.isLoading) this.indicatorsContainer.innerHTML = "<i class=\"fa-solid fa-circle-notch fa-spin text-muted\"></i>";
444
+ else this.indicatorsContainer.innerHTML = "<i class=\"fa-solid fa-chevron-down thek-arrow\"></i>";
445
+ this.renderSelectionContent(state);
446
+ this.renderOptionsContent(state, filteredOptions);
447
+ if (state.isOpen) this.positionDropdown();
448
+ }
449
+ renderSelectionContent(state) {
450
+ this.selectionContainer.innerHTML = "";
451
+ const hasSelection = state.selectedValues.length > 0;
452
+ this.placeholderElement.style.display = hasSelection ? "none" : "block";
453
+ this.selectionContainer.style.display = hasSelection ? "flex" : "none";
454
+ if (hasSelection) {
455
+ const vField = this.config.valueField;
456
+ const dField = this.config.displayField;
457
+ if (this.config.multiple) if (state.selectedValues.length > this.config.maxSelectedLabels) {
458
+ const summary = document.createElement("span");
459
+ summary.className = "thek-summary-text";
460
+ summary.textContent = `${state.selectedValues.length} items selected`;
461
+ this.selectionContainer.appendChild(summary);
462
+ } else state.selectedValues.forEach((val, i) => {
463
+ const option = state.options.find((o) => o[vField] === val) || state.selectedOptionsByValue[val] || {
464
+ [vField]: val,
465
+ [dField]: val
466
+ };
467
+ const tag = document.createElement("span");
468
+ tag.className = "thek-tag";
469
+ tag.draggable = true;
470
+ tag.dataset.index = i.toString();
471
+ tag.dataset.value = val;
472
+ const label = document.createElement("span");
473
+ label.className = "thek-tag-label";
474
+ const content = this.config.renderSelection(option);
475
+ if (content instanceof HTMLElement) label.appendChild(content);
476
+ else label.textContent = content;
477
+ tag.appendChild(label);
478
+ const removeBtn = document.createElement("span");
479
+ removeBtn.className = "thek-tag-remove";
480
+ removeBtn.innerHTML = "&times;";
481
+ removeBtn.addEventListener("click", (e) => {
482
+ e.stopPropagation();
483
+ this.callbacks.onSelect(option);
484
+ });
485
+ tag.appendChild(removeBtn);
486
+ this.setupTagDnd(tag);
487
+ this.selectionContainer.appendChild(tag);
488
+ });
489
+ else {
490
+ const val = state.selectedValues[0];
491
+ const option = state.options.find((o) => o[vField] === val) || state.selectedOptionsByValue[val];
492
+ if (option) {
493
+ const content = this.config.renderSelection(option);
494
+ if (content instanceof HTMLElement) this.selectionContainer.appendChild(content);
495
+ else this.selectionContainer.textContent = content;
496
+ }
497
+ }
498
+ }
499
+ }
500
+ renderOptionsContent(state, filteredOptions, alignFocused = true, preservedScrollTop) {
501
+ this.optionsList.innerHTML = "";
502
+ const vField = this.config.valueField;
503
+ const dField = this.config.displayField;
504
+ if (state.isLoading && filteredOptions.length === 0) {
505
+ const li = document.createElement("li");
506
+ li.className = "thek-option thek-loading";
507
+ li.textContent = "Loading...";
508
+ this.optionsList.appendChild(li);
509
+ return;
510
+ }
511
+ const canCreate = this.config.canCreate && state.inputValue && !filteredOptions.some((o) => o[dField] && o[dField].toString().toLowerCase() === state.inputValue.toLowerCase());
512
+ const shouldVirtualize = this.config.virtualize && filteredOptions.length >= this.config.virtualThreshold && !canCreate;
513
+ const itemHeight = Math.max(20, this.config.virtualItemHeight);
514
+ const overscan = Math.max(0, this.config.virtualOverscan);
515
+ if (shouldVirtualize) {
516
+ const viewportHeight = this.optionsList.clientHeight || 240;
517
+ if (alignFocused && state.focusedIndex >= 0 && state.focusedIndex < filteredOptions.length) {
518
+ const focusedTop = state.focusedIndex * itemHeight;
519
+ const focusedBottom = focusedTop + itemHeight;
520
+ const currentTop = this.optionsList.scrollTop;
521
+ const currentBottom = currentTop + viewportHeight;
522
+ if (focusedTop < currentTop) this.optionsList.scrollTop = focusedTop;
523
+ else if (focusedBottom > currentBottom) this.optionsList.scrollTop = focusedBottom - viewportHeight;
524
+ }
525
+ const scrollTop = preservedScrollTop ?? this.optionsList.scrollTop;
526
+ const start = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
527
+ const end = Math.min(filteredOptions.length, Math.ceil((scrollTop + viewportHeight) / itemHeight) + overscan);
528
+ if (start > 0) this.optionsList.appendChild(this.createSpacer(start * itemHeight));
529
+ for (let index = start; index < end; index++) this.optionsList.appendChild(this.createOptionItem(filteredOptions[index], index, state, vField));
530
+ if (end < filteredOptions.length) this.optionsList.appendChild(this.createSpacer((filteredOptions.length - end) * itemHeight));
531
+ if (typeof preservedScrollTop === "number") this.optionsList.scrollTop = preservedScrollTop;
532
+ } else filteredOptions.forEach((option, index) => {
533
+ this.optionsList.appendChild(this.createOptionItem(option, index, state, vField));
534
+ });
535
+ const exactMatch = filteredOptions.some((o) => o[dField] && o[dField].toString().toLowerCase() === state.inputValue.toLowerCase());
536
+ if (this.config.canCreate && state.inputValue && !exactMatch) {
537
+ const li = document.createElement("li");
538
+ li.className = "thek-option thek-create";
539
+ li.textContent = this.config.createText.replace("{%t}", state.inputValue);
540
+ if (state.focusedIndex === filteredOptions.length) li.classList.add("thek-focused");
541
+ li.addEventListener("click", (e) => {
542
+ e.stopPropagation();
543
+ this.callbacks.onCreate(state.inputValue);
544
+ });
545
+ this.optionsList.appendChild(li);
546
+ }
547
+ if (filteredOptions.length === 0 && (!this.config.canCreate || !state.inputValue)) {
548
+ const li = document.createElement("li");
549
+ li.className = "thek-option thek-no-results";
550
+ li.textContent = "No results found";
551
+ this.optionsList.appendChild(li);
552
+ }
553
+ const activeDescendantId = state.focusedIndex >= 0 && state.focusedIndex < filteredOptions.length && !!document.getElementById(`${this.id}-opt-${state.focusedIndex}`) ? `${this.id}-opt-${state.focusedIndex}` : null;
554
+ if (this.config.searchable) if (activeDescendantId) this.input.setAttribute("aria-activedescendant", activeDescendantId);
555
+ else this.input.removeAttribute("aria-activedescendant");
556
+ }
557
+ createSpacer(height) {
558
+ const spacer = document.createElement("li");
559
+ spacer.style.height = `${height}px`;
560
+ spacer.style.padding = "0";
561
+ spacer.style.margin = "0";
562
+ spacer.style.listStyle = "none";
563
+ spacer.setAttribute("aria-hidden", "true");
564
+ return spacer;
565
+ }
566
+ createOptionItem(option, index, state, valueField) {
567
+ const li = document.createElement("li");
568
+ li.className = "thek-option";
569
+ li.id = `${this.id}-opt-${index}`;
570
+ const isSelected = state.selectedValues.includes(option[valueField]);
571
+ if (option.disabled) li.classList.add("thek-disabled");
572
+ if (isSelected) li.classList.add("thek-selected");
573
+ if (state.focusedIndex === index) li.classList.add("thek-focused");
574
+ li.setAttribute("role", "option");
575
+ li.setAttribute("aria-selected", isSelected.toString());
576
+ if (this.config.multiple) {
577
+ const checkbox = document.createElement("div");
578
+ checkbox.className = "thek-checkbox";
579
+ if (isSelected) checkbox.innerHTML = "<i class=\"fa-solid fa-check\"></i>";
580
+ li.appendChild(checkbox);
581
+ }
582
+ const label = document.createElement("span");
583
+ label.className = "thek-option-label";
584
+ const content = this.config.renderOption(option);
585
+ if (content instanceof HTMLElement) label.appendChild(content);
586
+ else label.textContent = content;
587
+ li.appendChild(label);
588
+ li.addEventListener("click", (e) => {
589
+ e.stopPropagation();
590
+ this.callbacks.onSelect(option);
591
+ });
592
+ return li;
593
+ }
594
+ handleOptionsScroll() {
595
+ if (!this.config.virtualize || !this.lastState) return;
596
+ const scrollTop = this.optionsList.scrollTop;
597
+ this.renderOptionsContent(this.lastState, this.lastFilteredOptions, false, scrollTop);
598
+ }
599
+ handleOptionsWheel(e) {
600
+ if (!this.config.virtualize) return;
601
+ const list = this.optionsList;
602
+ const atTop = list.scrollTop <= 0;
603
+ const atBottom = list.scrollTop + list.clientHeight >= list.scrollHeight - 1;
604
+ const scrollingUp = e.deltaY < 0;
605
+ const scrollingDown = e.deltaY > 0;
606
+ if (scrollingUp && !atTop || scrollingDown && !atBottom) {
607
+ e.preventDefault();
608
+ list.scrollTop += e.deltaY;
609
+ }
610
+ }
611
+ positionDropdown() {
612
+ const rect = this.control.getBoundingClientRect();
613
+ const viewportWidth = document.documentElement.clientWidth;
614
+ const scrollX = window.scrollX;
615
+ const scrollY = window.scrollY;
616
+ this.dropdown.style.position = "absolute";
617
+ this.dropdown.style.zIndex = "9999";
618
+ let width = rect.width;
619
+ if (width > viewportWidth - 20) width = viewportWidth - 20;
620
+ this.dropdown.style.width = `${width}px`;
621
+ let left = rect.left + scrollX;
622
+ if (rect.left + width > viewportWidth) left = viewportWidth - width - 10 + scrollX;
623
+ if (left < scrollX + 10) left = scrollX + 10;
624
+ this.dropdown.style.left = `${left}px`;
625
+ this.dropdown.style.top = `${rect.bottom + scrollY}px`;
626
+ }
627
+ setupTagDnd(tag) {
628
+ tag.addEventListener("dragstart", (e) => {
629
+ e.dataTransfer?.setData("text/plain", tag.dataset.index);
630
+ tag.classList.add("thek-dragging");
631
+ });
632
+ tag.addEventListener("dragend", () => {
633
+ tag.classList.remove("thek-dragging");
634
+ });
635
+ tag.addEventListener("dragover", (e) => {
636
+ e.preventDefault();
637
+ tag.classList.add("thek-drag-over");
638
+ });
639
+ tag.addEventListener("dragleave", () => {
640
+ tag.classList.remove("thek-drag-over");
641
+ });
642
+ tag.addEventListener("drop", (e) => {
643
+ e.preventDefault();
644
+ tag.classList.remove("thek-drag-over");
645
+ const fromIndex = parseInt(e.dataTransfer?.getData("text/plain") || "-1");
646
+ const toIndex = parseInt(tag.dataset.index);
647
+ if (fromIndex !== -1 && fromIndex !== toIndex) this.callbacks.onReorder(fromIndex, toIndex);
648
+ });
649
+ }
650
+ setHeight(height) {
651
+ this.config.height = height;
652
+ this.applyHeight(height);
653
+ }
654
+ updateConfig(newConfig) {
655
+ this.config = {
656
+ ...this.config,
657
+ ...newConfig
658
+ };
659
+ }
660
+ destroy() {
661
+ if (this.wrapper.parentNode) this.wrapper.parentNode.removeChild(this.wrapper);
662
+ if (this.dropdown.parentNode) this.dropdown.parentNode.removeChild(this.dropdown);
663
+ }
664
+ };
665
+ //#endregion
666
+ //#region src/core/config-utils.ts
667
+ var NOOP_LOAD_OPTIONS = async (_query) => [];
668
+ function parseSelectOptions(select) {
669
+ return Array.from(select.options).map((opt) => ({
670
+ value: opt.value,
671
+ label: opt.text,
672
+ disabled: opt.disabled,
673
+ selected: opt.selected
674
+ }));
675
+ }
676
+ function buildConfig(element, config, globalDefaults = {}) {
677
+ const isSelect = element instanceof HTMLSelectElement;
678
+ const finalConfig = {
679
+ options: isSelect ? parseSelectOptions(element) : config.options || [],
680
+ multiple: isSelect ? element.multiple : false,
681
+ searchable: true,
682
+ disabled: isSelect ? element.disabled : false,
683
+ placeholder: "Select...",
684
+ canCreate: false,
685
+ createText: "Create '{%t}'...",
686
+ height: 40,
687
+ debounce: 300,
688
+ maxSelectedLabels: 3,
689
+ displayField: "label",
690
+ valueField: "value",
691
+ maxOptions: null,
692
+ virtualize: false,
693
+ virtualItemHeight: 40,
694
+ virtualOverscan: 4,
695
+ virtualThreshold: 80,
696
+ loadOptions: NOOP_LOAD_OPTIONS,
697
+ renderOption: (o) => o.label,
698
+ renderSelection: (o) => o.label,
699
+ ...globalDefaults,
700
+ ...config
701
+ };
702
+ const hasCustomRenderOption = !!(globalDefaults.renderOption || config.renderOption);
703
+ const hasCustomRenderSelection = !!(globalDefaults.renderSelection || config.renderSelection);
704
+ if (!hasCustomRenderOption) finalConfig.renderOption = (o) => o[finalConfig.displayField];
705
+ if (!hasCustomRenderSelection) finalConfig.renderSelection = (o) => o[finalConfig.displayField];
706
+ return finalConfig;
707
+ }
708
+ function buildInitialState(config) {
709
+ const valueField = config.valueField;
710
+ const firstSelected = config.options.find((o) => o.selected);
711
+ const selectedValues = config.multiple ? config.options.filter((o) => o.selected).map((o) => o[valueField]) : firstSelected && valueField in firstSelected ? [firstSelected[valueField]] : [];
712
+ const selectedOptionsByValue = {};
713
+ selectedValues.forEach((value) => {
714
+ const option = config.options.find((o) => o[valueField] === value);
715
+ if (option) selectedOptionsByValue[value] = option;
716
+ });
717
+ return {
718
+ options: config.options,
719
+ selectedValues,
720
+ selectedOptionsByValue,
721
+ isOpen: false,
722
+ focusedIndex: -1,
723
+ inputValue: "",
724
+ isLoading: false
725
+ };
726
+ }
727
+ //#endregion
728
+ //#region src/core/options-logic.ts
729
+ function isRemoteMode(config) {
730
+ return config.loadOptions !== NOOP_LOAD_OPTIONS;
731
+ }
732
+ function getFilteredOptions(config, state) {
733
+ if (isRemoteMode(config) && state.inputValue) return state.options;
734
+ const query = state.inputValue.toLowerCase();
735
+ const displayField = config.displayField;
736
+ const filtered = state.options.filter((option) => {
737
+ const value = option[displayField];
738
+ return value != null && value.toString().toLowerCase().includes(query);
739
+ });
740
+ if (config.maxOptions != null) {
741
+ const limit = Math.max(0, config.maxOptions);
742
+ return filtered.slice(0, limit);
743
+ }
744
+ return filtered;
745
+ }
746
+ function mergeSelectedOptionsByValue(valueField, selectedValues, previous, latestOptions) {
747
+ const byValueFromLatest = {};
748
+ latestOptions.forEach((option) => {
749
+ const value = option[valueField];
750
+ if (typeof value === "string") byValueFromLatest[value] = option;
751
+ });
752
+ const merged = {};
753
+ selectedValues.forEach((value) => {
754
+ const option = byValueFromLatest[value] || previous[value];
755
+ if (option) merged[value] = option;
756
+ });
757
+ return merged;
758
+ }
759
+ //#endregion
760
+ //#region src/core/selection-logic.ts
761
+ function applySelection(config, state, option) {
762
+ const valueField = config.valueField;
763
+ const optionValue = String(option[valueField]);
764
+ const selectedOptionsByValue = { ...state.selectedOptionsByValue };
765
+ if (config.multiple) {
766
+ if (state.selectedValues.includes(optionValue)) {
767
+ const selectedValues = state.selectedValues.filter((v) => v !== optionValue);
768
+ delete selectedOptionsByValue[optionValue];
769
+ return {
770
+ selectedValues,
771
+ selectedOptionsByValue,
772
+ inputValue: "",
773
+ tagEvent: "tagRemoved",
774
+ tagOption: option
775
+ };
776
+ }
777
+ const selectedValues = [...state.selectedValues, optionValue];
778
+ selectedOptionsByValue[optionValue] = option;
779
+ return {
780
+ selectedValues,
781
+ selectedOptionsByValue,
782
+ inputValue: "",
783
+ tagEvent: "tagAdded",
784
+ tagOption: option
785
+ };
786
+ }
787
+ return {
788
+ selectedValues: [optionValue],
789
+ selectedOptionsByValue: { [optionValue]: option },
790
+ inputValue: ""
791
+ };
792
+ }
793
+ function createOptionFromLabel(config, label) {
794
+ const value = label;
795
+ return {
796
+ value,
797
+ label,
798
+ [config.valueField]: value,
799
+ [config.displayField]: label
800
+ };
801
+ }
802
+ function removeLastSelection(config, state) {
803
+ const valueField = config.valueField;
804
+ const selectedValues = [...state.selectedValues];
805
+ const selectedOptionsByValue = { ...state.selectedOptionsByValue };
806
+ const removedValue = selectedValues.pop();
807
+ if (!removedValue) return {
808
+ selectedValues,
809
+ selectedOptionsByValue
810
+ };
811
+ const removedOption = state.options.find((o) => o[valueField] === removedValue) || selectedOptionsByValue[removedValue];
812
+ delete selectedOptionsByValue[removedValue];
813
+ return {
814
+ selectedValues,
815
+ selectedOptionsByValue,
816
+ removedOption
817
+ };
818
+ }
819
+ function reorderSelectedValues(state, from, to) {
820
+ if (!Number.isInteger(from) || !Number.isInteger(to)) return [...state.selectedValues];
821
+ if (from < 0 || to < 0 || from >= state.selectedValues.length || to >= state.selectedValues.length || from === to) return [...state.selectedValues];
822
+ const selectedValues = [...state.selectedValues];
823
+ const [movedItem] = selectedValues.splice(from, 1);
824
+ if (typeof movedItem === "undefined") return [...state.selectedValues];
825
+ selectedValues.splice(to, 0, movedItem);
826
+ return selectedValues;
827
+ }
828
+ function resolveSelectedOptions(config, state) {
829
+ const valueField = config.valueField;
830
+ const displayField = config.displayField;
831
+ return state.selectedValues.map((value) => state.options.find((o) => o[valueField] === value) || state.selectedOptionsByValue[value] || {
832
+ value,
833
+ label: value,
834
+ [valueField]: value,
835
+ [displayField]: value
836
+ });
837
+ }
838
+ function buildSelectedOptionsMapFromValues(config, state, values) {
839
+ const valueField = config.valueField;
840
+ const selectedOptionsByValue = {};
841
+ values.forEach((value) => {
842
+ const option = state.options.find((o) => o[valueField] === value) || state.selectedOptionsByValue[value];
843
+ if (option) selectedOptionsByValue[value] = option;
844
+ });
845
+ return selectedOptionsByValue;
846
+ }
847
+ //#endregion
848
+ //#region src/core/event-emitter.ts
849
+ var ThekSelectEventEmitter = class {
850
+ listeners = /* @__PURE__ */ new Map();
851
+ on(event, callback) {
852
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
853
+ this.listeners.get(event).add(callback);
854
+ return () => this.off(event, callback);
855
+ }
856
+ off(event, callback) {
857
+ const callbacks = this.listeners.get(event);
858
+ if (!callbacks) return;
859
+ callbacks.delete(callback);
860
+ if (callbacks.size === 0) this.listeners.delete(event);
861
+ }
862
+ emit(event, data) {
863
+ if (!this.listeners.has(event)) return;
864
+ this.listeners.get(event).forEach((listener) => listener(data));
865
+ }
866
+ };
867
+ var globalEventManager = class GlobalEventManager {
868
+ static instance;
869
+ resizeListeners = /* @__PURE__ */ new Set();
870
+ scrollListeners = /* @__PURE__ */ new Set();
871
+ clickListeners = /* @__PURE__ */ new Set();
872
+ constructor() {
873
+ if (typeof window !== "undefined") {
874
+ window.addEventListener("resize", (e) => this.notify(this.resizeListeners, e));
875
+ window.addEventListener("scroll", (e) => this.notify(this.scrollListeners, e), true);
876
+ document.addEventListener("click", (e) => this.notify(this.clickListeners, e));
877
+ }
878
+ }
879
+ static getInstance() {
880
+ if (!GlobalEventManager.instance) GlobalEventManager.instance = new GlobalEventManager();
881
+ return GlobalEventManager.instance;
882
+ }
883
+ notify(listeners, event) {
884
+ listeners.forEach((callback) => callback(event));
885
+ }
886
+ onResize(callback) {
887
+ this.resizeListeners.add(callback);
888
+ return () => this.resizeListeners.delete(callback);
889
+ }
890
+ onScroll(callback) {
891
+ this.scrollListeners.add(callback);
892
+ return () => this.scrollListeners.delete(callback);
893
+ }
894
+ onClick(callback) {
895
+ this.clickListeners.add(callback);
896
+ return () => this.clickListeners.delete(callback);
897
+ }
898
+ }.getInstance();
899
+ //#endregion
900
+ exports.ThekSelect = class ThekSelect {
901
+ static globalDefaults = {};
902
+ config;
903
+ stateManager;
904
+ renderer;
905
+ id;
906
+ events = new ThekSelectEventEmitter();
907
+ originalElement;
908
+ unsubscribeEvents = [];
909
+ unsubscribeState;
910
+ remoteRequestId = 0;
911
+ isDestroyed = false;
912
+ focusTimeoutId = null;
913
+ constructor(element, config = {}) {
914
+ injectStyles();
915
+ const el = typeof element === "string" ? document.querySelector(element) : element;
916
+ if (!el) throw new Error("Element not found");
917
+ this.originalElement = el;
918
+ this.id = generateId();
919
+ this.config = buildConfig(this.originalElement, config, ThekSelect.globalDefaults);
920
+ this.stateManager = new StateManager(buildInitialState(this.config));
921
+ this.renderer = new DomRenderer(this.config, this.id, {
922
+ onSelect: (option) => this.handleSelect(option),
923
+ onCreate: (label) => this.handleCreate(label),
924
+ onRemove: (option) => this.handleSelect(option),
925
+ onReorder: (from, to) => this.handleReorder(from, to)
926
+ });
927
+ this.setupHandleSearch();
928
+ this.initialize();
929
+ }
930
+ static init(element, config = {}) {
931
+ return new ThekSelect(element, config);
932
+ }
933
+ static setDefaults(defaults) {
934
+ ThekSelect.globalDefaults = {
935
+ ...ThekSelect.globalDefaults,
936
+ ...defaults
937
+ };
938
+ }
939
+ static resetDefaults() {
940
+ ThekSelect.globalDefaults = {};
941
+ }
942
+ initialize() {
943
+ this.renderer.createDom();
944
+ this.setupListeners();
945
+ this.unsubscribeState = this.stateManager.subscribe(() => this.render());
946
+ this.render();
947
+ if (this.originalElement.parentNode) {
948
+ this.originalElement.style.display = "none";
949
+ this.originalElement.parentNode.insertBefore(this.renderer.wrapper, this.originalElement.nextSibling);
950
+ }
951
+ }
952
+ setupListeners() {
953
+ this.renderer.control.addEventListener("click", () => {
954
+ if (this.config.disabled) return;
955
+ this.toggleDropdown();
956
+ });
957
+ if (this.config.searchable) this.renderer.input.addEventListener("input", (e) => {
958
+ const value = e.target.value;
959
+ this.stateManager.setState({ inputValue: value });
960
+ this.handleSearch(value);
961
+ });
962
+ this.renderer.input.addEventListener("keydown", (e) => this.handleKeyDown(e));
963
+ this.unsubscribeEvents.push(globalEventManager.onClick((e) => {
964
+ const event = e;
965
+ if (!this.renderer.wrapper.contains(event.target) && !this.renderer.dropdown.contains(event.target)) this.closeDropdown();
966
+ }));
967
+ this.unsubscribeEvents.push(globalEventManager.onResize(() => this.renderer.positionDropdown()));
968
+ this.unsubscribeEvents.push(globalEventManager.onScroll(() => this.renderer.positionDropdown()));
969
+ }
970
+ handleSearch;
971
+ setupHandleSearch() {
972
+ this.handleSearch = debounce(async (query) => {
973
+ this.emit("search", query);
974
+ if (isRemoteMode(this.config)) if (query.length > 0) {
975
+ const requestId = ++this.remoteRequestId;
976
+ this.stateManager.setState({ isLoading: true });
977
+ try {
978
+ const options = await this.config.loadOptions(query);
979
+ if (this.isDestroyed || requestId !== this.remoteRequestId) return;
980
+ const state = this.stateManager.getState();
981
+ this.stateManager.setState({
982
+ options,
983
+ isLoading: false,
984
+ focusedIndex: 0,
985
+ selectedOptionsByValue: mergeSelectedOptionsByValue(this.config.valueField, state.selectedValues, state.selectedOptionsByValue, options)
986
+ });
987
+ } catch {
988
+ if (this.isDestroyed || requestId !== this.remoteRequestId) return;
989
+ this.stateManager.setState({ isLoading: false });
990
+ }
991
+ } else {
992
+ this.remoteRequestId++;
993
+ this.stateManager.setState({
994
+ options: this.config.options,
995
+ focusedIndex: -1,
996
+ isLoading: false
997
+ });
998
+ }
999
+ else this.stateManager.setState({ focusedIndex: 0 });
1000
+ }, this.config.debounce);
1001
+ }
1002
+ handleKeyDown(e) {
1003
+ const state = this.stateManager.getState();
1004
+ const filteredOptions = getFilteredOptions(this.config, state);
1005
+ const displayField = this.config.displayField;
1006
+ switch (e.key) {
1007
+ case "ArrowDown":
1008
+ e.preventDefault();
1009
+ this.openDropdown();
1010
+ const maxIndex = this.config.canCreate && state.inputValue && !filteredOptions.some((o) => o[displayField].toLowerCase() === state.inputValue.toLowerCase()) ? filteredOptions.length : filteredOptions.length - 1;
1011
+ this.stateManager.setState({ focusedIndex: Math.min(state.focusedIndex + 1, maxIndex) });
1012
+ break;
1013
+ case "ArrowUp":
1014
+ e.preventDefault();
1015
+ this.stateManager.setState({ focusedIndex: Math.max(state.focusedIndex - 1, 0) });
1016
+ break;
1017
+ case "Enter":
1018
+ e.preventDefault();
1019
+ if (state.focusedIndex >= 0 && state.focusedIndex < filteredOptions.length) this.handleSelect(filteredOptions[state.focusedIndex]);
1020
+ else if (this.config.canCreate && state.inputValue && state.focusedIndex === filteredOptions.length) this.handleCreate(state.inputValue);
1021
+ break;
1022
+ case "Escape":
1023
+ this.closeDropdown();
1024
+ break;
1025
+ case "Backspace":
1026
+ if (state.inputValue === "" && this.config.multiple && state.selectedValues.length > 0) this.handleRemoveLastSelection();
1027
+ break;
1028
+ }
1029
+ }
1030
+ toggleDropdown() {
1031
+ if (this.stateManager.getState().isOpen) this.closeDropdown();
1032
+ else this.openDropdown();
1033
+ }
1034
+ openDropdown() {
1035
+ if (this.stateManager.getState().isOpen) return;
1036
+ this.stateManager.setState({
1037
+ isOpen: true,
1038
+ focusedIndex: 0
1039
+ });
1040
+ this.renderer.positionDropdown();
1041
+ if (this.config.searchable) this.focusTimeoutId = setTimeout(() => {
1042
+ if (!this.isDestroyed) this.renderer.input.focus();
1043
+ }, 10);
1044
+ this.emit("open", null);
1045
+ }
1046
+ closeDropdown() {
1047
+ if (!this.stateManager.getState().isOpen) return;
1048
+ this.stateManager.setState({
1049
+ isOpen: false,
1050
+ focusedIndex: -1,
1051
+ inputValue: ""
1052
+ });
1053
+ this.renderer.input.value = "";
1054
+ this.emit("close", null);
1055
+ }
1056
+ handleSelect(option) {
1057
+ if (option.disabled) return;
1058
+ const state = this.stateManager.getState();
1059
+ const update = applySelection(this.config, state, option);
1060
+ if (!this.config.multiple) this.closeDropdown();
1061
+ this.stateManager.setState({
1062
+ selectedValues: update.selectedValues,
1063
+ selectedOptionsByValue: update.selectedOptionsByValue,
1064
+ inputValue: ""
1065
+ });
1066
+ this.renderer.input.value = "";
1067
+ this.syncOriginalElement(update.selectedValues);
1068
+ if (update.tagEvent && update.tagOption) this.emit(update.tagEvent, update.tagOption);
1069
+ this.emit("change", this.getValue());
1070
+ }
1071
+ handleCreate(label) {
1072
+ const newOption = createOptionFromLabel(this.config, label);
1073
+ const state = this.stateManager.getState();
1074
+ this.stateManager.setState({ options: [...state.options, newOption] });
1075
+ this.handleSelect(newOption);
1076
+ }
1077
+ handleRemoveLastSelection() {
1078
+ const state = this.stateManager.getState();
1079
+ const update = removeLastSelection(this.config, state);
1080
+ this.stateManager.setState({
1081
+ selectedValues: update.selectedValues,
1082
+ selectedOptionsByValue: update.selectedOptionsByValue
1083
+ });
1084
+ this.syncOriginalElement(update.selectedValues);
1085
+ if (update.removedOption) this.emit("tagRemoved", update.removedOption);
1086
+ this.emit("change", this.getValue());
1087
+ }
1088
+ handleReorder(from, to) {
1089
+ const selectedValues = reorderSelectedValues(this.stateManager.getState(), from, to);
1090
+ this.stateManager.setState({ selectedValues });
1091
+ this.syncOriginalElement(selectedValues);
1092
+ this.emit("reordered", selectedValues);
1093
+ this.emit("change", this.getValue());
1094
+ }
1095
+ syncOriginalElement(values) {
1096
+ if (this.originalElement instanceof HTMLSelectElement) {
1097
+ const select = this.originalElement;
1098
+ Array.from(select.options).forEach((opt) => {
1099
+ opt.selected = values.includes(opt.value);
1100
+ });
1101
+ values.forEach((val) => {
1102
+ if (!Array.from(select.options).some((opt) => opt.value === val)) {
1103
+ const opt = new Option(val, val, true, true);
1104
+ select.add(opt);
1105
+ }
1106
+ });
1107
+ select.dispatchEvent(new Event("change", { bubbles: true }));
1108
+ }
1109
+ }
1110
+ render() {
1111
+ this.renderer.render(this.stateManager.getState(), getFilteredOptions(this.config, this.stateManager.getState()));
1112
+ }
1113
+ on(event, callback) {
1114
+ return this.events.on(event, callback);
1115
+ }
1116
+ emit(event, data) {
1117
+ this.events.emit(event, data);
1118
+ }
1119
+ getValue() {
1120
+ const state = this.stateManager.getState();
1121
+ return this.config.multiple ? state.selectedValues : state.selectedValues[0];
1122
+ }
1123
+ getSelectedOptions() {
1124
+ const selected = resolveSelectedOptions(this.config, this.stateManager.getState());
1125
+ return this.config.multiple ? selected : selected[0];
1126
+ }
1127
+ setValue(value, silent = false) {
1128
+ const state = this.stateManager.getState();
1129
+ const stringValues = (Array.isArray(value) ? value : [value]).filter((entry) => typeof entry === "string");
1130
+ const values = this.config.multiple ? Array.from(new Set(stringValues)) : stringValues.slice(0, 1);
1131
+ const selectedOptionsByValue = buildSelectedOptionsMapFromValues(this.config, state, values);
1132
+ this.stateManager.setState({
1133
+ selectedValues: values,
1134
+ selectedOptionsByValue
1135
+ });
1136
+ this.syncOriginalElement(values);
1137
+ if (!silent) this.emit("change", this.getValue());
1138
+ }
1139
+ setHeight(height) {
1140
+ this.config.height = height;
1141
+ this.renderer.setHeight(height);
1142
+ this.renderer.positionDropdown();
1143
+ }
1144
+ setRenderOption(callback) {
1145
+ this.config.renderOption = callback;
1146
+ this.renderer.updateConfig({ renderOption: callback });
1147
+ this.render();
1148
+ }
1149
+ setMaxOptions(max) {
1150
+ this.config.maxOptions = max;
1151
+ this.render();
1152
+ }
1153
+ destroy() {
1154
+ this.isDestroyed = true;
1155
+ this.remoteRequestId++;
1156
+ this.handleSearch.cancel();
1157
+ if (this.focusTimeoutId !== null) {
1158
+ clearTimeout(this.focusTimeoutId);
1159
+ this.focusTimeoutId = null;
1160
+ }
1161
+ if (this.unsubscribeState) {
1162
+ this.unsubscribeState();
1163
+ this.unsubscribeState = void 0;
1164
+ }
1165
+ this.unsubscribeEvents.forEach((unsub) => unsub());
1166
+ this.unsubscribeEvents = [];
1167
+ this.renderer.destroy();
1168
+ this.originalElement.style.display = "";
1169
+ }
1170
+ };
1171
+ });