@revenexx/studio-shared 0.4.0 → 0.4.1
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/dist/module.d.mts +5 -3
- package/dist/module.json +2 -2
- package/dist/module.mjs +7 -3
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardGrid.vue +45 -1
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +1 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +2 -2
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +14 -3
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +2 -2
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +73 -34
- package/dist/runtime/components/DataTable/DataTable.vue +352 -52
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +73 -34
- package/dist/runtime/components/DataTable/FacetPanel.vue +57 -8
- package/dist/runtime/components/DataTable/FilterMenu.vue +5 -5
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +61 -12
- package/dist/runtime/components/DataTable/SortMenu.vue +4 -1
- package/dist/runtime/components/DataTable/ToolbarLabelTrigger.d.vue.ts +23 -0
- package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue +29 -0
- package/dist/runtime/components/DataTable/ToolbarLabelTrigger.vue.d.ts +23 -0
- package/dist/runtime/components/DataTable/types.d.ts +17 -0
- package/dist/runtime/components/Form/FieldGrid.vue +1 -1
- package/dist/runtime/components/Form/SaveBar.d.vue.ts +1 -1
- package/dist/runtime/components/Form/SaveBar.vue.d.ts +1 -1
- package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +4 -4
- package/dist/runtime/components/Media/MediaFolderTree.vue +1 -2
- package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +4 -4
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +11 -0
- package/dist/runtime/components/Media/MediaPickerDialog.vue +206 -54
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +11 -0
- package/dist/runtime/components/Media/MediaUploader.vue +1 -1
- package/dist/runtime/components/Shared/AppIcon.d.vue.ts +1 -1
- package/dist/runtime/components/Shared/AppIcon.vue.d.ts +1 -1
- package/dist/runtime/components/Shared/CardSizeToggle.d.vue.ts +11 -0
- package/dist/runtime/components/Shared/CardSizeToggle.vue +19 -0
- package/dist/runtime/components/Shared/CardSizeToggle.vue.d.ts +11 -0
- package/dist/runtime/components/Shared/DetailSheet.d.vue.ts +1 -1
- package/dist/runtime/components/Shared/DetailSheet.vue +9 -5
- package/dist/runtime/components/Shared/DetailSheet.vue.d.ts +1 -1
- package/dist/runtime/components/Shared/EmptyNote.d.vue.ts +19 -0
- package/dist/runtime/components/Shared/EmptyNote.vue +25 -0
- package/dist/runtime/components/Shared/EmptyNote.vue.d.ts +19 -0
- package/dist/runtime/components/Shared/KpiTile.d.vue.ts +6 -0
- package/dist/runtime/components/Shared/KpiTile.vue +10 -9
- package/dist/runtime/components/Shared/KpiTile.vue.d.ts +6 -0
- package/dist/runtime/components/Shared/PageHeader.vue +15 -4
- package/dist/runtime/components/Shared/RecordLayout.vue +1 -2
- package/dist/runtime/components/Shared/RecordRailCard.vue +3 -3
- package/dist/runtime/components/Shared/SelectionBar.d.vue.ts +22 -0
- package/dist/runtime/components/Shared/SelectionBar.vue +40 -0
- package/dist/runtime/components/Shared/SelectionBar.vue.d.ts +22 -0
- package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +36 -2
- package/dist/runtime/components/Shared/SidebarNav.vue +71 -25
- package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +36 -2
- package/dist/runtime/components/Shared/SidebarNavAreas.d.vue.ts +7 -0
- package/dist/runtime/components/Shared/SidebarNavAreas.vue +291 -0
- package/dist/runtime/components/Shared/SidebarNavAreas.vue.d.ts +7 -0
- package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +1 -9
- package/dist/runtime/components/Shared/SidebarNavLink.vue +31 -5
- package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +1 -9
- package/dist/runtime/components/Shared/SortHeader.vue +6 -2
- package/dist/runtime/components/Shared/StateCard.vue +1 -1
- package/dist/runtime/components/Shared/TabBar.vue +6 -7
- package/dist/runtime/components/Shared/Wizard.d.vue.ts +11 -1
- package/dist/runtime/components/Shared/Wizard.vue +9 -5
- package/dist/runtime/components/Shared/Wizard.vue.d.ts +11 -1
- package/dist/runtime/composables/useMediaLibrary.d.ts +12 -0
- package/dist/runtime/composables/useSavedViews.d.ts +11 -1
- package/dist/runtime/composables/useSavedViews.js +4 -4
- package/dist/runtime/composables/useWidgetFigures.d.ts +12 -0
- package/dist/runtime/composables/useWidgetFigures.js +24 -0
- package/dist/runtime/composables/useWidgetHeader.d.ts +15 -0
- package/dist/runtime/composables/useWidgetHeader.js +15 -0
- package/dist/runtime/lib/auditEnvelope.d.ts +43 -0
- package/dist/runtime/lib/auditEnvelope.js +55 -0
- package/dist/runtime/lib/cardSize.d.ts +13 -0
- package/dist/runtime/lib/cardSize.js +20 -0
- package/dist/runtime/lib/dashboardIcons.js +2 -0
- package/dist/runtime/lib/dataTableFilters.d.ts +22 -0
- package/dist/runtime/lib/dataTableFilters.js +5 -0
- package/dist/runtime/lib/facets.d.ts +13 -0
- package/dist/runtime/lib/formLayout.d.ts +74 -4
- package/dist/runtime/lib/formLayout.js +53 -6
- package/dist/runtime/lib/gridTracks.d.ts +14 -1
- package/dist/runtime/lib/gridTracks.js +13 -2
- package/dist/runtime/lib/isoLists.d.ts +37 -2
- package/dist/runtime/lib/isoLists.js +54 -1
- package/dist/runtime/lib/notice.d.ts +14 -0
- package/dist/runtime/lib/notice.js +19 -0
- package/dist/runtime/lib/pageSize.d.ts +15 -0
- package/dist/runtime/lib/pageSize.js +26 -0
- package/dist/runtime/lib/sidebarAreas.d.ts +46 -0
- package/dist/runtime/lib/sidebarAreas.js +25 -0
- package/dist/runtime/lib/sidebarNav.d.ts +21 -2
- package/dist/runtime/lib/sidebarNav.js +23 -4
- package/dist/runtime/lib/solarIconMap.d.ts +2 -1
- package/dist/runtime/lib/solarIconMap.js +50 -4
- package/dist/runtime/lib/swipeToClose.d.ts +25 -0
- package/dist/runtime/lib/swipeToClose.js +118 -0
- package/dist/runtime/lib/tableExport.d.ts +31 -0
- package/dist/runtime/lib/tableExport.js +143 -0
- package/dist/runtime/lib/talkback.d.ts +5 -0
- package/dist/runtime/lib/talkback.js +8 -0
- package/dist/runtime/lib/talkbackTokenBatch.d.ts +35 -0
- package/dist/runtime/lib/talkbackTokenBatch.js +101 -0
- package/dist/runtime/types/nav.d.ts +9 -0
- package/dist/types.d.mts +4 -0
- package/package.json +4 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import {
|
|
3
|
+
AlignVerticalSpacing,
|
|
4
|
+
AltArrowRight,
|
|
3
5
|
Eye,
|
|
4
6
|
EyeClosed,
|
|
5
7
|
Filter,
|
|
@@ -19,16 +21,18 @@ import {
|
|
|
19
21
|
} from "@revenexx/studio";
|
|
20
22
|
import { orderColumnsBy } from "../../lib/columnOrder";
|
|
21
23
|
import { initiallyHidden, visibleColumnsOf } from "../../lib/columnVisibility";
|
|
22
|
-
import {
|
|
23
|
-
import { gridTemplateColumns } from "../../lib/gridTracks";
|
|
24
|
-
const RIGHT_ALIGNED = "text-right pr-4";
|
|
24
|
+
import { facetCounts, matchesFilter } from "../../lib/dataTableFilters";
|
|
25
|
+
import { gridTemplateColumns, headerFloor } from "../../lib/gridTracks";
|
|
25
26
|
import { dropEdge, dropTargetIndex, moveKey, stepTarget } from "../../lib/rowMove";
|
|
26
27
|
import SortHeader from "../Shared/SortHeader.vue";
|
|
27
28
|
import FacetChips from "./FacetChips.vue";
|
|
28
29
|
import FacetPanel from "./FacetPanel.vue";
|
|
29
30
|
import FilterMenu from "./FilterMenu.vue";
|
|
30
31
|
import SavedViewsMenu from "./SavedViewsMenu.vue";
|
|
32
|
+
import ToolbarLabelTrigger from "./ToolbarLabelTrigger.vue";
|
|
31
33
|
import SortMenu from "./SortMenu.vue";
|
|
34
|
+
import { DEFAULT_PAGE_SIZE, pageSizeOptions as pageSizeChoices, readPageSize, writePageSize } from "../../lib/pageSize";
|
|
35
|
+
const RIGHT_ALIGNED = "text-right pr-4";
|
|
32
36
|
const props = defineProps({
|
|
33
37
|
data: { type: Array, required: true },
|
|
34
38
|
columns: { type: Array, required: true },
|
|
@@ -43,6 +47,7 @@ const props = defineProps({
|
|
|
43
47
|
viewModes: { type: [Array, null], required: false, default: null },
|
|
44
48
|
rail: { type: [Boolean, Object], required: false, default: false },
|
|
45
49
|
scrollBody: { type: [Boolean, String, Object], required: false },
|
|
50
|
+
reserveBottom: { type: Number, required: false },
|
|
46
51
|
pagination: { type: [Boolean, Object], required: false, default: true },
|
|
47
52
|
defaultSort: { type: Object, required: false },
|
|
48
53
|
serverMode: { type: Boolean, required: false, default: false },
|
|
@@ -54,11 +59,17 @@ const props = defineProps({
|
|
|
54
59
|
activeRowKey: { type: [String, Number, null], required: false },
|
|
55
60
|
rowClass: { type: [Function, null], required: false, default: null },
|
|
56
61
|
reorderable: { type: Boolean, required: false, default: false },
|
|
57
|
-
reorderDisabled: { type: [String, null], required: false, default: null }
|
|
62
|
+
reorderDisabled: { type: [String, null], required: false, default: null },
|
|
63
|
+
mobileCards: { type: Boolean, required: false, default: true },
|
|
64
|
+
densityKey: { type: String, required: false, default: "cockpit.datatable.density" },
|
|
65
|
+
pageSizeKey: { type: [String, null], required: false, default: null },
|
|
66
|
+
embedded: { type: Boolean, required: false, default: false },
|
|
67
|
+
toolbar: { type: Boolean, required: false, default: true }
|
|
58
68
|
});
|
|
59
69
|
const emit = defineEmits(["rowClick", "stateChange", "orderChange", "rowMove"]);
|
|
70
|
+
defineSlots();
|
|
60
71
|
const t = useT();
|
|
61
|
-
const query =
|
|
72
|
+
const query = defineModel("query", { type: String, ...{ default: "" } });
|
|
62
73
|
const sort = defineModel("sort", { type: Object, ...{
|
|
63
74
|
default: () => ({
|
|
64
75
|
column: null,
|
|
@@ -68,19 +79,66 @@ const sort = defineModel("sort", { type: Object, ...{
|
|
|
68
79
|
const page = defineModel("page", { type: Number, ...{ default: 0 } });
|
|
69
80
|
const viewMode = defineModel("viewMode", { type: String, ...{ default: "list" } });
|
|
70
81
|
const railOpen = defineModel("railOpen", { type: Boolean, ...{ default: true } });
|
|
82
|
+
const density = defineModel("density", { type: String, ...{ default: "standard" } });
|
|
83
|
+
if (import.meta.client) {
|
|
84
|
+
try {
|
|
85
|
+
const stored = localStorage.getItem(props.densityKey);
|
|
86
|
+
if (stored === "compact" || stored === "standard") density.value = stored;
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function setDensity(next) {
|
|
91
|
+
density.value = next;
|
|
92
|
+
try {
|
|
93
|
+
localStorage.setItem(props.densityKey, next);
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const narrow = ref(false);
|
|
98
|
+
let narrowMql = null;
|
|
99
|
+
const onNarrow = (e) => {
|
|
100
|
+
narrow.value = e.matches;
|
|
101
|
+
};
|
|
102
|
+
onMounted(() => {
|
|
103
|
+
narrowMql = window.matchMedia("(max-width: 767px)");
|
|
104
|
+
narrow.value = narrowMql.matches;
|
|
105
|
+
narrowMql.addEventListener("change", onNarrow);
|
|
106
|
+
});
|
|
107
|
+
onBeforeUnmount(() => narrowMql?.removeEventListener("change", onNarrow));
|
|
108
|
+
const cardMode = computed(() => props.mobileCards && narrow.value);
|
|
109
|
+
const CONTROL_KEYS = /* @__PURE__ */ new Set(["__select", "__actions", "__move"]);
|
|
110
|
+
const STATUS_KEY = /(^|_)(status|state|phase|stage|lifecycle)(_|$)/i;
|
|
111
|
+
const cardLayout = computed(() => {
|
|
112
|
+
const cols = visibleColumns.value;
|
|
113
|
+
const data = cols.filter((c) => !CONTROL_KEYS.has(c.key) && c.card !== "hidden");
|
|
114
|
+
const title = data.find((c) => c.card === "title") ?? data.find((c) => c.primary) ?? data[0] ?? null;
|
|
115
|
+
const status = data.find((c) => c.card === "status") ?? data.find((c) => c !== title && c.card !== "meta" && STATUS_KEY.test(c.key)) ?? null;
|
|
116
|
+
const explicitMeta = data.filter((c) => c.card === "meta");
|
|
117
|
+
const meta = (explicitMeta.length ? explicitMeta : data.filter((c) => c !== title && c !== status)).slice(0, 4);
|
|
118
|
+
return {
|
|
119
|
+
select: cols.find((c) => c.key === "__select") ?? null,
|
|
120
|
+
actions: cols.find((c) => c.key === "__actions") ?? null,
|
|
121
|
+
title,
|
|
122
|
+
status,
|
|
123
|
+
meta
|
|
124
|
+
};
|
|
125
|
+
});
|
|
71
126
|
const facetSelection = defineModel("facetSelection", { type: Object, ...{ default: () => ({}) } });
|
|
72
127
|
const panelFacets = computed(() => props.facets);
|
|
73
128
|
if (props.defaultSort && sort.value.column == null) {
|
|
74
129
|
sort.value = { column: props.defaultSort.column, direction: props.defaultSort.direction };
|
|
75
130
|
}
|
|
131
|
+
const pageSizeTable = computed(() => props.pageSizeKey ?? props.savedViews?.storageKey ?? null);
|
|
76
132
|
const pageSize = ref(
|
|
77
|
-
typeof props.pagination === "object" && props.pagination?.pageSize ? props.pagination.pageSize :
|
|
133
|
+
readPageSize(pageSizeTable.value) ?? (typeof props.pagination === "object" && props.pagination?.pageSize ? props.pagination.pageSize : DEFAULT_PAGE_SIZE)
|
|
78
134
|
);
|
|
79
|
-
const pageSizeOptions = computed(() =>
|
|
135
|
+
const pageSizeOptions = computed(() => pageSizeChoices(pageSize.value));
|
|
136
|
+
watch(pageSize, (n) => writePageSize(pageSizeTable.value, n));
|
|
80
137
|
const hidden = ref(initiallyHidden(props.columns));
|
|
81
138
|
const columnOrder = ref([]);
|
|
82
139
|
const arrangedColumns = computed(() => orderColumnsBy(props.columns, columnOrder.value));
|
|
83
140
|
const visibleColumns = computed(() => visibleColumnsOf(arrangedColumns.value, hidden.value));
|
|
141
|
+
defineExpose({ visibleColumns });
|
|
84
142
|
const pickerColumns = computed(() => arrangedColumns.value.filter((c) => (c.label ?? "").trim().length > 0).map((c) => ({
|
|
85
143
|
key: c.key,
|
|
86
144
|
label: c.label ?? c.key,
|
|
@@ -127,22 +185,22 @@ const filterFacets = computed(() => filterDims.value.map((d) => {
|
|
|
127
185
|
};
|
|
128
186
|
}));
|
|
129
187
|
function dimCounts(d) {
|
|
130
|
-
return
|
|
188
|
+
return facetCounts(queried.value, filterDims.value, activeFilters.value, d);
|
|
131
189
|
}
|
|
132
|
-
const
|
|
133
|
-
if (props.serverMode || !filterDims.value.length) return props.data;
|
|
134
|
-
const active = filterDims.value.filter((d) => activeFilters.value[d.key]?.length);
|
|
135
|
-
if (!active.length) return props.data;
|
|
136
|
-
return props.data.filter((row) => active.every((d) => matchesFilter(d.getter, row, activeFilters.value[d.key])));
|
|
137
|
-
});
|
|
138
|
-
const searched = computed(() => {
|
|
190
|
+
const queried = computed(() => {
|
|
139
191
|
if (props.serverMode) return props.data;
|
|
140
192
|
const q = query.value.trim().toLowerCase();
|
|
141
|
-
if (!q) return
|
|
142
|
-
return
|
|
193
|
+
if (!q) return props.data;
|
|
194
|
+
return props.data.filter(
|
|
143
195
|
(row) => props.columns.some((col) => searchOf(col, row).includes(q))
|
|
144
196
|
);
|
|
145
197
|
});
|
|
198
|
+
const searched = computed(() => {
|
|
199
|
+
if (props.serverMode || !filterDims.value.length) return queried.value;
|
|
200
|
+
const active = filterDims.value.filter((d) => activeFilters.value[d.key]?.length);
|
|
201
|
+
if (!active.length) return queried.value;
|
|
202
|
+
return queried.value.filter((row) => active.every((d) => matchesFilter(d.getter, row, activeFilters.value[d.key])));
|
|
203
|
+
});
|
|
146
204
|
function sortKey(col, row) {
|
|
147
205
|
if (col.sortGetter) return col.sortGetter(row);
|
|
148
206
|
const raw = row[col.key];
|
|
@@ -262,7 +320,94 @@ function rowKeyFor(row) {
|
|
|
262
320
|
if (typeof props.rowKey === "function") return props.rowKey(row);
|
|
263
321
|
return row[props.rowKey];
|
|
264
322
|
}
|
|
265
|
-
const gridTemplate = computed(() => `grid-template-columns: ${gridTemplateColumns(
|
|
323
|
+
const gridTemplate = computed(() => `grid-template-columns: ${gridTemplateColumns(
|
|
324
|
+
renderColumns.value.map((c) => c.width),
|
|
325
|
+
void 0,
|
|
326
|
+
// A flexible column keeps room for its one-line heading and the (?)/sort icons beside it.
|
|
327
|
+
renderColumns.value.map((c) => c.primary ? c.floor ?? PRIMARY_FLOOR : c.floor ?? headerFloor(c.label, { icons: (c.sortable ? 1 : 0) + 1, maxChars: HEADER_NOWRAP_MAX }))
|
|
328
|
+
)};`);
|
|
329
|
+
const PRIMARY_FLOOR = "260px";
|
|
330
|
+
const pinnedCount = computed(() => {
|
|
331
|
+
if (cardMode.value) return 0;
|
|
332
|
+
const i = renderColumns.value.findIndex((c) => c.primary);
|
|
333
|
+
return i < 0 ? 0 : i + 1;
|
|
334
|
+
});
|
|
335
|
+
const pinnedOffsets = ref([]);
|
|
336
|
+
const scrolledX = ref(false);
|
|
337
|
+
const moreRight = ref(false);
|
|
338
|
+
function measurePinned() {
|
|
339
|
+
const row = headerRowEl.value;
|
|
340
|
+
if (!row || !pinnedCount.value) {
|
|
341
|
+
pinnedOffsets.value = [];
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const cells = Array.from(row.querySelectorAll(':scope > [role="columnheader"]'));
|
|
345
|
+
const rowLeft = row.getBoundingClientRect().left;
|
|
346
|
+
const style = getComputedStyle(row);
|
|
347
|
+
const pad = Number.parseFloat(style.paddingLeft) || 0;
|
|
348
|
+
const gap = Number.parseFloat(style.columnGap) || 0;
|
|
349
|
+
pinnedOffsets.value = cells.slice(0, pinnedCount.value).map((cell, i) => {
|
|
350
|
+
const left = cell.getBoundingClientRect().left - rowLeft + (Number.parseFloat(cell.style.marginLeft) ? -Number.parseFloat(cell.style.marginLeft) : 0);
|
|
351
|
+
const gutter = i === 0 ? pad : gap;
|
|
352
|
+
return { left: left - gutter, gutter };
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function pinnedStyle(index) {
|
|
356
|
+
const o = pinnedOffsets.value[index];
|
|
357
|
+
if (!o || index >= pinnedCount.value) return void 0;
|
|
358
|
+
return {
|
|
359
|
+
position: "sticky",
|
|
360
|
+
left: `${o.left}px`,
|
|
361
|
+
marginLeft: `-${o.gutter}px`,
|
|
362
|
+
paddingLeft: `${o.gutter}px`,
|
|
363
|
+
backgroundColor: "inherit",
|
|
364
|
+
zIndex: "1"
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const HEADER_NOWRAP_MAX = 18;
|
|
368
|
+
function headerNoWrap(col) {
|
|
369
|
+
return (col.label ?? "").length <= HEADER_NOWRAP_MAX ? "whitespace-nowrap" : "";
|
|
370
|
+
}
|
|
371
|
+
function rightPinStyle(col) {
|
|
372
|
+
if (!col.pinRight || cardMode.value) return void 0;
|
|
373
|
+
return { position: "sticky", right: "0px", backgroundColor: "inherit", zIndex: "1" };
|
|
374
|
+
}
|
|
375
|
+
function rightPinClass(col) {
|
|
376
|
+
return col.pinRight && moreRight.value ? "shadow-[-6px_0_8px_-6px_rgba(11,5,34,0.18)]" : "";
|
|
377
|
+
}
|
|
378
|
+
function pinnedClass(index) {
|
|
379
|
+
return index === pinnedCount.value - 1 && scrolledX.value ? "shadow-[6px_0_8px_-6px_rgba(11,5,34,0.18)]" : "";
|
|
380
|
+
}
|
|
381
|
+
function syncScrollX() {
|
|
382
|
+
const el = scrollEl.value;
|
|
383
|
+
if (!el) return;
|
|
384
|
+
scrolledX.value = el.scrollLeft > 0;
|
|
385
|
+
moreRight.value = el.scrollWidth - el.clientWidth - el.scrollLeft > 2;
|
|
386
|
+
}
|
|
387
|
+
function scrollRight() {
|
|
388
|
+
scrollEl.value?.scrollBy({ left: Math.max(240, (scrollEl.value?.clientWidth ?? 0) * 0.6), behavior: "smooth" });
|
|
389
|
+
}
|
|
390
|
+
let pinObserver = null;
|
|
391
|
+
onMounted(() => {
|
|
392
|
+
pinObserver = new ResizeObserver(() => {
|
|
393
|
+
measurePinned();
|
|
394
|
+
syncScrollX();
|
|
395
|
+
});
|
|
396
|
+
if (scrollEl.value) pinObserver.observe(scrollEl.value);
|
|
397
|
+
if (headerRowEl.value) pinObserver.observe(headerRowEl.value);
|
|
398
|
+
nextTick(() => {
|
|
399
|
+
measurePinned();
|
|
400
|
+
syncScrollX();
|
|
401
|
+
});
|
|
402
|
+
watch([() => renderColumns.value.map((c) => `${c.key}:${c.width ?? ""}`).join("|"), () => props.data, cardMode, density], () => {
|
|
403
|
+
nextTick(() => {
|
|
404
|
+
if (headerRowEl.value && pinObserver) pinObserver.observe(headerRowEl.value);
|
|
405
|
+
measurePinned();
|
|
406
|
+
syncScrollX();
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
onBeforeUnmount(() => pinObserver?.disconnect());
|
|
266
411
|
const SUBGRID_ROW = "grid-column: 1 / -1; grid-template-columns: subgrid;";
|
|
267
412
|
const placeholder = computed(
|
|
268
413
|
() => typeof props.search === "object" ? props.search.placeholder ?? t("shared.datatable.toolbar.search.placeholder.default") : t("shared.datatable.toolbar.search.placeholder.default")
|
|
@@ -273,7 +418,7 @@ const searchLabel = computed(() => {
|
|
|
273
418
|
});
|
|
274
419
|
const slots = useSlots();
|
|
275
420
|
const showToolbar = computed(
|
|
276
|
-
() => props.search || props.columnsMenu || props.filter || !!props.filterTabs || !!props.savedViews || !!props.viewModes?.length || !!props.rail || !!slots.actions || !!slots.scope || !!slots["toolbar-left"] || !!slots["toolbar-right"]
|
|
421
|
+
() => props.toolbar && (props.search || props.columnsMenu || props.filter || !!props.filterTabs || !!props.savedViews || !!props.viewModes?.length || !!props.rail || !!slots.actions || !!slots.scope || !!slots["toolbar-left"] || !!slots["toolbar-right"])
|
|
277
422
|
);
|
|
278
423
|
const railLabel = computed(
|
|
279
424
|
() => (typeof props.rail === "object" ? props.rail.label : void 0) ?? null
|
|
@@ -304,7 +449,7 @@ function measureFit() {
|
|
|
304
449
|
if (!el) return;
|
|
305
450
|
const container = scrollParent(el);
|
|
306
451
|
const offset = el.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
|
307
|
-
const reserve = (footerEl.value?.offsetHeight ?? 0) + BOTTOM_GAP_PX;
|
|
452
|
+
const reserve = (footerEl.value?.offsetHeight ?? 0) + BOTTOM_GAP_PX + (props.reserveBottom ?? 0);
|
|
308
453
|
const overflow = Math.max(container.scrollHeight - container.clientHeight, 0);
|
|
309
454
|
const available = Math.max(
|
|
310
455
|
Math.floor(container.clientHeight - offset - reserve - overflow) - 1,
|
|
@@ -339,6 +484,7 @@ if (import.meta.client) {
|
|
|
339
484
|
schedule();
|
|
340
485
|
syncThumb();
|
|
341
486
|
}));
|
|
487
|
+
watch(() => props.reserveBottom, () => nextTick(schedule));
|
|
342
488
|
onMounted(() => {
|
|
343
489
|
nextTick(syncThumb);
|
|
344
490
|
window.addEventListener("resize", syncThumb);
|
|
@@ -368,6 +514,7 @@ function syncThumb() {
|
|
|
368
514
|
}
|
|
369
515
|
function onRowsScroll() {
|
|
370
516
|
syncThumb();
|
|
517
|
+
syncScrollX();
|
|
371
518
|
}
|
|
372
519
|
function startThumbDrag(event) {
|
|
373
520
|
const el = scrollEl.value;
|
|
@@ -402,7 +549,7 @@ const scrollStyle = computed(() => {
|
|
|
402
549
|
const configured = typeof props.scrollBody === "object" ? props.scrollBody.maxHeight : void 0;
|
|
403
550
|
return { maxHeight: configured ?? "calc(100vh - 22rem)" };
|
|
404
551
|
});
|
|
405
|
-
const showFooter = computed(() => props.pagination !== false && !props.groupBy);
|
|
552
|
+
const showFooter = computed(() => props.pagination !== false && !props.groupBy && !(totalCount.value === 0 && !props.loading));
|
|
406
553
|
const lastPage = computed(
|
|
407
554
|
() => pageSize.value > 0 ? Math.max(0, Math.ceil(totalCount.value / pageSize.value) - 1) : 0
|
|
408
555
|
);
|
|
@@ -495,7 +642,7 @@ function dropClass(row) {
|
|
|
495
642
|
return at.edge === "before" ? "shadow-[inset_0_2px_0_0_var(--color-primary)]" : "shadow-[inset_0_-2px_0_0_var(--color-primary)]";
|
|
496
643
|
}
|
|
497
644
|
function rowGround(row) {
|
|
498
|
-
if (props.activeRowKey != null && rowKeyFor(row) === props.activeRowKey) return "bg-
|
|
645
|
+
if (props.activeRowKey != null && rowKeyFor(row) === props.activeRowKey) return "bg-[#f5f1fe]";
|
|
499
646
|
return props.rowClass?.(row) || "bg-gray-50";
|
|
500
647
|
}
|
|
501
648
|
function defaultCell(col, row) {
|
|
@@ -506,7 +653,7 @@ function defaultCell(col, row) {
|
|
|
506
653
|
</script>
|
|
507
654
|
|
|
508
655
|
<template>
|
|
509
|
-
<div class="rounded-lg border border-black/[0.06]
|
|
656
|
+
<div class="overflow-hidden flex flex-col" :class="embedded ? 'bg-white' : 'rounded-lg border border-black/[0.06]'">
|
|
510
657
|
<!-- Toolbar ------------------------------------------------------ -->
|
|
511
658
|
<!-- The single control row of a list surface.
|
|
512
659
|
|
|
@@ -543,9 +690,11 @@ function defaultCell(col, row) {
|
|
|
543
690
|
in it. -->
|
|
544
691
|
<div
|
|
545
692
|
v-if="showToolbar"
|
|
546
|
-
class="flex flex-wrap items-center justify-between gap-2 px-4 py-3
|
|
693
|
+
class="flex flex-wrap items-center justify-between gap-2 px-4 py-3 xl:flex-nowrap"
|
|
694
|
+
:class="embedded ? 'bg-white' : 'bg-gradient-to-r from-violet-50/80 via-white to-violet-50/60'"
|
|
547
695
|
>
|
|
548
|
-
|
|
696
|
+
<!-- One line on a wide screen (UI-F10): the search gives way first. -->
|
|
697
|
+
<div class="flex min-w-0 flex-wrap items-center gap-2 max-sm:w-full xl:flex-1 xl:flex-nowrap">
|
|
549
698
|
<Button
|
|
550
699
|
v-if="rail"
|
|
551
700
|
variant="outline"
|
|
@@ -573,7 +722,7 @@ function defaultCell(col, row) {
|
|
|
573
722
|
v-model="query"
|
|
574
723
|
:placeholder="placeholder"
|
|
575
724
|
:label="searchLabel"
|
|
576
|
-
class="w-
|
|
725
|
+
class="w-full min-w-40 max-w-full shrink sm:w-[21rem] xl:w-auto xl:flex-1 xl:max-w-[21rem]"
|
|
577
726
|
>
|
|
578
727
|
<template #icon>
|
|
579
728
|
<Search :size="14" weight="Linear" />
|
|
@@ -605,8 +754,20 @@ function defaultCell(col, row) {
|
|
|
605
754
|
:reset-label="t('shared.datatable.toolbar.filter.reset')"
|
|
606
755
|
@update:model-value="applyPanelSelection"
|
|
607
756
|
>
|
|
608
|
-
|
|
609
|
-
|
|
757
|
+
<!-- D7 (Charge 2): tool buttons carry their word on desktop, the
|
|
758
|
+
glyph alone on phones. studio's trigger is icon-only by design
|
|
759
|
+
(ToolbarFilterTrigger icon-only), so the dummy draws its own
|
|
760
|
+
trigger through the panel's `trigger` slot. -->
|
|
761
|
+
<template #trigger="{ count }">
|
|
762
|
+
<ToolbarLabelTrigger
|
|
763
|
+
:active="count > 0"
|
|
764
|
+
:count="count"
|
|
765
|
+
:label="t('shared.datatable.toolbar.filter.action')"
|
|
766
|
+
>
|
|
767
|
+
<template #icon>
|
|
768
|
+
<Filter :size="14" weight="BoldDuotone" />
|
|
769
|
+
</template>
|
|
770
|
+
</ToolbarLabelTrigger>
|
|
610
771
|
</template>
|
|
611
772
|
</FilterPanel>
|
|
612
773
|
<!--
|
|
@@ -621,8 +782,14 @@ function defaultCell(col, row) {
|
|
|
621
782
|
v-model="facetSelection"
|
|
622
783
|
:facets="facets"
|
|
623
784
|
/>
|
|
785
|
+
<!--
|
|
786
|
+
No `#filter-menu`, no trigger: `filter` alone used to draw a Filter
|
|
787
|
+
button that opened on "No filters defined" — a control promising to
|
|
788
|
+
narrow the list and refusing to (PO-313). The other two routes draw
|
|
789
|
+
only when the surface declared facets or dimensions.
|
|
790
|
+
-->
|
|
624
791
|
<FilterMenu
|
|
625
|
-
v-if="filter && !panelFacets.length"
|
|
792
|
+
v-if="filter && slots['filter-menu'] && !panelFacets.length"
|
|
626
793
|
:width="typeof filter === 'object' ? filter.width : void 0"
|
|
627
794
|
:active="typeof filter === 'object' ? filter.active : void 0"
|
|
628
795
|
:count="typeof filter === 'object' ? filter.count : void 0"
|
|
@@ -644,10 +811,16 @@ function defaultCell(col, row) {
|
|
|
644
811
|
:model-value="pickerColumns"
|
|
645
812
|
:label="t('shared.datatable.columnsmenu.tooltip')"
|
|
646
813
|
:heading="t('shared.datatable.columnsmenu.heading')"
|
|
814
|
+
class="w-8 gap-1.5 px-0 [&_span.bg-primary]:hidden"
|
|
647
815
|
@update:model-value="onPickerUpdate"
|
|
648
816
|
>
|
|
817
|
+
<!-- No count on the trigger (Michael: no count badges, PE-1082): the
|
|
818
|
+
studio picker draws one; hidden here until it has a prop.
|
|
819
|
+
D7: the word rides in the icon slot — studio's picker has no
|
|
820
|
+
label slot yet (see the studio change list in the ledger). -->
|
|
649
821
|
<template #icon>
|
|
650
822
|
<Columns :size="15" weight="BoldDuotone" />
|
|
823
|
+
<span class="sr-only">{{ t("shared.datatable.columnsmenu.tooltip") }}</span>
|
|
651
824
|
</template>
|
|
652
825
|
<template #handle>
|
|
653
826
|
<ListArrowDownMinimalistic :size="15" weight="Linear" />
|
|
@@ -659,6 +832,33 @@ function defaultCell(col, row) {
|
|
|
659
832
|
<EyeClosed :size="16" weight="Linear" />
|
|
660
833
|
</template>
|
|
661
834
|
</ColumnPicker>
|
|
835
|
+
<!-- Row height (D4, Charge 2): standard 44px, compact 36px. -->
|
|
836
|
+
<SegmentedToggle
|
|
837
|
+
v-if="showTable && !cardMode"
|
|
838
|
+
:model-value="density"
|
|
839
|
+
variant="inset"
|
|
840
|
+
:aria-label="t('shared.datatable.toolbar.density.label')"
|
|
841
|
+
@update:model-value="(v) => v && setDensity(v)"
|
|
842
|
+
>
|
|
843
|
+
<SegmentedToggleItem
|
|
844
|
+
value="standard"
|
|
845
|
+
:aria-label="t('shared.datatable.toolbar.density.standard')"
|
|
846
|
+
:title="t('shared.datatable.toolbar.density.standard')"
|
|
847
|
+
class="transition-colors data-[state=on]:text-primary data-[state=off]:hover:text-primary!"
|
|
848
|
+
>
|
|
849
|
+
<AlignVerticalSpacing :size="14" weight="Linear" />
|
|
850
|
+
<span class="sr-only">{{ t("shared.datatable.toolbar.density.standard") }}</span>
|
|
851
|
+
</SegmentedToggleItem>
|
|
852
|
+
<SegmentedToggleItem
|
|
853
|
+
value="compact"
|
|
854
|
+
:aria-label="t('shared.datatable.toolbar.density.compact')"
|
|
855
|
+
:title="t('shared.datatable.toolbar.density.compact')"
|
|
856
|
+
class="transition-colors data-[state=on]:text-primary data-[state=off]:hover:text-primary!"
|
|
857
|
+
>
|
|
858
|
+
<DragHandle :size="14" weight="Linear" />
|
|
859
|
+
<span class="sr-only">{{ t("shared.datatable.toolbar.density.compact") }}</span>
|
|
860
|
+
</SegmentedToggleItem>
|
|
861
|
+
</SegmentedToggle>
|
|
662
862
|
<!--
|
|
663
863
|
Saved views, beside the columns picker rather than out past the market
|
|
664
864
|
scope and the view-mode switch.
|
|
@@ -760,31 +960,39 @@ function defaultCell(col, row) {
|
|
|
760
960
|
that. A static table read with a screen reader's own table commands is
|
|
761
961
|
what these lists actually are, and both roles announce the column
|
|
762
962
|
heading with the cell, which is the thing that was missing. -->
|
|
763
|
-
<div :class="scrollBody ? 'relative flex min-h-0 flex-col' : ''">
|
|
963
|
+
<div :class="scrollBody && !cardMode ? 'relative flex min-h-0 flex-col' : 'relative'">
|
|
764
964
|
<div
|
|
765
965
|
ref="scrollEl"
|
|
766
|
-
:class="scrollBody ? 'rx-rows min-h-0 overflow-auto' : 'overflow-x-auto'"
|
|
767
|
-
:style="scrollStyle"
|
|
966
|
+
:class="scrollBody && !cardMode ? 'rx-rows min-h-0 overflow-auto' : 'overflow-x-auto'"
|
|
967
|
+
:style="cardMode ? void 0 : scrollStyle"
|
|
768
968
|
@scroll="onRowsScroll"
|
|
769
969
|
>
|
|
770
970
|
<div
|
|
771
971
|
:role="showTable ? 'table' : void 0"
|
|
772
972
|
:aria-busy="showTable && loading ? 'true' : void 0"
|
|
773
|
-
:class="showTable ? 'grid min-w-min gap-x-2' : ''"
|
|
774
|
-
:style="showTable ? gridTemplate : void 0"
|
|
973
|
+
:class="showTable && !cardMode ? 'grid min-w-min gap-x-2' : ''"
|
|
974
|
+
:style="showTable && !cardMode ? gridTemplate : void 0"
|
|
775
975
|
>
|
|
776
976
|
<!-- Header row -------------------------------------------------- -->
|
|
777
977
|
<div
|
|
778
|
-
v-if="showTable"
|
|
978
|
+
v-if="showTable && !cardMode"
|
|
779
979
|
ref="headerRowEl"
|
|
780
980
|
role="row"
|
|
781
|
-
|
|
782
|
-
|
|
981
|
+
data-rx-head
|
|
982
|
+
class="grid items-center gap-x-2 border-gray-200 px-4 py-2.5 text-sm font-medium uppercase tracking-normal text-muted-foreground"
|
|
983
|
+
:class="[
|
|
984
|
+
scrollBody ? 'sticky top-0 z-10' : '',
|
|
985
|
+
embedded ? 'bg-gray-100' : 'rounded-t-lg bg-gray-100',
|
|
986
|
+
// Embedded with the search row closed, the card head\'s own bottom
|
|
987
|
+
// edge is already the line above the headings; a second one here drew
|
|
988
|
+
// a double rule (Michael 14.10.).
|
|
989
|
+
embedded && !showToolbar ? '' : 'border-t'
|
|
990
|
+
]"
|
|
783
991
|
:style="SUBGRID_ROW"
|
|
784
992
|
>
|
|
785
993
|
<!-- The handle column's heading: nothing to see, but a columnheader the
|
|
786
994
|
handles' cells can be named by. -->
|
|
787
|
-
<div v-if="reorderable" role="columnheader" class="w-6">
|
|
995
|
+
<div v-if="reorderable" role="columnheader" class="w-6" :style="pinnedStyle(0)">
|
|
788
996
|
<span class="sr-only">{{ t("shared.datatable.reorder.column") }}</span>
|
|
789
997
|
</div>
|
|
790
998
|
<!-- `columnheader` is what names every cell underneath it, so this is
|
|
@@ -793,13 +1001,14 @@ function defaultCell(col, row) {
|
|
|
793
1001
|
current direction is a control whose state is visible only to
|
|
794
1002
|
people who can see the arrow. -->
|
|
795
1003
|
<div
|
|
796
|
-
v-for="col in visibleColumns"
|
|
1004
|
+
v-for="(col, ci) in visibleColumns"
|
|
797
1005
|
:key="col.key"
|
|
798
1006
|
role="columnheader"
|
|
799
1007
|
:aria-sort="ariaSortFor(col)"
|
|
800
1008
|
:title="col.label || void 0"
|
|
801
1009
|
class="min-w-0"
|
|
802
|
-
:class="col.alignRight ? RIGHT_ALIGNED : ''"
|
|
1010
|
+
:class="[col.alignRight ? RIGHT_ALIGNED : '', pinnedClass(ci + (reorderable ? 1 : 0)), rightPinClass(col)]"
|
|
1011
|
+
:style="rightPinStyle(col) ?? pinnedStyle(ci + (reorderable ? 1 : 0))"
|
|
803
1012
|
>
|
|
804
1013
|
<!-- `#header-<key>` lets a page take over a header cell (e.g. a
|
|
805
1014
|
select-all checkbox in a selection column). Default: sortable
|
|
@@ -813,19 +1022,74 @@ function defaultCell(col, row) {
|
|
|
813
1022
|
:align-right="col.alignRight"
|
|
814
1023
|
@toggle="toggleSort"
|
|
815
1024
|
>
|
|
816
|
-
{{ col.label }}
|
|
1025
|
+
<span :class="headerNoWrap(col)">{{ col.label }}</span>
|
|
817
1026
|
</SortHeader>
|
|
818
1027
|
<!-- `normal-case` to match SortHeader, whose button does not inherit
|
|
819
1028
|
the row's uppercase. Without it a column reads "LAST CHECK"
|
|
820
1029
|
beside "Channel" and "Provider" for no reason a reader can see —
|
|
821
1030
|
the header shouts wherever a column happens not to be sortable. -->
|
|
822
|
-
<span v-else class="block
|
|
1031
|
+
<span v-else class="block break-normal leading-tight normal-case" :class="headerNoWrap(col)">{{ col.label }}</span>
|
|
823
1032
|
</slot>
|
|
824
1033
|
</div>
|
|
825
1034
|
</div>
|
|
826
1035
|
|
|
827
1036
|
<!-- Body --------------------------------------------------------- -->
|
|
828
|
-
|
|
1037
|
+
<!-- Phone (R4, Charge 2): one card per row. Title and status on the first
|
|
1038
|
+
line, then up to four key facts in two lines, in column priority. The
|
|
1039
|
+
same cell slots render inside, so a card shows exactly what the row
|
|
1040
|
+
would. -->
|
|
1041
|
+
<template v-if="showTable && cardMode">
|
|
1042
|
+
<div
|
|
1043
|
+
v-for="entry in renderRows"
|
|
1044
|
+
:key="entry.key"
|
|
1045
|
+
:role="entry.kind === 'header' ? void 0 : 'button'"
|
|
1046
|
+
:data-row="entry.kind === 'header' ? void 0 : ''"
|
|
1047
|
+
:tabindex="entry.kind === 'header' ? void 0 : 0"
|
|
1048
|
+
class="border-t border-black/[0.04]"
|
|
1049
|
+
:class="entry.kind === 'header' ? 'bg-gray-100/70 px-4 py-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground' : ['group cursor-pointer px-4 py-3', rowGround(entry.row)]"
|
|
1050
|
+
@click="entry.kind !== 'header' && emit('rowClick', entry.row)"
|
|
1051
|
+
@keydown.enter="entry.kind !== 'header' && emit('rowClick', entry.row)"
|
|
1052
|
+
>
|
|
1053
|
+
<template v-if="entry.kind === 'header'">
|
|
1054
|
+
{{ entry.label }} <span class="tabular-nums normal-case text-muted-foreground/70">{{ entry.count }}</span>
|
|
1055
|
+
</template>
|
|
1056
|
+
<template v-else>
|
|
1057
|
+
<div class="flex items-start gap-3">
|
|
1058
|
+
<div v-if="cardLayout.select" class="pt-0.5" @click.stop>
|
|
1059
|
+
<slot :name="`cell-${cardLayout.select.key}`" :row="entry.row" />
|
|
1060
|
+
</div>
|
|
1061
|
+
<div class="min-w-0 flex-1">
|
|
1062
|
+
<div class="flex items-start justify-between gap-2">
|
|
1063
|
+
<div v-if="cardLayout.title" class="min-w-0 flex-1 text-sm font-semibold text-[#0B0522] [&_*]:max-w-full">
|
|
1064
|
+
<slot :name="`cell-${cardLayout.title.key}`" :row="entry.row">
|
|
1065
|
+
<span class="block truncate">{{ defaultCell(cardLayout.title, entry.row) }}</span>
|
|
1066
|
+
</slot>
|
|
1067
|
+
</div>
|
|
1068
|
+
<div v-if="cardLayout.status" class="shrink-0">
|
|
1069
|
+
<slot :name="`cell-${cardLayout.status.key}`" :row="entry.row">
|
|
1070
|
+
<span class="text-sm">{{ defaultCell(cardLayout.status, entry.row) }}</span>
|
|
1071
|
+
</slot>
|
|
1072
|
+
</div>
|
|
1073
|
+
<div v-if="cardLayout.actions" class="-mr-1 shrink-0" @click.stop>
|
|
1074
|
+
<slot :name="`cell-${cardLayout.actions.key}`" :row="entry.row" />
|
|
1075
|
+
</div>
|
|
1076
|
+
</div>
|
|
1077
|
+
<dl v-if="cardLayout.meta.length" class="mt-1.5 grid grid-cols-2 gap-x-3 gap-y-1">
|
|
1078
|
+
<div v-for="m in cardLayout.meta" :key="m.key" class="min-w-0">
|
|
1079
|
+
<dt class="truncate text-xs text-muted-foreground">{{ m.label }}</dt>
|
|
1080
|
+
<dd class="min-w-0 truncate text-sm">
|
|
1081
|
+
<slot :name="`cell-${m.key}`" :row="entry.row">
|
|
1082
|
+
<span class="block truncate">{{ defaultCell(m, entry.row) }}</span>
|
|
1083
|
+
</slot>
|
|
1084
|
+
</dd>
|
|
1085
|
+
</div>
|
|
1086
|
+
</dl>
|
|
1087
|
+
</div>
|
|
1088
|
+
</div>
|
|
1089
|
+
</template>
|
|
1090
|
+
</div>
|
|
1091
|
+
</template>
|
|
1092
|
+
<template v-for="entry in showTable && !cardMode ? renderRows : []" :key="entry.key">
|
|
829
1093
|
<!-- Group header (groupBy mode) — a row of one cell that spans the lot.
|
|
830
1094
|
The flex moves one element in, onto the cell: a `row` may hold
|
|
831
1095
|
cells and the label and the count are neither. -->
|
|
@@ -838,15 +1102,16 @@ function defaultCell(col, row) {
|
|
|
838
1102
|
scroll does not show this band stopping short of the rows it
|
|
839
1103
|
groups. No subgrid: it holds one cell, not a set of columns. -->
|
|
840
1104
|
<div role="cell" :aria-colspan="renderColumns.length" class="flex items-center gap-2">
|
|
841
|
-
{{ entry.label }}
|
|
1105
|
+
<slot name="group-header" :label="entry.label">{{ entry.label }}</slot>
|
|
842
1106
|
<span class="tabular-nums normal-case text-muted-foreground/70">{{ entry.count }}</span>
|
|
843
1107
|
</div>
|
|
844
1108
|
</div>
|
|
845
1109
|
<div
|
|
846
1110
|
v-else
|
|
847
1111
|
role="row"
|
|
848
|
-
|
|
849
|
-
|
|
1112
|
+
data-row
|
|
1113
|
+
class="group grid items-center gap-x-2 border-t border-black/[0.04] px-4 cursor-pointer transition-colors"
|
|
1114
|
+
:class="[density === 'compact' ? 'min-h-9 py-1' : 'min-h-11 py-2', rowGround(entry.row), dropClass(entry.row), dragKey != null && dragKey === rowKeyFor(entry.row) ? 'opacity-50' : '']"
|
|
850
1115
|
:aria-current="activeRowKey != null && rowKeyFor(entry.row) === activeRowKey ? 'true' : void 0"
|
|
851
1116
|
:style="SUBGRID_ROW"
|
|
852
1117
|
@click="emit('rowClick', entry.row)"
|
|
@@ -856,7 +1121,7 @@ function defaultCell(col, row) {
|
|
|
856
1121
|
<!-- The grip. A button, so it is reachable and the arrow keys can move
|
|
857
1122
|
the row; `draggable` on it rather than on the row, so selecting a
|
|
858
1123
|
cell's text or clicking an inline input never starts a drag. -->
|
|
859
|
-
<div v-if="reorderable" role="cell" class="flex items-center" @click.stop>
|
|
1124
|
+
<div v-if="reorderable" role="cell" class="flex items-center" :style="pinnedStyle(0)" @click.stop>
|
|
860
1125
|
<button
|
|
861
1126
|
type="button"
|
|
862
1127
|
:data-move-key="String(rowKeyFor(entry.row))"
|
|
@@ -874,11 +1139,12 @@ function defaultCell(col, row) {
|
|
|
874
1139
|
</button>
|
|
875
1140
|
</div>
|
|
876
1141
|
<div
|
|
877
|
-
v-for="col in visibleColumns"
|
|
1142
|
+
v-for="(col, ci) in visibleColumns"
|
|
878
1143
|
:key="col.key"
|
|
879
1144
|
role="cell"
|
|
880
1145
|
class="min-w-0"
|
|
881
|
-
:class="col.alignRight ? RIGHT_ALIGNED : ''"
|
|
1146
|
+
:class="[col.alignRight ? RIGHT_ALIGNED : '', pinnedClass(ci + (reorderable ? 1 : 0)), rightPinClass(col)]"
|
|
1147
|
+
:style="rightPinStyle(col) ?? pinnedStyle(ci + (reorderable ? 1 : 0))"
|
|
882
1148
|
>
|
|
883
1149
|
<slot :name="`cell-${col.key}`" :row="entry.row">
|
|
884
1150
|
<!-- Primary column: bold record anchor + optional tinted icon tile. -->
|
|
@@ -905,7 +1171,7 @@ function defaultCell(col, row) {
|
|
|
905
1171
|
information to read out, and announcing them as six empty rows would
|
|
906
1172
|
be worse than silence; the table says it is `aria-busy` instead,
|
|
907
1173
|
which is the same news in one word. -->
|
|
908
|
-
<template v-if="showTable && loading && !displayedRows.length">
|
|
1174
|
+
<template v-if="showTable && !cardMode && loading && !displayedRows.length">
|
|
909
1175
|
<div
|
|
910
1176
|
v-for="n in 6"
|
|
911
1177
|
:key="`sk-${n}`"
|
|
@@ -942,6 +1208,24 @@ function defaultCell(col, row) {
|
|
|
942
1208
|
</div>
|
|
943
1209
|
</div>
|
|
944
1210
|
|
|
1211
|
+
<!-- More columns to the right (D5, Charge 2): a soft edge over the rows
|
|
1212
|
+
and a button that scrolls on, so a wide table says that it is wide
|
|
1213
|
+
instead of ending in a clipped "Stat…". -->
|
|
1214
|
+
<div
|
|
1215
|
+
v-if="showTable && !cardMode && moreRight"
|
|
1216
|
+
class="pointer-events-none absolute inset-y-0 right-0 z-[15] w-10 bg-gradient-to-l from-[rgb(11_5_34_/_0.07)] to-transparent"
|
|
1217
|
+
/>
|
|
1218
|
+
<!-- Never over the rows (UI-F02, EC-F30): a table without a footer gets
|
|
1219
|
+
a slim line under it instead of a pill covering the last cells. -->
|
|
1220
|
+
<div
|
|
1221
|
+
v-if="showTable && !cardMode && moreRight && !showFooter"
|
|
1222
|
+
class="relative z-20 flex justify-end border-t border-black/[0.06] bg-white/70 px-2 py-1"
|
|
1223
|
+
>
|
|
1224
|
+
<Button variant="ghost" size="sm" class="h-7 gap-1 text-xs" @click="scrollRight">
|
|
1225
|
+
{{ t("shared.datatable.scroll.more") }}
|
|
1226
|
+
<AltArrowRight :size="12" weight="Bold" />
|
|
1227
|
+
</Button>
|
|
1228
|
+
</div>
|
|
945
1229
|
<!-- The rows' scrollbar ---------------------------------------- -->
|
|
946
1230
|
<!-- Drawn, not borrowed. A browser's own bar belongs to the scrolling
|
|
947
1231
|
box, and the column head is inside that box — it has to be, because
|
|
@@ -991,11 +1275,23 @@ function defaultCell(col, row) {
|
|
|
991
1275
|
</select>
|
|
992
1276
|
</label>
|
|
993
1277
|
</div>
|
|
1278
|
+
<div class="ml-auto flex items-center gap-2">
|
|
1279
|
+
<Button
|
|
1280
|
+
v-if="showTable && !cardMode && moreRight"
|
|
1281
|
+
variant="ghost"
|
|
1282
|
+
size="sm"
|
|
1283
|
+
class="gap-1"
|
|
1284
|
+
@click="scrollRight"
|
|
1285
|
+
>
|
|
1286
|
+
{{ t("shared.datatable.scroll.more") }}
|
|
1287
|
+
<AltArrowRight :size="12" weight="Bold" />
|
|
1288
|
+
</Button>
|
|
1289
|
+
</div>
|
|
994
1290
|
<div v-if="lastPage > 0" class="flex items-center gap-2">
|
|
995
1291
|
<Button
|
|
996
1292
|
variant="outline"
|
|
997
1293
|
size="sm"
|
|
998
|
-
class="
|
|
1294
|
+
class="text-sm"
|
|
999
1295
|
:disabled="page === 0 || loading"
|
|
1000
1296
|
@click="prevPage"
|
|
1001
1297
|
>
|
|
@@ -1007,7 +1303,7 @@ function defaultCell(col, row) {
|
|
|
1007
1303
|
<Button
|
|
1008
1304
|
variant="outline"
|
|
1009
1305
|
size="sm"
|
|
1010
|
-
class="
|
|
1306
|
+
class="text-sm"
|
|
1011
1307
|
:disabled="page >= lastPage || loading"
|
|
1012
1308
|
@click="nextPage"
|
|
1013
1309
|
>
|
|
@@ -1021,3 +1317,7 @@ function defaultCell(col, row) {
|
|
|
1021
1317
|
<style scoped>
|
|
1022
1318
|
.rx-rows::-webkit-scrollbar{height:10px;width:0}.rx-rows::-webkit-scrollbar-track{background:transparent}.rx-rows::-webkit-scrollbar-thumb{background:rgba(11,5,34,.18);background-clip:padding-box;border:3px solid transparent;border-radius:9999px}.rx-thumb{min-height:32px}
|
|
1023
1319
|
</style>
|
|
1320
|
+
|
|
1321
|
+
<style>
|
|
1322
|
+
@media (max-width:767.98px){[data-reka-popper-content-wrapper]:has(>[data-slot=popover-content].w-\[34rem\]){inset:auto 0 0 0!important;min-width:0!important;position:fixed!important;transform:none!important}[data-reka-popper-content-wrapper]>[data-slot=popover-content].w-\[34rem\]{border-radius:.75rem .75rem 0 0!important;max-height:80svh;max-width:100vw!important;overflow:auto;padding-bottom:env(safe-area-inset-bottom);width:100vw!important}}
|
|
1323
|
+
</style>
|