@lavalogic/scoria 0.20.0 → 0.21.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.
@@ -1,7 +1,7 @@
1
1
  import { browser, dev } from '$app/environment';
2
- import { asyncAll, asyncForEach, asyncMap, asyncSleep, devCatch, getCanvasContext, } from '../../../../Helpers/Helpers.svelte.js';
2
+ import { asyncAll, asyncForEach, asyncMap, devCatch, getCanvasContext, } from '../../../../Helpers/Helpers.svelte.js';
3
3
  import { tick, untrack } from 'svelte';
4
- import { SvelteMap, SvelteSet } from 'svelte/reactivity';
4
+ import { SvelteMap } from 'svelte/reactivity';
5
5
  import { ColumnType } from '../Columns/ColumnType.js';
6
6
  import { AccessorDef } from '../Columns/Definitions/Accessors/AccessorDef.svelte.js';
7
7
  import { CheckboxDef } from '../Columns/Definitions/Accessors/CheckboxDef.svelte.js';
@@ -69,16 +69,17 @@ export class TableContext {
69
69
  // this.dataRepo.forceRefresh = this.forceVisibilityUpdate;
70
70
  $effect(() => {
71
71
  void this.currentPageData;
72
- void this.selectedItems;
73
- void this.paginationRepo?.paginationState.pageIndex;
74
- untrack(async () => {
75
- await this.currentPageData;
72
+ // void this.selectedItems;
73
+ untrack(() => {
76
74
  if (this.paginationRepo?.paginationState.pageIndex != undefined) {
77
- await this.updateSelectedItems();
78
- this.resetHighlightedRows();
79
- this.recalculateAllSelectedInCurrentPage();
75
+ this.updateSelectedItems()
76
+ .then(() => {
77
+ this.resetHighlightedRows();
78
+ })
79
+ .catch(devCatch);
80
+ // this.recalculateAllSelectedInCurrentPage();
80
81
  }
81
- }).catch(devCatch);
82
+ });
82
83
  });
83
84
  let filteredIndicesAbortController = null;
84
85
  $effect(() => {
@@ -100,7 +101,7 @@ export class TableContext {
100
101
  });
101
102
  }
102
103
  else {
103
- await asyncSleep(300);
104
+ // await asyncSleep(300);
104
105
  const filtered = [];
105
106
  for (let index = 0; index < data.length; ++index) {
106
107
  if (currentController.signal.aborted) {
@@ -134,7 +135,182 @@ export class TableContext {
134
135
  }
135
136
  this._filteredIndices = filtered;
136
137
  }
137
- })().catch(devCatch);
138
+ })().catch((e) => {
139
+ devCatch(e);
140
+ });
141
+ });
142
+ let lastIncrement = Number.MIN_SAFE_INTEGER;
143
+ $effect(() => {
144
+ void this.dataRepo.data.length;
145
+ void this.selectedItems;
146
+ void this.paginationRepo?.pageStartIndex;
147
+ const invalidValues = this._invalidValues;
148
+ const allSelectedInCurrentPage = [...this.selectedItems.keys()];
149
+ const currentIncrement = ++lastIncrement;
150
+ void this.allSelectedInCurrentPage;
151
+ const isUpToDate = lastIncrement === currentIncrement;
152
+ if (isUpToDate) {
153
+ this.hasInvalidSelectedItems = allSelectedInCurrentPage.some((key) => {
154
+ return invalidValues.has(key);
155
+ });
156
+ }
157
+ else {
158
+ if (dev) {
159
+ console.log('skipping recalculation due to stale data');
160
+ }
161
+ }
162
+ });
163
+ let latest_pageColData = 0;
164
+ $effect(() => {
165
+ const current = (latest_pageColData = latest_pageColData + 1);
166
+ if (!this.focusStart || !this.focusEnd) {
167
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
168
+ this._focusedRowIndices = new Set();
169
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
170
+ this._focusedColumnIds = new Set();
171
+ return;
172
+ }
173
+ const { row: startRowIndexId, column: startColumnIndex } = this.focusStart;
174
+ const { row: endRowIndexId, column: endColumnIndex } = this.focusEnd;
175
+ const model = this.sortedRowModel;
176
+ if (current === latest_pageColData) {
177
+ const startRowIndex = model.rows.findIndex((row) => row.visibleIndex === startRowIndexId);
178
+ const endRowIndex = model.rows.findIndex((row) => row.visibleIndex === endRowIndexId);
179
+ if (startRowIndex == -1 || endRowIndex == -1) {
180
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
181
+ this._focusedRowIndices = new Set();
182
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
183
+ this._focusedColumnIds = new Set();
184
+ return;
185
+ }
186
+ const minRowIndex = Math.min(startRowIndex, endRowIndex);
187
+ const maxRowIndex = Math.max(startRowIndex, endRowIndex);
188
+ const minColumnIndex = Math.min(startColumnIndex, endColumnIndex);
189
+ const maxColumnIndex = Math.max(startColumnIndex, endColumnIndex);
190
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
191
+ const affectedColumns = new Set();
192
+ const visibility = this.columnVisibility;
193
+ this.columnDefs
194
+ .filter((it) => 'index' in it &&
195
+ visibility[it.id] !== false &&
196
+ // TODO handle pinning behaviour
197
+ // !columnPinning.left?.includes(it.id) &&
198
+ // !columnPinning.right?.includes(it.id) &&
199
+ // (!('columnType' in it) || it.columnType === undefined || it.columnType === 'Accessor') &&
200
+ it.index >= minColumnIndex &&
201
+ it.index <= maxColumnIndex)
202
+ .forEach((it) => affectedColumns.add(it.id));
203
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
204
+ const indices = new Set();
205
+ for (let i = minRowIndex; i <= maxRowIndex; i++) {
206
+ indices.add(parseInt(model.rows[i].id));
207
+ }
208
+ this._focusedRowIndices = indices;
209
+ this._focusedColumnIds = affectedColumns;
210
+ }
211
+ });
212
+ let latest_paginationRowModel = 0;
213
+ $effect(() => {
214
+ const current = (latest_paginationRowModel = latest_paginationRowModel + 1);
215
+ const rows = this.rowsPromise;
216
+ if (current === latest_paginationRowModel) {
217
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
218
+ const rowsById = $derived(new Map(rows.map((it, index) => [index.toString(), it])));
219
+ this._paginationRowModel = {
220
+ rows,
221
+ get rowsById() {
222
+ return rowsById;
223
+ }, //FIXME should this be by sorted id or by original id?
224
+ };
225
+ }
226
+ });
227
+ let latest_sortedRowModel = 0;
228
+ $effect(() => {
229
+ void this.originalRowIndices;
230
+ void this.sortedData;
231
+ // if (!$effect.tracking()) {
232
+ // throw new Error(`filtered indices are not in a tracking context`);
233
+ // }
234
+ const current = (latest_sortedRowModel = latest_sortedRowModel + 1);
235
+ const sd = this.sortedData;
236
+ const ori = this.originalRowIndices;
237
+ if (current !== latest_sortedRowModel || !ori.size) {
238
+ return;
239
+ }
240
+ Promise.resolve(asyncMap(sd, (item, index) => {
241
+ const rawKey = this._cachedKey(item);
242
+ const finish = (key) => {
243
+ const originalIndex = ori.get(key);
244
+ const unsortedIndex = originalIndex ?? index;
245
+ if (current !== latest_sortedRowModel) {
246
+ throw new Error(`stale data`);
247
+ }
248
+ if (originalIndex == null) {
249
+ throw new Error(`Sorted Data not found in Original Row Indices: original index ${originalIndex}, index ${index}`);
250
+ }
251
+ return this.createRow(item, index, unsortedIndex);
252
+ };
253
+ if (rawKey instanceof Promise) {
254
+ return rawKey.then(finish);
255
+ }
256
+ return finish(rawKey);
257
+ }))
258
+ .then((rows) => {
259
+ if (current !== latest_sortedRowModel) {
260
+ return;
261
+ }
262
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
263
+ const rowsById = new Map(rows.map((it, index) => [index.toString(), it])); //FIXME should this be by sorted id or by original id?
264
+ this._sortedRowModel = {
265
+ get rows() {
266
+ return rows;
267
+ },
268
+ get rowsById() {
269
+ return rowsById;
270
+ },
271
+ };
272
+ })
273
+ .catch(devCatch);
274
+ });
275
+ let latest_originalRowIndices = 0;
276
+ $effect(() => {
277
+ const dataRepo = this.dataRepo;
278
+ const current = (latest_originalRowIndices = latest_originalRowIndices + 1);
279
+ Promise.resolve(asyncMap(dataRepo.data, (item, index) => {
280
+ const raw = this._cachedKey(item);
281
+ if (raw instanceof Promise) {
282
+ return raw.then((key) => [key, index]);
283
+ }
284
+ return [raw, index];
285
+ }))
286
+ .then((mapped) => {
287
+ if (current === latest_originalRowIndices) {
288
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
289
+ this._originalRowIndices = new Map(mapped);
290
+ }
291
+ })
292
+ .catch(devCatch);
293
+ });
294
+ $effect(() => {
295
+ if (this.highlightedItems.size === 0) {
296
+ this._allHighlighted = false;
297
+ return;
298
+ }
299
+ const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
300
+ const pageEndIndexExclusive = this.paginationRepo
301
+ ? Math.min(this.paginationRepo.pageEndIndexExclusive, this.sortedData.length)
302
+ : this.sortedData.length;
303
+ const allHighlightedResult = this._areAllHighlighted(pageStartIndex, pageEndIndexExclusive);
304
+ if (allHighlightedResult instanceof Promise) {
305
+ allHighlightedResult
306
+ .then((flag) => {
307
+ this._allHighlighted = flag;
308
+ })
309
+ .catch(devCatch);
310
+ }
311
+ else {
312
+ this._allHighlighted = allHighlightedResult;
313
+ }
138
314
  });
