nuxt-ui-tools 1.23.0 → 1.25.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.
Files changed (226) hide show
  1. package/dist/module.d.mts +3 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +8 -0
  4. package/dist/runtime/display/components/blocks/display-accordion.d.vue.ts +4 -0
  5. package/dist/runtime/display/components/blocks/display-accordion.vue +93 -0
  6. package/dist/runtime/display/components/blocks/display-accordion.vue.d.ts +4 -0
  7. package/dist/runtime/display/components/blocks/display-alert.d.vue.ts +4 -0
  8. package/dist/runtime/display/components/blocks/display-alert.vue +133 -0
  9. package/dist/runtime/display/components/blocks/display-alert.vue.d.ts +4 -0
  10. package/dist/runtime/display/components/blocks/display-breakdown.d.vue.ts +4 -0
  11. package/dist/runtime/display/components/blocks/display-breakdown.vue +178 -0
  12. package/dist/runtime/display/components/blocks/display-breakdown.vue.d.ts +4 -0
  13. package/dist/runtime/display/components/blocks/display-chart.d.vue.ts +4 -0
  14. package/dist/runtime/display/components/blocks/display-chart.vue +237 -0
  15. package/dist/runtime/display/components/blocks/display-chart.vue.d.ts +4 -0
  16. package/dist/runtime/display/components/blocks/display-code.d.vue.ts +4 -0
  17. package/dist/runtime/display/components/blocks/display-code.vue +143 -0
  18. package/dist/runtime/display/components/blocks/display-code.vue.d.ts +4 -0
  19. package/dist/runtime/display/components/blocks/display-date.d.vue.ts +4 -0
  20. package/dist/runtime/display/components/blocks/display-date.vue +174 -0
  21. package/dist/runtime/display/components/blocks/display-date.vue.d.ts +4 -0
  22. package/dist/runtime/display/components/blocks/display-diff.d.vue.ts +4 -0
  23. package/dist/runtime/display/components/blocks/display-diff.vue +142 -0
  24. package/dist/runtime/display/components/blocks/display-diff.vue.d.ts +4 -0
  25. package/dist/runtime/display/components/blocks/display-empty.d.vue.ts +4 -0
  26. package/dist/runtime/display/components/blocks/display-empty.vue +74 -0
  27. package/dist/runtime/display/components/blocks/display-empty.vue.d.ts +4 -0
  28. package/dist/runtime/display/components/blocks/display-facts.d.vue.ts +4 -0
  29. package/dist/runtime/display/components/blocks/display-facts.vue +180 -0
  30. package/dist/runtime/display/components/blocks/display-facts.vue.d.ts +4 -0
  31. package/dist/runtime/display/components/blocks/display-heading.d.vue.ts +4 -0
  32. package/dist/runtime/display/components/blocks/display-heading.vue +96 -0
  33. package/dist/runtime/display/components/blocks/display-heading.vue.d.ts +4 -0
  34. package/dist/runtime/display/components/blocks/display-list.d.vue.ts +4 -0
  35. package/dist/runtime/display/components/blocks/display-list.vue +295 -0
  36. package/dist/runtime/display/components/blocks/display-list.vue.d.ts +4 -0
  37. package/dist/runtime/display/components/blocks/display-media.d.vue.ts +4 -0
  38. package/dist/runtime/display/components/blocks/display-media.vue +326 -0
  39. package/dist/runtime/display/components/blocks/display-media.vue.d.ts +4 -0
  40. package/dist/runtime/display/components/blocks/display-meter.d.vue.ts +4 -0
  41. package/dist/runtime/display/components/blocks/display-meter.vue +301 -0
  42. package/dist/runtime/display/components/blocks/display-meter.vue.d.ts +4 -0
  43. package/dist/runtime/display/components/blocks/display-person.d.vue.ts +4 -0
  44. package/dist/runtime/display/components/blocks/display-person.vue +146 -0
  45. package/dist/runtime/display/components/blocks/display-person.vue.d.ts +4 -0
  46. package/dist/runtime/display/components/blocks/display-record.d.vue.ts +4 -0
  47. package/dist/runtime/display/components/blocks/display-record.vue +208 -0
  48. package/dist/runtime/display/components/blocks/display-record.vue.d.ts +4 -0
  49. package/dist/runtime/display/components/blocks/display-stat.d.vue.ts +4 -0
  50. package/dist/runtime/display/components/blocks/display-stat.vue +178 -0
  51. package/dist/runtime/display/components/blocks/display-stat.vue.d.ts +4 -0
  52. package/dist/runtime/display/components/blocks/display-table.d.vue.ts +4 -0
  53. package/dist/runtime/display/components/blocks/display-table.vue +132 -0
  54. package/dist/runtime/display/components/blocks/display-table.vue.d.ts +4 -0
  55. package/dist/runtime/display/components/blocks/display-text.d.vue.ts +4 -0
  56. package/dist/runtime/display/components/blocks/display-text.vue +135 -0
  57. package/dist/runtime/display/components/blocks/display-text.vue.d.ts +4 -0
  58. package/dist/runtime/display/components/blocks/display-timeline.d.vue.ts +4 -0
  59. package/dist/runtime/display/components/blocks/display-timeline.vue +232 -0
  60. package/dist/runtime/display/components/blocks/display-timeline.vue.d.ts +4 -0
  61. package/dist/runtime/display/components/display.d.vue.ts +24 -0
  62. package/dist/runtime/display/components/display.vue +21 -0
  63. package/dist/runtime/display/components/display.vue.d.ts +24 -0
  64. package/dist/runtime/display/components/parts/display-actions.d.vue.ts +9 -0
  65. package/dist/runtime/display/components/parts/display-actions.vue +24 -0
  66. package/dist/runtime/display/components/parts/display-actions.vue.d.ts +9 -0
  67. package/dist/runtime/display/components/parts/display-chip.d.vue.ts +11 -0
  68. package/dist/runtime/display/components/parts/display-chip.vue +45 -0
  69. package/dist/runtime/display/components/parts/display-chip.vue.d.ts +11 -0
  70. package/dist/runtime/display/components/parts/display-copy.d.vue.ts +6 -0
  71. package/dist/runtime/display/components/parts/display-copy.vue +27 -0
  72. package/dist/runtime/display/components/parts/display-copy.vue.d.ts +6 -0
  73. package/dist/runtime/display/components/parts/display-empty-line.d.vue.ts +7 -0
  74. package/dist/runtime/display/components/parts/display-empty-line.vue +21 -0
  75. package/dist/runtime/display/components/parts/display-empty-line.vue.d.ts +7 -0
  76. package/dist/runtime/display/components/parts/display-fact-value.d.vue.ts +7 -0
  77. package/dist/runtime/display/components/parts/display-fact-value.vue +35 -0
  78. package/dist/runtime/display/components/parts/display-fact-value.vue.d.ts +7 -0
  79. package/dist/runtime/display/components/parts/display-load-error.d.vue.ts +7 -0
  80. package/dist/runtime/display/components/parts/display-load-error.vue +29 -0
  81. package/dist/runtime/display/components/parts/display-load-error.vue.d.ts +7 -0
  82. package/dist/runtime/display/components/parts/display-more.d.vue.ts +10 -0
  83. package/dist/runtime/display/components/parts/display-more.vue +23 -0
  84. package/dist/runtime/display/components/parts/display-more.vue.d.ts +10 -0
  85. package/dist/runtime/display/components/parts/display-record-media.d.vue.ts +9 -0
  86. package/dist/runtime/display/components/parts/display-record-media.vue +59 -0
  87. package/dist/runtime/display/components/parts/display-record-media.vue.d.ts +9 -0
  88. package/dist/runtime/display/components/parts/display-render.d.ts +10 -0
  89. package/dist/runtime/display/components/parts/display-render.js +5 -0
  90. package/dist/runtime/display/components/parts/display-skeleton.d.vue.ts +7 -0
  91. package/dist/runtime/display/components/parts/display-skeleton.vue +67 -0
  92. package/dist/runtime/display/components/parts/display-skeleton.vue.d.ts +7 -0
  93. package/dist/runtime/display/composables/use-display-config.d.ts +6 -0
  94. package/dist/runtime/display/composables/use-display-config.js +11 -0
  95. package/dist/runtime/display/composables/use-display-file-preview.d.ts +9 -0
  96. package/dist/runtime/display/composables/use-display-file-preview.js +11 -0
  97. package/dist/runtime/display/composables/use-display-icons.d.ts +8 -0
  98. package/dist/runtime/display/composables/use-display-icons.js +62 -0
  99. package/dist/runtime/display/composables/use-display-locale.d.ts +5 -0
  100. package/dist/runtime/display/composables/use-display-locale.js +5 -0
  101. package/dist/runtime/display/composables/use-display-number.d.ts +2 -0
  102. package/dist/runtime/display/composables/use-display-number.js +7 -0
  103. package/dist/runtime/display/composables/use-display-reveal.d.ts +14 -0
  104. package/dist/runtime/display/composables/use-display-reveal.js +15 -0
  105. package/dist/runtime/display/composables/use-display-url.d.ts +26 -0
  106. package/dist/runtime/display/composables/use-display-url.js +46 -0
  107. package/dist/runtime/display/index.d.ts +4 -0
  108. package/dist/runtime/display/index.js +3 -0
  109. package/dist/runtime/display/plugins/display-tones.d.ts +3 -0
  110. package/dist/runtime/display/plugins/display-tones.js +17 -0
  111. package/dist/runtime/display/types/blocks/accordion.d.ts +18 -0
  112. package/dist/runtime/display/types/blocks/accordion.js +0 -0
  113. package/dist/runtime/display/types/blocks/alert.d.ts +31 -0
  114. package/dist/runtime/display/types/blocks/alert.js +0 -0
  115. package/dist/runtime/display/types/blocks/breakdown.d.ts +36 -0
  116. package/dist/runtime/display/types/blocks/breakdown.js +0 -0
  117. package/dist/runtime/display/types/blocks/chart.d.ts +23 -0
  118. package/dist/runtime/display/types/blocks/chart.js +0 -0
  119. package/dist/runtime/display/types/blocks/code.d.ts +21 -0
  120. package/dist/runtime/display/types/blocks/code.js +0 -0
  121. package/dist/runtime/display/types/blocks/date.d.ts +27 -0
  122. package/dist/runtime/display/types/blocks/date.js +0 -0
  123. package/dist/runtime/display/types/blocks/diff.d.ts +20 -0
  124. package/dist/runtime/display/types/blocks/diff.js +0 -0
  125. package/dist/runtime/display/types/blocks/empty.d.ts +13 -0
  126. package/dist/runtime/display/types/blocks/empty.js +0 -0
  127. package/dist/runtime/display/types/blocks/facts.d.ts +50 -0
  128. package/dist/runtime/display/types/blocks/facts.js +0 -0
  129. package/dist/runtime/display/types/blocks/heading.d.ts +19 -0
  130. package/dist/runtime/display/types/blocks/heading.js +0 -0
  131. package/dist/runtime/display/types/blocks/list.d.ts +36 -0
  132. package/dist/runtime/display/types/blocks/list.js +0 -0
  133. package/dist/runtime/display/types/blocks/media.d.ts +42 -0
  134. package/dist/runtime/display/types/blocks/media.js +0 -0
  135. package/dist/runtime/display/types/blocks/meter.d.ts +48 -0
  136. package/dist/runtime/display/types/blocks/meter.js +0 -0
  137. package/dist/runtime/display/types/blocks/person.d.ts +32 -0
  138. package/dist/runtime/display/types/blocks/person.js +0 -0
  139. package/dist/runtime/display/types/blocks/record.d.ts +42 -0
  140. package/dist/runtime/display/types/blocks/record.js +0 -0
  141. package/dist/runtime/display/types/blocks/stat.d.ts +44 -0
  142. package/dist/runtime/display/types/blocks/stat.js +0 -0
  143. package/dist/runtime/display/types/blocks/table.d.ts +28 -0
  144. package/dist/runtime/display/types/blocks/table.js +0 -0
  145. package/dist/runtime/display/types/blocks/text.d.ts +22 -0
  146. package/dist/runtime/display/types/blocks/text.js +0 -0
  147. package/dist/runtime/display/types/blocks/timeline.d.ts +37 -0
  148. package/dist/runtime/display/types/blocks/timeline.js +0 -0
  149. package/dist/runtime/display/types/config.d.ts +53 -0
  150. package/dist/runtime/display/types/config.js +0 -0
  151. package/dist/runtime/display/types/index.d.ts +22 -0
  152. package/dist/runtime/display/types/index.js +0 -0
  153. package/dist/runtime/display/types/registry.d.ts +57 -0
  154. package/dist/runtime/display/types/registry.js +0 -0
  155. package/dist/runtime/display/types/shared.d.ts +71 -0
  156. package/dist/runtime/display/types/shared.js +0 -0
  157. package/dist/runtime/display/utils/classes.d.ts +3 -0
  158. package/dist/runtime/display/utils/classes.js +7 -0
  159. package/dist/runtime/display/utils/config.d.ts +6 -0
  160. package/dist/runtime/display/utils/config.js +7 -0
  161. package/dist/runtime/display/utils/content.d.ts +7 -0
  162. package/dist/runtime/display/utils/content.js +13 -0
  163. package/dist/runtime/display/utils/dates.d.ts +16 -0
  164. package/dist/runtime/display/utils/dates.js +34 -0
  165. package/dist/runtime/display/utils/json.d.ts +7 -0
  166. package/dist/runtime/display/utils/json.js +15 -0
  167. package/dist/runtime/display/utils/meter.d.ts +7 -0
  168. package/dist/runtime/display/utils/meter.js +13 -0
  169. package/dist/runtime/display/utils/registry.d.ts +47 -0
  170. package/dist/runtime/display/utils/registry.js +64 -0
  171. package/dist/runtime/display/utils/tone-css.d.ts +7 -0
  172. package/dist/runtime/display/utils/tone-css.js +38 -0
  173. package/dist/runtime/display/utils/tone.d.ts +26 -0
  174. package/dist/runtime/display/utils/tone.js +38 -0
  175. package/dist/runtime/display/utils/urls.d.ts +10 -0
  176. package/dist/runtime/display/utils/urls.js +33 -0
  177. package/dist/runtime/file-preview/components/shell/file-preview-stage.vue +5 -3
  178. package/dist/runtime/file-preview/composables/use-file-preview-source.d.ts +7 -0
  179. package/dist/runtime/file-preview/composables/use-file-preview-source.js +7 -1
  180. package/dist/runtime/file-preview/index.d.ts +1 -0
  181. package/dist/runtime/file-preview/index.js +1 -0
  182. package/dist/runtime/file-preview/types/file.d.ts +9 -0
  183. package/dist/runtime/file-preview/utils/errors.d.ts +30 -0
  184. package/dist/runtime/file-preview/utils/errors.js +26 -0
  185. package/dist/runtime/form/components/utils/form-file-preview.vue +7 -2
  186. package/dist/runtime/form/composables/use-form-root.js +2 -0
  187. package/dist/runtime/form/composables/use-form-runtime.js +7 -2
  188. package/dist/runtime/form/fields/index.d.ts +5 -0
  189. package/dist/runtime/form/fields/info/component.vue +51 -19
  190. package/dist/runtime/form/fields/info/config.d.ts +5 -0
  191. package/dist/runtime/form/fields/info/config.js +2 -1
  192. package/dist/runtime/form/fields/info/types.d.ts +78 -4
  193. package/dist/runtime/form/fields/info/use-info-display.d.ts +20 -0
  194. package/dist/runtime/form/fields/info/use-info-display.js +82 -0
  195. package/dist/runtime/form/fields/upload/upload-item.vue +10 -2
  196. package/dist/runtime/form/types/api.d.ts +6 -0
  197. package/dist/runtime/form/types/runtime.d.ts +2 -0
  198. package/dist/runtime/form/utils/dynamic.d.ts +6 -0
  199. package/dist/runtime/form/utils/dynamic.js +7 -0
  200. package/dist/runtime/i18n/locales/en.js +32 -0
  201. package/dist/runtime/i18n/locales/fr.js +32 -0
  202. package/dist/runtime/i18n/types.d.ts +40 -0
  203. package/dist/runtime/shared/styles/tokens.css +1 -1
  204. package/dist/runtime/shared/utils/file.d.ts +1 -1
  205. package/dist/runtime/table/components/data-list/data-list-root.vue +2 -0
  206. package/dist/runtime/table/components/data-list/data-list-selection-actions.d.vue.ts +1 -1
  207. package/dist/runtime/table/components/data-list/data-list-selection-actions.vue +32 -5
  208. package/dist/runtime/table/components/data-list/data-list-selection-actions.vue.d.ts +1 -1
  209. package/dist/runtime/table/components/data-list/data-list-selection-sheet.d.vue.ts +10 -0
  210. package/dist/runtime/table/components/data-list/data-list-selection-sheet.vue +238 -0
  211. package/dist/runtime/table/components/data-list/data-list-selection-sheet.vue.d.ts +10 -0
  212. package/dist/runtime/table/components/grid/grid-renderer.vue +2 -0
  213. package/dist/runtime/table/components/summary/summary-renderer.vue +2 -0
  214. package/dist/runtime/table/components/table/table-renderer.vue +2 -0
  215. package/dist/runtime/table/components/utils/overlay-inset-spacer.d.vue.ts +3 -0
  216. package/dist/runtime/table/components/utils/overlay-inset-spacer.vue +13 -0
  217. package/dist/runtime/table/components/utils/overlay-inset-spacer.vue.d.ts +3 -0
  218. package/dist/runtime/table/composables/use-data-list-overlay-inset.d.ts +10 -0
  219. package/dist/runtime/table/composables/use-data-list-overlay-inset.js +11 -0
  220. package/dist/runtime/table/composables/use-table-actions.js +11 -2
  221. package/dist/runtime/table/constants/mobile.d.ts +0 -7
  222. package/dist/runtime/table/constants/mobile.js +0 -1
  223. package/dist/runtime/table/types/actions.d.ts +7 -0
  224. package/dist/runtime/table/types/data-list.d.ts +8 -1
  225. package/dist/runtime/table/utils/data-list-ui.d.ts +1 -1
  226. package/package.json +16 -1
