vueless 1.3.9-beta.9 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composables/useUI.ts +187 -155
- package/package.json +2 -2
- package/types.ts +11 -0
- package/ui.button-link/ULink.vue +18 -2
- package/ui.data-table/UTable.vue +568 -198
- package/ui.data-table/UTableRow.vue +372 -168
- package/ui.data-table/config.ts +8 -1
- package/ui.data-table/storybook/stories.ts +90 -0
- package/ui.data-table/tests/UTable.test.ts +317 -20
- package/ui.data-table/tests/UTableRow.test.ts +35 -104
- package/ui.data-table/types.ts +37 -0
- package/ui.data-table/utilTable.ts +16 -10
- package/ui.form-checkbox/UCheckbox.vue +3 -7
- package/ui.text-notify/config.ts +1 -1
- 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/utils/node/mergeConfigs.d.ts +2 -2
- package/utils/node/mergeConfigs.js +240 -121
- package/utils/node/vuelessConfig.d.ts +1 -1
package/ui.data-table/UTable.vue
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {
|
|
3
|
+
shallowRef,
|
|
3
4
|
ref,
|
|
4
5
|
computed,
|
|
5
6
|
watch,
|
|
7
|
+
watchEffect,
|
|
6
8
|
useSlots,
|
|
7
9
|
nextTick,
|
|
8
10
|
onMounted,
|
|
9
|
-
onUpdated,
|
|
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,
|
|
@@ -42,8 +45,9 @@ import type {
|
|
|
42
45
|
DateDivider,
|
|
43
46
|
FlatRow,
|
|
44
47
|
ColumnObject,
|
|
48
|
+
SearchMatch,
|
|
49
|
+
UTableRowProps,
|
|
45
50
|
} from "./types";
|
|
46
|
-
import { StickySide } from "./types";
|
|
47
51
|
|
|
48
52
|
defineOptions({ inheritAttrs: false });
|
|
49
53
|
|
|
@@ -98,6 +102,12 @@ const emit = defineEmits([
|
|
|
98
102
|
* @property {array} rowId
|
|
99
103
|
*/
|
|
100
104
|
"update:expandedRows",
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Triggers when search matches are found.
|
|
108
|
+
* @property {number} totalMatches
|
|
109
|
+
*/
|
|
110
|
+
"search",
|
|
101
111
|
]);
|
|
102
112
|
|
|
103
113
|
const slots = useSlots();
|
|
@@ -123,35 +133,29 @@ const { localeMessages } = useComponentLocaleMessages<typeof defaultConfig.i18n>
|
|
|
123
133
|
props?.config?.i18n,
|
|
124
134
|
);
|
|
125
135
|
|
|
126
|
-
const localSelectedRows =
|
|
127
|
-
const localExpandedRows =
|
|
136
|
+
const localSelectedRows = shallowRef<Row[]>([]);
|
|
137
|
+
const localExpandedRows = shallowRef<RowId[]>([]);
|
|
138
|
+
|
|
139
|
+
const expandedRowsSet = computed(() => new Set(localExpandedRows.value));
|
|
128
140
|
|
|
129
141
|
const sortedRows: ComputedRef<FlatRow[]> = computed(() => {
|
|
130
142
|
const headerKeys = props.columns.map((column) =>
|
|
131
143
|
typeof column === "object" ? column.key : column,
|
|
132
144
|
);
|
|
133
145
|
|
|
134
|
-
|
|
135
|
-
const rowEntries = Object.entries(row);
|
|
136
|
-
|
|
137
|
-
const sortedEntries: typeof rowEntries = new Array(rowEntries.length);
|
|
146
|
+
const keyOrder = new Map(headerKeys.map((key, i) => [key, i]));
|
|
138
147
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const headerIndex = headerKeys.indexOf(key);
|
|
142
|
-
|
|
143
|
-
if (!~headerIndex) {
|
|
144
|
-
sortedEntries.push(entry);
|
|
148
|
+
return flatTableRows.value.map((row) => {
|
|
149
|
+
const entries = Object.entries(row);
|
|
145
150
|
|
|
146
|
-
|
|
147
|
-
|
|
151
|
+
entries.sort((a, b) => {
|
|
152
|
+
const aIdx = keyOrder.get(a[0]) ?? Infinity;
|
|
153
|
+
const bIdx = keyOrder.get(b[0]) ?? Infinity;
|
|
148
154
|
|
|
149
|
-
|
|
155
|
+
return aIdx - bIdx;
|
|
150
156
|
});
|
|
151
157
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return sortedRow as FlatRow;
|
|
158
|
+
return Object.fromEntries(entries) as FlatRow;
|
|
155
159
|
});
|
|
156
160
|
});
|
|
157
161
|
|
|
@@ -169,7 +173,7 @@ const visibleColumns = computed(() => {
|
|
|
169
173
|
return normalizedColumns.value.filter((column) => column.isShown !== false);
|
|
170
174
|
});
|
|
171
175
|
|
|
172
|
-
const columnPositions =
|
|
176
|
+
const columnPositions = shallowRef<Map<string, number>>(new Map());
|
|
173
177
|
|
|
174
178
|
const colsCount = computed(() => {
|
|
175
179
|
return normalizedColumns.value.length + 1;
|
|
@@ -184,11 +188,10 @@ const isShownActionsHeader = computed(() => {
|
|
|
184
188
|
return hasSelectedRows && hasHeaderActions;
|
|
185
189
|
});
|
|
186
190
|
|
|
187
|
-
const
|
|
188
|
-
const positionForFixHeader =
|
|
189
|
-
Number(headerRowRef.value?.getBoundingClientRect()?.top) + Number(window?.scrollY) || 0;
|
|
191
|
+
const headerOffsetTop = ref(0);
|
|
190
192
|
|
|
191
|
-
|
|
193
|
+
const isHeaderSticky = computed(() => {
|
|
194
|
+
return headerOffsetTop.value <= pagePositionY.value && props.stickyHeader;
|
|
192
195
|
});
|
|
193
196
|
|
|
194
197
|
const isShownFooterPosition = computed(() => {
|
|
@@ -208,9 +211,9 @@ const tableRowWidthStyle = computed(() => ({ width: `${tableWidth.value / PX_IN_
|
|
|
208
211
|
const flatTableRows = computed(() => getFlatRows(props.rows));
|
|
209
212
|
|
|
210
213
|
const visibleFlatRows = computed(() => {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
);
|
|
214
|
+
const expanded = expandedRowsSet.value;
|
|
215
|
+
|
|
216
|
+
return flatTableRows.value.filter((row) => !row.parentRowId || expanded.has(row.parentRowId));
|
|
214
217
|
});
|
|
215
218
|
|
|
216
219
|
const virtualScroll = useVirtualScroll({
|
|
@@ -221,58 +224,290 @@ const virtualScroll = useVirtualScroll({
|
|
|
221
224
|
});
|
|
222
225
|
|
|
223
226
|
const renderedRows = computed(() => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
function isRowVisible(row: FlatRow): boolean {
|
|
243
|
+
return !row.parentRowId || expandedRowsSet.value.has(row.parentRowId);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const searchMatches = computed<SearchMatch[]>(() => {
|
|
247
|
+
const query = props.search?.toLowerCase();
|
|
248
|
+
|
|
249
|
+
if (!query) return [];
|
|
250
|
+
|
|
251
|
+
const matches: SearchMatch[] = [];
|
|
252
|
+
const columns = visibleColumns.value;
|
|
253
|
+
const rows = visibleFlatRows.value;
|
|
254
|
+
|
|
255
|
+
for (let i = 0; i < rows.length; i++) {
|
|
256
|
+
const row = rows[i];
|
|
257
|
+
|
|
258
|
+
for (let j = 0; j < columns.length; j++) {
|
|
259
|
+
const key = columns[j].key;
|
|
260
|
+
const cellValue = row[key];
|
|
261
|
+
const text = getCellTextValue(cellValue);
|
|
262
|
+
|
|
263
|
+
if (!text) continue;
|
|
264
|
+
|
|
265
|
+
const lowerText = text.toLowerCase();
|
|
266
|
+
const indices: number[] = [];
|
|
267
|
+
let pos = 0;
|
|
268
|
+
|
|
269
|
+
while ((pos = lowerText.indexOf(query, pos)) !== -1) {
|
|
270
|
+
indices.push(pos);
|
|
271
|
+
pos += query.length;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (indices.length) {
|
|
275
|
+
matches.push({ rowId: row.id, columnKey: key, indices });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return matches;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const searchMatchColumnSets = computed(() => {
|
|
284
|
+
const map = new Map<RowId, Set<string>>();
|
|
285
|
+
|
|
286
|
+
for (const match of searchMatches.value) {
|
|
287
|
+
let set = map.get(match.rowId);
|
|
288
|
+
|
|
289
|
+
if (!set) {
|
|
290
|
+
set = new Set();
|
|
291
|
+
map.set(match.rowId, set);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
set.add(match.columnKey);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return map;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const activeMatch = computed(() => {
|
|
301
|
+
const idx = props.searchMatch;
|
|
302
|
+
|
|
303
|
+
if (idx === undefined || idx < 0 || !searchMatches.value.length) return null;
|
|
304
|
+
|
|
305
|
+
let globalIndex = 0;
|
|
306
|
+
|
|
307
|
+
for (const match of searchMatches.value) {
|
|
308
|
+
if (globalIndex + match.indices.length > idx) {
|
|
309
|
+
return {
|
|
310
|
+
rowId: match.rowId,
|
|
311
|
+
columnKey: match.columnKey,
|
|
312
|
+
charIndex: match.indices[idx - globalIndex],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
globalIndex += match.indices.length;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return null;
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const totalSearchMatches = computed(() => {
|
|
323
|
+
let count = 0;
|
|
324
|
+
|
|
325
|
+
for (const match of searchMatches.value) {
|
|
326
|
+
count += match.indices.length;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return count;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const selectedRowIds = computed(() => {
|
|
333
|
+
return new Set(localSelectedRows.value.map((row) => row.id));
|
|
227
334
|
});
|
|
228
335
|
|
|
229
336
|
const isSelectedAllRows = computed(() => {
|
|
230
337
|
return localSelectedRows.value.length === flatTableRows.value.length;
|
|
231
338
|
});
|
|
232
339
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
});
|
|
247
380
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!stopSelectedRowsWatch) {
|
|
394
|
+
stopSelectedRowsWatch = watch(() => props.selectedRows, onChangeSelectedRows, {
|
|
395
|
+
immediate: true,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!stopSelectAllWatch) {
|
|
400
|
+
stopSelectAllWatch = watch(selectAll, onChangeSelectAll);
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
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
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Expansion watcher (always register as it's a core feature)
|
|
422
|
+
watch(() => props.expandedRows, onChangeExpandedRows, { immediate: true });
|
|
423
|
+
|
|
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
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
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
|
+
});
|
|
461
|
+
|
|
462
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
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
|
+
}
|
|
256
477
|
|
|
257
478
|
onMounted(async () => {
|
|
258
479
|
document.addEventListener("keyup", onKeyupEsc);
|
|
259
480
|
document.addEventListener("scroll", onScroll, { passive: true });
|
|
260
|
-
window.addEventListener("resize", onWindowResize);
|
|
261
481
|
|
|
262
482
|
await nextTick();
|
|
483
|
+
updateHeaderOffsetTop();
|
|
263
484
|
calculateStickyColumnPositions();
|
|
264
|
-
});
|
|
265
485
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
486
|
+
if (tableWrapperRef.value) {
|
|
487
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
488
|
+
const entry = entries[0];
|
|
489
|
+
|
|
490
|
+
if (!entry) return;
|
|
491
|
+
|
|
492
|
+
scheduleWindowResize(entry.contentRect.width, entry.contentRect.height);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
resizeObserver.observe(tableWrapperRef.value);
|
|
496
|
+
}
|
|
270
497
|
});
|
|
271
498
|
|
|
272
499
|
onBeforeUnmount(() => {
|
|
273
500
|
document.removeEventListener("keyup", onKeyupEsc);
|
|
274
501
|
document.removeEventListener("scroll", onScroll);
|
|
275
|
-
|
|
502
|
+
resizeObserver?.disconnect();
|
|
503
|
+
|
|
504
|
+
if (scrollRafId !== null) {
|
|
505
|
+
cancelAnimationFrame(scrollRafId);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (resizeDebounceId !== null) {
|
|
509
|
+
clearTimeout(resizeDebounceId);
|
|
510
|
+
}
|
|
276
511
|
});
|
|
277
512
|
|
|
278
513
|
function onChangeSelectedRows() {
|
|
@@ -288,13 +523,18 @@ function onChangeExpandedRows() {
|
|
|
288
523
|
}
|
|
289
524
|
|
|
290
525
|
function onWindowResize() {
|
|
291
|
-
|
|
292
|
-
|
|
526
|
+
updateHeaderOffsetTop();
|
|
293
527
|
setHeaderCellWidth();
|
|
294
528
|
setFooterCellWidth();
|
|
295
529
|
calculateStickyColumnPositions();
|
|
296
530
|
}
|
|
297
531
|
|
|
532
|
+
function updateHeaderOffsetTop() {
|
|
533
|
+
if (headerRowRef.value) {
|
|
534
|
+
headerOffsetTop.value = headerRowRef.value.getBoundingClientRect().top + window.scrollY;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
298
538
|
function calculateStickyColumnPositions() {
|
|
299
539
|
if (!headerRowRef.value) return;
|
|
300
540
|
|
|
@@ -457,7 +697,12 @@ function setHeaderCellWidth() {
|
|
|
457
697
|
}
|
|
458
698
|
|
|
459
699
|
function onScroll() {
|
|
460
|
-
|
|
700
|
+
if (scrollRafId !== null) return;
|
|
701
|
+
|
|
702
|
+
scrollRafId = requestAnimationFrame(() => {
|
|
703
|
+
pagePositionY.value = Number(window?.scrollY);
|
|
704
|
+
scrollRafId = null;
|
|
705
|
+
});
|
|
461
706
|
}
|
|
462
707
|
|
|
463
708
|
function onKeyupEsc(event: KeyboardEvent) {
|
|
@@ -492,6 +737,85 @@ function onClickCell(cell: Cell, row: Row, key: string | number) {
|
|
|
492
737
|
emit("clickCell", cell, row, key);
|
|
493
738
|
}
|
|
494
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
|
+
|
|
495
819
|
function onChangeSelectAll(selectAll: boolean) {
|
|
496
820
|
if (selectAll && canSelectAll.value) {
|
|
497
821
|
localSelectedRows.value = [...flatTableRows.value];
|
|
@@ -511,6 +835,7 @@ function onChangeLocalSelectedRows(selectedRows: Row[]) {
|
|
|
511
835
|
nextTick(setHeaderCellWidth);
|
|
512
836
|
}
|
|
513
837
|
|
|
838
|
+
// Set flag to prevent selectAll watcher from triggering
|
|
514
839
|
selectAll.value = !!selectedRows.length;
|
|
515
840
|
|
|
516
841
|
emit("update:selectedRows", localSelectedRows.value);
|
|
@@ -521,15 +846,15 @@ function clearSelectedItems() {
|
|
|
521
846
|
}
|
|
522
847
|
|
|
523
848
|
function onToggleExpand(row: Row) {
|
|
524
|
-
const
|
|
849
|
+
const expanded = localExpandedRows.value;
|
|
525
850
|
|
|
526
|
-
if (
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
851
|
+
if (expandedRowsSet.value.has(row.id)) {
|
|
852
|
+
const idsToRemove = new Set([row.id, ...getRowChildrenIds(row)]);
|
|
853
|
+
|
|
854
|
+
localExpandedRows.value = expanded.filter((id) => !idsToRemove.has(id));
|
|
530
855
|
emit("row-collapse", row);
|
|
531
856
|
} else {
|
|
532
|
-
localExpandedRows.value
|
|
857
|
+
localExpandedRows.value = [...expanded, row.id];
|
|
533
858
|
emit("row-expand", row);
|
|
534
859
|
}
|
|
535
860
|
|
|
@@ -553,7 +878,9 @@ function isRowSelectedWithin(rowIndex: number) {
|
|
|
553
878
|
function onToggleRowCheckbox(row: Row) {
|
|
554
879
|
const targetIndex = localSelectedRows.value.findIndex((selectedRow) => selectedRow.id === row.id);
|
|
555
880
|
|
|
556
|
-
|
|
881
|
+
localSelectedRows.value = ~targetIndex
|
|
882
|
+
? localSelectedRows.value.filter((_, i) => i !== targetIndex)
|
|
883
|
+
: [...localSelectedRows.value, row];
|
|
557
884
|
}
|
|
558
885
|
|
|
559
886
|
function getDateDividerConfig(row: Row, isSelected: boolean) {
|
|
@@ -570,7 +897,46 @@ function getDateDividerConfig(row: Row, isSelected: boolean) {
|
|
|
570
897
|
function isRowSelected(row: Row | undefined) {
|
|
571
898
|
if (!row) return false;
|
|
572
899
|
|
|
573
|
-
return
|
|
900
|
+
return selectedRowIds.value.has(row.id);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function getCellTextValue(cellValue: unknown): string {
|
|
904
|
+
if (cellValue == null) return "";
|
|
905
|
+
|
|
906
|
+
if (typeof cellValue === "object" && "value" in (cellValue as Record<string, unknown>)) {
|
|
907
|
+
const val = (cellValue as Record<string, unknown>).value;
|
|
908
|
+
|
|
909
|
+
return val != null ? String(val) : "";
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
return String(cellValue);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function getRowSearchMatchColumns(row: FlatRow): Set<string> | undefined {
|
|
916
|
+
return searchMatchColumnSets.value.get(row.id);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function getRowActiveSearchMatchColumn(row: FlatRow): string | undefined {
|
|
920
|
+
if (!activeMatch.value || activeMatch.value.rowId !== row.id) return undefined;
|
|
921
|
+
|
|
922
|
+
return activeMatch.value.columnKey;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function scrollToRow(rowId: RowId) {
|
|
926
|
+
if (!tableWrapperRef.value) return;
|
|
927
|
+
|
|
928
|
+
const targetRow = tableWrapperRef.value.querySelector<HTMLTableRowElement>(
|
|
929
|
+
`tr[data-row-id="${rowId}"]`,
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
if (!targetRow) return;
|
|
933
|
+
|
|
934
|
+
const containerRect = tableWrapperRef.value.getBoundingClientRect();
|
|
935
|
+
const rowRect = targetRow.getBoundingClientRect();
|
|
936
|
+
|
|
937
|
+
if (rowRect.top < containerRect.top || rowRect.bottom > containerRect.bottom) {
|
|
938
|
+
targetRow.scrollIntoView({ block: "center" });
|
|
939
|
+
}
|
|
574
940
|
}
|
|
575
941
|
|
|
576
942
|
defineExpose({
|
|
@@ -587,6 +953,23 @@ defineExpose({
|
|
|
587
953
|
wrapperRef,
|
|
588
954
|
});
|
|
589
955
|
|
|
956
|
+
/* Cached slot-content checks to avoid re-creating VNodes on every render. */
|
|
957
|
+
const hasBeforeHeaderSlot = computed(() => {
|
|
958
|
+
return hasSlotContent(slots["before-header"]);
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
const hasBeforeFirstRowSlot = computed(() => {
|
|
962
|
+
return hasSlotContent(slots["before-first-row"]);
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
const hasAfterLastRowSlot = computed(() => {
|
|
966
|
+
return hasSlotContent(slots["after-last-row"]);
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
const hasFooterSlot = computed(() => {
|
|
970
|
+
return hasSlotContent(slots["footer"]);
|
|
971
|
+
});
|
|
972
|
+
|
|
590
973
|
/**
|
|
591
974
|
* Get element / nested component attributes for each config token ✨
|
|
592
975
|
* Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
|
|
@@ -649,7 +1032,93 @@ const {
|
|
|
649
1032
|
headerCellStickyRightAttrs,
|
|
650
1033
|
bodyCellStickyLeftAttrs,
|
|
651
1034
|
bodyCellStickyRightAttrs,
|
|
1035
|
+
bodyCellSearchMatchAttrs,
|
|
1036
|
+
bodyCellSearchMatchTextAttrs,
|
|
1037
|
+
bodyCellSearchMatchActiveAttrs,
|
|
1038
|
+
bodyCellSearchMatchTextActiveAttrs,
|
|
652
1039
|
} = useUI<Config>(defaultConfig, mutatedProps);
|
|
1040
|
+
|
|
1041
|
+
/* Plain object — inner refs are already reactive. */
|
|
1042
|
+
const tableRowAttrs = {
|
|
1043
|
+
bodyCellContentAttrs,
|
|
1044
|
+
bodyCellCheckboxAttrs,
|
|
1045
|
+
bodyCheckboxAttrs,
|
|
1046
|
+
bodyCellNestedAttrs,
|
|
1047
|
+
bodyCellNestedExpandIconAttrs,
|
|
1048
|
+
bodyCellNestedCollapseIconAttrs,
|
|
1049
|
+
bodyCellBaseAttrs,
|
|
1050
|
+
bodyCellNestedIconWrapperAttrs,
|
|
1051
|
+
bodyRowCheckedAttrs,
|
|
1052
|
+
bodyRowAttrs,
|
|
1053
|
+
bodyCellStickyLeftAttrs,
|
|
1054
|
+
bodyCellStickyRightAttrs,
|
|
1055
|
+
bodyCellSearchMatchAttrs,
|
|
1056
|
+
bodyCellSearchMatchTextAttrs,
|
|
1057
|
+
bodyCellSearchMatchActiveAttrs,
|
|
1058
|
+
bodyCellSearchMatchTextActiveAttrs,
|
|
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
|
+
}
|
|
653
1122
|
</script>
|
|
654
1123
|
|
|
655
1124
|
<template>
|
|
@@ -690,10 +1159,10 @@ const {
|
|
|
690
1159
|
>
|
|
691
1160
|
<template v-if="hasSlotContent($slots[`header-${column.key}`], { column, index })">
|
|
692
1161
|
<!--
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1162
|
+
@slot Use it to customize needed header cell.
|
|
1163
|
+
@binding {object} column
|
|
1164
|
+
@binding {number} index
|
|
1165
|
+
-->
|
|
697
1166
|
<slot :name="`header-${column.key}`" :column="column" :index="index" />
|
|
698
1167
|
</template>
|
|
699
1168
|
|
|
@@ -772,12 +1241,7 @@ const {
|
|
|
772
1241
|
<div ref="table-wrapper" v-bind="tableWrapperAttrs" @scroll="virtualScroll.onScroll">
|
|
773
1242
|
<table v-bind="tableAttrs">
|
|
774
1243
|
<thead v-bind="headerAttrs" :style="tableRowWidthStyle">
|
|
775
|
-
<tr
|
|
776
|
-
v-if="
|
|
777
|
-
hasSlotContent($slots['before-header'], { colsCount, classes: headerRowAttrs.class })
|
|
778
|
-
"
|
|
779
|
-
v-bind="beforeHeaderRowAttrs"
|
|
780
|
-
>
|
|
1244
|
+
<tr v-if="hasBeforeHeaderSlot" v-bind="beforeHeaderRowAttrs">
|
|
781
1245
|
<!--
|
|
782
1246
|
@slot Use it to add something before header row.
|
|
783
1247
|
@binding {number} cols-count
|
|
@@ -844,9 +1308,14 @@ const {
|
|
|
844
1308
|
<ULoaderProgress :loading="loading" v-bind="headerLoaderAttrs" />
|
|
845
1309
|
</thead>
|
|
846
1310
|
|
|
847
|
-
<tbody
|
|
1311
|
+
<tbody
|
|
1312
|
+
v-if="sortedRows.length"
|
|
1313
|
+
v-bind="bodyAttrs"
|
|
1314
|
+
@click="onBodyClick"
|
|
1315
|
+
@dblclick="onBodyDoubleClick"
|
|
1316
|
+
>
|
|
848
1317
|
<tr
|
|
849
|
-
v-if="
|
|
1318
|
+
v-if="hasBeforeFirstRowSlot"
|
|
850
1319
|
v-bind="isRowSelected(sortedRows[0]) ? beforeBodyRowCheckedAttrs : beforeBodyRowAttrs"
|
|
851
1320
|
>
|
|
852
1321
|
<td :colspan="colsCount" v-bind="beforeBodyRowCellAttrs">
|
|
@@ -862,100 +1331,9 @@ const {
|
|
|
862
1331
|
/>
|
|
863
1332
|
</tr>
|
|
864
1333
|
|
|
865
|
-
<
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
v-bind="bodyRowDateDividerAttrs"
|
|
869
|
-
>
|
|
870
|
-
<td v-bind="bodyCellDateDividerAttrs" :colspan="colsCount">
|
|
871
|
-
<UDivider
|
|
872
|
-
:label="getDateDividerData(row.rowDate).label"
|
|
873
|
-
v-bind="bodyDateDividerAttrs"
|
|
874
|
-
:config="getDateDividerConfig(row, false)"
|
|
875
|
-
/>
|
|
876
|
-
</td>
|
|
877
|
-
</tr>
|
|
878
|
-
|
|
879
|
-
<tr
|
|
880
|
-
v-if="isShownDateDivider(rowIndex) && isRowSelectedWithin(rowIndex) && row.rowDate"
|
|
881
|
-
v-bind="bodyRowCheckedDateDividerAttrs"
|
|
882
|
-
>
|
|
883
|
-
<td v-bind="bodyCellDateDividerAttrs" :colspan="colsCount">
|
|
884
|
-
<UDivider
|
|
885
|
-
:label="getDateDividerData(row.rowDate).label"
|
|
886
|
-
v-bind="bodySelectedDateDividerAttrs"
|
|
887
|
-
:config="getDateDividerConfig(row, true)"
|
|
888
|
-
/>
|
|
889
|
-
</td>
|
|
890
|
-
</tr>
|
|
891
|
-
|
|
892
|
-
<UTableRow
|
|
893
|
-
:selectable="selectable"
|
|
894
|
-
:row="row"
|
|
895
|
-
:columns="normalizedColumns"
|
|
896
|
-
:config="config"
|
|
897
|
-
:attrs="tableRowAttrs as unknown as UTableRowAttrs"
|
|
898
|
-
:cols-count="colsCount"
|
|
899
|
-
:nested-level="Number(row.nestedLevel || 0)"
|
|
900
|
-
:empty-cell-label="emptyCellLabel"
|
|
901
|
-
:data-test="getDataTest('row')"
|
|
902
|
-
:is-expanded="localExpandedRows.includes(row.id)"
|
|
903
|
-
:is-checked="isRowSelected(row)"
|
|
904
|
-
:column-positions="columnPositions"
|
|
905
|
-
@click="onClickRow"
|
|
906
|
-
@dblclick="onDoubleClickRow"
|
|
907
|
-
@click-cell="onClickCell"
|
|
908
|
-
@toggle-expand="onToggleExpand"
|
|
909
|
-
@toggle-checkbox="onToggleRowCheckbox"
|
|
910
|
-
>
|
|
911
|
-
<template
|
|
912
|
-
v-for="(value, key, cellIndex) in mapRowColumns(row, normalizedColumns)"
|
|
913
|
-
:key="`${rowIndex}-${cellIndex}`"
|
|
914
|
-
#[`cell-${key}`]="{ value: cellValue, row: cellRow }"
|
|
915
|
-
>
|
|
916
|
-
<!--
|
|
917
|
-
@slot Use it to customize needed table cell.
|
|
918
|
-
@binding {string} value
|
|
919
|
-
@binding {object} row
|
|
920
|
-
@binding {number} index
|
|
921
|
-
@binding {number} cellIndex
|
|
922
|
-
-->
|
|
923
|
-
<slot
|
|
924
|
-
:name="`cell-${key}`"
|
|
925
|
-
:value="cellValue"
|
|
926
|
-
:row="cellRow"
|
|
927
|
-
:index="rowIndex"
|
|
928
|
-
:cell-index="cellIndex"
|
|
929
|
-
/>
|
|
930
|
-
</template>
|
|
931
|
-
|
|
932
|
-
<template #expand="{ row: expandedRow, expanded }">
|
|
933
|
-
<!--
|
|
934
|
-
@slot Use it to customize row expand icon.
|
|
935
|
-
@binding {object} row
|
|
936
|
-
@binding {boolean} expanded
|
|
937
|
-
@binding {number} index
|
|
938
|
-
-->
|
|
939
|
-
<slot name="expand" :index="rowIndex" :row="expandedRow" :expanded="expanded" />
|
|
940
|
-
</template>
|
|
941
|
-
|
|
942
|
-
<template #nested-row>
|
|
943
|
-
<!--
|
|
944
|
-
@slot Use it to add inside nested row.
|
|
945
|
-
@binding {object} row
|
|
946
|
-
@binding {number} index
|
|
947
|
-
@binding {number} nestedLevel
|
|
948
|
-
-->
|
|
949
|
-
<slot
|
|
950
|
-
v-if="row"
|
|
951
|
-
name="nested-row"
|
|
952
|
-
:index="rowIndex"
|
|
953
|
-
:row="row"
|
|
954
|
-
:nested-level="Number(row.nestedLevel || 0)"
|
|
955
|
-
/>
|
|
956
|
-
</template>
|
|
957
|
-
</UTableRow>
|
|
958
|
-
</template>
|
|
1334
|
+
<component
|
|
1335
|
+
:is="() => renderedRows.map((row, rowIndex) => renderRowTemplate(row, rowIndex)).flat()"
|
|
1336
|
+
/>
|
|
959
1337
|
|
|
960
1338
|
<tr v-if="props.virtualScroll && virtualScroll.bottomSpacerHeight.value > 0">
|
|
961
1339
|
<td
|
|
@@ -964,15 +1342,7 @@ const {
|
|
|
964
1342
|
/>
|
|
965
1343
|
</tr>
|
|
966
1344
|
|
|
967
|
-
<tr
|
|
968
|
-
v-if="
|
|
969
|
-
hasSlotContent($slots['after-last-row'], {
|
|
970
|
-
colsCount,
|
|
971
|
-
classes: bodyCellBaseAttrs.class,
|
|
972
|
-
})
|
|
973
|
-
"
|
|
974
|
-
v-bind="afterBodyRowAttrs"
|
|
975
|
-
>
|
|
1345
|
+
<tr v-if="hasAfterLastRowSlot" v-bind="afterBodyRowAttrs">
|
|
976
1346
|
<!--
|
|
977
1347
|
@slot Use it to add something after last row.
|
|
978
1348
|
@binding {number} cols-count
|
|
@@ -1002,7 +1372,7 @@ const {
|
|
|
1002
1372
|
</tr>
|
|
1003
1373
|
</tbody>
|
|
1004
1374
|
|
|
1005
|
-
<tfoot v-if="
|
|
1375
|
+
<tfoot v-if="hasFooterSlot" v-bind="footerAttrs">
|
|
1006
1376
|
<tr ref="footer-row" v-bind="footerRowAttrs">
|
|
1007
1377
|
<td v-if="selectable" />
|
|
1008
1378
|
|