vueless 1.3.9-beta.13 → 1.3.9-beta.15
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/package.json +2 -2
- package/ui.data-table/UTable.vue +328 -153
- package/ui.data-table/UTableRow.vue +299 -166
- package/ui.data-table/tests/UTable.test.ts +18 -6
- package/ui.data-table/tests/UTableRow.test.ts +31 -104
- package/ui.data-table/types.ts +8 -0
- package/ui.text-number/UNumber.vue +36 -2
- package/ui.text-number/config.ts +1 -0
- package/ui.text-number/storybook/stories.ts +11 -0
- package/ui.text-number/tests/UNumber.test.ts +90 -0
- package/ui.text-number/types.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vueless",
|
|
3
|
-
"version": "1.3.9-beta.
|
|
3
|
+
"version": "1.3.9-beta.15",
|
|
4
4
|
"description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
|
|
5
5
|
"author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
|
|
6
6
|
"homepage": "https://vueless.com",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@vue/eslint-config-typescript": "^14.6.0",
|
|
58
58
|
"@vue/test-utils": "^2.4.6",
|
|
59
59
|
"@vue/tsconfig": "^0.7.0",
|
|
60
|
-
"@vueless/storybook": "^1.4.
|
|
60
|
+
"@vueless/storybook": "^1.4.9",
|
|
61
61
|
"eslint": "^9.32.0",
|
|
62
62
|
"eslint-plugin-storybook": "^10.0.2",
|
|
63
63
|
"eslint-plugin-vue": "^10.3.0",
|
package/ui.data-table/UTable.vue
CHANGED
|
@@ -4,33 +4,36 @@ import {
|
|
|
4
4
|
ref,
|
|
5
5
|
computed,
|
|
6
6
|
watch,
|
|
7
|
+
watchEffect,
|
|
7
8
|
useSlots,
|
|
8
9
|
nextTick,
|
|
9
10
|
onMounted,
|
|
10
11
|
onBeforeUnmount,
|
|
11
12
|
useTemplateRef,
|
|
13
|
+
h,
|
|
12
14
|
} from "vue";
|
|
13
15
|
import { isEqual } from "lodash-es";
|
|
14
16
|
|
|
15
|
-
import UEmpty from "../ui.container-empty/UEmpty.vue";
|
|
16
|
-
import UCheckbox from "../ui.form-checkbox/UCheckbox.vue";
|
|
17
|
-
import ULoaderProgress from "../ui.loader-progress/ULoaderProgress.vue";
|
|
18
|
-
import UTableRow from "./UTableRow.vue";
|
|
19
|
-
import UDivider from "../ui.container-divider/UDivider.vue";
|
|
20
|
-
|
|
21
17
|
import { useUI } from "../composables/useUI";
|
|
22
18
|
import { useVirtualScroll } from "../composables/useVirtualScroll";
|
|
19
|
+
import { useComponentLocaleMessages } from "../composables/useComponentLocaleMassages";
|
|
23
20
|
import { getDefaults, cx, getMergedConfig } from "../utils/ui";
|
|
24
21
|
import { hasSlotContent } from "../utils/helper";
|
|
25
|
-
import { useComponentLocaleMessages } from "../composables/useComponentLocaleMassages";
|
|
26
|
-
|
|
27
|
-
import defaultConfig from "./config";
|
|
28
|
-
import { normalizeColumns, mapRowColumns, getFlatRows, getRowChildrenIds } from "./utilTable";
|
|
29
22
|
|
|
30
23
|
import { PX_IN_REM } from "../constants";
|
|
31
24
|
import { COMPONENT_NAME } from "./constants";
|
|
32
25
|
|
|
33
|
-
import
|
|
26
|
+
import defaultConfig from "./config";
|
|
27
|
+
import { normalizeColumns, getFlatRows, getRowChildrenIds } from "./utilTable";
|
|
28
|
+
import { StickySide } from "./types";
|
|
29
|
+
|
|
30
|
+
import UEmpty from "../ui.container-empty/UEmpty.vue";
|
|
31
|
+
import UCheckbox from "../ui.form-checkbox/UCheckbox.vue";
|
|
32
|
+
import ULoaderProgress from "../ui.loader-progress/ULoaderProgress.vue";
|
|
33
|
+
import UDivider from "../ui.container-divider/UDivider.vue";
|
|
34
|
+
import UTableRow from "./UTableRow.vue";
|
|
35
|
+
|
|
36
|
+
import type { ComputedRef, VNode } from "vue";
|
|
34
37
|
import type { Config as UDividerConfig } from "../ui.container-divider/types";
|
|
35
38
|
import type {
|
|
36
39
|
Cell,
|
|
@@ -43,8 +46,8 @@ import type {
|
|
|
43
46
|
FlatRow,
|
|
44
47
|
ColumnObject,
|
|
45
48
|
SearchMatch,
|
|
49
|
+
UTableRowProps,
|
|
46
50
|
} from "./types";
|
|
47
|
-
import { StickySide } from "./types";
|
|
48
51
|
|
|
49
52
|
defineOptions({ inheritAttrs: false });
|
|
50
53
|
|
|
@@ -133,6 +136,8 @@ const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>
|
|
|
133
136
|
const localSelectedRows = shallowRef<Row[]>([]);
|
|
134
137
|
const localExpandedRows = shallowRef<RowId[]>([]);
|
|
135
138
|
|
|
139
|
+
const expandedRowsSet = computed(() => new Set(localExpandedRows.value));
|
|
140
|
+
|
|
136
141
|
const sortedRows: ComputedRef<FlatRow[]> = computed(() => {
|
|
137
142
|
const headerKeys = props.columns.map((column) =>
|
|
138
143
|
typeof column === "object" ? column.key : column,
|
|
@@ -206,9 +211,9 @@ const tableRowWidthStyle = computed(() => ({ width: `${tableWidth.value / PX_IN_
|
|
|
206
211
|
const flatTableRows = computed(() => getFlatRows(props.rows));
|
|
207
212
|
|
|
208
213
|
const visibleFlatRows = computed(() => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
);
|
|
214
|
+
const expanded = expandedRowsSet.value;
|
|
215
|
+
|
|
216
|
+
return flatTableRows.value.filter((row) => !row.parentRowId || expanded.has(row.parentRowId));
|
|
212
217
|
});
|
|
213
218
|
|
|
214
219
|
const virtualScroll = useVirtualScroll({
|
|
@@ -219,11 +224,25 @@ const virtualScroll = useVirtualScroll({
|
|
|
219
224
|
});
|
|
220
225
|
|
|
221
226
|
const renderedRows = computed(() => {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
227
|
+
if (props.virtualScroll) {
|
|
228
|
+
// For virtual scroll, we still need to slice based on visible rows
|
|
229
|
+
// but we need to map back to the actual rows from flatTableRows
|
|
230
|
+
const visibleSlice = visibleFlatRows.value.slice(
|
|
231
|
+
virtualScroll.startIndex.value,
|
|
232
|
+
virtualScroll.endIndex.value,
|
|
233
|
+
);
|
|
234
|
+
const visibleIds = new Set(visibleSlice.map((row) => row.id));
|
|
235
|
+
|
|
236
|
+
return flatTableRows.value.filter((row) => visibleIds.has(row.id));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return flatTableRows.value;
|
|
225
240
|
});
|
|
226
241
|
|
|
242
|
+
function isRowVisible(row: FlatRow): boolean {
|
|
243
|
+
return !row.parentRowId || expandedRowsSet.value.has(row.parentRowId);
|
|
244
|
+
}
|
|
245
|
+
|
|
227
246
|
const searchMatches = computed<SearchMatch[]>(() => {
|
|
228
247
|
const query = props.search?.toLowerCase();
|
|
229
248
|
|
|
@@ -310,48 +329,155 @@ const totalSearchMatches = computed(() => {
|
|
|
310
329
|
return count;
|
|
311
330
|
});
|
|
312
331
|
|
|
313
|
-
|
|
314
|
-
|
|
332
|
+
const selectedRowIds = computed(() => {
|
|
333
|
+
return new Set(localSelectedRows.value.map((row) => row.id));
|
|
315
334
|
});
|
|
316
335
|
|
|
317
|
-
|
|
318
|
-
|
|
336
|
+
const isSelectedAllRows = computed(() => {
|
|
337
|
+
return localSelectedRows.value.length === flatTableRows.value.length;
|
|
338
|
+
});
|
|
319
339
|
|
|
320
|
-
|
|
321
|
-
|
|
340
|
+
// Conditional watchers - only create listeners when their associated features are enabled
|
|
341
|
+
// Using watchEffect to dynamically create/destroy watchers based on feature state
|
|
342
|
+
|
|
343
|
+
// Search-related watchers
|
|
344
|
+
// Note: totalSearchMatches watcher always runs to emit events (even when search is cleared)
|
|
345
|
+
watch(totalSearchMatches, (count) => emit("search", count), { flush: "post" });
|
|
346
|
+
|
|
347
|
+
// activeMatch watcher - only created when search is active
|
|
348
|
+
let stopActiveMatchWatch: (() => void) | null = null;
|
|
349
|
+
|
|
350
|
+
watchEffect(() => {
|
|
351
|
+
if (props.search) {
|
|
352
|
+
// Create watcher if it doesn't exist
|
|
353
|
+
if (!stopActiveMatchWatch) {
|
|
354
|
+
stopActiveMatchWatch = watch(
|
|
355
|
+
activeMatch,
|
|
356
|
+
(match) => {
|
|
357
|
+
if (!match) return;
|
|
358
|
+
|
|
359
|
+
if (props.virtualScroll) {
|
|
360
|
+
const rowIndex = visibleFlatRows.value.findIndex((row) => row.id === match.rowId);
|
|
361
|
+
|
|
362
|
+
if (rowIndex === -1) return;
|
|
363
|
+
|
|
364
|
+
virtualScroll.scrollToIndex(rowIndex);
|
|
365
|
+
} else {
|
|
366
|
+
scrollToRow(match.rowId);
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
{ flush: "post" },
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
// Cleanup watcher if it exists
|
|
374
|
+
if (stopActiveMatchWatch) {
|
|
375
|
+
stopActiveMatchWatch();
|
|
376
|
+
stopActiveMatchWatch = null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Selection-related watchers (only created when selectable is enabled)
|
|
382
|
+
let stopLocalSelectedRowsWatch: (() => void) | null = null;
|
|
383
|
+
let stopSelectedRowsWatch: (() => void) | null = null;
|
|
384
|
+
let stopSelectAllWatch: (() => void) | null = null;
|
|
385
|
+
|
|
386
|
+
watchEffect(() => {
|
|
387
|
+
if (props.selectable) {
|
|
388
|
+
// Create watchers if they don't exist
|
|
389
|
+
if (!stopLocalSelectedRowsWatch) {
|
|
390
|
+
stopLocalSelectedRowsWatch = watch(localSelectedRows, onChangeLocalSelectedRows);
|
|
391
|
+
}
|
|
322
392
|
|
|
323
|
-
if (
|
|
393
|
+
if (!stopSelectedRowsWatch) {
|
|
394
|
+
stopSelectedRowsWatch = watch(() => props.selectedRows, onChangeSelectedRows, {
|
|
395
|
+
immediate: true,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
324
398
|
|
|
325
|
-
|
|
399
|
+
if (!stopSelectAllWatch) {
|
|
400
|
+
stopSelectAllWatch = watch(selectAll, onChangeSelectAll);
|
|
401
|
+
}
|
|
326
402
|
} else {
|
|
327
|
-
|
|
403
|
+
// Cleanup watchers if they exist
|
|
404
|
+
if (stopLocalSelectedRowsWatch) {
|
|
405
|
+
stopLocalSelectedRowsWatch();
|
|
406
|
+
stopLocalSelectedRowsWatch = null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (stopSelectedRowsWatch) {
|
|
410
|
+
stopSelectedRowsWatch();
|
|
411
|
+
stopSelectedRowsWatch = null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (stopSelectAllWatch) {
|
|
415
|
+
stopSelectAllWatch();
|
|
416
|
+
stopSelectAllWatch = null;
|
|
417
|
+
}
|
|
328
418
|
}
|
|
329
419
|
});
|
|
330
420
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
});
|
|
421
|
+
// Expansion watcher (always register as it's a core feature)
|
|
422
|
+
watch(() => props.expandedRows, onChangeExpandedRows, { immediate: true });
|
|
334
423
|
|
|
335
|
-
|
|
336
|
-
|
|
424
|
+
// Sticky header watcher (only created when stickyHeader is enabled)
|
|
425
|
+
let stopHeaderStickyWatch: (() => void) | null = null;
|
|
426
|
+
|
|
427
|
+
watchEffect(() => {
|
|
428
|
+
if (props.stickyHeader) {
|
|
429
|
+
// Create watcher if it doesn't exist
|
|
430
|
+
if (!stopHeaderStickyWatch) {
|
|
431
|
+
stopHeaderStickyWatch = watch(isHeaderSticky, setHeaderCellWidth);
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
// Cleanup watcher if it exists
|
|
435
|
+
if (stopHeaderStickyWatch) {
|
|
436
|
+
stopHeaderStickyWatch();
|
|
437
|
+
stopHeaderStickyWatch = null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
337
440
|
});
|
|
338
441
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
)
|
|
442
|
+
// Sticky footer watcher (only created when stickyFooter is enabled)
|
|
443
|
+
let stopFooterStickyWatch: (() => void) | null = null;
|
|
444
|
+
|
|
445
|
+
watchEffect(() => {
|
|
446
|
+
if (props.stickyFooter) {
|
|
447
|
+
// Create watcher if it doesn't exist
|
|
448
|
+
if (!stopFooterStickyWatch) {
|
|
449
|
+
stopFooterStickyWatch = watch(isFooterSticky, (newValue) =>
|
|
450
|
+
newValue ? nextTick(setFooterCellWidth) : setFooterCellWidth(null),
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// Cleanup watcher if it exists
|
|
455
|
+
if (stopFooterStickyWatch) {
|
|
456
|
+
stopFooterStickyWatch();
|
|
457
|
+
stopFooterStickyWatch = null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
});
|
|
347
461
|
|
|
348
462
|
let resizeObserver: ResizeObserver | null = null;
|
|
349
463
|
let scrollRafId: number | null = null;
|
|
464
|
+
let resizeDebounceId: ReturnType<typeof setTimeout> | null = null;
|
|
465
|
+
|
|
466
|
+
function scheduleWindowResize(tableRectWidth: number, tableRectHeight: number) {
|
|
467
|
+
if (resizeDebounceId) clearTimeout(resizeDebounceId);
|
|
468
|
+
|
|
469
|
+
resizeDebounceId = setTimeout(() => {
|
|
470
|
+
resizeDebounceId = null;
|
|
471
|
+
onWindowResize();
|
|
472
|
+
|
|
473
|
+
tableHeight.value = tableRectHeight;
|
|
474
|
+
tableWidth.value = tableRectWidth;
|
|
475
|
+
}, 300);
|
|
476
|
+
}
|
|
350
477
|
|
|
351
478
|
onMounted(async () => {
|
|
352
479
|
document.addEventListener("keyup", onKeyupEsc);
|
|
353
480
|
document.addEventListener("scroll", onScroll, { passive: true });
|
|
354
|
-
window.addEventListener("resize", onWindowResize);
|
|
355
481
|
|
|
356
482
|
await nextTick();
|
|
357
483
|
updateHeaderOffsetTop();
|
|
@@ -363,9 +489,7 @@ onMounted(async () => {
|
|
|
363
489
|
|
|
364
490
|
if (!entry) return;
|
|
365
491
|
|
|
366
|
-
|
|
367
|
-
tableWidth.value = entry.contentRect.width;
|
|
368
|
-
calculateStickyColumnPositions();
|
|
492
|
+
scheduleWindowResize(entry.contentRect.width, entry.contentRect.height);
|
|
369
493
|
});
|
|
370
494
|
|
|
371
495
|
resizeObserver.observe(tableWrapperRef.value);
|
|
@@ -375,12 +499,15 @@ onMounted(async () => {
|
|
|
375
499
|
onBeforeUnmount(() => {
|
|
376
500
|
document.removeEventListener("keyup", onKeyupEsc);
|
|
377
501
|
document.removeEventListener("scroll", onScroll);
|
|
378
|
-
window.removeEventListener("resize", onWindowResize);
|
|
379
502
|
resizeObserver?.disconnect();
|
|
380
503
|
|
|
381
504
|
if (scrollRafId !== null) {
|
|
382
505
|
cancelAnimationFrame(scrollRafId);
|
|
383
506
|
}
|
|
507
|
+
|
|
508
|
+
if (resizeDebounceId !== null) {
|
|
509
|
+
clearTimeout(resizeDebounceId);
|
|
510
|
+
}
|
|
384
511
|
});
|
|
385
512
|
|
|
386
513
|
function onChangeSelectedRows() {
|
|
@@ -396,8 +523,6 @@ function onChangeExpandedRows() {
|
|
|
396
523
|
}
|
|
397
524
|
|
|
398
525
|
function onWindowResize() {
|
|
399
|
-
tableWidth.value = tableWrapperRef.value?.offsetWidth || 0;
|
|
400
|
-
|
|
401
526
|
updateHeaderOffsetTop();
|
|
402
527
|
setHeaderCellWidth();
|
|
403
528
|
setFooterCellWidth();
|
|
@@ -612,6 +737,85 @@ function onClickCell(cell: Cell, row: Row, key: string | number) {
|
|
|
612
737
|
emit("clickCell", cell, row, key);
|
|
613
738
|
}
|
|
614
739
|
|
|
740
|
+
function onBodyClick(event: MouseEvent) {
|
|
741
|
+
const target = event.target as HTMLElement;
|
|
742
|
+
|
|
743
|
+
const row = target.closest("tr");
|
|
744
|
+
|
|
745
|
+
if (!row) return;
|
|
746
|
+
|
|
747
|
+
const rowId = row.getAttribute("data-row-id");
|
|
748
|
+
|
|
749
|
+
if (!rowId) return;
|
|
750
|
+
|
|
751
|
+
const rowData = flatTableRows.value.find((r) => String(r.id) === rowId);
|
|
752
|
+
|
|
753
|
+
if (!rowData) return;
|
|
754
|
+
|
|
755
|
+
// Handle checkbox toggle via event delegation.
|
|
756
|
+
// When unchecking, UCheckbox.onIconClick() calls input.click() which dispatches
|
|
757
|
+
// a second (programmatic) click event with target=INPUT and isTrusted=false.
|
|
758
|
+
// Without this guard, onToggleRowCheckbox fires twice (deselect then reselect).
|
|
759
|
+
const checkboxCell = target.closest("td[data-checkbox-id]");
|
|
760
|
+
|
|
761
|
+
if (checkboxCell) {
|
|
762
|
+
if (target.tagName === "INPUT" && !event.isTrusted) return;
|
|
763
|
+
|
|
764
|
+
onToggleRowCheckbox(rowData);
|
|
765
|
+
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Handle expand icon toggle via event delegation
|
|
770
|
+
const expandIconElement = target.closest("[data-expand-icon]");
|
|
771
|
+
|
|
772
|
+
if (expandIconElement) {
|
|
773
|
+
onToggleExpand(rowData);
|
|
774
|
+
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Handle row click via event delegation
|
|
779
|
+
onClickRow(rowData);
|
|
780
|
+
|
|
781
|
+
// Handle cell click via event delegation
|
|
782
|
+
const cell = target.closest("td");
|
|
783
|
+
|
|
784
|
+
if (cell) {
|
|
785
|
+
const cellKey = cell.getAttribute("data-cell-key");
|
|
786
|
+
|
|
787
|
+
if (cellKey) {
|
|
788
|
+
const cellValue = rowData[cellKey];
|
|
789
|
+
|
|
790
|
+
onClickCell(cellValue, rowData, cellKey);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function onBodyDoubleClick(event: MouseEvent) {
|
|
796
|
+
const target = event.target as HTMLElement;
|
|
797
|
+
|
|
798
|
+
const row = target.closest("tr");
|
|
799
|
+
|
|
800
|
+
if (!row) return;
|
|
801
|
+
|
|
802
|
+
const rowId = row.getAttribute("data-row-id");
|
|
803
|
+
|
|
804
|
+
if (!rowId) return;
|
|
805
|
+
|
|
806
|
+
const rowData = flatTableRows.value.find((r) => String(r.id) === rowId);
|
|
807
|
+
|
|
808
|
+
if (!rowData) return;
|
|
809
|
+
|
|
810
|
+
const selection = window.getSelection();
|
|
811
|
+
|
|
812
|
+
if (selection) {
|
|
813
|
+
selection.removeAllRanges();
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
onDoubleClickRow(rowData);
|
|
817
|
+
}
|
|
818
|
+
|
|
615
819
|
function onChangeSelectAll(selectAll: boolean) {
|
|
616
820
|
if (selectAll && canSelectAll.value) {
|
|
617
821
|
localSelectedRows.value = [...flatTableRows.value];
|
|
@@ -631,6 +835,7 @@ function onChangeLocalSelectedRows(selectedRows: Row[]) {
|
|
|
631
835
|
nextTick(setHeaderCellWidth);
|
|
632
836
|
}
|
|
633
837
|
|
|
838
|
+
// Set flag to prevent selectAll watcher from triggering
|
|
634
839
|
selectAll.value = !!selectedRows.length;
|
|
635
840
|
|
|
636
841
|
emit("update:selectedRows", localSelectedRows.value);
|
|
@@ -642,9 +847,8 @@ function clearSelectedItems() {
|
|
|
642
847
|
|
|
643
848
|
function onToggleExpand(row: Row) {
|
|
644
849
|
const expanded = localExpandedRows.value;
|
|
645
|
-
const targetIndex = expanded.indexOf(row.id);
|
|
646
850
|
|
|
647
|
-
if (
|
|
851
|
+
if (expandedRowsSet.value.has(row.id)) {
|
|
648
852
|
const idsToRemove = new Set([row.id, ...getRowChildrenIds(row)]);
|
|
649
853
|
|
|
650
854
|
localExpandedRows.value = expanded.filter((id) => !idsToRemove.has(id));
|
|
@@ -853,6 +1057,68 @@ const tableRowAttrs = {
|
|
|
853
1057
|
bodyCellSearchMatchActiveAttrs,
|
|
854
1058
|
bodyCellSearchMatchTextActiveAttrs,
|
|
855
1059
|
} as unknown as UTableRowAttrs;
|
|
1060
|
+
|
|
1061
|
+
function renderDateDividerRow(row: FlatRow, rowIndex: number): VNode | null {
|
|
1062
|
+
if (!isShownDateDivider(rowIndex) || !row.rowDate) return null;
|
|
1063
|
+
|
|
1064
|
+
const isSelected = isRowSelectedWithin(rowIndex);
|
|
1065
|
+
|
|
1066
|
+
const propsDateDivider = isSelected
|
|
1067
|
+
? bodyRowCheckedDateDividerAttrs.value
|
|
1068
|
+
: bodyRowDateDividerAttrs.value;
|
|
1069
|
+
|
|
1070
|
+
const dividerNode = h(UDivider, {
|
|
1071
|
+
label: getDateDividerData(row.rowDate).label,
|
|
1072
|
+
...(isSelected ? bodySelectedDateDividerAttrs.value : bodyDateDividerAttrs.value),
|
|
1073
|
+
config: getDateDividerConfig(row, isSelected),
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
return h("tr", { ...propsDateDivider, key: `date-divider-${row.id}` }, [
|
|
1077
|
+
h(
|
|
1078
|
+
"td",
|
|
1079
|
+
{
|
|
1080
|
+
...bodyCellDateDividerAttrs.value,
|
|
1081
|
+
colspan: colsCount.value,
|
|
1082
|
+
},
|
|
1083
|
+
[dividerNode],
|
|
1084
|
+
),
|
|
1085
|
+
]);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function renderTableRow(row: FlatRow, rowIndex: number): VNode {
|
|
1089
|
+
return h(
|
|
1090
|
+
UTableRow,
|
|
1091
|
+
{
|
|
1092
|
+
key: row.id,
|
|
1093
|
+
selectable: props.selectable,
|
|
1094
|
+
rowIndex,
|
|
1095
|
+
row,
|
|
1096
|
+
columns: normalizedColumns.value,
|
|
1097
|
+
config: config.value,
|
|
1098
|
+
attrs: tableRowAttrs as unknown as UTableRowAttrs,
|
|
1099
|
+
colsCount: colsCount.value,
|
|
1100
|
+
nestedLevel: Number(row.nestedLevel || 0),
|
|
1101
|
+
emptyCellLabel: props.emptyCellLabel,
|
|
1102
|
+
"data-test": getDataTest("row"),
|
|
1103
|
+
"data-row-id": row.id,
|
|
1104
|
+
isExpanded: expandedRowsSet.value.has(row.id),
|
|
1105
|
+
isChecked: isRowSelected(row),
|
|
1106
|
+
isVisible: isRowVisible(row),
|
|
1107
|
+
columnPositions: columnPositions.value,
|
|
1108
|
+
search: props.search,
|
|
1109
|
+
searchMatchColumns: getRowSearchMatchColumns(row),
|
|
1110
|
+
activeSearchMatchColumn: getRowActiveSearchMatchColumn(row),
|
|
1111
|
+
textEllipsis: props.textEllipsis,
|
|
1112
|
+
} as unknown as UTableRowProps,
|
|
1113
|
+
slots,
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function renderRowTemplate(row: FlatRow, rowIndex: number): VNode[] {
|
|
1118
|
+
return [renderDateDividerRow(row, rowIndex), renderTableRow(row, rowIndex)].filter(
|
|
1119
|
+
Boolean,
|
|
1120
|
+
) as VNode[];
|
|
1121
|
+
}
|
|
856
1122
|
</script>
|
|
857
1123
|
|
|
858
1124
|
<template>
|
|
@@ -893,10 +1159,10 @@ const tableRowAttrs = {
|
|
|
893
1159
|
>
|
|
894
1160
|
<template v-if="hasSlotContent($slots[`header-${column.key}`], { column, index })">
|
|
895
1161
|
<!--
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1162
|
+
@slot Use it to customize needed header cell.
|
|
1163
|
+
@binding {object} column
|
|
1164
|
+
@binding {number} index
|
|
1165
|
+
-->
|
|
900
1166
|
<slot :name="`header-${column.key}`" :column="column" :index="index" />
|
|
901
1167
|
</template>
|
|
902
1168
|
|
|
@@ -1042,7 +1308,12 @@ const tableRowAttrs = {
|
|
|
1042
1308
|
<ULoaderProgress :loading="loading" v-bind="headerLoaderAttrs" />
|
|
1043
1309
|
</thead>
|
|
1044
1310
|
|
|
1045
|
-
<tbody
|
|
1311
|
+
<tbody
|
|
1312
|
+
v-if="sortedRows.length"
|
|
1313
|
+
v-bind="bodyAttrs"
|
|
1314
|
+
@click="onBodyClick"
|
|
1315
|
+
@dblclick="onBodyDoubleClick"
|
|
1316
|
+
>
|
|
1046
1317
|
<tr
|
|
1047
1318
|
v-if="hasBeforeFirstRowSlot"
|
|
1048
1319
|
v-bind="isRowSelected(sortedRows[0]) ? beforeBodyRowCheckedAttrs : beforeBodyRowAttrs"
|
|
@@ -1060,105 +1331,9 @@ const tableRowAttrs = {
|
|
|
1060
1331
|
/>
|
|
1061
1332
|
</tr>
|
|
1062
1333
|
|
|
1063
|
-
<
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
v-bind="bodyRowDateDividerAttrs"
|
|
1067
|
-
>
|
|
1068
|
-
<td v-bind="bodyCellDateDividerAttrs" :colspan="colsCount">
|
|
1069
|
-
<UDivider
|
|
1070
|
-
:label="getDateDividerData(row.rowDate).label"
|
|
1071
|
-
v-bind="bodyDateDividerAttrs"
|
|
1072
|
-
:config="getDateDividerConfig(row, false)"
|
|
1073
|
-
/>
|
|
1074
|
-
</td>
|
|
1075
|
-
</tr>
|
|
1076
|
-
|
|
1077
|
-
<tr
|
|
1078
|
-
v-if="isShownDateDivider(rowIndex) && isRowSelectedWithin(rowIndex) && row.rowDate"
|
|
1079
|
-
v-bind="bodyRowCheckedDateDividerAttrs"
|
|
1080
|
-
>
|
|
1081
|
-
<td v-bind="bodyCellDateDividerAttrs" :colspan="colsCount">
|
|
1082
|
-
<UDivider
|
|
1083
|
-
:label="getDateDividerData(row.rowDate).label"
|
|
1084
|
-
v-bind="bodySelectedDateDividerAttrs"
|
|
1085
|
-
:config="getDateDividerConfig(row, true)"
|
|
1086
|
-
/>
|
|
1087
|
-
</td>
|
|
1088
|
-
</tr>
|
|
1089
|
-
|
|
1090
|
-
<UTableRow
|
|
1091
|
-
:selectable="selectable"
|
|
1092
|
-
:row="row"
|
|
1093
|
-
:columns="normalizedColumns"
|
|
1094
|
-
:config="config"
|
|
1095
|
-
:attrs="tableRowAttrs as unknown as UTableRowAttrs"
|
|
1096
|
-
:cols-count="colsCount"
|
|
1097
|
-
:nested-level="Number(row.nestedLevel || 0)"
|
|
1098
|
-
:empty-cell-label="emptyCellLabel"
|
|
1099
|
-
:data-test="getDataTest('row')"
|
|
1100
|
-
:data-row-id="row.id"
|
|
1101
|
-
:is-expanded="localExpandedRows.includes(row.id)"
|
|
1102
|
-
:is-checked="isRowSelected(row)"
|
|
1103
|
-
:column-positions="columnPositions"
|
|
1104
|
-
:search="search"
|
|
1105
|
-
:search-match-columns="getRowSearchMatchColumns(row)"
|
|
1106
|
-
:active-search-match-column="getRowActiveSearchMatchColumn(row)"
|
|
1107
|
-
:text-ellipsis="textEllipsis"
|
|
1108
|
-
@click="onClickRow"
|
|
1109
|
-
@dblclick="onDoubleClickRow"
|
|
1110
|
-
@click-cell="onClickCell"
|
|
1111
|
-
@toggle-expand="onToggleExpand"
|
|
1112
|
-
@toggle-checkbox="onToggleRowCheckbox"
|
|
1113
|
-
>
|
|
1114
|
-
<template
|
|
1115
|
-
v-for="(value, key, cellIndex) in mapRowColumns(row, normalizedColumns)"
|
|
1116
|
-
:key="`${rowIndex}-${cellIndex}`"
|
|
1117
|
-
#[`cell-${key}`]="{ value: cellValue, row: cellRow }"
|
|
1118
|
-
>
|
|
1119
|
-
<!--
|
|
1120
|
-
@slot Use it to customize needed table cell.
|
|
1121
|
-
@binding {string} value
|
|
1122
|
-
@binding {object} row
|
|
1123
|
-
@binding {number} index
|
|
1124
|
-
@binding {number} cellIndex
|
|
1125
|
-
-->
|
|
1126
|
-
<slot
|
|
1127
|
-
:name="`cell-${key}`"
|
|
1128
|
-
:value="cellValue"
|
|
1129
|
-
:row="cellRow"
|
|
1130
|
-
:index="rowIndex"
|
|
1131
|
-
:cell-index="cellIndex"
|
|
1132
|
-
/>
|
|
1133
|
-
</template>
|
|
1134
|
-
|
|
1135
|
-
<template #expand="{ row: expandedRow, expanded }">
|
|
1136
|
-
<!--
|
|
1137
|
-
@slot Use it to customize row expand icon.
|
|
1138
|
-
@binding {object} row
|
|
1139
|
-
@binding {boolean} expanded
|
|
1140
|
-
@binding {number} index
|
|
1141
|
-
-->
|
|
1142
|
-
<slot name="expand" :index="rowIndex" :row="expandedRow" :expanded="expanded" />
|
|
1143
|
-
</template>
|
|
1144
|
-
|
|
1145
|
-
<template #nested-row>
|
|
1146
|
-
<!--
|
|
1147
|
-
@slot Use it to add inside nested row.
|
|
1148
|
-
@binding {object} row
|
|
1149
|
-
@binding {number} index
|
|
1150
|
-
@binding {number} nestedLevel
|
|
1151
|
-
-->
|
|
1152
|
-
<slot
|
|
1153
|
-
v-if="row"
|
|
1154
|
-
name="nested-row"
|
|
1155
|
-
:index="rowIndex"
|
|
1156
|
-
:row="row"
|
|
1157
|
-
:nested-level="Number(row.nestedLevel || 0)"
|
|
1158
|
-
/>
|
|
1159
|
-
</template>
|
|
1160
|
-
</UTableRow>
|
|
1161
|
-
</template>
|
|
1334
|
+
<component
|
|
1335
|
+
:is="() => renderedRows.map((row, rowIndex) => renderRowTemplate(row, rowIndex)).flat()"
|
|
1336
|
+
/>
|
|
1162
1337
|
|
|
1163
1338
|
<tr v-if="props.virtualScroll && virtualScroll.bottomSpacerHeight.value > 0">
|
|
1164
1339
|
<td
|