@revenexx/studio-shared 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.d.mts +6 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -1
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +16 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +39 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +16 -0
- package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +58 -0
- package/dist/runtime/components/Dashboard/DashboardControls.vue +209 -0
- package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +58 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +17 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.vue +87 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +17 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +19 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +61 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +19 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +15 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +100 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +15 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +28 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +155 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +28 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +64 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue +79 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +64 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +44 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +153 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +44 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +28 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +80 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +28 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +7 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue +30 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +7 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +9 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue +13 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +9 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +24 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue +140 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +24 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +23 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue +124 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +23 -0
- package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +2 -1
- package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +105 -28
- package/dist/runtime/components/DataTable/DataTable.vue +403 -38
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +105 -28
- package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +17 -0
- package/dist/runtime/components/DataTable/FacetChips.vue +72 -0
- package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +17 -0
- package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +21 -0
- package/dist/runtime/components/DataTable/FacetPanel.vue +359 -0
- package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +21 -0
- package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +6 -5
- package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +6 -5
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +6 -1
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +83 -63
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +6 -1
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +9 -3
- package/dist/runtime/components/DataTable/SortMenu.vue +40 -12
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +9 -3
- package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +4 -2
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +4 -2
- package/dist/runtime/components/Form/FieldCell.d.vue.ts +32 -0
- package/dist/runtime/components/Form/FieldCell.vue +46 -0
- package/dist/runtime/components/Form/FieldCell.vue.d.ts +32 -0
- package/dist/runtime/components/Form/FieldGrid.d.vue.ts +13 -0
- package/dist/runtime/components/Form/FieldGrid.vue +11 -0
- package/dist/runtime/components/Form/FieldGrid.vue.d.ts +13 -0
- package/dist/runtime/components/Form/FormContextCard.d.vue.ts +33 -0
- package/dist/runtime/components/Form/FormContextCard.vue +56 -0
- package/dist/runtime/components/Form/FormContextCard.vue.d.ts +33 -0
- package/dist/runtime/components/Form/FormLayout.d.vue.ts +47 -0
- package/dist/runtime/components/Form/FormLayout.vue +169 -0
- package/dist/runtime/components/Form/FormLayout.vue.d.ts +47 -0
- package/dist/runtime/components/Form/FormSection.d.vue.ts +25 -0
- package/dist/runtime/components/Form/FormSection.vue +48 -0
- package/dist/runtime/components/Form/FormSection.vue.d.ts +25 -0
- package/dist/runtime/components/Form/SaveBar.d.vue.ts +44 -0
- package/dist/runtime/components/Form/SaveBar.vue +71 -0
- package/dist/runtime/components/Form/SaveBar.vue.d.ts +44 -0
- package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +2 -1
- package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +2 -1
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +4 -3
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +4 -3
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +4 -3
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +4 -3
- package/dist/runtime/components/Shared/AppIcon.d.vue.ts +21 -0
- package/dist/runtime/components/Shared/AppIcon.vue +47 -0
- package/dist/runtime/components/Shared/AppIcon.vue.d.ts +21 -0
- package/dist/runtime/components/Shared/BuildLogsFlyout.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/BuildLogsFlyout.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/DetailSheet.d.vue.ts +60 -0
- package/dist/runtime/components/Shared/DetailSheet.vue +207 -0
- package/dist/runtime/components/Shared/DetailSheet.vue.d.ts +60 -0
- package/dist/runtime/components/Shared/FloatingPanel.d.vue.ts +7 -6
- package/dist/runtime/components/Shared/FloatingPanel.vue.d.ts +7 -6
- package/dist/runtime/components/Shared/GitDeploymentSettings.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/GitDeploymentSettings.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/KpiTile.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/KpiTile.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/ListSurface.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/ListSurface.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/PageHeader.d.vue.ts +7 -6
- package/dist/runtime/components/Shared/PageHeader.vue.d.ts +7 -6
- package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +8 -7
- package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +8 -7
- package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/SortHeader.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/SortHeader.vue +6 -2
- package/dist/runtime/components/Shared/SortHeader.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/StateCard.d.vue.ts +6 -5
- package/dist/runtime/components/Shared/StateCard.vue.d.ts +6 -5
- package/dist/runtime/components/Shared/StudioCard.d.vue.ts +5 -4
- package/dist/runtime/components/Shared/StudioCard.vue.d.ts +5 -4
- package/dist/runtime/components/Shared/TabBar.d.vue.ts +9 -10
- package/dist/runtime/components/Shared/TabBar.vue +26 -7
- package/dist/runtime/components/Shared/TabBar.vue.d.ts +9 -10
- package/dist/runtime/components/Shared/ViewTab.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/ViewTab.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/Wizard.d.vue.ts +10 -7
- package/dist/runtime/components/Shared/Wizard.vue.d.ts +10 -7
- package/dist/runtime/composables/useDashboards.d.ts +27 -0
- package/dist/runtime/composables/useDashboards.js +143 -0
- package/dist/runtime/composables/useDetailSheet.d.ts +20 -0
- package/dist/runtime/composables/useDetailSheet.js +50 -0
- package/dist/runtime/composables/usePlatformTenant.d.ts +6 -0
- package/dist/runtime/composables/useSavedViews.d.ts +44 -0
- package/dist/runtime/composables/useSavedViews.js +44 -0
- package/dist/runtime/composables/useStickyListView.d.ts +60 -0
- package/dist/runtime/composables/useStickyListView.js +32 -0
- package/dist/runtime/lib/activeTenant.d.ts +58 -0
- package/dist/runtime/lib/activeTenant.js +45 -0
- package/dist/runtime/lib/appIcon.d.ts +48 -0
- package/dist/runtime/lib/appIcon.js +27 -0
- package/dist/runtime/lib/canonicalJson.d.ts +43 -0
- package/dist/runtime/lib/canonicalJson.js +38 -0
- package/dist/runtime/lib/dashboardIcons.d.ts +4 -0
- package/dist/runtime/lib/dashboardIcons.js +50 -0
- package/dist/runtime/lib/detailSheet.d.ts +43 -0
- package/dist/runtime/lib/detailSheet.js +34 -0
- package/dist/runtime/lib/facets.d.ts +200 -0
- package/dist/runtime/lib/facets.js +171 -0
- package/dist/runtime/lib/formLayout.d.ts +94 -0
- package/dist/runtime/lib/formLayout.js +70 -0
- package/dist/runtime/lib/formLayoutContext.d.ts +26 -0
- package/dist/runtime/lib/formLayoutContext.js +1 -0
- package/dist/runtime/lib/gridTracks.d.ts +55 -0
- package/dist/runtime/lib/gridTracks.js +29 -0
- package/dist/runtime/lib/rowMove.d.ts +38 -0
- package/dist/runtime/lib/rowMove.js +20 -0
- package/dist/runtime/lib/solarIconMap.d.ts +15 -0
- package/dist/runtime/lib/solarIconMap.js +280 -0
- package/dist/runtime/lib/solarIcons.d.ts +37 -0
- package/dist/runtime/lib/solarIcons.js +25 -0
- package/dist/runtime/lib/widgetRegistry.d.ts +124 -0
- package/dist/runtime/lib/widgetRegistry.js +66 -0
- package/dist/runtime/types/dashboard.d.ts +95 -0
- package/dist/runtime/types/dashboard.js +2 -0
- package/dist/types.d.mts +12 -0
- package/package.json +3 -2
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Eye,
|
|
4
4
|
EyeClosed,
|
|
5
5
|
Filter,
|
|
6
|
+
HamburgerMenu as DragHandle,
|
|
6
7
|
ListArrowDownMinimalistic,
|
|
7
8
|
Magnifier as Search,
|
|
8
9
|
SidebarMinimalistic,
|
|
@@ -19,7 +20,12 @@ import {
|
|
|
19
20
|
import { orderColumnsBy } from "../../lib/columnOrder";
|
|
20
21
|
import { initiallyHidden, visibleColumnsOf } from "../../lib/columnVisibility";
|
|
21
22
|
import { countByValue, matchesFilter } from "../../lib/dataTableFilters";
|
|
23
|
+
import { gridTemplateColumns } from "../../lib/gridTracks";
|
|
24
|
+
const RIGHT_ALIGNED = "text-right pr-4";
|
|
25
|
+
import { dropEdge, dropTargetIndex, moveKey, stepTarget } from "../../lib/rowMove";
|
|
22
26
|
import SortHeader from "../Shared/SortHeader.vue";
|
|
27
|
+
import FacetChips from "./FacetChips.vue";
|
|
28
|
+
import FacetPanel from "./FacetPanel.vue";
|
|
23
29
|
import FilterMenu from "./FilterMenu.vue";
|
|
24
30
|
import SavedViewsMenu from "./SavedViewsMenu.vue";
|
|
25
31
|
import SortMenu from "./SortMenu.vue";
|
|
@@ -31,10 +37,12 @@ const props = defineProps({
|
|
|
31
37
|
columnsMenu: { type: Boolean, required: false, default: true },
|
|
32
38
|
filter: { type: [Boolean, Object], required: false, default: false },
|
|
33
39
|
filterTabs: { type: [Object, Array], required: false },
|
|
40
|
+
facets: { type: Array, required: false, default: () => [] },
|
|
41
|
+
chipFacetLabels: { type: Boolean, required: false, default: false },
|
|
34
42
|
savedViews: { type: [Object, null], required: false, default: null },
|
|
35
43
|
viewModes: { type: [Array, null], required: false, default: null },
|
|
36
44
|
rail: { type: [Boolean, Object], required: false, default: false },
|
|
37
|
-
scrollBody: { type: [Boolean, Object], required: false },
|
|
45
|
+
scrollBody: { type: [Boolean, String, Object], required: false },
|
|
38
46
|
pagination: { type: [Boolean, Object], required: false, default: true },
|
|
39
47
|
defaultSort: { type: Object, required: false },
|
|
40
48
|
serverMode: { type: Boolean, required: false, default: false },
|
|
@@ -42,9 +50,13 @@ const props = defineProps({
|
|
|
42
50
|
loading: { type: Boolean, required: false, default: false },
|
|
43
51
|
emptyText: { type: String, required: false },
|
|
44
52
|
groupBy: { type: [Function, null], required: false, default: null },
|
|
45
|
-
groupEmptyLabel: { type: String, required: false }
|
|
53
|
+
groupEmptyLabel: { type: String, required: false },
|
|
54
|
+
activeRowKey: { type: [String, Number, null], required: false },
|
|
55
|
+
rowClass: { type: [Function, null], required: false, default: null },
|
|
56
|
+
reorderable: { type: Boolean, required: false, default: false },
|
|
57
|
+
reorderDisabled: { type: [String, null], required: false, default: null }
|
|
46
58
|
});
|
|
47
|
-
const emit = defineEmits(["rowClick", "stateChange"]);
|
|
59
|
+
const emit = defineEmits(["rowClick", "stateChange", "orderChange", "rowMove"]);
|
|
48
60
|
const t = useT();
|
|
49
61
|
const query = ref("");
|
|
50
62
|
const sort = defineModel("sort", { type: Object, ...{
|
|
@@ -56,6 +68,8 @@ const sort = defineModel("sort", { type: Object, ...{
|
|
|
56
68
|
const page = defineModel("page", { type: Number, ...{ default: 0 } });
|
|
57
69
|
const viewMode = defineModel("viewMode", { type: String, ...{ default: "list" } });
|
|
58
70
|
const railOpen = defineModel("railOpen", { type: Boolean, ...{ default: true } });
|
|
71
|
+
const facetSelection = defineModel("facetSelection", { type: Object, ...{ default: () => ({}) } });
|
|
72
|
+
const panelFacets = computed(() => props.facets);
|
|
59
73
|
if (props.defaultSort && sort.value.column == null) {
|
|
60
74
|
sort.value = { column: props.defaultSort.column, direction: props.defaultSort.direction };
|
|
61
75
|
}
|
|
@@ -157,9 +171,24 @@ const sorted = computed(() => {
|
|
|
157
171
|
return 0;
|
|
158
172
|
});
|
|
159
173
|
});
|
|
174
|
+
watch(sorted, (rows) => emit("orderChange", rows.map(rowKeyFor)), { immediate: true });
|
|
160
175
|
const totalCount = computed(
|
|
161
176
|
() => props.serverMode ? props.total ?? props.data.length : sorted.value.length
|
|
162
177
|
);
|
|
178
|
+
const activeNarrowing = computed(() => filterDims.value.map((d) => ({
|
|
179
|
+
label: d.label,
|
|
180
|
+
values: (activeFilters.value[d.key] ?? []).map((v) => d.options.find((o) => o.value === v)?.label ?? v)
|
|
181
|
+
})).filter((d) => d.values.length > 0));
|
|
182
|
+
function clearNarrowing() {
|
|
183
|
+
query.value = "";
|
|
184
|
+
activeFilters.value = {};
|
|
185
|
+
facetSelection.value = {};
|
|
186
|
+
}
|
|
187
|
+
const liveStatus = computed(() => {
|
|
188
|
+
if (!showTable.value) return "";
|
|
189
|
+
if (props.loading) return t("shared.datatable.state.loading");
|
|
190
|
+
return totalCount.value === 0 ? t("shared.datatable.footer.noresults") : t("shared.datatable.footer.showing", { start: rangeStart.value, end: rangeEnd.value, total: totalCount.value });
|
|
191
|
+
});
|
|
163
192
|
const paginated = computed(() => {
|
|
164
193
|
if (props.serverMode || !props.pagination) return sorted.value;
|
|
165
194
|
const start = page.value * pageSize.value;
|
|
@@ -233,10 +262,8 @@ function rowKeyFor(row) {
|
|
|
233
262
|
if (typeof props.rowKey === "function") return props.rowKey(row);
|
|
234
263
|
return row[props.rowKey];
|
|
235
264
|
}
|
|
236
|
-
const gridTemplate = computed(() => {
|
|
237
|
-
|
|
238
|
-
return `grid-template-columns: ${tracks.join(" ")};`;
|
|
239
|
-
});
|
|
265
|
+
const gridTemplate = computed(() => `grid-template-columns: ${gridTemplateColumns(renderColumns.value.map((c) => c.width))};`);
|
|
266
|
+
const SUBGRID_ROW = "grid-column: 1 / -1; grid-template-columns: subgrid;";
|
|
240
267
|
const placeholder = computed(
|
|
241
268
|
() => typeof props.search === "object" ? props.search.placeholder ?? t("shared.datatable.toolbar.search.placeholder.default") : t("shared.datatable.toolbar.search.placeholder.default")
|
|
242
269
|
);
|
|
@@ -249,7 +276,7 @@ const showToolbar = computed(
|
|
|
249
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"]
|
|
250
277
|
);
|
|
251
278
|
const railLabel = computed(
|
|
252
|
-
() => (typeof props.rail === "object" ? props.rail.label : void 0) ??
|
|
279
|
+
() => (typeof props.rail === "object" ? props.rail.label : void 0) ?? null
|
|
253
280
|
);
|
|
254
281
|
const showTable = computed(() => !props.viewModes?.length || viewMode.value === "list");
|
|
255
282
|
const sortableColumns = computed(() => props.columns.filter((c) => c.sortable));
|
|
@@ -257,8 +284,121 @@ const showSortMenu = computed(() => !showTable.value && sortableColumns.value.le
|
|
|
257
284
|
function cellIconFor(col) {
|
|
258
285
|
return col.cellIcon ?? null;
|
|
259
286
|
}
|
|
287
|
+
const scrollEl = ref(null);
|
|
288
|
+
const footerEl = ref(null);
|
|
289
|
+
const fitHeight = ref(null);
|
|
290
|
+
const MIN_FIT_PX = 220;
|
|
291
|
+
const BOTTOM_GAP_PX = 16;
|
|
292
|
+
function scrollParent(el) {
|
|
293
|
+
let node = el.parentElement;
|
|
294
|
+
while (node && node !== document.body) {
|
|
295
|
+
const overflow = getComputedStyle(node).overflowY;
|
|
296
|
+
if (overflow === "auto" || overflow === "scroll") return node;
|
|
297
|
+
node = node.parentElement;
|
|
298
|
+
}
|
|
299
|
+
return document.documentElement;
|
|
300
|
+
}
|
|
301
|
+
function measureFit() {
|
|
302
|
+
if (props.scrollBody !== "fit" || !import.meta.client) return;
|
|
303
|
+
const el = scrollEl.value;
|
|
304
|
+
if (!el) return;
|
|
305
|
+
const container = scrollParent(el);
|
|
306
|
+
const offset = el.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
|
307
|
+
const reserve = (footerEl.value?.offsetHeight ?? 0) + BOTTOM_GAP_PX;
|
|
308
|
+
const overflow = Math.max(container.scrollHeight - container.clientHeight, 0);
|
|
309
|
+
const available = Math.max(
|
|
310
|
+
Math.floor(container.clientHeight - offset - reserve - overflow) - 1,
|
|
311
|
+
MIN_FIT_PX
|
|
312
|
+
);
|
|
313
|
+
const current = fitHeight.value ? Number.parseInt(fitHeight.value, 10) : null;
|
|
314
|
+
if (current !== null && Math.abs(current - available) < 8) return;
|
|
315
|
+
fitHeight.value = `${available}px`;
|
|
316
|
+
}
|
|
317
|
+
if (import.meta.client) {
|
|
318
|
+
let frame = 0;
|
|
319
|
+
let timers = [];
|
|
320
|
+
const schedule = () => {
|
|
321
|
+
if (frame) return;
|
|
322
|
+
frame = requestAnimationFrame(() => {
|
|
323
|
+
frame = 0;
|
|
324
|
+
measureFit();
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
onMounted(() => {
|
|
328
|
+
measureFit();
|
|
329
|
+
window.addEventListener("resize", schedule);
|
|
330
|
+
timers = [setTimeout(schedule, 250), setTimeout(schedule, 1200)];
|
|
331
|
+
});
|
|
332
|
+
onBeforeUnmount(() => {
|
|
333
|
+
window.removeEventListener("resize", schedule);
|
|
334
|
+
for (const timer of timers) clearTimeout(timer);
|
|
335
|
+
timers = [];
|
|
336
|
+
if (frame) cancelAnimationFrame(frame);
|
|
337
|
+
});
|
|
338
|
+
watch(() => [props.data.length, props.loading], () => nextTick(() => {
|
|
339
|
+
schedule();
|
|
340
|
+
syncThumb();
|
|
341
|
+
}));
|
|
342
|
+
onMounted(() => {
|
|
343
|
+
nextTick(syncThumb);
|
|
344
|
+
window.addEventListener("resize", syncThumb);
|
|
345
|
+
});
|
|
346
|
+
onBeforeUnmount(() => window.removeEventListener("resize", syncThumb));
|
|
347
|
+
watch(fitHeight, () => nextTick(syncThumb));
|
|
348
|
+
}
|
|
349
|
+
const headerRowEl = ref(null);
|
|
350
|
+
const headerHeight = ref(0);
|
|
351
|
+
const rowsOverflow = ref(false);
|
|
352
|
+
const thumbHeight = ref(0);
|
|
353
|
+
const thumbOffset = ref(0);
|
|
354
|
+
const MIN_THUMB_PX = 32;
|
|
355
|
+
const drawnBarSupported = import.meta.client && typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("selector(::-webkit-scrollbar)");
|
|
356
|
+
function syncThumb() {
|
|
357
|
+
const el = scrollEl.value;
|
|
358
|
+
if (!props.scrollBody || !el) return;
|
|
359
|
+
headerHeight.value = headerRowEl.value?.offsetHeight ?? 0;
|
|
360
|
+
const trackHeight = el.clientHeight - headerHeight.value;
|
|
361
|
+
const scrollable = el.scrollHeight - el.clientHeight;
|
|
362
|
+
rowsOverflow.value = drawnBarSupported && scrollable > 1 && trackHeight > MIN_THUMB_PX;
|
|
363
|
+
if (!rowsOverflow.value) return;
|
|
364
|
+
const ratio = el.clientHeight / el.scrollHeight;
|
|
365
|
+
const height = Math.max(Math.round(trackHeight * ratio), MIN_THUMB_PX);
|
|
366
|
+
thumbHeight.value = height;
|
|
367
|
+
thumbOffset.value = Math.round(el.scrollTop / scrollable * (trackHeight - height));
|
|
368
|
+
}
|
|
369
|
+
function onRowsScroll() {
|
|
370
|
+
syncThumb();
|
|
371
|
+
}
|
|
372
|
+
function startThumbDrag(event) {
|
|
373
|
+
const el = scrollEl.value;
|
|
374
|
+
if (!el) return;
|
|
375
|
+
event.preventDefault();
|
|
376
|
+
const startY = event.clientY;
|
|
377
|
+
const startScroll = el.scrollTop;
|
|
378
|
+
const trackHeight = el.clientHeight - headerHeight.value;
|
|
379
|
+
const scrollable = el.scrollHeight - el.clientHeight;
|
|
380
|
+
const travel = trackHeight - thumbHeight.value;
|
|
381
|
+
if (travel <= 0) return;
|
|
382
|
+
const target = event.target;
|
|
383
|
+
target.setPointerCapture(event.pointerId);
|
|
384
|
+
const move = (e) => {
|
|
385
|
+
el.scrollTop = startScroll + (e.clientY - startY) / travel * scrollable;
|
|
386
|
+
};
|
|
387
|
+
const up = (e) => {
|
|
388
|
+
target.releasePointerCapture(e.pointerId);
|
|
389
|
+
target.removeEventListener("pointermove", move);
|
|
390
|
+
target.removeEventListener("pointerup", up);
|
|
391
|
+
target.removeEventListener("pointercancel", up);
|
|
392
|
+
};
|
|
393
|
+
target.addEventListener("pointermove", move);
|
|
394
|
+
target.addEventListener("pointerup", up);
|
|
395
|
+
target.addEventListener("pointercancel", up);
|
|
396
|
+
}
|
|
260
397
|
const scrollStyle = computed(() => {
|
|
261
398
|
if (!props.scrollBody) return void 0;
|
|
399
|
+
if (props.scrollBody === "fit") {
|
|
400
|
+
return fitHeight.value ? { maxHeight: fitHeight.value } : void 0;
|
|
401
|
+
}
|
|
262
402
|
const configured = typeof props.scrollBody === "object" ? props.scrollBody.maxHeight : void 0;
|
|
263
403
|
return { maxHeight: configured ?? "calc(100vh - 22rem)" };
|
|
264
404
|
});
|
|
@@ -276,6 +416,88 @@ function prevPage() {
|
|
|
276
416
|
function nextPage() {
|
|
277
417
|
if (page.value < lastPage.value) page.value += 1;
|
|
278
418
|
}
|
|
419
|
+
const MOVE_COLUMN = "__move";
|
|
420
|
+
const renderColumns = computed(() => props.reorderable ? [{ key: MOVE_COLUMN, label: "", width: "max-content" }, ...visibleColumns.value] : visibleColumns.value);
|
|
421
|
+
const reorderBlocked = computed(() => {
|
|
422
|
+
if (!props.reorderable) return null;
|
|
423
|
+
if (props.reorderDisabled) return props.reorderDisabled;
|
|
424
|
+
if (!props.serverMode && query.value.trim()) return t("shared.datatable.reorder.blocked.search");
|
|
425
|
+
if (Object.values(activeFilters.value).some((v) => v.length)) return t("shared.datatable.reorder.blocked.filter");
|
|
426
|
+
if (props.groupBy) return t("shared.datatable.reorder.blocked.group");
|
|
427
|
+
return null;
|
|
428
|
+
});
|
|
429
|
+
const dragKey = ref(null);
|
|
430
|
+
const dropAt = ref(null);
|
|
431
|
+
const moveStatus = ref("");
|
|
432
|
+
function orderedKeys() {
|
|
433
|
+
return sorted.value.map(rowKeyFor);
|
|
434
|
+
}
|
|
435
|
+
function commitMove(keys, from, to) {
|
|
436
|
+
emit("rowMove", { key: keys[from], from, to, keys: moveKey(keys, from, to) });
|
|
437
|
+
moveStatus.value = t("shared.datatable.reorder.moved", { position: to + 1, total: keys.length });
|
|
438
|
+
}
|
|
439
|
+
function finishDrag() {
|
|
440
|
+
dragKey.value = null;
|
|
441
|
+
dropAt.value = null;
|
|
442
|
+
}
|
|
443
|
+
function onHandleDragStart(event, row) {
|
|
444
|
+
if (reorderBlocked.value) {
|
|
445
|
+
event.preventDefault();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const key = rowKeyFor(row);
|
|
449
|
+
dragKey.value = key;
|
|
450
|
+
if (event.dataTransfer) {
|
|
451
|
+
event.dataTransfer.effectAllowed = "move";
|
|
452
|
+
event.dataTransfer.setData("text/plain", String(key));
|
|
453
|
+
const rowEl = event.target?.closest('[role="row"]');
|
|
454
|
+
if (rowEl) event.dataTransfer.setDragImage(rowEl, 16, 16);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function onRowDragOver(event, row) {
|
|
458
|
+
if (dragKey.value == null) return;
|
|
459
|
+
event.preventDefault();
|
|
460
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
461
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
462
|
+
dropAt.value = { key: rowKeyFor(row), edge: dropEdge(event.clientY, rect.top, rect.height) };
|
|
463
|
+
}
|
|
464
|
+
function onRowDrop(event, row) {
|
|
465
|
+
if (dragKey.value == null) return;
|
|
466
|
+
event.preventDefault();
|
|
467
|
+
const keys = orderedKeys();
|
|
468
|
+
const from = keys.indexOf(dragKey.value);
|
|
469
|
+
const target = keys.indexOf(rowKeyFor(row));
|
|
470
|
+
const edge = dropAt.value?.edge ?? "before";
|
|
471
|
+
finishDrag();
|
|
472
|
+
const to = dropTargetIndex(from, target, edge);
|
|
473
|
+
if (to !== null) commitMove(keys, from, to);
|
|
474
|
+
}
|
|
475
|
+
function onHandleKeydown(event, row) {
|
|
476
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
477
|
+
event.preventDefault();
|
|
478
|
+
if (reorderBlocked.value) return;
|
|
479
|
+
const keys = orderedKeys();
|
|
480
|
+
const key = rowKeyFor(row);
|
|
481
|
+
const from = keys.indexOf(key);
|
|
482
|
+
const to = stepTarget(from, event.key === "ArrowUp" ? -1 : 1, keys.length);
|
|
483
|
+
if (to === null) return;
|
|
484
|
+
commitMove(keys, from, to);
|
|
485
|
+
const handle = event.currentTarget;
|
|
486
|
+
const root = handle.closest('[role="table"]');
|
|
487
|
+
void nextTick(() => setTimeout(() => {
|
|
488
|
+
const again = root?.querySelector(`[data-move-key="${CSS.escape(String(key))}"]`);
|
|
489
|
+
again?.focus();
|
|
490
|
+
}, 0));
|
|
491
|
+
}
|
|
492
|
+
function dropClass(row) {
|
|
493
|
+
const at = dropAt.value;
|
|
494
|
+
if (!at || dragKey.value == null || at.key !== rowKeyFor(row)) return "";
|
|
495
|
+
return at.edge === "before" ? "shadow-[inset_0_2px_0_0_var(--color-primary)]" : "shadow-[inset_0_-2px_0_0_var(--color-primary)]";
|
|
496
|
+
}
|
|
497
|
+
function rowGround(row) {
|
|
498
|
+
if (props.activeRowKey != null && rowKeyFor(row) === props.activeRowKey) return "bg-primary/[0.06]";
|
|
499
|
+
return props.rowClass?.(row) || "bg-gray-50";
|
|
500
|
+
}
|
|
279
501
|
function defaultCell(col, row) {
|
|
280
502
|
const v = col.sortGetter ? col.sortGetter(row) : row[col.key];
|
|
281
503
|
if (v == null) return "\u2014";
|
|
@@ -329,13 +551,16 @@ function defaultCell(col, row) {
|
|
|
329
551
|
variant="outline"
|
|
330
552
|
size="sm"
|
|
331
553
|
class="h-7 shrink-0 gap-1.5 text-sm"
|
|
332
|
-
:class="
|
|
554
|
+
:class="[
|
|
555
|
+
railOpen ? 'border-primary/30 text-primary' : '',
|
|
556
|
+
railLabel ? '' : 'w-7 px-0 justify-center'
|
|
557
|
+
]"
|
|
333
558
|
:aria-pressed="railOpen"
|
|
334
|
-
:aria-label="railOpen ? t('shared.datatable.toolbar.rail.aria.hide', { label: railLabel }) : t('shared.datatable.toolbar.rail.aria.show', { label: railLabel })"
|
|
559
|
+
:aria-label="railLabel ? railOpen ? t('shared.datatable.toolbar.rail.aria.hide', { label: railLabel }) : t('shared.datatable.toolbar.rail.aria.show', { label: railLabel }) : railOpen ? t('shared.datatable.toolbar.rail.aria.hideUnnamed') : t('shared.datatable.toolbar.rail.aria.showUnnamed')"
|
|
335
560
|
@click="railOpen = !railOpen"
|
|
336
561
|
>
|
|
337
562
|
<SidebarMinimalistic :size="14" />
|
|
338
|
-
{{ railLabel }}
|
|
563
|
+
<template v-if="railLabel">{{ railLabel }}</template>
|
|
339
564
|
</Button>
|
|
340
565
|
<!-- Shrinks (never wraps the row) once the toolbar carries scope /
|
|
341
566
|
view-mode / saved-view controls next to it.
|
|
@@ -384,8 +609,20 @@ function defaultCell(col, row) {
|
|
|
384
609
|
<Filter :size="14" weight="BoldDuotone" />
|
|
385
610
|
</template>
|
|
386
611
|
</FilterPanel>
|
|
612
|
+
<!--
|
|
613
|
+
The one filter control, for a surface that owns its own query.
|
|
614
|
+
|
|
615
|
+
Drawn in the same slot as the other two so a surface never shows two
|
|
616
|
+
filter triggers: `facets` and `filter` are alternatives, and
|
|
617
|
+
`filterTabs` is the in-memory path this replaces.
|
|
618
|
+
-->
|
|
619
|
+
<FacetPanel
|
|
620
|
+
v-if="panelFacets.length"
|
|
621
|
+
v-model="facetSelection"
|
|
622
|
+
:facets="facets"
|
|
623
|
+
/>
|
|
387
624
|
<FilterMenu
|
|
388
|
-
v-if="filter"
|
|
625
|
+
v-if="filter && !panelFacets.length"
|
|
389
626
|
:width="typeof filter === 'object' ? filter.width : void 0"
|
|
390
627
|
:active="typeof filter === 'object' ? filter.active : void 0"
|
|
391
628
|
:count="typeof filter === 'object' ? filter.count : void 0"
|
|
@@ -422,23 +659,51 @@ function defaultCell(col, row) {
|
|
|
422
659
|
<EyeClosed :size="16" weight="Linear" />
|
|
423
660
|
</template>
|
|
424
661
|
</ColumnPicker>
|
|
662
|
+
<!--
|
|
663
|
+
Saved views, beside the columns picker rather than out past the market
|
|
664
|
+
scope and the view-mode switch.
|
|
665
|
+
|
|
666
|
+
It belongs with what it saves. Filters, sort and columns are the three
|
|
667
|
+
things a view is made of, so the control that names one sits at the end
|
|
668
|
+
of that group — not after the market scope, which is not part of a
|
|
669
|
+
view, or the view-mode switch, which is about what you are looking at.
|
|
670
|
+
-->
|
|
671
|
+
<SavedViewsMenu v-if="savedViews" :config="savedViews" />
|
|
425
672
|
<!-- Market scope — the surface passes the shared MarketScopeToggle in;
|
|
426
673
|
the row position is fixed here. -->
|
|
427
674
|
<slot name="scope" />
|
|
428
675
|
<!-- The editor's device switch, which is the right shape wherever the
|
|
429
676
|
choice changes what you are LOOKING AT rather than which rows. -->
|
|
430
677
|
<SegmentedToggle v-if="viewModes?.length" v-model="viewMode" variant="inset">
|
|
678
|
+
<!--
|
|
679
|
+
Violet on hover, and violet while it is the one in effect — the
|
|
680
|
+
same language the filter trigger, the column picker and the saved
|
|
681
|
+
views speak two controls to the left. The track's own variant only
|
|
682
|
+
lifts the active segment onto a white pill, which says WHICH is
|
|
683
|
+
picked but not that the row is a set of live controls.
|
|
684
|
+
|
|
685
|
+
The hover is written against `data-[state=off]` and marked
|
|
686
|
+
important because the design system's own toggle already ships
|
|
687
|
+
`data-[state=off]:hover:text-foreground`. A plain `hover:` loses to
|
|
688
|
+
it on specificity, and matching the specificity alone would leave
|
|
689
|
+
the winner to whatever order Tailwind happened to emit the two
|
|
690
|
+
utilities in — a coin toss that reads as a bug the day it lands the
|
|
691
|
+
other way. Delete the `!` if the toggle's own hover ever goes violet.
|
|
692
|
+
|
|
693
|
+
`aria-label` because these are icon-only: the mode still has to
|
|
694
|
+
announce itself.
|
|
695
|
+
-->
|
|
431
696
|
<SegmentedToggleItem
|
|
432
697
|
v-for="m in viewModes"
|
|
433
698
|
:key="m.value"
|
|
434
699
|
:value="m.value"
|
|
435
700
|
:aria-label="m.label"
|
|
701
|
+
class="transition-colors data-[state=on]:text-primary data-[state=off]:hover:text-primary!"
|
|
436
702
|
>
|
|
437
703
|
<component :is="m.icon" v-if="m.icon" :size="14" weight="Bold" />
|
|
438
704
|
<template v-if="!m.icon">{{ m.label }}</template>
|
|
439
705
|
</SegmentedToggleItem>
|
|
440
706
|
</SegmentedToggle>
|
|
441
|
-
<SavedViewsMenu v-if="savedViews" :config="savedViews" />
|
|
442
707
|
<slot name="toolbar-left" />
|
|
443
708
|
</div>
|
|
444
709
|
<div class="flex items-center gap-2 ml-auto shrink-0">
|
|
@@ -447,15 +712,40 @@ function defaultCell(col, row) {
|
|
|
447
712
|
</div>
|
|
448
713
|
</div>
|
|
449
714
|
|
|
715
|
+
<!--
|
|
716
|
+
What is filtering right now. Not optional beside a paned panel: with it
|
|
717
|
+
closed, a count on an icon says that something narrows the list and never
|
|
718
|
+
what.
|
|
719
|
+
-->
|
|
720
|
+
<FacetChips
|
|
721
|
+
v-if="facets.length"
|
|
722
|
+
v-model="facetSelection"
|
|
723
|
+
:facets="facets"
|
|
724
|
+
:show-facet="chipFacetLabels"
|
|
725
|
+
/>
|
|
726
|
+
|
|
450
727
|
<!-- Alternate view mode (gallery, board, …) — same toolbar, same footer. -->
|
|
451
728
|
<div v-if="!showTable" class="border-t border-black/[0.04] bg-white/55">
|
|
452
729
|
<slot name="view" :rows="displayedRows" :loading="loading" />
|
|
453
730
|
</div>
|
|
454
731
|
|
|
455
|
-
<!--
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
732
|
+
<!-- Two boxes: one that scrolls, and the table itself.
|
|
733
|
+
|
|
734
|
+
The table is ONE GRID and the rows are subgrids of it (`SUBGRID_ROW`),
|
|
735
|
+
which is what keeps a heading over its own column. It used to be a grid
|
|
736
|
+
per row, and a content-sized track then measured itself separately in
|
|
737
|
+
each one — see `SUBGRID_ROW` for what that did to every list with row
|
|
738
|
+
actions.
|
|
739
|
+
|
|
740
|
+
The scrolling is the outer box's job, and it has to be, because the
|
|
741
|
+
grid carries `min-w-min`: a box that grows to its own content cannot
|
|
742
|
+
also be the thing that scrolls it. The panel around them clips
|
|
743
|
+
(`overflow-hidden`, for the rounded corners), so without this a table
|
|
744
|
+
whose declared widths outgrow the panel simply lost the excess.
|
|
745
|
+
|
|
746
|
+
The inner element used to be `display: contents` when scrolling was
|
|
747
|
+
off, which made it vanish from layout — and a box that is not in the
|
|
748
|
+
layout can be neither a grid nor a scroller.
|
|
459
749
|
|
|
460
750
|
IT IS ALSO THE TABLE. The box that holds the header and the rows and
|
|
461
751
|
nothing else is the only element in this component that can carry the
|
|
@@ -470,20 +760,33 @@ function defaultCell(col, row) {
|
|
|
470
760
|
that. A static table read with a screen reader's own table commands is
|
|
471
761
|
what these lists actually are, and both roles announce the column
|
|
472
762
|
heading with the cell, which is the thing that was missing. -->
|
|
763
|
+
<div :class="scrollBody ? 'relative flex min-h-0 flex-col' : ''">
|
|
473
764
|
<div
|
|
474
|
-
|
|
475
|
-
:
|
|
476
|
-
:class="scrollBody ? 'min-h-0 overflow-y-auto' : 'contents'"
|
|
765
|
+
ref="scrollEl"
|
|
766
|
+
:class="scrollBody ? 'rx-rows min-h-0 overflow-auto' : 'overflow-x-auto'"
|
|
477
767
|
:style="scrollStyle"
|
|
768
|
+
@scroll="onRowsScroll"
|
|
478
769
|
>
|
|
770
|
+
<div
|
|
771
|
+
:role="showTable ? 'table' : void 0"
|
|
772
|
+
:aria-busy="showTable && loading ? 'true' : void 0"
|
|
773
|
+
:class="showTable ? 'grid min-w-min gap-x-2' : ''"
|
|
774
|
+
:style="showTable ? gridTemplate : void 0"
|
|
775
|
+
>
|
|
479
776
|
<!-- Header row -------------------------------------------------- -->
|
|
480
777
|
<div
|
|
481
778
|
v-if="showTable"
|
|
779
|
+
ref="headerRowEl"
|
|
482
780
|
role="row"
|
|
483
|
-
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"
|
|
781
|
+
class="grid items-center gap-x-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"
|
|
484
782
|
:class="scrollBody ? 'sticky top-0 z-10' : ''"
|
|
485
|
-
:style="
|
|
783
|
+
:style="SUBGRID_ROW"
|
|
486
784
|
>
|
|
785
|
+
<!-- The handle column's heading: nothing to see, but a columnheader the
|
|
786
|
+
handles' cells can be named by. -->
|
|
787
|
+
<div v-if="reorderable" role="columnheader" class="w-6">
|
|
788
|
+
<span class="sr-only">{{ t("shared.datatable.reorder.column") }}</span>
|
|
789
|
+
</div>
|
|
487
790
|
<!-- `columnheader` is what names every cell underneath it, so this is
|
|
488
791
|
the half of the fix that does the work. `aria-sort` goes with it:
|
|
489
792
|
a heading that carries a sort button and says nothing about the
|
|
@@ -494,7 +797,9 @@ function defaultCell(col, row) {
|
|
|
494
797
|
:key="col.key"
|
|
495
798
|
role="columnheader"
|
|
496
799
|
:aria-sort="ariaSortFor(col)"
|
|
497
|
-
:
|
|
800
|
+
:title="col.label || void 0"
|
|
801
|
+
class="min-w-0"
|
|
802
|
+
:class="col.alignRight ? RIGHT_ALIGNED : ''"
|
|
498
803
|
>
|
|
499
804
|
<!-- `#header-<key>` lets a page take over a header cell (e.g. a
|
|
500
805
|
select-all checkbox in a selection column). Default: sortable
|
|
@@ -514,7 +819,7 @@ function defaultCell(col, row) {
|
|
|
514
819
|
the row's uppercase. Without it a column reads "LAST CHECK"
|
|
515
820
|
beside "Channel" and "Provider" for no reason a reader can see —
|
|
516
821
|
the header shouts wherever a column happens not to be sortable. -->
|
|
517
|
-
<span v-else class="normal-case">{{ col.label }}</span>
|
|
822
|
+
<span v-else class="block truncate normal-case">{{ col.label }}</span>
|
|
518
823
|
</slot>
|
|
519
824
|
</div>
|
|
520
825
|
</div>
|
|
@@ -527,9 +832,12 @@ function defaultCell(col, row) {
|
|
|
527
832
|
<div
|
|
528
833
|
v-if="entry.kind === 'header'"
|
|
529
834
|
role="row"
|
|
530
|
-
class="border-t border-black/[0.04] bg-gray-100/70 px-4 py-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground"
|
|
835
|
+
class="col-[1/-1] border-t border-black/[0.04] bg-gray-100/70 px-4 py-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground"
|
|
531
836
|
>
|
|
532
|
-
|
|
837
|
+
<!-- Spans every column like the rows do, so a table wide enough to
|
|
838
|
+
scroll does not show this band stopping short of the rows it
|
|
839
|
+
groups. No subgrid: it holds one cell, not a set of columns. -->
|
|
840
|
+
<div role="cell" :aria-colspan="renderColumns.length" class="flex items-center gap-2">
|
|
533
841
|
{{ entry.label }}
|
|
534
842
|
<span class="tabular-nums normal-case text-muted-foreground/70">{{ entry.count }}</span>
|
|
535
843
|
</div>
|
|
@@ -537,16 +845,40 @@ function defaultCell(col, row) {
|
|
|
537
845
|
<div
|
|
538
846
|
v-else
|
|
539
847
|
role="row"
|
|
540
|
-
class="group grid items-center gap-2 border-t border-black/[0.04] px-4 py-3 cursor-pointer
|
|
541
|
-
:
|
|
848
|
+
class="group grid items-center gap-x-2 border-t border-black/[0.04] px-4 py-3 cursor-pointer transition-colors"
|
|
849
|
+
:class="[rowGround(entry.row), dropClass(entry.row), dragKey != null && dragKey === rowKeyFor(entry.row) ? 'opacity-50' : '']"
|
|
850
|
+
:aria-current="activeRowKey != null && rowKeyFor(entry.row) === activeRowKey ? 'true' : void 0"
|
|
851
|
+
:style="SUBGRID_ROW"
|
|
542
852
|
@click="emit('rowClick', entry.row)"
|
|
853
|
+
@dragover="onRowDragOver($event, entry.row)"
|
|
854
|
+
@drop="onRowDrop($event, entry.row)"
|
|
543
855
|
>
|
|
856
|
+
<!-- The grip. A button, so it is reachable and the arrow keys can move
|
|
857
|
+
the row; `draggable` on it rather than on the row, so selecting a
|
|
858
|
+
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>
|
|
860
|
+
<button
|
|
861
|
+
type="button"
|
|
862
|
+
:data-move-key="String(rowKeyFor(entry.row))"
|
|
863
|
+
:draggable="!reorderBlocked"
|
|
864
|
+
:aria-disabled="reorderBlocked ? 'true' : void 0"
|
|
865
|
+
:aria-label="t('shared.datatable.reorder.handle')"
|
|
866
|
+
:title="reorderBlocked ?? t('shared.datatable.reorder.hint')"
|
|
867
|
+
class="inline-flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors"
|
|
868
|
+
:class="reorderBlocked ? 'cursor-not-allowed opacity-30' : 'cursor-grab hover:bg-muted hover:text-primary active:cursor-grabbing'"
|
|
869
|
+
@dragstart="onHandleDragStart($event, entry.row)"
|
|
870
|
+
@dragend="finishDrag"
|
|
871
|
+
@keydown="onHandleKeydown($event, entry.row)"
|
|
872
|
+
>
|
|
873
|
+
<DragHandle :size="14" weight="Linear" />
|
|
874
|
+
</button>
|
|
875
|
+
</div>
|
|
544
876
|
<div
|
|
545
877
|
v-for="col in visibleColumns"
|
|
546
878
|
:key="col.key"
|
|
547
879
|
role="cell"
|
|
548
880
|
class="min-w-0"
|
|
549
|
-
:class="col.alignRight ?
|
|
881
|
+
:class="col.alignRight ? RIGHT_ALIGNED : ''"
|
|
550
882
|
>
|
|
551
883
|
<slot :name="`cell-${col.key}`" :row="entry.row">
|
|
552
884
|
<!-- Primary column: bold record anchor + optional tinted icon tile. -->
|
|
@@ -578,11 +910,11 @@ function defaultCell(col, row) {
|
|
|
578
910
|
v-for="n in 6"
|
|
579
911
|
:key="`sk-${n}`"
|
|
580
912
|
aria-hidden="true"
|
|
581
|
-
class="grid items-center gap-2 border-t border-black/[0.04] bg-gray-50 px-4 py-3"
|
|
582
|
-
:style="
|
|
913
|
+
class="grid items-center gap-x-2 border-t border-black/[0.04] bg-gray-50 px-4 py-3"
|
|
914
|
+
:style="SUBGRID_ROW"
|
|
583
915
|
>
|
|
584
916
|
<div
|
|
585
|
-
v-for="col in
|
|
917
|
+
v-for="col in renderColumns"
|
|
586
918
|
:key="col.key"
|
|
587
919
|
:class="col.alignRight ? 'flex justify-end' : ''"
|
|
588
920
|
>
|
|
@@ -598,21 +930,50 @@ function defaultCell(col, row) {
|
|
|
598
930
|
<div
|
|
599
931
|
v-if="showTable && !displayedRows.length && !loading"
|
|
600
932
|
role="row"
|
|
601
|
-
class="px-3 py-8 text-center text-sm text-muted-foreground"
|
|
933
|
+
class="col-[1/-1] px-3 py-8 text-center text-sm text-muted-foreground"
|
|
602
934
|
>
|
|
603
|
-
<div role="cell" :aria-colspan="
|
|
604
|
-
<slot name="empty">
|
|
935
|
+
<div role="cell" :aria-colspan="renderColumns.length">
|
|
936
|
+
<slot name="empty" :query="query" :filters="activeNarrowing" :clear="clearNarrowing">
|
|
605
937
|
{{ emptyText ?? t("shared.datatable.empty.default") }}
|
|
606
938
|
</slot>
|
|
607
939
|
</div>
|
|
608
940
|
</div>
|
|
609
941
|
|
|
942
|
+
</div>
|
|
610
943
|
</div>
|
|
611
944
|
|
|
945
|
+
<!-- The rows' scrollbar ---------------------------------------- -->
|
|
946
|
+
<!-- Drawn, not borrowed. A browser's own bar belongs to the scrolling
|
|
947
|
+
box, and the column head is inside that box — it has to be, because
|
|
948
|
+
the head and the rows are one grid and a `max-content` track resolves
|
|
949
|
+
per grid (PE-610). So the native bar runs beside the head, which is
|
|
950
|
+
not where a bar over the ROWS belongs. This one starts under the
|
|
951
|
+
head, ends at the footer, and drives the same native scrolling. -->
|
|
952
|
+
<div
|
|
953
|
+
v-if="scrollBody && rowsOverflow"
|
|
954
|
+
class="pointer-events-none absolute right-0 z-20 w-3.5"
|
|
955
|
+
:style="{ top: `${headerHeight}px`, bottom: '0px' }"
|
|
956
|
+
>
|
|
957
|
+
<div
|
|
958
|
+
class="rx-thumb pointer-events-auto absolute right-[3px] w-2 cursor-default rounded-full bg-[rgb(11_5_34_/_0.18)] transition-colors hover:bg-[rgb(11_5_34_/_0.32)]"
|
|
959
|
+
:style="{ height: `${thumbHeight}px`, transform: `translateY(${thumbOffset}px)` }"
|
|
960
|
+
@pointerdown="startThumbDrag"
|
|
961
|
+
/>
|
|
962
|
+
</div>
|
|
963
|
+
</div>
|
|
964
|
+
|
|
965
|
+
<!-- Result count, spoken. Outside the table role: a live region is not
|
|
966
|
+
a row, and the table may own rows only. -->
|
|
967
|
+
<span role="status" class="sr-only">{{ liveStatus }}</span>
|
|
968
|
+
<!-- A move, said once: a reader who stepped a row with the arrow keys
|
|
969
|
+
hears where it landed. -->
|
|
970
|
+
<span v-if="reorderable" role="status" class="sr-only">{{ moveStatus }}</span>
|
|
971
|
+
|
|
612
972
|
<!-- Footer ------------------------------------------------------- -->
|
|
613
973
|
<div
|
|
614
974
|
v-if="showFooter"
|
|
615
|
-
|
|
975
|
+
ref="footerEl"
|
|
976
|
+
class="flex items-center justify-between gap-2 border-t border-gray-200 bg-white/55 backdrop-blur-md px-4 py-3 text-sm text-muted-foreground"
|
|
616
977
|
>
|
|
617
978
|
<div class="flex items-center gap-2.5">
|
|
618
979
|
<span class="tabular-nums">
|
|
@@ -634,7 +995,7 @@ function defaultCell(col, row) {
|
|
|
634
995
|
<Button
|
|
635
996
|
variant="outline"
|
|
636
997
|
size="sm"
|
|
637
|
-
class="h-7 text-sm"
|
|
998
|
+
class="h-7 text-sm disabled:opacity-100 disabled:text-muted-foreground"
|
|
638
999
|
:disabled="page === 0 || loading"
|
|
639
1000
|
@click="prevPage"
|
|
640
1001
|
>
|
|
@@ -646,7 +1007,7 @@ function defaultCell(col, row) {
|
|
|
646
1007
|
<Button
|
|
647
1008
|
variant="outline"
|
|
648
1009
|
size="sm"
|
|
649
|
-
class="h-7 text-sm"
|
|
1010
|
+
class="h-7 text-sm disabled:opacity-100 disabled:text-muted-foreground"
|
|
650
1011
|
:disabled="page >= lastPage || loading"
|
|
651
1012
|
@click="nextPage"
|
|
652
1013
|
>
|
|
@@ -656,3 +1017,7 @@ function defaultCell(col, row) {
|
|
|
656
1017
|
</div>
|
|
657
1018
|
</div>
|
|
658
1019
|
</template>
|
|
1020
|
+
|
|
1021
|
+
<style scoped>
|
|
1022
|
+
.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
|
+
</style>
|