@revenexx/studio-shared 0.3.2 → 0.3.3
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 +4 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +15 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue +39 -0
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +15 -0
- package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +57 -0
- package/dist/runtime/components/Dashboard/DashboardControls.vue +209 -0
- package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +57 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +16 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.vue +87 -0
- package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +16 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue +61 -0
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +14 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue +100 -0
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +14 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue +155 -0
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +59 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue +79 -0
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +59 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +42 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue +153 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +42 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue +80 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +27 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +6 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue +30 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +6 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +8 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue +13 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +8 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +23 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue +140 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +23 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +22 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue +124 -0
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +22 -0
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +36 -3
- package/dist/runtime/components/DataTable/DataTable.vue +252 -27
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +36 -3
- package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +16 -0
- package/dist/runtime/components/DataTable/FacetChips.vue +72 -0
- package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +16 -0
- package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +20 -0
- package/dist/runtime/components/DataTable/FacetPanel.vue +359 -0
- package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +20 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +5 -1
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +83 -63
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +5 -1
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +5 -1
- package/dist/runtime/components/DataTable/SortMenu.vue +40 -12
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +5 -1
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +2 -2
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +2 -2
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +2 -2
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +2 -2
- package/dist/runtime/components/Shared/SortHeader.vue +6 -2
- package/dist/runtime/components/Shared/TabBar.d.vue.ts +23 -7
- package/dist/runtime/components/Shared/TabBar.vue +3 -2
- package/dist/runtime/components/Shared/TabBar.vue.d.ts +23 -7
- package/dist/runtime/composables/useDashboards.d.ts +27 -0
- package/dist/runtime/composables/useDashboards.js +143 -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/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/facets.d.ts +200 -0
- package/dist/runtime/lib/facets.js +171 -0
- package/dist/runtime/lib/gridTracks.d.ts +55 -0
- package/dist/runtime/lib/gridTracks.js +29 -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 +8 -0
- package/package.json +3 -2
|
@@ -19,7 +19,10 @@ import {
|
|
|
19
19
|
import { orderColumnsBy } from "../../lib/columnOrder";
|
|
20
20
|
import { initiallyHidden, visibleColumnsOf } from "../../lib/columnVisibility";
|
|
21
21
|
import { countByValue, matchesFilter } from "../../lib/dataTableFilters";
|
|
22
|
+
import { gridTemplateColumns } from "../../lib/gridTracks";
|
|
22
23
|
import SortHeader from "../Shared/SortHeader.vue";
|
|
24
|
+
import FacetChips from "./FacetChips.vue";
|
|
25
|
+
import FacetPanel from "./FacetPanel.vue";
|
|
23
26
|
import FilterMenu from "./FilterMenu.vue";
|
|
24
27
|
import SavedViewsMenu from "./SavedViewsMenu.vue";
|
|
25
28
|
import SortMenu from "./SortMenu.vue";
|
|
@@ -31,10 +34,12 @@ const props = defineProps({
|
|
|
31
34
|
columnsMenu: { type: Boolean, required: false, default: true },
|
|
32
35
|
filter: { type: [Boolean, Object], required: false, default: false },
|
|
33
36
|
filterTabs: { type: [Object, Array], required: false },
|
|
37
|
+
facets: { type: Array, required: false, default: () => [] },
|
|
38
|
+
chipFacetLabels: { type: Boolean, required: false, default: false },
|
|
34
39
|
savedViews: { type: [Object, null], required: false, default: null },
|
|
35
40
|
viewModes: { type: [Array, null], required: false, default: null },
|
|
36
41
|
rail: { type: [Boolean, Object], required: false, default: false },
|
|
37
|
-
scrollBody: { type: [Boolean, Object], required: false },
|
|
42
|
+
scrollBody: { type: [Boolean, String, Object], required: false },
|
|
38
43
|
pagination: { type: [Boolean, Object], required: false, default: true },
|
|
39
44
|
defaultSort: { type: Object, required: false },
|
|
40
45
|
serverMode: { type: Boolean, required: false, default: false },
|
|
@@ -56,6 +61,8 @@ const sort = defineModel("sort", { type: Object, ...{
|
|
|
56
61
|
const page = defineModel("page", { type: Number, ...{ default: 0 } });
|
|
57
62
|
const viewMode = defineModel("viewMode", { type: String, ...{ default: "list" } });
|
|
58
63
|
const railOpen = defineModel("railOpen", { type: Boolean, ...{ default: true } });
|
|
64
|
+
const facetSelection = defineModel("facetSelection", { type: Object, ...{ default: () => ({}) } });
|
|
65
|
+
const panelFacets = computed(() => props.facets);
|
|
59
66
|
if (props.defaultSort && sort.value.column == null) {
|
|
60
67
|
sort.value = { column: props.defaultSort.column, direction: props.defaultSort.direction };
|
|
61
68
|
}
|
|
@@ -233,10 +240,8 @@ function rowKeyFor(row) {
|
|
|
233
240
|
if (typeof props.rowKey === "function") return props.rowKey(row);
|
|
234
241
|
return row[props.rowKey];
|
|
235
242
|
}
|
|
236
|
-
const gridTemplate = computed(() => {
|
|
237
|
-
|
|
238
|
-
return `grid-template-columns: ${tracks.join(" ")};`;
|
|
239
|
-
});
|
|
243
|
+
const gridTemplate = computed(() => `grid-template-columns: ${gridTemplateColumns(visibleColumns.value.map((c) => c.width))};`);
|
|
244
|
+
const SUBGRID_ROW = "grid-column: 1 / -1; grid-template-columns: subgrid;";
|
|
240
245
|
const placeholder = computed(
|
|
241
246
|
() => typeof props.search === "object" ? props.search.placeholder ?? t("shared.datatable.toolbar.search.placeholder.default") : t("shared.datatable.toolbar.search.placeholder.default")
|
|
242
247
|
);
|
|
@@ -249,7 +254,7 @@ const showToolbar = computed(
|
|
|
249
254
|
() => 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
255
|
);
|
|
251
256
|
const railLabel = computed(
|
|
252
|
-
() => (typeof props.rail === "object" ? props.rail.label : void 0) ??
|
|
257
|
+
() => (typeof props.rail === "object" ? props.rail.label : void 0) ?? null
|
|
253
258
|
);
|
|
254
259
|
const showTable = computed(() => !props.viewModes?.length || viewMode.value === "list");
|
|
255
260
|
const sortableColumns = computed(() => props.columns.filter((c) => c.sortable));
|
|
@@ -257,8 +262,121 @@ const showSortMenu = computed(() => !showTable.value && sortableColumns.value.le
|
|
|
257
262
|
function cellIconFor(col) {
|
|
258
263
|
return col.cellIcon ?? null;
|
|
259
264
|
}
|
|
265
|
+
const scrollEl = ref(null);
|
|
266
|
+
const footerEl = ref(null);
|
|
267
|
+
const fitHeight = ref(null);
|
|
268
|
+
const MIN_FIT_PX = 220;
|
|
269
|
+
const BOTTOM_GAP_PX = 16;
|
|
270
|
+
function scrollParent(el) {
|
|
271
|
+
let node = el.parentElement;
|
|
272
|
+
while (node && node !== document.body) {
|
|
273
|
+
const overflow = getComputedStyle(node).overflowY;
|
|
274
|
+
if (overflow === "auto" || overflow === "scroll") return node;
|
|
275
|
+
node = node.parentElement;
|
|
276
|
+
}
|
|
277
|
+
return document.documentElement;
|
|
278
|
+
}
|
|
279
|
+
function measureFit() {
|
|
280
|
+
if (props.scrollBody !== "fit" || !import.meta.client) return;
|
|
281
|
+
const el = scrollEl.value;
|
|
282
|
+
if (!el) return;
|
|
283
|
+
const container = scrollParent(el);
|
|
284
|
+
const offset = el.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
|
285
|
+
const reserve = (footerEl.value?.offsetHeight ?? 0) + BOTTOM_GAP_PX;
|
|
286
|
+
const overflow = Math.max(container.scrollHeight - container.clientHeight, 0);
|
|
287
|
+
const available = Math.max(
|
|
288
|
+
Math.floor(container.clientHeight - offset - reserve - overflow) - 1,
|
|
289
|
+
MIN_FIT_PX
|
|
290
|
+
);
|
|
291
|
+
const current = fitHeight.value ? Number.parseInt(fitHeight.value, 10) : null;
|
|
292
|
+
if (current !== null && Math.abs(current - available) < 8) return;
|
|
293
|
+
fitHeight.value = `${available}px`;
|
|
294
|
+
}
|
|
295
|
+
if (import.meta.client) {
|
|
296
|
+
let frame = 0;
|
|
297
|
+
let timers = [];
|
|
298
|
+
const schedule = () => {
|
|
299
|
+
if (frame) return;
|
|
300
|
+
frame = requestAnimationFrame(() => {
|
|
301
|
+
frame = 0;
|
|
302
|
+
measureFit();
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
onMounted(() => {
|
|
306
|
+
measureFit();
|
|
307
|
+
window.addEventListener("resize", schedule);
|
|
308
|
+
timers = [setTimeout(schedule, 250), setTimeout(schedule, 1200)];
|
|
309
|
+
});
|
|
310
|
+
onBeforeUnmount(() => {
|
|
311
|
+
window.removeEventListener("resize", schedule);
|
|
312
|
+
for (const timer of timers) clearTimeout(timer);
|
|
313
|
+
timers = [];
|
|
314
|
+
if (frame) cancelAnimationFrame(frame);
|
|
315
|
+
});
|
|
316
|
+
watch(() => [props.data.length, props.loading], () => nextTick(() => {
|
|
317
|
+
schedule();
|
|
318
|
+
syncThumb();
|
|
319
|
+
}));
|
|
320
|
+
onMounted(() => {
|
|
321
|
+
nextTick(syncThumb);
|
|
322
|
+
window.addEventListener("resize", syncThumb);
|
|
323
|
+
});
|
|
324
|
+
onBeforeUnmount(() => window.removeEventListener("resize", syncThumb));
|
|
325
|
+
watch(fitHeight, () => nextTick(syncThumb));
|
|
326
|
+
}
|
|
327
|
+
const headerRowEl = ref(null);
|
|
328
|
+
const headerHeight = ref(0);
|
|
329
|
+
const rowsOverflow = ref(false);
|
|
330
|
+
const thumbHeight = ref(0);
|
|
331
|
+
const thumbOffset = ref(0);
|
|
332
|
+
const MIN_THUMB_PX = 32;
|
|
333
|
+
const drawnBarSupported = import.meta.client && typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("selector(::-webkit-scrollbar)");
|
|
334
|
+
function syncThumb() {
|
|
335
|
+
const el = scrollEl.value;
|
|
336
|
+
if (!props.scrollBody || !el) return;
|
|
337
|
+
headerHeight.value = headerRowEl.value?.offsetHeight ?? 0;
|
|
338
|
+
const trackHeight = el.clientHeight - headerHeight.value;
|
|
339
|
+
const scrollable = el.scrollHeight - el.clientHeight;
|
|
340
|
+
rowsOverflow.value = drawnBarSupported && scrollable > 1 && trackHeight > MIN_THUMB_PX;
|
|
341
|
+
if (!rowsOverflow.value) return;
|
|
342
|
+
const ratio = el.clientHeight / el.scrollHeight;
|
|
343
|
+
const height = Math.max(Math.round(trackHeight * ratio), MIN_THUMB_PX);
|
|
344
|
+
thumbHeight.value = height;
|
|
345
|
+
thumbOffset.value = Math.round(el.scrollTop / scrollable * (trackHeight - height));
|
|
346
|
+
}
|
|
347
|
+
function onRowsScroll() {
|
|
348
|
+
syncThumb();
|
|
349
|
+
}
|
|
350
|
+
function startThumbDrag(event) {
|
|
351
|
+
const el = scrollEl.value;
|
|
352
|
+
if (!el) return;
|
|
353
|
+
event.preventDefault();
|
|
354
|
+
const startY = event.clientY;
|
|
355
|
+
const startScroll = el.scrollTop;
|
|
356
|
+
const trackHeight = el.clientHeight - headerHeight.value;
|
|
357
|
+
const scrollable = el.scrollHeight - el.clientHeight;
|
|
358
|
+
const travel = trackHeight - thumbHeight.value;
|
|
359
|
+
if (travel <= 0) return;
|
|
360
|
+
const target = event.target;
|
|
361
|
+
target.setPointerCapture(event.pointerId);
|
|
362
|
+
const move = (e) => {
|
|
363
|
+
el.scrollTop = startScroll + (e.clientY - startY) / travel * scrollable;
|
|
364
|
+
};
|
|
365
|
+
const up = (e) => {
|
|
366
|
+
target.releasePointerCapture(e.pointerId);
|
|
367
|
+
target.removeEventListener("pointermove", move);
|
|
368
|
+
target.removeEventListener("pointerup", up);
|
|
369
|
+
target.removeEventListener("pointercancel", up);
|
|
370
|
+
};
|
|
371
|
+
target.addEventListener("pointermove", move);
|
|
372
|
+
target.addEventListener("pointerup", up);
|
|
373
|
+
target.addEventListener("pointercancel", up);
|
|
374
|
+
}
|
|
260
375
|
const scrollStyle = computed(() => {
|
|
261
376
|
if (!props.scrollBody) return void 0;
|
|
377
|
+
if (props.scrollBody === "fit") {
|
|
378
|
+
return fitHeight.value ? { maxHeight: fitHeight.value } : void 0;
|
|
379
|
+
}
|
|
262
380
|
const configured = typeof props.scrollBody === "object" ? props.scrollBody.maxHeight : void 0;
|
|
263
381
|
return { maxHeight: configured ?? "calc(100vh - 22rem)" };
|
|
264
382
|
});
|
|
@@ -329,13 +447,16 @@ function defaultCell(col, row) {
|
|
|
329
447
|
variant="outline"
|
|
330
448
|
size="sm"
|
|
331
449
|
class="h-7 shrink-0 gap-1.5 text-sm"
|
|
332
|
-
:class="
|
|
450
|
+
:class="[
|
|
451
|
+
railOpen ? 'border-primary/30 text-primary' : '',
|
|
452
|
+
railLabel ? '' : 'w-7 px-0 justify-center'
|
|
453
|
+
]"
|
|
333
454
|
: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 })"
|
|
455
|
+
: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
456
|
@click="railOpen = !railOpen"
|
|
336
457
|
>
|
|
337
458
|
<SidebarMinimalistic :size="14" />
|
|
338
|
-
{{ railLabel }}
|
|
459
|
+
<template v-if="railLabel">{{ railLabel }}</template>
|
|
339
460
|
</Button>
|
|
340
461
|
<!-- Shrinks (never wraps the row) once the toolbar carries scope /
|
|
341
462
|
view-mode / saved-view controls next to it.
|
|
@@ -384,8 +505,20 @@ function defaultCell(col, row) {
|
|
|
384
505
|
<Filter :size="14" weight="BoldDuotone" />
|
|
385
506
|
</template>
|
|
386
507
|
</FilterPanel>
|
|
508
|
+
<!--
|
|
509
|
+
The one filter control, for a surface that owns its own query.
|
|
510
|
+
|
|
511
|
+
Drawn in the same slot as the other two so a surface never shows two
|
|
512
|
+
filter triggers: `facets` and `filter` are alternatives, and
|
|
513
|
+
`filterTabs` is the in-memory path this replaces.
|
|
514
|
+
-->
|
|
515
|
+
<FacetPanel
|
|
516
|
+
v-if="panelFacets.length"
|
|
517
|
+
v-model="facetSelection"
|
|
518
|
+
:facets="facets"
|
|
519
|
+
/>
|
|
387
520
|
<FilterMenu
|
|
388
|
-
v-if="filter"
|
|
521
|
+
v-if="filter && !panelFacets.length"
|
|
389
522
|
:width="typeof filter === 'object' ? filter.width : void 0"
|
|
390
523
|
:active="typeof filter === 'object' ? filter.active : void 0"
|
|
391
524
|
:count="typeof filter === 'object' ? filter.count : void 0"
|
|
@@ -422,23 +555,51 @@ function defaultCell(col, row) {
|
|
|
422
555
|
<EyeClosed :size="16" weight="Linear" />
|
|
423
556
|
</template>
|
|
424
557
|
</ColumnPicker>
|
|
558
|
+
<!--
|
|
559
|
+
Saved views, beside the columns picker rather than out past the market
|
|
560
|
+
scope and the view-mode switch.
|
|
561
|
+
|
|
562
|
+
It belongs with what it saves. Filters, sort and columns are the three
|
|
563
|
+
things a view is made of, so the control that names one sits at the end
|
|
564
|
+
of that group — not after the market scope, which is not part of a
|
|
565
|
+
view, or the view-mode switch, which is about what you are looking at.
|
|
566
|
+
-->
|
|
567
|
+
<SavedViewsMenu v-if="savedViews" :config="savedViews" />
|
|
425
568
|
<!-- Market scope — the surface passes the shared MarketScopeToggle in;
|
|
426
569
|
the row position is fixed here. -->
|
|
427
570
|
<slot name="scope" />
|
|
428
571
|
<!-- The editor's device switch, which is the right shape wherever the
|
|
429
572
|
choice changes what you are LOOKING AT rather than which rows. -->
|
|
430
573
|
<SegmentedToggle v-if="viewModes?.length" v-model="viewMode" variant="inset">
|
|
574
|
+
<!--
|
|
575
|
+
Violet on hover, and violet while it is the one in effect — the
|
|
576
|
+
same language the filter trigger, the column picker and the saved
|
|
577
|
+
views speak two controls to the left. The track's own variant only
|
|
578
|
+
lifts the active segment onto a white pill, which says WHICH is
|
|
579
|
+
picked but not that the row is a set of live controls.
|
|
580
|
+
|
|
581
|
+
The hover is written against `data-[state=off]` and marked
|
|
582
|
+
important because the design system's own toggle already ships
|
|
583
|
+
`data-[state=off]:hover:text-foreground`. A plain `hover:` loses to
|
|
584
|
+
it on specificity, and matching the specificity alone would leave
|
|
585
|
+
the winner to whatever order Tailwind happened to emit the two
|
|
586
|
+
utilities in — a coin toss that reads as a bug the day it lands the
|
|
587
|
+
other way. Delete the `!` if the toggle's own hover ever goes violet.
|
|
588
|
+
|
|
589
|
+
`aria-label` because these are icon-only: the mode still has to
|
|
590
|
+
announce itself.
|
|
591
|
+
-->
|
|
431
592
|
<SegmentedToggleItem
|
|
432
593
|
v-for="m in viewModes"
|
|
433
594
|
:key="m.value"
|
|
434
595
|
:value="m.value"
|
|
435
596
|
:aria-label="m.label"
|
|
597
|
+
class="transition-colors data-[state=on]:text-primary data-[state=off]:hover:text-primary!"
|
|
436
598
|
>
|
|
437
599
|
<component :is="m.icon" v-if="m.icon" :size="14" weight="Bold" />
|
|
438
600
|
<template v-if="!m.icon">{{ m.label }}</template>
|
|
439
601
|
</SegmentedToggleItem>
|
|
440
602
|
</SegmentedToggle>
|
|
441
|
-
<SavedViewsMenu v-if="savedViews" :config="savedViews" />
|
|
442
603
|
<slot name="toolbar-left" />
|
|
443
604
|
</div>
|
|
444
605
|
<div class="flex items-center gap-2 ml-auto shrink-0">
|
|
@@ -447,15 +608,40 @@ function defaultCell(col, row) {
|
|
|
447
608
|
</div>
|
|
448
609
|
</div>
|
|
449
610
|
|
|
611
|
+
<!--
|
|
612
|
+
What is filtering right now. Not optional beside a paned panel: with it
|
|
613
|
+
closed, a count on an icon says that something narrows the list and never
|
|
614
|
+
what.
|
|
615
|
+
-->
|
|
616
|
+
<FacetChips
|
|
617
|
+
v-if="facets.length"
|
|
618
|
+
v-model="facetSelection"
|
|
619
|
+
:facets="facets"
|
|
620
|
+
:show-facet="chipFacetLabels"
|
|
621
|
+
/>
|
|
622
|
+
|
|
450
623
|
<!-- Alternate view mode (gallery, board, …) — same toolbar, same footer. -->
|
|
451
624
|
<div v-if="!showTable" class="border-t border-black/[0.04] bg-white/55">
|
|
452
625
|
<slot name="view" :rows="displayedRows" :loading="loading" />
|
|
453
626
|
</div>
|
|
454
627
|
|
|
455
|
-
<!--
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
628
|
+
<!-- Two boxes: one that scrolls, and the table itself.
|
|
629
|
+
|
|
630
|
+
The table is ONE GRID and the rows are subgrids of it (`SUBGRID_ROW`),
|
|
631
|
+
which is what keeps a heading over its own column. It used to be a grid
|
|
632
|
+
per row, and a content-sized track then measured itself separately in
|
|
633
|
+
each one — see `SUBGRID_ROW` for what that did to every list with row
|
|
634
|
+
actions.
|
|
635
|
+
|
|
636
|
+
The scrolling is the outer box's job, and it has to be, because the
|
|
637
|
+
grid carries `min-w-min`: a box that grows to its own content cannot
|
|
638
|
+
also be the thing that scrolls it. The panel around them clips
|
|
639
|
+
(`overflow-hidden`, for the rounded corners), so without this a table
|
|
640
|
+
whose declared widths outgrow the panel simply lost the excess.
|
|
641
|
+
|
|
642
|
+
The inner element used to be `display: contents` when scrolling was
|
|
643
|
+
off, which made it vanish from layout — and a box that is not in the
|
|
644
|
+
layout can be neither a grid nor a scroller.
|
|
459
645
|
|
|
460
646
|
IT IS ALSO THE TABLE. The box that holds the header and the rows and
|
|
461
647
|
nothing else is the only element in this component that can carry the
|
|
@@ -470,19 +656,27 @@ function defaultCell(col, row) {
|
|
|
470
656
|
that. A static table read with a screen reader's own table commands is
|
|
471
657
|
what these lists actually are, and both roles announce the column
|
|
472
658
|
heading with the cell, which is the thing that was missing. -->
|
|
659
|
+
<div :class="scrollBody ? 'relative flex min-h-0 flex-col' : ''">
|
|
473
660
|
<div
|
|
474
|
-
|
|
475
|
-
:
|
|
476
|
-
:class="scrollBody ? 'min-h-0 overflow-y-auto' : 'contents'"
|
|
661
|
+
ref="scrollEl"
|
|
662
|
+
:class="scrollBody ? 'rx-rows min-h-0 overflow-auto' : 'overflow-x-auto'"
|
|
477
663
|
:style="scrollStyle"
|
|
664
|
+
@scroll="onRowsScroll"
|
|
478
665
|
>
|
|
666
|
+
<div
|
|
667
|
+
:role="showTable ? 'table' : void 0"
|
|
668
|
+
:aria-busy="showTable && loading ? 'true' : void 0"
|
|
669
|
+
:class="showTable ? 'grid min-w-min gap-x-2' : ''"
|
|
670
|
+
:style="showTable ? gridTemplate : void 0"
|
|
671
|
+
>
|
|
479
672
|
<!-- Header row -------------------------------------------------- -->
|
|
480
673
|
<div
|
|
481
674
|
v-if="showTable"
|
|
675
|
+
ref="headerRowEl"
|
|
482
676
|
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"
|
|
677
|
+
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
678
|
:class="scrollBody ? 'sticky top-0 z-10' : ''"
|
|
485
|
-
:style="
|
|
679
|
+
:style="SUBGRID_ROW"
|
|
486
680
|
>
|
|
487
681
|
<!-- `columnheader` is what names every cell underneath it, so this is
|
|
488
682
|
the half of the fix that does the work. `aria-sort` goes with it:
|
|
@@ -494,6 +688,8 @@ function defaultCell(col, row) {
|
|
|
494
688
|
:key="col.key"
|
|
495
689
|
role="columnheader"
|
|
496
690
|
:aria-sort="ariaSortFor(col)"
|
|
691
|
+
:title="col.label || void 0"
|
|
692
|
+
class="min-w-0"
|
|
497
693
|
:class="col.alignRight ? 'text-right' : ''"
|
|
498
694
|
>
|
|
499
695
|
<!-- `#header-<key>` lets a page take over a header cell (e.g. a
|
|
@@ -514,7 +710,7 @@ function defaultCell(col, row) {
|
|
|
514
710
|
the row's uppercase. Without it a column reads "LAST CHECK"
|
|
515
711
|
beside "Channel" and "Provider" for no reason a reader can see —
|
|
516
712
|
the header shouts wherever a column happens not to be sortable. -->
|
|
517
|
-
<span v-else class="normal-case">{{ col.label }}</span>
|
|
713
|
+
<span v-else class="block truncate normal-case">{{ col.label }}</span>
|
|
518
714
|
</slot>
|
|
519
715
|
</div>
|
|
520
716
|
</div>
|
|
@@ -527,8 +723,11 @@ function defaultCell(col, row) {
|
|
|
527
723
|
<div
|
|
528
724
|
v-if="entry.kind === 'header'"
|
|
529
725
|
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"
|
|
726
|
+
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
727
|
>
|
|
728
|
+
<!-- Spans every column like the rows do, so a table wide enough to
|
|
729
|
+
scroll does not show this band stopping short of the rows it
|
|
730
|
+
groups. No subgrid: it holds one cell, not a set of columns. -->
|
|
532
731
|
<div role="cell" :aria-colspan="visibleColumns.length" class="flex items-center gap-2">
|
|
533
732
|
{{ entry.label }}
|
|
534
733
|
<span class="tabular-nums normal-case text-muted-foreground/70">{{ entry.count }}</span>
|
|
@@ -537,8 +736,8 @@ function defaultCell(col, row) {
|
|
|
537
736
|
<div
|
|
538
737
|
v-else
|
|
539
738
|
role="row"
|
|
540
|
-
class="group grid items-center gap-2 border-t border-black/[0.04] px-4 py-3 cursor-pointer bg-gray-50 transition-colors"
|
|
541
|
-
:style="
|
|
739
|
+
class="group grid items-center gap-x-2 border-t border-black/[0.04] px-4 py-3 cursor-pointer bg-gray-50 transition-colors"
|
|
740
|
+
:style="SUBGRID_ROW"
|
|
542
741
|
@click="emit('rowClick', entry.row)"
|
|
543
742
|
>
|
|
544
743
|
<div
|
|
@@ -578,8 +777,8 @@ function defaultCell(col, row) {
|
|
|
578
777
|
v-for="n in 6"
|
|
579
778
|
:key="`sk-${n}`"
|
|
580
779
|
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="
|
|
780
|
+
class="grid items-center gap-x-2 border-t border-black/[0.04] bg-gray-50 px-4 py-3"
|
|
781
|
+
:style="SUBGRID_ROW"
|
|
583
782
|
>
|
|
584
783
|
<div
|
|
585
784
|
v-for="col in visibleColumns"
|
|
@@ -598,7 +797,7 @@ function defaultCell(col, row) {
|
|
|
598
797
|
<div
|
|
599
798
|
v-if="showTable && !displayedRows.length && !loading"
|
|
600
799
|
role="row"
|
|
601
|
-
class="px-3 py-8 text-center text-sm text-muted-foreground"
|
|
800
|
+
class="col-[1/-1] px-3 py-8 text-center text-sm text-muted-foreground"
|
|
602
801
|
>
|
|
603
802
|
<div role="cell" :aria-colspan="visibleColumns.length">
|
|
604
803
|
<slot name="empty">
|
|
@@ -607,11 +806,33 @@ function defaultCell(col, row) {
|
|
|
607
806
|
</div>
|
|
608
807
|
</div>
|
|
609
808
|
|
|
809
|
+
</div>
|
|
810
|
+
</div>
|
|
811
|
+
|
|
812
|
+
<!-- The rows' scrollbar ---------------------------------------- -->
|
|
813
|
+
<!-- Drawn, not borrowed. A browser's own bar belongs to the scrolling
|
|
814
|
+
box, and the column head is inside that box — it has to be, because
|
|
815
|
+
the head and the rows are one grid and a `max-content` track resolves
|
|
816
|
+
per grid (PE-610). So the native bar runs beside the head, which is
|
|
817
|
+
not where a bar over the ROWS belongs. This one starts under the
|
|
818
|
+
head, ends at the footer, and drives the same native scrolling. -->
|
|
819
|
+
<div
|
|
820
|
+
v-if="scrollBody && rowsOverflow"
|
|
821
|
+
class="pointer-events-none absolute right-0 z-20 w-3.5"
|
|
822
|
+
:style="{ top: `${headerHeight}px`, bottom: '0px' }"
|
|
823
|
+
>
|
|
824
|
+
<div
|
|
825
|
+
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)]"
|
|
826
|
+
:style="{ height: `${thumbHeight}px`, transform: `translateY(${thumbOffset}px)` }"
|
|
827
|
+
@pointerdown="startThumbDrag"
|
|
828
|
+
/>
|
|
829
|
+
</div>
|
|
610
830
|
</div>
|
|
611
831
|
|
|
612
832
|
<!-- Footer ------------------------------------------------------- -->
|
|
613
833
|
<div
|
|
614
834
|
v-if="showFooter"
|
|
835
|
+
ref="footerEl"
|
|
615
836
|
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/60"
|
|
616
837
|
>
|
|
617
838
|
<div class="flex items-center gap-2.5">
|
|
@@ -656,3 +877,7 @@ function defaultCell(col, row) {
|
|
|
656
877
|
</div>
|
|
657
878
|
</div>
|
|
658
879
|
</template>
|
|
880
|
+
|
|
881
|
+
<style scoped>
|
|
882
|
+
.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}
|
|
883
|
+
</style>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Facet, FacetSelection } from '../../lib/facets.js';
|
|
1
2
|
import type { DataTableColumn, DataTableFilter, DataTableRail, DataTableSavedViews, DataTableState, DataTableViewMode, RowKey, SortState } from './types.js';
|
|
2
3
|
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
4
|
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
@@ -5,9 +6,10 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
|
|
|
5
6
|
readonly "onUpdate:page"?: ((value: number) => any) | undefined;
|
|
6
7
|
readonly "onUpdate:viewMode"?: ((value: string) => any) | undefined;
|
|
7
8
|
readonly "onUpdate:railOpen"?: ((value: boolean) => any) | undefined;
|
|
9
|
+
readonly "onUpdate:facetSelection"?: ((value: FacetSelection) => any) | undefined;
|
|
8
10
|
readonly onRowClick?: ((row: T) => any) | undefined;
|
|
9
11
|
readonly onStateChange?: ((state: DataTableState) => any) | undefined;
|
|
10
|
-
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:sort" | "onUpdate:page" | "onUpdate:viewMode" | "onUpdate:railOpen" | "onRowClick" | "onStateChange"> & ({
|
|
12
|
+
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onUpdate:sort" | "onUpdate:page" | "onUpdate:viewMode" | "onUpdate:railOpen" | "onUpdate:facetSelection" | "onRowClick" | "onStateChange"> & ({
|
|
11
13
|
data: T[];
|
|
12
14
|
columns: DataTableColumn<T>[];
|
|
13
15
|
rowKey: RowKey<T>;
|
|
@@ -28,6 +30,25 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
|
|
|
28
30
|
/** Built-in status/dimension filter pills (static mode). One dimension, or
|
|
29
31
|
* several (each renders as its own sub-menu). */
|
|
30
32
|
filterTabs?: DataTableFilter<T> | DataTableFilter<T>[];
|
|
33
|
+
/**
|
|
34
|
+
* The surface's declared dimensions, as one facet model (PE-6xx).
|
|
35
|
+
*
|
|
36
|
+
* The THIRD filter path, and the one the others are being folded into. It
|
|
37
|
+
* differs from `filterTabs` in who does the work: those are filtered in
|
|
38
|
+
* memory here, which a server-paged list cannot use — a facet count taken
|
|
39
|
+
* over one page of fifty rows out of twenty-three thousand is a number
|
|
40
|
+
* nobody can reconcile with the table under it. Here the surface owns the
|
|
41
|
+
* query and the counts, and this component only draws the control and hands
|
|
42
|
+
* back a committed selection (`v-model:facet-selection`).
|
|
43
|
+
*
|
|
44
|
+
* EVERY declared dimension lives in this one panel, committed together, and
|
|
45
|
+
* reads back in one chip strip under the toolbar. There is deliberately no
|
|
46
|
+
* second placement: a tree pinned in a left column beside a panel is two
|
|
47
|
+
* places to look and two commit rules for one question.
|
|
48
|
+
*/
|
|
49
|
+
facets?: Facet[];
|
|
50
|
+
/** Say which facet each chip belongs to (see `FacetChips.showFacet`). */
|
|
51
|
+
chipFacetLabels?: boolean;
|
|
31
52
|
/** Saved views control — the surface supplies snapshot/apply, the table
|
|
32
53
|
* owns the control, the storage and the dialogs. */
|
|
33
54
|
savedViews?: DataTableSavedViews | null;
|
|
@@ -52,8 +73,13 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
|
|
|
52
73
|
* sitting above it. That is not a detail: a scrollbar takes width from the
|
|
53
74
|
* scrolling box, so a header outside it stays wider than the rows and every
|
|
54
75
|
* column drifts by however wide the scrollbar is.
|
|
76
|
+
*
|
|
77
|
+
* `'fit'` measures instead of guessing: the rows get whatever height is left
|
|
78
|
+
* in the surrounding scroll container, so the table ends where the page ends
|
|
79
|
+
* and nothing below it is pushed out of sight. A short list still ends after
|
|
80
|
+
* its last row — the measurement is a MAXIMUM, not a height.
|
|
55
81
|
*/
|
|
56
|
-
scrollBody?: boolean | {
|
|
82
|
+
scrollBody?: boolean | "fit" | {
|
|
57
83
|
maxHeight?: string;
|
|
58
84
|
};
|
|
59
85
|
pagination?: boolean | {
|
|
@@ -74,6 +100,13 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
|
|
|
74
100
|
page?: number;
|
|
75
101
|
viewMode?: string;
|
|
76
102
|
railOpen?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* The committed facet selection. Two-way so a chip removed under the toolbar,
|
|
105
|
+
* a facet picked in the surface's pinned rail and the panel's own Apply all
|
|
106
|
+
* write to one place — and so the surface can seed it from a saved view or a
|
|
107
|
+
* URL without this component holding a second copy to drift from.
|
|
108
|
+
*/
|
|
109
|
+
facetSelection?: FacetSelection;
|
|
77
110
|
}) & Partial<{}>> & import("vue").PublicProps;
|
|
78
111
|
expose(exposed: import("vue").ShallowUnwrapRef<{}>): void;
|
|
79
112
|
attrs: any;
|
|
@@ -101,7 +134,7 @@ declare const _default: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>
|
|
|
101
134
|
} & {
|
|
102
135
|
empty?: (props: {}) => any;
|
|
103
136
|
};
|
|
104
|
-
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));
|
|
137
|
+
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) & ((evt: "update:facetSelection", value: FacetSelection) => void));
|
|
105
138
|
}>) => import("vue").VNode & {
|
|
106
139
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
107
140
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Facet, FacetSelection } from '../../lib/facets.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
facets: Facet[];
|
|
4
|
+
modelValue: FacetSelection;
|
|
5
|
+
/**
|
|
6
|
+
* Say which facet each chip belongs to. On where "Draft" could be a status or
|
|
7
|
+
* a lifecycle; off where the values speak for themselves.
|
|
8
|
+
*/
|
|
9
|
+
showFacet?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
|
+
"update:modelValue": (value: FacetSelection) => any;
|
|
13
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
14
|
+
"onUpdate:modelValue"?: ((value: FacetSelection) => any) | undefined;
|
|
15
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { CloseCircle } from "#solar-icons";
|
|
4
|
+
import { dateTimeFormat } from "../../lib/formatBridge";
|
|
5
|
+
import { appliedFacets, clearFacetValue } from "../../lib/facets";
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
facets: { type: Array, required: true },
|
|
8
|
+
modelValue: { type: Object, required: true },
|
|
9
|
+
showFacet: { type: Boolean, required: false }
|
|
10
|
+
});
|
|
11
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
12
|
+
const t = useT();
|
|
13
|
+
function formatRange(range) {
|
|
14
|
+
const fmt = dateTimeFormat({ dateStyle: "medium" });
|
|
15
|
+
const one = (value) => {
|
|
16
|
+
if (!value) return null;
|
|
17
|
+
const date = new Date(value);
|
|
18
|
+
return Number.isNaN(date.getTime()) ? value : fmt.format(date);
|
|
19
|
+
};
|
|
20
|
+
const from = one(range.from);
|
|
21
|
+
const to = one(range.to);
|
|
22
|
+
if (from && to) return `${from} \u2013 ${to}`;
|
|
23
|
+
if (from) return t("shared.facetchips.range.since", { date: from });
|
|
24
|
+
return t("shared.facetchips.range.until", { date: to ?? "" });
|
|
25
|
+
}
|
|
26
|
+
const chips = computed(() => appliedFacets(props.facets, props.modelValue, {
|
|
27
|
+
range: formatRange,
|
|
28
|
+
text: (value) => `\u201C${value}\u201D`
|
|
29
|
+
}));
|
|
30
|
+
function remove(chip) {
|
|
31
|
+
emit("update:modelValue", clearFacetValue(props.modelValue, chip.facetKey, chip.value));
|
|
32
|
+
}
|
|
33
|
+
function clearAll() {
|
|
34
|
+
emit("update:modelValue", {});
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<div
|
|
40
|
+
v-if="chips.length"
|
|
41
|
+
class="flex flex-wrap items-center gap-1.5 border-t border-black/[0.04] bg-white px-4 py-2.5"
|
|
42
|
+
role="group"
|
|
43
|
+
:aria-label="t('shared.facetchips.aria')"
|
|
44
|
+
>
|
|
45
|
+
<span
|
|
46
|
+
v-for="chip in chips"
|
|
47
|
+
:key="chip.id"
|
|
48
|
+
class="inline-flex items-center gap-1.5 rounded-full bg-primary/10 py-0.5 pr-1 pl-2.5 text-xs font-medium text-primary"
|
|
49
|
+
>
|
|
50
|
+
<span v-if="showFacet" class="font-normal opacity-60">{{ chip.facetLabel }}</span>
|
|
51
|
+
<span class="max-w-60 truncate">{{ chip.label }}</span>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
class="cursor-pointer rounded-full opacity-60 transition-opacity hover:opacity-100"
|
|
55
|
+
:aria-label="t('shared.facetchips.remove', { facet: chip.facetLabel, value: chip.label })"
|
|
56
|
+
@click="remove(chip)"
|
|
57
|
+
>
|
|
58
|
+
<CloseCircle :size="13" weight="Bold" />
|
|
59
|
+
</button>
|
|
60
|
+
</span>
|
|
61
|
+
<!-- Only past two. With one chip beside it, "clear all" is a second control
|
|
62
|
+
for what the cross next to it already does. -->
|
|
63
|
+
<button
|
|
64
|
+
v-if="chips.length > 1"
|
|
65
|
+
type="button"
|
|
66
|
+
class="ml-1 cursor-pointer text-xs text-muted-foreground underline decoration-from-font underline-offset-2 hover:text-foreground"
|
|
67
|
+
@click="clearAll"
|
|
68
|
+
>
|
|
69
|
+
{{ t("shared.facetchips.clearAll") }}
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Facet, FacetSelection } from '../../lib/facets.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
facets: Facet[];
|
|
4
|
+
modelValue: FacetSelection;
|
|
5
|
+
/**
|
|
6
|
+
* Say which facet each chip belongs to. On where "Draft" could be a status or
|
|
7
|
+
* a lifecycle; off where the values speak for themselves.
|
|
8
|
+
*/
|
|
9
|
+
showFacet?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
|
+
"update:modelValue": (value: FacetSelection) => any;
|
|
13
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
14
|
+
"onUpdate:modelValue"?: ((value: FacetSelection) => any) | undefined;
|
|
15
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Facet, FacetSelection } from '../../lib/facets.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Every dimension this list can be narrowed by. All of them live here. */
|
|
4
|
+
facets: Facet[];
|
|
5
|
+
/** What is applied right now. */
|
|
6
|
+
modelValue: FacetSelection;
|
|
7
|
+
size?: 'sm' | 'default';
|
|
8
|
+
};
|
|
9
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
|
+
apply: (value: FacetSelection) => any;
|
|
11
|
+
"update:modelValue": (value: FacetSelection) => any;
|
|
12
|
+
reset: () => any;
|
|
13
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
14
|
+
onApply?: ((value: FacetSelection) => any) | undefined;
|
|
15
|
+
"onUpdate:modelValue"?: ((value: FacetSelection) => any) | undefined;
|
|
16
|
+
onReset?: (() => any) | undefined;
|
|
17
|
+
}>, {
|
|
18
|
+
size: "sm" | "default";
|
|
19
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
20
|
+
export default _default;
|