vueless 1.3.9-beta.8 → 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/composables/useVirtualScroll.ts +100 -0
- package/package.json +2 -2
- package/types.ts +11 -0
- package/ui.button-link/ULink.vue +18 -2
- package/ui.data-table/UTable.vue +597 -198
- package/ui.data-table/UTableRow.vue +372 -168
- package/ui.data-table/config.ts +33 -5
- package/ui.data-table/storybook/stories.ts +163 -0
- package/ui.data-table/tests/UTable.test.ts +456 -20
- package/ui.data-table/tests/UTableRow.test.ts +35 -104
- package/ui.data-table/types.ts +57 -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
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Comment,
|
|
4
|
+
Fragment,
|
|
5
|
+
Text,
|
|
6
|
+
computed,
|
|
7
|
+
h,
|
|
8
|
+
onMounted,
|
|
9
|
+
useAttrs,
|
|
10
|
+
useSlots,
|
|
11
|
+
useTemplateRef,
|
|
12
|
+
} from "vue";
|
|
13
|
+
|
|
14
|
+
import { useUI } from "../composables/useUI";
|
|
15
|
+
import { useMutationObserver } from "../composables/useMutationObserver";
|
|
16
|
+
|
|
17
|
+
import { isEmptyValue } from "../utils/helper";
|
|
3
18
|
import { cx } from "../utils/ui";
|
|
4
|
-
import { hasSlotContent, isEmptyValue } from "../utils/helper";
|
|
5
19
|
|
|
6
20
|
import { PX_IN_REM } from "../constants";
|
|
7
|
-
import { mapRowColumns } from "./utilTable";
|
|
8
21
|
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
22
|
+
import defaultConfig from "./config";
|
|
23
|
+
import { mapRowColumns } from "./utilTable";
|
|
24
|
+
import { StickySide } from "./types";
|
|
11
25
|
|
|
12
|
-
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
13
26
|
import UCheckbox from "../ui.form-checkbox/UCheckbox.vue";
|
|
27
|
+
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
14
28
|
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
import { StickySide } from "./types";
|
|
29
|
+
import type { Slot, VNode } from "vue";
|
|
18
30
|
import type { Cell, CellObject, Row, UTableRowProps, Config, ColumnObject } from "./types";
|
|
19
31
|
|
|
20
32
|
const NESTED_ROW_SHIFT_REM = 1.5;
|
|
@@ -24,17 +36,19 @@ defineOptions({ internal: true });
|
|
|
24
36
|
|
|
25
37
|
const props = defineProps<UTableRowProps>();
|
|
26
38
|
|
|
27
|
-
const
|
|
39
|
+
const slots = useSlots();
|
|
40
|
+
const attrs = useAttrs();
|
|
28
41
|
|
|
29
42
|
const cellRef = useTemplateRef<HTMLDivElement[]>("cell");
|
|
30
|
-
const toggleWrapperRef = useTemplateRef<HTMLDivElement[]>("toggle-wrapper");
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
if (props.textEllipsis) {
|
|
45
|
+
useMutationObserver(cellRef, setCellTitle, {
|
|
46
|
+
subtree: true,
|
|
47
|
+
childList: true,
|
|
48
|
+
characterData: true,
|
|
49
|
+
attributes: false,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
38
52
|
|
|
39
53
|
const toggleIconConfig = computed(() => {
|
|
40
54
|
const nestedRow = props.row?.row;
|
|
@@ -63,17 +77,6 @@ function getToggleIconName() {
|
|
|
63
77
|
: props.config?.defaults?.expandIcon;
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
function getIconWidth() {
|
|
67
|
-
const icon = document.querySelector(`[data-row-toggle-icon='${props.row.id}']`);
|
|
68
|
-
const currentWrapperWidth = toggleWrapperRef.value?.at(0)?.getBoundingClientRect()?.width || 0;
|
|
69
|
-
|
|
70
|
-
if (icon) {
|
|
71
|
-
return `${icon.getBoundingClientRect().width / PX_IN_REM}rem`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return `${currentWrapperWidth / PX_IN_REM || 1}rem`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
80
|
function getCellClasses(row: Row, key: string) {
|
|
78
81
|
const isCellData = typeof row[key] === "object" && row[key] !== null && "class" in row[key];
|
|
79
82
|
const cell = row[key] as CellObject;
|
|
@@ -107,20 +110,6 @@ function getNestedCheckboxShift() {
|
|
|
107
110
|
return { transform: `translateX(${props.nestedLevel * LAST_NESTED_ROW_SHIFT_REM}rem)` };
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
function onClick(row: Row) {
|
|
111
|
-
emit("click", row);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function onDoubleClick(row: Row) {
|
|
115
|
-
const selection = window.getSelection();
|
|
116
|
-
|
|
117
|
-
if (selection) {
|
|
118
|
-
selection.removeAllRanges();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
emit("dblclick", row);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
113
|
function setCellTitle(mutations: MutationRecord[]) {
|
|
125
114
|
mutations.forEach((mutation) => {
|
|
126
115
|
const { target } = mutation;
|
|
@@ -145,10 +134,6 @@ function setElementTitle(element: HTMLElement) {
|
|
|
145
134
|
}
|
|
146
135
|
}
|
|
147
136
|
|
|
148
|
-
function onClickCell(cell: unknown | string | number, row: Row, key: string | number) {
|
|
149
|
-
emit("clickCell", cell, row, key);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
137
|
function getRowClasses(row: Row) {
|
|
153
138
|
const rowClasses = row?.class || "";
|
|
154
139
|
|
|
@@ -159,14 +144,6 @@ function getRowAttrs() {
|
|
|
159
144
|
return props.isChecked ? props.attrs.bodyRowCheckedAttrs.value : props.attrs.bodyRowAttrs.value;
|
|
160
145
|
}
|
|
161
146
|
|
|
162
|
-
function onToggleExpand(row: Row) {
|
|
163
|
-
emit("toggleExpand", row);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function onInputCheckbox(row: Row) {
|
|
167
|
-
emit("toggleCheckbox", row);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
147
|
function getStickyColumnStyle(column: ColumnObject) {
|
|
171
148
|
const position = props.columnPositions.get(column.key);
|
|
172
149
|
|
|
@@ -195,122 +172,349 @@ function getStickyColumnClass(column: ColumnObject) {
|
|
|
195
172
|
return "";
|
|
196
173
|
}
|
|
197
174
|
|
|
175
|
+
function isCellSearchMatch(key: string): boolean {
|
|
176
|
+
return Boolean(props.searchMatchColumns?.has(key));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isCellActiveSearchMatch(key: string): boolean {
|
|
180
|
+
return props.activeSearchMatchColumn === key;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getSearchMatchCellClass(key: string): string {
|
|
184
|
+
if (!isCellSearchMatch(key)) return "";
|
|
185
|
+
|
|
186
|
+
return isCellActiveSearchMatch(key)
|
|
187
|
+
? (props.attrs.bodyCellSearchMatchActiveAttrs.value.class as string)
|
|
188
|
+
: (props.attrs.bodyCellSearchMatchAttrs.value.class as string);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getHighlightedHtml(value: Cell, key: string): string {
|
|
192
|
+
const text = String(formatCellValue(value));
|
|
193
|
+
const query = props.search;
|
|
194
|
+
|
|
195
|
+
if (!query || !isCellSearchMatch(key)) return escapeHtml(text);
|
|
196
|
+
|
|
197
|
+
const isActive = isCellActiveSearchMatch(key);
|
|
198
|
+
const matchClass = isActive
|
|
199
|
+
? (props.attrs.bodyCellSearchMatchTextActiveAttrs.value.class as string)
|
|
200
|
+
: (props.attrs.bodyCellSearchMatchTextAttrs.value.class as string);
|
|
201
|
+
|
|
202
|
+
const lowerText = text.toLowerCase();
|
|
203
|
+
const lowerQuery = query.toLowerCase();
|
|
204
|
+
const parts: string[] = [];
|
|
205
|
+
let lastIndex = 0;
|
|
206
|
+
|
|
207
|
+
let pos = lowerText.indexOf(lowerQuery);
|
|
208
|
+
|
|
209
|
+
while (~pos) {
|
|
210
|
+
if (pos > lastIndex) {
|
|
211
|
+
parts.push(escapeHtml(text.slice(lastIndex, pos)));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
parts.push(
|
|
215
|
+
`<mark class="${matchClass}">${escapeHtml(text.slice(pos, pos + query.length))}</mark>`,
|
|
216
|
+
);
|
|
217
|
+
lastIndex = pos + query.length;
|
|
218
|
+
pos = lowerText.indexOf(lowerQuery, lastIndex);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (lastIndex < text.length) {
|
|
222
|
+
parts.push(escapeHtml(text.slice(lastIndex)));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return parts.join("");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function escapeHtml(text: string): string {
|
|
229
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function isNestedFirstCell(index: number): boolean {
|
|
233
|
+
return (Boolean(props.row.row) || Boolean(props.nestedLevel)) && index === 0;
|
|
234
|
+
}
|
|
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
|
+
|
|
284
|
+
function shouldRenderCellWrapper(row: Row, key: string): boolean {
|
|
285
|
+
return Boolean(
|
|
286
|
+
props.textEllipsis ||
|
|
287
|
+
(props.search && isCellSearchMatch(key)) ||
|
|
288
|
+
getCellContentClass(row, String(key)),
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function renderCellContent(value: Cell, key: string, cellIndex: number): VNode | VNode[] | string {
|
|
293
|
+
const keyStr = String(key);
|
|
294
|
+
|
|
295
|
+
const slotContent = resolveSlotContent(slots[`cell-${key}`], {
|
|
296
|
+
value,
|
|
297
|
+
row: props.row,
|
|
298
|
+
index: getRowIndex(),
|
|
299
|
+
cellIndex,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Check if slot exists
|
|
303
|
+
if (slotContent) {
|
|
304
|
+
return slotContent as VNode | VNode[] | string;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Render cell wrapper with highlighted HTML
|
|
308
|
+
if (shouldRenderCellWrapper(props.row, keyStr)) {
|
|
309
|
+
return h("div", {
|
|
310
|
+
ref: cellRef,
|
|
311
|
+
...props.attrs.bodyCellContentAttrs.value,
|
|
312
|
+
class: cx([
|
|
313
|
+
props.attrs.bodyCellContentAttrs.value.class,
|
|
314
|
+
getCellContentClass(props.row, keyStr),
|
|
315
|
+
]),
|
|
316
|
+
innerHTML: getHighlightedHtml(value, keyStr),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Render plain text
|
|
321
|
+
return formatCellValue(value);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function renderNestedFirstCell(value: Cell, key: string, cellIndex: number): VNode {
|
|
325
|
+
const keyStr = String(key);
|
|
326
|
+
|
|
327
|
+
const expandSlotContent = resolveSlotContent(slots.expand, {
|
|
328
|
+
index: getRowIndex(),
|
|
329
|
+
row: props.row,
|
|
330
|
+
expanded: props.isExpanded,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const toggleIconNode = h(UIcon, {
|
|
334
|
+
size: "xs",
|
|
335
|
+
interactive: true,
|
|
336
|
+
name: getToggleIconName(),
|
|
337
|
+
color: "primary",
|
|
338
|
+
...toggleIconConfig.value,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const toggleWrapperNode = h(
|
|
342
|
+
"div",
|
|
343
|
+
{
|
|
344
|
+
...props.attrs.bodyCellNestedIconWrapperAttrs.value,
|
|
345
|
+
},
|
|
346
|
+
[toggleIconNode],
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const toggleIconWrapperNode = h(
|
|
350
|
+
"div",
|
|
351
|
+
{
|
|
352
|
+
"data-row-toggle-icon": props.row.id,
|
|
353
|
+
"data-expand-icon": props.row.id,
|
|
354
|
+
onDblclick: (e: Event) => e.stopPropagation(),
|
|
355
|
+
},
|
|
356
|
+
expandSlotContent || [toggleWrapperNode],
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const cellSlotContent = resolveSlotContent(slots[`cell-${key}`], {
|
|
360
|
+
value,
|
|
361
|
+
row: props.row,
|
|
362
|
+
index: getRowIndex(),
|
|
363
|
+
cellIndex,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
return h(
|
|
367
|
+
"div",
|
|
368
|
+
{
|
|
369
|
+
style: getNestedShift(),
|
|
370
|
+
...props.attrs.bodyCellNestedAttrs.value,
|
|
371
|
+
},
|
|
372
|
+
[
|
|
373
|
+
props.row.row ? toggleIconWrapperNode : null,
|
|
374
|
+
// Cell content
|
|
375
|
+
...(() => {
|
|
376
|
+
if (cellSlotContent) {
|
|
377
|
+
return Array.isArray(cellSlotContent) ? cellSlotContent : [cellSlotContent];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (shouldRenderCellWrapper(props.row, keyStr)) {
|
|
381
|
+
return [
|
|
382
|
+
h("div", {
|
|
383
|
+
ref: cellRef,
|
|
384
|
+
...props.attrs.bodyCellContentAttrs.value,
|
|
385
|
+
class: cx([
|
|
386
|
+
props.attrs.bodyCellContentAttrs.value.class,
|
|
387
|
+
getCellContentClass(props.row, keyStr),
|
|
388
|
+
]),
|
|
389
|
+
"data-test": getDataTest(`${key}-cell`),
|
|
390
|
+
innerHTML: getHighlightedHtml(value, keyStr),
|
|
391
|
+
}),
|
|
392
|
+
];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return [formatCellValue(value)];
|
|
396
|
+
})(),
|
|
397
|
+
].filter(Boolean),
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function renderTableCell(value: Cell, key: string, index: number): VNode {
|
|
402
|
+
const keyStr = String(key);
|
|
403
|
+
|
|
404
|
+
const nestedCellNode = isNestedFirstCell(index)
|
|
405
|
+
? renderNestedFirstCell(value, key, index)
|
|
406
|
+
: renderCellContent(value, key, index);
|
|
407
|
+
|
|
408
|
+
const cellChildren = Array.isArray(nestedCellNode) ? nestedCellNode : [nestedCellNode];
|
|
409
|
+
|
|
410
|
+
return h(
|
|
411
|
+
"td",
|
|
412
|
+
{
|
|
413
|
+
key: index,
|
|
414
|
+
...props.attrs.bodyCellBaseAttrs.value,
|
|
415
|
+
class: cx([
|
|
416
|
+
props.attrs.bodyCellBaseAttrs.value.class,
|
|
417
|
+
props.columns[index].tdClass,
|
|
418
|
+
getCellClasses(props.row, keyStr),
|
|
419
|
+
getStickyColumnClass(props.columns[index]),
|
|
420
|
+
getSearchMatchCellClass(keyStr),
|
|
421
|
+
]),
|
|
422
|
+
style: getStickyColumnStyle(props.columns[index]),
|
|
423
|
+
"data-cell-key": key,
|
|
424
|
+
"data-test": getDataTest(`${key}-cell`),
|
|
425
|
+
},
|
|
426
|
+
cellChildren,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function renderCheckboxCell(): VNode | null {
|
|
431
|
+
if (!props.selectable) return null;
|
|
432
|
+
|
|
433
|
+
const checkboxNode = h(UCheckbox, {
|
|
434
|
+
modelValue: props.isChecked,
|
|
435
|
+
size: "md",
|
|
436
|
+
...props.attrs.bodyCheckboxAttrs.value,
|
|
437
|
+
"data-id": props.row.id,
|
|
438
|
+
"data-checkbox-id": props.row.id,
|
|
439
|
+
"data-test": getDataTest("body-checkbox"),
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
return h(
|
|
443
|
+
"td",
|
|
444
|
+
{
|
|
445
|
+
...props.attrs.bodyCellCheckboxAttrs.value,
|
|
446
|
+
"data-checkbox-id": props.row.id,
|
|
447
|
+
class: cx([
|
|
448
|
+
props.attrs.bodyCellCheckboxAttrs.value.class,
|
|
449
|
+
props.columns[0]?.sticky === StickySide.Left
|
|
450
|
+
? props.attrs.bodyCellStickyLeftAttrs.value.class
|
|
451
|
+
: "",
|
|
452
|
+
]),
|
|
453
|
+
style: {
|
|
454
|
+
...getNestedCheckboxShift(),
|
|
455
|
+
...(props.columns[0]?.sticky === StickySide.Left ? { left: "0" } : {}),
|
|
456
|
+
},
|
|
457
|
+
onDblclick: (e: Event) => e.stopPropagation(),
|
|
458
|
+
},
|
|
459
|
+
[checkboxNode],
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function renderMainRow(): VNode {
|
|
464
|
+
const cells = Object.entries(mapRowColumns(props.row, props.columns)).map(
|
|
465
|
+
([key, value], index) => {
|
|
466
|
+
return renderTableCell(value, key, index);
|
|
467
|
+
},
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
return h(
|
|
471
|
+
"tr",
|
|
472
|
+
{
|
|
473
|
+
...attrs,
|
|
474
|
+
...getRowAttrs(),
|
|
475
|
+
class: cx([getRowAttrs().class, getRowClasses(props.row)]),
|
|
476
|
+
style: { display: props.isVisible ? "" : "none" },
|
|
477
|
+
},
|
|
478
|
+
[renderCheckboxCell(), ...cells].filter(Boolean),
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function renderNestedRow(nestedRowSlotContent: VNode[]): VNode {
|
|
483
|
+
const tdNode = h(
|
|
484
|
+
"td",
|
|
485
|
+
{ colspan: props.columns.length + Number(props.selectable) },
|
|
486
|
+
nestedRowSlotContent,
|
|
487
|
+
);
|
|
488
|
+
|
|
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()];
|
|
513
|
+
}
|
|
514
|
+
|
|
198
515
|
const { getDataTest } = useUI<Config>(defaultConfig);
|
|
199
516
|
</script>
|
|
200
517
|
|
|
201
518
|
<template>
|
|
202
|
-
<
|
|
203
|
-
v-if="!row.parentRowId || !hasSlotContent($slots['nested-row'])"
|
|
204
|
-
v-bind="{ ...$attrs, ...getRowAttrs() }"
|
|
205
|
-
:class="cx([getRowAttrs().class, getRowClasses(row)])"
|
|
206
|
-
@click="onClick(props.row)"
|
|
207
|
-
@dblclick="onDoubleClick(props.row)"
|
|
208
|
-
>
|
|
209
|
-
<td
|
|
210
|
-
v-if="selectable"
|
|
211
|
-
:style="{
|
|
212
|
-
...getNestedCheckboxShift(),
|
|
213
|
-
...(columns[0]?.sticky === StickySide.Left ? { left: '0' } : {}),
|
|
214
|
-
}"
|
|
215
|
-
v-bind="attrs.bodyCellCheckboxAttrs.value"
|
|
216
|
-
:class="
|
|
217
|
-
cx([
|
|
218
|
-
attrs.bodyCellCheckboxAttrs.value.class,
|
|
219
|
-
columns[0]?.sticky === StickySide.Left ? attrs.bodyCellStickyLeftAttrs.value.class : '',
|
|
220
|
-
])
|
|
221
|
-
"
|
|
222
|
-
@click.stop
|
|
223
|
-
@dblclick.stop
|
|
224
|
-
>
|
|
225
|
-
<UCheckbox
|
|
226
|
-
:model-value="isChecked"
|
|
227
|
-
size="md"
|
|
228
|
-
v-bind="attrs.bodyCheckboxAttrs.value"
|
|
229
|
-
:data-id="row.id"
|
|
230
|
-
:data-test="getDataTest('body-checkbox')"
|
|
231
|
-
@input="onInputCheckbox(row)"
|
|
232
|
-
/>
|
|
233
|
-
</td>
|
|
234
|
-
|
|
235
|
-
<td
|
|
236
|
-
v-for="(value, key, index) in mapRowColumns(row, columns)"
|
|
237
|
-
:key="index"
|
|
238
|
-
v-bind="attrs.bodyCellBaseAttrs.value"
|
|
239
|
-
:class="
|
|
240
|
-
cx([
|
|
241
|
-
columns[index].tdClass,
|
|
242
|
-
getCellClasses(row, String(key)),
|
|
243
|
-
getStickyColumnClass(columns[index]),
|
|
244
|
-
])
|
|
245
|
-
"
|
|
246
|
-
:style="getStickyColumnStyle(columns[index])"
|
|
247
|
-
@click="onClickCell(value, row, key)"
|
|
248
|
-
>
|
|
249
|
-
<div
|
|
250
|
-
v-if="(row.row || nestedLevel) && index === 0"
|
|
251
|
-
:style="getNestedShift()"
|
|
252
|
-
v-bind="attrs.bodyCellNestedAttrs.value"
|
|
253
|
-
>
|
|
254
|
-
<div
|
|
255
|
-
v-if="row.row"
|
|
256
|
-
:data-row-toggle-icon="row.id"
|
|
257
|
-
@dblclick.stop
|
|
258
|
-
@click.stop="onToggleExpand(row)"
|
|
259
|
-
>
|
|
260
|
-
<slot name="expand" :row="row" :expanded="isExpanded">
|
|
261
|
-
<div
|
|
262
|
-
ref="toggle-wrapper"
|
|
263
|
-
v-bind="attrs.bodyCellNestedIconWrapperAttrs.value"
|
|
264
|
-
:style="{ width: getIconWidth() }"
|
|
265
|
-
>
|
|
266
|
-
<UIcon
|
|
267
|
-
size="xs"
|
|
268
|
-
interactive
|
|
269
|
-
:name="getToggleIconName()"
|
|
270
|
-
color="primary"
|
|
271
|
-
v-bind="toggleIconConfig"
|
|
272
|
-
/>
|
|
273
|
-
</div>
|
|
274
|
-
</slot>
|
|
275
|
-
</div>
|
|
276
|
-
<slot :name="`cell-${key}`" :value="value" :row="row" :index="index">
|
|
277
|
-
<div
|
|
278
|
-
v-if="value"
|
|
279
|
-
ref="cell"
|
|
280
|
-
v-bind="attrs.bodyCellContentAttrs.value"
|
|
281
|
-
:class="
|
|
282
|
-
cx([attrs.bodyCellContentAttrs.value.class, getCellContentClass(row, String(key))])
|
|
283
|
-
"
|
|
284
|
-
:data-test="getDataTest(`${key}-cell`)"
|
|
285
|
-
>
|
|
286
|
-
{{ formatCellValue(value) }}
|
|
287
|
-
</div>
|
|
288
|
-
</slot>
|
|
289
|
-
</div>
|
|
290
|
-
|
|
291
|
-
<template v-else>
|
|
292
|
-
<slot :name="`cell-${key}`" :value="value" :row="row" :index="index">
|
|
293
|
-
<div
|
|
294
|
-
v-bind="attrs.bodyCellContentAttrs.value"
|
|
295
|
-
ref="cell"
|
|
296
|
-
:class="
|
|
297
|
-
cx([attrs.bodyCellContentAttrs.value.class, getCellContentClass(row, String(key))])
|
|
298
|
-
"
|
|
299
|
-
:data-test="getDataTest(`${key}-cell`)"
|
|
300
|
-
>
|
|
301
|
-
{{ formatCellValue(value) }}
|
|
302
|
-
</div>
|
|
303
|
-
</slot>
|
|
304
|
-
</template>
|
|
305
|
-
</td>
|
|
306
|
-
</tr>
|
|
307
|
-
|
|
308
|
-
<tr
|
|
309
|
-
v-if="row.parentRowId && hasSlotContent($slots['nested-row'], { row, nestedLevel })"
|
|
310
|
-
:class="row.class"
|
|
311
|
-
>
|
|
312
|
-
<td :colspan="columns.length + Number(selectable)">
|
|
313
|
-
<slot name="nested-row" :row="row" :nested-level="nestedLevel" />
|
|
314
|
-
</td>
|
|
315
|
-
</tr>
|
|
519
|
+
<component :is="renderRows" />
|
|
316
520
|
</template>
|
package/ui.data-table/config.ts
CHANGED
|
@@ -29,19 +29,37 @@ export default /*tw*/ {
|
|
|
29
29
|
stickyHeaderLoader: "{ULoaderProgress} absolute top-auto bottom-0",
|
|
30
30
|
headerActionsCheckbox: "{UCheckbox}",
|
|
31
31
|
headerActionsCounter: "{>headerCounterBase} -ml-1.5",
|
|
32
|
-
tableWrapper:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
tableWrapper: {
|
|
33
|
+
base: "border border-solid border-muted rounded-medium bg-default overflow-x-auto ",
|
|
34
|
+
variants: {
|
|
35
|
+
virtualScroll: {
|
|
36
|
+
true: "h-150 overflow-y-auto",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
table: {
|
|
41
|
+
base: "min-w-full border-none text-medium w-auto table-auto border-separate border-spacing-0",
|
|
42
|
+
variants: {
|
|
43
|
+
virtualScroll: {
|
|
44
|
+
true: "table-layout-fixed w-full",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
header: {
|
|
49
|
+
base: "[&>tr:first-child>*]:first:rounded-tl-medium [&>tr:last-child>*]:last:rounded-tr-medium relative",
|
|
50
|
+
},
|
|
36
51
|
headerRow: "",
|
|
37
52
|
beforeHeaderRow: "border border-solid border-muted",
|
|
38
53
|
beforeHeaderCell: "{>headerCellBase}",
|
|
39
54
|
headerCellBase: {
|
|
40
|
-
base: "p-4 text-medium font-normal text-lifted text-left text-nowrap",
|
|
55
|
+
base: "border-b border-muted p-4 text-medium font-normal text-lifted text-left text-nowrap",
|
|
41
56
|
variants: {
|
|
42
57
|
compact: {
|
|
43
58
|
true: "px-4 py-3 last:px-4 last:py-3 first:px-4 first:py-3",
|
|
44
59
|
},
|
|
60
|
+
virtualScroll: {
|
|
61
|
+
true: "sticky top-0 z-10 bg-default",
|
|
62
|
+
},
|
|
45
63
|
},
|
|
46
64
|
},
|
|
47
65
|
headerCellSticky: "sticky z-20 bg-default",
|
|
@@ -79,6 +97,10 @@ export default /*tw*/ {
|
|
|
79
97
|
bodyCellNestedExpandIcon: "{UIcon}",
|
|
80
98
|
bodyCellNestedCollapseIcon: "{UIcon}",
|
|
81
99
|
bodyCheckbox: "{UCheckbox}",
|
|
100
|
+
bodyCellSearchMatch: "bg-warning/10",
|
|
101
|
+
bodyCellSearchMatchActive: "bg-warning/15",
|
|
102
|
+
bodyCellSearchMatchText: "bg-warning/50 rounded-xs",
|
|
103
|
+
bodyCellSearchMatchTextActive: "bg-warning rounded-xs",
|
|
82
104
|
bodyDateDivider: {
|
|
83
105
|
base: "{UDivider} py-2",
|
|
84
106
|
label: "py-0 leading-none",
|
|
@@ -116,12 +138,18 @@ export default /*tw*/ {
|
|
|
116
138
|
},
|
|
117
139
|
defaults: {
|
|
118
140
|
emptyCellLabel: "—",
|
|
141
|
+
rowHeight: 40,
|
|
142
|
+
bufferSize: 10,
|
|
143
|
+
virtualScroll: false,
|
|
119
144
|
compact: false,
|
|
120
145
|
selectable: false,
|
|
121
146
|
dateDivider: false,
|
|
122
147
|
stickyHeader: false,
|
|
123
148
|
stickyFooter: false,
|
|
124
149
|
loading: false,
|
|
150
|
+
textEllipsis: false,
|
|
151
|
+
search: "",
|
|
152
|
+
searchMatch: -1,
|
|
125
153
|
/* icons */
|
|
126
154
|
expandIcon: "add",
|
|
127
155
|
collapseIcon: "remove",
|