nuxt-ui-tools 1.8.1 → 1.10.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/runtime/dashboard/components/dashboard-stat.vue +4 -2
- package/dist/runtime/dashboard/components/dashboard-stats.vue +11 -2
- package/dist/runtime/dashboard/composables/use-dashboard-tween.d.ts +16 -0
- package/dist/runtime/dashboard/composables/use-dashboard-tween.js +20 -0
- package/dist/runtime/form/composables/use-field-control.d.ts +1 -1
- package/dist/runtime/form/composables/use-field-effects.js +2 -1
- package/dist/runtime/form/composables/use-field-options.js +7 -7
- package/dist/runtime/form/composables/use-remote-field-options.js +4 -4
- package/dist/runtime/form/fields/array-list/use-array-items.d.ts +1 -1
- package/dist/runtime/form/schema/page.d.ts +1 -1
- package/dist/runtime/form/types/callbacks.d.ts +19 -7
- package/dist/runtime/form/types/dependencies.d.ts +3 -1
- package/dist/runtime/form/types/field-base.d.ts +5 -5
- package/dist/runtime/form/types/field.d.ts +2 -2
- package/dist/runtime/form/types/options.d.ts +22 -15
- package/dist/runtime/form/types/validation.d.ts +2 -2
- package/dist/runtime/form/utils/dependencies.d.ts +13 -2
- package/dist/runtime/form/utils/dependencies.js +26 -14
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -6,6 +6,7 @@ import { useUiToolsLocale } from "#ui-tools/i18n";
|
|
|
6
6
|
import { isNumber, isString } from "#ui-tools/shared/utils/predicate";
|
|
7
7
|
import { resolveTextValue } from "#ui-tools/shared/utils/render";
|
|
8
8
|
import { DASHBOARD_MISSING_VALUE, useDashboardFormat } from "../composables/use-dashboard-format";
|
|
9
|
+
import { countedFigure, tweenTarget, useDashboardTween } from "../composables/use-dashboard-tween";
|
|
9
10
|
import { useDashboardUi } from "../composables/use-dashboard-ui";
|
|
10
11
|
import { resolveDashboardColor } from "../utils/charts";
|
|
11
12
|
import { resolveDashboardSparkBars, resolveDashboardSparkline } from "../utils/sparkline";
|
|
@@ -73,11 +74,12 @@ const ready = computed(() => {
|
|
|
73
74
|
});
|
|
74
75
|
const resolved = computed(() => ready.value ? value(ready.value.data) : void 0);
|
|
75
76
|
const formatValue = computed(() => formats.resolve(format));
|
|
77
|
+
const counted = useDashboardTween(() => [tweenTarget(resolved.value)]);
|
|
76
78
|
const display = computed(() => {
|
|
77
79
|
if (!ready.value) return "";
|
|
78
80
|
const shown = resolved.value;
|
|
79
81
|
if (shown === null || shown === void 0) return DASHBOARD_MISSING_VALUE;
|
|
80
|
-
return isNumber(shown) ? formatValue.value(shown) : resolveTextValue(shown);
|
|
82
|
+
return isNumber(shown) ? formatValue.value(countedFigure(shown, counted.value[0])) : resolveTextValue(shown);
|
|
81
83
|
});
|
|
82
84
|
const previous = computed(() => {
|
|
83
85
|
if (!ready.value || !compare) return null;
|
|
@@ -201,7 +203,7 @@ const extra = computed(() => trend ? "trend" : goal ? "goal" : void 0);
|
|
|
201
203
|
</template>
|
|
202
204
|
|
|
203
205
|
<template v-if="ready">
|
|
204
|
-
<div :class="classes.value">
|
|
206
|
+
<div :class="classes.value" data-stat-value>
|
|
205
207
|
<slot name="value" :data="ready.data" :value="display">{{ display }}</slot>
|
|
206
208
|
</div>
|
|
207
209
|
<div v-if="change || captionText || $slots.caption" :class="classes.meta">
|
|
@@ -6,6 +6,7 @@ import { useResponsiveValue } from "#ui-tools/shared/composables/use-responsive-
|
|
|
6
6
|
import { isNumber, isString } from "#ui-tools/shared/utils/predicate";
|
|
7
7
|
import { resolveTextValue } from "#ui-tools/shared/utils/render";
|
|
8
8
|
import { useDashboardFormat } from "../composables/use-dashboard-format";
|
|
9
|
+
import { countedFigure, tweenTarget, useDashboardTween } from "../composables/use-dashboard-tween";
|
|
9
10
|
import { useDashboardUi } from "../composables/use-dashboard-ui";
|
|
10
11
|
import { resolveDashboardColor } from "../utils/charts";
|
|
11
12
|
import { toDashboardCell } from "../utils/export";
|
|
@@ -100,6 +101,14 @@ const entries = computed(() => {
|
|
|
100
101
|
};
|
|
101
102
|
});
|
|
102
103
|
});
|
|
104
|
+
const counted = useDashboardTween(
|
|
105
|
+
() => items.map((_item, index) => tweenTarget(entries.value[index]?.raw))
|
|
106
|
+
);
|
|
107
|
+
const shown = computed(
|
|
108
|
+
() => entries.value.map(
|
|
109
|
+
(entry, index) => isNumber(entry.raw) ? formats.resolve(items[index]?.format)(countedFigure(entry.raw, counted.value[index])) : entry.value
|
|
110
|
+
)
|
|
111
|
+
);
|
|
103
112
|
function tabulate() {
|
|
104
113
|
const hasDelta = items.some((item) => item.delta);
|
|
105
114
|
return {
|
|
@@ -142,7 +151,7 @@ function tabulate() {
|
|
|
142
151
|
</template>
|
|
143
152
|
|
|
144
153
|
<div :class="classes.grid" :style="gridColumns ?? void 0" :data-variant="variant">
|
|
145
|
-
<div v-for="entry in entries" :key="entry.key" :class="classes.item" data-stats-item>
|
|
154
|
+
<div v-for="(entry, index) in entries" :key="entry.key" :class="classes.item" data-stats-item>
|
|
146
155
|
<div class="flex min-w-0 items-center gap-2">
|
|
147
156
|
<span
|
|
148
157
|
v-if="entry.icon"
|
|
@@ -157,7 +166,7 @@ function tabulate() {
|
|
|
157
166
|
<span :class="classes.label">{{ entry.label }}</span>
|
|
158
167
|
</div>
|
|
159
168
|
<div :class="classes.value">
|
|
160
|
-
<span class="truncate">{{
|
|
169
|
+
<span class="truncate">{{ shown[index] }}</span>
|
|
161
170
|
<span
|
|
162
171
|
v-if="entry.status"
|
|
163
172
|
:class="classes.status"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
/** How long a figure takes to count toward a new value. */
|
|
3
|
+
export declare const DASHBOARD_TWEEN_DURATION = 700;
|
|
4
|
+
/**
|
|
5
|
+
* Counts figures toward their new values instead of jumping to them: on the first load, a filter
|
|
6
|
+
* change, or a refresh. Keep the list the same length between updates, since each position counts
|
|
7
|
+
* from its previous value. Figures change at once when the user prefers reduced motion.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useDashboardTween(figures: MaybeRefOrGetter<number[]>): import("vue").ComputedRef<number[]>;
|
|
10
|
+
/**
|
|
11
|
+
* The figure to show while counting toward `target`. Whole numbers count through whole numbers, so
|
|
12
|
+
* a count never reads "12.3" on its way to 21; values that cannot be counted show as they are.
|
|
13
|
+
*/
|
|
14
|
+
export declare function countedFigure(target: number, current: number | undefined): number;
|
|
15
|
+
/** A figure a tween can count toward: finite numbers, and zero for anything else. */
|
|
16
|
+
export declare function tweenTarget(value: unknown): number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TransitionPresets, usePreferredReducedMotion, useTransition } from "@vueuse/core";
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { isNumber } from "../../shared/utils/predicate.js";
|
|
4
|
+
export const DASHBOARD_TWEEN_DURATION = 700;
|
|
5
|
+
export function useDashboardTween(figures) {
|
|
6
|
+
const reducedMotion = usePreferredReducedMotion();
|
|
7
|
+
return useTransition(figures, {
|
|
8
|
+
disabled: computed(() => reducedMotion.value === "reduce"),
|
|
9
|
+
duration: DASHBOARD_TWEEN_DURATION,
|
|
10
|
+
transition: TransitionPresets.easeOutCubic
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function countedFigure(target, current) {
|
|
14
|
+
if (!Number.isFinite(target)) return target;
|
|
15
|
+
const shown = current !== void 0 && Number.isFinite(current) ? current : target;
|
|
16
|
+
return Number.isInteger(target) ? Math.round(shown) : shown;
|
|
17
|
+
}
|
|
18
|
+
export function tweenTarget(value) {
|
|
19
|
+
return isNumber(value) && Number.isFinite(value) ? value : 0;
|
|
20
|
+
}
|
|
@@ -54,7 +54,7 @@ export declare function useFieldControl<TField extends FormField>(field: () => T
|
|
|
54
54
|
selectedItems: import("vue").ComputedRef<readonly import("../utils/options.js").ResolvedFormOption[]>;
|
|
55
55
|
setSearch: (term: string) => void;
|
|
56
56
|
};
|
|
57
|
-
params: import("vue").ComputedRef<import("../types/index.js").FormFieldCallbackParams<{},
|
|
57
|
+
params: import("vue").ComputedRef<import("../types/index.js").FormFieldCallbackParams<{}, import("../types/index.js").FormDependencies, unknown, unknown>>;
|
|
58
58
|
placeholder: import("vue").ComputedRef<string>;
|
|
59
59
|
validationPending: import("vue").ComputedRef<boolean>;
|
|
60
60
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { debounceFilter, throttleFilter, watchWithFilter } from "@vueuse/core";
|
|
2
2
|
import { computed, onMounted } from "vue";
|
|
3
3
|
import { isEqualFormValue } from "../utils/compare.js";
|
|
4
|
+
import { readFormDependencyValues } from "../utils/dependencies.js";
|
|
4
5
|
import { cloneFormValue } from "../utils/path.js";
|
|
5
6
|
import { isFunction, isNumber, isObject } from "../utils/predicate.js";
|
|
6
7
|
import { useFormRuntimeContext } from "./use-form-runtime.js";
|
|
@@ -29,7 +30,7 @@ export function useFieldEffects(field, path) {
|
|
|
29
30
|
}
|
|
30
31
|
);
|
|
31
32
|
watchWithFilter(
|
|
32
|
-
() => params.value.deps,
|
|
33
|
+
() => readFormDependencyValues(params.value.deps),
|
|
33
34
|
(deps) => {
|
|
34
35
|
if (lastDeps !== UNSET && isEqualFormValue(deps, lastDeps)) {
|
|
35
36
|
return;
|
|
@@ -64,6 +64,9 @@ export function useFieldOptions(params) {
|
|
|
64
64
|
() => contextResources.value.find((resource) => resource.error !== null)?.error ?? null
|
|
65
65
|
);
|
|
66
66
|
const optionConfig = computed(() => resolveOptionConfig(params.field()));
|
|
67
|
+
const declaresOptions = computed(
|
|
68
|
+
() => createFormFieldInstance(params.field()).capability.has("options") && !isUndefined(Object.getOwnPropertyDescriptor(params.field(), "options")?.value)
|
|
69
|
+
);
|
|
67
70
|
const querySource = computed(() => {
|
|
68
71
|
const source = resolvedSource.value;
|
|
69
72
|
return isRuntimeQueryOptions(source) ? source : null;
|
|
@@ -303,13 +306,10 @@ export function useFieldOptions(params) {
|
|
|
303
306
|
params.api.value.value.set([...selected, value]);
|
|
304
307
|
}
|
|
305
308
|
function clearInvalidValue(options) {
|
|
306
|
-
if (
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
if (pending.value) {
|
|
309
|
+
if (remoteConfig.value || pending.value || error.value !== null) {
|
|
310
310
|
return;
|
|
311
311
|
}
|
|
312
|
-
if (optionConfig.value?.clearOnInvalid === false) {
|
|
312
|
+
if (!declaresOptions.value || optionConfig.value?.clearOnInvalid === false) {
|
|
313
313
|
return;
|
|
314
314
|
}
|
|
315
315
|
const current = params.api.value.value.get();
|
|
@@ -359,7 +359,7 @@ function resolveRemoteOptionConfig(field) {
|
|
|
359
359
|
resolveSelected: loader.resolveSelected,
|
|
360
360
|
search: loader.search,
|
|
361
361
|
selectedQueryKeyFor: loader.selectedQueryKeyFor,
|
|
362
|
-
|
|
362
|
+
load: loader.load
|
|
363
363
|
};
|
|
364
364
|
}
|
|
365
365
|
return isRemoteOptionConfig(options) ? options : null;
|
|
@@ -372,7 +372,7 @@ function isRemoteLoaderOptionConfig(value) {
|
|
|
372
372
|
return isFunction(loader.load) && isFunction(loader.resolveSelected) && isRecord(loader.pagination);
|
|
373
373
|
}
|
|
374
374
|
function isRemoteOptionConfig(value) {
|
|
375
|
-
return isRecord(value) && value.mode === "remote" && isFunction(Object.getOwnPropertyDescriptor(value, "
|
|
375
|
+
return isRecord(value) && value.mode === "remote" && isFunction(Object.getOwnPropertyDescriptor(value, "load")?.value) && isRecord(value.pagination);
|
|
376
376
|
}
|
|
377
377
|
function resolveOptionConfig(field) {
|
|
378
378
|
if (!createFormFieldInstance(field).capability.has("options")) {
|
|
@@ -139,7 +139,7 @@ export function useRemoteFieldOptions(params) {
|
|
|
139
139
|
sourceFetching.value = true;
|
|
140
140
|
try {
|
|
141
141
|
const result = await resolveRemoteResult(
|
|
142
|
-
config.
|
|
142
|
+
config.load({
|
|
143
143
|
...params.callbackParams.value,
|
|
144
144
|
page: { cursor: page.cursor, index: page.index, size: pageSize.value },
|
|
145
145
|
search: search.value
|
|
@@ -310,7 +310,7 @@ export function useRemoteFieldOptions(params) {
|
|
|
310
310
|
async function loadChildrenPage(config, option, key, domain) {
|
|
311
311
|
try {
|
|
312
312
|
const result = await resolveRemoteResult(
|
|
313
|
-
config.
|
|
313
|
+
config.load({
|
|
314
314
|
...params.callbackParams.value,
|
|
315
315
|
page: { cursor: null, index: 1, size: pageSize.value },
|
|
316
316
|
parent: rawOptions.get(key) ?? option,
|
|
@@ -395,10 +395,10 @@ export function useRemoteFieldOptions(params) {
|
|
|
395
395
|
() => {
|
|
396
396
|
const config = params.config.value;
|
|
397
397
|
const { deps } = params.callbackParams.value;
|
|
398
|
-
if (!config?.refreshOn?.length
|
|
398
|
+
if (!config?.refreshOn?.length) {
|
|
399
399
|
return [];
|
|
400
400
|
}
|
|
401
|
-
return config.refreshOn.map((alias) => deps
|
|
401
|
+
return config.refreshOn.map((alias) => deps.get(alias));
|
|
402
402
|
},
|
|
403
403
|
(next, previous) => {
|
|
404
404
|
if (isUndefined(previous) || sameValues(next, previous)) {
|
|
@@ -7,7 +7,7 @@ export declare function isArrayActionConfig(action: FormArrayAction | undefined)
|
|
|
7
7
|
export declare function useFormArrayItems(field: () => FormArrayItemsField, path: () => readonly string[]): {
|
|
8
8
|
actionParams: (index: number) => {
|
|
9
9
|
ctx: {};
|
|
10
|
-
deps:
|
|
10
|
+
deps: import("../../types/index.js").FormDependencies;
|
|
11
11
|
getOptions: (key: string) => readonly unknown[];
|
|
12
12
|
getValue: (key: string) => unknown;
|
|
13
13
|
index: number;
|
|
@@ -16,7 +16,7 @@ import type { FormContextData, FormContextDefinition, FormPageSchema, FormPageSc
|
|
|
16
16
|
* label: 'VTEST ID',
|
|
17
17
|
* // `accountType` belongs to another section: a page is one form, one state.
|
|
18
18
|
* dependencies: ['accountType'],
|
|
19
|
-
* required: ({ deps }) => hasActiveContract || 'accountType'
|
|
19
|
+
* required: ({ deps }) => hasActiveContract || deps.get('accountType') !== null,
|
|
20
20
|
* },
|
|
21
21
|
* ],
|
|
22
22
|
* })
|
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
import type { FormValue } from './/index.js';
|
|
2
2
|
import type { FormFieldApi } from './api.js';
|
|
3
|
-
import type { FormObject } from './utils.js';
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The dependencies a field callback reads, each under the target name its `dependencies` entry
|
|
5
|
+
* declares.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* dependencies: ['year', ['profile.country', 'country']],
|
|
10
|
+
* options: ({ deps }) => ratesFor(deps.get<number | null>('year'), deps.get<string>('country')),
|
|
11
|
+
* ```
|
|
7
12
|
*/
|
|
8
|
-
export
|
|
13
|
+
export interface FormDependencies {
|
|
14
|
+
/**
|
|
15
|
+
* Reads the dependency declared under `key`, typed as the caller states. The engine does not
|
|
16
|
+
* check the type, since a reusable field can be mounted anywhere; a key the field did not declare
|
|
17
|
+
* reads as `undefined`.
|
|
18
|
+
*/
|
|
19
|
+
get: <TValue = FormValue>(key: string) => TValue;
|
|
20
|
+
}
|
|
9
21
|
/**
|
|
10
22
|
* Parameters passed to field-level callbacks.
|
|
11
23
|
*/
|
|
12
|
-
export interface FormFieldCallbackParams<TContext = NonNullable<unknown>, TDeps =
|
|
24
|
+
export interface FormFieldCallbackParams<TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue, TOption = FormValue> {
|
|
13
25
|
/** Fully typed form-scoped context declared on the schema. */
|
|
14
26
|
ctx: TContext;
|
|
15
|
-
/**
|
|
27
|
+
/** The field's declared dependencies, read with `deps.get(key)`. */
|
|
16
28
|
deps: TDeps;
|
|
17
29
|
/** Field-level API for values, options, upload work, and validation. */
|
|
18
30
|
api: FormFieldApi<TValue, TOption, TContext>;
|
|
@@ -20,4 +32,4 @@ export interface FormFieldCallbackParams<TContext = NonNullable<unknown>, TDeps
|
|
|
20
32
|
/**
|
|
21
33
|
* Function receiving field callback parameters.
|
|
22
34
|
*/
|
|
23
|
-
export type FormFieldCallback<TResult, TContext = NonNullable<unknown>, TDeps =
|
|
35
|
+
export type FormFieldCallback<TResult, TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue, TOption = FormValue> = (params: FormFieldCallbackParams<TContext, TDeps, TValue, TOption>) => TResult;
|
|
@@ -7,7 +7,8 @@ type PathValue<TSource, TPath extends string> = TPath extends '$root' ? TSource
|
|
|
7
7
|
type DependencyObject<TDependency, TState> = DependencyTarget<TDependency> extends infer TTarget ? TTarget extends string ? PathToObject<TTarget, PathValue<TState, DependencySource<TDependency>>> : NonNullable<unknown> : NonNullable<unknown>;
|
|
8
8
|
type DependenciesValue<TDependencies, TState> = TDependencies extends readonly FormValue[] ? UnionToIntersection<DependencyObject<TDependencies[number], TState>> : NonNullable<unknown>;
|
|
9
9
|
/**
|
|
10
|
-
* Extracts the
|
|
10
|
+
* Extracts the values of a single field's declared dependencies, keyed by target name: the types a
|
|
11
|
+
* callback reads through `deps.get(key)`.
|
|
11
12
|
*
|
|
12
13
|
* Pass the form internal state as the second type argument to resolve dependency values from
|
|
13
14
|
* absolute dotted paths. `$parent` dependencies intentionally resolve to `unknown` at this layer
|
|
@@ -20,6 +21,7 @@ type DependenciesValue<TDependencies, TState> = TDependencies extends readonly F
|
|
|
20
21
|
* dependencies: ['password', ['profile.country', 'country']]
|
|
21
22
|
* }, State>
|
|
22
23
|
* // { password: string | null, country: 'FR' | 'BE' | null }
|
|
24
|
+
* deps.get<Deps['country']>('country')
|
|
23
25
|
* ```
|
|
24
26
|
*/
|
|
25
27
|
export type ExtractFormFieldDependencies<TField, TState = FormObject> = TField extends {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FormValue } from './/index.js';
|
|
2
2
|
import type { FormFieldApi } from './api.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { FormDependencies, FormFieldCallback } from './callbacks.js';
|
|
4
4
|
import type { FormField } from './field.js';
|
|
5
5
|
import type { FormContainerLayout, FormItemLayout } from './layout.js';
|
|
6
6
|
import type { FormTransformConfig } from './transform.js';
|
|
@@ -19,11 +19,11 @@ export interface FormFieldDescription {
|
|
|
19
19
|
/**
|
|
20
20
|
* Common stateful field properties.
|
|
21
21
|
*/
|
|
22
|
-
export type FormFieldProps<TProps = FormObject, TContext = NonNullable<unknown>, TDeps =
|
|
22
|
+
export type FormFieldProps<TProps = FormObject, TContext = NonNullable<unknown>, TDeps = FormDependencies> = FormDynamic<TProps & FormObject, {
|
|
23
23
|
ctx: TContext;
|
|
24
24
|
deps: TDeps;
|
|
25
25
|
}>;
|
|
26
|
-
export interface FormStatefulFieldBase<TType extends FormFieldType, TValue, TContext = NonNullable<unknown>, TDeps =
|
|
26
|
+
export interface FormStatefulFieldBase<TType extends FormFieldType, TValue, TContext = NonNullable<unknown>, TDeps = FormDependencies, TProps = FormObject> {
|
|
27
27
|
/** Raw path used to read/write the field value in form state. */
|
|
28
28
|
key: string;
|
|
29
29
|
/** Discriminant used by the field registry. */
|
|
@@ -101,7 +101,7 @@ export interface FormStatefulFieldBase<TType extends FormFieldType, TValue, TCon
|
|
|
101
101
|
/**
|
|
102
102
|
* Base properties for stateless visual fields.
|
|
103
103
|
*/
|
|
104
|
-
export interface FormStatelessFieldBase<TType extends FormFieldType, TContext = NonNullable<unknown>, TDeps =
|
|
104
|
+
export interface FormStatelessFieldBase<TType extends FormFieldType, TContext = NonNullable<unknown>, TDeps = FormDependencies, TProps = FormObject> {
|
|
105
105
|
/** Stable key for renderer identity. Stateless fields do not write form state. */
|
|
106
106
|
key: string;
|
|
107
107
|
/** Discriminant used by the field registry. */
|
|
@@ -118,7 +118,7 @@ export interface FormStatelessFieldBase<TType extends FormFieldType, TContext =
|
|
|
118
118
|
/**
|
|
119
119
|
* Base properties for structural fields that render children.
|
|
120
120
|
*/
|
|
121
|
-
export interface FormContainerFieldBase<TType extends FormFieldType, TContext = NonNullable<unknown>, TDeps =
|
|
121
|
+
export interface FormContainerFieldBase<TType extends FormFieldType, TContext = NonNullable<unknown>, TDeps = FormDependencies, TProps = FormObject> {
|
|
122
122
|
/** Stable renderer key. */
|
|
123
123
|
key: string;
|
|
124
124
|
/** Discriminant used by the field registry. */
|
|
@@ -42,7 +42,7 @@ import type { FormTextField } from '../fields/text/types.js';
|
|
|
42
42
|
import type { FormTextareaField } from '../fields/textarea/types.js';
|
|
43
43
|
import type { FormTimeField } from '../fields/time/types.js';
|
|
44
44
|
import type { FormUploadField } from '../fields/upload/types.js';
|
|
45
|
-
import type {
|
|
45
|
+
import type { FormDependencies } from './callbacks.js';
|
|
46
46
|
export type { FormContainerFieldBase, FormFieldType, FormStatefulFieldBase, FormStatelessFieldBase, } from './field-base.js';
|
|
47
47
|
export type { FormArrayCollapseField } from '../fields/array-collapse/types.js';
|
|
48
48
|
export type { FormArrayListField } from '../fields/array-list/types.js';
|
|
@@ -94,4 +94,4 @@ export type { FormUploadCallbackParams, FormUploadDeleteParams, FormUploadField,
|
|
|
94
94
|
* Individual field schema contracts live beside their field implementation in
|
|
95
95
|
* `src/runtime/form/fields/<kind>/types.ts`; this file only assembles the public union.
|
|
96
96
|
*/
|
|
97
|
-
export type FormField<TContext = NonNullable<unknown>, TDeps =
|
|
97
|
+
export type FormField<TContext = NonNullable<unknown>, TDeps = FormDependencies> = FormTextField<TContext, TDeps> | FormPasswordField<TContext, TDeps> | FormTextareaField<TContext, TDeps> | FormNumberField<TContext, TDeps> | FormAutoCompleteField<TContext, TDeps> | FormCheckboxField<TContext, TDeps> | FormSwitchField<TContext, TDeps> | FormSwitchGroupField<TContext, TDeps> | FormRadioField<TContext, TDeps> | FormRadioCardField<TContext, TDeps> | FormCheckboxGroupField<TContext, TDeps> | FormCheckboxCardField<TContext, TDeps> | FormSelectField<TContext, TDeps> | FormDateField<TContext, TDeps> | FormDateTimeField<TContext, TDeps> | FormDateRangeField<TContext, TDeps> | FormMonthRangeField<TContext, TDeps> | FormDateTimeRangeField<TContext, TDeps> | FormMonthField<TContext, TDeps> | FormYearField<TContext, TDeps> | FormTimeField<TContext, TDeps> | FormTreeSelectField<TContext, TDeps> | FormCascaderField<TContext, TDeps> | FormTreeField<TContext, TDeps> | FormPhoneNumberField<TContext, TDeps> | FormHiddenField<TContext, TDeps> | FormInfoField<TContext, TDeps> | FormDividerField<TContext, TDeps> | FormSectionField<TContext, TDeps> | FormInputGroupField<TContext, TDeps> | FormTabsField<TContext, TDeps> | FormGroupField<TContext, TDeps> | FormObjectField<TContext, TDeps> | FormMatrixField<TContext, TDeps> | FormCustomComponentField<TContext, TDeps> | FormFileField<TContext, TDeps> | FormUploadField<TContext, TDeps> | FormArrayListField<TContext, TDeps> | FormArrayTableField<TContext, TDeps> | FormArrayTabsField<TContext, TDeps> | FormArrayVariantField<TContext, TDeps> | FormArrayCollapseField<TContext, TDeps> | FormArrayPrimitiveField<TContext, TDeps> | FormSliderField<TContext, TDeps> | FormColorPickerField<TContext, TDeps> | FormOneTimeCodeField<TContext, TDeps> | FormRatingField<TContext, TDeps> | FormTagField<TContext, TDeps> | FormButtonField<TContext, TDeps> | FormCardField<TContext, TDeps> | FormColumnField<TContext, TDeps>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { QueryKey } from '@tanstack/vue-query';
|
|
2
2
|
import type { RemoteOptionsLoader, RemoteOptionsPageRequest, RemoteOptionsPagination, RemoteOptionsResult, RemoteOptionsSearch } from '../../shared/types/remote-options.js';
|
|
3
3
|
import type { FormValue } from './/index.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FormDependencies, FormFieldCallback, FormFieldCallbackParams } from './callbacks.js';
|
|
5
5
|
import type { FormQueryOptions } from './context.js';
|
|
6
6
|
import type { FormMaybePromise, FormText } from './utils.js';
|
|
7
7
|
/**
|
|
@@ -28,15 +28,15 @@ export type FormOptionItem<TValue extends FormOptionValue = FormOptionValue> = T
|
|
|
28
28
|
/**
|
|
29
29
|
* A static, sync-derived, promise-backed, or TanStack Query-backed option source.
|
|
30
30
|
*/
|
|
31
|
-
export type FormOptionsSource<TOption, TContext = NonNullable<unknown>, TDeps =
|
|
31
|
+
export type FormOptionsSource<TOption, TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue> = readonly TOption[] | FormQueryOptions<readonly TOption[]> | FormFieldCallback<readonly TOption[] | Promise<readonly TOption[]> | FormQueryOptions<readonly TOption[]>, TContext, TDeps, TValue, TOption>;
|
|
32
32
|
/**
|
|
33
33
|
* Option creation hook for fields that can create a missing option from user input.
|
|
34
34
|
*/
|
|
35
|
-
export interface FormCreateOptionParams<TContext = NonNullable<unknown>, TDeps =
|
|
35
|
+
export interface FormCreateOptionParams<TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue, TOption = FormValue> extends FormFieldCallbackParams<TContext, TDeps, TValue, TOption> {
|
|
36
36
|
/** User-entered label that should be converted into a concrete option. */
|
|
37
37
|
label: string;
|
|
38
38
|
}
|
|
39
|
-
export interface FormCreateOption<TOption, TContext = NonNullable<unknown>, TDeps =
|
|
39
|
+
export interface FormCreateOption<TOption, TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue> {
|
|
40
40
|
/** Label shown by the create affordance. */
|
|
41
41
|
label?: FormText;
|
|
42
42
|
/** Selects the newly-created option immediately. Defaults to `true`. */
|
|
@@ -46,14 +46,14 @@ export interface FormCreateOption<TOption, TContext = NonNullable<unknown>, TDep
|
|
|
46
46
|
/** Creates an option and returns it. Return `null` when creation was cancelled. */
|
|
47
47
|
handler: (params: FormCreateOptionParams<TContext, TDeps, TValue, TOption>) => FormMaybePromise<TOption | null>;
|
|
48
48
|
}
|
|
49
|
-
export interface FormOptionsChangeParams<TContext = NonNullable<unknown>, TDeps =
|
|
49
|
+
export interface FormOptionsChangeParams<TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue, TOption = FormValue> extends FormFieldCallbackParams<TContext, TDeps, TValue, TOption> {
|
|
50
50
|
/** Previously resolved normalized options. */
|
|
51
51
|
previousOptions: readonly TOption[];
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Shared option configuration for option-based fields.
|
|
55
55
|
*/
|
|
56
|
-
export interface FormOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps =
|
|
56
|
+
export interface FormOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue> {
|
|
57
57
|
/** Local option configs never declare a mode; `mode: 'remote'` selects the remote config. */
|
|
58
58
|
mode?: never;
|
|
59
59
|
/** Static, sync-derived, promise-backed, or query-backed options. */
|
|
@@ -62,14 +62,18 @@ export interface FormOptionConfig<TOption, TContext = NonNullable<unknown>, TDep
|
|
|
62
62
|
create?: FormCreateOption<TOption, TContext, TDeps, TValue>;
|
|
63
63
|
/** Enables the field-level refresh affordance. Matches shared-ui `allowOptionsRefresh`. */
|
|
64
64
|
allowOptionsRefresh?: boolean;
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Clears current values that no longer exist in resolved options. Defaults to `true`, which
|
|
67
|
+
* shorthand sources (arrays, functions, promises, query options) always use. Values are kept
|
|
68
|
+
* while the source is pending or failed.
|
|
69
|
+
*/
|
|
66
70
|
clearOnInvalid?: boolean;
|
|
67
71
|
/** Runs when resolved source options change, excluding locally-created options. */
|
|
68
72
|
onOptionsChange?: (options: readonly TOption[], params: FormOptionsChangeParams<TContext, TDeps, TValue, TOption>) => void;
|
|
69
73
|
/** Disable the field while its option source has no usable data yet. */
|
|
70
74
|
disableOnLoading?: boolean;
|
|
71
75
|
}
|
|
72
|
-
export interface FormRemoteOptionsRequest<TContext = NonNullable<unknown>, TDeps =
|
|
76
|
+
export interface FormRemoteOptionsRequest<TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue, TOption = FormValue> extends FormFieldCallbackParams<TContext, TDeps, TValue, TOption> {
|
|
73
77
|
/** Current debounced search term. Empty when the menu lists unfiltered options. */
|
|
74
78
|
search: string;
|
|
75
79
|
/** Requested page. `cursor` is set for cursor pagination and `index` for page pagination. */
|
|
@@ -77,7 +81,7 @@ export interface FormRemoteOptionsRequest<TContext = NonNullable<unknown>, TDeps
|
|
|
77
81
|
/** Parent option whose direct children are requested. Undefined for root pages. */
|
|
78
82
|
parent?: NoInfer<TOption>;
|
|
79
83
|
}
|
|
80
|
-
export interface FormRemoteSelectedRequest<TContext = NonNullable<unknown>, TDeps =
|
|
84
|
+
export interface FormRemoteSelectedRequest<TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue, TOption = FormValue> extends FormFieldCallbackParams<TContext, TDeps, TValue, TOption> {
|
|
81
85
|
/** Selected values that are not present in the loaded pages. */
|
|
82
86
|
values: readonly FormOptionValue[];
|
|
83
87
|
}
|
|
@@ -85,10 +89,13 @@ export type FormRemoteSource<TResult> = FormQueryOptions<TResult> | Promise<TRes
|
|
|
85
89
|
/**
|
|
86
90
|
* Remote option configuration: server-side search, pagination, and selected-value hydration.
|
|
87
91
|
*/
|
|
88
|
-
export interface FormRemoteOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps =
|
|
92
|
+
export interface FormRemoteOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue> {
|
|
89
93
|
mode: 'remote';
|
|
90
|
-
/**
|
|
91
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Loads one page of options for the current search, page, and optional parent. Named apart from
|
|
96
|
+
* a local config's `source` so an object-form local source keeps typed callback parameters.
|
|
97
|
+
*/
|
|
98
|
+
load: (request: FormRemoteOptionsRequest<TContext, TDeps, TValue, TOption>) => FormRemoteSource<RemoteOptionsResult<TOption>>;
|
|
92
99
|
/** Hydrates selected values that the loaded pages do not contain. */
|
|
93
100
|
resolveSelected?: (request: FormRemoteSelectedRequest<TContext, TDeps, TValue, TOption>) => FormRemoteSource<readonly TOption[]>;
|
|
94
101
|
pagination: RemoteOptionsPagination;
|
|
@@ -114,13 +121,13 @@ export interface FormRemoteOptionConfig<TOption, TContext = NonNullable<unknown>
|
|
|
114
121
|
disableOnLoading?: boolean;
|
|
115
122
|
}
|
|
116
123
|
/** Reuses one remote loader while keeping field-specific refresh and creation behavior local. */
|
|
117
|
-
export interface FormRemoteLoaderOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps =
|
|
124
|
+
export interface FormRemoteLoaderOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue> extends Omit<FormRemoteOptionConfig<TOption, TContext, TDeps, TValue>, 'load' | 'resolveSelected' | 'pagination' | 'search' | 'queryKeyFor' | 'selectedQueryKeyFor'> {
|
|
118
125
|
loader: RemoteOptionsLoader<TOption>;
|
|
119
|
-
|
|
126
|
+
load?: never;
|
|
120
127
|
resolveSelected?: never;
|
|
121
128
|
pagination?: never;
|
|
122
129
|
search?: never;
|
|
123
130
|
queryKeyFor?: never;
|
|
124
131
|
selectedQueryKeyFor?: never;
|
|
125
132
|
}
|
|
126
|
-
export type FormAnyOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps =
|
|
133
|
+
export type FormAnyOptionConfig<TOption, TContext = NonNullable<unknown>, TDeps = FormDependencies, TValue = FormValue> = FormOptionConfig<TOption, TContext, TDeps, TValue> | FormRemoteOptionConfig<TOption, TContext, TDeps, TValue> | FormRemoteLoaderOptionConfig<TOption, TContext, TDeps, TValue> | FormOptionsSource<TOption, TContext, TDeps, TValue>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RegleRuleRaw } from '@regle/core';
|
|
2
2
|
import type { FormValue } from './/index.js';
|
|
3
3
|
import type { FormFieldCallback } from './callbacks.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { FormText } from './utils.js';
|
|
5
5
|
/**
|
|
6
6
|
* Controls when a field starts running validation while the user edits it.
|
|
7
7
|
*
|
|
@@ -52,4 +52,4 @@ export interface FormValidationConfig<TValue = FormValue, TContext = NonNullable
|
|
|
52
52
|
/** Native Regle rules keyed by their validation name. */
|
|
53
53
|
export type FormValidators = Record<string, RegleRuleRaw>;
|
|
54
54
|
/** Static or dependency-aware native Regle rules for one field. */
|
|
55
|
-
export type FormValidatorsConfig<TContext = NonNullable<unknown>> = FormValidators | FormFieldCallback<FormValidators, TContext
|
|
55
|
+
export type FormValidatorsConfig<TContext = NonNullable<unknown>> = FormValidators | FormFieldCallback<FormValidators, TContext>;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import type { FormField, FormObject } from '../types/index.js';
|
|
1
|
+
import type { FormDependencies, FormField, FormObject } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a field's declared dependencies against the form state, each under its target name.
|
|
4
|
+
*/
|
|
2
5
|
export declare function resolveFieldDependencies(params: {
|
|
3
6
|
field: FormField;
|
|
4
7
|
state: FormObject;
|
|
5
8
|
parentPath: readonly string[];
|
|
6
|
-
}):
|
|
9
|
+
}): FormDependencies;
|
|
10
|
+
/**
|
|
11
|
+
* Wraps resolved dependency values in the reader field callbacks receive.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createFormDependencies(values: FormObject): FormDependencies;
|
|
14
|
+
/**
|
|
15
|
+
* The plain values behind a dependency reader, for the engine's own change detection.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readFormDependencyValues(dependencies: FormDependencies): import("../../shared/index.js").GenericObject;
|
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
import { getScopedPathValue } from "./path.js";
|
|
2
2
|
import { isString } from "./predicate.js";
|
|
3
|
+
const dependencyValues = /* @__PURE__ */ new WeakMap();
|
|
3
4
|
export function resolveFieldDependencies(params) {
|
|
4
|
-
const
|
|
5
|
+
const values = {};
|
|
5
6
|
const dependencies = Object.getOwnPropertyDescriptor(params.field, "dependencies")?.value;
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
if (Array.isArray(dependencies)) {
|
|
8
|
+
for (const dependency of dependencies) {
|
|
9
|
+
const normalized = normalizeFieldDependency(dependency);
|
|
10
|
+
if (!normalized) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
values[normalized.target] = getScopedPathValue(
|
|
14
|
+
params.state,
|
|
15
|
+
normalized.source,
|
|
16
|
+
params.parentPath
|
|
17
|
+
);
|
|
13
18
|
}
|
|
14
|
-
output[normalized.target] = getScopedPathValue(
|
|
15
|
-
params.state,
|
|
16
|
-
normalized.source,
|
|
17
|
-
params.parentPath
|
|
18
|
-
);
|
|
19
19
|
}
|
|
20
|
-
return
|
|
20
|
+
return createFormDependencies(values);
|
|
21
|
+
}
|
|
22
|
+
export function createFormDependencies(values) {
|
|
23
|
+
const dependencies = {
|
|
24
|
+
// SAFETY: the callback states the type it expects from a dependency it declared; the engine
|
|
25
|
+
// holds untyped form values and does not validate them, as the `get` contract documents.
|
|
26
|
+
get: (key) => values[key]
|
|
27
|
+
};
|
|
28
|
+
dependencyValues.set(dependencies, values);
|
|
29
|
+
return dependencies;
|
|
30
|
+
}
|
|
31
|
+
export function readFormDependencyValues(dependencies) {
|
|
32
|
+
return dependencyValues.get(dependencies) ?? {};
|
|
21
33
|
}
|
|
22
34
|
function normalizeFieldDependency(value) {
|
|
23
35
|
if (isString(value)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ui-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.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",
|