forge-select 0.5.0 → 0.7.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.
- package/dist/index.cjs +343 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -4
- package/dist/index.d.ts +44 -4
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +343 -85
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/styles/forge-select.css +4 -5
package/dist/index.cjs
CHANGED
|
@@ -123,10 +123,10 @@ function parseOption(option) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
// src/option-renderer.ts
|
|
126
|
-
function renderOptionContent(container, option, template, variant = "row") {
|
|
126
|
+
function renderOptionContent(container, option, template, variant = "row", sanitizeTemplate) {
|
|
127
127
|
if (template) {
|
|
128
128
|
const result = template(option);
|
|
129
|
-
if (typeof result === "string") container.innerHTML = result;
|
|
129
|
+
if (typeof result === "string") container.innerHTML = sanitizeTemplate ? sanitizeTemplate(result, option) : result;
|
|
130
130
|
else container.append(result);
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
@@ -163,12 +163,12 @@ function renderOptionContent(container, option, template, variant = "row") {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// src/remote.ts
|
|
166
|
-
function buildUrl(ajax, query, page) {
|
|
166
|
+
function buildUrl(ajax, query, page, cursor) {
|
|
167
167
|
if (!ajax.url) throw new Error("ForgeSelect: ajax requires either url or request.");
|
|
168
168
|
if (typeof ajax.url === "function") return ajax.url(query, page);
|
|
169
169
|
if (!ajax.params) return ajax.url;
|
|
170
170
|
const params = new URLSearchParams();
|
|
171
|
-
for (const [key, value] of Object.entries(ajax.params(query, page))) params.set(key, String(value));
|
|
171
|
+
for (const [key, value] of Object.entries(ajax.params(query, page, cursor))) params.set(key, String(value));
|
|
172
172
|
const separator = ajax.url.includes("?") ? "&" : "?";
|
|
173
173
|
return `${ajax.url}${separator}${params.toString()}`;
|
|
174
174
|
}
|
|
@@ -180,10 +180,16 @@ function normalizeRemoteResult(ajax, response) {
|
|
|
180
180
|
"ForgeSelect: ajax.transform must return an array of options, or an object shaped like { options: Option[], hasMore?: boolean }."
|
|
181
181
|
);
|
|
182
182
|
}
|
|
183
|
-
|
|
183
|
+
const nextCursor = result.nextCursor == null ? void 0 : String(result.nextCursor);
|
|
184
|
+
return {
|
|
185
|
+
options: result.options,
|
|
186
|
+
hasMore: ajax.pagination ? result.hasMore ?? nextCursor !== void 0 : false,
|
|
187
|
+
nextCursor
|
|
188
|
+
};
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
// src/remote-cache.ts
|
|
192
|
+
var REMOTE_CACHE_LIMIT = 50;
|
|
187
193
|
var RemoteCache = class {
|
|
188
194
|
constructor() {
|
|
189
195
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -198,7 +204,12 @@ var RemoteCache = class {
|
|
|
198
204
|
return entry.value;
|
|
199
205
|
}
|
|
200
206
|
set(key, value, ttl, now = Date.now()) {
|
|
201
|
-
if (ttl
|
|
207
|
+
if (ttl <= 0) return;
|
|
208
|
+
if (this.entries.size >= REMOTE_CACHE_LIMIT && !this.entries.has(key)) {
|
|
209
|
+
const oldest = this.entries.keys().next().value;
|
|
210
|
+
this.entries.delete(oldest);
|
|
211
|
+
}
|
|
212
|
+
this.entries.set(key, { value, expiresAt: now + ttl });
|
|
202
213
|
}
|
|
203
214
|
clear() {
|
|
204
215
|
this.entries.clear();
|
|
@@ -288,21 +299,6 @@ function computeCheckState(option, selected, isDisabled = defaultIsDisabled) {
|
|
|
288
299
|
if (states.every((state) => state === "none")) return "none";
|
|
289
300
|
return "some";
|
|
290
301
|
}
|
|
291
|
-
function findOption(items, value) {
|
|
292
|
-
const search = (options) => {
|
|
293
|
-
for (const option of options) {
|
|
294
|
-
if (option.value === value) return option;
|
|
295
|
-
const found = option.children ? search(option.children) : void 0;
|
|
296
|
-
if (found) return found;
|
|
297
|
-
}
|
|
298
|
-
return void 0;
|
|
299
|
-
};
|
|
300
|
-
for (const item of items) {
|
|
301
|
-
const found = search(isGroup(item) ? item.options : [item]);
|
|
302
|
-
if (found) return found;
|
|
303
|
-
}
|
|
304
|
-
return void 0;
|
|
305
|
-
}
|
|
306
302
|
function syncTreeAncestors(items, selected, isDisabled = defaultIsDisabled) {
|
|
307
303
|
const sync = (option) => {
|
|
308
304
|
if (!option.children?.length) return;
|
|
@@ -332,9 +328,13 @@ var DEFAULT_ITEM_HEIGHT = 36;
|
|
|
332
328
|
var VIRTUAL_BUFFER = 5;
|
|
333
329
|
var VIRTUAL_THRESHOLD = 100;
|
|
334
330
|
var ROW_CACHE_LIMIT = 2e3;
|
|
331
|
+
var PAGE_SIZE = 10;
|
|
332
|
+
var TYPEAHEAD_RESET_MS = 500;
|
|
335
333
|
var uidCounter = 0;
|
|
336
334
|
var ForgeSelect = class {
|
|
337
335
|
constructor(target, options = {}) {
|
|
336
|
+
this.optionByValue = /* @__PURE__ */ new Map();
|
|
337
|
+
this.optionByLabel = /* @__PURE__ */ new Map();
|
|
338
338
|
this.selected = [];
|
|
339
339
|
this.selectedOptions = /* @__PURE__ */ new Map();
|
|
340
340
|
this.suppressNextTagClick = false;
|
|
@@ -349,8 +349,14 @@ var ForgeSelect = class {
|
|
|
349
349
|
this.rows = [];
|
|
350
350
|
this.navItems = [];
|
|
351
351
|
this.highlightedIndex = -1;
|
|
352
|
+
this.typeaheadBuffer = "";
|
|
353
|
+
this.typeaheadTimer = null;
|
|
352
354
|
this.rowContentCache = /* @__PURE__ */ new Map();
|
|
355
|
+
this.rowElementCache = /* @__PURE__ */ new Map();
|
|
353
356
|
this.rowHeightCache = /* @__PURE__ */ new Map();
|
|
357
|
+
this.rowOffsetsCache = null;
|
|
358
|
+
this.scrollRafId = null;
|
|
359
|
+
this.ancestorScrollRafId = null;
|
|
354
360
|
this.searchIndex = new SearchIndex();
|
|
355
361
|
this.expandedValues = /* @__PURE__ */ new Set();
|
|
356
362
|
this.loading = false;
|
|
@@ -362,6 +368,8 @@ var ForgeSelect = class {
|
|
|
362
368
|
this.ajaxController = null;
|
|
363
369
|
this.remoteLoaded = false;
|
|
364
370
|
this.remoteCache = new RemoteCache();
|
|
371
|
+
this.remoteInFlight = /* @__PURE__ */ new Map();
|
|
372
|
+
this.prefetchControllers = /* @__PURE__ */ new Set();
|
|
365
373
|
this.loadError = null;
|
|
366
374
|
this.originalDisplay = "";
|
|
367
375
|
this.originalDisabled = false;
|
|
@@ -379,7 +387,12 @@ var ForgeSelect = class {
|
|
|
379
387
|
this.positionDropdown();
|
|
380
388
|
};
|
|
381
389
|
this.onAncestorScroll = () => {
|
|
382
|
-
if (this.portalHost)
|
|
390
|
+
if (!this.portalHost) return;
|
|
391
|
+
if (this.ancestorScrollRafId != null) return;
|
|
392
|
+
this.ancestorScrollRafId = requestAnimationFrame(() => {
|
|
393
|
+
this.ancestorScrollRafId = null;
|
|
394
|
+
this.positionDropdown();
|
|
395
|
+
});
|
|
383
396
|
};
|
|
384
397
|
this.onNativeInvalid = (event) => {
|
|
385
398
|
event.preventDefault();
|
|
@@ -425,6 +438,13 @@ var ForgeSelect = class {
|
|
|
425
438
|
ajax: options.ajax,
|
|
426
439
|
templateResult: options.templateResult,
|
|
427
440
|
templateSelection: options.templateSelection,
|
|
441
|
+
sanitizeTemplate: options.sanitizeTemplate,
|
|
442
|
+
beforeSelect: options.beforeSelect,
|
|
443
|
+
beforeUnselect: options.beforeUnselect,
|
|
444
|
+
beforeCreate: options.beforeCreate,
|
|
445
|
+
createOption: options.createOption,
|
|
446
|
+
missingSelectionPolicy: options.missingSelectionPolicy ?? "preserve",
|
|
447
|
+
duplicateValuePolicy: options.duplicateValuePolicy ?? "warn",
|
|
428
448
|
filterOption: options.filterOption,
|
|
429
449
|
searchFields: options.searchFields ?? ["label", "description"],
|
|
430
450
|
tokenSearch: options.tokenSearch ?? true,
|
|
@@ -446,6 +466,7 @@ var ForgeSelect = class {
|
|
|
446
466
|
this.plugins = this.opts.plugins;
|
|
447
467
|
if (nativeSelect) nativeSelect.required = this.opts.required;
|
|
448
468
|
this.data = this.opts.data ?? (nativeSelect ? parseNativeOptions(nativeSelect) : []);
|
|
469
|
+
this.rebuildOptionIndexes();
|
|
449
470
|
if (nativeSelect && !this.opts.data) {
|
|
450
471
|
const nativeOptions = Array.from(nativeSelect.options);
|
|
451
472
|
const hasIntentionalSelection = nativeSelect.multiple || nativeSelect.selectedIndex > 0 || nativeOptions.some((option) => option.defaultSelected);
|
|
@@ -483,6 +504,8 @@ var ForgeSelect = class {
|
|
|
483
504
|
this.renderList();
|
|
484
505
|
this.positionDropdown();
|
|
485
506
|
window.addEventListener("resize", this.onWindowResize);
|
|
507
|
+
window.visualViewport?.addEventListener("resize", this.onWindowResize);
|
|
508
|
+
window.visualViewport?.addEventListener("scroll", this.onWindowResize);
|
|
486
509
|
document.addEventListener("scroll", this.onAncestorScroll, true);
|
|
487
510
|
if (this.searchInput && !this.searchInput.hidden) this.searchInput.focus();
|
|
488
511
|
this.emitter.emit("open");
|
|
@@ -497,7 +520,22 @@ var ForgeSelect = class {
|
|
|
497
520
|
this.control.setAttribute("aria-expanded", "false");
|
|
498
521
|
document.removeEventListener("mousedown", this.onDocumentMouseDown);
|
|
499
522
|
window.removeEventListener("resize", this.onWindowResize);
|
|
523
|
+
window.visualViewport?.removeEventListener("resize", this.onWindowResize);
|
|
524
|
+
window.visualViewport?.removeEventListener("scroll", this.onWindowResize);
|
|
500
525
|
document.removeEventListener("scroll", this.onAncestorScroll, true);
|
|
526
|
+
if (this.ancestorScrollRafId != null) {
|
|
527
|
+
cancelAnimationFrame(this.ancestorScrollRafId);
|
|
528
|
+
this.ancestorScrollRafId = null;
|
|
529
|
+
}
|
|
530
|
+
if (this.scrollRafId != null) {
|
|
531
|
+
cancelAnimationFrame(this.scrollRafId);
|
|
532
|
+
this.scrollRafId = null;
|
|
533
|
+
}
|
|
534
|
+
if (this.typeaheadTimer) {
|
|
535
|
+
clearTimeout(this.typeaheadTimer);
|
|
536
|
+
this.typeaheadTimer = null;
|
|
537
|
+
}
|
|
538
|
+
this.typeaheadBuffer = "";
|
|
501
539
|
this.highlightedIndex = -1;
|
|
502
540
|
if (this.searchInput) {
|
|
503
541
|
this.searchInput.value = "";
|
|
@@ -515,11 +553,16 @@ var ForgeSelect = class {
|
|
|
515
553
|
*/
|
|
516
554
|
positionDropdown() {
|
|
517
555
|
const controlRect = this.control.getBoundingClientRect();
|
|
518
|
-
const
|
|
556
|
+
const viewport = window.visualViewport;
|
|
557
|
+
const placement = computeDropdownPlacement(
|
|
558
|
+
controlRect,
|
|
559
|
+
this.dropdown.offsetHeight,
|
|
560
|
+
viewport?.height ?? window.innerHeight
|
|
561
|
+
);
|
|
519
562
|
this.root.classList.toggle("forge-select--drop-up", placement.dropUp);
|
|
520
563
|
if (this.portalHost) {
|
|
521
|
-
this.portalHost.style.top = `${placement.top}px`;
|
|
522
|
-
this.portalHost.style.left = `${controlRect.left}px`;
|
|
564
|
+
this.portalHost.style.top = `${placement.top + (viewport?.offsetTop ?? 0)}px`;
|
|
565
|
+
this.portalHost.style.left = `${controlRect.left + (viewport?.offsetLeft ?? 0)}px`;
|
|
523
566
|
this.portalHost.style.width = `${controlRect.width}px`;
|
|
524
567
|
}
|
|
525
568
|
}
|
|
@@ -530,10 +573,16 @@ var ForgeSelect = class {
|
|
|
530
573
|
this.destroyed = true;
|
|
531
574
|
if (this.ajaxTimer) clearTimeout(this.ajaxTimer);
|
|
532
575
|
this.ajaxController?.abort();
|
|
576
|
+
for (const controller of this.prefetchControllers) controller.abort();
|
|
577
|
+
this.prefetchControllers.clear();
|
|
578
|
+
this.remoteInFlight.clear();
|
|
579
|
+
if (this.scrollRafId != null) cancelAnimationFrame(this.scrollRafId);
|
|
580
|
+
if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer);
|
|
533
581
|
this.nativeSelect?.removeEventListener("change", this.onNativeChange);
|
|
534
582
|
this.nativeSelect?.removeEventListener("invalid", this.onNativeInvalid);
|
|
535
583
|
this.nativeForm?.removeEventListener("reset", this.onFormReset);
|
|
536
584
|
this.rowContentCache.clear();
|
|
585
|
+
this.rowElementCache.clear();
|
|
537
586
|
this.rowHeightCache.clear();
|
|
538
587
|
this.searchIndex.clear();
|
|
539
588
|
this.portalHost?.remove();
|
|
@@ -582,10 +631,23 @@ var ForgeSelect = class {
|
|
|
582
631
|
}
|
|
583
632
|
if (options.templateResult !== void 0) this.opts.templateResult = options.templateResult;
|
|
584
633
|
if (options.templateSelection !== void 0) this.opts.templateSelection = options.templateSelection;
|
|
634
|
+
if (options.sanitizeTemplate !== void 0) this.opts.sanitizeTemplate = options.sanitizeTemplate;
|
|
635
|
+
if (options.beforeSelect !== void 0) this.opts.beforeSelect = options.beforeSelect;
|
|
636
|
+
if (options.beforeUnselect !== void 0) this.opts.beforeUnselect = options.beforeUnselect;
|
|
637
|
+
if (options.beforeCreate !== void 0) this.opts.beforeCreate = options.beforeCreate;
|
|
638
|
+
if (options.createOption !== void 0) this.opts.createOption = options.createOption;
|
|
639
|
+
if (options.missingSelectionPolicy !== void 0) this.opts.missingSelectionPolicy = options.missingSelectionPolicy;
|
|
640
|
+
if (options.duplicateValuePolicy !== void 0) {
|
|
641
|
+
this.opts.duplicateValuePolicy = options.duplicateValuePolicy;
|
|
642
|
+
this.rebuildOptionIndexes();
|
|
643
|
+
}
|
|
585
644
|
if (options.filterOption !== void 0) this.opts.filterOption = options.filterOption;
|
|
586
645
|
if (options.searchFields !== void 0) this.opts.searchFields = options.searchFields;
|
|
587
646
|
if (options.tokenSearch !== void 0) this.opts.tokenSearch = options.tokenSearch;
|
|
588
|
-
if (options.accentInsensitive !== void 0)
|
|
647
|
+
if (options.accentInsensitive !== void 0) {
|
|
648
|
+
this.opts.accentInsensitive = options.accentInsensitive;
|
|
649
|
+
this.rebuildOptionIndexes();
|
|
650
|
+
}
|
|
589
651
|
if (options.searchScorer !== void 0) this.opts.searchScorer = options.searchScorer;
|
|
590
652
|
if (options.highlightSearch !== void 0) this.opts.highlightSearch = options.highlightSearch;
|
|
591
653
|
if (options.minSearchLength !== void 0)
|
|
@@ -614,6 +676,8 @@ var ForgeSelect = class {
|
|
|
614
676
|
this.root.classList.toggle("forge-select--sortable", this.opts.sortable && this.opts.multiple);
|
|
615
677
|
this.updateSearchVisibility();
|
|
616
678
|
this.rowContentCache.clear();
|
|
679
|
+
this.rowElementCache.clear();
|
|
680
|
+
this.rowHeightCache.clear();
|
|
617
681
|
this.searchIndex.clear();
|
|
618
682
|
this.renderValue();
|
|
619
683
|
if (this.isOpen) this.renderList();
|
|
@@ -645,6 +709,7 @@ var ForgeSelect = class {
|
|
|
645
709
|
}
|
|
646
710
|
clearRemoteCache() {
|
|
647
711
|
this.remoteCache.clear();
|
|
712
|
+
this.remoteInFlight.clear();
|
|
648
713
|
}
|
|
649
714
|
setValue(value, options = {}) {
|
|
650
715
|
const values = value == null ? [] : Array.isArray(value) ? value : [value];
|
|
@@ -674,10 +739,31 @@ var ForgeSelect = class {
|
|
|
674
739
|
this.remoteLoaded = true;
|
|
675
740
|
this.page = 0;
|
|
676
741
|
this.hasMore = false;
|
|
742
|
+
this.nextCursor = void 0;
|
|
743
|
+
const previousData = this.data;
|
|
677
744
|
this.data = data;
|
|
745
|
+
try {
|
|
746
|
+
this.rebuildOptionIndexes();
|
|
747
|
+
} catch (error) {
|
|
748
|
+
this.data = previousData;
|
|
749
|
+
this.rebuildOptionIndexes();
|
|
750
|
+
throw error;
|
|
751
|
+
}
|
|
752
|
+
const missing = this.selected.filter((value) => !this.optionByValue.has(value));
|
|
753
|
+
if (missing.length > 0 && this.opts.missingSelectionPolicy === "error") {
|
|
754
|
+
this.data = previousData;
|
|
755
|
+
this.rebuildOptionIndexes();
|
|
756
|
+
throw new Error(`ForgeSelect: setData() is missing selected value(s): ${missing.join(", ")}`);
|
|
757
|
+
}
|
|
678
758
|
this.opts.data = data;
|
|
759
|
+
if (missing.length > 0 && this.opts.missingSelectionPolicy === "prune") {
|
|
760
|
+
this.selected = this.selected.filter((value) => this.optionByValue.has(value));
|
|
761
|
+
this.afterSelectionChange();
|
|
762
|
+
}
|
|
679
763
|
this.updateSearchVisibility();
|
|
680
764
|
this.rowContentCache.clear();
|
|
765
|
+
this.rowElementCache.clear();
|
|
766
|
+
this.rowHeightCache.clear();
|
|
681
767
|
this.searchIndex.clear();
|
|
682
768
|
this.highlightedIndex = -1;
|
|
683
769
|
if (this.isOpen) this.renderList();
|
|
@@ -848,10 +934,24 @@ var ForgeSelect = class {
|
|
|
848
934
|
});
|
|
849
935
|
this.clearBtn.addEventListener("click", (event) => {
|
|
850
936
|
event.stopPropagation();
|
|
937
|
+
if (this.selected.some((value) => {
|
|
938
|
+
const option = this.findOption(value) ?? this.selectedOptions.get(value) ?? { value, label: value };
|
|
939
|
+
return this.opts.beforeUnselect?.(option) === false;
|
|
940
|
+
}))
|
|
941
|
+
return;
|
|
851
942
|
this.clearSelection();
|
|
852
943
|
});
|
|
853
944
|
if (this.searchInput) {
|
|
945
|
+
let composing = false;
|
|
946
|
+
this.searchInput.addEventListener("compositionstart", () => {
|
|
947
|
+
composing = true;
|
|
948
|
+
});
|
|
949
|
+
this.searchInput.addEventListener("compositionend", () => {
|
|
950
|
+
composing = false;
|
|
951
|
+
this.applySearchQuery(this.searchInput.value, true);
|
|
952
|
+
});
|
|
854
953
|
this.searchInput.addEventListener("input", () => {
|
|
954
|
+
if (composing) return;
|
|
855
955
|
this.applySearchQuery(this.searchInput.value, true);
|
|
856
956
|
});
|
|
857
957
|
this.searchInput.addEventListener("keydown", (event) => this.handleKeydown(event));
|
|
@@ -861,21 +961,22 @@ var ForgeSelect = class {
|
|
|
861
961
|
const labels = text.split(/[,\n]+/).map((s) => s.trim()).filter(Boolean);
|
|
862
962
|
if (labels.length < 2) return;
|
|
863
963
|
event.preventDefault();
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
if (
|
|
875
|
-
this.
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
|
|
964
|
+
void Promise.all(labels.map((label) => this.createTag(label))).then((results) => {
|
|
965
|
+
const created = results.filter((result) => result !== void 0);
|
|
966
|
+
if (created.length === 0 || this.destroyed) return;
|
|
967
|
+
this.searchInput.value = "";
|
|
968
|
+
this.query = "";
|
|
969
|
+
this.afterSelectionChange();
|
|
970
|
+
for (const result of created) {
|
|
971
|
+
if (result.created) this.emitter.emit("create", result.option);
|
|
972
|
+
this.emitter.emit("select", result.option);
|
|
973
|
+
}
|
|
974
|
+
if (this.opts.closeOnSelect) this.close();
|
|
975
|
+
else this.renderList();
|
|
976
|
+
}).catch((cause) => {
|
|
977
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
978
|
+
this.emitter.emit("error", error);
|
|
979
|
+
});
|
|
879
980
|
});
|
|
880
981
|
}
|
|
881
982
|
this.list.addEventListener("click", (event) => {
|
|
@@ -899,8 +1000,12 @@ var ForgeSelect = class {
|
|
|
899
1000
|
this.activateNavItem(navIndex);
|
|
900
1001
|
});
|
|
901
1002
|
this.list.addEventListener("scroll", () => {
|
|
902
|
-
if (this.
|
|
903
|
-
this.
|
|
1003
|
+
if (this.scrollRafId != null) return;
|
|
1004
|
+
this.scrollRafId = requestAnimationFrame(() => {
|
|
1005
|
+
this.scrollRafId = null;
|
|
1006
|
+
if (this.usesVirtualScroll()) this.renderRows();
|
|
1007
|
+
this.maybeLoadNextPage();
|
|
1008
|
+
});
|
|
904
1009
|
});
|
|
905
1010
|
}
|
|
906
1011
|
applySearchQuery(query, emitSearch) {
|
|
@@ -908,7 +1013,6 @@ var ForgeSelect = class {
|
|
|
908
1013
|
if (this.searchInput && this.searchInput.value !== query) this.searchInput.value = query;
|
|
909
1014
|
this.highlightedIndex = -1;
|
|
910
1015
|
this.list.scrollTop = 0;
|
|
911
|
-
this.rowContentCache.clear();
|
|
912
1016
|
if (emitSearch) this.emitter.emit("search", query);
|
|
913
1017
|
const trimmed = query.trim();
|
|
914
1018
|
const belowMinLength = trimmed !== "" && trimmed.length < this.opts.minSearchLength;
|
|
@@ -927,7 +1031,7 @@ var ForgeSelect = class {
|
|
|
927
1031
|
this.renderList();
|
|
928
1032
|
}
|
|
929
1033
|
handleKeydown(event) {
|
|
930
|
-
if (this.isDisabled) return;
|
|
1034
|
+
if (this.isDisabled || event.isComposing || event.keyCode === 229) return;
|
|
931
1035
|
switch (event.key) {
|
|
932
1036
|
case "Enter":
|
|
933
1037
|
event.preventDefault();
|
|
@@ -962,9 +1066,63 @@ var ForgeSelect = class {
|
|
|
962
1066
|
case "ArrowLeft":
|
|
963
1067
|
if (this.isOpen && this.navigateTree("left")) event.preventDefault();
|
|
964
1068
|
break;
|
|
1069
|
+
case "Home":
|
|
1070
|
+
if (this.isOpen) {
|
|
1071
|
+
event.preventDefault();
|
|
1072
|
+
this.focusNavIndex(0);
|
|
1073
|
+
}
|
|
1074
|
+
break;
|
|
1075
|
+
case "End":
|
|
1076
|
+
if (this.isOpen) {
|
|
1077
|
+
event.preventDefault();
|
|
1078
|
+
this.focusNavIndex(this.navItems.length - 1);
|
|
1079
|
+
}
|
|
1080
|
+
break;
|
|
1081
|
+
case "PageDown":
|
|
1082
|
+
if (this.isOpen) {
|
|
1083
|
+
event.preventDefault();
|
|
1084
|
+
this.focusNavIndex(
|
|
1085
|
+
Math.min(this.navItems.length - 1, (this.highlightedIndex === -1 ? 0 : this.highlightedIndex) + PAGE_SIZE)
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
break;
|
|
1089
|
+
case "PageUp":
|
|
1090
|
+
if (this.isOpen) {
|
|
1091
|
+
event.preventDefault();
|
|
1092
|
+
this.focusNavIndex(Math.max(0, (this.highlightedIndex === -1 ? 0 : this.highlightedIndex) - PAGE_SIZE));
|
|
1093
|
+
}
|
|
1094
|
+
break;
|
|
965
1095
|
case "Tab":
|
|
966
1096
|
this.close();
|
|
967
1097
|
break;
|
|
1098
|
+
default:
|
|
1099
|
+
if (this.isOpen && event.target === this.control && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
1100
|
+
this.handleTypeahead(event.key);
|
|
1101
|
+
}
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Jumps the highlight to the next nav item (wrapping) whose label starts
|
|
1107
|
+
* with the accumulated buffer, matching native <select> typeahead: rapid
|
|
1108
|
+
* distinct keystrokes narrow the prefix, a pause resets it.
|
|
1109
|
+
*/
|
|
1110
|
+
handleTypeahead(char) {
|
|
1111
|
+
if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer);
|
|
1112
|
+
this.typeaheadBuffer += normalizeSearchText(char, this.opts.accentInsensitive);
|
|
1113
|
+
this.typeaheadTimer = setTimeout(() => {
|
|
1114
|
+
this.typeaheadBuffer = "";
|
|
1115
|
+
this.typeaheadTimer = null;
|
|
1116
|
+
}, TYPEAHEAD_RESET_MS);
|
|
1117
|
+
const prefix = [...this.typeaheadBuffer].every((value) => value === this.typeaheadBuffer[0]) ? this.typeaheadBuffer[0] : this.typeaheadBuffer;
|
|
1118
|
+
const count = this.navItems.length;
|
|
1119
|
+
for (let step = 1; step <= count; step += 1) {
|
|
1120
|
+
const index = (this.highlightedIndex + step + count) % count;
|
|
1121
|
+
const item = this.navItems[index];
|
|
1122
|
+
if (item.kind === "option" && normalizeSearchText(item.option.label, this.opts.accentInsensitive).startsWith(prefix)) {
|
|
1123
|
+
this.focusNavIndex(index);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
968
1126
|
}
|
|
969
1127
|
}
|
|
970
1128
|
// ---------------------------------------------------------------- selection
|
|
@@ -1055,7 +1213,10 @@ var ForgeSelect = class {
|
|
|
1055
1213
|
this.control.classList.remove("forge-select__control--invalid");
|
|
1056
1214
|
this.control.removeAttribute("aria-invalid");
|
|
1057
1215
|
}
|
|
1058
|
-
if (this.isOpen)
|
|
1216
|
+
if (this.isOpen) {
|
|
1217
|
+
if (this.opts.maxSelections != null) this.renderList();
|
|
1218
|
+
else this.renderRows();
|
|
1219
|
+
}
|
|
1059
1220
|
if (emitChange) this.emitter.emit("change", this.getValue());
|
|
1060
1221
|
}
|
|
1061
1222
|
syncNativeSelect(dispatchChange = true) {
|
|
@@ -1088,23 +1249,27 @@ var ForgeSelect = class {
|
|
|
1088
1249
|
}
|
|
1089
1250
|
}
|
|
1090
1251
|
findOption(value) {
|
|
1091
|
-
return
|
|
1252
|
+
return this.optionByValue.get(value);
|
|
1092
1253
|
}
|
|
1093
1254
|
findOptionByLabel(label) {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1255
|
+
return this.optionByLabel.get(normalizeSearchText(label, this.opts.accentInsensitive));
|
|
1256
|
+
}
|
|
1257
|
+
rebuildOptionIndexes() {
|
|
1258
|
+
this.optionByValue.clear();
|
|
1259
|
+
this.optionByLabel.clear();
|
|
1260
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
1261
|
+
const visit = (option) => {
|
|
1262
|
+
if (this.optionByValue.has(option.value)) duplicates.add(option.value);
|
|
1263
|
+
else this.optionByValue.set(option.value, option);
|
|
1264
|
+
const label = normalizeSearchText(option.label, this.opts.accentInsensitive);
|
|
1265
|
+
if (!this.optionByLabel.has(label)) this.optionByLabel.set(label, option);
|
|
1266
|
+
option.children?.forEach(visit);
|
|
1102
1267
|
};
|
|
1103
|
-
for (const item of this.data)
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1268
|
+
for (const item of this.data) (isGroup(item) ? item.options : [item]).forEach(visit);
|
|
1269
|
+
if (duplicates.size === 0 || this.opts.duplicateValuePolicy === "ignore") return;
|
|
1270
|
+
const message = `ForgeSelect: duplicate option value(s): ${[...duplicates].join(", ")}`;
|
|
1271
|
+
if (this.opts.duplicateValuePolicy === "error") throw new Error(message);
|
|
1272
|
+
console.warn(message);
|
|
1108
1273
|
}
|
|
1109
1274
|
/** Selects an existing option matching `label` exactly, or creates and selects a new one. */
|
|
1110
1275
|
createTag(label) {
|
|
@@ -1120,12 +1285,26 @@ var ForgeSelect = class {
|
|
|
1120
1285
|
this.selectValue(existing.value, false);
|
|
1121
1286
|
return { option: existing, created: false };
|
|
1122
1287
|
}
|
|
1123
|
-
|
|
1288
|
+
if (this.opts.beforeCreate?.(trimmed) === false) return void 0;
|
|
1289
|
+
const created = this.opts.createOption?.(trimmed) ?? { value: trimmed, label: trimmed };
|
|
1290
|
+
if (created instanceof Promise) {
|
|
1291
|
+
return created.then((option) => option ? this.addCreatedOption(option) : void 0);
|
|
1292
|
+
}
|
|
1293
|
+
return created ? this.addCreatedOption(created) : void 0;
|
|
1294
|
+
}
|
|
1295
|
+
addCreatedOption(option) {
|
|
1124
1296
|
if (this.opts.multiple && !this.canSelectOption(option)) {
|
|
1125
1297
|
this.announceMaximum(option);
|
|
1126
1298
|
return void 0;
|
|
1127
1299
|
}
|
|
1300
|
+
const duplicate = this.findOption(option.value);
|
|
1301
|
+
if (duplicate) {
|
|
1302
|
+
if (this.selected.includes(duplicate.value)) return void 0;
|
|
1303
|
+
this.selectValue(duplicate.value, false);
|
|
1304
|
+
return { option: duplicate, created: false };
|
|
1305
|
+
}
|
|
1128
1306
|
this.data.push(option);
|
|
1307
|
+
this.rebuildOptionIndexes();
|
|
1129
1308
|
this.selectValue(option.value, false);
|
|
1130
1309
|
return { option, created: true };
|
|
1131
1310
|
}
|
|
@@ -1133,8 +1312,18 @@ var ForgeSelect = class {
|
|
|
1133
1312
|
const label = this.query.trim();
|
|
1134
1313
|
if (!label) return;
|
|
1135
1314
|
const result = this.createTag(label);
|
|
1136
|
-
if (
|
|
1137
|
-
|
|
1315
|
+
if (result instanceof Promise) {
|
|
1316
|
+
void result.then((created) => this.finishCreateFromQuery(created, label)).catch((cause) => {
|
|
1317
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
1318
|
+
this.emitter.emit("error", error);
|
|
1319
|
+
});
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
this.finishCreateFromQuery(result, label);
|
|
1323
|
+
}
|
|
1324
|
+
finishCreateFromQuery(result, sourceLabel) {
|
|
1325
|
+
if (!result || this.destroyed) return;
|
|
1326
|
+
if (this.searchInput && this.query.trim() === sourceLabel) {
|
|
1138
1327
|
this.searchInput.value = "";
|
|
1139
1328
|
this.query = "";
|
|
1140
1329
|
}
|
|
@@ -1142,6 +1331,7 @@ var ForgeSelect = class {
|
|
|
1142
1331
|
if (result.created) this.emitter.emit("create", result.option);
|
|
1143
1332
|
this.emitter.emit("select", result.option);
|
|
1144
1333
|
if (!this.opts.multiple || this.opts.closeOnSelect) this.close();
|
|
1334
|
+
else if (this.isOpen) this.renderList();
|
|
1145
1335
|
}
|
|
1146
1336
|
activateNavItem(navIndex) {
|
|
1147
1337
|
const item = this.navItems[navIndex];
|
|
@@ -1154,9 +1344,11 @@ var ForgeSelect = class {
|
|
|
1154
1344
|
if (this.opts.multiple) {
|
|
1155
1345
|
let changed = false;
|
|
1156
1346
|
if (this.selected.includes(value)) {
|
|
1347
|
+
if (this.opts.beforeUnselect?.(item.option) === false) return;
|
|
1157
1348
|
this.deselectValue(value, true);
|
|
1158
1349
|
changed = true;
|
|
1159
1350
|
} else if (this.canSelectOption(item.option)) {
|
|
1351
|
+
if (this.opts.beforeSelect?.(item.option) === false) return;
|
|
1160
1352
|
this.selectValue(value, true);
|
|
1161
1353
|
changed = true;
|
|
1162
1354
|
} else {
|
|
@@ -1164,6 +1356,7 @@ var ForgeSelect = class {
|
|
|
1164
1356
|
}
|
|
1165
1357
|
if (changed && this.opts.closeOnSelect) this.close();
|
|
1166
1358
|
} else {
|
|
1359
|
+
if (this.opts.beforeSelect?.(item.option) === false) return;
|
|
1167
1360
|
this.selectValue(value, true);
|
|
1168
1361
|
this.close();
|
|
1169
1362
|
this.control.focus();
|
|
@@ -1188,7 +1381,7 @@ var ForgeSelect = class {
|
|
|
1188
1381
|
tag.className = "forge-select__tag";
|
|
1189
1382
|
const label = document.createElement("span");
|
|
1190
1383
|
label.className = "forge-select__tag-label";
|
|
1191
|
-
renderOptionContent(label, option, this.opts.templateSelection, "inline");
|
|
1384
|
+
renderOptionContent(label, option, this.opts.templateSelection, "inline", this.opts.sanitizeTemplate);
|
|
1192
1385
|
const remove = document.createElement("button");
|
|
1193
1386
|
remove.type = "button";
|
|
1194
1387
|
remove.className = "forge-select__tag-remove";
|
|
@@ -1196,7 +1389,7 @@ var ForgeSelect = class {
|
|
|
1196
1389
|
remove.textContent = "\xD7";
|
|
1197
1390
|
remove.addEventListener("click", (event) => {
|
|
1198
1391
|
event.stopPropagation();
|
|
1199
|
-
if (!this.isDisabled) this.deselectValue(value, true);
|
|
1392
|
+
if (!this.isDisabled && this.opts.beforeUnselect?.(option) !== false) this.deselectValue(value, true);
|
|
1200
1393
|
});
|
|
1201
1394
|
tag.append(label, remove);
|
|
1202
1395
|
if (this.opts.sortable) {
|
|
@@ -1216,7 +1409,7 @@ var ForgeSelect = class {
|
|
|
1216
1409
|
};
|
|
1217
1410
|
const span = document.createElement("span");
|
|
1218
1411
|
span.className = "forge-select__single-value";
|
|
1219
|
-
renderOptionContent(span, option, this.opts.templateSelection, "inline");
|
|
1412
|
+
renderOptionContent(span, option, this.opts.templateSelection, "inline", this.opts.sanitizeTemplate);
|
|
1220
1413
|
this.valueEl.append(span);
|
|
1221
1414
|
}
|
|
1222
1415
|
}
|
|
@@ -1316,6 +1509,7 @@ var ForgeSelect = class {
|
|
|
1316
1509
|
buildRows() {
|
|
1317
1510
|
this.rows = [];
|
|
1318
1511
|
this.navItems = [];
|
|
1512
|
+
this.rowOffsetsCache = null;
|
|
1319
1513
|
const trimmedQuery = this.query.trim();
|
|
1320
1514
|
const query = normalizeSearchText(trimmedQuery, this.opts.accentInsensitive);
|
|
1321
1515
|
const matches = (option) => query === "" || (this.opts.filterOption ? this.opts.filterOption(option, trimmedQuery) : this.searchIndex.score(option, trimmedQuery, {
|
|
@@ -1324,7 +1518,14 @@ var ForgeSelect = class {
|
|
|
1324
1518
|
accentInsensitive: this.opts.accentInsensitive,
|
|
1325
1519
|
scorer: this.opts.searchScorer
|
|
1326
1520
|
}) > 0);
|
|
1327
|
-
const
|
|
1521
|
+
const subtreeMatchCache = /* @__PURE__ */ new Map();
|
|
1522
|
+
const subtreeMatches = (option) => {
|
|
1523
|
+
const cached = subtreeMatchCache.get(option);
|
|
1524
|
+
if (cached !== void 0) return cached;
|
|
1525
|
+
const result = query === "" || matches(option) || (option.children ?? []).some(subtreeMatches);
|
|
1526
|
+
subtreeMatchCache.set(option, result);
|
|
1527
|
+
return result;
|
|
1528
|
+
};
|
|
1328
1529
|
const pushOption = (option, depth, parentValue) => {
|
|
1329
1530
|
let navIndex = -1;
|
|
1330
1531
|
const interactionDisabled = this.isOptionDisabled(option) || this.hasReachedMaximum() && !this.selected.includes(option.value);
|
|
@@ -1380,7 +1581,7 @@ var ForgeSelect = class {
|
|
|
1380
1581
|
return this.opts.virtualScroll !== false && this.rows.length > VIRTUAL_THRESHOLD;
|
|
1381
1582
|
}
|
|
1382
1583
|
rowKey(row, index) {
|
|
1383
|
-
if (row.kind === "option") return `option:${row.option.value}`;
|
|
1584
|
+
if (row.kind === "option") return `option:${row.option.value}:${index}`;
|
|
1384
1585
|
if (row.kind === "group") return `group:${row.label}:${index}`;
|
|
1385
1586
|
return `${row.kind}:${index}`;
|
|
1386
1587
|
}
|
|
@@ -1394,8 +1595,10 @@ var ForgeSelect = class {
|
|
|
1394
1595
|
return offset;
|
|
1395
1596
|
}
|
|
1396
1597
|
rowOffsets() {
|
|
1598
|
+
if (this.rowOffsetsCache) return this.rowOffsetsCache;
|
|
1397
1599
|
const offsets = [0];
|
|
1398
1600
|
for (let i = 0; i < this.rows.length; i += 1) offsets.push(offsets[i] + this.measuredRowHeight(i));
|
|
1601
|
+
this.rowOffsetsCache = offsets;
|
|
1399
1602
|
return offsets;
|
|
1400
1603
|
}
|
|
1401
1604
|
renderList() {
|
|
@@ -1420,7 +1623,14 @@ var ForgeSelect = class {
|
|
|
1420
1623
|
if (virtual) {
|
|
1421
1624
|
const viewport = clientHeight || rowHeight * 8;
|
|
1422
1625
|
if (this.opts.variableItemHeight) {
|
|
1423
|
-
|
|
1626
|
+
let low = 0;
|
|
1627
|
+
let high = this.rows.length;
|
|
1628
|
+
while (low < high) {
|
|
1629
|
+
const middle = low + high >>> 1;
|
|
1630
|
+
if (offsets[middle + 1] < scrollTop) low = middle + 1;
|
|
1631
|
+
else high = middle;
|
|
1632
|
+
}
|
|
1633
|
+
start = low;
|
|
1424
1634
|
start = Math.max(0, start - VIRTUAL_BUFFER);
|
|
1425
1635
|
end = start;
|
|
1426
1636
|
const target = scrollTop + viewport + VIRTUAL_BUFFER * rowHeight;
|
|
@@ -1435,12 +1645,27 @@ var ForgeSelect = class {
|
|
|
1435
1645
|
topSpacer.style.height = `${offsets?.[start] ?? this.rowOffset(start)}px`;
|
|
1436
1646
|
this.list.append(topSpacer);
|
|
1437
1647
|
}
|
|
1648
|
+
const appended = [];
|
|
1438
1649
|
for (let i = start; i < end; i++) {
|
|
1439
|
-
const
|
|
1650
|
+
const key = this.rowKey(this.rows[i], i);
|
|
1651
|
+
const element = this.renderRow(this.rows[i], this.rowElementCache.get(key));
|
|
1652
|
+
this.rowElementCache.set(key, element);
|
|
1653
|
+
if (this.rowElementCache.size > ROW_CACHE_LIMIT) {
|
|
1654
|
+
const oldest = this.rowElementCache.keys().next().value;
|
|
1655
|
+
this.rowElementCache.delete(oldest);
|
|
1656
|
+
}
|
|
1440
1657
|
this.list.append(element);
|
|
1441
|
-
|
|
1658
|
+
appended.push(element);
|
|
1659
|
+
}
|
|
1660
|
+
if (this.opts.variableItemHeight) {
|
|
1661
|
+
for (let i = start; i < end; i++) {
|
|
1662
|
+
const element = appended[i - start];
|
|
1442
1663
|
const measured = element.getBoundingClientRect().height || element.offsetHeight;
|
|
1443
|
-
if (measured > 0)
|
|
1664
|
+
if (measured > 0) {
|
|
1665
|
+
const key = this.rowKey(this.rows[i], i);
|
|
1666
|
+
if (this.rowHeightCache.get(key) !== measured) this.rowOffsetsCache = null;
|
|
1667
|
+
this.rowHeightCache.set(key, measured);
|
|
1668
|
+
}
|
|
1444
1669
|
}
|
|
1445
1670
|
}
|
|
1446
1671
|
if (virtual) {
|
|
@@ -1455,8 +1680,22 @@ var ForgeSelect = class {
|
|
|
1455
1680
|
}
|
|
1456
1681
|
this.updateActiveDescendant();
|
|
1457
1682
|
}
|
|
1458
|
-
renderRow(row) {
|
|
1459
|
-
const li = document.createElement("li");
|
|
1683
|
+
renderRow(row, recycled) {
|
|
1684
|
+
const li = recycled ?? document.createElement("li");
|
|
1685
|
+
li.replaceChildren();
|
|
1686
|
+
li.className = "";
|
|
1687
|
+
for (const attribute of [
|
|
1688
|
+
"role",
|
|
1689
|
+
"id",
|
|
1690
|
+
"aria-hidden",
|
|
1691
|
+
"aria-selected",
|
|
1692
|
+
"aria-disabled",
|
|
1693
|
+
"aria-expanded",
|
|
1694
|
+
"aria-level",
|
|
1695
|
+
"data-nav-index",
|
|
1696
|
+
"data-option-value"
|
|
1697
|
+
])
|
|
1698
|
+
li.removeAttribute(attribute);
|
|
1460
1699
|
switch (row.kind) {
|
|
1461
1700
|
case "group":
|
|
1462
1701
|
li.className = "forge-select__group-label";
|
|
@@ -1576,7 +1815,7 @@ var ForgeSelect = class {
|
|
|
1576
1815
|
if (!cached) {
|
|
1577
1816
|
const holder = document.createElement("span");
|
|
1578
1817
|
holder.className = "forge-select__option-content";
|
|
1579
|
-
renderOptionContent(holder, option, this.opts.templateResult);
|
|
1818
|
+
renderOptionContent(holder, option, this.opts.templateResult, "row", this.opts.sanitizeTemplate);
|
|
1580
1819
|
if (this.rowContentCache.size >= ROW_CACHE_LIMIT) {
|
|
1581
1820
|
const oldest = this.rowContentCache.keys().next().value;
|
|
1582
1821
|
this.rowContentCache.delete(oldest);
|
|
@@ -1592,6 +1831,7 @@ var ForgeSelect = class {
|
|
|
1592
1831
|
this.focusNavIndex(next);
|
|
1593
1832
|
}
|
|
1594
1833
|
focusNavIndex(next) {
|
|
1834
|
+
if (this.navItems.length === 0) return;
|
|
1595
1835
|
this.highlightedIndex = next;
|
|
1596
1836
|
if (this.usesVirtualScroll()) {
|
|
1597
1837
|
const rowIndex = this.rows.findIndex(
|
|
@@ -1664,6 +1904,7 @@ var ForgeSelect = class {
|
|
|
1664
1904
|
this.ajaxController = null;
|
|
1665
1905
|
this.page = 0;
|
|
1666
1906
|
this.hasMore = true;
|
|
1907
|
+
this.nextCursor = void 0;
|
|
1667
1908
|
this.setLoading(true);
|
|
1668
1909
|
this.loadingMore = false;
|
|
1669
1910
|
this.loadError = null;
|
|
@@ -1678,17 +1919,27 @@ var ForgeSelect = class {
|
|
|
1678
1919
|
this.loading = loading;
|
|
1679
1920
|
this.emitter.emit("loading", loading);
|
|
1680
1921
|
}
|
|
1681
|
-
remoteCacheKey(query, page) {
|
|
1682
|
-
return `${query}\0${page}`;
|
|
1922
|
+
remoteCacheKey(query, page, cursor) {
|
|
1923
|
+
return `${query}\0${cursor ?? page}`;
|
|
1683
1924
|
}
|
|
1684
|
-
|
|
1925
|
+
fetchRemoteResult(query, page, signal, cursor) {
|
|
1926
|
+
const key = this.remoteCacheKey(query, page, cursor);
|
|
1927
|
+
const pending = this.remoteInFlight.get(key);
|
|
1928
|
+
if (pending) return pending;
|
|
1929
|
+
const ajax = this.opts.ajax;
|
|
1930
|
+
const request = this.requestRemote(query, page, signal, cursor).then((json) => normalizeRemoteResult(ajax, json)).finally(() => this.remoteInFlight.delete(key));
|
|
1931
|
+
this.remoteInFlight.set(key, request);
|
|
1932
|
+
return request;
|
|
1933
|
+
}
|
|
1934
|
+
async requestRemote(query, page, signal, cursor) {
|
|
1685
1935
|
const ajax = this.opts.ajax;
|
|
1686
1936
|
const attempts = Math.max(0, Math.floor(ajax.retry ?? 0)) + 1;
|
|
1687
1937
|
let lastError;
|
|
1688
1938
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1689
1939
|
try {
|
|
1690
|
-
if (ajax.request)
|
|
1691
|
-
|
|
1940
|
+
if (ajax.request)
|
|
1941
|
+
return cursor === void 0 ? await ajax.request(query, page, signal) : await ajax.request(query, page, signal, cursor);
|
|
1942
|
+
const response = await fetch(buildUrl(ajax, query, page, cursor), { signal });
|
|
1692
1943
|
if (response.ok === false) throw new Error(`ForgeSelect: remote request failed with HTTP ${response.status}`);
|
|
1693
1944
|
return await response.json();
|
|
1694
1945
|
} catch (error) {
|
|
@@ -1716,10 +1967,13 @@ var ForgeSelect = class {
|
|
|
1716
1967
|
const key = this.remoteCacheKey(query, 0);
|
|
1717
1968
|
if (this.remoteCache.get(key)) return;
|
|
1718
1969
|
const controller = new AbortController();
|
|
1970
|
+
this.prefetchControllers.add(controller);
|
|
1719
1971
|
try {
|
|
1720
|
-
const
|
|
1721
|
-
this.remoteCache.set(key,
|
|
1972
|
+
const result = await this.fetchRemoteResult(query, 0, controller.signal);
|
|
1973
|
+
this.remoteCache.set(key, result, ajax.cacheTtl ?? 3e4);
|
|
1722
1974
|
} catch {
|
|
1975
|
+
} finally {
|
|
1976
|
+
this.prefetchControllers.delete(controller);
|
|
1723
1977
|
}
|
|
1724
1978
|
}
|
|
1725
1979
|
/**
|
|
@@ -1745,12 +1999,12 @@ var ForgeSelect = class {
|
|
|
1745
1999
|
const controller = new AbortController();
|
|
1746
2000
|
this.ajaxController = controller;
|
|
1747
2001
|
const page = append ? this.page + 1 : 0;
|
|
2002
|
+
const cursor = append ? this.nextCursor : void 0;
|
|
1748
2003
|
try {
|
|
1749
|
-
const key = this.remoteCacheKey(query, page);
|
|
2004
|
+
const key = this.remoteCacheKey(query, page, cursor);
|
|
1750
2005
|
let result = this.remoteCache.get(key);
|
|
1751
2006
|
if (!result) {
|
|
1752
|
-
|
|
1753
|
-
result = normalizeRemoteResult(ajax, json);
|
|
2007
|
+
result = await this.fetchRemoteResult(query, page, controller.signal, cursor);
|
|
1754
2008
|
this.remoteCache.set(key, result, ajax.cacheTtl ?? 3e4);
|
|
1755
2009
|
}
|
|
1756
2010
|
if (activeRequestId !== this.ajaxRequestId || this.destroyed) return;
|
|
@@ -1761,17 +2015,21 @@ var ForgeSelect = class {
|
|
|
1761
2015
|
} else {
|
|
1762
2016
|
this.data = options;
|
|
1763
2017
|
this.rowContentCache.clear();
|
|
2018
|
+
this.rowHeightCache.clear();
|
|
1764
2019
|
}
|
|
1765
2020
|
this.page = page;
|
|
1766
2021
|
this.hasMore = hasMore;
|
|
2022
|
+
this.nextCursor = result.nextCursor;
|
|
1767
2023
|
this.remoteLoaded = true;
|
|
1768
2024
|
this.loadError = null;
|
|
2025
|
+
this.rebuildOptionIndexes();
|
|
1769
2026
|
} catch (cause) {
|
|
1770
2027
|
if (activeRequestId !== this.ajaxRequestId || this.destroyed || controller.signal.aborted) return;
|
|
1771
2028
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
1772
2029
|
if (!append) {
|
|
1773
2030
|
this.data = [];
|
|
1774
2031
|
this.rowContentCache.clear();
|
|
2032
|
+
this.rowHeightCache.clear();
|
|
1775
2033
|
}
|
|
1776
2034
|
this.hasMore = false;
|
|
1777
2035
|
this.loadError = error;
|