nuxt-ui-tools 1.24.0 → 1.25.1

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 (210) 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 +96 -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 +136 -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 +184 -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 +241 -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 +144 -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 +179 -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 +154 -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 +75 -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 +195 -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 +103 -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 +301 -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 +336 -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 +302 -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 +153 -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 +268 -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 +189 -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 +136 -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 +141 -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 +238 -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 +27 -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 +71 -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 +36 -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 +11 -0
  86. package/dist/runtime/display/components/parts/display-record-media.vue +61 -0
  87. package/dist/runtime/display/components/parts/display-record-media.vue.d.ts +11 -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/directives/reveal-truncated.d.ts +10 -0
  108. package/dist/runtime/display/directives/reveal-truncated.js +31 -0
  109. package/dist/runtime/display/index.d.ts +4 -0
  110. package/dist/runtime/display/index.js +3 -0
  111. package/dist/runtime/display/plugins/display-tones.d.ts +3 -0
  112. package/dist/runtime/display/plugins/display-tones.js +17 -0
  113. package/dist/runtime/display/types/blocks/accordion.d.ts +18 -0
  114. package/dist/runtime/display/types/blocks/accordion.js +0 -0
  115. package/dist/runtime/display/types/blocks/alert.d.ts +31 -0
  116. package/dist/runtime/display/types/blocks/alert.js +0 -0
  117. package/dist/runtime/display/types/blocks/breakdown.d.ts +36 -0
  118. package/dist/runtime/display/types/blocks/breakdown.js +0 -0
  119. package/dist/runtime/display/types/blocks/chart.d.ts +23 -0
  120. package/dist/runtime/display/types/blocks/chart.js +0 -0
  121. package/dist/runtime/display/types/blocks/code.d.ts +21 -0
  122. package/dist/runtime/display/types/blocks/code.js +0 -0
  123. package/dist/runtime/display/types/blocks/date.d.ts +27 -0
  124. package/dist/runtime/display/types/blocks/date.js +0 -0
  125. package/dist/runtime/display/types/blocks/diff.d.ts +20 -0
  126. package/dist/runtime/display/types/blocks/diff.js +0 -0
  127. package/dist/runtime/display/types/blocks/empty.d.ts +13 -0
  128. package/dist/runtime/display/types/blocks/empty.js +0 -0
  129. package/dist/runtime/display/types/blocks/facts.d.ts +52 -0
  130. package/dist/runtime/display/types/blocks/facts.js +0 -0
  131. package/dist/runtime/display/types/blocks/heading.d.ts +19 -0
  132. package/dist/runtime/display/types/blocks/heading.js +0 -0
  133. package/dist/runtime/display/types/blocks/list.d.ts +36 -0
  134. package/dist/runtime/display/types/blocks/list.js +0 -0
  135. package/dist/runtime/display/types/blocks/media.d.ts +42 -0
  136. package/dist/runtime/display/types/blocks/media.js +0 -0
  137. package/dist/runtime/display/types/blocks/meter.d.ts +48 -0
  138. package/dist/runtime/display/types/blocks/meter.js +0 -0
  139. package/dist/runtime/display/types/blocks/person.d.ts +34 -0
  140. package/dist/runtime/display/types/blocks/person.js +0 -0
  141. package/dist/runtime/display/types/blocks/record.d.ts +44 -0
  142. package/dist/runtime/display/types/blocks/record.js +0 -0
  143. package/dist/runtime/display/types/blocks/stat.d.ts +44 -0
  144. package/dist/runtime/display/types/blocks/stat.js +0 -0
  145. package/dist/runtime/display/types/blocks/table.d.ts +28 -0
  146. package/dist/runtime/display/types/blocks/table.js +0 -0
  147. package/dist/runtime/display/types/blocks/text.d.ts +22 -0
  148. package/dist/runtime/display/types/blocks/text.js +0 -0
  149. package/dist/runtime/display/types/blocks/timeline.d.ts +37 -0
  150. package/dist/runtime/display/types/blocks/timeline.js +0 -0
  151. package/dist/runtime/display/types/config.d.ts +60 -0
  152. package/dist/runtime/display/types/config.js +0 -0
  153. package/dist/runtime/display/types/index.d.ts +22 -0
  154. package/dist/runtime/display/types/index.js +0 -0
  155. package/dist/runtime/display/types/registry.d.ts +57 -0
  156. package/dist/runtime/display/types/registry.js +0 -0
  157. package/dist/runtime/display/types/shared.d.ts +80 -0
  158. package/dist/runtime/display/types/shared.js +0 -0
  159. package/dist/runtime/display/utils/classes.d.ts +3 -0
  160. package/dist/runtime/display/utils/classes.js +7 -0
  161. package/dist/runtime/display/utils/config.d.ts +11 -0
  162. package/dist/runtime/display/utils/config.js +17 -0
  163. package/dist/runtime/display/utils/content.d.ts +7 -0
  164. package/dist/runtime/display/utils/content.js +13 -0
  165. package/dist/runtime/display/utils/dates.d.ts +16 -0
  166. package/dist/runtime/display/utils/dates.js +34 -0
  167. package/dist/runtime/display/utils/json.d.ts +7 -0
  168. package/dist/runtime/display/utils/json.js +15 -0
  169. package/dist/runtime/display/utils/links.d.ts +12 -0
  170. package/dist/runtime/display/utils/links.js +9 -0
  171. package/dist/runtime/display/utils/meter.d.ts +7 -0
  172. package/dist/runtime/display/utils/meter.js +13 -0
  173. package/dist/runtime/display/utils/registry.d.ts +47 -0
  174. package/dist/runtime/display/utils/registry.js +64 -0
  175. package/dist/runtime/display/utils/tone-css.d.ts +7 -0
  176. package/dist/runtime/display/utils/tone-css.js +38 -0
  177. package/dist/runtime/display/utils/tone.d.ts +26 -0
  178. package/dist/runtime/display/utils/tone.js +38 -0
  179. package/dist/runtime/display/utils/urls.d.ts +10 -0
  180. package/dist/runtime/display/utils/urls.js +33 -0
  181. package/dist/runtime/file-preview/components/shell/file-preview-stage.vue +5 -3
  182. package/dist/runtime/file-preview/composables/use-file-preview-source.d.ts +7 -0
  183. package/dist/runtime/file-preview/composables/use-file-preview-source.js +7 -1
  184. package/dist/runtime/file-preview/index.d.ts +1 -0
  185. package/dist/runtime/file-preview/index.js +1 -0
  186. package/dist/runtime/file-preview/types/file.d.ts +9 -0
  187. package/dist/runtime/file-preview/utils/errors.d.ts +30 -0
  188. package/dist/runtime/file-preview/utils/errors.js +26 -0
  189. package/dist/runtime/form/components/utils/form-file-preview.vue +7 -2
  190. package/dist/runtime/form/composables/use-form-root.js +17 -4
  191. package/dist/runtime/form/composables/use-form-runtime.js +7 -2
  192. package/dist/runtime/form/fields/index.d.ts +5 -0
  193. package/dist/runtime/form/fields/info/component.vue +51 -19
  194. package/dist/runtime/form/fields/info/config.d.ts +5 -0
  195. package/dist/runtime/form/fields/info/config.js +2 -1
  196. package/dist/runtime/form/fields/info/types.d.ts +78 -4
  197. package/dist/runtime/form/fields/info/use-info-display.d.ts +20 -0
  198. package/dist/runtime/form/fields/info/use-info-display.js +82 -0
  199. package/dist/runtime/form/fields/upload/upload-item.vue +10 -2
  200. package/dist/runtime/form/types/api.d.ts +6 -0
  201. package/dist/runtime/form/types/runtime.d.ts +2 -0
  202. package/dist/runtime/form/utils/dynamic.d.ts +6 -0
  203. package/dist/runtime/form/utils/dynamic.js +7 -0
  204. package/dist/runtime/i18n/locales/en.js +27 -0
  205. package/dist/runtime/i18n/locales/fr.js +27 -0
  206. package/dist/runtime/i18n/types.d.ts +35 -0
  207. package/dist/runtime/shared/styles/tokens.css +1 -1
  208. package/dist/runtime/shared/utils/file.d.ts +1 -1
  209. package/dist/runtime/table/utils/data-list-ui.d.ts +1 -1
  210. package/package.json +16 -1