@@ -0,0 +1,38 @@
1
+ const BUILTIN_TONES = [
2
+ "primary",
3
+ "secondary",
4
+ "success",
5
+ "info",
6
+ "warning",
7
+ "error",
8
+ "neutral"
9
+ ];
10
+ export function displayToneClass(tone) {
11
+ return `nut-display-tone-${tone}`;
12
+ }
13
+ export function isBuiltinTone(tone) {
14
+ return BUILTIN_TONES.some((builtin) => builtin === tone);
15
+ }
16
+ export const DISPLAY_SURFACE_CLASSES = {
17
+ bordered: "rounded-[var(--nut-display-radius)] border border-default",
18
+ elevated: "rounded-[var(--nut-display-radius)] bg-default shadow-[0_10px_28px_-14px_rgb(0_0_0/0.3)] ring ring-default",
19
+ muted: "rounded-[var(--nut-display-radius)] bg-elevated",
20
+ plain: ""
21
+ };
22
+ export const DISPLAY_SURFACE_PADDING = {
23
+ bordered: "p-4",
24
+ elevated: "p-4",
25
+ muted: "p-4",
26
+ plain: ""
27
+ };
28
+ const NAME_TONES = ["primary", "info", "success", "warning", "secondary", "error"];
29
+ export function toneFromName(name) {
30
+ let hash = 0;
31
+ for (const char of name) hash = (hash * 31 + char.charCodeAt(0)) % 2147483647;
32
+ return NAME_TONES[hash % NAME_TONES.length] ?? "primary";
33
+ }
34
+ export function initialsOf(name) {
35
+ const words = name.trim().split(/\s+/u).filter(Boolean);
36
+ const letters = words.length > 1 ? [words[0], words.at(-1)] : [words[0]];
37
+ return letters.map((word) => word?.[0] ?? "").join("").toUpperCase();
38
+ }
@@ -0,0 +1,10 @@
1
+ import type { DisplayUrl } from '../types/index.js';
2
+ /** The address of a link, calling it when it is a function. */
3
+ export declare function resolveDisplayUrl(source: DisplayUrl): Promise<DisplayUrl & string>;
4
+ /**
5
+ * Opens a link in a new tab. A function's tab opens during the click, so popup blockers let it
6
+ * through, and follows the address once it resolves; it closes again if the address fails.
7
+ */
8
+ export declare function openDisplayUrl(source: DisplayUrl): Promise<void>;
9
+ /** Downloads a link under `name`, resolving it first when it is a function. */
10
+ export declare function downloadDisplayUrl(source: DisplayUrl, name?: string): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { isString } from "../../shared/utils/predicate.js";
2
+ export async function resolveDisplayUrl(source) {
3
+ return isString(source) ? source : await source();
4
+ }
5
+ export async function openDisplayUrl(source) {
6
+ if (isString(source)) {
7
+ window.open(source, "_blank", "noopener");
8
+ return;
9
+ }
10
+ const tab = window.open("about:blank", "_blank");
11
+ try {
12
+ const url = await source();
13
+ if (!tab) {
14
+ window.open(url, "_blank", "noopener");
15
+ return;
16
+ }
17
+ tab.opener = null;
18
+ tab.location.href = url;
19
+ } catch (error) {
20
+ tab?.close();
21
+ throw error;
22
+ }
23
+ }
24
+ export async function downloadDisplayUrl(source, name) {
25
+ const anchor = document.createElement("a");
26
+ anchor.href = await resolveDisplayUrl(source);
27
+ anchor.rel = "noopener";
28
+ anchor.target = "_blank";
29
+ if (name) anchor.download = name;
30
+ document.body.append(anchor);
31
+ anchor.click();
32
+ anchor.remove();
33
+ }
@@ -88,9 +88,11 @@ function retry() {
88
88
  v-else-if="source.status.value === 'error'"
89
89
  icon="i-lucide-triangle-alert"
90
90
  tone="error"
91
- :title="t('filePreview.errors.sourceTitle')"
92
- :description="t('filePreview.errors.sourceDescription')"
93
- :detail="source.error.value?.message"
91
+ :title="source.failure.value?.title ?? t('filePreview.errors.sourceTitle')"
92
+ :description="
93
+ source.failure.value?.description ?? t('filePreview.errors.sourceDescription')
94
+ "
95
+ :detail="source.failure.value?.description ? null : source.error.value?.message"
94
96
  >
95
97
  <UButton
96
98
  v-if="source.retryable.value"
@@ -8,6 +8,13 @@ type FilePreviewSourceStatus = 'loading' | 'ready' | 'error';
8
8
  export declare function useFilePreviewSource(item: () => FilePreviewItem | null): {
9
9
  blob: import("vue").ComputedRef<Blob | null>;
10
10
  error: import("vue").ShallowRef<Error | null, Error | null>;
11
+ failure: import("vue").ShallowRef<{
12
+ description: string | undefined;
13
+ title: string | undefined;
14
+ } | null, {
15
+ description: string | undefined;
16
+ title: string | undefined;
17
+ } | null>;
11
18
  /** Whether `retry()` can fetch the source again, which only a source function can. */
12
19
  retryable: import("vue").ComputedRef<boolean>;
13
20
  retry: () => Promise<void>;
@@ -1,11 +1,13 @@
1
1
  import { useObjectUrl } from "@vueuse/core";
2
2
  import { computed, ref, shallowRef, watch } from "vue";
3
- import { isFunction, isString } from "../../shared/utils/predicate.js";
3
+ import { isFunction, isObject, isString } from "../../shared/utils/predicate.js";
4
+ import { explainFilePreviewFailure } from "../utils/errors.js";
4
5
  const resolved = /* @__PURE__ */ new WeakMap();
5
6
  export function useFilePreviewSource(item) {
6
7
  const value = shallowRef(null);
7
8
  const status = ref("loading");
8
9
  const error = shallowRef(null);
10
+ const failure = shallowRef(null);
9
11
  const blob = computed(() => value.value instanceof Blob ? value.value : null);
10
12
  const objectUrl = useObjectUrl(blob);
11
13
  const url = computed(() => isString(value.value) ? value.value : objectUrl.value ?? null);
@@ -13,6 +15,7 @@ export function useFilePreviewSource(item) {
13
15
  function settle(next) {
14
16
  value.value = next;
15
17
  error.value = null;
18
+ failure.value = null;
16
19
  status.value = "ready";
17
20
  }
18
21
  async function resolve(options) {
@@ -26,6 +29,7 @@ export function useFilePreviewSource(item) {
26
29
  if (known !== void 0) return settle(known);
27
30
  value.value = null;
28
31
  error.value = null;
32
+ failure.value = null;
29
33
  status.value = "loading";
30
34
  try {
31
35
  const next = await src();
@@ -35,6 +39,7 @@ export function useFilePreviewSource(item) {
35
39
  } catch (cause) {
36
40
  if (current !== attempt) return;
37
41
  error.value = cause instanceof Error ? cause : new Error(String(cause));
42
+ failure.value = cause instanceof Error || isObject(cause) ? explainFilePreviewFailure(cause) : null;
38
43
  status.value = "error";
39
44
  }
40
45
  }
@@ -42,6 +47,7 @@ export function useFilePreviewSource(item) {
42
47
  return {
43
48
  blob,
44
49
  error,
50
+ failure,
45
51
  /** Whether `retry()` can fetch the source again, which only a source function can. */
46
52
  retryable: computed(() => isFunction(item()?.file.src)),
47
53
  retry: () => resolve({ force: true }),
@@ -1,5 +1,6 @@
1
1
  export { createFilePreviewApi, filePreviewApiKey, provideFilePreview, useFilePreview, } from './composables/use-file-preview-api.js';
2
2
  export { onFilePreviewKey, useFilePreviewShell } from './composables/use-file-preview-shell.js';
3
3
  export { FILE_PREVIEW_TEXT_LIMIT, useFilePreviewText } from './composables/use-file-preview-text.js';
4
+ export { FilePreviewSourceError } from './utils/errors.js';
4
5
  export { builtInFilePreviewRenderers, defineFilePreviewRenderer, detectFilePreviewKind, matchFilePreview, } from './utils/renderers.js';
5
6
  export type * from './types';
@@ -6,6 +6,7 @@ export {
6
6
  } from "./composables/use-file-preview-api.js";
7
7
  export { onFilePreviewKey, useFilePreviewShell } from "./composables/use-file-preview-shell.js";
8
8
  export { FILE_PREVIEW_TEXT_LIMIT, useFilePreviewText } from "./composables/use-file-preview-text.js";
9
+ export { FilePreviewSourceError } from "./utils/errors.js";
9
10
  export {
10
11
  builtInFilePreviewRenderers,
11
12
  defineFilePreviewRenderer,
@@ -11,6 +11,15 @@ export type FilePreviewSourceValue = string | Blob;
11
11
  * URLs are requested on demand; it runs again when the user presses Retry after an error.
12
12
  */
13
13
  export type FilePreviewSource = FilePreviewSourceValue | (() => MaybePromise<FilePreviewSourceValue>);
14
+ /**
15
+ * Why a source function failed, carried by the error it throws (for example a
16
+ * `FilePreviewSourceError`, or any error with these properties). The preview shows them in place
17
+ * of its generic copy and leaves out the raw error message.
18
+ */
19
+ export interface FilePreviewSourceFailure {
20
+ title?: FilePreviewText;
21
+ description?: FilePreviewText;
22
+ }
14
23
  export interface FilePreviewDetail {
15
24
  label: FilePreviewText;
16
25
  value: FilePreviewText;
@@ -0,0 +1,30 @@
1
+ import type { GenericObject } from '../../shared/types/utils.js';
2
+ import type { FilePreviewSourceFailure, FilePreviewText } from '../types/index.js';
3
+ /**
4
+ * Error a source function throws when the app knows why the file is unavailable. The preview shows
5
+ * its `title` and `description` in place of the generic ones, without the raw error message.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * src: async () => {
10
+ * const response = await fetch(`/api/invoices/${id}/file`)
11
+ * if (response.status === 404)
12
+ * throw new FilePreviewSourceError({ description: 'The invoice file cannot be found.' })
13
+ * return (await response.json()).url
14
+ * }
15
+ * ```
16
+ */
17
+ export declare class FilePreviewSourceError extends Error implements FilePreviewSourceFailure {
18
+ readonly title?: FilePreviewText;
19
+ readonly description?: FilePreviewText;
20
+ constructor(options: {
21
+ title?: FilePreviewText;
22
+ description?: FilePreviewText;
23
+ message?: string;
24
+ });
25
+ }
26
+ /** The title and description a thrown source error carries, if any, resolved to text. */
27
+ export declare function explainFilePreviewFailure(cause: Error | GenericObject): {
28
+ description: string | undefined;
29
+ title: string | undefined;
30
+ } | null;
@@ -0,0 +1,26 @@
1
+ import { isFunction, isString } from "../../shared/utils/predicate.js";
2
+ export class FilePreviewSourceError extends Error {
3
+ title;
4
+ description;
5
+ constructor(options) {
6
+ super(
7
+ options.message ?? textOf(options.description) ?? textOf(options.title) ?? "File source failed"
8
+ );
9
+ this.name = "FilePreviewSourceError";
10
+ this.title = options.title;
11
+ this.description = options.description;
12
+ }
13
+ }
14
+ export function explainFilePreviewFailure(cause) {
15
+ const title = "title" in cause ? textOf(cause.title) : void 0;
16
+ const description = "description" in cause ? textOf(cause.description) : void 0;
17
+ return title === void 0 && description === void 0 ? null : { description, title };
18
+ }
19
+ function textOf(value) {
20
+ if (isString(value)) return value;
21
+ if (isFunction(value)) {
22
+ const text = value();
23
+ return isString(text) ? text : void 0;
24
+ }
25
+ return void 0;
26
+ }
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import UButton from "@nuxt/ui/components/Button.vue";
3
3
  import UIcon from "@nuxt/ui/components/Icon.vue";
4
- import { computed, onScopeDispose, watch } from "vue";
4
+ import { computed, onScopeDispose, ref, watch } from "vue";
5
5
  import { useUiToolsLocale } from "../../../i18n/use-locale";
6
6
  import { fileExtensionLabel, fileIconName, formatFileSize } from "../../../shared/utils/file";
7
7
  const props = defineProps({
@@ -25,6 +25,10 @@ const preview = computed(() => {
25
25
  return previewUrl;
26
26
  });
27
27
  const icon = computed(() => fileIconName(props.file.type));
28
+ const previewFailed = ref(false);
29
+ watch(preview, () => {
30
+ previewFailed.value = false;
31
+ });
28
32
  const meta = computed(
29
33
  () => `${fileExtensionLabel(props.file)} \xB7 ${formatFileSize(props.file.size, code.value)}`
30
34
  );
@@ -50,8 +54,9 @@ onScopeDispose(() => {
50
54
  data-form-file=""
51
55
  >
52
56
  <img
53
- v-if="preview"
57
+ v-if="preview && !previewFailed"
54
58
  :src="preview"
59
+ @error="previewFailed = true"
55
60
  :alt="file.name"
56
61
  class="size-10 shrink-0 rounded-md border border-default object-cover"
57
62
  />
@@ -37,6 +37,8 @@ export function useFormRoot(params) {
37
37
  const ownedRuntime = params.runtime?.();
38
38
  const runtime = ownedRuntime ?? useFormRuntime({ input, schema, syncInput, validationMode });
39
39
  provideFormRuntime(runtime);
40
+ runtime.bindCancel(() => cancel());
41
+ onBeforeUnmount(() => runtime.bindCancel(null));
40
42
  const confirm = useFormConfirm();
41
43
  let leaveConfirmed = false;
42
44
  watch(
@@ -1,4 +1,4 @@
1
- import { computed, inject, nextTick, provide, ref, watch } from "vue";
1
+ import { computed, inject, nextTick, provide, ref, shallowRef, watch } from "vue";
2
2
  import { useUiToolsLocale } from "../../i18n/use-locale.js";
3
3
  import { createPublicFormApi } from "../utils/api.js";
4
4
  import { resolveFieldDependencies } from "../utils/dependencies.js";
@@ -44,6 +44,7 @@ export function useFormRuntime(params) {
44
44
  const currentStepIndex = ref(0);
45
45
  const navigationActionPending = ref(null);
46
46
  const effects = createEffectLifecycle();
47
+ const cancelHandler = shallowRef(null);
47
48
  const { t } = useUiToolsLocale();
48
49
  setContext(getSchemaContext(params.schema.value));
49
50
  function createFormNamespace(path, read) {
@@ -64,7 +65,8 @@ export function useFormRuntime(params) {
64
65
  setError: (target, message, options) => runtime.setError(resolve(target), message, options),
65
66
  state: () => read.state(),
66
67
  submit: () => runtime.submit(),
67
- validate: () => runtime.validate()
68
+ validate: () => runtime.validate(),
69
+ close: () => cancelHandler.value?.() ?? Promise.resolve()
68
70
  };
69
71
  }
70
72
  function createApiFactory(read) {
@@ -449,6 +451,9 @@ export function useFormRuntime(params) {
449
451
  return result.success;
450
452
  },
451
453
  submitHandler,
454
+ bindCancel: (cancel) => {
455
+ cancelHandler.value = cancel;
456
+ },
452
457
  trackEffect: effects.track,
453
458
  validate,
454
459
  validateCurrentStep
@@ -134,6 +134,11 @@ export declare const formFieldKinds: readonly [{
134
134
  };
135
135
  readonly state: "stateless";
136
136
  readonly type: "info";
137
+ readonly ui: {
138
+ readonly description: true;
139
+ readonly hint: true;
140
+ readonly label: true;
141
+ };
137
142
  }, {
138
143
  readonly layout: {
139
144
  readonly item: true;
@@ -1,30 +1,62 @@
1
1
  <script setup>
2
- import UAlert from "@nuxt/ui/components/Alert.vue";
3
2
  import { computed } from "vue";
4
- import { useFieldControl } from "../../composables/use-field-control";
5
- import { invokeFormFunction, isNumber, isString } from "../../utils/predicate";
3
+ import DisplayAlert from "../../../display/components/blocks/display-alert.vue";
4
+ import DisplayBlock from "../../../display/components/display.vue";
5
+ import DisplayLoadError from "../../../display/components/parts/display-load-error.vue";
6
+ import DisplaySkeleton from "../../../display/components/parts/display-skeleton.vue";
7
+ import { DISPLAY_SKELETON_LAYOUTS } from "../../../display/utils/registry";
8
+ import FormFieldShell from "../../components/renderer/form-field-shell.vue";
9
+ import { useFormRuntimeContext } from "../../composables/use-form-runtime";
10
+ import { resolveFormDynamic } from "../../utils/dynamic";
6
11
  import { resolveFormText } from "../../utils/text";
12
+ import { useInfoDisplay } from "./use-info-display";
7
13
  const props = defineProps({
8
- field: { type: Object, required: true },
14
+ field: { type: null, required: true },
9
15
  path: { type: Array, required: true }
10
16
  });
11
- const { fieldProps, params } = useFieldControl(
12
- () => props.field,
13
- () => props.path
14
- );
15
- const description = computed(() => {
16
- const { content } = props.field;
17
- const resolved = invokeFormFunction(content, [params.value]) ?? content;
18
- return isString(resolved) || isNumber(resolved) ? String(resolved) : void 0;
17
+ const form = useFormRuntimeContext();
18
+ const params = computed(() => form.getFieldCallbackParams(props.path, props.field));
19
+ const displayField = computed(() => props.field.display === void 0 ? void 0 : props.field);
20
+ const display = useInfoDisplay({
21
+ callbackParams: params,
22
+ field: () => displayField.value,
23
+ path: () => props.path
24
+ });
25
+ const shelled = computed(() => {
26
+ const { description, help, hint, label } = props.field;
27
+ return [description, help, hint, label].some((value) => value !== void 0);
28
+ });
29
+ const Bare = (_, { slots }) => slots.default?.();
30
+ const notice = computed(() => {
31
+ const field = props.field;
32
+ if (field.display !== void 0) return void 0;
33
+ const options = field.props === void 0 ? void 0 : resolveFormDynamic(field.props, params.value);
34
+ return {
35
+ appearance: options?.variant ?? "soft",
36
+ icon: options?.icon === false ? false : options?.icon ?? "i-lucide-info",
37
+ message: resolveFormDynamic(field.content, params.value),
38
+ title: resolveFormText(options?.title),
39
+ tone: options?.color ?? "neutral"
40
+ };
19
41
  });
20
42
  </script>
21
43
 
22
44
  <template>
23
- <UAlert
24
- :color="fieldProps.color ?? 'neutral'"
25
- :variant="fieldProps.variant ?? 'soft'"
26
- :icon="fieldProps.icon === false ? void 0 : fieldProps.icon ?? 'i-lucide-info'"
27
- :title="resolveFormText(fieldProps.title)"
28
- :description="description"
29
- />
45
+ <component :is="shelled ? FormFieldShell : Bare" v-bind="shelled ? { field, path } : {}">
46
+ <template v-if="displayField">
47
+ <DisplaySkeleton
48
+ v-if="display.pending.value"
49
+ :layout="DISPLAY_SKELETON_LAYOUTS[displayField.display]"
50
+ :count="3"
51
+ />
52
+ <DisplayLoadError v-else-if="display.error.value !== null" @retry="display.retry" />
53
+ <DisplayBlock
54
+ v-else-if="display.props.value"
55
+ :display="displayField.display"
56
+ :props="display.props.value"
57
+ :preset="displayField.preset"
58
+ />
59
+ </template>
60
+ <DisplayAlert v-else-if="notice" v-bind="notice" />
61
+ </component>
30
62
  </template>
@@ -4,4 +4,9 @@ export declare const infoFieldKind: {
4
4
  };
5
5
  readonly state: "stateless";
6
6
  readonly type: "info";
7
+ readonly ui: {
8
+ readonly description: true;
9
+ readonly hint: true;
10
+ readonly label: true;
11
+ };
7
12
  };
@@ -2,5 +2,6 @@ import { defineFormFieldKind } from "../../utils/field-kind.js";
2
2
  export const infoFieldKind = defineFormFieldKind({
3
3
  layout: { item: true },
4
4
  state: "stateless",
5
- type: "info"
5
+ type: "info",
6
+ ui: { description: true, hint: true, label: true }
6
7
  });
@@ -1,12 +1,86 @@
1
- import type { FormFieldCallback } from '../../types/callbacks.js';
2
- import type { FormStatelessFieldBase } from '../../types/field-base.js';
3
- import type { FormRenderable, FormText } from '../../types/utils.js';
1
+ import type { DisplayKind, DisplayPropsMap } from '../../../display/types/index.js';
2
+ import type { FormFieldCallback, FormFieldCallbackParams } from '../../types/callbacks.js';
3
+ import type { FormQueryOptions } from '../../types/context.js';
4
+ import type { FormFieldDescription, FormFieldProps, FormStatelessFieldBase } from '../../types/field-base.js';
5
+ import type { FormDynamic, FormRenderable, FormText } from '../../types/utils.js';
6
+ /** Props of a notice field: its `content` drawn in an alert. */
4
7
  export interface FormInfoProps {
5
8
  title?: FormText;
6
9
  icon?: string | false;
7
10
  color?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral';
8
11
  variant?: 'solid' | 'outline' | 'soft' | 'subtle';
9
12
  }
10
- export interface FormInfoField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> extends FormStatelessFieldBase<'info', TContext, TDeps, FormInfoProps> {
13
+ interface FormInfoFieldBase<TContext, TDeps> extends Omit<FormStatelessFieldBase<'info', TContext, TDeps>, 'props'> {
14
+ /** Raw dependency paths read before evaluating `props`, `content` and `condition`. */
15
+ dependencies?: readonly (string | readonly [string, string])[];
16
+ /**
17
+ * Draws the field inside the standard label wrapper, like an input, for a read-only value that
18
+ * belongs in the form grid. `description`, `hint` and `help` do the same.
19
+ */
20
+ label?: FormText;
21
+ description?: FormText | FormFieldDescription;
22
+ hint?: FormText;
23
+ help?: FormText;
24
+ }
25
+ /** A notice: `content` in an alert, styled by `props`. */
26
+ export interface FormInfoNoticeField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> extends FormInfoFieldBase<TContext, TDeps> {
27
+ display?: undefined;
28
+ preset?: undefined;
11
29
  content: FormRenderable | FormFieldCallback<FormRenderable, TContext, TDeps>;
30
+ props?: FormFieldProps<FormInfoProps, TContext, TDeps>;
12
31
  }
32
+ /**
33
+ * Props of a display block, or a way to load them: a promise, or TanStack Query options whose
34
+ * data (after `select`) is the props, cached under their `queryKey`.
35
+ */
36
+ export type FormInfoDisplaySource<TProps> = TProps | Promise<TProps> | FormQueryOptions<TProps>;
37
+ /**
38
+ * A display block: `display` picks the kind and `props` configure it, statically or from the
39
+ * field's dependencies, so the block follows the form values. `props` may also load them: a
40
+ * callback returning a promise or query options runs again when a dependency changes, shows the
41
+ * block's skeleton until they arrive, drops a late answer to an older run, and shows a compact
42
+ * error with a retry button when loading fails.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * {
47
+ * key: 'quote',
48
+ * type: 'info',
49
+ * display: 'breakdown',
50
+ * dependencies: ['seats'],
51
+ * props: ({ deps }) => ({
52
+ * lines: [{ label: 'Licences', detail: `${deps.get<number>('seats')} × 96 €`, value: '…' }],
53
+ * total: { label: 'Total', value: '…' },
54
+ * }),
55
+ * }
56
+ *
57
+ * {
58
+ * key: 'accountNote',
59
+ * type: 'info',
60
+ * display: 'text',
61
+ * dependencies: ['accountId'],
62
+ * props: ({ deps }) =>
63
+ * queryOptions({
64
+ * queryKey: ['accounts', deps.get('accountId'), 'note'],
65
+ * queryFn: () => fetchAccountNote(deps.get('accountId')),
66
+ * select: (note) => ({ appearance: 'quote', text: note.body, author: note.author }),
67
+ * }),
68
+ * }
69
+ * ```
70
+ */
71
+ export type FormInfoDisplayField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> = {
72
+ [TKind in DisplayKind]: FormInfoFieldBase<TContext, TDeps> & {
73
+ display: TKind;
74
+ /** Name of a preset in `appConfig.nuxtUiTools.display.presets[display]`; `props` override it. */
75
+ preset?: string;
76
+ content?: undefined;
77
+ /**
78
+ * The block's props, or a callback receiving `{ ctx, deps, api }` that returns them, a promise
79
+ * of them, or query options. `api.form` sets values, submits, or closes the form, so a block
80
+ * action can act on it.
81
+ */
82
+ props: FormDynamic<FormInfoDisplaySource<DisplayPropsMap[TKind]>, FormFieldCallbackParams<TContext, TDeps>>;
83
+ };
84
+ }[DisplayKind];
85
+ export type FormInfoField<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> = FormInfoNoticeField<TContext, TDeps> | FormInfoDisplayField<TContext, TDeps>;
86
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { ComputedRef } from 'vue';
2
+ import type { DisplayKind, DisplayPropsMap } from '../../../display/types/index.js';
3
+ import type { FormFieldCallbackParams } from '../../types/index.js';
4
+ import type { FormInfoDisplayField } from './types.js';
5
+ type DisplayProps = DisplayPropsMap[DisplayKind];
6
+ /**
7
+ * Resolves the props of a display field: static, derived from its dependencies, or loaded through
8
+ * a promise or TanStack Query options. A promise from an older run never overwrites a newer one.
9
+ */
10
+ export declare function useInfoDisplay(params: {
11
+ field: () => FormInfoDisplayField | undefined;
12
+ callbackParams: ComputedRef<FormFieldCallbackParams>;
13
+ path: () => readonly string[];
14
+ }): {
15
+ error: ComputedRef<unknown>;
16
+ pending: ComputedRef<boolean>;
17
+ props: ComputedRef<DisplayProps | undefined>;
18
+ retry: () => void;
19
+ };
20
+ export {};
@@ -0,0 +1,82 @@
1
+ import { useQuery } from "@tanstack/vue-query";
2
+ import { computed, ref, shallowRef, unref, watch } from "vue";
3
+ import { isPlainObject, isPromise } from "../../../shared/utils/predicate.js";
4
+ import { resolveFormDynamic } from "../../utils/dynamic.js";
5
+ import { isRecord } from "../../utils/path.js";
6
+ export function useInfoDisplay(params) {
7
+ const attempt = ref(0);
8
+ const promiseRun = ref(0);
9
+ const promiseProps = shallowRef(void 0);
10
+ const promisePending = ref(false);
11
+ const promiseError = shallowRef(null);
12
+ const source = computed(() => {
13
+ void attempt.value;
14
+ const field = params.field();
15
+ return field ? resolveFormDynamic(
16
+ field.props,
17
+ params.callbackParams.value
18
+ ) : void 0;
19
+ });
20
+ const querySource = computed(() => isQueryOptions(source.value) ? source.value : null);
21
+ const query = useQuery(
22
+ computed(
23
+ () => querySource.value ?? {
24
+ enabled: false,
25
+ queryFn: async () => null,
26
+ queryKey: ["form-info", params.path().join("."), "disabled"]
27
+ }
28
+ )
29
+ );
30
+ watch(
31
+ source,
32
+ async (current) => {
33
+ if (!isPromise(current)) {
34
+ promiseRun.value += 1;
35
+ promisePending.value = false;
36
+ promiseError.value = null;
37
+ return;
38
+ }
39
+ const run = promiseRun.value + 1;
40
+ promiseRun.value = run;
41
+ promisePending.value = true;
42
+ promiseError.value = null;
43
+ try {
44
+ const result = await current;
45
+ if (promiseRun.value !== run) return;
46
+ promiseProps.value = isDisplayProps(result) ? result : void 0;
47
+ } catch (caughtError) {
48
+ if (promiseRun.value === run) promiseError.value = caughtError;
49
+ } finally {
50
+ if (promiseRun.value === run) promisePending.value = false;
51
+ }
52
+ },
53
+ { immediate: true }
54
+ );
55
+ const props = computed(() => {
56
+ const current = source.value;
57
+ if (querySource.value) {
58
+ const data = unref(query.data);
59
+ return isDisplayProps(data) ? data : void 0;
60
+ }
61
+ if (isPromise(current)) return promisePending.value ? void 0 : promiseProps.value;
62
+ return isDisplayProps(current) ? current : void 0;
63
+ });
64
+ const pending = computed(() => {
65
+ if (querySource.value) return unref(query.isPending) && unref(query.fetchStatus) !== "idle";
66
+ return promisePending.value;
67
+ });
68
+ const error = computed(
69
+ () => querySource.value ? unref(query.error) ?? null : promiseError.value
70
+ );
71
+ function retry() {
72
+ if (querySource.value) void query.refetch();
73
+ else attempt.value += 1;
74
+ }
75
+ return { error, pending, props, retry };
76
+ }
77
+ function isQueryOptions(value) {
78
+ return isRecord(value) && Array.isArray(Object.getOwnPropertyDescriptor(value, "queryKey")?.value);
79
+ }
80
+ function isDisplayProps(value) {
81
+ return isPlainObject(value) && !isQueryOptions(value);
82
+ }