forge-select 0.4.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/README.md +1 -1
- package/dist/index.cjs +467 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -3
- package/dist/index.d.ts +65 -3
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +467 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/styles/forge-select.css +12 -0
package/dist/index.cjs
CHANGED
|
@@ -183,6 +183,95 @@ function normalizeRemoteResult(ajax, response) {
|
|
|
183
183
|
return { options: result.options, hasMore: ajax.pagination ? Boolean(result.hasMore) : false };
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// src/remote-cache.ts
|
|
187
|
+
var REMOTE_CACHE_LIMIT = 50;
|
|
188
|
+
var RemoteCache = class {
|
|
189
|
+
constructor() {
|
|
190
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
191
|
+
}
|
|
192
|
+
get(key, now = Date.now()) {
|
|
193
|
+
const entry = this.entries.get(key);
|
|
194
|
+
if (!entry) return void 0;
|
|
195
|
+
if (entry.expiresAt <= now) {
|
|
196
|
+
this.entries.delete(key);
|
|
197
|
+
return void 0;
|
|
198
|
+
}
|
|
199
|
+
return entry.value;
|
|
200
|
+
}
|
|
201
|
+
set(key, value, ttl, now = Date.now()) {
|
|
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 });
|
|
208
|
+
}
|
|
209
|
+
clear() {
|
|
210
|
+
this.entries.clear();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/search.ts
|
|
215
|
+
function normalizeSearchText(value, accentInsensitive = true) {
|
|
216
|
+
const lower = value.toLocaleLowerCase();
|
|
217
|
+
return accentInsensitive ? lower.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/đ/g, "d") : lower;
|
|
218
|
+
}
|
|
219
|
+
function getSearchField(option, field) {
|
|
220
|
+
if (field === "label") return option.label;
|
|
221
|
+
if (field === "description") return option.description ?? "";
|
|
222
|
+
const path = field.slice(5).split(".");
|
|
223
|
+
let value = option.meta;
|
|
224
|
+
for (const key of path) {
|
|
225
|
+
if (!value || typeof value !== "object") return "";
|
|
226
|
+
value = value[key];
|
|
227
|
+
}
|
|
228
|
+
return value == null ? "" : String(value);
|
|
229
|
+
}
|
|
230
|
+
var SearchIndex = class {
|
|
231
|
+
constructor() {
|
|
232
|
+
this.cache = /* @__PURE__ */ new WeakMap();
|
|
233
|
+
}
|
|
234
|
+
clear() {
|
|
235
|
+
this.cache = /* @__PURE__ */ new WeakMap();
|
|
236
|
+
}
|
|
237
|
+
score(option, query, config) {
|
|
238
|
+
const normalizedQuery = normalizeSearchText(query.trim(), config.accentInsensitive);
|
|
239
|
+
if (!normalizedQuery) return 1;
|
|
240
|
+
if (config.scorer) return config.scorer(option, query.trim(), normalizedQuery);
|
|
241
|
+
const key = `${config.accentInsensitive ? "1" : "0"}:${config.fields.join("\0")}`;
|
|
242
|
+
let variants = this.cache.get(option);
|
|
243
|
+
if (!variants) {
|
|
244
|
+
variants = /* @__PURE__ */ new Map();
|
|
245
|
+
this.cache.set(option, variants);
|
|
246
|
+
}
|
|
247
|
+
let haystacks = variants.get(key);
|
|
248
|
+
if (!haystacks) {
|
|
249
|
+
haystacks = config.fields.map(
|
|
250
|
+
(field) => normalizeSearchText(getSearchField(option, field), config.accentInsensitive)
|
|
251
|
+
);
|
|
252
|
+
variants.set(key, haystacks);
|
|
253
|
+
}
|
|
254
|
+
const tokens = config.tokenSearch ? normalizedQuery.split(/\s+/).filter(Boolean) : [normalizedQuery];
|
|
255
|
+
if (!tokens.every((token) => haystacks.some((field) => field.includes(token)))) return 0;
|
|
256
|
+
const label = haystacks[config.fields.indexOf("label")];
|
|
257
|
+
if (label === normalizedQuery) return 4;
|
|
258
|
+
if (label?.startsWith(normalizedQuery)) return 3;
|
|
259
|
+
if (label?.includes(normalizedQuery)) return 2;
|
|
260
|
+
return 1;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
function findNormalizedRanges(label, query, accentInsensitive = true) {
|
|
264
|
+
const tokens = normalizeSearchText(query.trim(), accentInsensitive).split(/\s+/).filter(Boolean);
|
|
265
|
+
if (!tokens.length) return [];
|
|
266
|
+
const normalized = normalizeSearchText(label, accentInsensitive);
|
|
267
|
+
const ranges = [];
|
|
268
|
+
for (const token of tokens) {
|
|
269
|
+
const index = normalized.indexOf(token);
|
|
270
|
+
if (index >= 0) ranges.push([index, index + token.length]);
|
|
271
|
+
}
|
|
272
|
+
return ranges.sort((a, b) => a[0] - b[0]);
|
|
273
|
+
}
|
|
274
|
+
|
|
186
275
|
// src/selection.ts
|
|
187
276
|
function isGroup(item) {
|
|
188
277
|
return item.options !== void 0;
|
|
@@ -249,6 +338,8 @@ var DEFAULT_ITEM_HEIGHT = 36;
|
|
|
249
338
|
var VIRTUAL_BUFFER = 5;
|
|
250
339
|
var VIRTUAL_THRESHOLD = 100;
|
|
251
340
|
var ROW_CACHE_LIMIT = 2e3;
|
|
341
|
+
var PAGE_SIZE = 10;
|
|
342
|
+
var TYPEAHEAD_RESET_MS = 500;
|
|
252
343
|
var uidCounter = 0;
|
|
253
344
|
var ForgeSelect = class {
|
|
254
345
|
constructor(target, options = {}) {
|
|
@@ -266,7 +357,14 @@ var ForgeSelect = class {
|
|
|
266
357
|
this.rows = [];
|
|
267
358
|
this.navItems = [];
|
|
268
359
|
this.highlightedIndex = -1;
|
|
360
|
+
this.typeaheadBuffer = "";
|
|
361
|
+
this.typeaheadTimer = null;
|
|
269
362
|
this.rowContentCache = /* @__PURE__ */ new Map();
|
|
363
|
+
this.rowHeightCache = /* @__PURE__ */ new Map();
|
|
364
|
+
this.rowOffsetsCache = null;
|
|
365
|
+
this.scrollRafId = null;
|
|
366
|
+
this.ancestorScrollRafId = null;
|
|
367
|
+
this.searchIndex = new SearchIndex();
|
|
270
368
|
this.expandedValues = /* @__PURE__ */ new Set();
|
|
271
369
|
this.loading = false;
|
|
272
370
|
this.loadingMore = false;
|
|
@@ -276,6 +374,7 @@ var ForgeSelect = class {
|
|
|
276
374
|
this.ajaxRequestId = 0;
|
|
277
375
|
this.ajaxController = null;
|
|
278
376
|
this.remoteLoaded = false;
|
|
377
|
+
this.remoteCache = new RemoteCache();
|
|
279
378
|
this.loadError = null;
|
|
280
379
|
this.originalDisplay = "";
|
|
281
380
|
this.originalDisabled = false;
|
|
@@ -293,7 +392,12 @@ var ForgeSelect = class {
|
|
|
293
392
|
this.positionDropdown();
|
|
294
393
|
};
|
|
295
394
|
this.onAncestorScroll = () => {
|
|
296
|
-
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
|
+
});
|
|
297
401
|
};
|
|
298
402
|
this.onNativeInvalid = (event) => {
|
|
299
403
|
event.preventDefault();
|
|
@@ -301,6 +405,7 @@ var ForgeSelect = class {
|
|
|
301
405
|
this.control.setAttribute("aria-invalid", "true");
|
|
302
406
|
if (!this.isOpen) this.open();
|
|
303
407
|
this.control.focus();
|
|
408
|
+
this.emitter.emit("invalid", this.nativeSelect?.validationMessage ?? "");
|
|
304
409
|
};
|
|
305
410
|
this.onNativeChange = () => {
|
|
306
411
|
if (!this.nativeSelect || this.destroyed || this.syncingNative) return;
|
|
@@ -339,11 +444,17 @@ var ForgeSelect = class {
|
|
|
339
444
|
templateResult: options.templateResult,
|
|
340
445
|
templateSelection: options.templateSelection,
|
|
341
446
|
filterOption: options.filterOption,
|
|
447
|
+
searchFields: options.searchFields ?? ["label", "description"],
|
|
448
|
+
tokenSearch: options.tokenSearch ?? true,
|
|
449
|
+
accentInsensitive: options.accentInsensitive ?? true,
|
|
450
|
+
searchScorer: options.searchScorer,
|
|
451
|
+
highlightSearch: options.highlightSearch ?? false,
|
|
342
452
|
minSearchLength: Math.max(0, Math.floor(options.minSearchLength ?? 0)),
|
|
343
453
|
minResultsForSearch: Math.max(0, Math.floor(options.minResultsForSearch ?? 0)),
|
|
344
454
|
isOptionDisabled: options.isOptionDisabled,
|
|
345
455
|
virtualScroll: options.virtualScroll,
|
|
346
|
-
itemHeight: options.itemHeight
|
|
456
|
+
itemHeight: typeof options.itemHeight === "number" ? Math.max(1, options.itemHeight) : DEFAULT_ITEM_HEIGHT,
|
|
457
|
+
variableItemHeight: options.itemHeight === "auto",
|
|
347
458
|
language: options.language ?? "en",
|
|
348
459
|
plugins: options.plugins ?? [],
|
|
349
460
|
openOnFocus: options.openOnFocus ?? false,
|
|
@@ -367,6 +478,7 @@ var ForgeSelect = class {
|
|
|
367
478
|
nativeSelect?.addEventListener("invalid", this.onNativeInvalid);
|
|
368
479
|
this.nativeForm?.addEventListener("reset", this.onFormReset);
|
|
369
480
|
for (const plugin of this.plugins) plugin.onInit?.(this);
|
|
481
|
+
for (const query of this.opts.ajax?.prefetch ?? []) void this.prefetchRemote(query);
|
|
370
482
|
}
|
|
371
483
|
applyNativeValues(values) {
|
|
372
484
|
this.selected = [];
|
|
@@ -383,7 +495,7 @@ var ForgeSelect = class {
|
|
|
383
495
|
this.root.classList.add("forge-select--open");
|
|
384
496
|
this.control.setAttribute("aria-expanded", "true");
|
|
385
497
|
document.addEventListener("mousedown", this.onDocumentMouseDown);
|
|
386
|
-
if (this.opts.ajax && !this.remoteLoaded) {
|
|
498
|
+
if (this.opts.ajax && (this.opts.ajax.loadOnOpen ?? true) && !this.remoteLoaded) {
|
|
387
499
|
this.scheduleRemoteLoad(this.query, 0);
|
|
388
500
|
}
|
|
389
501
|
this.renderList();
|
|
@@ -404,6 +516,19 @@ var ForgeSelect = class {
|
|
|
404
516
|
document.removeEventListener("mousedown", this.onDocumentMouseDown);
|
|
405
517
|
window.removeEventListener("resize", this.onWindowResize);
|
|
406
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 = "";
|
|
407
532
|
this.highlightedIndex = -1;
|
|
408
533
|
if (this.searchInput) {
|
|
409
534
|
this.searchInput.value = "";
|
|
@@ -436,10 +561,14 @@ var ForgeSelect = class {
|
|
|
436
561
|
this.destroyed = true;
|
|
437
562
|
if (this.ajaxTimer) clearTimeout(this.ajaxTimer);
|
|
438
563
|
this.ajaxController?.abort();
|
|
564
|
+
if (this.scrollRafId != null) cancelAnimationFrame(this.scrollRafId);
|
|
565
|
+
if (this.typeaheadTimer) clearTimeout(this.typeaheadTimer);
|
|
439
566
|
this.nativeSelect?.removeEventListener("change", this.onNativeChange);
|
|
440
567
|
this.nativeSelect?.removeEventListener("invalid", this.onNativeInvalid);
|
|
441
568
|
this.nativeForm?.removeEventListener("reset", this.onFormReset);
|
|
442
569
|
this.rowContentCache.clear();
|
|
570
|
+
this.rowHeightCache.clear();
|
|
571
|
+
this.searchIndex.clear();
|
|
443
572
|
this.portalHost?.remove();
|
|
444
573
|
this.root.remove();
|
|
445
574
|
this.el.style.display = this.originalDisplay;
|
|
@@ -450,6 +579,107 @@ var ForgeSelect = class {
|
|
|
450
579
|
if (this.opts.multiple) return [...this.selected];
|
|
451
580
|
return this.selected[0] ?? null;
|
|
452
581
|
}
|
|
582
|
+
getSearchQuery() {
|
|
583
|
+
return this.query;
|
|
584
|
+
}
|
|
585
|
+
setSearchQuery(query, options = {}) {
|
|
586
|
+
this.applySearchQuery(query, options.emitSearch ?? true);
|
|
587
|
+
}
|
|
588
|
+
isDropdownOpen() {
|
|
589
|
+
return this.isOpen;
|
|
590
|
+
}
|
|
591
|
+
updateOptions(options) {
|
|
592
|
+
if (options.data) this.setData(options.data);
|
|
593
|
+
if ("ajax" in options && options.ajax !== this.opts.ajax) {
|
|
594
|
+
this.opts.ajax = options.ajax;
|
|
595
|
+
this.remoteLoaded = false;
|
|
596
|
+
this.clearRemoteCache();
|
|
597
|
+
}
|
|
598
|
+
if (options.placeholder !== void 0) this.opts.placeholder = options.placeholder;
|
|
599
|
+
if (options.clearable !== void 0) this.opts.clearable = options.clearable;
|
|
600
|
+
if (options.allowCreate !== void 0) this.opts.allowCreate = options.allowCreate;
|
|
601
|
+
if (options.sortable !== void 0) this.opts.sortable = options.sortable;
|
|
602
|
+
if (options.closeOnSelect !== void 0) this.opts.closeOnSelect = options.closeOnSelect;
|
|
603
|
+
if ("maxSelections" in options)
|
|
604
|
+
this.opts.maxSelections = options.maxSelections == null || !Number.isFinite(options.maxSelections) ? void 0 : Math.max(0, Math.floor(options.maxSelections));
|
|
605
|
+
if (options.theme !== void 0) {
|
|
606
|
+
this.opts.theme = options.theme;
|
|
607
|
+
this.root.dataset.theme = options.theme;
|
|
608
|
+
if (this.portalHost) this.portalHost.dataset.theme = options.theme;
|
|
609
|
+
}
|
|
610
|
+
if (options.required !== void 0) {
|
|
611
|
+
this.opts.required = options.required;
|
|
612
|
+
if (options.required) this.control.setAttribute("aria-required", "true");
|
|
613
|
+
else this.control.removeAttribute("aria-required");
|
|
614
|
+
if (this.nativeSelect) this.nativeSelect.required = options.required;
|
|
615
|
+
}
|
|
616
|
+
if (options.templateResult !== void 0) this.opts.templateResult = options.templateResult;
|
|
617
|
+
if (options.templateSelection !== void 0) this.opts.templateSelection = options.templateSelection;
|
|
618
|
+
if (options.filterOption !== void 0) this.opts.filterOption = options.filterOption;
|
|
619
|
+
if (options.searchFields !== void 0) this.opts.searchFields = options.searchFields;
|
|
620
|
+
if (options.tokenSearch !== void 0) this.opts.tokenSearch = options.tokenSearch;
|
|
621
|
+
if (options.accentInsensitive !== void 0) this.opts.accentInsensitive = options.accentInsensitive;
|
|
622
|
+
if (options.searchScorer !== void 0) this.opts.searchScorer = options.searchScorer;
|
|
623
|
+
if (options.highlightSearch !== void 0) this.opts.highlightSearch = options.highlightSearch;
|
|
624
|
+
if (options.minSearchLength !== void 0)
|
|
625
|
+
this.opts.minSearchLength = Math.max(0, Math.floor(options.minSearchLength));
|
|
626
|
+
if (options.minResultsForSearch !== void 0)
|
|
627
|
+
this.opts.minResultsForSearch = Math.max(0, Math.floor(options.minResultsForSearch));
|
|
628
|
+
if (options.isOptionDisabled !== void 0) this.opts.isOptionDisabled = options.isOptionDisabled;
|
|
629
|
+
if (options.virtualScroll !== void 0) this.opts.virtualScroll = options.virtualScroll;
|
|
630
|
+
if (options.itemHeight !== void 0) {
|
|
631
|
+
this.opts.variableItemHeight = options.itemHeight === "auto";
|
|
632
|
+
if (typeof options.itemHeight === "number") this.opts.itemHeight = Math.max(1, options.itemHeight);
|
|
633
|
+
this.root.style.setProperty("--fs-item-height", `${this.opts.itemHeight}px`);
|
|
634
|
+
this.portalHost?.style.setProperty("--fs-item-height", `${this.opts.itemHeight}px`);
|
|
635
|
+
}
|
|
636
|
+
if (options.language !== void 0) {
|
|
637
|
+
this.opts.language = options.language;
|
|
638
|
+
this.strings = getStrings(options.language);
|
|
639
|
+
this.clearBtn.setAttribute("aria-label", this.strings.clearSelection);
|
|
640
|
+
this.searchInput?.setAttribute("aria-label", this.strings.search);
|
|
641
|
+
}
|
|
642
|
+
if (options.openOnFocus !== void 0) this.opts.openOnFocus = options.openOnFocus;
|
|
643
|
+
if (options.disabled !== void 0) {
|
|
644
|
+
if (options.disabled) this.disable();
|
|
645
|
+
else this.enable();
|
|
646
|
+
}
|
|
647
|
+
this.root.classList.toggle("forge-select--sortable", this.opts.sortable && this.opts.multiple);
|
|
648
|
+
this.updateSearchVisibility();
|
|
649
|
+
this.rowContentCache.clear();
|
|
650
|
+
this.rowHeightCache.clear();
|
|
651
|
+
this.searchIndex.clear();
|
|
652
|
+
this.renderValue();
|
|
653
|
+
if (this.isOpen) this.renderList();
|
|
654
|
+
}
|
|
655
|
+
validate() {
|
|
656
|
+
const valid = (!this.opts.required || this.selected.length > 0) && (this.control.dataset.validationMessage ?? "") === "";
|
|
657
|
+
this.control.classList.toggle("forge-select__control--invalid", !valid);
|
|
658
|
+
this.control.setAttribute("aria-invalid", String(!valid));
|
|
659
|
+
return valid;
|
|
660
|
+
}
|
|
661
|
+
setCustomValidity(message) {
|
|
662
|
+
this.nativeSelect?.setCustomValidity(message);
|
|
663
|
+
this.control.dataset.validationMessage = message;
|
|
664
|
+
}
|
|
665
|
+
reportValidity() {
|
|
666
|
+
const valid = this.validate() && (this.nativeSelect?.checkValidity() ?? true);
|
|
667
|
+
if (!valid) {
|
|
668
|
+
const message = this.nativeSelect?.validationMessage ?? this.control.dataset.validationMessage ?? "";
|
|
669
|
+
if (this.nativeSelect) return this.nativeSelect.reportValidity();
|
|
670
|
+
this.emitter.emit("invalid", message);
|
|
671
|
+
}
|
|
672
|
+
return valid;
|
|
673
|
+
}
|
|
674
|
+
reload() {
|
|
675
|
+
if (!this.opts.ajax) return;
|
|
676
|
+
this.clearRemoteCache();
|
|
677
|
+
this.remoteLoaded = false;
|
|
678
|
+
this.scheduleRemoteLoad(this.query, 0);
|
|
679
|
+
}
|
|
680
|
+
clearRemoteCache() {
|
|
681
|
+
this.remoteCache.clear();
|
|
682
|
+
}
|
|
453
683
|
setValue(value, options = {}) {
|
|
454
684
|
const values = value == null ? [] : Array.isArray(value) ? value : [value];
|
|
455
685
|
const next = this.opts.multiple ? values : values.slice(0, 1);
|
|
@@ -472,7 +702,7 @@ var ForgeSelect = class {
|
|
|
472
702
|
this.ajaxController?.abort();
|
|
473
703
|
this.ajaxController = null;
|
|
474
704
|
this.ajaxRequestId += 1;
|
|
475
|
-
this.
|
|
705
|
+
this.setLoading(false);
|
|
476
706
|
this.loadingMore = false;
|
|
477
707
|
this.loadError = null;
|
|
478
708
|
this.remoteLoaded = true;
|
|
@@ -482,6 +712,8 @@ var ForgeSelect = class {
|
|
|
482
712
|
this.opts.data = data;
|
|
483
713
|
this.updateSearchVisibility();
|
|
484
714
|
this.rowContentCache.clear();
|
|
715
|
+
this.rowHeightCache.clear();
|
|
716
|
+
this.searchIndex.clear();
|
|
485
717
|
this.highlightedIndex = -1;
|
|
486
718
|
if (this.isOpen) this.renderList();
|
|
487
719
|
}
|
|
@@ -655,25 +887,7 @@ var ForgeSelect = class {
|
|
|
655
887
|
});
|
|
656
888
|
if (this.searchInput) {
|
|
657
889
|
this.searchInput.addEventListener("input", () => {
|
|
658
|
-
this.
|
|
659
|
-
this.highlightedIndex = -1;
|
|
660
|
-
this.list.scrollTop = 0;
|
|
661
|
-
this.emitter.emit("search", this.query);
|
|
662
|
-
const trimmed = this.query.trim();
|
|
663
|
-
const belowMinLength = trimmed !== "" && trimmed.length < this.opts.minSearchLength;
|
|
664
|
-
if (this.opts.ajax && !belowMinLength) {
|
|
665
|
-
this.scheduleRemoteLoad(this.query, this.opts.ajax.debounce ?? 250);
|
|
666
|
-
} else {
|
|
667
|
-
if (belowMinLength) {
|
|
668
|
-
if (this.ajaxTimer) {
|
|
669
|
-
clearTimeout(this.ajaxTimer);
|
|
670
|
-
this.ajaxTimer = null;
|
|
671
|
-
}
|
|
672
|
-
this.ajaxController?.abort();
|
|
673
|
-
this.loading = false;
|
|
674
|
-
}
|
|
675
|
-
this.renderList();
|
|
676
|
-
}
|
|
890
|
+
this.applySearchQuery(this.searchInput.value, true);
|
|
677
891
|
});
|
|
678
892
|
this.searchInput.addEventListener("keydown", (event) => this.handleKeydown(event));
|
|
679
893
|
this.searchInput.addEventListener("paste", (event) => {
|
|
@@ -720,10 +934,36 @@ var ForgeSelect = class {
|
|
|
720
934
|
this.activateNavItem(navIndex);
|
|
721
935
|
});
|
|
722
936
|
this.list.addEventListener("scroll", () => {
|
|
723
|
-
if (this.
|
|
724
|
-
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
|
+
});
|
|
725
943
|
});
|
|
726
944
|
}
|
|
945
|
+
applySearchQuery(query, emitSearch) {
|
|
946
|
+
this.query = query;
|
|
947
|
+
if (this.searchInput && this.searchInput.value !== query) this.searchInput.value = query;
|
|
948
|
+
this.highlightedIndex = -1;
|
|
949
|
+
this.list.scrollTop = 0;
|
|
950
|
+
if (emitSearch) this.emitter.emit("search", query);
|
|
951
|
+
const trimmed = query.trim();
|
|
952
|
+
const belowMinLength = trimmed !== "" && trimmed.length < this.opts.minSearchLength;
|
|
953
|
+
if (this.opts.ajax && !belowMinLength) {
|
|
954
|
+
this.scheduleRemoteLoad(query, this.opts.ajax.debounce ?? 250);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (belowMinLength) {
|
|
958
|
+
if (this.ajaxTimer) {
|
|
959
|
+
clearTimeout(this.ajaxTimer);
|
|
960
|
+
this.ajaxTimer = null;
|
|
961
|
+
}
|
|
962
|
+
this.ajaxController?.abort();
|
|
963
|
+
this.setLoading(false);
|
|
964
|
+
}
|
|
965
|
+
this.renderList();
|
|
966
|
+
}
|
|
727
967
|
handleKeydown(event) {
|
|
728
968
|
if (this.isDisabled) return;
|
|
729
969
|
switch (event.key) {
|
|
@@ -760,9 +1000,63 @@ var ForgeSelect = class {
|
|
|
760
1000
|
case "ArrowLeft":
|
|
761
1001
|
if (this.isOpen && this.navigateTree("left")) event.preventDefault();
|
|
762
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;
|
|
763
1029
|
case "Tab":
|
|
764
1030
|
this.close();
|
|
765
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
|
+
}
|
|
766
1060
|
}
|
|
767
1061
|
}
|
|
768
1062
|
// ---------------------------------------------------------------- selection
|
|
@@ -853,7 +1147,10 @@ var ForgeSelect = class {
|
|
|
853
1147
|
this.control.classList.remove("forge-select__control--invalid");
|
|
854
1148
|
this.control.removeAttribute("aria-invalid");
|
|
855
1149
|
}
|
|
856
|
-
if (this.isOpen)
|
|
1150
|
+
if (this.isOpen) {
|
|
1151
|
+
if (this.opts.maxSelections != null) this.renderList();
|
|
1152
|
+
else this.renderRows();
|
|
1153
|
+
}
|
|
857
1154
|
if (emitChange) this.emitter.emit("change", this.getValue());
|
|
858
1155
|
}
|
|
859
1156
|
syncNativeSelect(dispatchChange = true) {
|
|
@@ -940,6 +1237,7 @@ var ForgeSelect = class {
|
|
|
940
1237
|
if (result.created) this.emitter.emit("create", result.option);
|
|
941
1238
|
this.emitter.emit("select", result.option);
|
|
942
1239
|
if (!this.opts.multiple || this.opts.closeOnSelect) this.close();
|
|
1240
|
+
else if (this.isOpen) this.renderList();
|
|
943
1241
|
}
|
|
944
1242
|
activateNavItem(navIndex) {
|
|
945
1243
|
const item = this.navItems[navIndex];
|
|
@@ -1114,9 +1412,15 @@ var ForgeSelect = class {
|
|
|
1114
1412
|
buildRows() {
|
|
1115
1413
|
this.rows = [];
|
|
1116
1414
|
this.navItems = [];
|
|
1415
|
+
this.rowOffsetsCache = null;
|
|
1117
1416
|
const trimmedQuery = this.query.trim();
|
|
1118
|
-
const query = trimmedQuery.
|
|
1119
|
-
const matches = (option) => query === "" || (this.opts.filterOption ? this.opts.filterOption(option, trimmedQuery) :
|
|
1417
|
+
const query = normalizeSearchText(trimmedQuery, this.opts.accentInsensitive);
|
|
1418
|
+
const matches = (option) => query === "" || (this.opts.filterOption ? this.opts.filterOption(option, trimmedQuery) : this.searchIndex.score(option, trimmedQuery, {
|
|
1419
|
+
fields: this.opts.searchFields,
|
|
1420
|
+
tokenSearch: this.opts.tokenSearch,
|
|
1421
|
+
accentInsensitive: this.opts.accentInsensitive,
|
|
1422
|
+
scorer: this.opts.searchScorer
|
|
1423
|
+
}) > 0);
|
|
1120
1424
|
const subtreeMatches = (option) => query === "" || matches(option) || (option.children ?? []).some(subtreeMatches);
|
|
1121
1425
|
const pushOption = (option, depth, parentValue) => {
|
|
1122
1426
|
let navIndex = -1;
|
|
@@ -1172,6 +1476,27 @@ var ForgeSelect = class {
|
|
|
1172
1476
|
usesVirtualScroll() {
|
|
1173
1477
|
return this.opts.virtualScroll !== false && this.rows.length > VIRTUAL_THRESHOLD;
|
|
1174
1478
|
}
|
|
1479
|
+
rowKey(row, index) {
|
|
1480
|
+
if (row.kind === "option") return `option:${row.option.value}`;
|
|
1481
|
+
if (row.kind === "group") return `group:${row.label}:${index}`;
|
|
1482
|
+
return `${row.kind}:${index}`;
|
|
1483
|
+
}
|
|
1484
|
+
measuredRowHeight(index) {
|
|
1485
|
+
return this.opts.variableItemHeight ? this.rowHeightCache.get(this.rowKey(this.rows[index], index)) ?? this.opts.itemHeight : this.opts.itemHeight;
|
|
1486
|
+
}
|
|
1487
|
+
rowOffset(index) {
|
|
1488
|
+
if (!this.opts.variableItemHeight) return index * this.opts.itemHeight;
|
|
1489
|
+
let offset = 0;
|
|
1490
|
+
for (let i = 0; i < index; i += 1) offset += this.measuredRowHeight(i);
|
|
1491
|
+
return offset;
|
|
1492
|
+
}
|
|
1493
|
+
rowOffsets() {
|
|
1494
|
+
if (this.rowOffsetsCache) return this.rowOffsetsCache;
|
|
1495
|
+
const offsets = [0];
|
|
1496
|
+
for (let i = 0; i < this.rows.length; i += 1) offsets.push(offsets[i] + this.measuredRowHeight(i));
|
|
1497
|
+
this.rowOffsetsCache = offsets;
|
|
1498
|
+
return offsets;
|
|
1499
|
+
}
|
|
1175
1500
|
renderList() {
|
|
1176
1501
|
this.buildRows();
|
|
1177
1502
|
this.renderRows();
|
|
@@ -1188,26 +1513,49 @@ var ForgeSelect = class {
|
|
|
1188
1513
|
const virtual = this.usesVirtualScroll();
|
|
1189
1514
|
this.list.textContent = "";
|
|
1190
1515
|
const rowHeight = this.opts.itemHeight;
|
|
1516
|
+
const offsets = this.opts.variableItemHeight ? this.rowOffsets() : null;
|
|
1191
1517
|
let start = 0;
|
|
1192
1518
|
let end = this.rows.length;
|
|
1193
1519
|
if (virtual) {
|
|
1194
1520
|
const viewport = clientHeight || rowHeight * 8;
|
|
1195
|
-
|
|
1196
|
-
|
|
1521
|
+
if (this.opts.variableItemHeight) {
|
|
1522
|
+
while (start < this.rows.length && offsets[start + 1] < scrollTop) start += 1;
|
|
1523
|
+
start = Math.max(0, start - VIRTUAL_BUFFER);
|
|
1524
|
+
end = start;
|
|
1525
|
+
const target = scrollTop + viewport + VIRTUAL_BUFFER * rowHeight;
|
|
1526
|
+
while (end < this.rows.length && offsets[end] < target) end += 1;
|
|
1527
|
+
} else {
|
|
1528
|
+
start = Math.max(0, Math.floor(scrollTop / rowHeight) - VIRTUAL_BUFFER);
|
|
1529
|
+
end = Math.min(this.rows.length, start + Math.ceil(viewport / rowHeight) + VIRTUAL_BUFFER * 2);
|
|
1530
|
+
}
|
|
1197
1531
|
const topSpacer = document.createElement("li");
|
|
1198
1532
|
topSpacer.className = "forge-select__spacer";
|
|
1199
1533
|
topSpacer.setAttribute("aria-hidden", "true");
|
|
1200
|
-
topSpacer.style.height = `${start
|
|
1534
|
+
topSpacer.style.height = `${offsets?.[start] ?? this.rowOffset(start)}px`;
|
|
1201
1535
|
this.list.append(topSpacer);
|
|
1202
1536
|
}
|
|
1537
|
+
const appended = [];
|
|
1203
1538
|
for (let i = start; i < end; i++) {
|
|
1204
|
-
this.
|
|
1539
|
+
const element = this.renderRow(this.rows[i]);
|
|
1540
|
+
this.list.append(element);
|
|
1541
|
+
appended.push(element);
|
|
1542
|
+
}
|
|
1543
|
+
if (this.opts.variableItemHeight) {
|
|
1544
|
+
for (let i = start; i < end; i++) {
|
|
1545
|
+
const element = appended[i - start];
|
|
1546
|
+
const measured = element.getBoundingClientRect().height || element.offsetHeight;
|
|
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
|
+
}
|
|
1552
|
+
}
|
|
1205
1553
|
}
|
|
1206
1554
|
if (virtual) {
|
|
1207
1555
|
const bottomSpacer = document.createElement("li");
|
|
1208
1556
|
bottomSpacer.className = "forge-select__spacer";
|
|
1209
1557
|
bottomSpacer.setAttribute("aria-hidden", "true");
|
|
1210
|
-
bottomSpacer.style.height = `${
|
|
1558
|
+
bottomSpacer.style.height = `${offsets ? offsets[this.rows.length] - offsets[end] : this.rowOffset(this.rows.length) - this.rowOffset(end)}px`;
|
|
1211
1559
|
this.list.append(bottomSpacer);
|
|
1212
1560
|
if (this.list.scrollTop !== scrollTop) {
|
|
1213
1561
|
this.list.scrollTop = scrollTop;
|
|
@@ -1274,6 +1622,7 @@ var ForgeSelect = class {
|
|
|
1274
1622
|
if (isSelected) li.classList.add("forge-select__option--selected");
|
|
1275
1623
|
if (this.opts.multiple && row.hasChildren && computeCheckState(row.option, this.selected, this.isOptionDisabled) === "some") {
|
|
1276
1624
|
li.classList.add("forge-select__option--indeterminate");
|
|
1625
|
+
li.dataset.selectionState = "mixed";
|
|
1277
1626
|
}
|
|
1278
1627
|
if (row.depth > 0) {
|
|
1279
1628
|
li.style.paddingLeft = `calc(12px + ${row.depth} * var(--fs-tree-indent, 18px))`;
|
|
@@ -1309,6 +1658,28 @@ var ForgeSelect = class {
|
|
|
1309
1658
|
* cached content state-free.
|
|
1310
1659
|
*/
|
|
1311
1660
|
optionContent(option) {
|
|
1661
|
+
if (this.opts.highlightSearch && this.query.trim() && !this.opts.templateResult) {
|
|
1662
|
+
const holder = document.createElement("span");
|
|
1663
|
+
holder.className = "forge-select__option-content";
|
|
1664
|
+
renderOptionContent(holder, option, void 0);
|
|
1665
|
+
const label = holder.querySelector(".forge-select__option-label") ?? holder;
|
|
1666
|
+
const ranges = findNormalizedRanges(option.label, this.query, this.opts.accentInsensitive);
|
|
1667
|
+
if (ranges.length) {
|
|
1668
|
+
label.textContent = "";
|
|
1669
|
+
let cursor = 0;
|
|
1670
|
+
for (const [start, end] of ranges) {
|
|
1671
|
+
if (start < cursor) continue;
|
|
1672
|
+
label.append(document.createTextNode(option.label.slice(cursor, start)));
|
|
1673
|
+
const mark = document.createElement("mark");
|
|
1674
|
+
mark.className = "forge-select__match";
|
|
1675
|
+
mark.textContent = option.label.slice(start, end);
|
|
1676
|
+
label.append(mark);
|
|
1677
|
+
cursor = end;
|
|
1678
|
+
}
|
|
1679
|
+
label.append(document.createTextNode(option.label.slice(cursor)));
|
|
1680
|
+
}
|
|
1681
|
+
return holder;
|
|
1682
|
+
}
|
|
1312
1683
|
let cached = this.rowContentCache.get(option.value);
|
|
1313
1684
|
if (!cached) {
|
|
1314
1685
|
const holder = document.createElement("span");
|
|
@@ -1329,14 +1700,15 @@ var ForgeSelect = class {
|
|
|
1329
1700
|
this.focusNavIndex(next);
|
|
1330
1701
|
}
|
|
1331
1702
|
focusNavIndex(next) {
|
|
1703
|
+
if (this.navItems.length === 0) return;
|
|
1332
1704
|
this.highlightedIndex = next;
|
|
1333
1705
|
if (this.usesVirtualScroll()) {
|
|
1334
1706
|
const rowIndex = this.rows.findIndex(
|
|
1335
1707
|
(row) => (row.kind === "option" || row.kind === "create") && row.navIndex === next
|
|
1336
1708
|
);
|
|
1337
1709
|
if (rowIndex >= 0) {
|
|
1338
|
-
const rowHeight = this.
|
|
1339
|
-
const top = rowIndex
|
|
1710
|
+
const rowHeight = this.measuredRowHeight(rowIndex);
|
|
1711
|
+
const top = this.rowOffset(rowIndex);
|
|
1340
1712
|
const viewport = this.list.clientHeight || rowHeight * 8;
|
|
1341
1713
|
let target = this.list.scrollTop;
|
|
1342
1714
|
if (top < target) target = top;
|
|
@@ -1401,7 +1773,7 @@ var ForgeSelect = class {
|
|
|
1401
1773
|
this.ajaxController = null;
|
|
1402
1774
|
this.page = 0;
|
|
1403
1775
|
this.hasMore = true;
|
|
1404
|
-
this.
|
|
1776
|
+
this.setLoading(true);
|
|
1405
1777
|
this.loadingMore = false;
|
|
1406
1778
|
this.loadError = null;
|
|
1407
1779
|
this.renderList();
|
|
@@ -1410,6 +1782,55 @@ var ForgeSelect = class {
|
|
|
1410
1782
|
void this.loadRemote(query, { requestId });
|
|
1411
1783
|
}, delay);
|
|
1412
1784
|
}
|
|
1785
|
+
setLoading(loading) {
|
|
1786
|
+
if (this.loading === loading) return;
|
|
1787
|
+
this.loading = loading;
|
|
1788
|
+
this.emitter.emit("loading", loading);
|
|
1789
|
+
}
|
|
1790
|
+
remoteCacheKey(query, page) {
|
|
1791
|
+
return `${query}\0${page}`;
|
|
1792
|
+
}
|
|
1793
|
+
async requestRemote(query, page, signal) {
|
|
1794
|
+
const ajax = this.opts.ajax;
|
|
1795
|
+
const attempts = Math.max(0, Math.floor(ajax.retry ?? 0)) + 1;
|
|
1796
|
+
let lastError;
|
|
1797
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1798
|
+
try {
|
|
1799
|
+
if (ajax.request) return await ajax.request(query, page, signal);
|
|
1800
|
+
const response = await fetch(buildUrl(ajax, query, page), { signal });
|
|
1801
|
+
if (response.ok === false) throw new Error(`ForgeSelect: remote request failed with HTTP ${response.status}`);
|
|
1802
|
+
return await response.json();
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
lastError = error;
|
|
1805
|
+
if (signal.aborted || attempt === attempts - 1) throw error;
|
|
1806
|
+
const delay = Math.max(0, ajax.retryDelay ?? 250) * 2 ** attempt;
|
|
1807
|
+
await new Promise((resolve, reject) => {
|
|
1808
|
+
const timer = setTimeout(resolve, delay);
|
|
1809
|
+
signal.addEventListener(
|
|
1810
|
+
"abort",
|
|
1811
|
+
() => {
|
|
1812
|
+
clearTimeout(timer);
|
|
1813
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
1814
|
+
},
|
|
1815
|
+
{ once: true }
|
|
1816
|
+
);
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
throw lastError;
|
|
1821
|
+
}
|
|
1822
|
+
async prefetchRemote(query) {
|
|
1823
|
+
const ajax = this.opts.ajax;
|
|
1824
|
+
if (!ajax || (ajax.cacheTtl ?? 3e4) <= 0) return;
|
|
1825
|
+
const key = this.remoteCacheKey(query, 0);
|
|
1826
|
+
if (this.remoteCache.get(key)) return;
|
|
1827
|
+
const controller = new AbortController();
|
|
1828
|
+
try {
|
|
1829
|
+
const json = await this.requestRemote(query, 0, controller.signal);
|
|
1830
|
+
this.remoteCache.set(key, normalizeRemoteResult(ajax, json), ajax.cacheTtl ?? 3e4);
|
|
1831
|
+
} catch {
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1413
1834
|
/**
|
|
1414
1835
|
* Fires on every list scroll. Only acts when pagination is opted into via
|
|
1415
1836
|
* `ajax.pagination`; reads real scroll geometry rather than row counts so
|
|
@@ -1434,23 +1855,22 @@ var ForgeSelect = class {
|
|
|
1434
1855
|
this.ajaxController = controller;
|
|
1435
1856
|
const page = append ? this.page + 1 : 0;
|
|
1436
1857
|
try {
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
if (response.ok === false) throw new Error(`ForgeSelect: remote request failed with HTTP ${response.status}`);
|
|
1444
|
-
json = await response.json();
|
|
1858
|
+
const key = this.remoteCacheKey(query, page);
|
|
1859
|
+
let result = this.remoteCache.get(key);
|
|
1860
|
+
if (!result) {
|
|
1861
|
+
const json = await this.requestRemote(query, page, controller.signal);
|
|
1862
|
+
result = normalizeRemoteResult(ajax, json);
|
|
1863
|
+
this.remoteCache.set(key, result, ajax.cacheTtl ?? 3e4);
|
|
1445
1864
|
}
|
|
1446
1865
|
if (activeRequestId !== this.ajaxRequestId || this.destroyed) return;
|
|
1447
|
-
const { options, hasMore } =
|
|
1866
|
+
const { options, hasMore } = result;
|
|
1448
1867
|
if (append) {
|
|
1449
1868
|
const existing = collectValues(this.data);
|
|
1450
1869
|
this.data = [...this.data, ...options.filter((o) => !existing.has(o.value))];
|
|
1451
1870
|
} else {
|
|
1452
1871
|
this.data = options;
|
|
1453
1872
|
this.rowContentCache.clear();
|
|
1873
|
+
this.rowHeightCache.clear();
|
|
1454
1874
|
}
|
|
1455
1875
|
this.page = page;
|
|
1456
1876
|
this.hasMore = hasMore;
|
|
@@ -1462,6 +1882,7 @@ var ForgeSelect = class {
|
|
|
1462
1882
|
if (!append) {
|
|
1463
1883
|
this.data = [];
|
|
1464
1884
|
this.rowContentCache.clear();
|
|
1885
|
+
this.rowHeightCache.clear();
|
|
1465
1886
|
}
|
|
1466
1887
|
this.hasMore = false;
|
|
1467
1888
|
this.loadError = error;
|
|
@@ -1469,7 +1890,7 @@ var ForgeSelect = class {
|
|
|
1469
1890
|
} finally {
|
|
1470
1891
|
if (activeRequestId === this.ajaxRequestId && !this.destroyed) {
|
|
1471
1892
|
this.ajaxController = null;
|
|
1472
|
-
this.
|
|
1893
|
+
this.setLoading(false);
|
|
1473
1894
|
this.loadingMore = false;
|
|
1474
1895
|
if (this.isOpen) this.renderList();
|
|
1475
1896
|
}
|