@bexis2/bexis2-core-ui 0.4.95 → 0.4.97
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 +105 -24
- package/dist/components/CodeEditor/CodeEditor.svelte +2 -2
- package/dist/components/Facets/ShowMore.svelte +4 -6
- package/dist/components/File/FileUploader.svelte +6 -3
- package/dist/components/Table/Table.svelte +31 -5
- package/dist/components/Table/TableContent.svelte +226 -27
- package/dist/components/Table/TableFilter.svelte +25 -12
- package/dist/components/Table/TableFilterServer.svelte +51 -6
- package/dist/components/Table/TablePaginationServer.svelte +27 -8
- package/dist/components/Table/clientDB.d.ts +22 -0
- package/dist/components/Table/clientDB.js +236 -0
- package/dist/components/Table/filter.js +21 -2
- package/dist/components/Table/tableWorker.d.ts +1 -0
- package/dist/components/Table/tableWorker.js +179 -0
- package/dist/components/Table/utils.d.ts +1 -2
- package/dist/components/Table/utils.js +64 -31
- package/dist/components/form/CheckboxKvPList.svelte +15 -15
- package/dist/components/form/CheckboxList.svelte +1 -1
- package/dist/components/form/DateInput.svelte +12 -1
- package/dist/components/form/DatePickerInput.svelte +51 -29
- package/dist/components/form/Dropdown.svelte +14 -3
- package/dist/components/form/DropdownKvP.svelte +13 -2
- package/dist/components/form/InputContainer.svelte +17 -15
- package/dist/components/form/MultiSelect.svelte +30 -29
- package/dist/components/form/NumberInput.svelte +12 -1
- package/dist/components/form/TextArea.svelte +12 -1
- package/dist/components/form/TextInput.svelte +12 -1
- package/dist/components/page/Alert.svelte +5 -1
- package/dist/components/page/BackToTop.svelte +3 -1
- package/dist/components/page/Docs.svelte +9 -2
- package/dist/components/page/GoToTop.svelte +14 -15
- package/dist/components/page/Page.svelte +50 -56
- package/dist/components/page/breadcrumb/BreadcrumbDataCaller.js +11 -15
- package/dist/components/page/menu/MenuAccountBar.svelte +1 -5
- package/dist/components/page/menu/MenuDataCaller.js +1 -1
- package/dist/components/page/menu/MenuItem.svelte +9 -7
- package/dist/components/page/menu/MenuSublist.svelte +7 -5
- package/dist/components/page/menu/SettingsBar.svelte +4 -1
- package/dist/components/toggle/Toggle.svelte +9 -9
- package/dist/css/core.ui.postcss +1 -2
- package/dist/services/Api.js +4 -4
- package/dist/services/BaseCaller.js +1 -1
- package/package.json +114 -114
- package/src/lib/components/CodeEditor/CodeEditor.svelte +4 -4
- package/src/lib/components/Facets/Facets.svelte +2 -2
- package/src/lib/components/Facets/ShowMore.svelte +4 -6
- package/src/lib/components/File/FileUploader.svelte +17 -14
- package/src/lib/components/Table/Table.svelte +32 -5
- package/src/lib/components/Table/TableContent.svelte +261 -38
- package/src/lib/components/Table/TableFilter.svelte +34 -8
- package/src/lib/components/Table/TableFilterServer.svelte +65 -9
- package/src/lib/components/Table/TablePagination.svelte +6 -2
- package/src/lib/components/Table/TablePaginationServer.svelte +36 -10
- package/src/lib/components/Table/clientDB.js +236 -0
- package/src/lib/components/Table/filter.ts +44 -24
- package/src/lib/components/Table/tableWorker.js +179 -0
- package/src/lib/components/Table/utils.ts +80 -46
- package/src/lib/components/form/Checkbox.svelte +1 -1
- package/src/lib/components/form/CheckboxKvPList.svelte +15 -16
- package/src/lib/components/form/CheckboxList.svelte +1 -1
- package/src/lib/components/form/DateInput.svelte +13 -2
- package/src/lib/components/form/DatePickerInput.svelte +51 -29
- package/src/lib/components/form/Dropdown.svelte +14 -3
- package/src/lib/components/form/DropdownKvP.svelte +13 -2
- package/src/lib/components/form/InputContainer.svelte +18 -17
- package/src/lib/components/form/MultiSelect.svelte +30 -29
- package/src/lib/components/form/NumberInput.svelte +21 -12
- package/src/lib/components/form/TextArea.svelte +13 -2
- package/src/lib/components/form/TextInput.svelte +13 -2
- package/src/lib/components/page/Alert.svelte +5 -1
- package/src/lib/components/page/BackToTop.svelte +3 -1
- package/src/lib/components/page/Docs.svelte +9 -2
- package/src/lib/components/page/GoToTop.svelte +14 -15
- package/src/lib/components/page/Notification.svelte +1 -1
- package/src/lib/components/page/Page.svelte +67 -78
- package/src/lib/components/page/breadcrumb/Breadcrumb.svelte +2 -3
- package/src/lib/components/page/breadcrumb/BreadcrumbDataCaller.js +11 -15
- package/src/lib/components/page/menu/MenuAccountBar.svelte +1 -6
- package/src/lib/components/page/menu/MenuDataCaller.js +1 -1
- package/src/lib/components/page/menu/MenuItem.svelte +10 -8
- package/src/lib/components/page/menu/MenuSublist.svelte +35 -41
- package/src/lib/components/page/menu/SettingsBar.svelte +5 -2
- package/src/lib/components/toggle/Toggle.svelte +28 -30
- package/src/lib/css/core.ui.postcss +1 -2
- package/src/lib/index.ts +1 -2
- package/src/lib/services/Api.ts +21 -20
- package/src/lib/services/BaseCaller.js +1 -1
- package/src/lib/stores/apiStores.ts +1 -5
- package/src/lib/stores/pageStores.ts +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { afterUpdate, createEventDispatcher, onDestroy, onMount } from 'svelte';
|
|
3
|
-
import {
|
|
3
|
+
import { writable } from 'svelte/store';
|
|
4
4
|
|
|
5
5
|
import Fa from 'svelte-fa';
|
|
6
6
|
import { faCompress, faDownload, faXmark } from '@fortawesome/free-solid-svg-icons';
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
} from 'svelte-headless-table/plugins';
|
|
17
17
|
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
|
|
18
18
|
import { SlideToggle, storePopup } from '@skeletonlabs/skeleton';
|
|
19
|
-
import type { PaginationConfig } from 'svelte-headless-table/lib/plugins/addPagination';
|
|
20
19
|
|
|
21
20
|
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
|
|
22
21
|
|
|
@@ -26,6 +25,7 @@
|
|
|
26
25
|
import TablePagination from './TablePagination.svelte';
|
|
27
26
|
import TablePaginationServer from './TablePaginationServer.svelte';
|
|
28
27
|
import ColumnsMenu from './ColumnsMenu.svelte';
|
|
28
|
+
import ClientDB from './clientDB.js';
|
|
29
29
|
import * as utils from './utils';
|
|
30
30
|
import { columnFilter, searchFilter } from './filter';
|
|
31
31
|
import { Send } from '$models/Models';
|
|
@@ -53,19 +53,30 @@
|
|
|
53
53
|
server
|
|
54
54
|
} = config;
|
|
55
55
|
|
|
56
|
+
// `clientDb` is optional and may not be part of the `TableConfig` type
|
|
57
|
+
const clientDb = (config as any).clientDb ?? false;
|
|
58
|
+
const clientDbSeedData = (config as any).clientDbSeedData ?? [];
|
|
59
|
+
const initialServerCount = Number((config as any).__initialServerCount ?? 0);
|
|
60
|
+
|
|
56
61
|
let searchValue = '';
|
|
57
62
|
let isFetching = false;
|
|
58
63
|
let tableRef: HTMLTableElement;
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
let serverItemCountValue = Number.isFinite(initialServerCount) ? initialServerCount : 0;
|
|
65
|
+
|
|
66
|
+
const serverSide = server !== undefined || clientDb;
|
|
67
|
+
let _clientDbInstance: any = null;
|
|
68
|
+
let clientDbEnabled = clientDb;
|
|
69
|
+
let clientDbReady = false;
|
|
70
|
+
let clientDbInitSource: any[] = [];
|
|
71
|
+
let clientDbSeedSize = 0;
|
|
61
72
|
const { sendModel = new Send() } = server ?? {};
|
|
62
73
|
|
|
63
74
|
const filters = writable<{
|
|
64
75
|
[key: string]: { [key in FilterOptionsEnum]?: number | string | Date };
|
|
65
76
|
}>({});
|
|
66
77
|
|
|
67
|
-
//
|
|
68
|
-
type AccessorType =
|
|
78
|
+
// Accessor keys are handled as string keys to avoid heavy type coupling to full dataset shape
|
|
79
|
+
type AccessorType = string;
|
|
69
80
|
|
|
70
81
|
// Creating a dispatcher to dispatch actions to the parent component
|
|
71
82
|
const dispatch = createEventDispatcher();
|
|
@@ -76,12 +87,8 @@
|
|
|
76
87
|
const colWidths = writable<number[]>([]);
|
|
77
88
|
|
|
78
89
|
// Server-side variables
|
|
79
|
-
const serverItems = serverSide ? writable<number>(0) : undefined;
|
|
80
|
-
const serverItemCount =
|
|
81
|
-
? readable<Number>(0, (set) => {
|
|
82
|
-
serverItems!.subscribe((val) => set(val));
|
|
83
|
-
})
|
|
84
|
-
: undefined;
|
|
90
|
+
const serverItems = serverSide ? writable<number>(Number.isFinite(initialServerCount) ? initialServerCount : 0) : undefined;
|
|
91
|
+
const serverItemCount = serverItems;
|
|
85
92
|
|
|
86
93
|
// Initializing the table
|
|
87
94
|
const table = createTable(data, {
|
|
@@ -99,21 +106,29 @@
|
|
|
99
106
|
initialPageSize: defaultPageSize,
|
|
100
107
|
serverSide,
|
|
101
108
|
serverItemCount
|
|
102
|
-
} as
|
|
109
|
+
} as any),
|
|
103
110
|
expand: addExpandedRows(),
|
|
104
111
|
export: addDataExport({ format: 'json' })
|
|
105
112
|
});
|
|
106
113
|
|
|
107
|
-
//
|
|
114
|
+
// Build column keys without scanning all rows when column config is already provided.
|
|
108
115
|
const allCols: { [key: string]: any } = {};
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
allCols[key] = {};
|
|
116
|
+
if (columns && Object.keys(columns).length > 0) {
|
|
117
|
+
Object.keys(columns).forEach((key) => {
|
|
118
|
+
if (optionsComponent !== undefined && key === 'optionsColumn') {
|
|
119
|
+
return;
|
|
114
120
|
}
|
|
121
|
+
allCols[key] = {};
|
|
115
122
|
});
|
|
116
|
-
}
|
|
123
|
+
} else {
|
|
124
|
+
$data.forEach((item) => {
|
|
125
|
+
Object.keys(item).forEach((key) => {
|
|
126
|
+
if (!allCols[key]) {
|
|
127
|
+
allCols[key] = {};
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
117
132
|
|
|
118
133
|
Object.keys(allCols).forEach((key) => {
|
|
119
134
|
$filters = { ...$filters, [key]: {} };
|
|
@@ -269,11 +284,23 @@
|
|
|
269
284
|
})
|
|
270
285
|
];
|
|
271
286
|
|
|
287
|
+
const getUniqueOptionsColumnId = () => {
|
|
288
|
+
const used = new Set<string>(unexcludedColumns.map((col) => String(col)));
|
|
289
|
+
let id = 'optionsColumn';
|
|
290
|
+
let i = 1;
|
|
291
|
+
while (used.has(id)) {
|
|
292
|
+
id = `optionsColumn_${i}`;
|
|
293
|
+
i += 1;
|
|
294
|
+
}
|
|
295
|
+
return id;
|
|
296
|
+
};
|
|
297
|
+
|
|
272
298
|
// If optionsComponent is provided, add a column for it at the end
|
|
273
299
|
if (optionsComponent !== undefined) {
|
|
300
|
+
const optionsColumnId = getUniqueOptionsColumnId();
|
|
274
301
|
tableColumns.push(
|
|
275
302
|
table.display({
|
|
276
|
-
id:
|
|
303
|
+
id: optionsColumnId,
|
|
277
304
|
header: '',
|
|
278
305
|
plugins: {
|
|
279
306
|
export: {
|
|
@@ -304,7 +331,7 @@
|
|
|
304
331
|
// Column visibility configuration
|
|
305
332
|
const { hiddenColumnIds } = pluginStates.hideColumns;
|
|
306
333
|
|
|
307
|
-
let prevSort: { column: string; direction: 'asc' | 'desc' }[]
|
|
334
|
+
let prevSort: { column: string; direction: 'asc' | 'desc' }[] = [];
|
|
308
335
|
const sortServer = (order: 'asc' | 'desc' | undefined, id: string) => {
|
|
309
336
|
// console.log('Sorting server with', { order, id, sendModel, prevSort });
|
|
310
337
|
if (!sendModel) throw new Error('Server-side configuration is missing');
|
|
@@ -315,18 +342,14 @@
|
|
|
315
342
|
sendModel.order = [{ column: id, direction: order }];
|
|
316
343
|
}
|
|
317
344
|
|
|
318
|
-
if (JSON.stringify(sendModel.order) !== JSON.stringify(prevSort)
|
|
345
|
+
if (JSON.stringify(sendModel.order) !== JSON.stringify(prevSort)) {
|
|
319
346
|
// Reset pagination
|
|
320
347
|
$pageIndex = 0;
|
|
321
348
|
// console.log('Sorting server with', { order, id, sendModel, prevSort });
|
|
322
349
|
$data = [];
|
|
323
350
|
updateTableWithParams();
|
|
324
351
|
}
|
|
325
|
-
|
|
326
|
-
prevSort = [];
|
|
327
|
-
} else {
|
|
328
|
-
prevSort = sendModel.order;
|
|
329
|
-
}
|
|
352
|
+
prevSort = sendModel.order ? [...sendModel.order] : [];
|
|
330
353
|
};
|
|
331
354
|
|
|
332
355
|
// Function to update the table with the provided parameters for easily passing to other components
|
|
@@ -339,13 +362,147 @@
|
|
|
339
362
|
// sendModel
|
|
340
363
|
// });
|
|
341
364
|
|
|
342
|
-
|
|
365
|
+
// If we're using a client-side DB (worker + IndexedDB), route queries there
|
|
366
|
+
if (clientDbEnabled) {
|
|
367
|
+
isFetching = true;
|
|
368
|
+
const filtersNormalized = utils.normalizeFilters($filters);
|
|
369
|
+
const q = sendModel?.q || '';
|
|
370
|
+
const offset = $pageSize * $pageIndex;
|
|
371
|
+
const limit = $pageSize;
|
|
372
|
+
|
|
373
|
+
if (!_clientDbInstance) {
|
|
374
|
+
_clientDbInstance = new ClientDB(tableId);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!clientDbReady) {
|
|
378
|
+
// Fallback while DB is still importing: apply local search/filter/sort for responsiveness.
|
|
379
|
+
const source = clientDbInitSource.length > 0 ? clientDbInitSource : $data;
|
|
380
|
+
|
|
381
|
+
const getValue = (row, column) => {
|
|
382
|
+
const dot = String(column || '').replaceAll('%%%', '.');
|
|
383
|
+
return row[dot] ?? row[column] ?? row[dot.replaceAll('.', '%%%')];
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const localMatches = (row) => {
|
|
387
|
+
if (q && q.length > 0) {
|
|
388
|
+
const ql = String(q).toLowerCase();
|
|
389
|
+
let found = false;
|
|
390
|
+
for (const k in row) {
|
|
391
|
+
const v = row[k];
|
|
392
|
+
if (v == null) continue;
|
|
393
|
+
if (String(v).toLowerCase().includes(ql)) {
|
|
394
|
+
found = true;
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (!found) return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const f of filtersNormalized) {
|
|
402
|
+
const actual = getValue(row, f.column);
|
|
403
|
+
const val = f.value;
|
|
404
|
+
if (val == null) continue;
|
|
405
|
+
|
|
406
|
+
const aStr = String(actual ?? '').toLowerCase();
|
|
407
|
+
const vStr = String(val ?? '').toLowerCase();
|
|
408
|
+
const aNum = Number(actual);
|
|
409
|
+
const vNum = Number(val);
|
|
410
|
+
const aDate = new Date(actual as any).getTime();
|
|
411
|
+
const vDate = new Date(val as any).getTime();
|
|
412
|
+
|
|
413
|
+
switch (f.filterBy) {
|
|
414
|
+
case 'c':
|
|
415
|
+
if (!aStr.includes(vStr)) return false;
|
|
416
|
+
break;
|
|
417
|
+
case 'nc':
|
|
418
|
+
if (aStr.includes(vStr)) return false;
|
|
419
|
+
break;
|
|
420
|
+
case 'sw':
|
|
421
|
+
if (!aStr.startsWith(vStr)) return false;
|
|
422
|
+
break;
|
|
423
|
+
case 'ew':
|
|
424
|
+
if (!aStr.endsWith(vStr)) return false;
|
|
425
|
+
break;
|
|
426
|
+
case 'e':
|
|
427
|
+
if (actual != val) return false;
|
|
428
|
+
break;
|
|
429
|
+
case 'ne':
|
|
430
|
+
if (actual == val) return false;
|
|
431
|
+
break;
|
|
432
|
+
case 'gt':
|
|
433
|
+
if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum > vNum)) return false;
|
|
434
|
+
break;
|
|
435
|
+
case 'lt':
|
|
436
|
+
if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum < vNum)) return false;
|
|
437
|
+
break;
|
|
438
|
+
case 'gte':
|
|
439
|
+
if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum >= vNum)) return false;
|
|
440
|
+
break;
|
|
441
|
+
case 'lte':
|
|
442
|
+
if (Number.isNaN(aNum) || Number.isNaN(vNum) || !(aNum <= vNum)) return false;
|
|
443
|
+
break;
|
|
444
|
+
case 'o':
|
|
445
|
+
if (Number.isNaN(aDate) || Number.isNaN(vDate) || aDate !== vDate) return false;
|
|
446
|
+
break;
|
|
447
|
+
case 'sf':
|
|
448
|
+
case 'a':
|
|
449
|
+
if (Number.isNaN(aDate) || Number.isNaN(vDate) || !(aDate >= vDate)) return false;
|
|
450
|
+
break;
|
|
451
|
+
case 'u':
|
|
452
|
+
case 'b':
|
|
453
|
+
if (Number.isNaN(aDate) || Number.isNaN(vDate) || !(aDate <= vDate)) return false;
|
|
454
|
+
break;
|
|
455
|
+
case 'no':
|
|
456
|
+
if (Number.isNaN(aDate) || Number.isNaN(vDate) || aDate === vDate) return false;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return true;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const filtered = source.filter(localMatches);
|
|
465
|
+
const order = sendModel?.order || [];
|
|
466
|
+
if (order.length > 0) {
|
|
467
|
+
const { column, direction } = order[0];
|
|
468
|
+
const dir = direction === 'desc' ? -1 : 1;
|
|
469
|
+
filtered.sort((a, b) => {
|
|
470
|
+
const av = getValue(a, column);
|
|
471
|
+
const bv = getValue(b, column);
|
|
472
|
+
if (av == null && bv == null) return 0;
|
|
473
|
+
if (av == null) return -1 * dir;
|
|
474
|
+
if (bv == null) return 1 * dir;
|
|
475
|
+
if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir;
|
|
476
|
+
return String(av).localeCompare(String(bv)) * dir;
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const localRows = filtered.slice(offset, offset + limit);
|
|
481
|
+
serverItems?.set(filtered.length);
|
|
482
|
+
data.set(localRows);
|
|
483
|
+
isFetching = false;
|
|
484
|
+
return { rows: localRows, total: filtered.length };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const res = await _clientDbInstance.query({
|
|
488
|
+
q,
|
|
489
|
+
filters: filtersNormalized,
|
|
490
|
+
order: sendModel?.order || [],
|
|
491
|
+
offset,
|
|
492
|
+
limit
|
|
493
|
+
});
|
|
494
|
+
const payload = res.payload ? res.payload : res;
|
|
495
|
+
serverItems?.set(payload.total ?? payload.totalCount ?? 0);
|
|
496
|
+
data.set(payload.rows ?? []);
|
|
497
|
+
isFetching = false;
|
|
498
|
+
return payload;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if ($data.length > 0 && serverSide && Number($serverItemCount) > 0) {
|
|
343
502
|
console.log('Table is not empty');
|
|
344
|
-
// $data = [];
|
|
345
503
|
return;
|
|
346
504
|
}
|
|
347
505
|
|
|
348
|
-
// throw new Error("Stack trace inspection");
|
|
349
506
|
isFetching = true;
|
|
350
507
|
const result = await utils.updateTable(
|
|
351
508
|
$pageSize,
|
|
@@ -419,6 +576,9 @@
|
|
|
419
576
|
if (resizable !== 'rows' && resizable !== 'both') {
|
|
420
577
|
return;
|
|
421
578
|
}
|
|
579
|
+
if (!$pageRows || $pageRows.length === 0) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
422
582
|
// Making sure tableRef is up to date and contains the new rows
|
|
423
583
|
// If it contains even one element, it means it contains them all
|
|
424
584
|
const e = tableRef?.querySelector(`#${tableId}-row-${$pageRows[0].id}`);
|
|
@@ -430,11 +590,59 @@
|
|
|
430
590
|
onDestroy(() => {
|
|
431
591
|
resizeColumnsObserver.disconnect();
|
|
432
592
|
resizeRowsObserver.disconnect();
|
|
593
|
+
if (_clientDbInstance) {
|
|
594
|
+
try {
|
|
595
|
+
_clientDbInstance.destroy();
|
|
596
|
+
} catch (e) {
|
|
597
|
+
// ignore
|
|
598
|
+
}
|
|
599
|
+
}
|
|
433
600
|
});
|
|
434
601
|
|
|
435
602
|
$: sortKeys = pluginStates.sort.sortKeys;
|
|
436
|
-
|
|
437
|
-
|
|
603
|
+
$: serverItemCountValue = clientDbEnabled
|
|
604
|
+
? Number($serverItemCount) > 0
|
|
605
|
+
? Math.max(
|
|
606
|
+
Number($serverItemCount),
|
|
607
|
+
initialServerCount,
|
|
608
|
+
clientDbSeedSize,
|
|
609
|
+
clientDbInitSource.length,
|
|
610
|
+
$data.length
|
|
611
|
+
)
|
|
612
|
+
: Math.max(initialServerCount, clientDbSeedSize, clientDbInitSource.length, $data.length)
|
|
613
|
+
: Number($serverItemCount);
|
|
614
|
+
onMount(async () => {
|
|
615
|
+
if (clientDbEnabled) {
|
|
616
|
+
const seed = Array.isArray(clientDbSeedData) ? clientDbSeedData : [];
|
|
617
|
+
const hasSeed = seed.length > 0;
|
|
618
|
+
clientDbSeedSize = seed.length;
|
|
619
|
+
if (hasSeed || ($data && $data.length > 0)) {
|
|
620
|
+
// clone source to avoid mutating config-provided arrays
|
|
621
|
+
clientDbInitSource = hasSeed ? [...seed] : [...$data];
|
|
622
|
+
const firstPage = clientDbInitSource.slice(0, $pageSize);
|
|
623
|
+
serverItems?.set(clientDbInitSource.length);
|
|
624
|
+
data.set(firstPage);
|
|
625
|
+
|
|
626
|
+
_clientDbInstance = new ClientDB(tableId);
|
|
627
|
+
_clientDbInstance
|
|
628
|
+
.init(clientDbInitSource)
|
|
629
|
+
.then(async () => {
|
|
630
|
+
// Only mark ready and release seed after the first DB-backed query succeeds.
|
|
631
|
+
const result = await updateTableWithParams();
|
|
632
|
+
clientDbReady = true;
|
|
633
|
+
clientDbInitSource = [];
|
|
634
|
+
return result;
|
|
635
|
+
})
|
|
636
|
+
.catch(() => {
|
|
637
|
+
clientDbReady = false;
|
|
638
|
+
});
|
|
639
|
+
} else if (!_clientDbInstance) {
|
|
640
|
+
_clientDbInstance = new ClientDB(tableId);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Only call server update when a real server config is present
|
|
645
|
+
if (server !== undefined) {
|
|
438
646
|
updateTableWithParams();
|
|
439
647
|
}
|
|
440
648
|
});
|
|
@@ -449,14 +657,19 @@
|
|
|
449
657
|
{#if search}
|
|
450
658
|
<form
|
|
451
659
|
class="flex gap-2"
|
|
452
|
-
on:submit|preventDefault={() => {
|
|
660
|
+
on:submit|preventDefault={async () => {
|
|
453
661
|
if (serverSide && !sendModel) {
|
|
454
662
|
throw new Error('Server-side configuration is missing');
|
|
455
663
|
} else {
|
|
456
664
|
sendModel.q = searchValue;
|
|
457
665
|
}
|
|
458
666
|
|
|
667
|
+
$pageIndex = 0;
|
|
459
668
|
$filterValue = searchValue;
|
|
669
|
+
if (serverSide) {
|
|
670
|
+
$data = [];
|
|
671
|
+
await updateTableWithParams();
|
|
672
|
+
}
|
|
460
673
|
}}
|
|
461
674
|
>
|
|
462
675
|
<div class="relative w-full flex items-center">
|
|
@@ -474,15 +687,20 @@
|
|
|
474
687
|
id="{tableId}-searchReset"
|
|
475
688
|
class="absolute right-3 items-center"
|
|
476
689
|
aria-label="Clear search"
|
|
477
|
-
on:click|preventDefault={() => {
|
|
690
|
+
on:click|preventDefault={async () => {
|
|
478
691
|
if (serverSide && !sendModel) {
|
|
479
692
|
throw new Error('Server-side configuration is missing');
|
|
480
693
|
} else {
|
|
481
694
|
sendModel.q = '';
|
|
482
695
|
}
|
|
483
696
|
|
|
697
|
+
$pageIndex = 0;
|
|
484
698
|
searchValue = '';
|
|
485
699
|
$filterValue = '';
|
|
700
|
+
if (serverSide) {
|
|
701
|
+
$data = [];
|
|
702
|
+
await updateTableWithParams();
|
|
703
|
+
}
|
|
486
704
|
}}><Fa icon={faXmark} /></button
|
|
487
705
|
>
|
|
488
706
|
</div>
|
|
@@ -492,14 +710,19 @@
|
|
|
492
710
|
id="{tableId}-searchSubmit"
|
|
493
711
|
class="btn variant-filled-primary"
|
|
494
712
|
aria-label="Search"
|
|
495
|
-
on:click|preventDefault={() => {
|
|
713
|
+
on:click|preventDefault={async () => {
|
|
496
714
|
if (serverSide && !sendModel) {
|
|
497
715
|
throw new Error('Server-side configuration is missing');
|
|
498
716
|
} else {
|
|
499
717
|
sendModel.q = searchValue;
|
|
500
718
|
}
|
|
501
719
|
|
|
720
|
+
$pageIndex = 0;
|
|
502
721
|
$filterValue = searchValue;
|
|
722
|
+
if (serverSide) {
|
|
723
|
+
$data = [];
|
|
724
|
+
await updateTableWithParams();
|
|
725
|
+
}
|
|
503
726
|
}}>Search</button
|
|
504
727
|
>
|
|
505
728
|
</form>
|
|
@@ -621,7 +844,7 @@
|
|
|
621
844
|
? `Remove sorting by ${cell.label} column`
|
|
622
845
|
: `Sort by ${cell.label} column in ascending order`}
|
|
623
846
|
>
|
|
624
|
-
{cell.render().replaceAll('%%%', '.')}
|
|
847
|
+
{String(cell.render()).replaceAll('%%%', '.')}
|
|
625
848
|
</span>
|
|
626
849
|
<div class="w-2">
|
|
627
850
|
{#if props.sort.order === 'asc'}
|
|
@@ -716,7 +939,7 @@
|
|
|
716
939
|
<TablePaginationServer
|
|
717
940
|
pageConfig={pluginStates.page}
|
|
718
941
|
{pageSizes}
|
|
719
|
-
itemCount={
|
|
942
|
+
itemCount={serverItemCountValue}
|
|
720
943
|
updateTable={updateTableWithParams}
|
|
721
944
|
id={tableId}
|
|
722
945
|
{data}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
// Check the type of the column
|
|
30
30
|
$values.forEach((item) => {
|
|
31
|
-
if (item) {
|
|
31
|
+
if (item !== null && item !== undefined) {
|
|
32
32
|
type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
|
|
33
33
|
|
|
34
34
|
if (type === 'object') {
|
|
@@ -119,6 +119,16 @@
|
|
|
119
119
|
value: FilterOptionsEnum.no,
|
|
120
120
|
label: 'Is not on'
|
|
121
121
|
}
|
|
122
|
+
],
|
|
123
|
+
boolean: [
|
|
124
|
+
{
|
|
125
|
+
value: FilterOptionsEnum.e,
|
|
126
|
+
label: 'Is equal to'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
value: FilterOptionsEnum.ne,
|
|
130
|
+
label: 'Is not equal to'
|
|
131
|
+
}
|
|
122
132
|
]
|
|
123
133
|
};
|
|
124
134
|
|
|
@@ -156,13 +166,15 @@
|
|
|
156
166
|
|
|
157
167
|
const optionChangeHandler = (e, index) => {
|
|
158
168
|
delete $filters[id][dropdowns[index].option];
|
|
169
|
+
const val = dropdowns[index].value;
|
|
159
170
|
$filters[id] = {
|
|
160
171
|
...$filters[id],
|
|
161
172
|
[e.target.value]:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
173
|
+
type === 'boolean'
|
|
174
|
+
? val === '' || val === undefined
|
|
175
|
+
? undefined
|
|
176
|
+
: getMissingValue(val as string) === 'true' || getMissingValue(val as string) === true
|
|
177
|
+
: getMissingValue(val as string)
|
|
166
178
|
};
|
|
167
179
|
$filters = $filters;
|
|
168
180
|
|
|
@@ -184,9 +196,12 @@
|
|
|
184
196
|
[id]: {
|
|
185
197
|
...$filters[id],
|
|
186
198
|
[dropdowns[index].option]:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
199
|
+
type === 'boolean'
|
|
200
|
+
? e.target.value === ''
|
|
201
|
+
? undefined
|
|
202
|
+
: getMissingValue(e.target.value) === 'true' ||
|
|
203
|
+
getMissingValue(e.target.value) === true
|
|
204
|
+
: getMissingValue(e.target.value)
|
|
190
205
|
}
|
|
191
206
|
};
|
|
192
207
|
};
|
|
@@ -304,6 +319,17 @@
|
|
|
304
319
|
bind:value={dropdown.value}
|
|
305
320
|
aria-label="Filter value"
|
|
306
321
|
/>
|
|
322
|
+
{:else if type === 'boolean'}
|
|
323
|
+
<select
|
|
324
|
+
class="select border border-primary-500 text-sm p-1"
|
|
325
|
+
on:change={(e) => valueChangeHandler(e, index)}
|
|
326
|
+
bind:value={dropdown.value}
|
|
327
|
+
aria-label="Filter value"
|
|
328
|
+
>
|
|
329
|
+
<option value="" disabled selected>Select value...</option>
|
|
330
|
+
<option value="true">True</option>
|
|
331
|
+
<option value="false">False</option>
|
|
332
|
+
</select>
|
|
307
333
|
{:else}
|
|
308
334
|
<input
|
|
309
335
|
type="date"
|
|
@@ -28,8 +28,11 @@
|
|
|
28
28
|
|
|
29
29
|
// Check the type of the column
|
|
30
30
|
$values.forEach((item) => {
|
|
31
|
-
if (item) {
|
|
32
|
-
|
|
31
|
+
if (item !== null && item !== undefined) {
|
|
32
|
+
const rawType = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
|
|
33
|
+
|
|
34
|
+
// Treat bigint exactly as 'number' for layout options
|
|
35
|
+
type = rawType === 'bigint' ? 'number' : rawType;
|
|
33
36
|
|
|
34
37
|
if (type === 'object') {
|
|
35
38
|
if ((toFilterableValueFn ? toFilterableValueFn(item) : item) instanceof Date) {
|
|
@@ -119,6 +122,16 @@
|
|
|
119
122
|
// value: FilterOptionsEnum.no,
|
|
120
123
|
// label: 'Is not on'
|
|
121
124
|
// }
|
|
125
|
+
],
|
|
126
|
+
boolean: [
|
|
127
|
+
{
|
|
128
|
+
value: FilterOptionsEnum.e,
|
|
129
|
+
label: 'Is equal to'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
value: FilterOptionsEnum.ne,
|
|
133
|
+
label: 'Is not equal to'
|
|
134
|
+
}
|
|
122
135
|
]
|
|
123
136
|
};
|
|
124
137
|
|
|
@@ -134,7 +147,9 @@
|
|
|
134
147
|
const stringValues = $values.map((item) => (toStringFn ? toStringFn(item) : item));
|
|
135
148
|
|
|
136
149
|
const missingValues = stringValues.reduce((acc, item, index) => {
|
|
137
|
-
|
|
150
|
+
// Convert everything (including BigInt values) to string first
|
|
151
|
+
const strRepresentation = String(item).toLowerCase();
|
|
152
|
+
acc[strRepresentation] = $values[index];
|
|
138
153
|
return acc;
|
|
139
154
|
}, {});
|
|
140
155
|
|
|
@@ -146,9 +161,15 @@
|
|
|
146
161
|
|
|
147
162
|
const optionChangeHandler = (e, index) => {
|
|
148
163
|
delete $filters[id][dropdowns[index].option];
|
|
164
|
+
const val = dropdowns[index].value;
|
|
149
165
|
$filters[id] = {
|
|
150
166
|
...$filters[id],
|
|
151
|
-
[e.target.value]:
|
|
167
|
+
[e.target.value]:
|
|
168
|
+
type === 'boolean'
|
|
169
|
+
? val === '' || val === undefined
|
|
170
|
+
? undefined
|
|
171
|
+
: getMissingValue(val as string) === 'true' || getMissingValue(val as string) === true
|
|
172
|
+
: getMissingValue(dropdowns[index].value as string)
|
|
152
173
|
};
|
|
153
174
|
$filters = $filters;
|
|
154
175
|
|
|
@@ -169,10 +190,34 @@
|
|
|
169
190
|
...$filters,
|
|
170
191
|
[id]: {
|
|
171
192
|
...$filters[id],
|
|
172
|
-
[dropdowns[index].option]:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
[dropdowns[index].option]: (() => {
|
|
194
|
+
const rawVal = getMissingValue(e.target.value);
|
|
195
|
+
|
|
196
|
+
if (rawVal === '' || rawVal === undefined || rawVal === null) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (type === 'number') {
|
|
201
|
+
const cleanStr = String(rawVal).trim();
|
|
202
|
+
// If it's a 16+ digit integer, parse as BigInt to keep precision
|
|
203
|
+
const isBigInt = /^\d+$/.test(cleanStr) && cleanStr.length >= 16;
|
|
204
|
+
|
|
205
|
+
if (isBigInt) {
|
|
206
|
+
try {
|
|
207
|
+
return BigInt(cleanStr);
|
|
208
|
+
} catch {
|
|
209
|
+
return cleanStr;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return parseFloat(cleanStr) || undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (type === 'boolean') {
|
|
216
|
+
return rawVal === 'true' || rawVal === true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return rawVal;
|
|
220
|
+
})()
|
|
176
221
|
}
|
|
177
222
|
};
|
|
178
223
|
};
|
|
@@ -284,7 +329,7 @@
|
|
|
284
329
|
on:click|preventDefault={() => removeFilter(dropdown.option)}
|
|
285
330
|
on:keydown|preventDefault={() => removeFilter(dropdown.option)}
|
|
286
331
|
aria-label="Remove filter"
|
|
287
|
-
|
|
332
|
+
>
|
|
288
333
|
<Fa icon={faXmark} />
|
|
289
334
|
</div>
|
|
290
335
|
{/if}
|
|
@@ -305,6 +350,17 @@
|
|
|
305
350
|
bind:value={dropdown.value}
|
|
306
351
|
aria-label="Filter value"
|
|
307
352
|
/>
|
|
353
|
+
{:else if type === 'boolean'}
|
|
354
|
+
<select
|
|
355
|
+
class="select border border-primary-500 text-sm p-1"
|
|
356
|
+
on:change={(e) => valueChangeHandler(e, index)}
|
|
357
|
+
bind:value={dropdown.value}
|
|
358
|
+
aria-label="Filter value"
|
|
359
|
+
>
|
|
360
|
+
<option value="" disabled selected>Select value...</option>
|
|
361
|
+
<option value="true">True</option>
|
|
362
|
+
<option value="false">False</option>
|
|
363
|
+
</select>
|
|
308
364
|
{:else}
|
|
309
365
|
<input
|
|
310
366
|
type="date"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
: `Displaying items ${$pageIndex * $pageSize + 1} - ${Math.min(
|
|
29
29
|
($pageIndex + 1) * $pageSize,
|
|
30
30
|
itemCount
|
|
31
|
-
|
|
31
|
+
)} of ${Math.min($pageCount * $pageSize, itemCount)}`;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
$: paginationSettings = {
|
|
@@ -38,7 +38,11 @@
|
|
|
38
38
|
amounts: pageSizes
|
|
39
39
|
};
|
|
40
40
|
$: $pageSize = pageSizeDropdownValue;
|
|
41
|
-
$: $pageCount,
|
|
41
|
+
$: ($pageCount,
|
|
42
|
+
$pageIndex,
|
|
43
|
+
$pageSize,
|
|
44
|
+
itemCount,
|
|
45
|
+
(indexInformation = getIndexInfomationString()));
|
|
42
46
|
</script>
|
|
43
47
|
|
|
44
48
|
<div class="grid grid-cols-3 w-full items-stretch gap-10">
|