139
315
  if (defaultColumnDefs.remoteFilters) {
140
316
  this.remoteFilters = defaultColumnDefs.remoteFilters;
@@ -251,11 +427,23 @@ export class TableContext {
251
427
  this._remoteFilters = v;
252
428
  }
253
429
  _showSelectedOnly = $state(false);
430
+ _pageBeforeSelectedOnly = undefined;
254
431
  get showSelectedOnly() {
255
432
  return this._showSelectedOnly;
256
433
  }
257
434
  set showSelectedOnly(v) {
435
+ if (v === this._showSelectedOnly) {
436
+ return;
437
+ }
258
438
  this._showSelectedOnly = v;
439
+ if (v) {
440
+ this._pageBeforeSelectedOnly = (this.paginationRepo?.paginationState.pageIndex ?? 0) + 1;
441
+ this.paginationRepo?.setPage(1);
442
+ }
443
+ else if (this._pageBeforeSelectedOnly != null) {
444
+ this.paginationRepo?.setPage(this._pageBeforeSelectedOnly);
445
+ this._pageBeforeSelectedOnly = undefined;
446
+ }
259
447
  }
260
448
  dateFilter = $derived(this.paginationRepo?.dateFilter);
261
449
  numberFilter = $derived(this.paginationRepo?.numberFilter);
@@ -298,52 +486,20 @@ export class TableContext {
298
486
  set selectedPreset(v) {
299
487
  this._selectedPreset = v;
300
488
  }
301
- _pageColData = $derived.by(async () => {
302
- if (!this.focusStart || !this.focusEnd) {
303
- return { indices: new SvelteSet(), affectedColumns: new SvelteSet() };
304
- }
305
- const { row: startRowIndexId, column: startColumnIndex } = this.focusStart;
306
- const { row: endRowIndexId, column: endColumnIndex } = this.focusEnd;
307
- const model = await this.sortedRowModel;
308
- const startRowIndex = model.rows.findIndex((row) => row.visibleIndex === startRowIndexId);
309
- const endRowIndex = model.rows.findIndex((row) => row.visibleIndex === endRowIndexId);
310
- if (startRowIndex == -1 || endRowIndex == -1) {
311
- return { indices: new SvelteSet(), affectedColumns: new SvelteSet() };
312
- }
313
- const minRowIndex = Math.min(startRowIndex, endRowIndex);
314
- const maxRowIndex = Math.max(startRowIndex, endRowIndex);
315
- const minColumnIndex = Math.min(startColumnIndex, endColumnIndex);
316
- const maxColumnIndex = Math.max(startColumnIndex, endColumnIndex);
317
- const affectedColumns = new SvelteSet();
318
- const visibility = this.columnVisibility;
319
- this.columnDefs
320
- .filter((it) => 'index' in it &&
321
- visibility[it.id] !== false &&
322
- // TODO handle pinning behaviour
323
- // !columnPinning.left?.includes(it.id) &&
324
- // !columnPinning.right?.includes(it.id) &&
325
- // (!('columnType' in it) || it.columnType === undefined || it.columnType === 'Accessor') &&
326
- it.index >= minColumnIndex &&
327
- it.index <= maxColumnIndex)
328
- .forEach((it) => affectedColumns.add(it.id));
329
- const indices = new SvelteSet();
330
- for (let i = minRowIndex; i <= maxRowIndex; i++) {
331
- indices.add(parseInt(model.rows[i].id));
332
- }
333
- return { indices, affectedColumns };
334
- });
335
- focusedRowIndices = $derived.by(async () => {
336
- void this._pageColData;
337
- return (await this._pageColData).indices;
338
- });
489
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
490
+ _focusedRowIndices = $state(new Set());
491
+ get focusedRowIndices() {
492
+ return this._focusedRowIndices;
493
+ }
339
494
  _highlightedItems = $state(new SvelteMap());
340
495
  get highlightedItems() {
341
496
  return this._highlightedItems;
342
497
  }
343
- focusedColumnIds = $derived.by(async () => {
344
- void this._pageColData;
345
- return (await this._pageColData).affectedColumns;
346
- });
498
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
499
+ _focusedColumnIds = $state(new Set());
500
+ get focusedColumnIds() {
501
+ return this._focusedColumnIds;
502
+ }
347
503
  _isMousingDown = $state(false);
348
504
  get isMousingDown() {
349
505
  return this._isMousingDown;
@@ -442,64 +598,113 @@ export class TableContext {
442
598
  set openModalCoordinates(v) {
443
599
  this._openModalCoordinates = v;
444
600
  }
445
- _allSelectedInCurrentPage = $derived.by(async () => {
601
+ _allSelectedAsync = $state(false);
602
+ _allSelectedInCurrentPage = $derived.by(() => {
603
+ void this.columnDefs;
446
604
  if (this.selectedItems.size === 0) {
447
- return Promise.resolve(false);
605
+ return false;
448
606
  }
449
- void this.paginationRepo?.pageStartIndex;
450
- void this.columnDefs;
451
- const sortedData = await this.sortedData;
607
+ const sortedData = this.sortedData;
452
608
  const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
453
609
  const pageEndIndexExclusive = this.paginationRepo
454
610
  ? Math.min(this.paginationRepo.pageEndIndexExclusive, sortedData.length)
455
611
  : sortedData.length;
456
612
  const selectableMethod = this.columnDefs.find((it) => it instanceof RowSelectionDef)?.isSelectable;
457
- return await this.allSelectedBySelectableMethod(pageStartIndex, pageEndIndexExclusive, selectableMethod);
613
+ const result = this.allSelectedBySelectableMethod(pageStartIndex, pageEndIndexExclusive, selectableMethod);
614
+ if (result instanceof Promise) {
615
+ result
616
+ .then((flag) => {
617
+ this._allSelectedAsync = flag;
618
+ })
619
+ .catch(devCatch);
620
+ return this._allSelectedAsync;
621
+ }
622
+ return result;
458
623
  });
459
624
  get allSelectedInCurrentPage() {
460
625
  return this._allSelectedInCurrentPage;
461
626
  }
462
- async allSelectedBySelectableMethod(pageStartIndex, pageEndIndexExclusive, selectableMethod) {
463
- const sortedData = await this.sortedData;
627
+ allSelectedBySelectableMethod(pageStartIndex, pageEndIndexExclusive, selectableMethod) {
628
+ const sortedData = this.sortedData;
629
+ const emptyPromise = new Promise(() => { });
464
630
  if (selectableMethod) {
465
- for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
466
- const item = sortedData[index];
467
- if ((await selectableMethod(item, index)) &&
468
- !this.selectedItems.has(await this.selectionExtractor(item))) {
469
- return Promise.resolve(false);
631
+ const pending = [];
632
+ for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
633
+ const item = sortedData[i];
634
+ const relIdx = i - pageStartIndex;
635
+ const raw = this._cachedKey(item);
636
+ const checkUnselected = (key) => {
637
+ const selectable = selectableMethod(item, relIdx);
638
+ if (selectable instanceof Promise) {
639
+ return selectable.then((s) => s && !this.selectedItems.has(key));
640
+ }
641
+ return selectable && !this.selectedItems.has(key);
642
+ };
643
+ const r = raw instanceof Promise ? raw.then(checkUnselected) : checkUnselected(raw);
644
+ if (r === true) {
645
+ return false;
646
+ }
647
+ if (r instanceof Promise) {
648
+ pending.push(r);
470
649
  }
471
650
  }
651
+ if (pending.length === 0) {
652
+ return true;
653
+ }
654
+ return Promise.race([
655
+ Promise.race(pending.map(async (p) => ((await p) ? false : emptyPromise))),
656
+ Promise.all(pending).then((results) => {
657
+ return !results.some(Boolean);
658
+ }),
659
+ ]);
472
660
  }
473
661
  else {
474
- for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
475
- const item = sortedData[index];
476
- if (!this.selectedItems.has(await this.selectionExtractor(item))) {
477
- return Promise.resolve(false);
662
+ const pending = [];
663
+ for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
664
+ const raw = this._cachedKey(sortedData[i]);
665
+ if (raw instanceof Promise) {
666
+ pending.push(raw.then((key) => !this.selectedItems.has(key)));
667
+ }
668
+ else if (!this.selectedItems.has(raw)) {
669
+ return false;
478
670
  }
479
671
  }
672
+ if (pending.length === 0) {
673
+ return true;
674
+ }
675
+ return Promise.race([
676
+ Promise.race(pending.map(async (p) => ((await p) ? false : emptyPromise))),
677
+ Promise.all(pending).then((results) => !results.some(Boolean)),
678
+ ]);
480
679
  }
481
- return Promise.resolve(true);
482
680
  }
483
- async _areAllHighlighted(pageStartIndex, pageEndIndexExclusive) {
484
- const sortedData = await this.sortedData;
681
+ _areAllHighlighted(pageStartIndex, pageEndIndexExclusive) {
682
+ const sortedData = this.sortedData;
485
683
  for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
486
684
  const item = sortedData[index];
487
- if (!this.highlightedItems.has(await this.selectionExtractor(item))) {
488
- return Promise.resolve(false);
685
+ const raw = this._cachedKey(item);
686
+ if (raw instanceof Promise) {
687
+ return raw.then(async (key) => {
688
+ if (!this.highlightedItems.has(key)) {
689
+ return false;
690
+ }
691
+ for (let j = index + 1; j < pageEndIndexExclusive; j++) {
692
+ const r = this._cachedKey(sortedData[j]);
693
+ const k = r instanceof Promise ? await r : r;
694
+ if (!this.highlightedItems.has(k)) {
695
+ return false;
696
+ }
697
+ }
698
+ return true;
699
+ });
700
+ }
701
+ if (!this.highlightedItems.has(raw)) {
702
+ return false;
489
703
  }
490
704
  }
491
- return Promise.resolve(true);
705
+ return true;
492
706
  }
493
- _allHighlighted = $derived.by(async () => {
494
- if (this.highlightedItems.size === 0) {
495
- return Promise.resolve(false);
496
- }
497
- const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
498
- const pageEndIndexExclusive = this.paginationRepo
499
- ? Math.min(this.paginationRepo.pageEndIndexExclusive, (await this.sortedData).length)
500
- : (await this.sortedData).length;
501
- return await this._areAllHighlighted(pageStartIndex, pageEndIndexExclusive);
502
- });
707
+ _allHighlighted = $state(false);
503
708
  get allHighlighted() {
504
709
  return this._allHighlighted;
505
710
  }
@@ -527,7 +732,8 @@ export class TableContext {
527
732
  this._hasInvalidSelectedItems = v;
528
733
  }
529
734
  // FIXME decide
530
- columnDefsById = $derived(new SvelteMap(this.columnDefs.map((it) => [it.id, it])));
735
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
736
+ columnDefsById = $derived(new Map(this.columnDefs.map((it) => [it.id, it])));
531
737
  getSortingFn = (id, desc) => {
532
738
  if (this.dataRepo.data.length == 0) {
533
739
  return undefined;
@@ -560,11 +766,14 @@ export class TableContext {
560
766
  get filteredIndices() {
561
767
  return this._filteredIndices;
562
768
  }
563
- filteredData = $derived.by(async () => {
769
+ filteredData = $derived.by(() => {
564
770
  void this.dataRepo.data;
565
771
  void this.filteredIndices;
566
772
  void this.showSelectedOnly;
567
- void this.selectedItems.size;
773
+ // selectedItems is intentionally NOT read here unconditionally — doing so would
774
+ // invalidate filteredData → sortedData → currentPageData → entire row model on
775
+ // every single toggle. The selectedItems.values() reads inside the showSelectedOnly
776
+ // branches below are sufficient to track reactivity when it actually matters.
568
777
  if (this.paginationRepo?.paginationType == PaginationType.Local) {
569
778
  const data = this.showSelectedOnly
570
779
  ? // FIXME this will show the selected items by order of insertion to the selectedItems map.
@@ -580,7 +789,7 @@ export class TableContext {
580
789
  return this.showSelectedOnly ? [...this.selectedItems.values()] : this.dataRepo.data;
581
790
  });
582
791
  sortedDataAbortController = null;
583
- sortedData = $derived.by(async () => {
792
+ sortedData = $derived.by(() => {
584
793
  // if (!$effect.tracking()) {
585
794
  // throw new Error(`sorted data is not in a tracking context`);
586
795
  // }
@@ -592,7 +801,7 @@ export class TableContext {
592
801
  console.log('sorted data');
593
802
  }
594
803
  if (this.paginationRepo?.paginationType == PaginationType.Local) {
595
- const data = await this.filteredData;
804
+ const data = this.filteredData;
596
805
  if (currentController.signal.aborted) {
597
806
  return [];
598
807
  }
@@ -629,83 +838,51 @@ export class TableContext {
629
838
  }
630
839
  return this.filteredData;
631
840
  });
632
- sortedRowModel = $derived.by(async () => {
633
- void this.originalRowIndices;
634
- void this.sortedData;
635
- // if (!$effect.tracking()) {
636
- // throw new Error(`filtered indices are not in a tracking context`);
637
- // }
638
- const sd = await this.sortedData;
639
- const ori = await this.originalRowIndices;
640
- const rows = await asyncMap(sd, async (item, index) => {
641
- const originalIndex = ori.get(await this.selectionExtractor(item));
642
- const unsortedIndex = originalIndex ?? index;
643
- if (originalIndex == null) {
644
- throw new Error(`Sorted Data not found in Original Row Indices: original index ${originalIndex}, index ${index}`);
645
- }
646
- return this.createRow(item, index, unsortedIndex);
647
- });
648
- const rowsById = $derived(new SvelteMap(rows.map((it, index) => [index.toString(), it]))); //FIXME should this be by sorted id or by original id?
649
- return {
650
- get rows() {
651
- return rows;
652
- },
653
- get rowsById() {
654
- return rowsById;
655
- },
656
- };
657
- });
841
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
842
+ _sortedRowModel = $state({ rows: [], rowsById: new Map() });
843
+ get sortedRowModel() {
844
+ return this._sortedRowModel;
845
+ }
658
846
  CurrentPageDataAbortController = null;
659
- currentPageData = $derived.by(async () => {
660
- // void this.originalRowIndices;
847
+ currentPageData = $derived.by(() => {
661
848
  void this.sortedData;
662
- void this.dataRepo.pageStartIndex;
663
- void this.dataRepo.pageEndIndexExclusive;
664
849
  // if (!$effect.tracking()) {
665
850
  // throw new Error(`Current Page Data is not in a tracking context`);
666
851
  // }
667
852
  this.CurrentPageDataAbortController?.abort();
668
853
  const currentController = (this.CurrentPageDataAbortController = new AbortController());
669
- const sd = await this.sortedData;
854
+ const sd = this.sortedData;
670
855
  if (currentController.signal.aborted) {
671
856
  return [];
672
857
  }
673
- if (this.dataRepo.paginationType != PaginationType.Local) {
858
+ // When showing selected only, bypass pagination and return all filtered rows at once.
859
+ // Only track page indices when we will actually paginate, to avoid unnecessary re-runs
860
+ // while the user is in show-selected-only mode.
861
+ if (this.dataRepo.paginationType != PaginationType.Local || this.showSelectedOnly) {
674
862
  return sd;
675
863
  }
864
+ void this.dataRepo.pageStartIndex;
865
+ void this.dataRepo.pageEndIndexExclusive;
676
866
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
677
867
  if (currentController.signal.aborted) {
678
868
  return [];
679
869
  }
680
870
  return sd.slice(this.dataRepo.pageStartIndex, this.dataRepo.pageEndIndexExclusive);
681
871
  });
682
- // FIXME this is most likely the cuplrit for add pod not working on receipt.
683
- // Tracking changes in a promise of a map is a problem
684
- // worst case fix: turn it into a map of promises
685
- originalRowIndices = $derived.by(async () => {
686
- const dataRepo = this.dataRepo;
687
- const awaited = await asyncMap(dataRepo.data, async (item, index) => [await this.selectionExtractor(item), index]);
688
- return new SvelteMap(awaited);
689
- });
690
- rowsPromiseAbortController = null;
691
- rowsPromise = $derived.by(async () => {
872
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
873
+ _originalRowIndices = $state(new Map());
874
+ get originalRowIndices() {
875
+ return this._originalRowIndices;
876
+ }
877
+ rowsPromise = $derived.by(() => {
692
878
  // void this.originalRowIndices;
693
879
  void this.currentPageData;
694
880
  // void this.columnDefsById;
695
881
  void this.columnVisibility;
696
882
  void this.paginationRepo?.sorting;
697
883
  void this.columnPinning;
698
- this.rowsPromiseAbortController?.abort();
699
- const currentController = (this.rowsPromiseAbortController = new AbortController());
700
- const currentPageData = await this.currentPageData;
701
- if (currentController.signal.aborted) {
702
- return [];
703
- }
704
- const indices = await this.originalRowIndices;
705
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
706
- if (currentController.signal.aborted) {
707
- return [];
708
- }
884
+ const currentPageData = this.currentPageData;
885
+ const indices = this.originalRowIndices;
709
886
  return currentPageData.map((item, index) => {
710
887
  const unsortedIndex = indices.get(item) ?? index;
711
888
  const getValue = $derived.by(() => {
@@ -819,16 +996,68 @@ export class TableContext {
819
996
  return row;
820
997
  });
821
998
  });
822
- paginationRowModel = $derived.by(async () => {
823
- void this.rowsPromise;
824
- const rows = await this.rowsPromise;
825
- return {
826
- rows,
827
- rowsById: new SvelteMap(rows.map((it, index) => [index.toString(), it])), //FIXME should this be by sorted id or by original id?
828
- };
999
+ _paginationRowModel = $state({
1000
+ rows: [],
1001
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
1002
+ rowsById: new Map(),
829
1003
  });
1004
+ get paginationRowModel() {
1005
+ return this._paginationRowModel;
1006
+ }
830
1007
  // #endregion
831
1008
  // #region public methods
1009
+ // Key extraction cache — avoids re-calling selectionExtractor for the same item object.
1010
+ // Only onEditCell explicitly invalidates the cache (when the item is mutated in-place).
1011
+ _keyCache = new WeakMap();
1012
+ _cachedKey(item) {
1013
+ const cached = this._keyCache.get(item);
1014
+ if (cached !== undefined) {
1015
+ return cached;
1016
+ }
1017
+ const raw = this.selectionExtractor(item);
1018
+ if (raw instanceof Promise) {
1019
+ return raw.then((key) => {
1020
+ this._keyCache.set(item, key);
1021
+ return key;
1022
+ });
1023
+ }
1024
+ this._keyCache.set(item, raw);
1025
+ return raw;
1026
+ }
1027
+ // Pending selection changes — buffered and flushed as a single SvelteMap reassignment
1028
+ // to avoid per-toggle reactive cascades.
1029
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
1030
+ _pendingSelectionChanges = new Map();
1031
+ _flushScheduled = false;
1032
+ _flushPendingSelectionChanges() {
1033
+ if (this._pendingSelectionChanges.size === 0) {
1034
+ return;
1035
+ }
1036
+ // Mutate in place to preserve SvelteMap's per-key reactive tracking.
1037
+ // Replacing _selectedItems with a cloned map would invalidate every .has()/.get()
1038
+ // subscriber across all visible rows, not just the changed keys.
1039
+ for (const [key, item] of this._pendingSelectionChanges) {
1040
+ if (item === null) {
1041
+ this.selectedItems.delete(key);
1042
+ }
1043
+ else {
1044
+ this.selectedItems.set(key, item);
1045
+ }
1046
+ }
1047
+ this._pendingSelectionChanges.clear();
1048
+ this._flushScheduled = false;
1049
+ }
1050
+ _schedulePendingFlush() {
1051
+ if (this._flushScheduled) {
1052
+ return;
1053
+ }
1054
+ this._flushScheduled = true;
1055
+ tick()
1056
+ .then(() => {
1057
+ this._flushPendingSelectionChanges();
1058
+ })
1059
+ .catch(devCatch);
1060
+ }
832
1061
  selectionContains = async (item) => {
833
1062
  void item;
834
1063
  void this.isSelectable;
@@ -839,174 +1068,226 @@ export class TableContext {
839
1068
  if (!this.isSelectable) {
840
1069
  return true;
841
1070
  }
842
- const identity = await this.selectionExtractor(item);
1071
+ const rawIdentity = this._cachedKey(item);
1072
+ const identity = rawIdentity instanceof Promise ? await rawIdentity : rawIdentity;
843
1073
  const has = this.selectedItems.has(identity);
844
1074
  return has;
845
1075
  };
846
- addInvalidValue = async (item) => {
847
- this.invalidValues.set(await this.selectionExtractor(item), item);
1076
+ addInvalidValue = (item) => {
1077
+ const raw = this._cachedKey(item);
1078
+ if (raw instanceof Promise) {
1079
+ return raw.then((key) => {
1080
+ this.invalidValues.set(key, item);
1081
+ });
1082
+ }
1083
+ this.invalidValues.set(raw, item);
848
1084
  };
849
- removeInvalidValue = async (item) => {
850
- this.invalidValues.delete(await this.selectionExtractor(item));
1085
+ removeInvalidValue = (item) => {
1086
+ const raw = this._cachedKey(item);
1087
+ if (raw instanceof Promise) {
1088
+ return raw.then((key) => {
1089
+ this.invalidValues.delete(key);
1090
+ });
1091
+ }
1092
+ this.invalidValues.delete(raw);
851
1093
  };
852
1094
  setOpenModalCoordinates = (coordinates) => {
853
1095
  this.openModalCoordinates = coordinates ? { ...coordinates } : null;
854
1096
  };
855
- toggleHighlightedRow = async (item) => {
856
- const key = await this.selectionExtractor(item);
857
- if (this.highlightedItems.has(key)) {
858
- this.highlightedItems.delete(key);
1097
+ toggleHighlightedRow = (item) => {
1098
+ const raw = this._cachedKey(item);
1099
+ if (raw instanceof Promise) {
1100
+ return raw.then((key) => {
1101
+ if (this.highlightedItems.has(key)) {
1102
+ this.highlightedItems.delete(key);
1103
+ }
1104
+ else {
1105
+ this.highlightedItems.set(key, item);
1106
+ }
1107
+ });
1108
+ }
1109
+ if (this.highlightedItems.has(raw)) {
1110
+ this.highlightedItems.delete(raw);
859
1111
  }
860
1112
  else {
861
- this.highlightedItems.set(key, item);
1113
+ this.highlightedItems.set(raw, item);
862
1114
  }
863
1115
  };
864
1116
  resetHighlightedRows = () => {
865
1117
  this.highlightedItems.clear();
866
1118
  };
867
- toggleSelected = async (item) => {
868
- const key = await this.selectionExtractor(item);
869
- if (this.selectedItems.has(key)) {
870
- this.selectedItems.delete(key);
871
- }
872
- else {
873
- this.selectedItems.set(key, item);
1119
+ toggleSelected = (item) => {
1120
+ const raw = this._cachedKey(item);
1121
+ if (raw instanceof Promise) {
1122
+ return raw.then((key) => {
1123
+ const pendingValue = this._pendingSelectionChanges.get(key);
1124
+ const isSelected = pendingValue !== undefined ? pendingValue !== null : this.selectedItems.has(key);
1125
+ this._pendingSelectionChanges.set(key, isSelected ? null : item);
1126
+ this._schedulePendingFlush();
1127
+ });
874
1128
  }
1129
+ const pendingValue = this._pendingSelectionChanges.get(raw);
1130
+ const isSelected = pendingValue !== undefined ? pendingValue !== null : this.selectedItems.has(raw);
1131
+ this._pendingSelectionChanges.set(raw, isSelected ? null : item);
1132
+ this._schedulePendingFlush();
875
1133
  };
876
- setSelected = async (item, selected) => {
877
- const key = await this.selectionExtractor(item);
878
- if (selected) {
879
- this.selectedItems.set(key, item);
880
- }
881
- else {
882
- this.selectedItems.delete(key);
1134
+ setSelected = (item, selected) => {
1135
+ const raw = this._cachedKey(item);
1136
+ if (raw instanceof Promise) {
1137
+ return raw.then((key) => {
1138
+ this._pendingSelectionChanges.set(key, selected ? item : null);
1139
+ this._schedulePendingFlush();
1140
+ });
883
1141
  }
1142
+ this._pendingSelectionChanges.set(raw, selected ? item : null);
1143
+ this._schedulePendingFlush();
884
1144
  };
885
1145
  setSelectionMap = (set) => {
886
1146
  this._selectedItems = set;
887
1147
  };
888
1148
  updateSelectedItems = async () => {
1149
+ if (this.selectedItems.size === 0) {
1150
+ return;
1151
+ }
889
1152
  const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
890
- const sortedData = await this.sortedData;
1153
+ const sortedData = this.sortedData;
891
1154
  const pageEndIndexExclusive = this.paginationRepo
892
1155
  ? Math.min(this.paginationRepo.pageEndIndexExclusive, sortedData.length)
893
1156
  : sortedData.length;
894
- const selectableMethod = this.columnDefs.find((it) => it instanceof RowSelectionDef)?.isSelectable;
895
- const updateSelectedItem = async (item) => {
896
- const key = await this.selectionExtractor(item);
897
- if (this.selectedItems.has(key)) {
898
- //replace selected item with newer version of the same identity
899
- this.selectedItems.set(key, item);
1157
+ // Build key item map for the current page (O(page_size) extractions)
1158
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
1159
+ const pageKeyMap = new Map();
1160
+ const pending = [];
1161
+ for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
1162
+ const item = sortedData[i];
1163
+ const raw = this._cachedKey(item);
1164
+ if (raw instanceof Promise) {
1165
+ pending.push(raw.then((key) => {
1166
+ pageKeyMap.set(key, item);
1167
+ }));
900
1168
  }
901
- };
902
- if (selectableMethod) {
903
- for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
904
- const item = sortedData[index];
905
- if (await selectableMethod(item, index)) {
906
- await updateSelectedItem(item);
907
- }
1169
+ else {
1170
+ pageKeyMap.set(raw, item);
908
1171
  }
909
1172
  }
910
- else {
911
- for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
912
- const item = sortedData[index];
913
- await updateSelectedItem(item);
914
- }
1173
+ if (pending.length > 0) {
1174
+ await Promise.all(pending);
915
1175
  }
916
- };
917
- resetSelectedItems = () => {
1176
+ // Update only selected items that appear on this page (O(selected_size))
918
1177
  for (const key of this.selectedItems.keys()) {
919
- this.selectedItems.delete(key);
1178
+ const newItem = pageKeyMap.get(key);
1179
+ if (newItem !== undefined) {
1180
+ this.selectedItems.set(key, newItem);
1181
+ }
920
1182
  }
921
- this.recalculateAllSelectedInCurrentPage();
922
1183
  };
923
- lastIncrement = Number.MIN_SAFE_INTEGER;
924
- recalculateAllSelectedInCurrentPage = () => {
925
- const invalidValues = this._invalidValues;
926
- const allSelectedInCurrentPage = [...this.selectedItems.keys()];
927
- const currentIncrement = ++this.lastIncrement;
928
- this.allSelectedInCurrentPage
929
- .then(() => {
930
- if (this.lastIncrement == currentIncrement) {
931
- this.hasInvalidSelectedItems = allSelectedInCurrentPage.some((key) => {
932
- return invalidValues.has(key);
933
- });
934
- }
935
- else {
936
- if (dev) {
937
- console.log('skipping recalculation due to stale data');
938
- }
939
- }
940
- })
941
- .catch(devCatch);
1184
+ resetSelectedItems = () => {
1185
+ this._selectedItems = new SvelteMap();
942
1186
  };
943
1187
  selectAllInCurrentPage = async () => {
944
1188
  void this.paginationRepo?.pageStartIndex;
945
- const sortedData = await this.sortedData;
946
- if (await this.allSelectedInCurrentPage) {
947
- const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
948
- const pageEndIndexExclusive = this.paginationRepo
949
- ? Math.min(this.paginationRepo.pageEndIndexExclusive, sortedData.length)
950
- : sortedData.length;
951
- for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
952
- const item = sortedData[index];
953
- this.selectedItems.delete(await this.selectionExtractor(item));
1189
+ const sortedData = this.sortedData;
1190
+ const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
1191
+ const pageEndIndexExclusive = this.paginationRepo
1192
+ ? Math.min(this.paginationRepo.pageEndIndexExclusive, sortedData.length)
1193
+ : sortedData.length;
1194
+ // Will be doing one full reassignment to this.selectedItems to avoid svelteMap redraws
1195
+ const cloned = new SvelteMap(this.selectedItems);
1196
+ const pending = [];
1197
+ if (this.allSelectedInCurrentPage) {
1198
+ for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
1199
+ const raw = this._cachedKey(sortedData[i]);
1200
+ if (raw instanceof Promise) {
1201
+ pending.push(raw.then((key) => {
1202
+ cloned.delete(key);
1203
+ }));
1204
+ }
1205
+ else {
1206
+ cloned.delete(raw);
1207
+ }
954
1208
  }
955
1209
  }
956
1210
  else {
957
- // this.resetSelectedItems();
958
- // const selectedItems = new SvelteSet<number>();
959
- const pageStartIndex = this.paginationRepo ? this.paginationRepo.pageStartIndex : 0;
960
- const pageEndIndexExclusive = this.paginationRepo
961
- ? Math.min(this.paginationRepo.pageEndIndexExclusive, sortedData.length)
962
- : sortedData.length;
963
1211
  const selectableMethod = this.columnDefs.find((it) => it instanceof RowSelectionDef)?.isSelectable;
964
1212
  if (selectableMethod) {
965
- for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
966
- const item = sortedData[index];
967
- if (await selectableMethod(item, index)) {
968
- this.selectedItems.set(await this.selectionExtractor(item), item);
1213
+ for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
1214
+ const item = sortedData[i];
1215
+ const relIdx = i - pageStartIndex;
1216
+ const s = selectableMethod(item, relIdx);
1217
+ const doSet = (ok) => {
1218
+ if (!ok) {
1219
+ return;
1220
+ }
1221
+ const raw = this._cachedKey(item);
1222
+ if (raw instanceof Promise) {
1223
+ return raw.then((key) => {
1224
+ cloned.set(key, item);
1225
+ });
1226
+ }
1227
+ cloned.set(raw, item);
1228
+ };
1229
+ const r = s instanceof Promise ? s.then(doSet) : doSet(s);
1230
+ if (r instanceof Promise) {
1231
+ pending.push(r);
969
1232
  }
970
1233
  }
971
1234
  }
972
1235
  else {
973
- for (let index = pageStartIndex; index < pageEndIndexExclusive; index++) {
974
- const item = sortedData[index];
975
- this.selectedItems.set(await this.selectionExtractor(item), item);
1236
+ for (let i = pageStartIndex; i < pageEndIndexExclusive; i++) {
1237
+ const item = sortedData[i];
1238
+ const raw = this._cachedKey(item);
1239
+ if (raw instanceof Promise) {
1240
+ pending.push(raw.then((key) => {
1241
+ cloned.set(key, item);
1242
+ }));
1243
+ }
1244
+ else {
1245
+ cloned.set(raw, item);
1246
+ }
976
1247
  }
977
1248
  }
978
1249
  }
1250
+ if (pending.length > 0) {
1251
+ await Promise.all(pending);
1252
+ }
1253
+ this._selectedItems = cloned;
979
1254
  };
980
1255
  onHighlightAll = () => {
981
- this.allHighlighted
982
- .then((areAllHighlighted) => {
983
- if (areAllHighlighted) {
984
- this.highlightedItems.clear();
985
- return Promise.resolve();
986
- }
987
- else {
988
- return this.currentPageData.then((items) => {
989
- return asyncForEach(items, async (item) => {
990
- this.highlightedItems.set(await this.selectionExtractor(item), item);
1256
+ if (this.allHighlighted) {
1257
+ this.highlightedItems.clear();
1258
+ return Promise.resolve();
1259
+ }
1260
+ else {
1261
+ const items = this.currentPageData;
1262
+ return asyncForEach(items, (item) => {
1263
+ const raw = this._cachedKey(item);
1264
+ if (raw instanceof Promise) {
1265
+ return raw.then((key) => {
1266
+ this.highlightedItems.set(key, item);
991
1267
  });
992
- });
993
- }
994
- })
995
- .catch(devCatch);
1268
+ }
1269
+ this.highlightedItems.set(raw, item);
1270
+ return;
1271
+ });
1272
+ }
996
1273
  };
997
1274
  onEditCell = async (item, coords, value) => {
998
- const oldKey = await this.selectionExtractor(item);
1275
+ const rawOldKey = this._cachedKey(item);
1276
+ const oldKey = rawOldKey instanceof Promise ? await rawOldKey : rawOldKey;
999
1277
  const wasSelected = this.selectedItems.has(oldKey);
1000
1278
  if (wasSelected) {
1001
1279
  this.selectedItems.delete(oldKey);
1002
1280
  }
1281
+ // Invalidate cache — _onEditCell may mutate the item's key field in-place
1282
+ this._keyCache.delete(item);
1003
1283
  await this._onEditCell?.(item, coords, value);
1004
1284
  if (wasSelected && this.isSelectable) {
1005
- const newKey = await this.selectionExtractor(item);
1285
+ const rawNewKey = this._cachedKey(item);
1286
+ const newKey = rawNewKey instanceof Promise ? await rawNewKey : rawNewKey;
1006
1287
  if (newKey) {
1007
1288
  this.selectedItems.set(newKey, item);
1008
1289
  }
1009
- this.recalculateAllSelectedInCurrentPage();
1290
+ // this.recalculateAllSelectedInCurrentPage();
1010
1291
  }
1011
1292
  };
1012
1293
  handleTdKeydown = (e) => {
@@ -1031,7 +1312,7 @@ export class TableContext {
1031
1312
  this.tabBackward();
1032
1313
  }
1033
1314
  else {
1034
- this.tabForward().catch(devCatch);
1315
+ this.tabForward();
1035
1316
  }
1036
1317
  }
1037
1318
  if (e.target.tagName === 'INPUT' &&
@@ -1058,20 +1339,20 @@ export class TableContext {
1058
1339
  if (!this.allowCopy) {
1059
1340
  return;
1060
1341
  }
1061
- await this.focusToTop();
1342
+ this.focusToTop();
1062
1343
  }
1063
1344
  else {
1064
- await this.moveToTop();
1345
+ this.moveToTop();
1065
1346
  }
1066
1347
  }
1067
1348
  else if (e.getModifierState('Shift')) {
1068
1349
  if (!this.allowCopy) {
1069
1350
  return;
1070
1351
  }
1071
- await this.moveFocusUp();
1352
+ this.moveFocusUp();
1072
1353
  }
1073
1354
  else {
1074
- await this.setFocusUp();
1355
+ this.setFocusUp();
1075
1356
  }
1076
1357
  return;
1077
1358
  }
@@ -1082,20 +1363,20 @@ export class TableContext {
1082
1363
  if (!this.allowCopy) {
1083
1364
  return;
1084
1365
  }
1085
- await this.focusToBottom();
1366
+ this.focusToBottom();
1086
1367
  }
1087
1368
  else {
1088
- await this.moveToBottom();
1369
+ this.moveToBottom();
1089
1370
  }
1090
1371
  }
1091
1372
  else if (e.getModifierState('Shift')) {
1092
1373
  if (!this.allowCopy) {
1093
1374
  return;
1094
1375
  }
1095
- await this.moveFocusDown();
1376
+ this.moveFocusDown();
1096
1377
  }
1097
1378
  else {
1098
- await this.setFocusDown();
1379
+ this.setFocusDown();
1099
1380
  }
1100
1381
  return;
1101
1382
  }
@@ -1157,9 +1438,9 @@ export class TableContext {
1157
1438
  case 'a': {
1158
1439
  if (e.ctrlKey) {
1159
1440
  e.preventDefault();
1160
- await this.moveToTop();
1441
+ this.moveToTop();
1161
1442
  this.moveToStart();
1162
- await this.focusToBottom();
1443
+ this.focusToBottom();
1163
1444
  this.focusToEnd();
1164
1445
  }
1165
1446
  break;
@@ -1253,7 +1534,7 @@ export class TableContext {
1253
1534
  if (!colId) {
1254
1535
  return;
1255
1536
  }
1256
- const currentPageContents = await this.currentPageData;
1537
+ const currentPageContents = this.currentPageData;
1257
1538
  const ctx = getCanvasContext();
1258
1539
  let maxWidth = 0;
1259
1540
  if (columnDef.accessorFn) {
@@ -1332,7 +1613,7 @@ export class TableContext {
1332
1613
  // this.forceVisibilityUpdate();
1333
1614
  };
1334
1615
  copyHighlightedRowsToClipboard = async () => {
1335
- // HACK clipboard does not exist in http environments
1616
+ // NOTE: False Positive. Clipboard does not exist in http environments
1336
1617
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1337
1618
  if (!navigator.clipboard) {
1338
1619
  alert('cannot copy in an insecure connection');
@@ -1341,7 +1622,7 @@ export class TableContext {
1341
1622
  return this._performRowCopy(await this.getHighlightedRowData());
1342
1623
  };
1343
1624
  copyDataToClipboard = async () => {
1344
- // HACK clipboard does not exist in http environments
1625
+ // NOTE: False Positive. Clipboard does not exist in http environments
1345
1626
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1346
1627
  if (!navigator.clipboard) {
1347
1628
  alert('cannot copy in an insecure connection');
@@ -1352,7 +1633,8 @@ export class TableContext {
1352
1633
  _performRowCopy = async ({ items, indicesByItem, focusedElementData, affectedColumns, }) => {
1353
1634
  const focusedColumnIds = affectedColumns.map((it) => encapsulate(typeof it.header === 'string' ? it.header : it.id));
1354
1635
  const value = await (async () => {
1355
- const selectColumns = new SvelteMap();
1636
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
1637
+ const selectColumns = new Map();
1356
1638
  for (let i = affectedColumns.length - 1; i > -1; i--) {
1357
1639
  const columnDef = affectedColumns[i];
1358
1640
  if (columnDef instanceof SelectDef) {
@@ -1426,41 +1708,41 @@ export class TableContext {
1426
1708
  this.focusStart = null;
1427
1709
  this.focusEnd = null;
1428
1710
  };
1429
- focusToTop = async () => {
1711
+ focusToTop = () => {
1430
1712
  if (!this.allowCopy) {
1431
1713
  return;
1432
1714
  }
1433
- return this._moveFocusEndRow(-Infinity);
1715
+ this._moveFocusEndRow(-Infinity);
1434
1716
  };
1435
1717
  focusToBottom = () => {
1436
1718
  if (!this.allowCopy) {
1437
1719
  return;
1438
1720
  }
1439
- return this._moveFocusEndRow(Infinity);
1721
+ this._moveFocusEndRow(Infinity);
1440
1722
  };
1441
- moveToBottom = async () => {
1442
- return this._moveFocusRow(Infinity);
1723
+ moveToBottom = () => {
1724
+ this._moveFocusRow(Infinity);
1443
1725
  };
1444
- moveToTop = async () => {
1445
- return this._moveFocusRow(-Infinity);
1726
+ moveToTop = () => {
1727
+ this._moveFocusRow(-Infinity);
1446
1728
  };
1447
- moveFocusUp = async () => {
1729
+ moveFocusUp = () => {
1448
1730
  if (!this.allowCopy) {
1449
1731
  return;
1450
1732
  }
1451
- return this._moveFocusEndRow(-1);
1733
+ this._moveFocusEndRow(-1);
1452
1734
  };
1453
- setFocusUp = async () => {
1454
- return this._moveFocusRow(-1);
1735
+ setFocusUp = () => {
1736
+ this._moveFocusRow(-1);
1455
1737
  };
1456
- tabForward = async () => {
1738
+ tabForward = () => {
1457
1739
  if (!this.allowCopy) {
1458
1740
  return;
1459
1741
  }
1460
1742
  const defs = this.columnDefs;
1461
1743
  const visibility = this.columnVisibility;
1462
1744
  const indexBounds = defs.length;
1463
- const rowBounds = (await this.sortedData).length;
1745
+ const rowBounds = this.sortedData.length;
1464
1746
  if (!this.focusStart || !rowBounds) {
1465
1747
  return;
1466
1748
  }
@@ -1582,14 +1864,14 @@ export class TableContext {
1582
1864
  }
1583
1865
  //otherwise return without doing anything
1584
1866
  };
1585
- moveFocusDown = async () => {
1867
+ moveFocusDown = () => {
1586
1868
  if (!this.allowCopy) {
1587
1869
  return;
1588
1870
  }
1589
- return this._moveFocusEndRow(1);
1871
+ this._moveFocusEndRow(1);
1590
1872
  };
1591
- setFocusDown = async () => {
1592
- return this._moveFocusRow(1);
1873
+ setFocusDown = () => {
1874
+ this._moveFocusRow(1);
1593
1875
  };
1594
1876
  moveFocusLeft = () => {
1595
1877
  if (!this.allowCopy) {
@@ -1856,7 +2138,7 @@ export class TableContext {
1856
2138
  };
1857
2139
  localStorage.setItem(`${this.userId}-${this.tableName}-settings`, JSON.stringify(settingsObject));
1858
2140
  }
1859
- async _moveFocusEndRow(offset) {
2141
+ _moveFocusEndRow(offset) {
1860
2142
  if (!this.allowCopy) {
1861
2143
  return;
1862
2144
  }
@@ -1868,21 +2150,23 @@ export class TableContext {
1868
2150
  // throw new Error(`_moveFocusEndRow is not in a tracking context.`);
1869
2151
  // }
1870
2152
  const focusEnd = this.focusEnd;
1871
- const rows = (await this.paginationRowModel).rows;
2153
+ const prm = this.paginationRowModel;
2154
+ const rows = prm.rows;
1872
2155
  const endRowIndex = rows.findIndex((row) => row.visibleIndex === focusEnd.row);
1873
2156
  this.focusEnd = {
1874
2157
  ...this.focusEnd,
1875
2158
  row: rows[Math.max(Math.min(endRowIndex + offset, rows.length - 1), 0)].visibleIndex,
1876
2159
  };
1877
2160
  }
1878
- async _moveFocusRow(offset) {
2161
+ _moveFocusRow(offset) {
1879
2162
  if (!this.focusStart) {
1880
2163
  console.warn('no focusStart found');
1881
2164
  return;
1882
2165
  }
1883
2166
  const focusStart = this.focusStart;
1884
- const newEndRowIndex = await (async () => {
1885
- const rows = (await this.paginationRowModel).rows;
2167
+ const newEndRowIndex = (() => {
2168
+ const prm = this.paginationRowModel;
2169
+ const rows = prm.rows;
1886
2170
  const endRowIndex = rows.findIndex((row) => row.visibleIndex === focusStart.row);
1887
2171
  return rows[Math.max(Math.min(endRowIndex + offset, rows.length - 1), 0)].visibleIndex;
1888
2172
  })();
@@ -1890,9 +2174,10 @@ export class TableContext {
1890
2174
  }
1891
2175
  async getHighlightedRowData() {
1892
2176
  const affectedColumns = this.columnDefs.filter(this.columnDefReturnsTextValue);
1893
- const indices = new SvelteMap();
2177
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
2178
+ const indices = new Map();
1894
2179
  const selectedRows = [...this.highlightedItems.values()];
1895
- const sortedData = await this.sortedData;
2180
+ const sortedData = this.sortedData;
1896
2181
  for (const item of selectedRows) {
1897
2182
  const rowIndex = sortedData.indexOf(item);
1898
2183
  if (rowIndex == -1) {
@@ -1906,7 +2191,8 @@ export class TableContext {
1906
2191
  const rowLength = selectedRows.length;
1907
2192
  for (let rowIndex = 0; rowIndex < rowLength; rowIndex++) {
1908
2193
  const item = selectedRows[rowIndex];
1909
- const key = await this.selectionExtractor(item);
2194
+ const rawKey = this._cachedKey(item);
2195
+ const key = rawKey instanceof Promise ? await rawKey : rawKey;
1910
2196
  selectedCells[rowIndex] = [];
1911
2197
  const targetRow = selectedCells[rowIndex];
1912
2198
  const colLength = affectedColumns.length;
@@ -1949,10 +2235,10 @@ export class TableContext {
1949
2235
  const { row: startRowIndexId, column: startColumnIndex } = this.focusStart;
1950
2236
  const { row: endRowIndexId, column: endColumnIndex } = this.focusEnd;
1951
2237
  // const model = this._currentPageData;
1952
- const model = await this.paginationRowModel;
2238
+ const model = this.paginationRowModel;
1953
2239
  const startRowIndex = model.rows.findIndex((row) => row.visibleIndex === startRowIndexId);
1954
2240
  const endRowIndex = model.rows.findIndex((row) => row.visibleIndex === endRowIndexId);
1955
- if (!(await this.currentPageData).length || startRowIndex == -1 || endRowIndex == -1) {
2241
+ if (!this.currentPageData.length || startRowIndex == -1 || endRowIndex == -1) {
1956
2242
  throw new Error(`copy was missing one of the two indices. First: ${startRowIndex}, second: ${endRowIndex}`);
1957
2243
  }
1958
2244
  const minRowIndex = Math.min(startRowIndex, endRowIndex);
@@ -1965,7 +2251,8 @@ export class TableContext {
1965
2251
  this.columnDefReturnsTextValue(it));
1966
2252
  });
1967
2253
  const selectedRows = [];
1968
- const indices = new SvelteMap();
2254
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
2255
+ const indices = new Map();
1969
2256
  for (let i = minRowIndex; i <= maxRowIndex; i++) {
1970
2257
  const row = model.rows[i];
1971
2258
  const item = row.original;
@@ -1976,10 +2263,11 @@ export class TableContext {
1976
2263
  // TODO somewhere between here and the ending of the copy functionality, we end up with issues where instead of it using the accessor fn, it tries to pick up the select option, and ends up using the value ("1", etc)
1977
2264
  const selectedCells = [];
1978
2265
  const rowLength = selectedRows.length;
1979
- const sortedData = await this.sortedData;
2266
+ const sortedData = this.sortedData;
1980
2267
  for (let rowIndex = 0; rowIndex < rowLength; rowIndex++) {
1981
2268
  const item = selectedRows[rowIndex];
1982
- const key = await this.selectionExtractor(item);
2269
+ const rawKey = this._cachedKey(item);
2270
+ const key = rawKey instanceof Promise ? await rawKey : rawKey;
1983
2271
  selectedCells[rowIndex] = [];
1984
2272
  const targetRow = selectedCells[rowIndex];
1985
2273
  const colLength = affectedColumns.length;
@@ -2019,7 +2307,8 @@ export class TableContext {
2019
2307
  }
2020
2308
  return {
2021
2309
  items: [],
2022
- indicesByItem: new SvelteMap(),
2310
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
2311
+ indicesByItem: new Map(),
2023
2312
  focusedElementData: [],
2024
2313
  affectedColumns: [],
2025
2314
  };
@@ -2049,7 +2338,8 @@ export class TableContext {
2049
2338
  return allDefs;
2050
2339
  };
2051
2340
  assertNoDuplicateIds() {
2052
- const foundIds = new SvelteSet();
2341
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
2342
+ const foundIds = new Set();
2053
2343
  this.defaultColumnDefs.defs.forEach((def) => {
2054
2344
  if (foundIds.has(def.id)) {
2055
2345
  throw new Error(`Duplicate Column Definition ID ${def.id} in table ${this.tableName}`);
@@ -2223,13 +2513,15 @@ export class TableContext {
2223
2513
  }
2224
2514
  _setNewPinningState() {
2225
2515
  this.columnPinning = {
2226
- left: new SvelteMap(this.columnPinning.left &&
2516
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
2517
+ left: new Map(this.columnPinning.left &&
2227
2518
  this.columnDefs
2228
2519
  .filter((col) => {
2229
2520
  return this.columnPinning.left?.has(col.id);
2230
2521
  })
2231
2522
  .map((col, index) => [col.id, index])),
2232
- right: new SvelteMap(this.columnPinning.right &&
2523
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
2524
+ right: new Map(this.columnPinning.right &&
2233
2525
  this.columnDefs
2234
2526
  .filter((col) => this.columnPinning.right?.has(col.id))
2235
2527
  .map((col, index) => [col.id, index])),
@@ -2308,7 +2600,8 @@ function areDefsEquivalent(newColumnDefs, oldColumnDefs) {
2308
2600
  });
2309
2601
  }
2310
2602
  function columnDefsToMap(defs) {
2311
- const map = new SvelteMap();
2603
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity
2604
+ const map = new Map();
2312
2605
  defs.forEach((def) => {
2313
2606
  map.set(def.id, def);
2314
2607
  });