nuxt-ui-tools 1.8.1 → 1.9.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 CHANGED
@@ -5,7 +5,7 @@
5
5
  "configKey": "nuxtUiTools",
6
6
  "docs": "https://ui.nuxt.com/docs/getting-started/installation/nuxt",
7
7
  "name": "nuxt-ui-tools",
8
- "version": "1.8.1",
8
+ "version": "1.9.0",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
@@ -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">{{ entry.value }}</span>
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
+ }
@@ -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 (!optionConfig.value || remoteConfig.value) {
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();
@@ -62,7 +62,11 @@ 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
- /** Clears current values that no longer exist in resolved options. Defaults to `true`. */
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-ui-tools",
3
- "version": "1.8.1",
3
+ "version": "1.9.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",