@bexis2/bexis2-core-ui 0.4.96 → 0.4.98

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.
Files changed (86) hide show
  1. package/README.md +104 -25
  2. package/dist/components/CodeEditor/CodeEditor.svelte +2 -2
  3. package/dist/components/Facets/ShowMore.svelte +4 -6
  4. package/dist/components/File/FileUploader.svelte +6 -3
  5. package/dist/components/Table/Table.svelte +26 -5
  6. package/dist/components/Table/TableContent.svelte +222 -22
  7. package/dist/components/Table/TablePaginationServer.svelte +27 -8
  8. package/dist/components/Table/clientDB.d.ts +22 -0
  9. package/dist/components/Table/clientDB.js +236 -0
  10. package/dist/components/Table/filter.js +5 -1
  11. package/dist/components/Table/tableWorker.d.ts +1 -0
  12. package/dist/components/Table/tableWorker.js +179 -0
  13. package/dist/components/Table/utils.d.ts +1 -2
  14. package/dist/components/Table/utils.js +45 -26
  15. package/dist/components/form/CheckboxKvPList.svelte +15 -15
  16. package/dist/components/form/CheckboxList.svelte +1 -1
  17. package/dist/components/form/DateInput.svelte +12 -1
  18. package/dist/components/form/DatePickerInput.svelte +51 -29
  19. package/dist/components/form/Dropdown.svelte +14 -3
  20. package/dist/components/form/DropdownKvP.svelte +13 -2
  21. package/dist/components/form/InputContainer.svelte +17 -15
  22. package/dist/components/form/MultiSelect.svelte +30 -29
  23. package/dist/components/form/NumberInput.svelte +12 -1
  24. package/dist/components/form/TextArea.svelte +12 -1
  25. package/dist/components/form/TextInput.svelte +12 -1
  26. package/dist/components/page/Alert.svelte +5 -1
  27. package/dist/components/page/BackToTop.svelte +3 -1
  28. package/dist/components/page/Docs.svelte +9 -2
  29. package/dist/components/page/GoToTop.svelte +14 -15
  30. package/dist/components/page/Page.svelte +50 -56
  31. package/dist/components/page/breadcrumb/BreadcrumbDataCaller.js +11 -15
  32. package/dist/components/page/menu/MenuAccountBar.svelte +1 -5
  33. package/dist/components/page/menu/MenuDataCaller.js +1 -1
  34. package/dist/components/page/menu/MenuItem.svelte +9 -7
  35. package/dist/components/page/menu/MenuSublist.svelte +7 -5
  36. package/dist/components/page/menu/SettingsBar.svelte +4 -1
  37. package/dist/components/toggle/Toggle.svelte +9 -9
  38. package/dist/css/core.ui.postcss +1 -2
  39. package/dist/services/Api.js +4 -4
  40. package/dist/services/BaseCaller.js +1 -1
  41. package/package.json +114 -114
  42. package/src/lib/components/CodeEditor/CodeEditor.svelte +4 -4
  43. package/src/lib/components/Facets/Facets.svelte +2 -2
  44. package/src/lib/components/Facets/ShowMore.svelte +4 -6
  45. package/src/lib/components/File/FileUploader.svelte +17 -14
  46. package/src/lib/components/Table/Table.svelte +31 -10
  47. package/src/lib/components/Table/TableContent.svelte +258 -33
  48. package/src/lib/components/Table/TableFilter.svelte +7 -2
  49. package/src/lib/components/Table/TablePagination.svelte +6 -2
  50. package/src/lib/components/Table/TablePaginationServer.svelte +36 -10
  51. package/src/lib/components/Table/clientDB.js +236 -0
  52. package/src/lib/components/Table/filter.ts +27 -23
  53. package/src/lib/components/Table/tableWorker.js +179 -0
  54. package/src/lib/components/Table/utils.ts +75 -56
  55. package/src/lib/components/form/Checkbox.svelte +1 -1
  56. package/src/lib/components/form/CheckboxKvPList.svelte +15 -16
  57. package/src/lib/components/form/CheckboxList.svelte +1 -1
  58. package/src/lib/components/form/DateInput.svelte +13 -2
  59. package/src/lib/components/form/DatePickerInput.svelte +51 -29
  60. package/src/lib/components/form/Dropdown.svelte +14 -3
  61. package/src/lib/components/form/DropdownKvP.svelte +13 -2
  62. package/src/lib/components/form/InputContainer.svelte +18 -17
  63. package/src/lib/components/form/MultiSelect.svelte +30 -29
  64. package/src/lib/components/form/NumberInput.svelte +21 -12
  65. package/src/lib/components/form/TextArea.svelte +13 -2
  66. package/src/lib/components/form/TextInput.svelte +13 -2
  67. package/src/lib/components/page/Alert.svelte +5 -1
  68. package/src/lib/components/page/BackToTop.svelte +3 -1
  69. package/src/lib/components/page/Docs.svelte +9 -2
  70. package/src/lib/components/page/GoToTop.svelte +14 -15
  71. package/src/lib/components/page/Notification.svelte +1 -1
  72. package/src/lib/components/page/Page.svelte +67 -78
  73. package/src/lib/components/page/breadcrumb/Breadcrumb.svelte +2 -3
  74. package/src/lib/components/page/breadcrumb/BreadcrumbDataCaller.js +11 -15
  75. package/src/lib/components/page/menu/MenuAccountBar.svelte +1 -6
  76. package/src/lib/components/page/menu/MenuDataCaller.js +1 -1
  77. package/src/lib/components/page/menu/MenuItem.svelte +10 -8
  78. package/src/lib/components/page/menu/MenuSublist.svelte +35 -41
  79. package/src/lib/components/page/menu/SettingsBar.svelte +5 -2
  80. package/src/lib/components/toggle/Toggle.svelte +28 -30
  81. package/src/lib/css/core.ui.postcss +1 -2
  82. package/src/lib/index.ts +1 -2
  83. package/src/lib/services/Api.ts +21 -20
  84. package/src/lib/services/BaseCaller.js +1 -1
  85. package/src/lib/stores/apiStores.ts +1 -5
  86. package/src/lib/stores/pageStores.ts +0 -2