@@ -0,0 +1,64 @@
1
+ import DisplayAccordion from "../components/blocks/display-accordion.vue";
2
+ import DisplayAlert from "../components/blocks/display-alert.vue";
3
+ import DisplayBreakdown from "../components/blocks/display-breakdown.vue";
4
+ import DisplayChart from "../components/blocks/display-chart.vue";
5
+ import DisplayCode from "../components/blocks/display-code.vue";
6
+ import DisplayDate from "../components/blocks/display-date.vue";
7
+ import DisplayDiff from "../components/blocks/display-diff.vue";
8
+ import DisplayEmpty from "../components/blocks/display-empty.vue";
9
+ import DisplayFacts from "../components/blocks/display-facts.vue";
10
+ import DisplayHeading from "../components/blocks/display-heading.vue";
11
+ import DisplayList from "../components/blocks/display-list.vue";
12
+ import DisplayMedia from "../components/blocks/display-media.vue";
13
+ import DisplayMeter from "../components/blocks/display-meter.vue";
14
+ import DisplayPerson from "../components/blocks/display-person.vue";
15
+ import DisplayRecord from "../components/blocks/display-record.vue";
16
+ import DisplayStat from "../components/blocks/display-stat.vue";
17
+ import DisplayTable from "../components/blocks/display-table.vue";
18
+ import DisplayText from "../components/blocks/display-text.vue";
19
+ import DisplayTimeline from "../components/blocks/display-timeline.vue";
20
+ export const DISPLAY_BLOCKS = {
21
+ accordion: DisplayAccordion,
22
+ alert: DisplayAlert,
23
+ breakdown: DisplayBreakdown,
24
+ chart: DisplayChart,
25
+ code: DisplayCode,
26
+ date: DisplayDate,
27
+ diff: DisplayDiff,
28
+ empty: DisplayEmpty,
29
+ facts: DisplayFacts,
30
+ heading: DisplayHeading,
31
+ list: DisplayList,
32
+ media: DisplayMedia,
33
+ meter: DisplayMeter,
34
+ person: DisplayPerson,
35
+ record: DisplayRecord,
36
+ stat: DisplayStat,
37
+ table: DisplayTable,
38
+ text: DisplayText,
39
+ timeline: DisplayTimeline
40
+ };
41
+ export const DISPLAY_KINDS = Object.freeze(
42
+ Object.keys(DISPLAY_BLOCKS).filter((kind) => kind in DISPLAY_BLOCKS)
43
+ );
44
+ export const DISPLAY_SKELETON_LAYOUTS = {
45
+ accordion: "rows",
46
+ alert: "block",
47
+ breakdown: "rows",
48
+ chart: "block",
49
+ code: "block",
50
+ date: "card",
51
+ diff: "rows",
52
+ empty: "block",
53
+ facts: "rows",
54
+ heading: "lines",
55
+ list: "lines",
56
+ media: "media",
57
+ meter: "lines",
58
+ person: "card",
59
+ record: "card",
60
+ stat: "lines",
61
+ table: "rows",
62
+ text: "lines",
63
+ timeline: "lines"
64
+ };
@@ -0,0 +1,7 @@
1
+ import type { DisplayUiConfig } from '../types/index.js';
2
+ /**
3
+ * The stylesheet of the tones an app registers or restyles: their `--nut-display-<tone>*`
4
+ * variables (light, then `.dark`) and, for new tones, the class that points `--nut-tone*` at them.
5
+ * Names other than lowercase words and values that could leave the declaration are skipped.
6
+ */
7
+ export declare function buildDisplayToneCss(tones: DisplayUiConfig['tones']): string;
@@ -0,0 +1,38 @@
1
+ import { isBuiltinTone } from "./tone.js";
2
+ const TONE_NAME = /^[a-z][a-z0-9-]*$/u;
3
+ const UNSAFE_VALUE = /[{}<>;]/u;
4
+ export function buildDisplayToneCss(tones) {
5
+ const light = [];
6
+ const dark = [];
7
+ const classes = [];
8
+ for (const [name, definition] of Object.entries(tones ?? {})) {
9
+ if (!definition || !TONE_NAME.test(name)) continue;
10
+ const builtin = isBuiltinTone(name);
11
+ if (!builtin && !definition.color) continue;
12
+ light.push(...declarations(name, definition, !builtin));
13
+ if (definition.dark) dark.push(...declarations(name, definition.dark, false));
14
+ if (!builtin) classes.push(toneClassRule(name));
15
+ }
16
+ return [
17
+ light.length ? `:root{${light.join(";")}}` : "",
18
+ dark.length ? `.dark{${dark.join(";")}}` : "",
19
+ ...classes
20
+ ].filter(Boolean).join("\n");
21
+ }
22
+ function declarations(name, colors, fill) {
23
+ const mix = (share) => fill && colors.color ? `color-mix(in oklab, ${colors.color} ${share}%, var(--ui-bg))` : void 0;
24
+ const entries = [
25
+ ["", colors.color],
26
+ ["-ink", colors.ink ?? (fill ? colors.color : void 0)],
27
+ ["-soft", colors.soft ?? mix(12)],
28
+ ["-line", colors.line ?? mix(40)],
29
+ ["-contrast", colors.contrast ?? (fill ? "var(--ui-text-inverted)" : void 0)]
30
+ ];
31
+ return entries.flatMap(
32
+ ([part, value]) => value && !UNSAFE_VALUE.test(value) ? [`--nut-display-${name}${part}:${value}`] : []
33
+ );
34
+ }
35
+ function toneClassRule(name) {
36
+ const variable = (part) => `var(--nut-display-${name}${part ? `-${part}` : ""})`;
37
+ return `:where(.nut-display-tone-${name}){--nut-tone:${variable("")};--nut-tone-ink:${variable("ink")};--nut-tone-soft:${variable("soft")};--nut-tone-line:${variable("line")};--nut-tone-contrast:${variable("contrast")}}`;
38
+ }
@@ -0,0 +1,26 @@
1
+ import type { DisplayBuiltinTone, DisplayTone } from '../types/index.js';
2
+ /**
3
+ * Class that points the `--nut-tone*` variables of an element at a tone. Built-in tones are
4
+ * defined in the token stylesheet; registered ones by the rules the display plugin injects.
5
+ */
6
+ export declare function displayToneClass(tone: DisplayTone): string;
7
+ /** Whether a tone is one of the Nuxt UI colors, which Nuxt UI components take as `color`. */
8
+ export declare function isBuiltinTone(tone: string): tone is DisplayBuiltinTone;
9
+ /** Box classes of each surface. `plain` adds nothing so a block sits on the page. */
10
+ export declare const DISPLAY_SURFACE_CLASSES: {
11
+ readonly bordered: "rounded-[var(--nut-display-radius)] border border-default";
12
+ readonly elevated: "rounded-[var(--nut-display-radius)] bg-default shadow-[0_10px_28px_-14px_rgb(0_0_0/0.3)] ring ring-default";
13
+ readonly muted: "rounded-[var(--nut-display-radius)] bg-elevated";
14
+ readonly plain: "";
15
+ };
16
+ /** Padding applied inside a boxed surface. */
17
+ export declare const DISPLAY_SURFACE_PADDING: {
18
+ readonly bordered: "p-4";
19
+ readonly elevated: "p-4";
20
+ readonly muted: "p-4";
21
+ readonly plain: "";
22
+ };
23
+ /** A stable tone for a name, so a person keeps the same tint everywhere. */
24
+ export declare function toneFromName(name: string): DisplayTone;
25
+ /** Up to two initials of a name. */
26
+ export declare function initialsOf(name: string): string;
@@ -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
  />
