nuxt-ui-tools 1.28.0 → 1.30.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.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/form/components/page/form-page-section.vue +4 -1
- package/dist/runtime/form/types/page.d.ts +6 -0
- package/dist/runtime/i18n/locales/en.js +5 -0
- package/dist/runtime/i18n/locales/fr.js +5 -0
- package/dist/runtime/i18n/types.d.ts +7 -0
- package/dist/runtime/shared/composables/use-sheet-navigation.d.ts +25 -0
- package/dist/runtime/shared/composables/use-sheet-navigation.js +41 -0
- package/dist/runtime/shared/index.d.ts +1 -0
- package/dist/runtime/shared/index.js +1 -0
- package/dist/runtime/table/components/grid/grid-renderer.vue +33 -8
- package/dist/runtime/table/components/group-header.d.vue.ts +30 -0
- package/dist/runtime/table/components/group-header.vue +44 -0
- package/dist/runtime/table/components/group-header.vue.d.ts +30 -0
- package/dist/runtime/table/components/table/table-group-header.d.vue.ts +9 -0
- package/dist/runtime/table/components/table/table-group-header.vue +56 -0
- package/dist/runtime/table/components/table/table-group-header.vue.d.ts +9 -0
- package/dist/runtime/table/components/table/table-renderer.vue +12 -7
- package/dist/runtime/table/components/table/table-row.d.vue.ts +4 -1
- package/dist/runtime/table/components/table/table-row.vue +16 -1
- package/dist/runtime/table/components/table/table-row.vue.d.ts +4 -1
- package/dist/runtime/table/components/table/table-selection-control.d.vue.ts +2 -0
- package/dist/runtime/table/components/table/table-selection-control.vue +3 -2
- package/dist/runtime/table/components/table/table-selection-control.vue.d.ts +2 -0
- package/dist/runtime/table/composables/use-table-grid.d.ts +7 -3
- package/dist/runtime/table/composables/use-table-grid.js +27 -2
- package/dist/runtime/table/composables/use-table-groups.d.ts +28 -0
- package/dist/runtime/table/composables/use-table-groups.js +133 -0
- package/dist/runtime/table/composables/use-table-internals.d.ts +42 -0
- package/dist/runtime/table/composables/use-table-internals.js +9 -0
- package/dist/runtime/table/composables/use-table-selection.d.ts +5 -0
- package/dist/runtime/table/composables/use-table-selection.js +13 -3
- package/dist/runtime/table/composables/use-table-tree.js +7 -5
- package/dist/runtime/table/constants/row-motion.d.ts +8 -0
- package/dist/runtime/table/constants/row-motion.js +5 -0
- package/dist/runtime/table/types/groups.d.ts +44 -0
- package/dist/runtime/table/types/groups.js +0 -0
- package/dist/runtime/table/types/index.d.ts +1 -0
- package/dist/runtime/table/types/layout.d.ts +5 -0
- package/dist/runtime/table/utils/groups.d.ts +14 -0
- package/dist/runtime/table/utils/groups.js +55 -0
- package/dist/runtime/table/utils/index.d.ts +1 -0
- package/dist/runtime/table/utils/index.js +1 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -47,6 +47,11 @@ function getPublicComponents(runtimeDir, options) {
|
|
|
47
47
|
global: options.global,
|
|
48
48
|
name: `${prefix}DataList${part}`
|
|
49
49
|
})),
|
|
50
|
+
{
|
|
51
|
+
filePath: `${runtimeDir}/table/components/group-header.vue`,
|
|
52
|
+
global: options.global,
|
|
53
|
+
name: `${prefix}GroupHeader`
|
|
54
|
+
},
|
|
50
55
|
// Spreadsheet
|
|
51
56
|
{
|
|
52
57
|
filePath: `${runtimeDir}/spreadsheet/components/spreadsheet-import.vue`,
|
|
@@ -177,7 +177,10 @@ onBeforeUnmount(() => unregister?.());
|
|
|
177
177
|
</header>
|
|
178
178
|
<div
|
|
179
179
|
:class="
|
|
180
|
-
mergeFormUiClass(
|
|
180
|
+
mergeFormUiClass(
|
|
181
|
+
entry.section.flush ? 'min-w-0' : `min-w-0 ${page.surfaceClasses.value.sectionBody}`,
|
|
182
|
+
pageUi?.sectionBody
|
|
183
|
+
)
|
|
181
184
|
"
|
|
182
185
|
:style="grid.style.value"
|
|
183
186
|
>
|
|
@@ -40,6 +40,12 @@ export interface FormPageSection<TContext = NonNullable<unknown>, TFields extend
|
|
|
40
40
|
label: FormText;
|
|
41
41
|
/** Supporting copy next to the title. */
|
|
42
42
|
description?: FormText;
|
|
43
|
+
/**
|
|
44
|
+
* Lets the section's content reach the card's edges: the body under the heading has no padding.
|
|
45
|
+
* For a block that draws its own rows, such as a price grid. Only panel surfaces pad a section's
|
|
46
|
+
* body, so this changes nothing on a plain one.
|
|
47
|
+
*/
|
|
48
|
+
flush?: boolean;
|
|
43
49
|
/**
|
|
44
50
|
* Iconify icon drawn before the title, so each section of a long page reads at a glance
|
|
45
51
|
* (`'i-lucide-map-pin'` for an address). Style it with `ui.page.ui.sectionIcon`.
|
|
@@ -748,6 +748,11 @@ export default defineUiToolsLocale({
|
|
|
748
748
|
label: "Actions for {label}",
|
|
749
749
|
labelAt: "Actions for row {position}"
|
|
750
750
|
},
|
|
751
|
+
groups: {
|
|
752
|
+
empty: "Other",
|
|
753
|
+
select: "Select the whole group",
|
|
754
|
+
toggle: "Fold or unfold the group"
|
|
755
|
+
},
|
|
751
756
|
selection: {
|
|
752
757
|
selectAll: "Select all rows",
|
|
753
758
|
selectRow: "Select {label}",
|
|
@@ -748,6 +748,11 @@ export default defineUiToolsLocale({
|
|
|
748
748
|
label: "Actions pour {label}",
|
|
749
749
|
labelAt: "Actions pour la ligne {position}"
|
|
750
750
|
},
|
|
751
|
+
groups: {
|
|
752
|
+
empty: "Autres",
|
|
753
|
+
select: "S\xE9lectionner tout le groupe",
|
|
754
|
+
toggle: "Replier ou d\xE9plier le groupe"
|
|
755
|
+
},
|
|
751
756
|
selection: {
|
|
752
757
|
selectAll: "S\xE9lectionner toutes les lignes",
|
|
753
758
|
selectRow: "S\xE9lectionner {label}",
|
|
@@ -23,6 +23,13 @@ export interface UiToolsTableMessages {
|
|
|
23
23
|
firstPage: string;
|
|
24
24
|
lastPage: string;
|
|
25
25
|
};
|
|
26
|
+
/** A row group's header. */
|
|
27
|
+
groups: {
|
|
28
|
+
/** The label of the group holding rows with no value. */
|
|
29
|
+
empty: string;
|
|
30
|
+
select: string;
|
|
31
|
+
toggle: string;
|
|
32
|
+
};
|
|
26
33
|
/** Accessible names of the selection checkboxes. */
|
|
27
34
|
selection: {
|
|
28
35
|
selectAll: string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
/** The attribute a cell's input carries: its column, so up and down stay in it. */
|
|
3
|
+
export declare const SHEET_COLUMN_ATTRIBUTE = "data-sheet-col";
|
|
4
|
+
/**
|
|
5
|
+
* Where a key press moves in a grid of inputs, or `undefined` to leave it to the input. Up, down
|
|
6
|
+
* and Enter stay in the column; left and right follow the reading order, and only once the caret
|
|
7
|
+
* has nothing left to cross, so editing inside a value still works.
|
|
8
|
+
*/
|
|
9
|
+
export declare function nextSheetInput<TInput>(params: {
|
|
10
|
+
inputs: readonly TInput[];
|
|
11
|
+
current: TInput;
|
|
12
|
+
key: string;
|
|
13
|
+
shiftKey?: boolean;
|
|
14
|
+
columnOf: (input: TInput) => string | null;
|
|
15
|
+
caret: {
|
|
16
|
+
atStart: boolean;
|
|
17
|
+
atEnd: boolean;
|
|
18
|
+
};
|
|
19
|
+
}): TInput | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Makes the inputs under `root` walk like a spreadsheet. Mark each one with
|
|
22
|
+
* `data-sheet-col="<column>"`: arrows and Enter move between them, and focusing one selects its
|
|
23
|
+
* value, so typing replaces it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useSheetNavigation(root: MaybeRefOrGetter<HTMLElement | null | undefined>): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useEventListener } from "@vueuse/core";
|
|
2
|
+
export const SHEET_COLUMN_ATTRIBUTE = "data-sheet-col";
|
|
3
|
+
const SELECTOR = `input[${SHEET_COLUMN_ATTRIBUTE}]:not(:disabled)`;
|
|
4
|
+
export function nextSheetInput(params) {
|
|
5
|
+
const { inputs, current, key, caret } = params;
|
|
6
|
+
const across = inputs.indexOf(current);
|
|
7
|
+
const column = inputs.filter((input) => params.columnOf(input) === params.columnOf(current));
|
|
8
|
+
const along = column.indexOf(current);
|
|
9
|
+
if (key === "ArrowDown" || key === "Enter" && !params.shiftKey) return column[along + 1];
|
|
10
|
+
if (key === "ArrowUp" || key === "Enter" && params.shiftKey) return column[along - 1];
|
|
11
|
+
if (key === "ArrowRight" && caret.atEnd) return inputs[across + 1];
|
|
12
|
+
if (key === "ArrowLeft" && caret.atStart) return inputs[across - 1];
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
export function useSheetNavigation(root) {
|
|
16
|
+
useEventListener(root, "focusin", (event) => {
|
|
17
|
+
if (event.target instanceof HTMLInputElement && event.target.matches(SELECTOR)) {
|
|
18
|
+
event.target.select();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
useEventListener(root, "keydown", (event) => {
|
|
22
|
+
const { currentTarget, target } = event;
|
|
23
|
+
if (!(target instanceof HTMLInputElement) || !(currentTarget instanceof HTMLElement)) return;
|
|
24
|
+
if (!target.matches(SELECTOR)) return;
|
|
25
|
+
const next = nextSheetInput({
|
|
26
|
+
caret: {
|
|
27
|
+
atEnd: target.selectionEnd === target.value.length,
|
|
28
|
+
atStart: target.selectionStart === 0
|
|
29
|
+
},
|
|
30
|
+
columnOf: (input) => input.getAttribute(SHEET_COLUMN_ATTRIBUTE),
|
|
31
|
+
current: target,
|
|
32
|
+
inputs: [...currentTarget.querySelectorAll(SELECTOR)],
|
|
33
|
+
key: event.key,
|
|
34
|
+
shiftKey: event.shiftKey
|
|
35
|
+
});
|
|
36
|
+
if (next) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
next.focus();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './composables/use-responsive-value.js';
|
|
2
2
|
export * from './composables/use-range-select.js';
|
|
3
|
+
export * from './composables/use-sheet-navigation.js';
|
|
3
4
|
export { defineRemoteOptions } from './utils/define-remote-options.js';
|
|
4
5
|
export * from './types/responsive.js';
|
|
5
6
|
export * from './types/utils.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./composables/use-responsive-value.js";
|
|
2
2
|
export * from "./composables/use-range-select.js";
|
|
3
|
+
export * from "./composables/use-sheet-navigation.js";
|
|
3
4
|
export { defineRemoteOptions } from "./utils/define-remote-options.js";
|
|
4
5
|
export * from "./types/responsive.js";
|
|
5
6
|
export * from "./types/utils.js";
|
|
@@ -11,6 +11,7 @@ import { mergeDataListUiClass, resolveTableRowId } from "../../utils";
|
|
|
11
11
|
import DataListErrorState from "../data-list/data-list-error-state.vue";
|
|
12
12
|
import ProgressLine from "../layout/progress-line.vue";
|
|
13
13
|
import TableEmptyState from "../table/table-empty-state.vue";
|
|
14
|
+
import TableGroupHeader from "../table/table-group-header.vue";
|
|
14
15
|
import OverlayInsetSpacer from "../utils/overlay-inset-spacer.vue";
|
|
15
16
|
import GridCard from "./grid-card.vue";
|
|
16
17
|
import GridSkeleton from "./grid-skeleton.vue";
|
|
@@ -75,12 +76,21 @@ watch(
|
|
|
75
76
|
);
|
|
76
77
|
function getVirtualRowKey(index) {
|
|
77
78
|
const chunk = rowChunks.value[index];
|
|
79
|
+
if (chunk?.group) return `grid-group:${chunk.group.key}`;
|
|
78
80
|
const firstRow = chunk?.rows[0];
|
|
79
81
|
return firstRow ? `grid-row:${resolveTableRowId({ index: chunk.start, row: firstRow, rowKey: internals.schema.value.rowKey })}` : `grid-row:${index}`;
|
|
80
82
|
}
|
|
81
83
|
function rowId(row, index) {
|
|
82
84
|
return resolveTableRowId({ index, row, rowKey: internals.schema.value.rowKey });
|
|
83
85
|
}
|
|
86
|
+
const flowItems = computed(
|
|
87
|
+
() => rowChunks.value.flatMap(
|
|
88
|
+
(chunk) => chunk.group ? [{ group: chunk.group, index: chunk.start, key: `grid-group:${chunk.group.key}` }] : chunk.rows.map((row, offset) => ({
|
|
89
|
+
index: chunk.start + offset,
|
|
90
|
+
key: rowId(row, chunk.start + offset)
|
|
91
|
+
}))
|
|
92
|
+
)
|
|
93
|
+
);
|
|
84
94
|
function scrollToTop() {
|
|
85
95
|
viewportRef.value?.scrollTo({ behavior: "auto", left: 0, top: 0 });
|
|
86
96
|
}
|
|
@@ -258,6 +268,13 @@ function refreshData() {
|
|
|
258
268
|
}"
|
|
259
269
|
:data-index="virtualRow.index"
|
|
260
270
|
>
|
|
271
|
+
<div
|
|
272
|
+
v-if="rowChunks[virtualRow.index]?.group"
|
|
273
|
+
class="nut-dl-grid__group min-w-0"
|
|
274
|
+
:style="{ gridColumn: '1 / -1' }"
|
|
275
|
+
>
|
|
276
|
+
<TableGroupHeader :group="rowChunks[virtualRow.index].group" />
|
|
277
|
+
</div>
|
|
261
278
|
<div
|
|
262
279
|
v-for="(row, offset) in rowChunks[virtualRow.index]?.rows ?? []"
|
|
263
280
|
:key="rowId(row, (rowChunks[virtualRow.index]?.start ?? 0) + offset)"
|
|
@@ -275,14 +292,22 @@ function refreshData() {
|
|
|
275
292
|
:class="mergeDataListUiClass('nut-dl-grid__flow grid', void 0, ui?.flow)"
|
|
276
293
|
:style="{ gridTemplateColumns, gap: `${gap}px` }"
|
|
277
294
|
>
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
<template v-for="item in flowItems" :key="item.key">
|
|
296
|
+
<div
|
|
297
|
+
v-if="item.group"
|
|
298
|
+
class="nut-dl-grid__group min-w-0"
|
|
299
|
+
:style="{ gridColumn: '1 / -1' }"
|
|
300
|
+
>
|
|
301
|
+
<TableGroupHeader :group="item.group" />
|
|
302
|
+
</div>
|
|
303
|
+
<div
|
|
304
|
+
v-else
|
|
305
|
+
:class="mergeDataListUiClass('nut-dl-grid__item min-w-0', void 0, ui?.item)"
|
|
306
|
+
:style="{ gridColumn }"
|
|
307
|
+
>
|
|
308
|
+
<GridCard :row-index="item.index" />
|
|
309
|
+
</div>
|
|
310
|
+
</template>
|
|
286
311
|
</div>
|
|
287
312
|
<OverlayInsetSpacer />
|
|
288
313
|
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fold header of a group of rows: chevron, label and count, with room for a checkbox before
|
|
3
|
+
* them. It holds no state, so a table's row groups and a hand-built grid read the same.
|
|
4
|
+
*/
|
|
5
|
+
type __VLS_Props = {
|
|
6
|
+
collapsed?: boolean;
|
|
7
|
+
/** Names the chevron for assistive technology. */
|
|
8
|
+
toggleLabel: string;
|
|
9
|
+
};
|
|
10
|
+
type __VLS_Slots = {
|
|
11
|
+
/** Sits before the chevron, usually a checkbox. A click in it does not fold the group. */
|
|
12
|
+
leading?: () => unknown;
|
|
13
|
+
/** The group's name. */
|
|
14
|
+
default?: () => unknown;
|
|
15
|
+
/** Shown after the name, usually a count. */
|
|
16
|
+
summary?: () => unknown;
|
|
17
|
+
};
|
|
18
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
|
+
toggle: () => any;
|
|
20
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
21
|
+
onToggle?: (() => any) | undefined;
|
|
22
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
23
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
26
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
27
|
+
new (): {
|
|
28
|
+
$slots: S;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
collapsed: { type: Boolean, required: false },
|
|
4
|
+
toggleLabel: { type: String, required: true }
|
|
5
|
+
});
|
|
6
|
+
defineSlots();
|
|
7
|
+
const emit = defineEmits(["toggle"]);
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div
|
|
12
|
+
class="nut-dl-group"
|
|
13
|
+
:data-collapsed="collapsed || void 0"
|
|
14
|
+
:data-selectable="$slots.leading ? true : void 0"
|
|
15
|
+
@click="emit('toggle')"
|
|
16
|
+
>
|
|
17
|
+
<span v-if="$slots.leading" class="nut-dl-group__check"><slot name="leading" /></span>
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
class="nut-dl-group__toggle"
|
|
21
|
+
:aria-expanded="!collapsed"
|
|
22
|
+
:aria-label="toggleLabel"
|
|
23
|
+
@click.stop="emit('toggle')"
|
|
24
|
+
>
|
|
25
|
+
<svg
|
|
26
|
+
viewBox="0 0 16 16"
|
|
27
|
+
fill="none"
|
|
28
|
+
stroke="currentColor"
|
|
29
|
+
stroke-width="1.75"
|
|
30
|
+
stroke-linecap="round"
|
|
31
|
+
stroke-linejoin="round"
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
>
|
|
34
|
+
<path d="m4 6 4 4 4-4" />
|
|
35
|
+
</svg>
|
|
36
|
+
</button>
|
|
37
|
+
<span class="nut-dl-group__label"><slot /></span>
|
|
38
|
+
<span v-if="$slots.summary" class="nut-dl-group__summary"><slot name="summary" /></span>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<style>
|
|
43
|
+
.nut-dl-group{--nut-dl-group-gutter:var(--nut-dl-gutter,0px);box-sizing:border-box;color:var(--ui-text-highlighted);cursor:pointer;font-size:13px;font-weight:600;gap:var(--nut-group-gap,6px);height:100%;left:0;max-width:100%;min-height:32px;padding-inline:var(--nut-dl-group-gutter);position:sticky;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.nut-dl-group,.nut-dl-group__check{align-items:center;display:inline-flex}.nut-dl-group__check{flex:none;margin-right:-6px;width:calc(var(--nut-dl-select-w, 44px) - var(--nut-dl-group-gutter) + var(--nut-dl-cell-x, 0px))}.nut-dl-group__toggle{background:transparent;border:0;border-radius:calc(var(--ui-radius)*1.5);color:var(--ui-text-muted);cursor:pointer;display:grid;flex:none;height:22px;outline:none;padding:0;place-items:center;width:var(--nut-group-toggle,22px)}.nut-dl-group__toggle:hover{background:var(--nut-dl-toggle-hover,var(--ui-bg-accented))}.nut-dl-group__toggle:focus-visible{box-shadow:0 0 0 2px var(--ui-primary)}.nut-dl-group__toggle svg{height:16px;transition:transform .16s ease;width:16px}.nut-dl-group[data-collapsed] .nut-dl-group__toggle svg{transform:rotate(-90deg)}.nut-dl-group__label{min-width:0;overflow:hidden;text-overflow:ellipsis}.nut-dl-group__summary{color:var(--ui-text-muted);flex:none;font-size:12px;font-variant-numeric:tabular-nums;font-weight:500}@media (prefers-reduced-motion:reduce){.nut-dl-group__toggle svg{transition:none}}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fold header of a group of rows: chevron, label and count, with room for a checkbox before
|
|
3
|
+
* them. It holds no state, so a table's row groups and a hand-built grid read the same.
|
|
4
|
+
*/
|
|
5
|
+
type __VLS_Props = {
|
|
6
|
+
collapsed?: boolean;
|
|
7
|
+
/** Names the chevron for assistive technology. */
|
|
8
|
+
toggleLabel: string;
|
|
9
|
+
};
|
|
10
|
+
type __VLS_Slots = {
|
|
11
|
+
/** Sits before the chevron, usually a checkbox. A click in it does not fold the group. */
|
|
12
|
+
leading?: () => unknown;
|
|
13
|
+
/** The group's name. */
|
|
14
|
+
default?: () => unknown;
|
|
15
|
+
/** Shown after the name, usually a count. */
|
|
16
|
+
summary?: () => unknown;
|
|
17
|
+
};
|
|
18
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
|
+
toggle: () => any;
|
|
20
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
21
|
+
onToggle?: (() => any) | undefined;
|
|
22
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
23
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
26
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
27
|
+
new (): {
|
|
28
|
+
$slots: S;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TableGroup } from '../../types/index.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
group: TableGroup;
|
|
4
|
+
/** Whether the rows beneath it carry checkboxes. */
|
|
5
|
+
selectable?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { useUiToolsLocale } from "#ui-tools/i18n";
|
|
4
|
+
import { useTableInternals } from "../../composables/use-table-internals";
|
|
5
|
+
import GroupHeader from "../group-header.vue";
|
|
6
|
+
import TableCell from "./table-cell";
|
|
7
|
+
import TableSelectionControl from "./table-selection-control.vue";
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
group: { type: Object, required: true },
|
|
10
|
+
selectable: { type: Boolean, required: false }
|
|
11
|
+
});
|
|
12
|
+
const { groups, selection } = useTableInternals();
|
|
13
|
+
const { t } = useUiToolsLocale();
|
|
14
|
+
const collapsed = computed(() => groups.isCollapsed(props.group));
|
|
15
|
+
const params = computed(() => ({ rows: props.group.rows, value: props.group.value }));
|
|
16
|
+
const checked = computed(() => selection.getRowsSelectionState({ rowIds: props.group.rowIds }));
|
|
17
|
+
const showCheckbox = computed(() => props.selectable && selection.selectionEnabled.value);
|
|
18
|
+
function renderLabel() {
|
|
19
|
+
const label = groups.config.value?.label;
|
|
20
|
+
if (label) return label(params.value);
|
|
21
|
+
return props.group.value === void 0 ? t("table.groups.empty") : String(props.group.value);
|
|
22
|
+
}
|
|
23
|
+
function renderSummary() {
|
|
24
|
+
const summary = groups.config.value?.summary;
|
|
25
|
+
return summary ? summary(params.value) : props.group.rows.length;
|
|
26
|
+
}
|
|
27
|
+
function toggleSelection() {
|
|
28
|
+
const { rowIds } = props.group;
|
|
29
|
+
if (checked.value === true) selection.unselectRows({ rowIds });
|
|
30
|
+
else selection.selectRows({ rowIds });
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<GroupHeader
|
|
36
|
+
:collapsed="collapsed"
|
|
37
|
+
:toggle-label="t('table.groups.toggle')"
|
|
38
|
+
@toggle="groups.toggle(group)"
|
|
39
|
+
>
|
|
40
|
+
<template v-if="showCheckbox" #leading>
|
|
41
|
+
<TableSelectionControl
|
|
42
|
+
:model-value="checked"
|
|
43
|
+
:label="t('table.groups.select')"
|
|
44
|
+
@toggle="toggleSelection"
|
|
45
|
+
/>
|
|
46
|
+
</template>
|
|
47
|
+
<TableCell :index="0" :render="renderLabel" :row="group" />
|
|
48
|
+
<template #summary>
|
|
49
|
+
<TableCell :index="0" :render="renderSummary" :row="group" />
|
|
50
|
+
</template>
|
|
51
|
+
</GroupHeader>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
.nut-dl-row.nut-dl-row--group{background:var(--nut-dl-head-bg)}.nut-dl-row.nut-dl-row--group:hover{background:color-mix(in srgb,var(--nut-dl-head-fg) 7%,var(--nut-dl-head-bg))}.nut-dl-group-cell{background:inherit;border-bottom:1px solid var(--nut-dl-line);height:var(--nut-dl-head-h);padding:0}.nut-dl-grid__group{border-bottom:1px solid var(--ui-border)}
|
|
56
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TableGroup } from '../../types/index.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
group: TableGroup;
|
|
4
|
+
/** Whether the rows beneath it carry checkboxes. */
|
|
5
|
+
selectable?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
|
@@ -6,7 +6,7 @@ import { isFunction, isNumber, isObject } from "../../../shared/utils/predicate"
|
|
|
6
6
|
import { useDataListUi } from "../../composables/use-data-list-ui";
|
|
7
7
|
import { useTableInternals } from "../../composables/use-table-internals";
|
|
8
8
|
import { useTanstackTable } from "../../composables/use-tanstack-table";
|
|
9
|
-
import { mergeDataListUiClass } from "../../utils";
|
|
9
|
+
import { mergeDataListUiClass, tableGroupOf } from "../../utils";
|
|
10
10
|
import {
|
|
11
11
|
ROW_ACTIONS_COLUMN_ID,
|
|
12
12
|
SELECT_COLUMN_ID,
|
|
@@ -54,7 +54,7 @@ const props = defineProps({
|
|
|
54
54
|
ui: { type: Object, required: false }
|
|
55
55
|
});
|
|
56
56
|
const internals = useTableInternals();
|
|
57
|
-
const { tree } = internals;
|
|
57
|
+
const { groups, tree } = internals;
|
|
58
58
|
const dataListUi = useDataListUi();
|
|
59
59
|
const { locale, t } = useUiToolsLocale();
|
|
60
60
|
const rows = computed(
|
|
@@ -89,6 +89,7 @@ const tokenStyle = computed(() => ({
|
|
|
89
89
|
"--nut-dl-gutter": `${gutter.value}px`,
|
|
90
90
|
"--nut-dl-head-h": `${tokens.value.head}px`,
|
|
91
91
|
"--nut-dl-row-h": `${tokens.value.row}px`,
|
|
92
|
+
"--nut-dl-select-w": `${SELECT_COLUMN_WIDTH + gutterExtra.value}px`,
|
|
92
93
|
"--nut-dl-tree-gutter": `${TREE_GUTTER_WIDTH}px`,
|
|
93
94
|
"--nut-dl-tree-toggle": `${TREE_TOGGLE_WIDTH}px`,
|
|
94
95
|
"--nut-dl-tree-gap": `${TREE_GAP}px`,
|
|
@@ -141,11 +142,13 @@ const pinned = computed(() => {
|
|
|
141
142
|
};
|
|
142
143
|
});
|
|
143
144
|
function getRowId(row, index) {
|
|
144
|
-
return internals.selection.getRowId({ index, row });
|
|
145
|
+
return tableGroupOf(row)?.key ?? internals.selection.getRowId({ index, row });
|
|
145
146
|
}
|
|
146
|
-
const sourceRows = computed(
|
|
147
|
-
(
|
|
148
|
-
);
|
|
147
|
+
const sourceRows = computed(() => {
|
|
148
|
+
if (rows.value.length === 0) return rows.value;
|
|
149
|
+
if (tree.enabled.value) return tree.visibleRows.value;
|
|
150
|
+
return groups.enabled.value ? groups.displayRows.value : rows.value;
|
|
151
|
+
});
|
|
149
152
|
const renderRows = computed(() => rowsMounted.value ? sourceRows.value : []);
|
|
150
153
|
const virtualized = computed(
|
|
151
154
|
() => renderRows.value.length >= VIRTUALIZE_ROW_THRESHOLD || renderRows.value.length * columnDefs.value.length >= VIRTUALIZE_CELL_THRESHOLD
|
|
@@ -745,7 +748,9 @@ defineExpose({ resetColumnSizing });
|
|
|
745
748
|
:node="tree.nodeOf(row.id)"
|
|
746
749
|
:expanded="tree.isExpanded(row.id)"
|
|
747
750
|
:checkbox="hasCheckbox(row.id)"
|
|
748
|
-
:motion="tree.motionOf(row.id)"
|
|
751
|
+
:motion="tree.motionOf(row.id) ?? groups.motionOf(row.id)"
|
|
752
|
+
:group="tableGroupOf(row.original)"
|
|
753
|
+
:col-span="columnCount"
|
|
749
754
|
@toggle="tree.toggleRow(row.id)"
|
|
750
755
|
@select="onSelectRow(row.id, $event)"
|
|
751
756
|
/>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* so rows that stay on screen while the virtual window slides keep their cells untouched.
|
|
4
4
|
*/
|
|
5
5
|
import type { HTMLAttributes } from 'vue';
|
|
6
|
-
import type { GenericObject, TableTreeMotion, TableTreeNode } from '../../types/index.js';
|
|
6
|
+
import type { GenericObject, TableGroup, TableTreeMotion, TableTreeNode } from '../../types/index.js';
|
|
7
7
|
import type { tableRowCells } from './table-layout.js';
|
|
8
8
|
type __VLS_Props = {
|
|
9
9
|
rowId: string;
|
|
@@ -20,6 +20,9 @@ type __VLS_Props = {
|
|
|
20
20
|
checkbox?: boolean;
|
|
21
21
|
/** The row is being revealed or concealed by a branch opening or closing. */
|
|
22
22
|
motion?: TableTreeMotion;
|
|
23
|
+
/** Set on a group's header row, which spans the columns instead of filling them. */
|
|
24
|
+
group?: TableGroup;
|
|
25
|
+
colSpan?: number;
|
|
23
26
|
};
|
|
24
27
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
25
28
|
select: (event: MouseEvent) => any;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import TableCell from "./table-cell";
|
|
3
|
+
import TableGroupHeader from "./table-group-header.vue";
|
|
3
4
|
import TableTreeControl from "./table-tree-control.vue";
|
|
4
5
|
defineProps({
|
|
5
6
|
rowId: { type: String, required: true },
|
|
@@ -12,13 +13,27 @@ defineProps({
|
|
|
12
13
|
node: { type: Object, required: false },
|
|
13
14
|
expanded: { type: Boolean, required: false },
|
|
14
15
|
checkbox: { type: Boolean, required: false },
|
|
15
|
-
motion: { type: Object, required: false }
|
|
16
|
+
motion: { type: Object, required: false },
|
|
17
|
+
group: { type: Object, required: false },
|
|
18
|
+
colSpan: { type: Number, required: false }
|
|
16
19
|
});
|
|
17
20
|
const emit = defineEmits(["toggle", "select"]);
|
|
18
21
|
</script>
|
|
19
22
|
|
|
20
23
|
<template>
|
|
21
24
|
<tr
|
|
25
|
+
v-if="group"
|
|
26
|
+
class="nut-dl-row nut-dl-row--group"
|
|
27
|
+
:class="rowClass"
|
|
28
|
+
:data-index="index"
|
|
29
|
+
:data-row-id="rowId"
|
|
30
|
+
>
|
|
31
|
+
<td :colspan="colSpan" class="nut-dl-group-cell">
|
|
32
|
+
<TableGroupHeader :group="group" selectable />
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<tr
|
|
36
|
+
v-else
|
|
22
37
|
class="nut-dl-row group/row"
|
|
23
38
|
:class="[
|
|
24
39
|
selected ? 'nut-dl-row--selected' : '',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* so rows that stay on screen while the virtual window slides keep their cells untouched.
|
|
4
4
|
*/
|
|
5
5
|
import type { HTMLAttributes } from 'vue';
|
|
6
|
-
import type { GenericObject, TableTreeMotion, TableTreeNode } from '../../types/index.js';
|
|
6
|
+
import type { GenericObject, TableGroup, TableTreeMotion, TableTreeNode } from '../../types/index.js';
|
|
7
7
|
import type { tableRowCells } from './table-layout.js';
|
|
8
8
|
type __VLS_Props = {
|
|
9
9
|
rowId: string;
|
|
@@ -20,6 +20,9 @@ type __VLS_Props = {
|
|
|
20
20
|
checkbox?: boolean;
|
|
21
21
|
/** The row is being revealed or concealed by a branch opening or closing. */
|
|
22
22
|
motion?: TableTreeMotion;
|
|
23
|
+
/** Set on a group's header row, which spans the columns instead of filling them. */
|
|
24
|
+
group?: TableGroup;
|
|
25
|
+
colSpan?: number;
|
|
23
26
|
};
|
|
24
27
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
25
28
|
select: (event: MouseEvent) => any;
|
|
@@ -5,6 +5,8 @@ type __VLS_Props = {
|
|
|
5
5
|
row?: GenericObject;
|
|
6
6
|
/** Where the record sits in the rendered rows: it names the record when the schema has no `rowLabel`. */
|
|
7
7
|
index?: number;
|
|
8
|
+
/** Names what it selects when that is neither one record nor every row. */
|
|
9
|
+
label?: string;
|
|
8
10
|
};
|
|
9
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
12
|
toggle: (event: MouseEvent) => any;
|
|
@@ -7,7 +7,8 @@ import { mergeDataListProps, mergeDataListUiClass } from "../../utils";
|
|
|
7
7
|
const props = defineProps({
|
|
8
8
|
modelValue: { type: [Boolean, String], required: true },
|
|
9
9
|
row: { type: Object, required: false },
|
|
10
|
-
index: { type: Number, required: false }
|
|
10
|
+
index: { type: Number, required: false },
|
|
11
|
+
label: { type: String, required: false }
|
|
11
12
|
});
|
|
12
13
|
const emit = defineEmits(["toggle"]);
|
|
13
14
|
const dataListUi = useDataListUi();
|
|
@@ -20,7 +21,7 @@ const areaClass = computed(
|
|
|
20
21
|
)
|
|
21
22
|
);
|
|
22
23
|
const ariaLabel = computed(
|
|
23
|
-
() => props.row ? labels.selectRow({ index: props.index ?? 0, row: props.row }) : labels.selectAll()
|
|
24
|
+
() => props.label ?? (props.row ? labels.selectRow({ index: props.index ?? 0, row: props.row }) : labels.selectAll())
|
|
24
25
|
);
|
|
25
26
|
const checkboxProps = computed(
|
|
26
27
|
() => mergeDataListProps(
|
|
@@ -5,6 +5,8 @@ type __VLS_Props = {
|
|
|
5
5
|
row?: GenericObject;
|
|
6
6
|
/** Where the record sits in the rendered rows: it names the record when the schema has no `rowLabel`. */
|
|
7
7
|
index?: number;
|
|
8
|
+
/** Names what it selects when that is neither one record nor every row. */
|
|
9
|
+
label?: string;
|
|
8
10
|
};
|
|
9
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
12
|
toggle: (event: MouseEvent) => any;
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import type { ComputedRef } from 'vue';
|
|
2
|
-
import type { GenericObject, TableExternalState, TableGridMode, TableSchemaView } from '../types/index.js';
|
|
2
|
+
import type { GenericObject, TableExternalState, TableGridMode, TableGroup, TableSchemaView } from '../types/index.js';
|
|
3
|
+
import type { useTableGroups } from './use-table-groups.js';
|
|
3
4
|
export interface GridRowChunk<TRow = unknown> {
|
|
4
5
|
index: number;
|
|
5
6
|
start: number;
|
|
6
7
|
end: number;
|
|
7
8
|
rows: TRow[];
|
|
9
|
+
/** The group this chunk is the header of; a header holds no rows. */
|
|
10
|
+
group?: TableGroup;
|
|
8
11
|
}
|
|
9
12
|
export interface UseTableGridParams<TRow extends GenericObject = GenericObject> {
|
|
10
13
|
schema: ComputedRef<TableSchemaView>;
|
|
11
14
|
data: ComputedRef<TableExternalState<TRow>>;
|
|
15
|
+
groups: ReturnType<typeof useTableGroups>;
|
|
12
16
|
}
|
|
13
17
|
export declare function useTableGrid<TRow extends GenericObject>(options: UseTableGridParams<TRow>): {
|
|
14
18
|
cardsPerRow: ComputedRef<number>;
|
|
15
19
|
columnCount: ComputedRef<number>;
|
|
16
20
|
itemColumnSpan: ComputedRef<number>;
|
|
17
21
|
mode: ComputedRef<TableGridMode>;
|
|
18
|
-
rowChunks: ComputedRef<GridRowChunk<
|
|
19
|
-
rows: ComputedRef<
|
|
22
|
+
rowChunks: ComputedRef<GridRowChunk<object>[]>;
|
|
23
|
+
rows: ComputedRef<object[]>;
|
|
20
24
|
};
|