nuxt-ui-tools 1.29.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/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/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.vue +12 -33
- 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`.
|
|
@@ -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";
|
|
@@ -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
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed } from "vue";
|
|
3
3
|
import { useUiToolsLocale } from "#ui-tools/i18n";
|
|
4
4
|
import { useTableInternals } from "../../composables/use-table-internals";
|
|
5
|
+
import GroupHeader from "../group-header.vue";
|
|
5
6
|
import TableCell from "./table-cell";
|
|
6
7
|
import TableSelectionControl from "./table-selection-control.vue";
|
|
7
8
|
const props = defineProps({
|
|
@@ -31,47 +32,25 @@ function toggleSelection() {
|
|
|
31
32
|
</script>
|
|
32
33
|
|
|
33
34
|
<template>
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
:
|
|
37
|
-
|
|
38
|
-
@click="groups.toggle(group)"
|
|
35
|
+
<GroupHeader
|
|
36
|
+
:collapsed="collapsed"
|
|
37
|
+
:toggle-label="t('table.groups.toggle')"
|
|
38
|
+
@toggle="groups.toggle(group)"
|
|
39
39
|
>
|
|
40
|
-
<
|
|
40
|
+
<template v-if="showCheckbox" #leading>
|
|
41
41
|
<TableSelectionControl
|
|
42
42
|
:model-value="checked"
|
|
43
43
|
:label="t('table.groups.select')"
|
|
44
44
|
@toggle="toggleSelection"
|
|
45
45
|
/>
|
|
46
|
-
</
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
class="nut-dl-group__toggle"
|
|
50
|
-
:aria-expanded="!collapsed"
|
|
51
|
-
:aria-label="t('table.groups.toggle')"
|
|
52
|
-
@click.stop="groups.toggle(group)"
|
|
53
|
-
>
|
|
54
|
-
<svg
|
|
55
|
-
viewBox="0 0 16 16"
|
|
56
|
-
fill="none"
|
|
57
|
-
stroke="currentColor"
|
|
58
|
-
stroke-width="1.75"
|
|
59
|
-
stroke-linecap="round"
|
|
60
|
-
stroke-linejoin="round"
|
|
61
|
-
aria-hidden="true"
|
|
62
|
-
>
|
|
63
|
-
<path d="m4 6 4 4 4-4" />
|
|
64
|
-
</svg>
|
|
65
|
-
</button>
|
|
66
|
-
<span class="nut-dl-group__label">
|
|
67
|
-
<TableCell :index="0" :render="renderLabel" :row="group" />
|
|
68
|
-
</span>
|
|
69
|
-
<span class="nut-dl-group__summary">
|
|
46
|
+
</template>
|
|
47
|
+
<TableCell :index="0" :render="renderLabel" :row="group" />
|
|
48
|
+
<template #summary>
|
|
70
49
|
<TableCell :index="0" :render="renderSummary" :row="group" />
|
|
71
|
-
</
|
|
72
|
-
</
|
|
50
|
+
</template>
|
|
51
|
+
</GroupHeader>
|
|
73
52
|
</template>
|
|
74
53
|
|
|
75
54
|
<style>
|
|
76
|
-
.nut-dl-
|
|
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)}
|
|
77
56
|
</style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"description": "Nuxt module and runtime toolkit for UI-heavy Nuxt applications",
|
|
5
5
|
"homepage": "https://github.com/ChronicStone/nuxt-ui-tools#readme",
|
|
6
6
|
"bugs": "https://github.com/ChronicStone/nuxt-ui-tools/issues",
|