nuxt-ui-tools 1.3.0 → 1.5.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 +10 -2
- package/dist/runtime/dashboard/composables/use-dashboard-remote-options.js +8 -3
- package/dist/runtime/dashboard/types/filters.d.ts +7 -1
- package/dist/runtime/dashboard/utils/builders/dashboard-filters.js +19 -2
- package/dist/runtime/form/components/actions/form-actions.vue +13 -127
- package/dist/runtime/form/components/page/form-page-actions.d.vue.ts +3 -0
- package/dist/runtime/form/components/page/form-page-actions.vue +40 -0
- package/dist/runtime/form/components/page/form-page-actions.vue.d.ts +3 -0
- package/dist/runtime/form/components/page/form-page-header.d.vue.ts +24 -0
- package/dist/runtime/form/components/page/form-page-header.vue +99 -0
- package/dist/runtime/form/components/page/form-page-header.vue.d.ts +24 -0
- package/dist/runtime/form/components/page/form-page-navigation.d.vue.ts +34 -0
- package/dist/runtime/form/components/page/form-page-navigation.vue +229 -0
- package/dist/runtime/form/components/page/form-page-navigation.vue.d.ts +34 -0
- package/dist/runtime/form/components/page/form-page-section.d.vue.ts +19 -0
- package/dist/runtime/form/components/page/form-page-section.vue +114 -0
- package/dist/runtime/form/components/page/form-page-section.vue.d.ts +19 -0
- package/dist/runtime/form/components/page/form-page-sections.d.vue.ts +21 -0
- package/dist/runtime/form/components/page/form-page-sections.vue +75 -0
- package/dist/runtime/form/components/page/form-page-sections.vue.d.ts +21 -0
- package/dist/runtime/form/components/page/form-page.d.vue.ts +80 -0
- package/dist/runtime/form/components/page/form-page.vue +91 -0
- package/dist/runtime/form/components/page/form-page.vue.d.ts +80 -0
- package/dist/runtime/form/components/renderer/form-field-shell.vue +7 -11
- package/dist/runtime/form/components/root/form.vue +25 -194
- package/dist/runtime/form/composables/use-field-options.js +19 -0
- package/dist/runtime/form/composables/use-form-action-buttons.d.ts +32 -0
- package/dist/runtime/form/composables/use-form-action-buttons.js +89 -0
- package/dist/runtime/form/composables/use-form-actions.d.ts +8 -5
- package/dist/runtime/form/composables/use-form-actions.js +1 -1
- package/dist/runtime/form/composables/use-form-page-scroll.d.ts +22 -0
- package/dist/runtime/form/composables/use-form-page-scroll.js +178 -0
- package/dist/runtime/form/composables/use-form-page.d.ts +114 -0
- package/dist/runtime/form/composables/use-form-page.js +83 -0
- package/dist/runtime/form/composables/use-form-root.d.ts +34 -0
- package/dist/runtime/form/composables/use-form-root.js +171 -0
- package/dist/runtime/form/composables/use-form-runtime.js +13 -4
- package/dist/runtime/form/composables/use-form.js +2 -22
- package/dist/runtime/form/composables/use-remote-field-options.js +21 -2
- package/dist/runtime/form/fields/checkbox-card/component.vue +28 -19
- package/dist/runtime/form/fields/checkbox-card/types.d.ts +2 -5
- package/dist/runtime/form/fields/choice-card/choice-card-label.d.vue.ts +17 -0
- package/dist/runtime/form/fields/choice-card/choice-card-label.vue +50 -0
- package/dist/runtime/form/fields/choice-card/choice-card-label.vue.d.ts +17 -0
- package/dist/runtime/form/fields/choice-card/types.d.ts +31 -0
- package/dist/runtime/form/fields/choice-card/types.js +0 -0
- package/dist/runtime/form/fields/choice-card/use-choice-card.d.ts +22 -0
- package/dist/runtime/form/fields/choice-card/use-choice-card.js +52 -0
- package/dist/runtime/form/fields/radio-card/component.vue +17 -18
- package/dist/runtime/form/fields/radio-card/types.d.ts +2 -4
- package/dist/runtime/form/index.d.ts +1 -1
- package/dist/runtime/form/index.js +7 -1
- package/dist/runtime/form/schema/index.d.ts +1 -1
- package/dist/runtime/form/schema/index.js +1 -0
- package/dist/runtime/form/schema/page.d.ts +50 -0
- package/dist/runtime/form/schema/page.js +25 -0
- package/dist/runtime/form/types/index.d.ts +1 -0
- package/dist/runtime/form/types/options.d.ts +22 -2
- package/dist/runtime/form/types/page.d.ts +172 -0
- package/dist/runtime/form/types/page.js +0 -0
- package/dist/runtime/form/types/ui.d.ts +60 -0
- package/dist/runtime/form/utils/controls.d.ts +15 -0
- package/dist/runtime/form/utils/controls.js +38 -0
- package/dist/runtime/form/utils/layout.js +3 -1
- package/dist/runtime/form/utils/page.d.ts +20 -0
- package/dist/runtime/form/utils/page.js +107 -0
- package/dist/runtime/form/utils/state.d.ts +2 -0
- package/dist/runtime/form/utils/state.js +1 -1
- package/dist/runtime/form/utils/ui.js +1 -0
- package/dist/runtime/i18n/locales/en.js +18 -0
- package/dist/runtime/i18n/locales/fr.js +18 -0
- package/dist/runtime/i18n/types.d.ts +29 -0
- package/dist/runtime/shared/index.d.ts +1 -0
- package/dist/runtime/shared/index.js +1 -0
- package/dist/runtime/shared/types/query.d.ts +5 -1
- package/dist/runtime/shared/types/remote-options.d.ts +56 -0
- package/dist/runtime/shared/utils/define-remote-options.d.ts +23 -0
- package/dist/runtime/shared/utils/define-remote-options.js +24 -0
- package/dist/runtime/shared/utils/remote-options-query.d.ts +6 -0
- package/dist/runtime/shared/utils/remote-options-query.js +18 -0
- package/dist/runtime/table/components/data-list/data-list-filter-tags.d.vue.ts +1 -1
- package/dist/runtime/table/components/data-list/data-list-filter-tags.vue +1 -1
- package/dist/runtime/table/components/data-list/data-list-filter-tags.vue.d.ts +1 -1
- package/dist/runtime/table/components/filters/panel/filter-panel.vue +14 -2
- package/dist/runtime/table/components/filters/shared/filter-option-multiple-list.vue +2 -2
- package/dist/runtime/table/components/filters/shared/filter-option-single-list.vue +2 -2
- package/dist/runtime/table/components/filters/shared/filter-option-tree-content.vue +5 -5
- package/dist/runtime/table/components/filters/shared/filter-trigger-tag.vue +7 -1
- package/dist/runtime/table/composables/use-table-filter-selected-options.js +16 -1
- package/dist/runtime/table/types/filters.d.ts +7 -0
- package/dist/runtime/table/types/remote-options.d.ts +3 -16
- package/dist/runtime/table/utils/remote-table-options.js +37 -37
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -89,7 +89,12 @@ function getPublicComponents(runtimeDir, options) {
|
|
|
89
89
|
filePath: `${runtimeDir}/form/components/provider/form-provider.vue`,
|
|
90
90
|
global: options.global,
|
|
91
91
|
name: `${prefix}FormProvider`
|
|
92
|
-
}
|
|
92
|
+
},
|
|
93
|
+
...["", "Header", "Navigation", "Sections", "Actions"].map((part) => ({
|
|
94
|
+
filePath: `${runtimeDir}/form/components/page/form-page${part ? `-${toKebabCase(part)}` : ""}.vue`,
|
|
95
|
+
global: options.global,
|
|
96
|
+
name: `${prefix}FormPage${part}`
|
|
97
|
+
}))
|
|
93
98
|
];
|
|
94
99
|
}
|
|
95
100
|
function toKebabCase(value) {
|
|
@@ -138,8 +143,9 @@ function setupImports(runtimeDir) {
|
|
|
138
143
|
{ from: "i18n", name: "provideUiToolsLocale" },
|
|
139
144
|
{ from: "i18n", name: "useUiToolsLocale" }
|
|
140
145
|
]),
|
|
141
|
-
// Shared
|
|
146
|
+
// Shared runtime
|
|
142
147
|
...withRuntime(runtimeDir, [
|
|
148
|
+
{ from: "shared", name: "defineRemoteOptions" },
|
|
143
149
|
{ from: "shared", name: "getResponsiveValue" },
|
|
144
150
|
{ from: "shared", name: "parseResponsiveValue" },
|
|
145
151
|
{ from: "shared", name: "resolveResponsiveValueAtBreakpoint" },
|
|
@@ -169,6 +175,8 @@ function setupImports(runtimeDir) {
|
|
|
169
175
|
...withRuntime(runtimeDir, [
|
|
170
176
|
{ from: "form", name: "defineFormField" },
|
|
171
177
|
{ from: "form", name: "defineFormFields" },
|
|
178
|
+
{ from: "form", name: "defineFormPageSchema" },
|
|
179
|
+
{ from: "form", name: "defineFormPageSection" },
|
|
172
180
|
{ from: "form", name: "defineFormSchema" },
|
|
173
181
|
{ from: "form", name: "useForm" },
|
|
174
182
|
{ from: "form", name: "useFormApi" }
|
|
@@ -10,7 +10,6 @@ import { mergeDashboardOptions, resolveDashboardOptionValues } from "../utils/op
|
|
|
10
10
|
import { runDashboardRemoteResult } from "../utils/remote.js";
|
|
11
11
|
export function useDashboardRemoteOptions(params) {
|
|
12
12
|
const { config } = params;
|
|
13
|
-
const queryKey = config.queryKey ?? params.queryKey;
|
|
14
13
|
const pageSize = Math.max(
|
|
15
14
|
1,
|
|
16
15
|
config.pagination?.size ?? DASHBOARD_REMOTE_OPTIONS_DEFAULTS.pageSize
|
|
@@ -22,6 +21,12 @@ export function useDashboardRemoteOptions(params) {
|
|
|
22
21
|
computed(() => search.value.trim()),
|
|
23
22
|
config.search?.debounce ?? DASHBOARD_REMOTE_OPTIONS_DEFAULTS.searchDebounce
|
|
24
23
|
);
|
|
24
|
+
function queryKeyFor(term2) {
|
|
25
|
+
return config.queryKeyFor?.({
|
|
26
|
+
page: { ...REMOTE_OPTIONS_FIRST_PAGE, size: pageSize },
|
|
27
|
+
search: term2
|
|
28
|
+
}) ?? config.queryKey ?? params.queryKey;
|
|
29
|
+
}
|
|
25
30
|
const pages = useInfiniteQuery(
|
|
26
31
|
computed(() => ({
|
|
27
32
|
enabled: (open.value || search.value !== "") && (term.value.length === 0 || term.value.length >= minLength),
|
|
@@ -33,7 +38,7 @@ export function useDashboardRemoteOptions(params) {
|
|
|
33
38
|
config.load({ page: { ...context.pageParam, size: pageSize }, search: term.value }),
|
|
34
39
|
context
|
|
35
40
|
),
|
|
36
|
-
queryKey: [...
|
|
41
|
+
queryKey: config.queryKeyFor ? [...queryKeyFor(term.value), { remoteOptionPages: pageSize }] : [...queryKeyFor(term.value), "options", term.value],
|
|
37
42
|
staleTime: DASHBOARD_REMOTE_OPTIONS_DEFAULTS.staleTime
|
|
38
43
|
}))
|
|
39
44
|
);
|
|
@@ -50,7 +55,7 @@ export function useDashboardRemoteOptions(params) {
|
|
|
50
55
|
computed(() => ({
|
|
51
56
|
enabled: Boolean(config.resolveSelected) && missing.value.length > 0,
|
|
52
57
|
queryFn: (context) => config.resolveSelected ? runDashboardRemoteResult(config.resolveSelected({ values: missing.value }), context) : Promise.resolve([]),
|
|
53
|
-
queryKey: [...
|
|
58
|
+
queryKey: config.selectedQueryKeyFor ? config.selectedQueryKeyFor({ values: missing.value }) : [...queryKeyFor(""), "selected", missing.value],
|
|
54
59
|
// Hydrated labels stay while the next set of missing values resolves.
|
|
55
60
|
placeholderData: (previous) => previous,
|
|
56
61
|
staleTime: DASHBOARD_REMOTE_OPTIONS_DEFAULTS.selectedStaleTime
|
|
@@ -150,8 +150,14 @@ export interface DashboardRemoteOptionsConfig {
|
|
|
150
150
|
/** Defaults to `{ type: 'page', size: 25 }`. */
|
|
151
151
|
pagination?: RemoteOptionsPagination;
|
|
152
152
|
search?: RemoteOptionsSearch;
|
|
153
|
-
/** Cache identity of
|
|
153
|
+
/** Cache identity of an inline source. Defaults to a key derived from the filter location. */
|
|
154
154
|
queryKey?: QueryKey;
|
|
155
|
+
/** Cache identity of a reusable loader, derived from its endpoint request and current search. */
|
|
156
|
+
queryKeyFor?: (request: DashboardRemoteOptionsRequest) => QueryKey;
|
|
157
|
+
/** Cache identity of a selected lookup, including scope absent from the page endpoint. */
|
|
158
|
+
selectedQueryKeyFor?: (request: {
|
|
159
|
+
values: readonly string[];
|
|
160
|
+
}) => QueryKey;
|
|
155
161
|
}
|
|
156
162
|
/** Option items of a filter: fixed, or read reactively (e.g. from another query's data). */
|
|
157
163
|
export type DashboardFilterItems = readonly DashboardOption[] | (() => readonly DashboardOption[]);
|
|
@@ -79,8 +79,25 @@ function optionsFilter(items, options) {
|
|
|
79
79
|
return defineFilter("options", codec, { ...options, items });
|
|
80
80
|
}
|
|
81
81
|
function remoteFilter(config, extra) {
|
|
82
|
-
const {
|
|
83
|
-
|
|
82
|
+
const {
|
|
83
|
+
load,
|
|
84
|
+
pagination,
|
|
85
|
+
queryKey,
|
|
86
|
+
queryKeyFor,
|
|
87
|
+
resolveSelected,
|
|
88
|
+
search,
|
|
89
|
+
selectedQueryKeyFor,
|
|
90
|
+
...options
|
|
91
|
+
} = config;
|
|
92
|
+
const remote = {
|
|
93
|
+
load,
|
|
94
|
+
pagination,
|
|
95
|
+
queryKey,
|
|
96
|
+
queryKeyFor,
|
|
97
|
+
resolveSelected,
|
|
98
|
+
search,
|
|
99
|
+
selectedQueryKeyFor
|
|
100
|
+
};
|
|
84
101
|
return defineFilter("remote", optionalStringCodec, {
|
|
85
102
|
searchable: true,
|
|
86
103
|
...options,
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import UButton from "@nuxt/ui/components/Button.vue";
|
|
3
3
|
import { computed } from "vue";
|
|
4
|
-
import {
|
|
4
|
+
import { useFormActionButtons } from "../../composables/use-form-action-buttons";
|
|
5
5
|
import { useFormUi } from "../../composables/use-form-ui";
|
|
6
|
-
import { createPublicFormApi } from "../../utils/api";
|
|
7
|
-
import { invokeFormFunction, isString } from "../../utils/predicate";
|
|
8
|
-
import { resolveFormText } from "../../utils/text";
|
|
9
6
|
import { mergeFormUiClass } from "../../utils/ui";
|
|
10
7
|
const props = defineProps({
|
|
11
8
|
runtime: { type: Object, required: true },
|
|
@@ -13,117 +10,24 @@ const props = defineProps({
|
|
|
13
10
|
});
|
|
14
11
|
const formUi = useFormUi();
|
|
15
12
|
const emit = defineEmits(["cancel"]);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
isLastStep: props.runtime.isLastStep.value,
|
|
22
|
-
isMultiStep: props.runtime.isStepped.value
|
|
23
|
-
}));
|
|
24
|
-
const visibleActions = computed(
|
|
25
|
-
() => props.actions.filter((action) => action.condition?.(actionContext.value) ?? true)
|
|
26
|
-
);
|
|
13
|
+
const buttons = useFormActionButtons({
|
|
14
|
+
actions: () => props.actions,
|
|
15
|
+
onCancel: () => emit("cancel"),
|
|
16
|
+
runtime: props.runtime
|
|
17
|
+
});
|
|
27
18
|
const actionsLeft = computed(
|
|
28
|
-
() =>
|
|
19
|
+
() => buttons.visible.value.filter((action) => buttons.slot(action) === "left")
|
|
29
20
|
);
|
|
30
21
|
const actionsRight = computed(
|
|
31
|
-
() =>
|
|
22
|
+
() => buttons.visible.value.filter((action) => buttons.slot(action) !== "left")
|
|
32
23
|
);
|
|
33
|
-
async function runAction(action) {
|
|
34
|
-
if (isBuiltInAction(action, "submit")) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (isBuiltInAction(action, "next")) {
|
|
38
|
-
await props.runtime.nextStep();
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (isBuiltInAction(action, "previous")) {
|
|
42
|
-
await props.runtime.previousStep();
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (isBuiltInAction(action, "reset")) {
|
|
46
|
-
await props.runtime.reset();
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (isBuiltInAction(action, "cancel")) {
|
|
50
|
-
emit("cancel");
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if ("action" in action) {
|
|
54
|
-
await action.action?.(actionContext.value);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function resolveActionLabel(action) {
|
|
58
|
-
return resolveFormText(action.label);
|
|
59
|
-
}
|
|
60
|
-
function resolveActionSlot(action) {
|
|
61
|
-
const slot = getResponsiveValue(action.slot ?? "right");
|
|
62
|
-
return slot === "left" ? "left" : "right";
|
|
63
|
-
}
|
|
64
|
-
function resolveActionWidth(action) {
|
|
65
|
-
const width = getResponsiveValue(action.width ?? "fit");
|
|
66
|
-
return width === "fill" ? "fill" : "fit";
|
|
67
|
-
}
|
|
68
|
-
function resolveActionLink(action) {
|
|
69
|
-
if (!("link" in action)) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (isString(action.link)) {
|
|
73
|
-
return action.link;
|
|
74
|
-
}
|
|
75
|
-
const link = invokeFormFunction(action.link, [actionContext.value]);
|
|
76
|
-
return isString(link) ? link : void 0;
|
|
77
|
-
}
|
|
78
|
-
function isActionDisabled(action) {
|
|
79
|
-
const disabled = action.disabled?.(actionContext.value) ?? false;
|
|
80
|
-
if (disabled) {
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
if (!props.runtime.actionPending.value) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
return !isActionLoading(action);
|
|
87
|
-
}
|
|
88
|
-
function isActionLoading(action) {
|
|
89
|
-
if (!isBuiltInActionKey(action.key)) {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
const pending = props.runtime.actionPending.value;
|
|
93
|
-
if (pending === null) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
return action.key === pending;
|
|
97
|
-
}
|
|
98
|
-
function resolveActionColor(action) {
|
|
99
|
-
if (action.color) {
|
|
100
|
-
return action.color;
|
|
101
|
-
}
|
|
102
|
-
if (action.type === "primary") {
|
|
103
|
-
return "primary";
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function resolveActionVariant(action) {
|
|
107
|
-
if (action.variant) {
|
|
108
|
-
return action.variant;
|
|
109
|
-
}
|
|
110
|
-
if (action.type === "primary") {
|
|
111
|
-
return "solid";
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
24
|
function actionButtonClass(action) {
|
|
115
25
|
return mergeFormUiClass(
|
|
116
|
-
|
|
26
|
+
buttons.fills(action) ? "flex-1 justify-center" : void 0,
|
|
117
27
|
formUi.ui.value.actions?.ui?.button,
|
|
118
28
|
action.class
|
|
119
29
|
);
|
|
120
30
|
}
|
|
121
|
-
function isBuiltInAction(action, key) {
|
|
122
|
-
return action.key === key;
|
|
123
|
-
}
|
|
124
|
-
function isBuiltInActionKey(value) {
|
|
125
|
-
return value === "reset" || value === "cancel" || value === "submit" || value === "previous" || value === "next";
|
|
126
|
-
}
|
|
127
31
|
</script>
|
|
128
32
|
|
|
129
33
|
<template>
|
|
@@ -147,18 +51,9 @@ function isBuiltInActionKey(value) {
|
|
|
147
51
|
<UButton
|
|
148
52
|
v-for="(action, index) in actionsLeft"
|
|
149
53
|
:key="action.key ?? index"
|
|
150
|
-
|
|
151
|
-
:to="resolveActionLink(action)"
|
|
152
|
-
:label="resolveActionLabel(action)"
|
|
153
|
-
:icon="action.icon === false ? void 0 : action.icon"
|
|
154
|
-
:trailing-icon="action.trailingIcon === false ? void 0 : action.trailingIcon"
|
|
155
|
-
:color="resolveActionColor(action)"
|
|
156
|
-
:variant="resolveActionVariant(action)"
|
|
157
|
-
:size="action.size ?? formUi.controlSize.value"
|
|
54
|
+
v-bind="buttons.button(action)"
|
|
158
55
|
:class="actionButtonClass(action)"
|
|
159
|
-
|
|
160
|
-
:loading="isActionLoading(action)"
|
|
161
|
-
@click="runAction(action)"
|
|
56
|
+
@click="buttons.run(action)"
|
|
162
57
|
/>
|
|
163
58
|
</div>
|
|
164
59
|
|
|
@@ -174,18 +69,9 @@ function isBuiltInActionKey(value) {
|
|
|
174
69
|
<UButton
|
|
175
70
|
v-for="(action, index) in actionsRight"
|
|
176
71
|
:key="action.key ?? index"
|
|
177
|
-
|
|
178
|
-
:to="resolveActionLink(action)"
|
|
179
|
-
:label="resolveActionLabel(action)"
|
|
180
|
-
:icon="action.icon === false ? void 0 : action.icon"
|
|
181
|
-
:trailing-icon="action.trailingIcon === false ? void 0 : action.trailingIcon"
|
|
182
|
-
:color="resolveActionColor(action)"
|
|
183
|
-
:variant="resolveActionVariant(action)"
|
|
184
|
-
:size="action.size ?? formUi.controlSize.value"
|
|
72
|
+
v-bind="buttons.button(action)"
|
|
185
73
|
:class="actionButtonClass(action)"
|
|
186
|
-
|
|
187
|
-
:loading="isActionLoading(action)"
|
|
188
|
-
@click="runAction(action)"
|
|
74
|
+
@click="buttons.run(action)"
|
|
189
75
|
/>
|
|
190
76
|
</div>
|
|
191
77
|
</div>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import UButton from "@nuxt/ui/components/Button.vue";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import { useFormActionButtons } from "../../composables/use-form-action-buttons";
|
|
5
|
+
import { useFormActions } from "../../composables/use-form-actions";
|
|
6
|
+
import { useFormPageContext } from "../../composables/use-form-page";
|
|
7
|
+
import { useFormUi } from "../../composables/use-form-ui";
|
|
8
|
+
import { mergeFormUiClass } from "../../utils/ui";
|
|
9
|
+
const page = useFormPageContext();
|
|
10
|
+
const formUi = useFormUi();
|
|
11
|
+
const pageUi = computed(() => formUi.ui.value.page?.ui);
|
|
12
|
+
const actions = useFormActions({ runtime: page.root.runtime, slot: () => "right" });
|
|
13
|
+
const buttons = useFormActionButtons({
|
|
14
|
+
actions: () => actions.value,
|
|
15
|
+
onCancel: page.cancel,
|
|
16
|
+
runtime: page.root.runtime
|
|
17
|
+
});
|
|
18
|
+
function buttonClass(action) {
|
|
19
|
+
return mergeFormUiClass(
|
|
20
|
+
buttons.fills(action) ? "flex-1 justify-center" : void 0,
|
|
21
|
+
formUi.ui.value.actions?.ui?.button,
|
|
22
|
+
action.class
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<div
|
|
29
|
+
:class="mergeFormUiClass('flex shrink-0 flex-wrap items-center gap-2', pageUi?.actions)"
|
|
30
|
+
data-form-page-actions
|
|
31
|
+
>
|
|
32
|
+
<UButton
|
|
33
|
+
v-for="(action, index) in buttons.visible.value"
|
|
34
|
+
:key="action.key ?? index"
|
|
35
|
+
v-bind="buttons.button(action)"
|
|
36
|
+
:class="buttonClass(action)"
|
|
37
|
+
@click="buttons.run(action)"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header of a form page: the schema `header` (eyebrow, title, description), an unsaved-changes
|
|
3
|
+
* badge while a form with `controls.dirtyCheck` is modified, and the page actions. It stays
|
|
4
|
+
* pinned while the page scrolls on wide layouts, and sections scroll to just below it.
|
|
5
|
+
*/
|
|
6
|
+
type __VLS_Slots = {
|
|
7
|
+
/** Before the heading, e.g. an avatar of the record. */
|
|
8
|
+
leading?: () => unknown;
|
|
9
|
+
eyebrow?: () => unknown;
|
|
10
|
+
title?: () => unknown;
|
|
11
|
+
/** Replaces the description, keeping the unsaved-changes badge after it. */
|
|
12
|
+
description?: () => unknown;
|
|
13
|
+
/** Replaces the page actions. */
|
|
14
|
+
actions?: () => unknown;
|
|
15
|
+
};
|
|
16
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
17
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
20
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
21
|
+
new (): {
|
|
22
|
+
$slots: S;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import UBadge from "@nuxt/ui/components/Badge.vue";
|
|
3
|
+
import { useResizeObserver } from "@vueuse/core";
|
|
4
|
+
import { computed, onBeforeUnmount, useTemplateRef } from "vue";
|
|
5
|
+
import { useUiToolsLocale } from "../../../i18n/use-locale";
|
|
6
|
+
import { useFormPageContext } from "../../composables/use-form-page";
|
|
7
|
+
import { useFormUi } from "../../composables/use-form-ui";
|
|
8
|
+
import { getFormHeader } from "../../utils/overlay";
|
|
9
|
+
import { isRecord } from "../../utils/path";
|
|
10
|
+
import { resolveFormBoundaryText } from "../../utils/text";
|
|
11
|
+
import { mergeFormUiClass } from "../../utils/ui";
|
|
12
|
+
import FormPageActions from "./form-page-actions.vue";
|
|
13
|
+
defineSlots();
|
|
14
|
+
const page = useFormPageContext();
|
|
15
|
+
const formUi = useFormUi();
|
|
16
|
+
const { t } = useUiToolsLocale();
|
|
17
|
+
const pageUi = computed(() => formUi.ui.value.page?.ui);
|
|
18
|
+
const header = computed(() => getFormHeader(page.root.schema.value));
|
|
19
|
+
const eyebrow = computed(() => headerText(header.value, "eyebrow"));
|
|
20
|
+
const title = computed(() => headerText(header.value, "title"));
|
|
21
|
+
const description = computed(() => headerText(header.value, "description"));
|
|
22
|
+
const unsaved = computed(() => page.dirtyCheck.value && page.root.runtime.isDirty.value);
|
|
23
|
+
const element = useTemplateRef("element");
|
|
24
|
+
useResizeObserver(element, () => {
|
|
25
|
+
const node = element.value;
|
|
26
|
+
page.stickyOffset.value = node && getComputedStyle(node).position === "sticky" ? node.offsetHeight : 0;
|
|
27
|
+
});
|
|
28
|
+
onBeforeUnmount(() => {
|
|
29
|
+
page.stickyOffset.value = 0;
|
|
30
|
+
});
|
|
31
|
+
function headerText(config, key) {
|
|
32
|
+
return isRecord(config) ? resolveFormBoundaryText(config[key]) : void 0;
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<header
|
|
38
|
+
ref="element"
|
|
39
|
+
:class="
|
|
40
|
+
mergeFormUiClass(
|
|
41
|
+
'z-10 flex flex-col gap-3 border-b border-default bg-default px-4 py-4 @3xl/form-page:sticky @3xl/form-page:top-0 @3xl/form-page:flex-row @3xl/form-page:items-center @3xl/form-page:justify-between @3xl/form-page:gap-5 @3xl/form-page:px-7 @3xl/form-page:pt-[18px] @3xl/form-page:pb-4',
|
|
42
|
+
pageUi?.header
|
|
43
|
+
)
|
|
44
|
+
"
|
|
45
|
+
data-form-page-header
|
|
46
|
+
>
|
|
47
|
+
<div :class="mergeFormUiClass('flex min-w-0 items-center gap-3.5', pageUi?.headerContent)">
|
|
48
|
+
<slot name="leading" />
|
|
49
|
+
<div :class="mergeFormUiClass('grid min-w-0 gap-1.5', pageUi?.heading)">
|
|
50
|
+
<p
|
|
51
|
+
v-if="eyebrow || $slots.eyebrow"
|
|
52
|
+
:class="
|
|
53
|
+
mergeFormUiClass(
|
|
54
|
+
'text-[11px] font-semibold tracking-[0.08em] text-dimmed uppercase',
|
|
55
|
+
pageUi?.eyebrow
|
|
56
|
+
)
|
|
57
|
+
"
|
|
58
|
+
>
|
|
59
|
+
<slot name="eyebrow">{{ eyebrow }}</slot>
|
|
60
|
+
</p>
|
|
61
|
+
<h1
|
|
62
|
+
v-if="title || $slots.title"
|
|
63
|
+
:class="
|
|
64
|
+
mergeFormUiClass(
|
|
65
|
+
'm-0 truncate text-[22px] leading-[1.1] font-semibold tracking-[-0.025em] text-highlighted @3xl/form-page:text-[28px]',
|
|
66
|
+
pageUi?.title
|
|
67
|
+
)
|
|
68
|
+
"
|
|
69
|
+
>
|
|
70
|
+
<slot name="title">{{ title }}</slot>
|
|
71
|
+
</h1>
|
|
72
|
+
<div
|
|
73
|
+
v-if="description || unsaved || $slots.description"
|
|
74
|
+
:class="
|
|
75
|
+
mergeFormUiClass(
|
|
76
|
+
'flex flex-wrap items-center gap-2.5 text-[13.5px] text-muted',
|
|
77
|
+
pageUi?.meta
|
|
78
|
+
)
|
|
79
|
+
"
|
|
80
|
+
>
|
|
81
|
+
<slot name="description">
|
|
82
|
+
<span v-if="description">{{ description }}</span>
|
|
83
|
+
</slot>
|
|
84
|
+
<UBadge
|
|
85
|
+
v-if="unsaved"
|
|
86
|
+
color="warning"
|
|
87
|
+
variant="soft"
|
|
88
|
+
:label="t('form.page.unsavedChanges')"
|
|
89
|
+
:class="pageUi?.unsaved"
|
|
90
|
+
data-form-page-unsaved
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<slot name="actions">
|
|
96
|
+
<FormPageActions />
|
|
97
|
+
</slot>
|
|
98
|
+
</header>
|
|
99
|
+
</template>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header of a form page: the schema `header` (eyebrow, title, description), an unsaved-changes
|
|
3
|
+
* badge while a form with `controls.dirtyCheck` is modified, and the page actions. It stays
|
|
4
|
+
* pinned while the page scrolls on wide layouts, and sections scroll to just below it.
|
|
5
|
+
*/
|
|
6
|
+
type __VLS_Slots = {
|
|
7
|
+
/** Before the heading, e.g. an avatar of the record. */
|
|
8
|
+
leading?: () => unknown;
|
|
9
|
+
eyebrow?: () => unknown;
|
|
10
|
+
title?: () => unknown;
|
|
11
|
+
/** Replaces the description, keeping the unsaved-changes badge after it. */
|
|
12
|
+
description?: () => unknown;
|
|
13
|
+
/** Replaces the page actions. */
|
|
14
|
+
actions?: () => unknown;
|
|
15
|
+
};
|
|
16
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
17
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
export default _default;
|
|
20
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
21
|
+
new (): {
|
|
22
|
+
$slots: S;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FormPageSectionState } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Navigation of a form page: one entry per visible section, following the section in view.
|
|
4
|
+
* An entry shows whether its section is complete, invalid, or still to fill, marks optional
|
|
5
|
+
* sections, and, with `controls.dirtyCheck`, a dot on modified sections. The footer sums up
|
|
6
|
+
* the sections left to complete, or the modified ones with dirty checking.
|
|
7
|
+
*
|
|
8
|
+
* From 768px of page width it is a pinned column; below, a row of chips that scrolls sideways.
|
|
9
|
+
*/
|
|
10
|
+
type __VLS_Slots = {
|
|
11
|
+
/** Replaces the heading, which comes from the schema `navigation.title`. */
|
|
12
|
+
title?: () => unknown;
|
|
13
|
+
/** Replaces an entry. Call `select` to scroll to the section. */
|
|
14
|
+
item?: (props: {
|
|
15
|
+
section: FormPageSectionState;
|
|
16
|
+
active: boolean;
|
|
17
|
+
select: () => void;
|
|
18
|
+
}) => unknown;
|
|
19
|
+
/** Replaces the summary under the entries. */
|
|
20
|
+
footer?: (props: {
|
|
21
|
+
sections: readonly FormPageSectionState[];
|
|
22
|
+
remaining: number;
|
|
23
|
+
modified: number;
|
|
24
|
+
}) => unknown;
|
|
25
|
+
};
|
|
26
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
27
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
28
|
+
declare const _default: typeof __VLS_export;
|
|
29
|
+
export default _default;
|
|
30
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
31
|
+
new (): {
|
|
32
|
+
$slots: S;
|
|
33
|
+
};
|
|
34
|
+
};
|