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.cjs
CHANGED
|
@@ -184,6 +184,7 @@ function normalizeRemoteResult(ajax, response) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// src/remote-cache.ts
|
|
187
|
+
var REMOTE_CACHE_LIMIT = 50;
|
|
187
188
|
var RemoteCache = class {
|
|
188
189
|
constructor() {
|
|
189
190
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -198,7 +199,12 @@ var RemoteCache = class {
|
|
|
198
199
|
return entry.value;
|
|
199
200
|
}
|
|
200
201
|
set(key, value, ttl, now = Date.now()) {
|
|
201
|
-
if (ttl
|
|
202
|
+
if (ttl <= 0) return;
|
|
203
|
+
if (this.entries.size >= REMOTE_CACHE_LIMIT && !this.entries.has(key)) {
|
|
204
|
+
const oldest = this.entries.keys().next().value;
|
|
205
|
+
this.entries.delete(oldest);
|
|
206
|
+
}
|
|
207
|
+
this.entries.set(key, { value, expiresAt: now + ttl });
|
|
202
208
|
}
|
|
203
209
|
clear() {
|
|
204
210
|
this.entries.clear();
|
|
@@ -332,6 +338,8 @@ var DEFAULT_ITEM_HEIGHT = 36;
|
|
|
332
338
|
var VIRTUAL_BUFFER = 5;
|
|
333
339
|
var VIRTUAL_THRESHOLD = 100;
|
|
334
340
|
var ROW_CACHE_LIMIT = 2e3;
|
|
341
|
+
var PAGE_SIZE = 10;
|
|
342
|
+
var TYPEAHEAD_RESET_MS = 500;
|
|
335
343
|
var uidCounter = 0;
|
|
336
344
|
var ForgeSelect = class {
|
|
337
345
|
constructor(target, options = {}) {
|
|
@@ -349,8 +357,13 @@ var ForgeSelect = class {
|
|
|
349
357
|
this.rows = [];
|
|
350
358
|
this.navItems = [];
|
|
351
359
|
this.highlightedIndex = -1;
|
|
360
|
+
this.typeaheadBuffer = "";
|
|
361
|
+
this.typeaheadTimer = null;
|
|
352
362
|
this.rowContentCache = /* @__PURE__ */ new Map();
|
|
353
363
|
this.rowHeightCache = /* @__PURE__ */ new Map();
|
|
364
|
+
this.rowOffsetsCache = null;
|
|
365
|
+
this.scrollRafId = null;
|
|
366
|
+
this.ancestorScrollRafId = null;
|
|
354
367
|
this.searchIndex = new SearchIndex();
|
|
355
368
|
this.expandedValues = /* @__PURE__ */ new Set();
|
|
356
369
|
this.loading = false;
|
|
@@ -379,7 +392,12 @@ var ForgeSelect = class {
|
|
|
379
392
|
this.positionDropdown();
|
|
380
393
|
};
|
|
381
394
|
this.onAncestorScroll = () => {
|
|
382
|
-
if (this.portalHost)
|
|
395
|
+
if (!this.portalHost) return;
|
|
396
|
+
if (this.ancestorScrollRafId != null) return;
|
|
397
|
+
this.ancestorScrollRafId = requestAnimationFrame(() => {
|
|
398
|
+
this.ancestorScrollRafId = null;
|
|
399
|
+
this.positionDropdown();
|
|
400
|
+
});
|
|
383
401
|
};
|
|
384
402
|
this.onNativeInvalid = (event) => {
|
|
385
403
|
event.preventDefault();
|
|
@@ -498,6 +516,19 @@ var ForgeSelect = class {
|
|
|
498
516
|
document.removeEventListener("mousedown", this.onDocumentMouseDown);
|
|
499
517
|
window.removeEventListener("resize", this.onWindowResize);
|
|
500
518
|
document.removeEventListener("scroll", this.onAncestorScroll, true);
|
|
519
|
+
if (this.ancestorScrollRafId != null) {
|
|
520
|
+
cancelAnimationFrame(this.ancestorScrollRafId);
|
|
521
|
+
this.ancestorScrollRafId = null;
|
|
522
|
+
}
|
|
523
|
+
if (this.scrollRafId != null) {
|
|
524
|
+
cancelAnimationFrame(this.scrollRafId);
|
|
525
|
+
this.scrollRafId = null;
|
|
526
|
+
}
|
|
527
|
+
if (this.typeaheadTimer) {
|
|
528
|
+
clearTimeout(this.typeaheadTimer);
|
|
529
|
+
this.typeaheadTimer = null;
|
|
530
|
+
}
|
|
531
|
+
this.typeaheadBuffer = "";
|
|
501
532
|
this.highlightedIndex = -1;
|
|
502
533
|
if (this.searchInput) {
|
|
503
534
|
this.searchInput.value = "";
|
|
@@ -530,6 +561,8 @@ var ForgeSelect = class {
|
|
|
530
561
|
this.destroyed = true;
|
|
531
562
|
if (this.ajaxTimer) clearTimeout(this.ajaxTimer);
|
|
532
563
|
this.ajaxController?.abort();
|
|
564
|
+
if (this.scrollRafId != null) cancelAnimationFrame(this.scrollRafId);
|
|
565
|
+
if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer);
|
|
533
566
|
this.nativeSelect?.removeEventListener("change", this.onNativeChange);
|
|
534
567
|
this.nativeSelect?.removeEventListener("invalid", this.onNativeInvalid);
|
|
535
568
|
this.nativeForm?.removeEventListener("reset", this.onFormReset);
|
|
@@ -614,6 +647,7 @@ var ForgeSelect = class {
|
|
|
614
647
|
this.root.classList.toggle("forge-select--sortable", this.opts.sortable && this.opts.multiple);
|
|
615
648
|
this.updateSearchVisibility();
|
|
616
649
|
this.rowContentCache.clear();
|
|
650
|
+
this.rowHeightCache.clear();
|
|
617
651
|
this.searchIndex.clear();
|
|
618
652
|
this.renderValue();
|
|
619
653
|
if (this.isOpen) this.renderList();
|
|
@@ -678,6 +712,7 @@ var ForgeSelect = class {
|
|
|
678
712
|
this.opts.data = data;
|
|
679
713
|
this.updateSearchVisibility();
|
|
680
714
|
this.rowContentCache.clear();
|
|
715
|
+
this.rowHeightCache.clear();
|
|
681
716
|
this.searchIndex.clear();
|
|
682
717
|
this.highlightedIndex = -1;
|
|
683
718
|
if (this.isOpen) this.renderList();
|
|
@@ -899,8 +934,12 @@ var ForgeSelect = class {
|
|
|
899
934
|
this.activateNavItem(navIndex);
|
|
900
935
|
});
|
|
901
936
|
this.list.addEventListener("scroll", () => {
|
|
902
|
-
if (this.
|
|
903
|
-
this.
|
|
937
|
+
if (this.scrollRafId != null) return;
|
|
938
|
+
this.scrollRafId = requestAnimationFrame(() => {
|
|
939
|
+
this.scrollRafId = null;
|
|
940
|
+
if (this.usesVirtualScroll()) this.renderRows();
|
|
941
|
+
this.maybeLoadNextPage();
|
|
942
|
+
});
|
|
904
943
|
});
|
|
905
944
|
}
|
|
906
945
|
applySearchQuery(query, emitSearch) {
|
|
@@ -908,7 +947,6 @@ var ForgeSelect = class {
|
|
|
908
947
|
if (this.searchInput && this.searchInput.value !== query) this.searchInput.value = query;
|
|
909
948
|
this.highlightedIndex = -1;
|
|
910
949
|
this.list.scrollTop = 0;
|
|
911
|
-
this.rowContentCache.clear();
|
|
912
950
|
if (emitSearch) this.emitter.emit("search", query);
|
|
913
951
|
const trimmed = query.trim();
|
|
914
952
|
const belowMinLength = trimmed !== "" && trimmed.length < this.opts.minSearchLength;
|
|
@@ -962,9 +1000,63 @@ var ForgeSelect = class {
|
|
|
962
1000
|
case "ArrowLeft":
|
|
963
1001
|
if (this.isOpen && this.navigateTree("left")) event.preventDefault();
|
|
964
1002
|
break;
|
|
1003
|
+
case "Home":
|
|
1004
|
+
if (this.isOpen) {
|
|
1005
|
+
event.preventDefault();
|
|
1006
|
+
this.focusNavIndex(0);
|
|
1007
|
+
}
|
|
1008
|
+
break;
|
|
1009
|
+
case "End":
|
|
1010
|
+
if (this.isOpen) {
|
|
1011
|
+
event.preventDefault();
|
|
1012
|
+
this.focusNavIndex(this.navItems.length - 1);
|
|
1013
|
+
}
|
|
1014
|
+
break;
|
|
1015
|
+
case "PageDown":
|
|
1016
|
+
if (this.isOpen) {
|
|
1017
|
+
event.preventDefault();
|
|
1018
|
+
this.focusNavIndex(
|
|
1019
|
+
Math.min(this.navItems.length - 1, (this.highlightedIndex === -1 ? 0 : this.highlightedIndex) + PAGE_SIZE)
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
break;
|
|
1023
|
+
case "PageUp":
|
|
1024
|
+
if (this.isOpen) {
|
|
1025
|
+
event.preventDefault();
|
|
1026
|
+
this.focusNavIndex(Math.max(0, (this.highlightedIndex === -1 ? 0 : this.highlightedIndex) - PAGE_SIZE));
|
|
1027
|
+
}
|
|
1028
|
+
break;
|
|
965
1029
|
case "Tab":
|
|
966
1030
|
this.close();
|
|
967
1031
|
break;
|
|
1032
|
+
default:
|
|
1033
|
+
if (this.isOpen && event.target === this.control && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
1034
|
+
this.handleTypeahead(event.key);
|
|
1035
|
+
}
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Jumps the highlight to the next nav item (wrapping) whose label starts
|
|
1041
|
+
* with the accumulated buffer, matching native <select> typeahead: rapid
|
|
1042
|
+
* distinct keystrokes narrow the prefix, a pause resets it.
|
|
1043
|
+
*/
|
|
1044
|
+
handleTypeahead(char) {
|
|
1045
|
+
if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer);
|
|
1046
|
+
this.typeaheadBuffer += normalizeSearchText(char, this.opts.accentInsensitive);
|
|
1047
|
+
this.typeaheadTimer = setTimeout(() => {
|
|
1048
|
+
this.typeaheadBuffer = "";
|
|
1049
|
+
this.typeaheadTimer = null;
|
|
1050
|
+
}, TYPEAHEAD_RESET_MS);
|
|
1051
|
+
const prefix = [...this.typeaheadBuffer].every((value) => value === this.typeaheadBuffer[0]) ? this.typeaheadBuffer[0] : this.typeaheadBuffer;
|
|
1052
|
+
const count = this.navItems.length;
|
|
1053
|
+
for (let step = 1; step <= count; step += 1) {
|
|
1054
|
+
const index = (this.highlightedIndex + step + count) % count;
|
|
1055
|
+
const item = this.navItems[index];
|
|
1056
|
+
if (item.kind === "option" && normalizeSearchText(item.option.label, this.opts.accentInsensitive).startsWith(prefix)) {
|
|
1057
|
+
this.focusNavIndex(index);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
968
1060
|
}
|
|
969
1061
|
}
|
|
970
1062
|
// ---------------------------------------------------------------- selection
|
|
@@ -1055,7 +1147,10 @@ var ForgeSelect = class {
|
|
|
1055
1147
|
this.control.classList.remove("forge-select__control--invalid");
|
|
1056
1148
|
this.control.removeAttribute("aria-invalid");
|
|
1057
1149
|
}
|
|
1058
|
-
if (this.isOpen)
|
|
1150
|
+
if (this.isOpen) {
|
|
1151
|
+
if (this.opts.maxSelections != null) this.renderList();
|
|
1152
|
+
else this.renderRows();
|
|
1153
|
+
}
|
|
1059
1154
|
if (emitChange) this.emitter.emit("change", this.getValue());
|
|
1060
1155
|
}
|
|
1061
1156
|
syncNativeSelect(dispatchChange = true) {
|
|
@@ -1142,6 +1237,7 @@ var ForgeSelect = class {
|
|
|
1142
1237
|
if (result.created) this.emitter.emit("create", result.option);
|
|
1143
1238
|
this.emitter.emit("select", result.option);
|
|
1144
1239
|
if (!this.opts.multiple || this.opts.closeOnSelect) this.close();
|
|
1240
|
+
else if (this.isOpen) this.renderList();
|
|
1145
1241
|
}
|
|
1146
1242
|
activateNavItem(navIndex) {
|
|
1147
1243
|
const item = this.navItems[navIndex];
|
|
@@ -1316,6 +1412,7 @@ var ForgeSelect = class {
|
|
|
1316
1412
|
buildRows() {
|
|
1317
1413
|
this.rows = [];
|
|
1318
1414
|
this.navItems = [];
|
|
1415
|
+
this.rowOffsetsCache = null;
|
|
1319
1416
|
const trimmedQuery = this.query.trim();
|
|
1320
1417
|
const query = normalizeSearchText(trimmedQuery, this.opts.accentInsensitive);
|
|
1321
1418
|
const matches = (option) => query === "" || (this.opts.filterOption ? this.opts.filterOption(option, trimmedQuery) : this.searchIndex.score(option, trimmedQuery, {
|
|
@@ -1394,8 +1491,10 @@ var ForgeSelect = class {
|
|
|
1394
1491
|
return offset;
|
|
1395
1492
|
}
|
|
1396
1493
|
rowOffsets() {
|
|
1494
|
+
if (this.rowOffsetsCache) return this.rowOffsetsCache;
|
|
1397
1495
|
const offsets = [0];
|
|
1398
1496
|
for (let i = 0; i < this.rows.length; i += 1) offsets.push(offsets[i] + this.measuredRowHeight(i));
|
|
1497
|
+
this.rowOffsetsCache = offsets;
|
|
1399
1498
|
return offsets;
|
|
1400
1499
|
}
|
|
1401
1500
|
renderList() {
|
|
@@ -1435,12 +1534,21 @@ var ForgeSelect = class {
|
|
|
1435
1534
|
topSpacer.style.height = `${offsets?.[start] ?? this.rowOffset(start)}px`;
|
|
1436
1535
|
this.list.append(topSpacer);
|
|
1437
1536
|
}
|
|
1537
|
+
const appended = [];
|
|
1438
1538
|
for (let i = start; i < end; i++) {
|
|
1439
1539
|
const element = this.renderRow(this.rows[i]);
|
|
1440
1540
|
this.list.append(element);
|
|
1441
|
-
|
|
1541
|
+
appended.push(element);
|
|
1542
|
+
}
|
|
1543
|
+
if (this.opts.variableItemHeight) {
|
|
1544
|
+
for (let i = start; i < end; i++) {
|
|
1545
|
+
const element = appended[i - start];
|
|
1442
1546
|
const measured = element.getBoundingClientRect().height || element.offsetHeight;
|
|
1443
|
-
if (measured > 0)
|
|
1547
|
+
if (measured > 0) {
|
|
1548
|
+
const key = this.rowKey(this.rows[i], i);
|
|
1549
|
+
if (this.rowHeightCache.get(key) !== measured) this.rowOffsetsCache = null;
|
|
1550
|
+
this.rowHeightCache.set(key, measured);
|
|
1551
|
+
}
|
|
1444
1552
|
}
|
|
1445
1553
|
}
|
|
1446
1554
|
if (virtual) {
|
|
@@ -1592,6 +1700,7 @@ var ForgeSelect = class {
|
|
|
1592
1700
|
this.focusNavIndex(next);
|
|
1593
1701
|
}
|
|
1594
1702
|
focusNavIndex(next) {
|
|
1703
|
+
if (this.navItems.length === 0) return;
|
|
1595
1704
|
this.highlightedIndex = next;
|
|
1596
1705
|
if (this.usesVirtualScroll()) {
|
|
1597
1706
|
const rowIndex = this.rows.findIndex(
|
|
@@ -1761,6 +1870,7 @@ var ForgeSelect = class {
|
|
|
1761
1870
|
} else {
|
|
1762
1871
|
this.data = options;
|
|
1763
1872
|
this.rowContentCache.clear();
|
|
1873
|
+
this.rowHeightCache.clear();
|
|
1764
1874
|
}
|
|
1765
1875
|
this.page = page;
|
|
1766
1876
|
this.hasMore = hasMore;
|
|
@@ -1772,6 +1882,7 @@ var ForgeSelect = class {
|
|
|
1772
1882
|
if (!append) {
|
|
1773
1883
|
this.data = [];
|
|
1774
1884
|
this.rowContentCache.clear();
|
|
1885
|
+
this.rowHeightCache.clear();
|
|
1775
1886
|
}
|
|
1776
1887
|
this.hasMore = false;
|
|
1777
1888
|
this.loadError = error;
|