vueless 1.5.1-beta.0 → 1.5.1-beta.2
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
CHANGED
package/ui.button/config.ts
CHANGED
package/ui.data-table/UTable.vue
CHANGED
|
@@ -851,6 +851,36 @@ function onBodyDoubleClick(event: MouseEvent) {
|
|
|
851
851
|
onDoubleClickRow(rowData);
|
|
852
852
|
}
|
|
853
853
|
|
|
854
|
+
// Cells are always clipped (`truncate`), so show the full value in a native tooltip when it is.
|
|
855
|
+
const OVERFLOW_THRESHOLD_PX = 1;
|
|
856
|
+
|
|
857
|
+
function isElementOverflown(element: HTMLElement) {
|
|
858
|
+
return (
|
|
859
|
+
element.scrollWidth - element.clientWidth > OVERFLOW_THRESHOLD_PX ||
|
|
860
|
+
element.scrollHeight - element.clientHeight > OVERFLOW_THRESHOLD_PX
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/** Content may be clipped by the cell itself or by any wrapper inside it (slot content included). */
|
|
865
|
+
function isCellContentTruncated(cell: HTMLElement) {
|
|
866
|
+
return [cell, ...cell.querySelectorAll<HTMLElement>("*")].some(isElementOverflown);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function onBodyPointerOver(event: PointerEvent) {
|
|
870
|
+
const target = event.target as HTMLElement;
|
|
871
|
+
const cell = target.closest("td");
|
|
872
|
+
const previousCell = (event.relatedTarget as HTMLElement | null)?.closest("td");
|
|
873
|
+
|
|
874
|
+
// Moving between elements of the same cell needs no re-measure.
|
|
875
|
+
if (!cell || cell === previousCell) return;
|
|
876
|
+
|
|
877
|
+
if (isCellContentTruncated(cell)) {
|
|
878
|
+
cell.setAttribute("title", cell.textContent?.trim() || "");
|
|
879
|
+
} else {
|
|
880
|
+
cell.removeAttribute("title");
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
854
884
|
// Detect double-tap on touch devices and dispatch a native `dblclick`,
|
|
855
885
|
// which bubbles to the tbody and triggers onBodyDoubleClick.
|
|
856
886
|
const DOUBLE_TAP_DELAY = 300;
|
|
@@ -1469,6 +1499,7 @@ const BodyRows = () =>
|
|
|
1469
1499
|
@click="onBodyClick"
|
|
1470
1500
|
@dblclick="onBodyDoubleClick"
|
|
1471
1501
|
@touchend="onBodyTouchEnd"
|
|
1502
|
+
@pointerover="onBodyPointerOver"
|
|
1472
1503
|
>
|
|
1473
1504
|
<tr
|
|
1474
1505
|
v-if="hasBeforeFirstRowSlot"
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
Comment,
|
|
4
|
-
Fragment,
|
|
5
|
-
Text,
|
|
6
|
-
computed,
|
|
7
|
-
h,
|
|
8
|
-
onMounted,
|
|
9
|
-
useAttrs,
|
|
10
|
-
useSlots,
|
|
11
|
-
useTemplateRef,
|
|
12
|
-
} from "vue";
|
|
2
|
+
import { Comment, Fragment, Text, computed, h, useAttrs, useSlots } from "vue";
|
|
13
3
|
|
|
14
4
|
import { useUI } from "../composables/useUI";
|
|
15
|
-
import { useMutationObserver } from "../composables/useMutationObserver";
|
|
16
5
|
|
|
17
6
|
import { isEmptyValue } from "../utils/helper";
|
|
18
7
|
import { cx } from "../utils/ui";
|
|
@@ -39,17 +28,6 @@ const props = defineProps<UTableRowProps>();
|
|
|
39
28
|
const slots = useSlots();
|
|
40
29
|
const attrs = useAttrs();
|
|
41
30
|
|
|
42
|
-
const cellRef = useTemplateRef<HTMLDivElement>("cell");
|
|
43
|
-
|
|
44
|
-
if (props.textEllipsis) {
|
|
45
|
-
useMutationObserver(cellRef, setCellTitle, {
|
|
46
|
-
subtree: true,
|
|
47
|
-
childList: true,
|
|
48
|
-
characterData: true,
|
|
49
|
-
attributes: false,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
31
|
const toggleIconConfig = computed(() => {
|
|
54
32
|
const nestedRow = props.row?.row;
|
|
55
33
|
let isShown = false;
|
|
@@ -65,12 +43,6 @@ const toggleIconConfig = computed(() => {
|
|
|
65
43
|
: props.attrs.bodyCellNestedExpandIconAttrs.value;
|
|
66
44
|
});
|
|
67
45
|
|
|
68
|
-
onMounted(() => {
|
|
69
|
-
if (cellRef.value) {
|
|
70
|
-
setElementTitle(cellRef.value);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
46
|
function getToggleIconName() {
|
|
75
47
|
return props.isExpanded
|
|
76
48
|
? props.config?.defaults?.collapseIcon
|
|
@@ -110,30 +82,6 @@ function getNestedCheckboxShift() {
|
|
|
110
82
|
return { transform: `translateX(${props.nestedLevel * LAST_NESTED_ROW_SHIFT_REM}rem)` };
|
|
111
83
|
}
|
|
112
84
|
|
|
113
|
-
function setCellTitle(mutations: MutationRecord[]) {
|
|
114
|
-
mutations.forEach((mutation) => {
|
|
115
|
-
const { target } = mutation;
|
|
116
|
-
|
|
117
|
-
setElementTitle(target as HTMLElement);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function isElementOverflown(element: HTMLElement) {
|
|
122
|
-
return element.clientWidth < element.scrollWidth || element.clientHeight < element.scrollHeight;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function setElementTitle(element: HTMLElement) {
|
|
126
|
-
const isOverflown = isElementOverflown(element);
|
|
127
|
-
|
|
128
|
-
if (isOverflown) {
|
|
129
|
-
element.setAttribute("title", String(element.textContent));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!isOverflown && element.hasAttribute("title")) {
|
|
133
|
-
element.removeAttribute("title");
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
85
|
function getRowClasses(row: Row) {
|
|
138
86
|
const rowClasses = row?.class || "";
|
|
139
87
|
|
|
@@ -307,7 +255,6 @@ function renderCellContent(value: Cell, key: string, cellIndex: number): VNode |
|
|
|
307
255
|
// Render cell wrapper with highlighted HTML
|
|
308
256
|
if (shouldRenderCellWrapper(props.row, keyStr)) {
|
|
309
257
|
return h("div", {
|
|
310
|
-
ref: cellRef,
|
|
311
258
|
...props.attrs.bodyCellContentAttrs.value,
|
|
312
259
|
class: cx([
|
|
313
260
|
props.attrs.bodyCellContentAttrs.value.class,
|
|
@@ -380,7 +327,6 @@ function renderNestedFirstCell(value: Cell, key: string, cellIndex: number): VNo
|
|
|
380
327
|
if (shouldRenderCellWrapper(props.row, keyStr)) {
|
|
381
328
|
return [
|
|
382
329
|
h("div", {
|
|
383
|
-
ref: cellRef,
|
|
384
330
|
...props.attrs.bodyCellContentAttrs.value,
|
|
385
331
|
class: cx([
|
|
386
332
|
props.attrs.bodyCellContentAttrs.value.class,
|
|
@@ -84,6 +84,11 @@ describe("UTable.vue", () => {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function setElementSize(element: Element, size: { clientWidth: number; scrollWidth: number }) {
|
|
88
|
+
Object.defineProperty(element, "clientWidth", { value: size.clientWidth, configurable: true });
|
|
89
|
+
Object.defineProperty(element, "scrollWidth", { value: size.scrollWidth, configurable: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
describe("Props", () => {
|
|
88
93
|
it("Columns – renders table headers correctly", () => {
|
|
89
94
|
const component = mountUTable(getDefaultProps());
|
|
@@ -1054,6 +1059,48 @@ describe("UTable.vue", () => {
|
|
|
1054
1059
|
});
|
|
1055
1060
|
});
|
|
1056
1061
|
|
|
1062
|
+
describe("Cell Title", () => {
|
|
1063
|
+
it("Cell Title – sets title with the full value when cell content is truncated", async () => {
|
|
1064
|
+
const component = mountUTable(getDefaultProps());
|
|
1065
|
+
|
|
1066
|
+
const cell = component.find("tbody td");
|
|
1067
|
+
|
|
1068
|
+
setElementSize(cell.element, { clientWidth: 100, scrollWidth: 200 });
|
|
1069
|
+
|
|
1070
|
+
await cell.trigger("pointerover");
|
|
1071
|
+
|
|
1072
|
+
expect(cell.attributes("title")).toBe("John Doe");
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
it("Cell Title – removes title when cell content is not truncated", async () => {
|
|
1076
|
+
const component = mountUTable(getDefaultProps());
|
|
1077
|
+
|
|
1078
|
+
const cell = component.find("tbody td");
|
|
1079
|
+
|
|
1080
|
+
cell.element.setAttribute("title", "John Doe");
|
|
1081
|
+
setElementSize(cell.element, { clientWidth: 200, scrollWidth: 200 });
|
|
1082
|
+
|
|
1083
|
+
await cell.trigger("pointerover");
|
|
1084
|
+
|
|
1085
|
+
expect(cell.attributes("title")).toBeUndefined();
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
it("Cell Title – skips re-measure when moving within the same cell", async () => {
|
|
1089
|
+
const component = mountUTable(getDefaultProps());
|
|
1090
|
+
|
|
1091
|
+
const cell = component.find("tbody td");
|
|
1092
|
+
|
|
1093
|
+
setElementSize(cell.element, { clientWidth: 100, scrollWidth: 200 });
|
|
1094
|
+
|
|
1095
|
+
cell.element.dispatchEvent(
|
|
1096
|
+
new MouseEvent("pointerover", { bubbles: true, relatedTarget: cell.element }),
|
|
1097
|
+
);
|
|
1098
|
+
await nextTick();
|
|
1099
|
+
|
|
1100
|
+
expect(cell.attributes("title")).toBeUndefined();
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1057
1104
|
describe("Virtual Scroll with Search", () => {
|
|
1058
1105
|
it("Virtual Scroll with Search – renders only visible rows with search", async () => {
|
|
1059
1106
|
const manyRows = Array.from({ length: 100 }, (_, i) => ({
|
|
@@ -383,61 +383,6 @@ describe("UTableRow.vue", () => {
|
|
|
383
383
|
});
|
|
384
384
|
|
|
385
385
|
describe("Functionality", () => {
|
|
386
|
-
it("Element Title – sets title attribute when element is overflown", async () => {
|
|
387
|
-
// Mock element properties to simulate overflow
|
|
388
|
-
const mockElement = {
|
|
389
|
-
clientWidth: 100,
|
|
390
|
-
scrollWidth: 200,
|
|
391
|
-
clientHeight: 50,
|
|
392
|
-
scrollHeight: 50,
|
|
393
|
-
textContent: "Very long text that overflows",
|
|
394
|
-
setAttribute: vi.fn(),
|
|
395
|
-
removeAttribute: vi.fn(),
|
|
396
|
-
hasAttribute: vi.fn().mockReturnValue(false),
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
const component = mount(UTableRow, {
|
|
400
|
-
props: getDefaultProps(),
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// Access the component's internal methods
|
|
404
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
405
|
-
const vm = component.vm as any;
|
|
406
|
-
|
|
407
|
-
vm.setElementTitle(mockElement);
|
|
408
|
-
|
|
409
|
-
expect(mockElement.setAttribute).toHaveBeenCalledWith(
|
|
410
|
-
"title",
|
|
411
|
-
"Very long text that overflows",
|
|
412
|
-
);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
it("Element Title – removes title attribute when element is not overflown", async () => {
|
|
416
|
-
// Mock element properties to simulate no overflow
|
|
417
|
-
const mockElement = {
|
|
418
|
-
clientWidth: 200,
|
|
419
|
-
scrollWidth: 100,
|
|
420
|
-
clientHeight: 50,
|
|
421
|
-
scrollHeight: 50,
|
|
422
|
-
textContent: "Short text",
|
|
423
|
-
setAttribute: vi.fn(),
|
|
424
|
-
removeAttribute: vi.fn(),
|
|
425
|
-
hasAttribute: vi.fn().mockReturnValue(true),
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
const component = mount(UTableRow, {
|
|
429
|
-
props: getDefaultProps(),
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// Access the component's internal methods
|
|
433
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
434
|
-
const vm = component.vm as any;
|
|
435
|
-
|
|
436
|
-
vm.setElementTitle(mockElement);
|
|
437
|
-
|
|
438
|
-
expect(mockElement.removeAttribute).toHaveBeenCalledWith("title");
|
|
439
|
-
});
|
|
440
|
-
|
|
441
386
|
it("Icon Width – calculates correct icon width", async () => {
|
|
442
387
|
const expandableRow: FlatRow = {
|
|
443
388
|
...defaultRow,
|
package/ui.text-block/config.ts
CHANGED
|
@@ -7,9 +7,9 @@ export default /*tw*/ {
|
|
|
7
7
|
[&_a:not([class]):hover]:no-underline
|
|
8
8
|
[&_ul]:font-normal [&_ol]:font-normal
|
|
9
9
|
[&_ul]:leading-normal [&_ol]:leading-normal
|
|
10
|
-
[&_ul]:list-
|
|
10
|
+
[&_ul]:list-outside [&_ol]:list-outside
|
|
11
11
|
[&_ul]:list-disc [&_ol]:list-decimal
|
|
12
|
-
[&_ul]:
|
|
12
|
+
[&_ul]:ps-4 [&_ol]:ps-4
|
|
13
13
|
`,
|
|
14
14
|
variants: {
|
|
15
15
|
variant: {
|