@@ -1,5 +1,5 @@
1
1
  import { useAppConfig } from "nuxt/app";
2
- import { computed, nextTick, onBeforeUnmount, onMounted, watch } from "vue";
2
+ import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, watch } from "vue";
3
3
  import { useRouter } from "vue-router";
4
4
  import { useUiToolsLocale } from "../../i18n/use-locale.js";
5
5
  import {
@@ -19,6 +19,7 @@ import { provideFormRuntime, useFormRuntime } from "./use-form-runtime.js";
19
19
  import { provideFormUi } from "./use-form-ui.js";
20
20
  export function useFormRoot(params) {
21
21
  const router = useRouter();
22
+ const appContext = getCurrentInstance()?.appContext;
22
23
  const { t } = useUiToolsLocale();
23
24
  const appConfig = useAppConfig();
24
25
  const schema = computed(() => params.form()?.schema.value ?? params.schema() ?? {});
@@ -37,6 +38,8 @@ export function useFormRoot(params) {
37
38
  const ownedRuntime = params.runtime?.();
38
39
  const runtime = ownedRuntime ?? useFormRuntime({ input, schema, syncInput, validationMode });
39
40
  provideFormRuntime(runtime);
41
+ runtime.bindCancel(() => cancel());
42
+ onBeforeUnmount(() => runtime.bindCancel(null));
40
43
  const confirm = useFormConfirm();
41
44
  let leaveConfirmed = false;
42
45
  watch(
@@ -120,11 +123,21 @@ export function useFormRoot(params) {
120
123
  }
121
124
  async function submit() {
122
125
  const controller = params.form();
123
- const result = controller ? await controller.submitHandler() : await runtime.submitHandler();
124
- if (result.success) {
125
- params.onSubmitted(runtime.output.value, result);
126
+ try {
127
+ const result = controller ? await controller.submitHandler() : await runtime.submitHandler();
128
+ if (result.success) params.onSubmitted(runtime.output.value, result);
129
+ } catch (error) {
130
+ reportSubmitError(error);
126
131
  }
127
132
  }
133
+ function reportSubmitError(error) {
134
+ const handler = appContext?.config.errorHandler;
135
+ if (handler) handler(error, null, "nuxt-ui-tools form submit");
136
+ else
137
+ queueMicrotask(() => {
138
+ throw error;
139
+ });
140
+ }
128
141
  async function cancel() {
129
142
  if (!leaveConfirmed && shouldConfirmDirtyNavigation() && !await confirmLeave()) {
130
143
  return;
@@ -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
  });