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,301 @@
1
+ <script setup>
2
+ import UButton from "@nuxt/ui/components/Button.vue";
3
+ import UIcon from "@nuxt/ui/components/Icon.vue";
4
+ import { computed } from "vue";
5
+ import { fileExtensionLabel, formatFileSize } from "../../../shared/utils/file";
6
+ import { isString } from "../../../shared/utils/predicate";
7
+ import { useDisplayAppUi } from "../../composables/use-display-config";
8
+ import { useDisplayFilePreview } from "../../composables/use-display-file-preview";
9
+ import { useDisplayIcons } from "../../composables/use-display-icons";
10
+ import { useDisplayLocale } from "../../composables/use-display-locale";
11
+ import { useDisplayReveal } from "../../composables/use-display-reveal";
12
+ import { useDisplayUrlActions } from "../../composables/use-display-url";
13
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
14
+ import { resolveDisplayClasses } from "../../utils/classes";
15
+ import { displayContentText, isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
16
+ import { displayToneClass } from "../../utils/tone";
17
+ import { downloadDisplayUrl, openDisplayUrl } from "../../utils/urls";
18
+ import DisplayChip from "../parts/display-chip.vue";
19
+ import DisplayEmptyLine from "../parts/display-empty-line.vue";
20
+ import DisplayLoadError from "../parts/display-load-error.vue";
21
+ import DisplayMore from "../parts/display-more.vue";
22
+ import DisplayRender from "../parts/display-render";
23
+ import DisplaySkeleton from "../parts/display-skeleton.vue";
24
+ const {
25
+ variant = "bullets",
26
+ title = void 0,
27
+ items,
28
+ columns = 2,
29
+ preview = true,
30
+ max = void 0,
31
+ step = void 0,
32
+ empty = void 0,
33
+ loading = false,
34
+ ui = void 0
35
+ } = defineProps({
36
+ variant: { type: String, required: false },
37
+ title: { type: [String, Number, Function, Object, Boolean, null, Array], required: false, skipCheck: true },
38
+ items: { type: Array, required: true },
39
+ columns: { type: Number, required: false },
40
+ preview: { type: Boolean, required: false },
41
+ loading: { type: Boolean, required: false },
42
+ ui: { type: Object, required: false },
43
+ max: { type: Number, required: false },
44
+ step: { type: Number, required: false },
45
+ empty: { type: [String, Number, Function, Object, Boolean, null, Array], required: false, skipCheck: true }
46
+ });
47
+ const LIST_CLASSES = {
48
+ bullets: "flex flex-col gap-1.5",
49
+ checks: "flex flex-col gap-1.5",
50
+ chips: "flex flex-wrap gap-1.5",
51
+ columns: "block",
52
+ files: "flex flex-col divide-y divide-muted rounded-[var(--nut-display-radius)] border border-default",
53
+ icons: "flex flex-col gap-1.5",
54
+ numbered: "flex flex-col gap-1.5",
55
+ steps: "flex flex-col"
56
+ };
57
+ const ITEM_CLASSES = {
58
+ bullets: "relative ps-4",
59
+ checks: "flex items-start gap-2",
60
+ chips: "inline-flex",
61
+ columns: "relative mb-1.5 break-inside-avoid ps-4",
62
+ files: "flex min-w-0 items-center gap-2.5 px-2.5 py-2",
63
+ icons: "flex items-start gap-2",
64
+ numbered: "flex items-start gap-2.5",
65
+ steps: "relative grid grid-cols-[1.375rem_minmax(0,1fr)] gap-x-2.5 pb-3 before:absolute before:start-[0.6875rem] before:top-6 before:bottom-0 before:w-px before:bg-[var(--ui-border)] last:pb-0 last:before:hidden"
66
+ };
67
+ const MARKER_CLASSES = {
68
+ bullets: "absolute start-1 top-[0.6em] size-1.5 rounded-full bg-[var(--ui-border-accented)]",
69
+ checks: "mt-0.5 size-4 shrink-0",
70
+ chips: "",
71
+ columns: "absolute start-1 top-[0.6em] size-1.5 rounded-full bg-[var(--ui-border-accented)]",
72
+ files: "grid h-8 w-7 shrink-0 place-items-center rounded-[4px] bg-[var(--nut-tone)] text-[8.5px] font-extrabold tracking-[0.02em] text-[var(--nut-tone-contrast)]",
73
+ icons: "mt-0.5 size-4 shrink-0 text-muted",
74
+ numbered: "grid size-5 shrink-0 place-items-center rounded-full bg-elevated text-[11px] font-bold text-toned",
75
+ steps: "grid size-5.5 place-items-center rounded-full text-[11px] font-bold"
76
+ };
77
+ const FILE_TONES = [
78
+ [["pdf"], "error"],
79
+ [["xls", "xlsx", "csv", "ods"], "success"],
80
+ [["doc", "docx", "odt", "txt", "md"], "info"],
81
+ [["png", "jpg", "jpeg", "gif", "webp", "svg", "avif"], "secondary"],
82
+ [["zip", "rar", "7z", "gz"], "warning"]
83
+ ];
84
+ const { t, code } = useDisplayLocale();
85
+ const icons = useDisplayIcons();
86
+ const filePreview = useDisplayFilePreview();
87
+ const links = useDisplayUrlActions();
88
+ const appUi = useDisplayAppUi("list");
89
+ const reveal = useDisplayReveal({ items: () => items, max: () => max, step: () => step });
90
+ const files = computed(
91
+ () => items.map((item) => ({
92
+ item,
93
+ label: fileExtensionLabel({ name: fileName(item), type: item.type }),
94
+ size: item.size === void 0 ? void 0 : formatFileSize(item.size, code.value)
95
+ }))
96
+ );
97
+ const classes = computed(
98
+ () => resolveDisplayClasses(
99
+ {
100
+ action: "ms-auto shrink-0",
101
+ description: variant === "files" ? "text-[11px] text-dimmed" : "text-xs text-muted",
102
+ empty: "",
103
+ item: ITEM_CLASSES[variant],
104
+ list: `m-0 list-none p-0 text-sm ${LIST_CLASSES[variant]}`,
105
+ marker: MARKER_CLASSES[variant],
106
+ meta: "ms-auto shrink-0 text-xs text-dimmed",
107
+ more: "self-start",
108
+ root: "flex min-w-0 flex-col gap-2",
109
+ text: variant === "files" ? "truncate font-medium text-highlighted" : "min-w-0 text-default",
110
+ title: "text-sm font-semibold text-toned"
111
+ },
112
+ appUi.value,
113
+ ui
114
+ )
115
+ );
116
+ const ordered = computed(() => variant === "numbered" || variant === "steps");
117
+ function fileName(item) {
118
+ return item.name ?? displayContentText(item.text) ?? (isString(item.href) ? item.href : "");
119
+ }
120
+ function fileTone(item) {
121
+ const extension = fileName(item).split(".").at(-1)?.toLowerCase() ?? "";
122
+ return FILE_TONES.find(([extensions]) => extensions.includes(extension))?.[1] ?? "neutral";
123
+ }
124
+ function stepState(item) {
125
+ if (item.done) return "done";
126
+ return item.current ? "current" : "todo";
127
+ }
128
+ function stepClass(item) {
129
+ const state = stepState(item);
130
+ if (state === "done")
131
+ return "bg-[var(--nut-display-success-soft)] text-[var(--nut-display-success-ink)]";
132
+ if (state === "current")
133
+ return "bg-[var(--nut-tone)] text-[var(--nut-tone-contrast)] ring-3 ring-[var(--nut-tone-soft)]";
134
+ return "bg-elevated text-dimmed";
135
+ }
136
+ function openFile(event, index) {
137
+ const href = items[index]?.href;
138
+ if (!href) return;
139
+ const previewed = preview && filePreview.open(
140
+ items.flatMap(
141
+ (item) => item.href ? [{ mime: item.type, name: fileName(item), size: item.size, src: item.href }] : []
142
+ ),
143
+ items.slice(0, index).filter((item) => item.href).length
144
+ );
145
+ if (previewed) {
146
+ event.preventDefault();
147
+ return;
148
+ }
149
+ if (isString(href)) return;
150
+ event.preventDefault();
151
+ void links.run(`open-${index}`, () => openDisplayUrl(href));
152
+ }
153
+ function downloadFile(index) {
154
+ const item = items[index];
155
+ if (!item?.href) return;
156
+ const href = item.href;
157
+ void links.run(`download-${index}`, () => downloadDisplayUrl(href, fileName(item)));
158
+ }
159
+ </script>
160
+
161
+ <template>
162
+ <DisplaySkeleton v-if="loading" :count="Math.min(Math.max(items.length, 2), 5)" />
163
+ <div v-else :class="classes.root" data-display="list" :data-variant="variant">
164
+ <div v-if="!isDisplayContentEmpty(title)" v-reveal-truncated :class="classes.title">
165
+ <DisplayRender :content="title" />
166
+ </div>
167
+ <DisplayEmptyLine v-if="!items.length" :content="empty" :class="classes.empty" />
168
+ <component
169
+ :is="ordered ? 'ol' : 'ul'"
170
+ v-else
171
+ :class="classes.list"
172
+ :style="variant === 'columns' ? { columnCount: columns, columnGap: '1.25rem' } : void 0"
173
+ >
174
+ <li
175
+ v-for="(item, index) in reveal.visible.value"
176
+ :key="index"
177
+ :class="[
178
+ classes.item,
179
+ item.tone ? displayToneClass(item.tone) : displayToneClass('primary')
180
+ ]"
181
+ :data-state="
182
+ variant === 'steps' ? stepState(item) : variant === 'checks' ? item.done ? 'done' : 'todo' : void 0
183
+ "
184
+ >
185
+ <template v-if="variant === 'chips'">
186
+ <DisplayChip
187
+ :chip="{ icon: item.icon, size: 'md', tone: item.tone ?? 'neutral' }"
188
+ :content="item.text"
189
+ :title="resolveDisplayText(item.tooltip)"
190
+ />
191
+ </template>
192
+ <template v-else-if="variant === 'files'">
193
+ <span :class="[classes.marker, displayToneClass(fileTone(item))]" aria-hidden="true">
194
+ {{ files[index]?.label }}
195
+ </span>
196
+ <div class="min-w-0 flex-1">
197
+ <div v-reveal-truncated :class="classes.text">
198
+ <a
199
+ v-if="item.href"
200
+ :href="isString(item.href) ? item.href : '#'"
201
+ target="_blank"
202
+ rel="noopener"
203
+ class="hover:underline"
204
+ :aria-busy="links.pending.value === `open-${index}` || void 0"
205
+ @click="openFile($event, index)"
206
+ >
207
+ <DisplayRender :content="item.text" />
208
+ </a>
209
+ <DisplayRender v-else :content="item.text" />
210
+ </div>
211
+ <div
212
+ v-if="files[index]?.size || !isDisplayContentEmpty(item.description)"
213
+ v-reveal-truncated
214
+ :class="classes.description"
215
+ >
216
+ {{ files[index]?.size
217
+ }}<template v-if="files[index]?.size && !isDisplayContentEmpty(item.description)">
218
+ ·
219
+ </template>
220
+ <DisplayRender :content="item.description" />
221
+ </div>
222
+ </div>
223
+ <UButton
224
+ v-if="item.href"
225
+ :aria-label="t('display.download')"
226
+ :loading="
227
+ links.pending.value === `download-${index}` || links.pending.value === `open-${index}`
228
+ "
229
+ icon="i-lucide-download"
230
+ color="neutral"
231
+ variant="ghost"
232
+ size="xs"
233
+ square
234
+ :class="classes.action"
235
+ data-display-download
236
+ @click="downloadFile(index)"
237
+ />
238
+ </template>
239
+ <template v-else>
240
+ <span
241
+ v-if="variant === 'bullets' || variant === 'columns'"
242
+ :class="classes.marker"
243
+ aria-hidden="true"
244
+ />
245
+ <span v-else-if="variant === 'numbered'" :class="classes.marker" aria-hidden="true">{{
246
+ index + 1
247
+ }}</span>
248
+ <UIcon
249
+ v-else-if="variant === 'icons'"
250
+ :name="item.icon ?? icons.chevronRight"
251
+ :class="[classes.marker, item.tone ? 'text-[var(--nut-tone-ink)]' : '']"
252
+ />
253
+ <UIcon
254
+ v-else-if="variant === 'checks'"
255
+ :name="item.done ? icons.success : 'i-lucide-circle'"
256
+ :class="[
257
+ classes.marker,
258
+ item.done ? 'text-[var(--nut-display-success-ink)]' : 'text-dimmed'
259
+ ]"
260
+ />
261
+ <span
262
+ v-else-if="variant === 'steps'"
263
+ :class="[classes.marker, stepClass(item)]"
264
+ aria-hidden="true"
265
+ >
266
+ <UIcon v-if="item.done" :name="icons.check" class="size-3" />
267
+ <template v-else>{{ index + 1 }}</template>
268
+ </span>
269
+ <div class="min-w-0 flex-1">
270
+ <div
271
+ :class="[
272
+ classes.text,
273
+ variant === 'steps' ? stepState(item) === 'todo' ? 'leading-[1.375rem] font-medium text-muted' : 'leading-[1.375rem] font-semibold text-highlighted' : '',
274
+ variant === 'checks' && !item.done ? 'text-muted' : ''
275
+ ]"
276
+ >
277
+ <DisplayRender :content="item.text" />
278
+ </div>
279
+ <div
280
+ v-if="!isDisplayContentEmpty(item.description)"
281
+ v-reveal-truncated
282
+ :class="classes.description"
283
+ >
284
+ <DisplayRender :content="item.description" />
285
+ </div>
286
+ </div>
287
+ <span v-if="!isDisplayContentEmpty(item.meta)" v-reveal-truncated :class="classes.meta"
288
+ ><DisplayRender :content="item.meta"
289
+ /></span>
290
+ </template>
291
+ </li>
292
+ </component>
293
+ <DisplayLoadError v-if="links.failed.value" @retry="links.failed.value?.retry()" />
294
+ <DisplayMore
295
+ v-if="reveal.hidden.value"
296
+ :count="reveal.next.value"
297
+ :class="classes.more"
298
+ @reveal="reveal.reveal"
299
+ />
300
+ </div>
301
+ </template>
@@ -0,0 +1,4 @@
1
+ import type { DisplayListProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<DisplayListProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<DisplayListProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
+ declare const _default: typeof __VLS_export;
4
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import type { DisplayMediaProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<DisplayMediaProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<DisplayMediaProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
+ declare const _default: typeof __VLS_export;
4
+ export default _default;
@@ -0,0 +1,336 @@
1
+ <script setup>
2
+ import UIcon from "@nuxt/ui/components/Icon.vue";
3
+ import ULink from "@nuxt/ui/components/Link.vue";
4
+ import { computed, ref } from "vue";
5
+ import { fileExtensionLabel, fileNameFromUrl, formatFileSize } from "../../../shared/utils/file";
6
+ import { isString } from "../../../shared/utils/predicate";
7
+ import { useDisplayAppUi } from "../../composables/use-display-config";
8
+ import { useDisplayFilePreview } from "../../composables/use-display-file-preview";
9
+ import { useDisplayLocale } from "../../composables/use-display-locale";
10
+ import { useDisplayUrlActions, useResolvedDisplayUrl } from "../../composables/use-display-url";
11
+ import { vRevealTruncated } from "../../directives/reveal-truncated";
12
+ import { resolveDisplayClasses } from "../../utils/classes";
13
+ import { isDisplayContentEmpty, resolveDisplayText } from "../../utils/content";
14
+ import { downloadDisplayUrl, openDisplayUrl } from "../../utils/urls";
15
+ import DisplayActions from "../parts/display-actions.vue";
16
+ import DisplayLoadError from "../parts/display-load-error.vue";
17
+ import DisplayRender from "../parts/display-render";
18
+ import DisplaySkeleton from "../parts/display-skeleton.vue";
19
+ const {
20
+ variant = "image",
21
+ src = void 0,
22
+ alt = void 0,
23
+ caption = void 0,
24
+ images = void 0,
25
+ max = 3,
26
+ thumbnail = void 0,
27
+ title = void 0,
28
+ duration = void 0,
29
+ name = void 0,
30
+ size = void 0,
31
+ type = void 0,
32
+ pages = void 0,
33
+ href = void 0,
34
+ description = void 0,
35
+ aspect = "16 / 9",
36
+ preview = true,
37
+ actions = void 0,
38
+ loading = false,
39
+ ui = void 0
40
+ } = defineProps({
41
+ variant: { type: String, required: false },
42
+ src: { type: [String, Function], required: false },
43
+ alt: { type: String, required: false },
44
+ caption: { type: [String, Number, Function, Object, Boolean, null, Array], required: false, skipCheck: true },
45
+ images: { type: Array, required: false },
46
+ max: { type: Number, required: false },
47
+ thumbnail: { type: String, required: false },
48
+ title: { type: [String, Number, Function, Object, Boolean, null, Array], required: false, skipCheck: true },
49
+ duration: { type: [String, Number, Function], required: false },
50
+ name: { type: String, required: false },
51
+ size: { type: Number, required: false },
52
+ type: { type: String, required: false },
53
+ pages: { type: Number, required: false },
54
+ href: { type: [String, Function], required: false },
55
+ description: { type: [String, Number, Function, Object, Boolean, null, Array], required: false, skipCheck: true },
56
+ aspect: { type: String, required: false },
57
+ preview: { type: Boolean, required: false },
58
+ actions: { type: Array, required: false },
59
+ loading: { type: Boolean, required: false },
60
+ ui: { type: Object, required: false }
61
+ });
62
+ const { t, code } = useDisplayLocale();
63
+ const filePreview = useDisplayFilePreview();
64
+ const appUi = useDisplayAppUi("media");
65
+ const playing = ref(false);
66
+ const links = useDisplayUrlActions();
67
+ const shown = useResolvedDisplayUrl(
68
+ () => variant === "image" || variant === "video" || variant === "gallery" && !images ? src : void 0
69
+ );
70
+ const gallery = computed(
71
+ () => images ?? (shown.url.value ? [{ alt, src: shown.url.value }] : [])
72
+ );
73
+ const tiles = computed(() => gallery.value.slice(0, Math.max(1, max)));
74
+ const hiddenCount = computed(() => gallery.value.length - tiles.value.length);
75
+ const fileName = computed(() => name ?? (isString(src) ? fileNameFromUrl(src) : ""));
76
+ const documentMeta = computed(
77
+ () => [
78
+ fileExtensionLabel({ name: fileName.value, type }),
79
+ size === void 0 ? void 0 : formatFileSize(size, code.value),
80
+ pages === void 0 ? void 0 : t("display.pages", { count: pages })
81
+ ].filter(Boolean).join(" \xB7 ")
82
+ );
83
+ const documentActions = computed(
84
+ () => actions ?? (src ? [
85
+ {
86
+ icon: "i-lucide-eye",
87
+ label: t("display.open"),
88
+ loading: links.pending.value === "open",
89
+ onClick: openDocument
90
+ },
91
+ {
92
+ icon: "i-lucide-download",
93
+ label: t("display.download"),
94
+ loading: links.pending.value === "download",
95
+ onClick: downloadDocument
96
+ }
97
+ ] : [])
98
+ );
99
+ const domain = computed(() => {
100
+ if (!isString(href)) return "";
101
+ try {
102
+ return new URL(href).hostname.replace(/^www\./u, "");
103
+ } catch {
104
+ return href;
105
+ }
106
+ });
107
+ const galleryGrid = computed(() => {
108
+ const count = tiles.value.length;
109
+ if (count >= 3) return "grid-cols-[2fr_1fr] grid-rows-2 [&>*:first-child]:row-span-2";
110
+ return count === 2 ? "grid-cols-2" : "grid-cols-1";
111
+ });
112
+ const classes = computed(
113
+ () => resolveDisplayClasses(
114
+ {
115
+ actions: "mt-2 flex flex-wrap gap-1.5",
116
+ body: "min-w-0 flex-1",
117
+ caption: "flex items-center gap-1.5 text-xs text-muted",
118
+ description: "line-clamp-2 text-xs text-muted",
119
+ domain: "flex items-center gap-1.5 text-xs text-muted",
120
+ duration: "absolute end-2 bottom-2 rounded-[4px] bg-black/60 px-1.5 py-px text-[11px] font-semibold text-white",
121
+ frame: "relative w-full overflow-hidden rounded-[var(--nut-display-radius)] bg-elevated",
122
+ grid: `grid w-full gap-1 overflow-hidden rounded-[var(--nut-display-radius)] ${galleryGrid.value}`,
123
+ image: "size-full object-cover",
124
+ meta: "mt-0.5 text-xs text-dimmed",
125
+ name: "text-sm font-semibold break-all text-highlighted",
126
+ overflow: "absolute inset-0 grid place-items-center bg-black/50 text-base font-bold text-white",
127
+ page: "flex h-19 w-15 shrink-0 flex-col gap-1 overflow-hidden rounded-[4px] border border-default bg-white p-1.5",
128
+ play: "grid size-12 place-items-center rounded-full bg-white/90 text-black shadow-lg transition-transform group-hover:scale-105",
129
+ root: variant === "document" ? "flex min-w-0 gap-3 rounded-[var(--nut-display-radius)] border border-default p-2.5" : variant === "link" ? "flex min-w-0 overflow-hidden rounded-[var(--nut-display-radius)] border border-default transition-colors hover:bg-elevated" : "m-0 flex min-w-0 flex-col gap-1.5",
130
+ thumbnail: "w-24 shrink-0 object-cover",
131
+ tile: "relative min-h-0 overflow-hidden bg-elevated",
132
+ title: "text-sm font-semibold text-highlighted"
133
+ },
134
+ appUi.value,
135
+ ui
136
+ )
137
+ );
138
+ function openImages(index) {
139
+ if (!preview) return;
140
+ filePreview.open(
141
+ gallery.value.map((image) => ({ name: image.alt, src: image.src })),
142
+ index
143
+ );
144
+ }
145
+ function openDocument() {
146
+ if (!src) return;
147
+ if (preview && filePreview.open([{ mime: type, name: fileName.value, size, src }])) return;
148
+ void links.run("open", () => openDisplayUrl(src));
149
+ }
150
+ function downloadDocument() {
151
+ if (src) void links.run("download", () => downloadDisplayUrl(src, fileName.value || void 0));
152
+ }
153
+ function openLink() {
154
+ if (href) void links.run("open", () => openDisplayUrl(href));
155
+ }
156
+ </script>
157
+
158
+ <template>
159
+ <DisplaySkeleton
160
+ v-if="loading"
161
+ :layout="variant === 'document' || variant === 'link' ? 'card' : 'media'"
162
+ />
163
+ <figure
164
+ v-else-if="variant === 'image'"
165
+ :class="classes.root"
166
+ data-display="media"
167
+ data-variant="image"
168
+ >
169
+ <component
170
+ :is="preview && filePreview.available ? 'button' : 'div'"
171
+ :type="preview && filePreview.available ? 'button' : void 0"
172
+ :class="[classes.frame, preview && filePreview.available ? 'cursor-zoom-in' : '']"
173
+ :style="{ aspectRatio: aspect }"
174
+ @click="openImages(0)"
175
+ >
176
+ <img
177
+ v-if="shown.url.value"
178
+ :src="shown.url.value"
179
+ :alt="alt ?? ''"
180
+ :class="classes.image"
181
+ loading="lazy"
182
+ />
183
+ <span v-else-if="shown.pending.value" class="nut-dash-shimmer absolute inset-0" />
184
+ </component>
185
+ <figcaption v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
186
+ <DisplayRender :content="caption" />
187
+ </figcaption>
188
+ </figure>
189
+ <figure
190
+ v-else-if="variant === 'gallery'"
191
+ :class="classes.root"
192
+ data-display="media"
193
+ data-variant="gallery"
194
+ >
195
+ <div :class="classes.grid" :style="{ aspectRatio: aspect }">
196
+ <button
197
+ v-for="(image, index) in tiles"
198
+ :key="index"
199
+ type="button"
200
+ :class="[
201
+ classes.tile,
202
+ preview && filePreview.available ? 'cursor-zoom-in' : 'cursor-default'
203
+ ]"
204
+ @click="openImages(index)"
205
+ >
206
+ <img :src="image.src" :alt="image.alt ?? ''" :class="classes.image" loading="lazy" />
207
+ <span v-if="hiddenCount > 0 && index === tiles.length - 1" :class="classes.overflow"
208
+ >+{{ hiddenCount }}</span
209
+ >
210
+ </button>
211
+ </div>
212
+ <figcaption v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
213
+ <DisplayRender :content="caption" />
214
+ </figcaption>
215
+ </figure>
216
+ <figure
217
+ v-else-if="variant === 'video'"
218
+ :class="classes.root"
219
+ data-display="media"
220
+ data-variant="video"
221
+ >
222
+ <div :class="[classes.frame, 'bg-black']" :style="{ aspectRatio: aspect }">
223
+ <video
224
+ v-if="playing || !thumbnail"
225
+ :src="shown.url.value"
226
+ :poster="thumbnail"
227
+ :autoplay="playing"
228
+ controls
229
+ preload="metadata"
230
+ :class="classes.image"
231
+ />
232
+ <button
233
+ v-else
234
+ type="button"
235
+ class="group absolute inset-0 grid cursor-pointer place-items-center"
236
+ :aria-label="t('display.play')"
237
+ @click="playing = true"
238
+ >
239
+ <img :src="thumbnail" alt="" :class="[classes.image, 'absolute inset-0']" />
240
+ <span class="absolute inset-0 bg-gradient-to-t from-black/55 to-transparent to-45%" />
241
+ <span :class="[classes.play, 'relative']"
242
+ ><UIcon name="i-lucide-play" class="size-5 translate-x-px"
243
+ /></span>
244
+ <span
245
+ v-if="!isDisplayContentEmpty(title)"
246
+ v-reveal-truncated
247
+ :class="[classes.title, 'absolute start-3 bottom-2 text-white']"
248
+ >
249
+ <DisplayRender :content="title" />
250
+ </span>
251
+ <span v-if="duration !== void 0" :class="classes.duration">{{
252
+ resolveDisplayText(duration)
253
+ }}</span>
254
+ </button>
255
+ </div>
256
+ <figcaption v-if="!isDisplayContentEmpty(caption)" v-reveal-truncated :class="classes.caption">
257
+ <DisplayRender :content="caption" />
258
+ </figcaption>
259
+ </figure>
260
+ <div
261
+ v-else-if="variant === 'document'"
262
+ :class="classes.root"
263
+ data-display="media"
264
+ data-variant="document"
265
+ >
266
+ <img
267
+ v-if="thumbnail"
268
+ :src="thumbnail"
269
+ alt=""
270
+ :class="[classes.page, 'p-0 object-cover object-top']"
271
+ />
272
+ <div v-else :class="classes.page" aria-hidden="true">
273
+ <i class="mb-0.5 h-1 w-3/5 rounded-full bg-[var(--nut-display-primary)]" />
274
+ <i
275
+ v-for="width in [100, 92, 100, 78, 96, 60]"
276
+ :key="width"
277
+ class="h-0.5 rounded-full bg-[#e5e1db]"
278
+ :style="{ width: `${width}%` }"
279
+ />
280
+ </div>
281
+ <div :class="classes.body">
282
+ <div v-reveal-truncated :class="classes.name">{{ fileName }}</div>
283
+ <div v-if="documentMeta" v-reveal-truncated :class="classes.meta">{{ documentMeta }}</div>
284
+ <div
285
+ v-if="!isDisplayContentEmpty(description)"
286
+ v-reveal-truncated
287
+ :class="classes.description"
288
+ >
289
+ <DisplayRender :content="description" />
290
+ </div>
291
+ <DisplayActions
292
+ v-if="documentActions.length"
293
+ :actions="documentActions"
294
+ size="xs"
295
+ :class="classes.actions"
296
+ />
297
+ </div>
298
+ </div>
299
+ <component
300
+ :is="isString(href) ? ULink : 'button'"
301
+ v-else
302
+ v-bind="isString(href) ? { target: '_blank', to: href } : { type: 'button' }"
303
+ :class="[classes.root, isString(href) ? '' : 'w-full cursor-pointer text-start']"
304
+ :aria-busy="links.pending.value === 'open' || void 0"
305
+ data-display="media"
306
+ data-variant="link"
307
+ @click="isString(href) ? void 0 : openLink()"
308
+ >
309
+ <div :class="[classes.body, 'px-3 py-2.5']">
310
+ <div v-if="domain" v-reveal-truncated :class="classes.domain">
311
+ <UIcon name="i-lucide-globe" class="size-3.5 shrink-0" />{{ domain }}
312
+ </div>
313
+ <div v-reveal-truncated :class="[classes.title, 'mt-0.5 flex items-center gap-1.5']">
314
+ <DisplayRender :content="title ?? (isString(href) ? href : void 0)" />
315
+ <UIcon
316
+ v-if="links.pending.value === 'open'"
317
+ name="i-lucide-loader-circle"
318
+ class="size-3.5 shrink-0 animate-spin text-muted"
319
+ />
320
+ </div>
321
+ <div
322
+ v-if="!isDisplayContentEmpty(description)"
323
+ v-reveal-truncated
324
+ :class="classes.description"
325
+ >
326
+ <DisplayRender :content="description" />
327
+ </div>
328
+ </div>
329
+ <img v-if="thumbnail" :src="thumbnail" alt="" :class="classes.thumbnail" loading="lazy" />
330
+ </component>
331
+ <DisplayLoadError
332
+ v-if="!loading && (links.failed.value || shown.failed.value)"
333
+ class="mt-1.5"
334
+ @retry="links.failed.value ? links.failed.value.retry() : shown.retry()"
335
+ />
336
+ </template>
@@ -0,0 +1,4 @@
1
+ import type { DisplayMediaProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<DisplayMediaProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<DisplayMediaProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
+ declare const _default: typeof __VLS_export;
4
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import type { DisplayMeterProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<DisplayMeterProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<DisplayMeterProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
+ declare const _default: typeof __VLS_export;
4
+ export default _default;