@@ -1,5 +1,5 @@
1
1
  <script>import { afterUpdate, createEventDispatcher, onDestroy, onMount } from "svelte";
2
- import { readable, writable } from "svelte/store";
2
+ import { writable } from "svelte/store";
3
3
  import Fa from "svelte-fa";
4
4
  import { faCompress, faDownload, faXmark } from "@fortawesome/free-solid-svg-icons";
5
5
  import { createTable, Subscribe, Render, createRender } from "svelte-headless-table";
@@ -21,6 +21,7 @@ import TableFilterServer from "./TableFilterServer.svelte";
21
21
  import TablePagination from "./TablePagination.svelte";
22
22
  import TablePaginationServer from "./TablePaginationServer.svelte";
23
23
  import ColumnsMenu from "./ColumnsMenu.svelte";
24
+ import ClientDB from "./clientDB.js";
24
25
  import * as utils from "./utils";
25
26
  import { columnFilter, searchFilter } from "./filter";
26
27
  import { Send } from "../../models/Models";
@@ -56,20 +57,27 @@ let {
56
57
  // Whether to display the export button and enable export functionality
57
58
  server
58
59
  } = config;
60
+ const clientDb = config.clientDb ?? false;
61
+ const clientDbSeedData = config.clientDbSeedData ?? [];
62
+ const initialServerCount = Number(config.__initialServerCount ?? 0);
59
63
  let searchValue = "";
60
64
  let isFetching = false;
61
65
  let tableRef;
62
- const serverSide = server !== void 0;
66
+ let serverItemCountValue = Number.isFinite(initialServerCount) ? initialServerCount : 0;
67
+ const serverSide = server !== void 0 || clientDb;
68
+ let _clientDbInstance = null;
69
+ let clientDbEnabled = clientDb;
70
+ let clientDbReady = false;
71
+ let clientDbInitSource = [];
72
+ let clientDbSeedSize = 0;
63
73
  const { sendModel = new Send() } = server ?? {};
64
74
  const filters = writable({});
65
75
  const dispatch = createEventDispatcher();
66
76
  const actionDispatcher = (obj) => dispatch("action", obj);
67
77
  const rowHeights = writable({});
68
78
  const colWidths = writable([]);
