@revenexx/studio-shared 0.2.0 → 0.3.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 +7 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +37 -1
- package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +2 -0
- package/dist/runtime/components/DataTable/ColumnsMenu.vue +93 -36
- package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +2 -0
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +53 -4
- package/dist/runtime/components/DataTable/DataTable.vue +274 -118
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +53 -4
- package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +14 -3
- package/dist/runtime/components/DataTable/FilterMenu.vue +35 -29
- package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +14 -3
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +6 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +189 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +6 -0
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +13 -0
- package/dist/runtime/components/DataTable/SortMenu.vue +47 -0
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +13 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +20 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue +26 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +20 -0
- package/dist/runtime/components/DataTable/types.d.ts +49 -2
- package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +17 -0
- package/dist/runtime/components/Media/MediaFolderTree.vue +65 -0
- package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +17 -0
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Media/MediaPickerDialog.vue +163 -0
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +29 -0
- package/dist/runtime/components/Media/MediaUploader.vue +161 -0
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +29 -0
- package/dist/runtime/components/Shared/BuildLogsFlyout.vue +14 -13
- package/dist/runtime/components/Shared/FloatingPanel.d.vue.ts +55 -0
- package/dist/runtime/components/Shared/FloatingPanel.vue +118 -0
- package/dist/runtime/components/Shared/FloatingPanel.vue.d.ts +55 -0
- package/dist/runtime/components/Shared/GitDeploymentSettings.vue +11 -10
- package/dist/runtime/components/Shared/ListSurface.d.vue.ts +38 -0
- package/dist/runtime/components/Shared/ListSurface.vue +27 -0
- package/dist/runtime/components/Shared/ListSurface.vue.d.ts +38 -0
- package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +52 -0
- package/dist/runtime/components/Shared/RecordLayout.vue +63 -0
- package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +52 -0
- package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +26 -0
- package/dist/runtime/components/Shared/RecordRailCard.vue +20 -0
- package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +26 -0
- package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +5 -0
- package/dist/runtime/components/Shared/SidebarNav.vue +6 -26
- package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +5 -0
- package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +15 -0
- package/dist/runtime/components/Shared/SidebarNavLink.vue +49 -0
- package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +15 -0
- package/dist/runtime/components/Shared/Wizard.vue +6 -3
- package/dist/runtime/composables/useFloatingPanel.d.ts +69 -0
- package/dist/runtime/composables/useFloatingPanel.js +112 -0
- package/dist/runtime/composables/useListRail.d.ts +9 -0
- package/dist/runtime/composables/useListRail.js +19 -0
- package/dist/runtime/composables/useMediaLibrary.d.ts +41 -0
- package/dist/runtime/composables/useMediaLibrary.js +36 -0
- package/dist/runtime/composables/usePlatformAuth.d.ts +18 -0
- package/dist/runtime/composables/usePlatformTalkback.d.ts +29 -0
- package/dist/runtime/composables/usePlatformTalkback.js +54 -0
- package/dist/runtime/composables/useT.d.ts +11 -0
- package/dist/runtime/composables/useT.js +8 -0
- package/dist/runtime/composables/useTalkbackChannels.d.ts +93 -0
- package/dist/runtime/composables/useTalkbackChannels.js +56 -0
- package/dist/runtime/lib/columnOrder.d.ts +45 -0
- package/dist/runtime/lib/columnOrder.js +28 -0
- package/dist/runtime/lib/columnVisibility.d.ts +41 -0
- package/dist/runtime/lib/columnVisibility.js +12 -0
- package/dist/runtime/lib/dataTableFilters.d.ts +83 -0
- package/dist/runtime/lib/dataTableFilters.js +23 -0
- package/dist/runtime/lib/formatBridge.d.ts +50 -0
- package/dist/runtime/lib/formatBridge.js +54 -0
- package/dist/runtime/lib/i18nBridge.d.ts +36 -0
- package/dist/runtime/lib/i18nBridge.js +17 -0
- package/dist/runtime/lib/isoLists.d.ts +61 -0
- package/dist/runtime/lib/isoLists.js +62 -0
- package/dist/runtime/lib/sidebarNav.d.ts +34 -0
- package/dist/runtime/lib/sidebarNav.js +22 -0
- package/dist/runtime/lib/talkback.d.ts +59 -0
- package/dist/runtime/lib/talkback.js +49 -0
- package/dist/runtime/types/nav.d.ts +15 -1
- package/dist/runtime/types/storage.d.ts +67 -0
- package/dist/runtime/types/storage.js +0 -0
- package/dist/types.d.mts +14 -0
- package/package.json +19 -3
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Eye,
|
|
4
|
+
EyeClosed,
|
|
5
|
+
Filter,
|
|
6
|
+
ListArrowDownMinimalistic,
|
|
7
|
+
Magnifier as Search,
|
|
8
|
+
SidebarMinimalistic,
|
|
9
|
+
Widget as Columns
|
|
10
|
+
} from "#solar-icons";
|
|
3
11
|
import {
|
|
4
12
|
Button,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
DropdownMenuSubContent,
|
|
11
|
-
DropdownMenuSubTrigger,
|
|
12
|
-
DropdownMenuTrigger,
|
|
13
|
-
Input
|
|
13
|
+
ColumnPicker,
|
|
14
|
+
FilterPanel,
|
|
15
|
+
SearchField,
|
|
16
|
+
SegmentedToggle,
|
|
17
|
+
SegmentedToggleItem
|
|
14
18
|
} from "@revenexx/studio";
|
|
19
|
+
import { orderColumnsBy } from "../../lib/columnOrder";
|
|
20
|
+
import { initiallyHidden, visibleColumnsOf } from "../../lib/columnVisibility";
|
|
21
|
+
import { countByValue, matchesFilter } from "../../lib/dataTableFilters";
|
|
15
22
|
import SortHeader from "../Shared/SortHeader.vue";
|
|
16
|
-
import ColumnsMenu from "./ColumnsMenu.vue";
|
|
17
23
|
import FilterMenu from "./FilterMenu.vue";
|
|
24
|
+
import SavedViewsMenu from "./SavedViewsMenu.vue";
|
|
25
|
+
import SortMenu from "./SortMenu.vue";
|
|
18
26
|
const props = defineProps({
|
|
19
27
|
data: { type: Array, required: true },
|
|
20
28
|
columns: { type: Array, required: true },
|
|
21
29
|
rowKey: { type: Function, required: true, skipCheck: true },
|
|
22
30
|
search: { type: [Boolean, Object], required: false, default: true },
|
|
23
31
|
columnsMenu: { type: Boolean, required: false, default: true },
|
|
24
|
-
filter: { type: Boolean, required: false, default: false },
|
|
32
|
+
filter: { type: [Boolean, Object], required: false, default: false },
|
|
25
33
|
filterTabs: { type: [Object, Array], required: false },
|
|
34
|
+
savedViews: { type: [Object, null], required: false, default: null },
|
|
35
|
+
viewModes: { type: [Array, null], required: false, default: null },
|
|
36
|
+
rail: { type: [Boolean, Object], required: false, default: false },
|
|
37
|
+
scrollBody: { type: [Boolean, Object], required: false },
|
|
26
38
|
pagination: { type: [Boolean, Object], required: false, default: true },
|
|
27
39
|
defaultSort: { type: Object, required: false },
|
|
28
40
|
serverMode: { type: Boolean, required: false, default: false },
|
|
@@ -33,6 +45,7 @@ const props = defineProps({
|
|
|
33
45
|
groupEmptyLabel: { type: String, required: false }
|
|
34
46
|
});
|
|
35
47
|
const emit = defineEmits(["rowClick", "stateChange"]);
|
|
48
|
+
const t = useT();
|
|
36
49
|
const query = ref("");
|
|
37
50
|
const sort = defineModel("sort", { type: Object, ...{
|
|
38
51
|
default: () => ({
|
|
@@ -41,6 +54,8 @@ const sort = defineModel("sort", { type: Object, ...{
|
|
|
41
54
|
})
|
|
42
55
|
} });
|
|
43
56
|
const page = defineModel("page", { type: Number, ...{ default: 0 } });
|
|
57
|
+
const viewMode = defineModel("viewMode", { type: String, ...{ default: "list" } });
|
|
58
|
+
const railOpen = defineModel("railOpen", { type: Boolean, ...{ default: true } });
|
|
44
59
|
if (props.defaultSort && sort.value.column == null) {
|
|
45
60
|
sort.value = { column: props.defaultSort.column, direction: props.defaultSort.direction };
|
|
46
61
|
}
|
|
@@ -48,19 +63,20 @@ const pageSize = ref(
|
|
|
48
63
|
typeof props.pagination === "object" && props.pagination?.pageSize ? props.pagination.pageSize : 20
|
|
49
64
|
);
|
|
50
65
|
const pageSizeOptions = computed(() => Array.from(/* @__PURE__ */ new Set([10, 20, 50, 100, pageSize.value])).sort((a, b) => a - b));
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
const hidden = ref(initiallyHidden(props.columns));
|
|
67
|
+
const columnOrder = ref([]);
|
|
68
|
+
const arrangedColumns = computed(() => orderColumnsBy(props.columns, columnOrder.value));
|
|
69
|
+
const visibleColumns = computed(() => visibleColumnsOf(arrangedColumns.value, hidden.value));
|
|
70
|
+
const pickerColumns = computed(() => arrangedColumns.value.filter((c) => (c.label ?? "").trim().length > 0).map((c) => ({
|
|
71
|
+
key: c.key,
|
|
72
|
+
label: c.label ?? c.key,
|
|
73
|
+
visible: !hidden.value.has(c.key),
|
|
74
|
+
toggleable: c.toggleable !== false
|
|
75
|
+
})));
|
|
76
|
+
function onPickerUpdate(next) {
|
|
77
|
+
columnOrder.value = next.map((c) => c.key);
|
|
78
|
+
hidden.value = new Set(next.filter((c) => c.visible === false).map((c) => c.key));
|
|
59
79
|
}
|
|
60
|
-
const visibleColumns = computed(
|
|
61
|
-
() => props.columns.filter((c) => visibleKeys.value.has(c.key))
|
|
62
|
-
);
|
|
63
|
-
const menuColumns = computed(() => props.columns);
|
|
64
80
|
function searchOf(col, row) {
|
|
65
81
|
if (col.searchGetter) return col.searchGetter(row).toLowerCase();
|
|
66
82
|
if (col.sortGetter) {
|
|
@@ -74,26 +90,36 @@ const filterDims = computed(() => {
|
|
|
74
90
|
const raw = props.filterTabs;
|
|
75
91
|
if (!raw) return [];
|
|
76
92
|
const arr = Array.isArray(raw) ? raw : [raw];
|
|
77
|
-
return arr.map((f, i) => ({ key: f.key ?? f.label ?? `dim${i}`, label: f.label ?? "
|
|
93
|
+
return arr.map((f, i) => ({ key: f.key ?? f.label ?? `dim${i}`, label: f.label ?? t("shared.datatable.filter.dimension.label.default"), getter: f.getter, options: f.options, allLabel: f.allLabel }));
|
|
78
94
|
});
|
|
79
95
|
const activeFilters = ref({});
|
|
80
|
-
const
|
|
81
|
-
function
|
|
82
|
-
|
|
96
|
+
const panelSelection = computed(() => Object.entries(activeFilters.value).flatMap(([key, values]) => values.map((v) => `${key}:${v}`)));
|
|
97
|
+
function applyPanelSelection(selected) {
|
|
98
|
+
const next = {};
|
|
99
|
+
for (const id of selected) {
|
|
100
|
+
const at = id.indexOf(":");
|
|
101
|
+
if (at === -1) continue;
|
|
102
|
+
const key = id.slice(0, at);
|
|
103
|
+
(next[key] ??= []).push(id.slice(at + 1));
|
|
104
|
+
}
|
|
105
|
+
activeFilters.value = next;
|
|
83
106
|
}
|
|
107
|
+
const filterFacets = computed(() => filterDims.value.map((d) => {
|
|
108
|
+
const counts = dimCounts(d);
|
|
109
|
+
return {
|
|
110
|
+
key: d.key,
|
|
111
|
+
label: d.label,
|
|
112
|
+
options: d.options.map((o) => ({ value: o.value, label: o.label, count: counts[o.value] ?? 0 }))
|
|
113
|
+
};
|
|
114
|
+
}));
|
|
84
115
|
function dimCounts(d) {
|
|
85
|
-
|
|
86
|
-
for (const row of props.data) {
|
|
87
|
-
const k = String(d.getter(row) ?? "");
|
|
88
|
-
m[k] = (m[k] ?? 0) + 1;
|
|
89
|
-
}
|
|
90
|
-
return m;
|
|
116
|
+
return countByValue(props.data, d.getter);
|
|
91
117
|
}
|
|
92
118
|
const filtered = computed(() => {
|
|
93
119
|
if (props.serverMode || !filterDims.value.length) return props.data;
|
|
94
|
-
const active = filterDims.value.filter((d) => activeFilters.value[d.key]);
|
|
120
|
+
const active = filterDims.value.filter((d) => activeFilters.value[d.key]?.length);
|
|
95
121
|
if (!active.length) return props.data;
|
|
96
|
-
return props.data.filter((row) => active.every((d) =>
|
|
122
|
+
return props.data.filter((row) => active.every((d) => matchesFilter(d.getter, row, activeFilters.value[d.key])));
|
|
97
123
|
});
|
|
98
124
|
const searched = computed(() => {
|
|
99
125
|
if (props.serverMode) return props.data;
|
|
@@ -151,7 +177,7 @@ const keyedRows = computed(() => {
|
|
|
151
177
|
});
|
|
152
178
|
const renderRows = computed(() => {
|
|
153
179
|
if (!props.groupBy) return keyedRows.value.map(({ row, key }) => ({ kind: "row", row, key }));
|
|
154
|
-
const emptyLabel = props.groupEmptyLabel ?? "
|
|
180
|
+
const emptyLabel = props.groupEmptyLabel ?? t("shared.datatable.group.ungrouped.default");
|
|
155
181
|
const groups = /* @__PURE__ */ new Map();
|
|
156
182
|
for (const entry of keyedRows.value) {
|
|
157
183
|
const labels2 = props.groupBy(entry.row);
|
|
@@ -207,15 +233,26 @@ const gridTemplate = computed(() => {
|
|
|
207
233
|
return `grid-template-columns: ${tracks.join(" ")};`;
|
|
208
234
|
});
|
|
209
235
|
const placeholder = computed(
|
|
210
|
-
() => typeof props.search === "object" ? props.search.placeholder ?? "
|
|
236
|
+
() => typeof props.search === "object" ? props.search.placeholder ?? t("shared.datatable.toolbar.search.placeholder.default") : t("shared.datatable.toolbar.search.placeholder.default")
|
|
211
237
|
);
|
|
212
238
|
const slots = useSlots();
|
|
213
239
|
const showToolbar = computed(
|
|
214
|
-
() => props.search || props.columnsMenu || props.filter || !!props.filterTabs || !!slots.actions || !!slots["toolbar-left"]
|
|
240
|
+
() => 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"]
|
|
215
241
|
);
|
|
242
|
+
const railLabel = computed(
|
|
243
|
+
() => (typeof props.rail === "object" ? props.rail.label : void 0) ?? t("shared.datatable.toolbar.rail.label.default")
|
|
244
|
+
);
|
|
245
|
+
const showTable = computed(() => !props.viewModes?.length || viewMode.value === "list");
|
|
246
|
+
const sortableColumns = computed(() => props.columns.filter((c) => c.sortable));
|
|
247
|
+
const showSortMenu = computed(() => !showTable.value && sortableColumns.value.length > 0);
|
|
216
248
|
function cellIconFor(col) {
|
|
217
249
|
return col.cellIcon ?? null;
|
|
218
250
|
}
|
|
251
|
+
const scrollStyle = computed(() => {
|
|
252
|
+
if (!props.scrollBody) return void 0;
|
|
253
|
+
const configured = typeof props.scrollBody === "object" ? props.scrollBody.maxHeight : void 0;
|
|
254
|
+
return { maxHeight: configured ?? "calc(100vh - 22rem)" };
|
|
255
|
+
});
|
|
219
256
|
const showFooter = computed(() => props.pagination !== false && !props.groupBy);
|
|
220
257
|
const lastPage = computed(
|
|
221
258
|
() => pageSize.value > 0 ? Math.max(0, Math.ceil(totalCount.value / pageSize.value) - 1) : 0
|
|
@@ -240,79 +277,184 @@ function defaultCell(col, row) {
|
|
|
240
277
|
<template>
|
|
241
278
|
<div class="rounded-lg border border-black/[0.06] overflow-hidden flex flex-col">
|
|
242
279
|
<!-- Toolbar ------------------------------------------------------ -->
|
|
243
|
-
<!--
|
|
244
|
-
|
|
280
|
+
<!-- The single control row of a list surface.
|
|
281
|
+
|
|
282
|
+
The bar carries the dashboard's tint rather than flat white, so a
|
|
283
|
+
list's chrome and the home screen's are recognisably one surface. On
|
|
284
|
+
white the controls sat on nothing and the row read as a gap above the
|
|
285
|
+
table; with a ground under them they read as a row.
|
|
286
|
+
|
|
287
|
+
The stops are REVERSED from the dashboard's, and measuring is what
|
|
288
|
+
said so. The dashboard panel runs white → tint → white, which is right
|
|
289
|
+
for a panel whose content sits in the middle. A toolbar's content sits
|
|
290
|
+
at the two ENDS: the search field lands at 12% along the bar, where a
|
|
291
|
+
gradient starting pure white at 0% and reaching its tint at 50% is
|
|
292
|
+
still white. So a white field sat on a white ground with a 10% hairline
|
|
293
|
+
and a white rim doing nothing, while the identical control on the
|
|
294
|
+
dashboard read cleanly against 0.972 lightness.
|
|
295
|
+
|
|
296
|
+
Tinted at the ends, clear in the middle where nothing stands.
|
|
297
|
+
|
|
298
|
+
Not the dashboard's shimmer or its blurred corner blob: those are for a
|
|
299
|
+
panel somebody looks AT, and this is one they look through on the way
|
|
300
|
+
to the rows.
|
|
301
|
+
LEFT is LOOKING: rail · search · filter · sort · columns · scope ·
|
|
302
|
+
view mode · saved views · #toolbar-left
|
|
303
|
+
RIGHT is DOING: #toolbar-right · #actions
|
|
304
|
+
(Pagination always lives in the footer.)
|
|
305
|
+
|
|
306
|
+
The line is not "filters versus the rest". View mode and column
|
|
307
|
+
visibility narrow nothing and still belong left, because reaching for
|
|
308
|
+
them means you are still looking for something; once your hand crosses
|
|
309
|
+
to the right you have stopped looking and started acting. Scope, view
|
|
310
|
+
mode and saved views used to sit on the right beside the primary
|
|
311
|
+
action, which made a row of eight controls with no readable boundary
|
|
312
|
+
in it. -->
|
|
245
313
|
<div
|
|
246
314
|
v-if="showToolbar"
|
|
247
|
-
class="flex items-center justify-between gap-2 px-4 py-3 bg-
|
|
315
|
+
class="flex flex-wrap items-center justify-between gap-2 px-4 py-3 bg-gradient-to-r from-violet-50/80 via-white to-violet-50/60"
|
|
248
316
|
>
|
|
249
317
|
<div class="flex items-center gap-2 min-w-0">
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
318
|
+
<Button
|
|
319
|
+
v-if="rail"
|
|
320
|
+
variant="outline"
|
|
321
|
+
size="sm"
|
|
322
|
+
class="h-7 shrink-0 gap-1.5 text-sm"
|
|
323
|
+
:class="railOpen ? 'border-primary/30 text-primary' : ''"
|
|
324
|
+
:aria-pressed="railOpen"
|
|
325
|
+
:aria-label="railOpen ? t('shared.datatable.toolbar.rail.aria.hide', { label: railLabel }) : t('shared.datatable.toolbar.rail.aria.show', { label: railLabel })"
|
|
326
|
+
@click="railOpen = !railOpen"
|
|
327
|
+
>
|
|
328
|
+
<SidebarMinimalistic :size="14" />
|
|
329
|
+
{{ railLabel }}
|
|
330
|
+
</Button>
|
|
331
|
+
<!-- Shrinks (never wraps the row) once the toolbar carries scope /
|
|
332
|
+
view-mode / saved-view controls next to it.
|
|
333
|
+
|
|
334
|
+
`SearchField` rather than an Input with an icon positioned over it
|
|
335
|
+
and a counted `pl-8`: three decisions that had to agree, and any
|
|
336
|
+
change to the icon size or the field height broke one of them. -->
|
|
337
|
+
<SearchField
|
|
338
|
+
v-if="search"
|
|
339
|
+
v-model="query"
|
|
340
|
+
:placeholder="placeholder"
|
|
341
|
+
class="w-[21rem] min-w-40 max-w-full shrink"
|
|
342
|
+
>
|
|
343
|
+
<template #icon>
|
|
344
|
+
<Search :size="14" weight="Linear" />
|
|
345
|
+
</template>
|
|
346
|
+
</SearchField>
|
|
347
|
+
<!--
|
|
348
|
+
Every facet at once, committed with Apply.
|
|
349
|
+
|
|
350
|
+
It was a menu of submenus: one facet visible at a time, one value
|
|
351
|
+
each, and the answer to "what is filtering right now?" only available
|
|
352
|
+
by opening all of them in turn. The panel lets a facet hold more than
|
|
353
|
+
one value and does not move the list until the reader is done
|
|
354
|
+
deciding.
|
|
355
|
+
|
|
356
|
+
`panes` rather than the automatic choice. Auto reads the numbers - it
|
|
357
|
+
switches past four facets or eight options - and today's surfaces sit
|
|
358
|
+
just under both thresholds, so they would draw as columns while the
|
|
359
|
+
same control on the pattern page draws as panes. A table that gains a
|
|
360
|
+
fifth facet would then change shape on its own. One layout, whatever
|
|
361
|
+
the surface happens to declare this week.
|
|
362
|
+
-->
|
|
363
|
+
<FilterPanel
|
|
364
|
+
v-if="filterFacets.length"
|
|
365
|
+
:model-value="panelSelection"
|
|
366
|
+
:facets="filterFacets"
|
|
367
|
+
layout="panes"
|
|
368
|
+
:label="t('shared.datatable.toolbar.filter.action')"
|
|
369
|
+
:apply-label="t('shared.datatable.toolbar.filter.apply')"
|
|
370
|
+
:reset-label="t('shared.datatable.toolbar.filter.reset')"
|
|
371
|
+
@update:model-value="applyPanelSelection"
|
|
372
|
+
>
|
|
373
|
+
<template #icon>
|
|
374
|
+
<Filter :size="14" weight="BoldDuotone" />
|
|
375
|
+
</template>
|
|
376
|
+
</FilterPanel>
|
|
377
|
+
<FilterMenu
|
|
378
|
+
v-if="filter"
|
|
379
|
+
:width="typeof filter === 'object' ? filter.width : void 0"
|
|
380
|
+
:active="typeof filter === 'object' ? filter.active : void 0"
|
|
381
|
+
:count="typeof filter === 'object' ? filter.count : void 0"
|
|
382
|
+
>
|
|
298
383
|
<slot name="filter-menu" />
|
|
299
384
|
</FilterMenu>
|
|
300
|
-
<
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
385
|
+
<SortMenu v-if="showSortMenu" v-model="sort" :columns="sortableColumns" />
|
|
386
|
+
<!--
|
|
387
|
+
The design system's picker, drag and drop included. It was a local
|
|
388
|
+
copy that did its own dragging with the browser's own drag events,
|
|
389
|
+
which cannot say where a row may GO: a row could be pulled out over
|
|
390
|
+
the table behind the popover and dropped there. Only the rows of this
|
|
391
|
+
list register as drop targets now, and the edge the pointer is nearest
|
|
392
|
+
decides the index rather than arithmetic on a row height that a
|
|
393
|
+
wrapped label invalidates.
|
|
394
|
+
-->
|
|
395
|
+
<ColumnPicker
|
|
396
|
+
v-if="columnsMenu && showTable"
|
|
397
|
+
:model-value="pickerColumns"
|
|
398
|
+
:label="t('shared.datatable.columnsmenu.tooltip')"
|
|
399
|
+
:heading="t('shared.datatable.columnsmenu.heading')"
|
|
400
|
+
@update:model-value="onPickerUpdate"
|
|
401
|
+
>
|
|
402
|
+
<template #icon>
|
|
403
|
+
<Columns :size="15" weight="BoldDuotone" />
|
|
404
|
+
</template>
|
|
405
|
+
<template #handle>
|
|
406
|
+
<ListArrowDownMinimalistic :size="15" weight="Linear" />
|
|
407
|
+
</template>
|
|
408
|
+
<template #visible>
|
|
409
|
+
<Eye :size="16" weight="Linear" />
|
|
410
|
+
</template>
|
|
411
|
+
<template #hidden>
|
|
412
|
+
<EyeClosed :size="16" weight="Linear" />
|
|
413
|
+
</template>
|
|
414
|
+
</ColumnPicker>
|
|
415
|
+
<!-- Market scope — the surface passes the shared MarketScopeToggle in;
|
|
416
|
+
the row position is fixed here. -->
|
|
417
|
+
<slot name="scope" />
|
|
418
|
+
<!-- The editor's device switch, which is the right shape wherever the
|
|
419
|
+
choice changes what you are LOOKING AT rather than which rows. -->
|
|
420
|
+
<SegmentedToggle v-if="viewModes?.length" v-model="viewMode" variant="inset">
|
|
421
|
+
<SegmentedToggleItem
|
|
422
|
+
v-for="m in viewModes"
|
|
423
|
+
:key="m.value"
|
|
424
|
+
:value="m.value"
|
|
425
|
+
:aria-label="m.label"
|
|
426
|
+
>
|
|
427
|
+
<component :is="m.icon" v-if="m.icon" :size="14" weight="Bold" />
|
|
428
|
+
<template v-if="!m.icon">{{ m.label }}</template>
|
|
429
|
+
</SegmentedToggleItem>
|
|
430
|
+
</SegmentedToggle>
|
|
431
|
+
<SavedViewsMenu v-if="savedViews" :config="savedViews" />
|
|
306
432
|
<slot name="toolbar-left" />
|
|
307
433
|
</div>
|
|
308
|
-
<div class="flex items-center gap-
|
|
434
|
+
<div class="flex items-center gap-2 ml-auto shrink-0">
|
|
435
|
+
<slot name="toolbar-right" />
|
|
309
436
|
<slot name="actions" />
|
|
310
437
|
</div>
|
|
311
438
|
</div>
|
|
312
439
|
|
|
440
|
+
<!-- Alternate view mode (gallery, board, …) — same toolbar, same footer. -->
|
|
441
|
+
<div v-if="!showTable" class="border-t border-black/[0.04] bg-white/55">
|
|
442
|
+
<slot name="view" :rows="displayedRows" :loading="loading" />
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<!-- The header and the rows share one box so their columns cannot drift.
|
|
446
|
+
`display: contents` when scrolling is off makes this element vanish
|
|
447
|
+
from layout entirely, so every table that has not asked for it renders
|
|
448
|
+
exactly as it did. -->
|
|
449
|
+
<div
|
|
450
|
+
:class="scrollBody ? 'min-h-0 overflow-y-auto' : 'contents'"
|
|
451
|
+
:style="scrollStyle"
|
|
452
|
+
>
|
|
313
453
|
<!-- Header row -------------------------------------------------- -->
|
|
314
454
|
<div
|
|
455
|
+
v-if="showTable"
|
|
315
456
|
class="grid items-center gap-2 border-t rounded-t-lg border-gray-200 bg-gray-100 px-4 py-2.5 text-sm font-medium uppercase tracking-normal text-muted-foreground"
|
|
457
|
+
:class="scrollBody ? 'sticky top-0 z-10' : ''"
|
|
316
458
|
:style="gridTemplate"
|
|
317
459
|
>
|
|
318
460
|
<div
|
|
@@ -320,22 +462,31 @@ function defaultCell(col, row) {
|
|
|
320
462
|
:key="col.key"
|
|
321
463
|
:class="col.alignRight ? 'text-right' : ''"
|
|
322
464
|
>
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
465
|
+
<!-- `#header-<key>` lets a page take over a header cell (e.g. a
|
|
466
|
+
select-all checkbox in a selection column). Default: sortable
|
|
467
|
+
SortHeader or the plain label. -->
|
|
468
|
+
<slot :name="`header-${col.key}`">
|
|
469
|
+
<SortHeader
|
|
470
|
+
v-if="col.sortable"
|
|
471
|
+
:column="col.key"
|
|
472
|
+
:active="sort.column"
|
|
473
|
+
:direction="sort.direction"
|
|
474
|
+
:align-right="col.alignRight"
|
|
475
|
+
@toggle="toggleSort"
|
|
476
|
+
>
|
|
477
|
+
{{ col.label }}
|
|
478
|
+
</SortHeader>
|
|
479
|
+
<!-- `normal-case` to match SortHeader, whose button does not inherit
|
|
480
|
+
the row's uppercase. Without it a column reads "LAST CHECK"
|
|
481
|
+
beside "Channel" and "Provider" for no reason a reader can see —
|
|
482
|
+
the header shouts wherever a column happens not to be sortable. -->
|
|
483
|
+
<span v-else class="normal-case">{{ col.label }}</span>
|
|
484
|
+
</slot>
|
|
334
485
|
</div>
|
|
335
486
|
</div>
|
|
336
487
|
|
|
337
488
|
<!-- Body --------------------------------------------------------- -->
|
|
338
|
-
<template v-for="entry in renderRows" :key="entry.key">
|
|
489
|
+
<template v-for="entry in showTable ? renderRows : []" :key="entry.key">
|
|
339
490
|
<!-- Group header (groupBy mode) -->
|
|
340
491
|
<div
|
|
341
492
|
v-if="entry.kind === 'header'"
|
|
@@ -367,14 +518,17 @@ function defaultCell(col, row) {
|
|
|
367
518
|
</span>
|
|
368
519
|
<span class="text-sm font-semibold text-[#0B0522] truncate transition-colors group-hover:text-primary">{{ defaultCell(col, entry.row) }}</span>
|
|
369
520
|
</div>
|
|
370
|
-
|
|
521
|
+
<!-- `block`, or `truncate` clips nothing: an inline span establishes
|
|
522
|
+
no box, so a long unbroken value overruns the next column
|
|
523
|
+
instead of ending in an ellipsis. -->
|
|
524
|
+
<span v-else class="block truncate text-sm" :title="String(defaultCell(col, entry.row) ?? '')">{{ defaultCell(col, entry.row) }}</span>
|
|
371
525
|
</slot>
|
|
372
526
|
</div>
|
|
373
527
|
</div>
|
|
374
528
|
</template>
|
|
375
529
|
|
|
376
530
|
<!-- Loading skeleton (only when there are no rows yet) ----------- -->
|
|
377
|
-
<template v-if="loading && !displayedRows.length">
|
|
531
|
+
<template v-if="showTable && loading && !displayedRows.length">
|
|
378
532
|
<div
|
|
379
533
|
v-for="n in 6"
|
|
380
534
|
:key="`sk-${n}`"
|
|
@@ -396,14 +550,16 @@ function defaultCell(col, row) {
|
|
|
396
550
|
|
|
397
551
|
<!-- Empty -------------------------------------------------------- -->
|
|
398
552
|
<div
|
|
399
|
-
v-if="!displayedRows.length && !loading"
|
|
553
|
+
v-if="showTable && !displayedRows.length && !loading"
|
|
400
554
|
class="px-3 py-8 text-center text-sm text-muted-foreground"
|
|
401
555
|
>
|
|
402
556
|
<slot name="empty">
|
|
403
|
-
{{ emptyText ?? "
|
|
557
|
+
{{ emptyText ?? t("shared.datatable.empty.default") }}
|
|
404
558
|
</slot>
|
|
405
559
|
</div>
|
|
406
560
|
|
|
561
|
+
</div>
|
|
562
|
+
|
|
407
563
|
<!-- Footer ------------------------------------------------------- -->
|
|
408
564
|
<div
|
|
409
565
|
v-if="showFooter"
|
|
@@ -411,12 +567,12 @@ function defaultCell(col, row) {
|
|
|
411
567
|
>
|
|
412
568
|
<div class="flex items-center gap-2.5">
|
|
413
569
|
<span class="tabular-nums">
|
|
414
|
-
<span v-if="totalCount === 0">
|
|
415
|
-
<span v-else>
|
|
570
|
+
<span v-if="totalCount === 0">{{ t("shared.datatable.footer.noresults") }}</span>
|
|
571
|
+
<span v-else>{{ t("shared.datatable.footer.showing", { start: rangeStart, end: rangeEnd, total: totalCount }) }}</span>
|
|
416
572
|
</span>
|
|
417
573
|
<span class="text-muted-foreground/40">-</span>
|
|
418
574
|
<label class="flex items-center gap-1.5">
|
|
419
|
-
<span class="hidden sm:inline">
|
|
575
|
+
<span class="hidden sm:inline">{{ t("shared.datatable.footer.pagesize.label") }}</span>
|
|
420
576
|
<select
|
|
421
577
|
v-model.number="pageSize"
|
|
422
578
|
class="h-7 rounded-md border border-input bg-white px-1.5 text-sm tabular-nums focus:outline-none focus:ring-1 focus:ring-ring"
|
|
@@ -433,7 +589,7 @@ function defaultCell(col, row) {
|
|
|
433
589
|
:disabled="page === 0 || loading"
|
|
434
590
|
@click="prevPage"
|
|
435
591
|
>
|
|
436
|
-
|
|
592
|
+
{{ t("shared.datatable.footer.action.previous") }}
|
|
437
593
|
</Button>
|
|
438
594
|
<span class="tabular-nums">
|
|
439
595
|
{{ page + 1 }} / {{ lastPage + 1 }}
|
|
@@ -445,7 +601,7 @@ function defaultCell(col, row) {
|
|
|
445
601
|
:disabled="page >= lastPage || loading"
|
|
446
602
|
@click="nextPage"
|
|
447
603
|
>
|
|
448
|
-
|
|
604
|
+
{{ t("shared.datatable.footer.action.next") }}
|
|
449
605
|
</Button>
|
|
450
606
|
</div>
|
|
451
607
|
</div>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type { DataTableColumn, DataTableFilter, DataTableState, RowKey, SortState } from './types.js';
|
|
1
|
+
import type { DataTableColumn, DataTableFilter, DataTableRail, DataTableSavedViews, DataTableState, DataTableViewMode, RowKey, SortState } from './types.js';
|
|
2
2
|
declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
3
3
|
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
4
4
|
readonly "onUpdate:sort"?: ((value: SortState) => any) | undefined;
|
|
5
5
|
readonly "onUpdate:page"?: ((value: number) => any) | undefined;
|
|
6
|
+
readonly "onUpdate:viewMode"?: ((value: string) => any) | undefined;
|
|
7
|
+
readonly "onUpdate:railOpen"?: ((value: boolean) => any) | undefined;
|
|
6
8
|
readonly onRowClick?: ((row: T) => any) | undefined;
|
|
7
9
|
readonly onStateChange?: ((state: DataTableState) => any) | undefined;
|
|
8
|
-
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:sort" | "onUpdate:page" | "onRowClick" | "onStateChange"> & ({
|
|
10
|
+
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:sort" | "onUpdate:page" | "onUpdate:viewMode" | "onUpdate:railOpen" | "onRowClick" | "onStateChange"> & ({
|
|
9
11
|
data: T[];
|
|
10
12
|
columns: DataTableColumn<T>[];
|
|
11
13
|
rowKey: RowKey<T>;
|
|
@@ -13,10 +15,44 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
|
|
|
13
15
|
placeholder?: string;
|
|
14
16
|
};
|
|
15
17
|
columnsMenu?: boolean;
|
|
16
|
-
filter
|
|
18
|
+
/** Mount the surface's own filter panel in the toolbar's Filter popover
|
|
19
|
+
* (`#filter-menu`). Pass an object to widen it / flag it as active. */
|
|
20
|
+
filter?: boolean | {
|
|
21
|
+
width?: string;
|
|
22
|
+
active?: boolean;
|
|
23
|
+
count?: number;
|
|
24
|
+
};
|
|
17
25
|
/** Built-in status/dimension filter pills (static mode). One dimension, or
|
|
18
26
|
* several (each renders as its own sub-menu). */
|
|
19
27
|
filterTabs?: DataTableFilter<T> | DataTableFilter<T>[];
|
|
28
|
+
/** Saved views control — the surface supplies snapshot/apply, the table
|
|
29
|
+
* owns the control, the storage and the dialogs. */
|
|
30
|
+
savedViews?: DataTableSavedViews | null;
|
|
31
|
+
/** View-mode switch (e.g. List | Gallery). Any mode other than `list`
|
|
32
|
+
* renders the `#view` slot in place of the table body. */
|
|
33
|
+
viewModes?: DataTableViewMode[] | null;
|
|
34
|
+
/** Collapse handle for the surface's left rail (see `ListSurface`). */
|
|
35
|
+
rail?: boolean | DataTableRail;
|
|
36
|
+
/**
|
|
37
|
+
* Scroll the ROWS inside the table instead of the page.
|
|
38
|
+
*
|
|
39
|
+
* The toolbar, the header and the footer stay put and only the data moves,
|
|
40
|
+
* so paging through a long list keeps the search box, the filters and the
|
|
41
|
+
* "showing 20 of 400" in view — you stay in the table's context instead of
|
|
42
|
+
* scrolling the whole surface away and losing the controls that scope it.
|
|
43
|
+
*
|
|
44
|
+
* OPT-IN. A short list scrolling inside a box shorter than the list is worse
|
|
45
|
+
* than a page that scrolls, and there is no way for this component to know
|
|
46
|
+
* which it is looking at.
|
|
47
|
+
*
|
|
48
|
+
* The header row moves INSIDE the scroll container and sticks, rather than
|
|
49
|
+
* sitting above it. That is not a detail: a scrollbar takes width from the
|
|
50
|
+
* scrolling box, so a header outside it stays wider than the rows and every
|
|
51
|
+
* column drifts by however wide the scrollbar is.
|
|
52
|
+
*/
|
|
53
|
+
scrollBody?: boolean | {
|
|
54
|
+
maxHeight?: string;
|
|
55
|
+
};
|
|
20
56
|
pagination?: boolean | {
|
|
21
57
|
pageSize?: number;
|
|
22
58
|
};
|
|
@@ -33,23 +69,36 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
|
|
|
33
69
|
} & {
|
|
34
70
|
sort?: SortState;
|
|
35
71
|
page?: number;
|
|
72
|
+
viewMode?: string;
|
|
73
|
+
railOpen?: boolean;
|
|
36
74
|
}) & Partial<{}>> & import("vue").PublicProps;
|
|
37
75
|
expose(exposed: import("vue").ShallowUnwrapRef<{}>): void;
|
|
38
76
|
attrs: any;
|
|
39
77
|
slots: {
|
|
78
|
+
[x: string]: ((props: {}) => any) | undefined;
|
|
79
|
+
} & {
|
|
40
80
|
[x: string]: ((props: {
|
|
41
81
|
row: any;
|
|
42
82
|
}) => any) | undefined;
|
|
43
83
|
} & {
|
|
44
84
|
'filter-menu'?: (props: {}) => any;
|
|
85
|
+
} & {
|
|
86
|
+
scope?: (props: {}) => any;
|
|
45
87
|
} & {
|
|
46
88
|
'toolbar-left'?: (props: {}) => any;
|
|
89
|
+
} & {
|
|
90
|
+
'toolbar-right'?: (props: {}) => any;
|
|
47
91
|
} & {
|
|
48
92
|
actions?: (props: {}) => any;
|
|
93
|
+
} & {
|
|
94
|
+
view?: (props: {
|
|
95
|
+
rows: any;
|
|
96
|
+
loading: any;
|
|
97
|
+
}) => any;
|
|
49
98
|
} & {
|
|
50
99
|
empty?: (props: {}) => any;
|
|
51
100
|
};
|
|
52
|
-
emit: (((evt: "rowClick", row: T) => void) & ((evt: "stateChange", state: DataTableState) => void)) & (((evt: "update:sort", value: SortState) => void) & ((evt: "update:page", value: number) => void));
|
|
101
|
+
emit: (((evt: "rowClick", row: T) => void) & ((evt: "stateChange", state: DataTableState) => void)) & (((evt: "update:sort", value: SortState) => void) & ((evt: "update:page", value: number) => void) & ((evt: "update:viewMode", value: string) => void) & ((evt: "update:railOpen", value: boolean) => void));
|
|
53
102
|
}>) => import("vue").VNode & {
|
|
54
103
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
55
104
|
};
|