vueless 1.3.9-beta.14 → 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 +60 -101
- package/ui.data-table/UTableRow.vue +123 -66
- package/ui.data-table/tests/UTableRow.test.ts +22 -0
- package/ui.data-table/types.ts +6 -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
|
@@ -14,24 +14,25 @@ import {
|
|
|
14
14
|
} from "vue";
|
|
15
15
|
import { isEqual } from "lodash-es";
|
|
16
16
|
|
|
17
|
-
import UEmpty from "../ui.container-empty/UEmpty.vue";
|
|
18
|
-
import UCheckbox from "../ui.form-checkbox/UCheckbox.vue";
|
|
19
|
-
import ULoaderProgress from "../ui.loader-progress/ULoaderProgress.vue";
|
|
20
|
-
import UTableRow from "./UTableRow.vue";
|
|
21
|
-
import UDivider from "../ui.container-divider/UDivider.vue";
|
|
22
|
-
|
|
23
17
|
import { useUI } from "../composables/useUI";
|
|
24
18
|
import { useVirtualScroll } from "../composables/useVirtualScroll";
|
|
19
|
+
import { useComponentLocaleMessages } from "../composables/useComponentLocaleMassages";
|
|
25
20
|
import { getDefaults, cx, getMergedConfig } from "../utils/ui";
|
|
26
21
|
import { hasSlotContent } from "../utils/helper";
|
|
27
|
-
import { useComponentLocaleMessages } from "../composables/useComponentLocaleMassages";
|
|
28
|
-
|
|
29
|
-
import defaultConfig from "./config";
|
|
30
|
-
import { normalizeColumns, mapRowColumns, getFlatRows, getRowChildrenIds } from "./utilTable";
|
|
31
22
|
|
|
32
23
|
import { PX_IN_REM } from "../constants";
|
|
33
24
|
import { COMPONENT_NAME } from "./constants";
|
|
34
25
|
|
|
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
|
+
|
|
35
36
|
import type { ComputedRef, VNode } from "vue";
|
|
36
37
|
import type { Config as UDividerConfig } from "../ui.container-divider/types";
|
|
37
38
|
import type {
|
|
@@ -47,7 +48,6 @@ import type {
|
|
|
47
48
|
SearchMatch,
|
|
48
49
|
UTableRowProps,
|
|
49
50
|
} from "./types";
|
|
50
|
-
import { StickySide } from "./types";
|
|
51
51
|
|
|
52
52
|
defineOptions({ inheritAttrs: false });
|
|
53
53
|
|
|
@@ -136,6 +136,8 @@ const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>
|
|
|
136
136
|
const localSelectedRows = shallowRef<Row[]>([]);
|
|
137
137
|
const localExpandedRows = shallowRef<RowId[]>([]);
|
|
138
138
|
|
|
139
|
+
const expandedRowsSet = computed(() => new Set(localExpandedRows.value));
|
|
140
|
+
|
|
139
141
|
const sortedRows: ComputedRef<FlatRow[]> = computed(() => {
|
|
140
142
|
const headerKeys = props.columns.map((column) =>
|
|
141
143
|
typeof column === "object" ? column.key : column,
|
|
@@ -209,9 +211,9 @@ const tableRowWidthStyle = computed(() => ({ width: `${tableWidth.value / PX_IN_
|
|
|
209
211
|
const flatTableRows = computed(() => getFlatRows(props.rows));
|
|
210
212
|
|
|
211
213
|
const visibleFlatRows = computed(() => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
);
|
|
214
|
+
const expanded = expandedRowsSet.value;
|
|
215
|
+
|
|
216
|
+
return flatTableRows.value.filter((row) => !row.parentRowId || expanded.has(row.parentRowId));
|
|
215
217
|
});
|
|
216
218
|
|
|
217
219
|
const virtualScroll = useVirtualScroll({
|
|
@@ -222,11 +224,25 @@ const virtualScroll = useVirtualScroll({
|
|
|
222
224
|
});
|
|
223
225
|
|
|
224
226
|
const renderedRows = computed(() => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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;
|
|
228
240
|
});
|
|
229
241
|
|
|
242
|
+
function isRowVisible(row: FlatRow): boolean {
|
|
243
|
+
return !row.parentRowId || expandedRowsSet.value.has(row.parentRowId);
|
|
244
|
+
}
|
|
245
|
+
|
|
230
246
|
const searchMatches = computed<SearchMatch[]>(() => {
|
|
231
247
|
const query = props.search?.toLowerCase();
|
|
232
248
|
|
|
@@ -445,11 +461,23 @@ watchEffect(() => {
|
|
|
445
461
|
|
|
446
462
|
let resizeObserver: ResizeObserver | null = null;
|
|
447
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
|
+
}
|
|
448
477
|
|
|
449
478
|
onMounted(async () => {
|
|
450
479
|
document.addEventListener("keyup", onKeyupEsc);
|
|
451
480
|
document.addEventListener("scroll", onScroll, { passive: true });
|
|
452
|
-
window.addEventListener("resize", onWindowResize);
|
|
453
481
|
|
|
454
482
|
await nextTick();
|
|
455
483
|
updateHeaderOffsetTop();
|
|
@@ -461,9 +489,7 @@ onMounted(async () => {
|
|
|
461
489
|
|
|
462
490
|
if (!entry) return;
|
|
463
491
|
|
|
464
|
-
|
|
465
|
-
tableWidth.value = entry.contentRect.width;
|
|
466
|
-
calculateStickyColumnPositions();
|
|
492
|
+
scheduleWindowResize(entry.contentRect.width, entry.contentRect.height);
|
|
467
493
|
});
|
|
468
494
|
|
|
469
495
|
resizeObserver.observe(tableWrapperRef.value);
|
|
@@ -473,12 +499,15 @@ onMounted(async () => {
|
|
|
473
499
|
onBeforeUnmount(() => {
|
|
474
500
|
document.removeEventListener("keyup", onKeyupEsc);
|
|
475
501
|
document.removeEventListener("scroll", onScroll);
|
|
476
|
-
window.removeEventListener("resize", onWindowResize);
|
|
477
502
|
resizeObserver?.disconnect();
|
|
478
503
|
|
|
479
504
|
if (scrollRafId !== null) {
|
|
480
505
|
cancelAnimationFrame(scrollRafId);
|
|
481
506
|
}
|
|
507
|
+
|
|
508
|
+
if (resizeDebounceId !== null) {
|
|
509
|
+
clearTimeout(resizeDebounceId);
|
|
510
|
+
}
|
|
482
511
|
});
|
|
483
512
|
|
|
484
513
|
function onChangeSelectedRows() {
|
|
@@ -494,8 +523,6 @@ function onChangeExpandedRows() {
|
|
|
494
523
|
}
|
|
495
524
|
|
|
496
525
|
function onWindowResize() {
|
|
497
|
-
tableWidth.value = tableWrapperRef.value?.offsetWidth || 0;
|
|
498
|
-
|
|
499
526
|
updateHeaderOffsetTop();
|
|
500
527
|
setHeaderCellWidth();
|
|
501
528
|
setFooterCellWidth();
|
|
@@ -721,7 +748,7 @@ function onBodyClick(event: MouseEvent) {
|
|
|
721
748
|
|
|
722
749
|
if (!rowId) return;
|
|
723
750
|
|
|
724
|
-
const rowData =
|
|
751
|
+
const rowData = flatTableRows.value.find((r) => String(r.id) === rowId);
|
|
725
752
|
|
|
726
753
|
if (!rowData) return;
|
|
727
754
|
|
|
@@ -776,7 +803,7 @@ function onBodyDoubleClick(event: MouseEvent) {
|
|
|
776
803
|
|
|
777
804
|
if (!rowId) return;
|
|
778
805
|
|
|
779
|
-
const rowData =
|
|
806
|
+
const rowData = flatTableRows.value.find((r) => String(r.id) === rowId);
|
|
780
807
|
|
|
781
808
|
if (!rowData) return;
|
|
782
809
|
|
|
@@ -820,9 +847,8 @@ function clearSelectedItems() {
|
|
|
820
847
|
|
|
821
848
|
function onToggleExpand(row: Row) {
|
|
822
849
|
const expanded = localExpandedRows.value;
|
|
823
|
-
const targetIndex = expanded.indexOf(row.id);
|
|
824
850
|
|
|
825
|
-
if (
|
|
851
|
+
if (expandedRowsSet.value.has(row.id)) {
|
|
826
852
|
const idsToRemove = new Set([row.id, ...getRowChildrenIds(row)]);
|
|
827
853
|
|
|
828
854
|
localExpandedRows.value = expanded.filter((id) => !idsToRemove.has(id));
|
|
@@ -1047,7 +1073,7 @@ function renderDateDividerRow(row: FlatRow, rowIndex: number): VNode | null {
|
|
|
1047
1073
|
config: getDateDividerConfig(row, isSelected),
|
|
1048
1074
|
});
|
|
1049
1075
|
|
|
1050
|
-
return h("tr", propsDateDivider, [
|
|
1076
|
+
return h("tr", { ...propsDateDivider, key: `date-divider-${row.id}` }, [
|
|
1051
1077
|
h(
|
|
1052
1078
|
"td",
|
|
1053
1079
|
{
|
|
@@ -1059,81 +1085,13 @@ function renderDateDividerRow(row: FlatRow, rowIndex: number): VNode | null {
|
|
|
1059
1085
|
]);
|
|
1060
1086
|
}
|
|
1061
1087
|
|
|
1062
|
-
function renderTableRowSlots(row: FlatRow, rowIndex: number) {
|
|
1063
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
1064
|
-
const cellSlots: Record<string, Function> = {};
|
|
1065
|
-
|
|
1066
|
-
// Create cell slots
|
|
1067
|
-
Object.entries(mapRowColumns(row, normalizedColumns.value)).forEach(([key], cellIndex) => {
|
|
1068
|
-
cellSlots[`cell-${key}`] = ({
|
|
1069
|
-
value: cellValue,
|
|
1070
|
-
row: cellRow,
|
|
1071
|
-
}: {
|
|
1072
|
-
value: Cell;
|
|
1073
|
-
row: FlatRow;
|
|
1074
|
-
}) => {
|
|
1075
|
-
const hasCellSlot = hasSlotContent(slots[`cell-${key}`], {
|
|
1076
|
-
value: cellValue,
|
|
1077
|
-
row: cellRow,
|
|
1078
|
-
index: rowIndex,
|
|
1079
|
-
cellIndex,
|
|
1080
|
-
});
|
|
1081
|
-
|
|
1082
|
-
if (hasCellSlot) {
|
|
1083
|
-
return slots[`cell-${key}`]?.({
|
|
1084
|
-
value: cellValue,
|
|
1085
|
-
row: cellRow,
|
|
1086
|
-
index: rowIndex,
|
|
1087
|
-
cellIndex,
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
return null;
|
|
1092
|
-
};
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
// Add expand slot
|
|
1096
|
-
cellSlots.expand = ({ row: expandedRow, expanded }: { row: FlatRow; expanded: boolean }) => {
|
|
1097
|
-
const hasExpandSlot = hasSlotContent(slots.expand, {
|
|
1098
|
-
index: rowIndex,
|
|
1099
|
-
row: expandedRow,
|
|
1100
|
-
expanded,
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
if (hasExpandSlot) {
|
|
1104
|
-
return slots.expand?.({ index: rowIndex, row: expandedRow, expanded });
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
return null;
|
|
1108
|
-
};
|
|
1109
|
-
|
|
1110
|
-
// Add nested-row slot
|
|
1111
|
-
cellSlots["nested-row"] = () => {
|
|
1112
|
-
const hasNestedRowSlot = hasSlotContent(slots["nested-row"], {
|
|
1113
|
-
index: rowIndex,
|
|
1114
|
-
row,
|
|
1115
|
-
nestedLevel: Number(row.nestedLevel || 0),
|
|
1116
|
-
});
|
|
1117
|
-
|
|
1118
|
-
if (hasNestedRowSlot && row) {
|
|
1119
|
-
return slots["nested-row"]?.({
|
|
1120
|
-
index: rowIndex,
|
|
1121
|
-
row,
|
|
1122
|
-
nestedLevel: Number(row.nestedLevel || 0),
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
return null;
|
|
1127
|
-
};
|
|
1128
|
-
|
|
1129
|
-
return cellSlots;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
1088
|
function renderTableRow(row: FlatRow, rowIndex: number): VNode {
|
|
1133
1089
|
return h(
|
|
1134
1090
|
UTableRow,
|
|
1135
1091
|
{
|
|
1092
|
+
key: row.id,
|
|
1136
1093
|
selectable: props.selectable,
|
|
1094
|
+
rowIndex,
|
|
1137
1095
|
row,
|
|
1138
1096
|
columns: normalizedColumns.value,
|
|
1139
1097
|
config: config.value,
|
|
@@ -1143,15 +1101,16 @@ function renderTableRow(row: FlatRow, rowIndex: number): VNode {
|
|
|
1143
1101
|
emptyCellLabel: props.emptyCellLabel,
|
|
1144
1102
|
"data-test": getDataTest("row"),
|
|
1145
1103
|
"data-row-id": row.id,
|
|
1146
|
-
isExpanded:
|
|
1104
|
+
isExpanded: expandedRowsSet.value.has(row.id),
|
|
1147
1105
|
isChecked: isRowSelected(row),
|
|
1106
|
+
isVisible: isRowVisible(row),
|
|
1148
1107
|
columnPositions: columnPositions.value,
|
|
1149
1108
|
search: props.search,
|
|
1150
1109
|
searchMatchColumns: getRowSearchMatchColumns(row),
|
|
1151
1110
|
activeSearchMatchColumn: getRowActiveSearchMatchColumn(row),
|
|
1152
1111
|
textEllipsis: props.textEllipsis,
|
|
1153
1112
|
} as unknown as UTableRowProps,
|
|
1154
|
-
|
|
1113
|
+
slots,
|
|
1155
1114
|
);
|
|
1156
1115
|
}
|
|
1157
1116
|
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import {
|
|
3
|
+
Comment,
|
|
4
|
+
Fragment,
|
|
5
|
+
Text,
|
|
6
|
+
computed,
|
|
7
|
+
h,
|
|
8
|
+
onMounted,
|
|
9
|
+
useAttrs,
|
|
10
|
+
useSlots,
|
|
11
|
+
useTemplateRef,
|
|
12
|
+
} from "vue";
|
|
8
13
|
|
|
9
14
|
import { useUI } from "../composables/useUI";
|
|
10
15
|
import { useMutationObserver } from "../composables/useMutationObserver";
|
|
11
16
|
|
|
12
|
-
import
|
|
13
|
-
import
|
|
17
|
+
import { isEmptyValue } from "../utils/helper";
|
|
18
|
+
import { cx } from "../utils/ui";
|
|
14
19
|
|
|
15
|
-
import
|
|
20
|
+
import { PX_IN_REM } from "../constants";
|
|
16
21
|
|
|
22
|
+
import defaultConfig from "./config";
|
|
23
|
+
import { mapRowColumns } from "./utilTable";
|
|
17
24
|
import { StickySide } from "./types";
|
|
18
|
-
|
|
25
|
+
|
|
26
|
+
import UCheckbox from "../ui.form-checkbox/UCheckbox.vue";
|
|
27
|
+
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
28
|
+
|
|
29
|
+
import type { Slot, VNode } from "vue";
|
|
19
30
|
import type { Cell, CellObject, Row, UTableRowProps, Config, ColumnObject } from "./types";
|
|
20
31
|
|
|
21
32
|
const NESTED_ROW_SHIFT_REM = 1.5;
|
|
@@ -29,7 +40,6 @@ const slots = useSlots();
|
|
|
29
40
|
const attrs = useAttrs();
|
|
30
41
|
|
|
31
42
|
const cellRef = useTemplateRef<HTMLDivElement[]>("cell");
|
|
32
|
-
const toggleWrapperRef = useTemplateRef<HTMLDivElement[]>("toggle-wrapper");
|
|
33
43
|
|
|
34
44
|
if (props.textEllipsis) {
|
|
35
45
|
useMutationObserver(cellRef, setCellTitle, {
|
|
@@ -67,17 +77,6 @@ function getToggleIconName() {
|
|
|
67
77
|
: props.config?.defaults?.expandIcon;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
function getIconWidth() {
|
|
71
|
-
const icon = document.querySelector(`[data-row-toggle-icon='${props.row.id}']`);
|
|
72
|
-
const currentWrapperWidth = toggleWrapperRef.value?.at(0)?.getBoundingClientRect()?.width || 0;
|
|
73
|
-
|
|
74
|
-
if (icon) {
|
|
75
|
-
return `${icon.getBoundingClientRect().width / PX_IN_REM}rem`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return `${currentWrapperWidth / PX_IN_REM || 1}rem`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
80
|
function getCellClasses(row: Row, key: string) {
|
|
82
81
|
const isCellData = typeof row[key] === "object" && row[key] !== null && "class" in row[key];
|
|
83
82
|
const cell = row[key] as CellObject;
|
|
@@ -234,6 +233,54 @@ function isNestedFirstCell(index: number): boolean {
|
|
|
234
233
|
return (Boolean(props.row.row) || Boolean(props.nestedLevel)) && index === 0;
|
|
235
234
|
}
|
|
236
235
|
|
|
236
|
+
function getRowIndex(): number {
|
|
237
|
+
return props.rowIndex ?? 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isSlotContentEmpty(content: unknown): boolean {
|
|
241
|
+
const toArray = (arg: unknown) => {
|
|
242
|
+
return Array.isArray(arg) ? arg : [arg];
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (content === null || content === undefined) return true;
|
|
246
|
+
|
|
247
|
+
if (typeof content === "boolean" || typeof content === "number") {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (typeof content === "string") {
|
|
252
|
+
return content.length === 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return toArray(content).every((node) => {
|
|
256
|
+
if (node === null || node === undefined) return true;
|
|
257
|
+
|
|
258
|
+
if (typeof node === "boolean" || typeof node === "number") {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (typeof node === "string") {
|
|
263
|
+
return node.length === 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const vnode = node as VNode;
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
vnode.type === Comment ||
|
|
270
|
+
(vnode.type === Text && !vnode.children?.length) ||
|
|
271
|
+
(vnode.type === Fragment && !vnode.children?.length)
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function resolveSlotContent(slot: Slot | undefined, slotParams: Record<string, unknown>) {
|
|
277
|
+
if (!slot) return null;
|
|
278
|
+
|
|
279
|
+
const content = slot(slotParams);
|
|
280
|
+
|
|
281
|
+
return isSlotContentEmpty(content) ? null : content;
|
|
282
|
+
}
|
|
283
|
+
|
|
237
284
|
function shouldRenderCellWrapper(row: Row, key: string): boolean {
|
|
238
285
|
return Boolean(
|
|
239
286
|
props.textEllipsis ||
|
|
@@ -242,13 +289,19 @@ function shouldRenderCellWrapper(row: Row, key: string): boolean {
|
|
|
242
289
|
);
|
|
243
290
|
}
|
|
244
291
|
|
|
245
|
-
function renderCellContent(value: Cell, key: string,
|
|
292
|
+
function renderCellContent(value: Cell, key: string, cellIndex: number): VNode | VNode[] | string {
|
|
246
293
|
const keyStr = String(key);
|
|
247
|
-
|
|
294
|
+
|
|
295
|
+
const slotContent = resolveSlotContent(slots[`cell-${key}`], {
|
|
296
|
+
value,
|
|
297
|
+
row: props.row,
|
|
298
|
+
index: getRowIndex(),
|
|
299
|
+
cellIndex,
|
|
300
|
+
});
|
|
248
301
|
|
|
249
302
|
// Check if slot exists
|
|
250
|
-
if (
|
|
251
|
-
return
|
|
303
|
+
if (slotContent) {
|
|
304
|
+
return slotContent as VNode | VNode[] | string;
|
|
252
305
|
}
|
|
253
306
|
|
|
254
307
|
// Render cell wrapper with highlighted HTML
|
|
@@ -268,9 +321,11 @@ function renderCellContent(value: Cell, key: string, index: number): VNode | VNo
|
|
|
268
321
|
return formatCellValue(value);
|
|
269
322
|
}
|
|
270
323
|
|
|
271
|
-
function renderNestedFirstCell(value: Cell, key: string,
|
|
324
|
+
function renderNestedFirstCell(value: Cell, key: string, cellIndex: number): VNode {
|
|
272
325
|
const keyStr = String(key);
|
|
273
|
-
|
|
326
|
+
|
|
327
|
+
const expandSlotContent = resolveSlotContent(slots.expand, {
|
|
328
|
+
index: getRowIndex(),
|
|
274
329
|
row: props.row,
|
|
275
330
|
expanded: props.isExpanded,
|
|
276
331
|
});
|
|
@@ -286,9 +341,7 @@ function renderNestedFirstCell(value: Cell, key: string, index: number): VNode {
|
|
|
286
341
|
const toggleWrapperNode = h(
|
|
287
342
|
"div",
|
|
288
343
|
{
|
|
289
|
-
ref: toggleWrapperRef,
|
|
290
344
|
...props.attrs.bodyCellNestedIconWrapperAttrs.value,
|
|
291
|
-
style: { width: getIconWidth() },
|
|
292
345
|
},
|
|
293
346
|
[toggleIconNode],
|
|
294
347
|
);
|
|
@@ -300,12 +353,15 @@ function renderNestedFirstCell(value: Cell, key: string, index: number): VNode {
|
|
|
300
353
|
"data-expand-icon": props.row.id,
|
|
301
354
|
onDblclick: (e: Event) => e.stopPropagation(),
|
|
302
355
|
},
|
|
303
|
-
|
|
304
|
-
? slots?.expand?.({ row: props.row, expanded: props.isExpanded })
|
|
305
|
-
: [toggleWrapperNode],
|
|
356
|
+
expandSlotContent || [toggleWrapperNode],
|
|
306
357
|
);
|
|
307
358
|
|
|
308
|
-
const
|
|
359
|
+
const cellSlotContent = resolveSlotContent(slots[`cell-${key}`], {
|
|
360
|
+
value,
|
|
361
|
+
row: props.row,
|
|
362
|
+
index: getRowIndex(),
|
|
363
|
+
cellIndex,
|
|
364
|
+
});
|
|
309
365
|
|
|
310
366
|
return h(
|
|
311
367
|
"div",
|
|
@@ -317,10 +373,8 @@ function renderNestedFirstCell(value: Cell, key: string, index: number): VNode {
|
|
|
317
373
|
props.row.row ? toggleIconWrapperNode : null,
|
|
318
374
|
// Cell content
|
|
319
375
|
...(() => {
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
return Array.isArray(slotContent) ? slotContent : [slotContent];
|
|
376
|
+
if (cellSlotContent) {
|
|
377
|
+
return Array.isArray(cellSlotContent) ? cellSlotContent : [cellSlotContent];
|
|
324
378
|
}
|
|
325
379
|
|
|
326
380
|
if (shouldRenderCellWrapper(props.row, keyStr)) {
|
|
@@ -351,6 +405,8 @@ function renderTableCell(value: Cell, key: string, index: number): VNode {
|
|
|
351
405
|
? renderNestedFirstCell(value, key, index)
|
|
352
406
|
: renderCellContent(value, key, index);
|
|
353
407
|
|
|
408
|
+
const cellChildren = Array.isArray(nestedCellNode) ? nestedCellNode : [nestedCellNode];
|
|
409
|
+
|
|
354
410
|
return h(
|
|
355
411
|
"td",
|
|
356
412
|
{
|
|
@@ -367,7 +423,7 @@ function renderTableCell(value: Cell, key: string, index: number): VNode {
|
|
|
367
423
|
"data-cell-key": key,
|
|
368
424
|
"data-test": getDataTest(`${key}-cell`),
|
|
369
425
|
},
|
|
370
|
-
|
|
426
|
+
cellChildren,
|
|
371
427
|
);
|
|
372
428
|
}
|
|
373
429
|
|
|
@@ -404,16 +460,7 @@ function renderCheckboxCell(): VNode | null {
|
|
|
404
460
|
);
|
|
405
461
|
}
|
|
406
462
|
|
|
407
|
-
function renderMainRow(): VNode
|
|
408
|
-
const hasNestedRowSlot = hasSlotContent(slots["nested-row"], {
|
|
409
|
-
row: props.row,
|
|
410
|
-
nestedLevel: props.nestedLevel,
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
if (hasNestedRowSlot && props.row.parentRowId) {
|
|
414
|
-
return null;
|
|
415
|
-
}
|
|
416
|
-
|
|
463
|
+
function renderMainRow(): VNode {
|
|
417
464
|
const cells = Object.entries(mapRowColumns(props.row, props.columns)).map(
|
|
418
465
|
([key, value], index) => {
|
|
419
466
|
return renderTableCell(value, key, index);
|
|
@@ -426,38 +473,48 @@ function renderMainRow(): VNode | null {
|
|
|
426
473
|
...attrs,
|
|
427
474
|
...getRowAttrs(),
|
|
428
475
|
class: cx([getRowAttrs().class, getRowClasses(props.row)]),
|
|
476
|
+
style: { display: props.isVisible ? "" : "none" },
|
|
429
477
|
},
|
|
430
478
|
[renderCheckboxCell(), ...cells].filter(Boolean),
|
|
431
479
|
);
|
|
432
480
|
}
|
|
433
481
|
|
|
434
|
-
function renderNestedRow(): VNode
|
|
435
|
-
const hasNestedRowSlot = hasSlotContent(slots["nested-row"], {
|
|
436
|
-
row: props.row,
|
|
437
|
-
nestedLevel: props.nestedLevel,
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
if (!hasNestedRowSlot || !props.row.parentRowId) {
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const nestedRowSlotContent = slots["nested-row"]?.({
|
|
445
|
-
row: props.row,
|
|
446
|
-
nestedLevel: props.nestedLevel,
|
|
447
|
-
});
|
|
448
|
-
|
|
482
|
+
function renderNestedRow(nestedRowSlotContent: VNode[]): VNode {
|
|
449
483
|
const tdNode = h(
|
|
450
484
|
"td",
|
|
451
485
|
{ colspan: props.columns.length + Number(props.selectable) },
|
|
452
486
|
nestedRowSlotContent,
|
|
453
487
|
);
|
|
454
488
|
|
|
455
|
-
return h(
|
|
489
|
+
return h(
|
|
490
|
+
"tr",
|
|
491
|
+
{
|
|
492
|
+
class: props.row.class,
|
|
493
|
+
style: { display: props.isVisible ? "" : "none" },
|
|
494
|
+
},
|
|
495
|
+
[tdNode],
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function renderRows(): VNode[] {
|
|
500
|
+
if (props.row.parentRowId) {
|
|
501
|
+
const nestedRowSlotContent = resolveSlotContent(slots["nested-row"], {
|
|
502
|
+
index: getRowIndex(),
|
|
503
|
+
row: props.row,
|
|
504
|
+
nestedLevel: props.nestedLevel,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
if (nestedRowSlotContent) {
|
|
508
|
+
return [renderNestedRow(nestedRowSlotContent)];
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return [renderMainRow()];
|
|
456
513
|
}
|
|
457
514
|
|
|
458
515
|
const { getDataTest } = useUI<Config>(defaultConfig);
|
|
459
516
|
</script>
|
|
460
517
|
|
|
461
518
|
<template>
|
|
462
|
-
<component :is="
|
|
519
|
+
<component :is="renderRows" />
|
|
463
520
|
</template>
|
|
@@ -68,6 +68,7 @@ describe("UTableRow.vue", () => {
|
|
|
68
68
|
config: defaultConfig,
|
|
69
69
|
isChecked: false,
|
|
70
70
|
isExpanded: false,
|
|
71
|
+
isVisible: true,
|
|
71
72
|
columnPositions,
|
|
72
73
|
...overrides,
|
|
73
74
|
};
|
|
@@ -459,5 +460,26 @@ describe("UTableRow.vue", () => {
|
|
|
459
460
|
|
|
460
461
|
expect(iconWrapper.exists()).toBe(true);
|
|
461
462
|
});
|
|
463
|
+
|
|
464
|
+
it("isVisible – shows row when isVisible is true", () => {
|
|
465
|
+
const component = mount(UTableRow, {
|
|
466
|
+
props: getDefaultProps({ isVisible: true }),
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const row = component.find("tr");
|
|
470
|
+
const style = row.attributes("style") || "";
|
|
471
|
+
|
|
472
|
+
expect(style).not.toContain("display: none");
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("isVisible – hides row when isVisible is false", () => {
|
|
476
|
+
const component = mount(UTableRow, {
|
|
477
|
+
props: getDefaultProps({ isVisible: false }),
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const row = component.find("tr");
|
|
481
|
+
|
|
482
|
+
expect(row.attributes("style")).toContain("display: none");
|
|
483
|
+
});
|
|
462
484
|
});
|
|
463
485
|
});
|
package/ui.data-table/types.ts
CHANGED
|
@@ -185,6 +185,11 @@ export interface UTableRowAttrs {
|
|
|
185
185
|
export interface UTableRowProps {
|
|
186
186
|
row: FlatRow;
|
|
187
187
|
columns: ColumnObject[];
|
|
188
|
+
/**
|
|
189
|
+
* Row index in the parent table (used for slot params).
|
|
190
|
+
* Optional to keep UTableRow mountable standalone in tests/internal usage.
|
|
191
|
+
*/
|
|
192
|
+
rowIndex?: number;
|
|
188
193
|
emptyCellLabel?: string;
|
|
189
194
|
selectable: boolean;
|
|
190
195
|
nestedLevel: number;
|
|
@@ -194,6 +199,7 @@ export interface UTableRowProps {
|
|
|
194
199
|
config: Config;
|
|
195
200
|
isChecked: boolean;
|
|
196
201
|
isExpanded: boolean;
|
|
202
|
+
isVisible: boolean;
|
|
197
203
|
columnPositions: Map<string, number>;
|
|
198
204
|
search?: string;
|
|
199
205
|
searchMatchColumns?: Set<string>;
|