69
- const serverItems = serverSide ? writable(0) : void 0;
70
- const serverItemCount = serverSide ? readable(0, (set) => {
71
- serverItems.subscribe((val) => set(val));
72
- }) : void 0;
79
+ const serverItems = serverSide ? writable(Number.isFinite(initialServerCount) ? initialServerCount : 0) : void 0;
80
+ const serverItemCount = serverItems;
73
81
  const table = createTable(data, {
74
82
  colFilter: addColumnFilters(),
75
83
  tableFilter: addTableFilter({
@@ -97,6 +105,16 @@ $data.forEach((item) => {
97
105
  }
98
106
  });
99
107
  });
108
+ if (columns && Object.keys(columns).length > 0) {
109
+ Object.keys(columns).forEach((key) => {
110
+ if (optionsComponent !== void 0 && key === "optionsColumn") {
111
+ return;
112
+ }
113
+ if (!allCols[key]) {
114
+ allCols[key] = {};
115
+ }
116
+ });
117
+ }
100
118
  Object.keys(allCols).forEach((key) => {
101
119
  $filters = { ...$filters, [key]: {} };
102
120
  });
@@ -226,10 +244,21 @@ const tableColumns = [
226
244
  }
227
245
  })
228
246
  ];
247
+ const getUniqueOptionsColumnId = () => {
248
+ const used = new Set(unexcludedColumns.map((col) => String(col)));
249
+ let id = "optionsColumn";
250
+ let i = 1;
251
+ while (used.has(id)) {
252
+ id = `optionsColumn_${i}`;
253
+ i += 1;
254
+ }
255
+ return id;
256
+ };
229
257
  if (optionsComponent !== void 0) {
258
+ const optionsColumnId = getUniqueOptionsColumnId();
230
259
  tableColumns.push(
231
260
  table.display({
232
- id: "optionsColumn",
261
+ id: optionsColumnId,
233
262
  header: "",
234
263
  plugins: {
235
264
  export: {
@@ -251,7 +280,7 @@ const { filterValue } = pluginStates.tableFilter;
251
280
  const { exportedData } = pluginStates.export;
252
281
  const { pageIndex, pageSize } = pluginStates.page;
253
282
  const { hiddenColumnIds } = pluginStates.hideColumns;
254
- let prevSort = void 0;
283
+ let prevSort = [];
255
284
  const sortServer = (order, id) => {
256
285
  if (!sendModel) throw new Error("Server-side configuration is missing");
257
286
  if (order === void 0) {
@@ -259,19 +288,137 @@ const sortServer = (order, id) => {
259
288
  } else {
260
289
  sendModel.order = [{ column: id, direction: order }];
261
290
  }
262
- if (JSON.stringify(sendModel.order) !== JSON.stringify(prevSort) && prevSort !== void 0) {
291
+ if (JSON.stringify(sendModel.order) !== JSON.stringify(prevSort)) {
263
292
  $pageIndex = 0;
264
293
  $data = [];
265
294
  updateTableWithParams();
266
295
  }
267
- if (order === void 0) {
268
- prevSort = [];
269
- } else {
270
- prevSort = sendModel.order;
271
- }
296
+ prevSort = sendModel.order ? [...sendModel.order] : [];
272
297
  };
273
298
  const updateTableWithParams = async () => {
274
- if ($data.length > 0 && serverSide) {
299
+ if (clientDbEnabled) {
300
+ isFetching = true;
301
+ const filtersNormalized = utils.normalizeFilters($filters);
302
+ const q = sendModel?.q || "";
303
+ const offset2 = $pageSize * $pageIndex;
304
+ const limit = $pageSize;
305
+ if (!_clientDbInstance) {
306
+ _clientDbInstance = new ClientDB(tableId);
307
+ }
308
+ if (!clientDbReady) {
309
+ const source = clientDbInitSource.length > 0 ? clientDbInitSource : $data;
310
+ const getValue = (row, column) => {
311
+ const dot = String(column || "").replaceAll("%%%", ".");
312
+ return row[dot] ?? row[column] ?? row[dot.replaceAll(".", "%%%")];
313
+ };
314
+ const localMatches = (row) => {
315
+ if (q && q.length > 0) {
316
+ const ql = String(q).toLowerCase();
317
+ let found = false;
318
+ for (const k in row) {
319
+ const v = row[k];
320
+ if (v == null) continue;
321
+ if (String(v).toLowerCase().includes(ql)) {
322
+ found = true;
323
+ break;
324
+ }
325
+ }
326
+ if (!found) return false;
327
+ }
328
+ for (const f of filtersNormalized) {
329
+ const actual = getValue(row, f.column);
330
+ const val = f.value;
331
+ if (val == null) continue;
332
+ const aStr = String(actual ?? "").toLowerCase();
333
+ const vStr = String(val ?? "").toLowerCase();
334
+ const aNum = Number(actual);
335
+ const vNum = Number(val);
336
+ const aDate = new Date(actual).getTime();
337
+ const vDate = new Date(val).getTime();
338
+ switch (f.filterBy) {
339
+ case "c":
340
+ if (!aStr.includes(vStr)) return false;
341
+ break;
342
+ case "nc":
343
+ if (aStr.includes(vStr)) return false;
344
+ break;
345
+ case "sw":
346
+ if (!aStr.startsWith(vStr)) return false;
347
+ break;
348
+ case "ew":
349
+ if (!aStr.endsWith(vStr)) return false;
350
+ break;
351
+ case "e":
352
+ if (actual != val) return false;
353
+ break;
354
+ case "ne":
355
+ if (actual == val) return false;
356
+ break;
357
+ case "gt":
358
+ if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum > vNum)) return false;
359
+ break;
360
+ case "lt":
361
+ if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum < vNum)) return false;
362
+ break;
363
+ case "gte":
364
+ if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum >= vNum)) return false;
365
+ break;
366
+ case "lte":
367
+ if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum <= vNum)) return false;
368
+ break;
369
+ case "o":
370
+ if (Number.isNaN(aDate) || Number.isNaN(vDate) || aDate !== vDate) return false;
371
+ break;
372
+ case "sf":
373
+ case "a":
374
+ if (Number.isNaN(aDate) || Number.isNaN(vDate) || !(aDate >= vDate)) return false;
375
+ break;
376
+ case "u":
377
+ case "b":
378
+ if (Number.isNaN(aDate) || Number.isNaN(vDate) || !(aDate <= vDate)) return false;
379
+ break;
380
+ case "no":
381
+ if (Number.isNaN(aDate) || Number.isNaN(vDate) || aDate === vDate) return false;
382
+ break;
383
+ }
384
+ }
385
+ return true;
386
+ };
387
+ const filtered = source.filter(localMatches);
388
+ const order = sendModel?.order || [];
389
+ if (order.length > 0) {
390
+ const { column, direction } = order[0];
391
+ const dir = direction === "desc" ? -1 : 1;
392
+ filtered.sort((a, b) => {
393
+ const av = getValue(a, column);
394
+ const bv = getValue(b, column);
395
+ if (av == null && bv == null) return 0;
396
+ if (av == null) return -1 * dir;
397
+ if (bv == null) return 1 * dir;
398
+ if (typeof av === "number" && typeof bv === "number") return (av - bv) * dir;
399
+ return String(av).localeCompare(String(bv)) * dir;
400
+ });
401
+ }
402
+ const localRows = filtered.slice(offset2, offset2 + limit);
403
+ serverItems?.set(filtered.length);
404
+ data.set(localRows);
405
+ isFetching = false;
406
+ return { rows: localRows, total: filtered.length };
407
+ }
408
+ const res = await _clientDbInstance.query({
409
+ q,
410
+ filters: filtersNormalized,
411
+ order: sendModel?.order || [],
412
+ offset: offset2,
413
+ limit
414
+ });
415
+ const payload = res.payload ? res.payload : res;
416
+ serverItems?.set(payload.total ?? payload.totalCount ?? 0);
417
+ data.set(payload.rows ?? []);
418
+ isFetching = false;
419
+ return payload;
420
+ }
421
+ if ($data.length > 0 && serverSide && Number($serverItemCount) > 0) {
275
422
  console.log("Table is not empty");
276
423
  return;
277
424
  }
@@ -332,6 +479,9 @@ afterUpdate(() => {
332
479
  if (resizable !== "rows" && resizable !== "both") {
333
480
  return;
334
481
  }
482
+ if (!$pageRows || $pageRows.length === 0) {
483
+ return;
484
+ }
335
485
  const e = tableRef?.querySelector(`#${tableId}-row-${$pageRows[0].id}`);
336
486
  if (e) {
337
487
  getDimensions();
@@ -340,10 +490,45 @@ afterUpdate(() => {
340
490
  onDestroy(() => {
341
491
  resizeColumnsObserver.disconnect();
342
492
  resizeRowsObserver.disconnect();
493
+ if (_clientDbInstance) {
494
+ try {
495
+ _clientDbInstance.destroy();
496
+ } catch (e) {
497
+ }
498
+ }
343
499
  });
344
500
  $: sortKeys = pluginStates.sort.sortKeys;
345
- onMount(() => {
346
- if (serverSide) {
501
+ $: serverItemCountValue = clientDbEnabled ? Number($serverItemCount) > 0 ? Math.max(
502
+ Number($serverItemCount),
503
+ initialServerCount,
504
+ clientDbSeedSize,
505
+ clientDbInitSource.length,
506
+ $data.length
507
+ ) : Math.max(initialServerCount, clientDbSeedSize, clientDbInitSource.length, $data.length) : Number($serverItemCount);
508
+ onMount(async () => {
509
+ if (clientDbEnabled) {
510
+ const seed = Array.isArray(clientDbSeedData) ? clientDbSeedData : [];
511
+ const hasSeed = seed.length > 0;
512
+ clientDbSeedSize = seed.length;
513
+ if (hasSeed || $data && $data.length > 0) {
514
+ clientDbInitSource = hasSeed ? [...seed] : [...$data];
515
+ const firstPage = clientDbInitSource.slice(0, $pageSize);
516
+ serverItems?.set(clientDbInitSource.length);
517
+ data.set(firstPage);
518
+ _clientDbInstance = new ClientDB(tableId);
519
+ _clientDbInstance.init(clientDbInitSource).then(async () => {
520
+ const result = await updateTableWithParams();
521
+ clientDbReady = true;
522
+ clientDbInitSource = [];
523
+ return result;
524
+ }).catch(() => {
525
+ clientDbReady = false;
526
+ });
527
+ } else if (!_clientDbInstance) {
528
+ _clientDbInstance = new ClientDB(tableId);
529
+ }
530
+ }
531
+ if (server !== void 0) {
347
532
  updateTableWithParams();
348
533
  }
349
534
  });
@@ -358,14 +543,19 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
358
543
  {#if search}
359
544
  <form
360
545
  class="flex gap-2"
361
- on:submit|preventDefault={() => {
546
+ on:submit|preventDefault={async () => {
362
547
  if (serverSide && !sendModel) {
363
548
  throw new Error('Server-side configuration is missing');
364
549
  } else {
365
550
  sendModel.q = searchValue;
366
551
  }
367
552
 
553
+ $pageIndex = 0;
368
554
  $filterValue = searchValue;
555
+ if (serverSide) {
556
+ $data = [];
557
+ await updateTableWithParams();
558
+ }
369
559
  }}
370
560
  >
371
561
  <div class="relative w-full flex items-center">
@@ -383,15 +573,20 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
383
573
  id="{tableId}-searchReset"
384
574
  class="absolute right-3 items-center"
385
575
  aria-label="Clear search"
386
- on:click|preventDefault={() => {
576
+ on:click|preventDefault={async () => {
387
577
  if (serverSide && !sendModel) {
388
578
  throw new Error('Server-side configuration is missing');
389
579
  } else {
390
580
  sendModel.q = '';
391
581
  }
392
582
 
583
+ $pageIndex = 0;
393
584
  searchValue = '';
394
585
  $filterValue = '';
586
+ if (serverSide) {
587
+ $data = [];
588
+ await updateTableWithParams();
589
+ }
395
590
  }}><Fa icon={faXmark} /></button
396
591
  >
397
592
  </div>
@@ -401,14 +596,19 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
401
596
  id="{tableId}-searchSubmit"
402
597
  class="btn variant-filled-primary"
403
598
  aria-label="Search"
404
- on:click|preventDefault={() => {
599
+ on:click|preventDefault={async () => {
405
600
  if (serverSide && !sendModel) {
406
601
  throw new Error('Server-side configuration is missing');
407
602
  } else {
408
603
  sendModel.q = searchValue;
409
604
  }
410
605
 
606
+ $pageIndex = 0;
411
607
  $filterValue = searchValue;
608
+ if (serverSide) {
609
+ $data = [];
610
+ await updateTableWithParams();
611
+ }
412
612
  }}>Search</button
413
613
  >
414
614
  </form>
@@ -530,7 +730,7 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
530
730
  ? `Remove sorting by ${cell.label} column`
531
731
  : `Sort by ${cell.label} column in ascending order`}
532
732
  >
533
- {cell.render().replaceAll('%%%', '.')}
733
+ {String(cell.render()).replaceAll('%%%', '.')}
534
734
  </span>
535
735
  <div class="w-2">
536
736
  {#if props.sort.order === 'asc'}
@@ -625,7 +825,7 @@ $: $hiddenColumnIds = shownColumns.filter((col) => !col.visible).map((col) => co
625
825
  <TablePaginationServer
626
826
  pageConfig={pluginStates.page}
627
827
  {pageSizes}
628
- itemCount={$serverItemCount}
828
+ itemCount={serverItemCountValue}
629
829
  updateTable={updateTableWithParams}
630
830
  id={tableId}
631
831
  {data}
@@ -7,6 +7,17 @@ export let pageSizes;
7
7
  export let id;
8
8
  export let updateTable;
9
9
  export let data;
10
+ const toCount = (value) => {
11
+ if (typeof value === "number") return Number.isNaN(value) ? 0 : value;
12
+ const parsed = Number(String(value ?? "").replace(/[^0-9.-]/g, ""));
13
+ return Number.isNaN(parsed) ? 0 : parsed;
14
+ };
15
+ const getEffectiveCount = () => {
16
+ const count = toCount(itemCount);
17
+ if (count > 0) return count;
18
+ return Array.isArray($data) ? $data.length : 0;
19
+ };
20
+ let effectiveCount = 0;
10
21
  let indexInformation = "";
11
22
  const { pageIndex, pageCount, pageSize } = pageConfig;
12
23
  let pageSizeDropdownValue = $pageSize;
@@ -17,17 +28,19 @@ const pageSizePopup = {
17
28
  closeQuery: ".listbox-item"
18
29
  };
19
30
  const getIndexInfomationString = () => {
20
- return itemCount === 0 ? "No items" : `Displaying items ${$pageIndex * $pageSize + 1} - ${Math.min(
31
+ const count = effectiveCount;
32
+ return count === 0 ? "No items" : `Displaying items ${$pageIndex * $pageSize + 1} - ${Math.min(
21
33
  ($pageIndex + 1) * $pageSize,
22
- itemCount
23
- )} of ${Math.min($pageCount * $pageSize, itemCount)}`;
34
+ count
35
+ )} of ${count}`;
24
36
  };
25
37
  function updateTableServer() {
26
38
  $data = [];
27
39
  updateTable();
28
40
  }
41
+ $: effectiveCount = getEffectiveCount();
29
42
  $: paginationSettings = {
30
- size: itemCount,
43
+ size: effectiveCount,
31
44
  limit: $pageSize,
32
45
  page: $pageIndex,
33
46
  amounts: pageSizes
@@ -52,9 +65,12 @@ $: $pageCount, $pageIndex, $pageSize, itemCount, indexInformation = getIndexInfo
52
65
  {#each pageSizes as size}
53
66
  <ListBoxItem
54
67
  bind:group={pageSizeDropdownValue}
55
- name="medium" value={size}
56
- on:click={() => { $pageSize = size; updateTableServer(); }}
57
- >{size}</ListBoxItem
68
+ name="medium"
69
+ value={size}
70
+ on:click={() => {
71
+ $pageSize = size;
72
+ updateTableServer();
73
+ }}>{size}</ListBoxItem
58
74
  >
59
75
  {/each}
60
76
  </ListBox>
@@ -63,7 +79,10 @@ $: $pageCount, $pageIndex, $pageSize, itemCount, indexInformation = getIndexInfo
63
79
  </div>
64
80
  <div class="flex justify-center">
65
81
  <Paginator
66
- on:page={(page) => {$pageIndex = page.detail; updateTableServer(); }}
82
+ on:page={(page) => {
83
+ $pageIndex = page.detail;
84
+ updateTableServer();
85
+ }}
67
86
  settings={paginationSettings}
68
87
  select="hidden"
69
88
  active="!variant-filled-secondary !text-on-secondary-token"
@@ -0,0 +1,22 @@
1
+ export default class ClientDB {
2
+ constructor(tableId: any);
3
+ tableId: any;
4
+ db: any;
5
+ init(rows: any): Promise<{
6
+ tableId: any;
7
+ count: any;
8
+ }>;
9
+ query({ q, filters, order, offset, limit }?: {
10
+ q?: string | undefined;
11
+ filters?: never[] | undefined;
12
+ order?: never[] | undefined;
13
+ offset?: number | undefined;
14
+ limit?: number | undefined;
15
+ }): Promise<{
16
+ tableId: any;
17
+ rows: any[];
18
+ total: number;
19
+ }>;
20
+ clear(): Promise<void>;
21
+ destroy(): void;
22
+ }
@@ -0,0 +1,236 @@
1
+ // Simple client-side DB wrapper using IndexedDB directly (no worker)
2
+ const DB_PREFIX = 'table-db-';
3
+
4
+ function openDB(name) {
5
+ return new Promise((resolve, reject) => {
6
+ const req = indexedDB.open(DB_PREFIX + name, 1);
7
+ req.onupgradeneeded = () => {
8
+ const db = req.result;
9
+ if (!db.objectStoreNames.contains('rows')) {
10
+ db.createObjectStore('rows', { keyPath: '__id', autoIncrement: true });
11
+ }
12
+ };
13
+ req.onsuccess = () => resolve(req.result);
14
+ req.onerror = () => reject(req.error);
15
+ });
16
+ }
17
+
18
+ function clearStore(db) {
19
+ return new Promise((resolve, reject) => {
20
+ const tx = db.transaction('rows', 'readwrite');
21
+ const store = tx.objectStore('rows');
22
+ const req = store.clear();
23
+ req.onsuccess = () => resolve(undefined);
24
+ req.onerror = () => reject(req.error);
25
+ });
26
+ }
27
+
28
+ function bulkInsert(db, rows) {
29
+ return new Promise((resolve, reject) => {
30
+ const tx = db.transaction('rows', 'readwrite');
31
+ const store = tx.objectStore('rows');
32
+ for (let i = 0; i < rows.length; i++) {
33
+ store.add({ __r: rows[i] });
34
+ }
35
+ tx.oncomplete = () => resolve(undefined);
36
+ tx.onerror = () => reject(tx.error);
37
+ });
38
+ }
39
+
40
+ function matches(rowObj, q, filters) {
41
+ const row = rowObj.__r || rowObj;
42
+
43
+ if (q && q.length > 0) {
44
+ const ql = String(q).toLowerCase();
45
+ let found = false;
46
+ for (const k in row) {
47
+ const v = row[k];
48
+ if (v == null) continue;
49
+ if (typeof v === 'string' && v.toLowerCase().includes(ql)) {
50
+ found = true;
51
+ break;
52
+ }
53
+ if (typeof v === 'number' && String(v).includes(ql)) {
54
+ found = true;
55
+ break;
56
+ }
57
+ }
58
+ if (!found) return false;
59
+ }
60
+
61
+ const normalizeKey = (k) => String(k || '').replaceAll('%%%', '.');
62
+ const getValue = (column) => {
63
+ const dot = normalizeKey(column);
64
+ return row[dot] ?? row[column] ?? row[dot.replaceAll('.', '%%%')];
65
+ };
66
+
67
+ const asComparableNumber = (v) => {
68
+ if (typeof v === 'number') return v;
69
+ if (typeof v === 'bigint') return Number(v);
70
+ const n = Number(v);
71
+ return Number.isNaN(n) ? null : n;
72
+ };
73
+
74
+ const asTime = (v) => {
75
+ if (v instanceof Date) return v.getTime();
76
+ const d = new Date(v);
77
+ const t = d.getTime();
78
+ return Number.isNaN(t) ? null : t;
79
+ };
80
+
81
+ if (filters && filters.length > 0) {
82
+ for (const f of filters) {
83
+ const op = f.filterBy;
84
+ const val = f.value;
85
+ const actual = getValue(f.column);
86
+ if (val == null) continue;
87
+
88
+ const aStr = String(actual ?? '').toLowerCase();
89
+ const vStr = String(val ?? '').toLowerCase();
90
+ const aNum = asComparableNumber(actual);
91
+ const vNum = asComparableNumber(val);
92
+ const aDate = asTime(actual);
93
+ const vDate = asTime(val);
94
+
95
+ if (op === 'c' && !aStr.includes(vStr)) return false;
96
+ if (op === 'nc' && aStr.includes(vStr)) return false;
97
+ if (op === 'sw' && !aStr.startsWith(vStr)) return false;
98
+ if (op === 'ew' && !aStr.endsWith(vStr)) return false;
99
+
100
+ if (op === 'e' && actual != val) return false;
101
+ if (op === 'ne' && actual == val) return false;
102
+
103
+ if (op === 'gt') {
104
+ if (aNum == null || vNum == null || !(aNum > vNum)) return false;
105
+ }
106
+ if (op === 'lt') {
107
+ if (aNum == null || vNum == null || !(aNum < vNum)) return false;
108
+ }
109
+ if (op === 'gte') {
110
+ if (aNum == null || vNum == null || !(aNum >= vNum)) return false;
111
+ }
112
+ if (op === 'lte') {
113
+ if (aNum == null || vNum == null || !(aNum <= vNum)) return false;
114
+ }
115
+
116
+ if (op === 'o') {
117
+ if (aDate == null || vDate == null || aDate !== vDate) return false;
118
+ }
119
+ if (op === 'sf' || op === 'a') {
120
+ if (aDate == null || vDate == null || !(aDate > vDate || aDate === vDate)) return false;
121
+ }
122
+ if (op === 'u' || op === 'b') {
123
+ if (aDate == null || vDate == null || !(aDate < vDate || aDate === vDate)) return false;
124
+ }
125
+ if (op === 'no') {
126
+ if (aDate == null || vDate == null || aDate === vDate) return false;
127
+ }
128
+ }
129
+ }
130
+
131
+ return true;
132
+ }
133
+
134
+ export default class ClientDB {
135
+ constructor(tableId) {
136
+ this.tableId = tableId;
137
+ this.db = null;
138
+ }
139
+
140
+ async init(rows) {
141
+ this.db = this.db || (await openDB(this.tableId));
142
+ await clearStore(this.db);
143
+
144
+ const CHUNK = 1000;
145
+ for (let i = 0; i < rows.length; i += CHUNK) {
146
+ const chunk = rows.slice(i, i + CHUNK);
147
+ await bulkInsert(this.db, chunk);
148
+ // Yield to keep the UI responsive while importing
149
+ await new Promise((r) => setTimeout(r, 0));
150
+ }
151
+
152
+ return { tableId: this.tableId, count: rows.length };
153
+ }
154
+
155
+ async query({ q = '', filters = [], order = [], offset = 0, limit = 100 } = {}) {
156
+ this.db = this.db || (await openDB(this.tableId));
157
+
158
+ const tx = this.db.transaction('rows', 'readonly');
159
+ const store = tx.objectStore('rows');
160
+ const req = store.openCursor();
161
+
162
+ const matched = [];
163
+
164
+ await new Promise((resolve, reject) => {
165
+ req.onsuccess = (evt) => {
166
+ const cursor = evt.target.result;
167
+ if (!cursor) {
168
+ resolve(undefined);
169
+ return;
170
+ }
171
+
172
+ const rec = cursor.value;
173
+ if (matches(rec, q, filters)) {
174
+ matched.push(rec.__r || rec);
175
+ }
176
+
177
+ cursor.continue();
178
+ };
179
+ req.onerror = () => reject(req.error);
180
+ });
181
+
182
+ const getValue = (row, column) => {
183
+ const dot = String(column || '').replaceAll('%%%', '.');
184
+ return row[dot] ?? row[column] ?? row[dot.replaceAll('.', '%%%')];
185
+ };
186
+
187
+ if (order && order.length > 0) {
188
+ const { column, direction } = order[0];
189
+ const dir = direction === 'desc' ? -1 : 1;
190
+ matched.sort((a, b) => {
191
+ const av = getValue(a, column);
192
+ const bv = getValue(b, column);
193
+
194
+ if (av == null && bv == null) return 0;
195
+ if (av == null) return -1 * dir;
196
+ if (bv == null) return 1 * dir;
197
+
198
+ if (typeof av === 'number' && typeof bv === 'number') {
199
+ return (av - bv) * dir;
200
+ }
201
+
202
+ if (typeof av === 'bigint' || typeof bv === 'bigint') {
203
+ const ai = BigInt(av);
204
+ const bi = BigInt(bv);
205
+ if (ai === bi) return 0;
206
+ return ai > bi ? dir : -dir;
207
+ }
208
+
209
+ const ad = new Date(av).getTime();
210
+ const bd = new Date(bv).getTime();
211
+ if (!Number.isNaN(ad) && !Number.isNaN(bd)) {
212
+ return (ad - bd) * dir;
213
+ }
214
+
215
+ return String(av).localeCompare(String(bv)) * dir;
216
+ });
217
+ }
218
+
219
+ const total = matched.length;
220
+ const rows = matched.slice(offset, offset + limit);
221
+
222
+ return { tableId: this.tableId, rows, total };
223
+ }
224
+
225
+ async clear() {
226
+ this.db = this.db || (await openDB(this.tableId));
227
+ await clearStore(this.db);
228
+ }
229
+
230
+ destroy() {
231
+ if (this.db) {
232
+ this.db.close();
233
+ this.db = null;
234
+ }
235
+ }
236
+ }