forge-select 0.5.0 → 0.6.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 +119 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +119 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -157,6 +157,7 @@ function normalizeRemoteResult(ajax, response) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// src/remote-cache.ts
|
|
160
|
+
var REMOTE_CACHE_LIMIT = 50;
|
|
160
161
|
var RemoteCache = class {
|
|
161
162
|
constructor() {
|
|
162
163
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -171,7 +172,12 @@ var RemoteCache = class {
|
|
|
171
172
|
return entry.value;
|
|
172
173
|
}
|
|
173
174
|
set(key, value, ttl, now = Date.now()) {
|
|
174
|
-
if (ttl
|
|
175
|
+
if (ttl <= 0) return;
|
|
176
|
+
if (this.entries.size >= REMOTE_CACHE_LIMIT && !this.entries.has(key)) {
|
|
177
|
+
const oldest = this.entries.keys().next().value;
|
|
178
|
+
this.entries.delete(oldest);
|
|
179
|
+
}
|
|
180
|
+
this.entries.set(key, { value, expiresAt: now + ttl });
|
|
175
181
|
}
|
|
176
182
|
clear() {
|
|
177
183
|
this.entries.clear();
|
|
@@ -305,6 +311,8 @@ var DEFAULT_ITEM_HEIGHT = 36;
|
|
|
305
311
|
var VIRTUAL_BUFFER = 5;
|
|
306
312
|
var VIRTUAL_THRESHOLD = 100;
|
|
307
313
|
var ROW_CACHE_LIMIT = 2e3;
|
|
314
|
+
var PAGE_SIZE = 10;
|
|
315
|
+
var TYPEAHEAD_RESET_MS = 500;
|
|
308
316
|
var uidCounter = 0;
|
|
309
317
|
var ForgeSelect = class {
|
|
310
318
|
constructor(target, options = {}) {
|
|
@@ -322,8 +330,13 @@ var ForgeSelect = class {
|
|
|
322
330
|
this.rows = [];
|
|
323
331
|
this.navItems = [];
|
|
324
332
|
this.highlightedIndex = -1;
|
|
333
|
+
this.typeaheadBuffer = "";
|
|
334
|
+
this.typeaheadTimer = null;
|
|
325
335
|
this.rowContentCache = /* @__PURE__ */ new Map();
|
|
326
336
|
this.rowHeightCache = /* @__PURE__ */ new Map();
|
|
337
|
+
this.rowOffsetsCache = null;
|
|
338
|
+
this.scrollRafId = null;
|
|
339
|
+
this.ancestorScrollRafId = null;
|
|
327
340
|
this.searchIndex = new SearchIndex();
|
|
328
341
|
this.expandedValues = /* @__PURE__ */ new Set();
|
|
329
342
|
this.loading = false;
|
|
@@ -352,7 +365,12 @@ var ForgeSelect = class {
|
|
|
352
365
|
this.positionDropdown();
|
|
353
366
|
};
|
|
354
367
|
this.onAncestorScroll = () => {
|
|
355
|
-
if (this.portalHost)
|
|
368
|
+
if (!this.portalHost) return;
|
|
369
|
+
if (this.ancestorScrollRafId != null) return;
|
|
370
|
+
this.ancestorScrollRafId = requestAnimationFrame(() => {
|
|
371
|
+
this.ancestorScrollRafId = null;
|
|
372
|
+
this.positionDropdown();
|
|
373
|
+
});
|
|
356
374
|
};
|
|
357
375
|
this.onNativeInvalid = (event) => {
|
|
358
376
|
event.preventDefault();
|
|
@@ -471,6 +489,19 @@ var ForgeSelect = class {
|
|
|
471
489
|
document.removeEventListener("mousedown", this.onDocumentMouseDown);
|
|
472
490
|
window.removeEventListener("resize", this.onWindowResize);
|
|
473
491
|
document.removeEventListener("scroll", this.onAncestorScroll, true);
|
|
492
|
+
if (this.ancestorScrollRafId != null) {
|
|
493
|
+
cancelAnimationFrame(this.ancestorScrollRafId);
|
|
494
|
+
this.ancestorScrollRafId = null;
|
|
495
|
+
}
|
|
496
|
+
if (this.scrollRafId != null) {
|
|
497
|
+
cancelAnimationFrame(this.scrollRafId);
|
|
498
|
+
this.scrollRafId = null;
|
|
499
|
+
}
|
|
500
|
+
if (this.typeaheadTimer) {
|
|
501
|
+
clearTimeout(this.typeaheadTimer);
|
|
502
|
+
this.typeaheadTimer = null;
|
|
503
|
+
}
|
|
504
|
+
this.typeaheadBuffer = "";
|
|
474
505
|
this.highlightedIndex = -1;
|
|
475
506
|
if (this.searchInput) {
|
|
476
507
|
this.searchInput.value = "";
|
|
@@ -503,6 +534,8 @@ var ForgeSelect = class {
|
|
|
503
534
|
this.destroyed = true;
|
|
504
535
|
if (this.ajaxTimer) clearTimeout(this.ajaxTimer);
|
|
505
536
|
this.ajaxController?.abort();
|
|
537
|
+
if (this.scrollRafId != null) cancelAnimationFrame(this.scrollRafId);
|
|
538
|
+
if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer);
|
|
506
539
|
this.nativeSelect?.removeEventListener("change", this.onNativeChange);
|
|
507
540
|
this.nativeSelect?.removeEventListener("invalid", this.onNativeInvalid);
|
|
508
541
|
this.nativeForm?.removeEventListener("reset", this.onFormReset);
|
|
@@ -587,6 +620,7 @@ var ForgeSelect = class {
|
|
|
587
620
|
this.root.classList.toggle("forge-select--sortable", this.opts.sortable && this.opts.multiple);
|
|
588
621
|
this.updateSearchVisibility();
|
|
589
622
|
this.rowContentCache.clear();
|
|
623
|
+
this.rowHeightCache.clear();
|
|
590
624
|
this.searchIndex.clear();
|
|
591
625
|
this.renderValue();
|
|
592
626
|
if (this.isOpen) this.renderList();
|
|
@@ -651,6 +685,7 @@ var ForgeSelect = class {
|
|
|
651
685
|
this.opts.data = data;
|
|
652
686
|
this.updateSearchVisibility();
|
|
653
687
|
this.rowContentCache.clear();
|
|
688
|
+
this.rowHeightCache.clear();
|
|
654
689
|
this.searchIndex.clear();
|
|
655
690
|
this.highlightedIndex = -1;
|
|
656
691
|
if (this.isOpen) this.renderList();
|
|
@@ -872,8 +907,12 @@ var ForgeSelect = class {
|
|
|
872
907
|
this.activateNavItem(navIndex);
|
|
873
908
|
});
|
|
874
909
|
this.list.addEventListener("scroll", () => {
|
|
875
|
-
if (this.
|
|
876
|
-
this.
|
|
910
|
+
if (this.scrollRafId != null) return;
|
|
911
|
+
this.scrollRafId = requestAnimationFrame(() => {
|
|
912
|
+
this.scrollRafId = null;
|
|
913
|
+
if (this.usesVirtualScroll()) this.renderRows();
|
|
914
|
+
this.maybeLoadNextPage();
|
|
915
|
+
});
|
|
877
916
|
});
|
|
878
917
|
}
|
|
879
918
|
applySearchQuery(query, emitSearch) {
|
|
@@ -881,7 +920,6 @@ var ForgeSelect = class {
|
|
|
881
920
|
if (this.searchInput && this.searchInput.value !== query) this.searchInput.value = query;
|
|
882
921
|
this.highlightedIndex = -1;
|
|
883
922
|
this.list.scrollTop = 0;
|
|
884
|
-
this.rowContentCache.clear();
|
|
885
923
|
if (emitSearch) this.emitter.emit("search", query);
|
|
886
924
|
const trimmed = query.trim();
|
|
887
925
|
const belowMinLength = trimmed !== "" && trimmed.length < this.opts.minSearchLength;
|
|
@@ -935,9 +973,63 @@ var ForgeSelect = class {
|
|
|
935
973
|
case "ArrowLeft":
|
|
936
974
|
if (this.isOpen && this.navigateTree("left")) event.preventDefault();
|
|
937
975
|
break;
|
|
976
|
+
case "Home":
|
|
977
|
+
if (this.isOpen) {
|
|
978
|
+
event.preventDefault();
|
|
979
|
+
this.focusNavIndex(0);
|
|
980
|
+
}
|
|
981
|
+
break;
|
|
982
|
+
case "End":
|
|
983
|
+
if (this.isOpen) {
|
|
984
|
+
event.preventDefault();
|
|
985
|
+
this.focusNavIndex(this.navItems.length - 1);
|
|
986
|
+
}
|
|
987
|
+
break;
|
|
988
|
+
case "PageDown":
|
|
989
|
+
if (this.isOpen) {
|
|
990
|
+
event.preventDefault();
|
|
991
|
+
this.focusNavIndex(
|
|
992
|
+
Math.min(this.navItems.length - 1, (this.highlightedIndex === -1 ? 0 : this.highlightedIndex) + PAGE_SIZE)
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
break;
|
|
996
|
+
case "PageUp":
|
|
997
|
+
if (this.isOpen) {
|
|
998
|
+
event.preventDefault();
|
|
999
|
+
this.focusNavIndex(Math.max(0, (this.highlightedIndex === -1 ? 0 : this.highlightedIndex) - PAGE_SIZE));
|
|
1000
|
+
}
|
|
1001
|
+
break;
|
|
938
1002
|
case "Tab":
|
|
939
1003
|
this.close();
|
|
940
1004
|
break;
|
|
1005
|
+
default:
|
|
1006
|
+
if (this.isOpen && event.target === this.control && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
1007
|
+
this.handleTypeahead(event.key);
|
|
1008
|
+
}
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Jumps the highlight to the next nav item (wrapping) whose label starts
|
|
1014
|
+
* with the accumulated buffer, matching native <select> typeahead: rapid
|
|
1015
|
+
* distinct keystrokes narrow the prefix, a pause resets it.
|
|
1016
|
+
*/
|
|
1017
|
+
handleTypeahead(char) {
|
|
1018
|
+
if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer);
|
|
1019
|
+
this.typeaheadBuffer += normalizeSearchText(char, this.opts.accentInsensitive);
|
|
1020
|
+
this.typeaheadTimer = setTimeout(() => {
|
|
1021
|
+
this.typeaheadBuffer = "";
|
|
1022
|
+
this.typeaheadTimer = null;
|
|
1023
|
+
}, TYPEAHEAD_RESET_MS);
|
|
1024
|
+
const prefix = [...this.typeaheadBuffer].every((value) => value === this.typeaheadBuffer[0]) ? this.typeaheadBuffer[0] : this.typeaheadBuffer;
|
|
1025
|
+
const count = this.navItems.length;
|
|
1026
|
+
for (let step = 1; step <= count; step += 1) {
|
|
1027
|
+
const index = (this.highlightedIndex + step + count) % count;
|
|
1028
|
+
const item = this.navItems[index];
|
|
1029
|
+
if (item.kind === "option" && normalizeSearchText(item.option.label, this.opts.accentInsensitive).startsWith(prefix)) {
|
|
1030
|
+
this.focusNavIndex(index);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
941
1033
|
}
|
|
942
1034
|
}
|
|
943
1035
|
// ---------------------------------------------------------------- selection
|
|
@@ -1028,7 +1120,10 @@ var ForgeSelect = class {
|
|
|
1028
1120
|
this.control.classList.remove("forge-select__control--invalid");
|
|
1029
1121
|
this.control.removeAttribute("aria-invalid");
|
|
1030
1122
|
}
|
|
1031
|
-
if (this.isOpen)
|
|
1123
|
+
if (this.isOpen) {
|
|
1124
|
+
if (this.opts.maxSelections != null) this.renderList();
|
|
1125
|
+
else this.renderRows();
|
|
1126
|
+
}
|
|
1032
1127
|
if (emitChange) this.emitter.emit("change", this.getValue());
|
|
1033
1128
|
}
|
|
1034
1129
|
syncNativeSelect(dispatchChange = true) {
|
|
@@ -1115,6 +1210,7 @@ var ForgeSelect = class {
|
|
|
1115
1210
|
if (result.created) this.emitter.emit("create", result.option);
|
|
1116
1211
|
this.emitter.emit("select", result.option);
|
|
1117
1212
|
if (!this.opts.multiple || this.opts.closeOnSelect) this.close();
|
|
1213
|
+
else if (this.isOpen) this.renderList();
|
|
1118
1214
|
}
|
|
1119
1215
|
activateNavItem(navIndex) {
|
|
1120
1216
|
const item = this.navItems[navIndex];
|
|
@@ -1289,6 +1385,7 @@ var ForgeSelect = class {
|
|
|
1289
1385
|
buildRows() {
|
|
1290
1386
|
this.rows = [];
|
|
1291
1387
|
this.navItems = [];
|
|
1388
|
+
this.rowOffsetsCache = null;
|
|
1292
1389
|
const trimmedQuery = this.query.trim();
|
|
1293
1390
|
const query = normalizeSearchText(trimmedQuery, this.opts.accentInsensitive);
|
|
1294
1391
|
const matches = (option) => query === "" || (this.opts.filterOption ? this.opts.filterOption(option, trimmedQuery) : this.searchIndex.score(option, trimmedQuery, {
|
|
@@ -1367,8 +1464,10 @@ var ForgeSelect = class {
|
|
|
1367
1464
|
return offset;
|
|
1368
1465
|
}
|
|
1369
1466
|
rowOffsets() {
|
|
1467
|
+
if (this.rowOffsetsCache) return this.rowOffsetsCache;
|
|
1370
1468
|
const offsets = [0];
|
|
1371
1469
|
for (let i = 0; i < this.rows.length; i += 1) offsets.push(offsets[i] + this.measuredRowHeight(i));
|
|
1470
|
+
this.rowOffsetsCache = offsets;
|
|
1372
1471
|
return offsets;
|
|
1373
1472
|
}
|
|
1374
1473
|
renderList() {
|
|
@@ -1408,12 +1507,21 @@ var ForgeSelect = class {
|
|
|
1408
1507
|
topSpacer.style.height = `${offsets?.[start] ?? this.rowOffset(start)}px`;
|
|
1409
1508
|
this.list.append(topSpacer);
|
|
1410
1509
|
}
|
|
1510
|
+
const appended = [];
|
|
1411
1511
|
for (let i = start; i < end; i++) {
|
|
1412
1512
|
const element = this.renderRow(this.rows[i]);
|
|
1413
1513
|
this.list.append(element);
|
|
1414
|
-
|
|
1514
|
+
appended.push(element);
|
|
1515
|
+
}
|
|
1516
|
+
if (this.opts.variableItemHeight) {
|
|
1517
|
+
for (let i = start; i < end; i++) {
|
|
1518
|
+
const element = appended[i - start];
|
|
1415
1519
|
const measured = element.getBoundingClientRect().height || element.offsetHeight;
|
|
1416
|
-
if (measured > 0)
|
|
1520
|
+
if (measured > 0) {
|
|
1521
|
+
const key = this.rowKey(this.rows[i], i);
|
|
1522
|
+
if (this.rowHeightCache.get(key) !== measured) this.rowOffsetsCache = null;
|
|
1523
|
+
this.rowHeightCache.set(key, measured);
|
|
1524
|
+
}
|
|
1417
1525
|
}
|
|
1418
1526
|
}
|
|
1419
1527
|
if (virtual) {
|
|
@@ -1565,6 +1673,7 @@ var ForgeSelect = class {
|
|
|
1565
1673
|
this.focusNavIndex(next);
|
|
1566
1674
|
}
|
|
1567
1675
|
focusNavIndex(next) {
|
|
1676
|
+
if (this.navItems.length === 0) return;
|
|
1568
1677
|
this.highlightedIndex = next;
|
|
1569
1678
|
if (this.usesVirtualScroll()) {
|
|
1570
1679
|
const rowIndex = this.rows.findIndex(
|
|
@@ -1734,6 +1843,7 @@ var ForgeSelect = class {
|
|
|
1734
1843
|
} else {
|
|
1735
1844
|
this.data = options;
|
|
1736
1845
|
this.rowContentCache.clear();
|
|
1846
|
+
this.rowHeightCache.clear();
|
|
1737
1847
|
}
|
|
1738
1848
|
this.page = page;
|
|
1739
1849
|
this.hasMore = hasMore;
|
|
@@ -1745,6 +1855,7 @@ var ForgeSelect = class {
|
|
|
1745
1855
|
if (!append) {
|
|
1746
1856
|
this.data = [];
|
|
1747
1857
|
this.rowContentCache.clear();
|
|
1858
|
+
this.rowHeightCache.clear();
|
|
1748
1859
|
}
|
|
1749
1860
|
this.hasMore = false;
|
|
1750
1861
|
this.loadError = error;
|