nuxt-ui-tools 1.7.0 → 1.8.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 (161) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +18 -0
  3. package/dist/runtime/file-preview/components/provider/file-preview-host.d.vue.ts +7 -0
  4. package/dist/runtime/file-preview/components/provider/file-preview-host.vue +147 -0
  5. package/dist/runtime/file-preview/components/provider/file-preview-host.vue.d.ts +7 -0
  6. package/dist/runtime/file-preview/components/provider/file-preview-provider.d.vue.ts +21 -0
  7. package/dist/runtime/file-preview/components/provider/file-preview-provider.vue +23 -0
  8. package/dist/runtime/file-preview/components/provider/file-preview-provider.vue.d.ts +21 -0
  9. package/dist/runtime/file-preview/components/renderers/audio-renderer.d.vue.ts +10 -0
  10. package/dist/runtime/file-preview/components/renderers/audio-renderer.vue +83 -0
  11. package/dist/runtime/file-preview/components/renderers/audio-renderer.vue.d.ts +10 -0
  12. package/dist/runtime/file-preview/components/renderers/csv-renderer.d.vue.ts +10 -0
  13. package/dist/runtime/file-preview/components/renderers/csv-renderer.vue +116 -0
  14. package/dist/runtime/file-preview/components/renderers/csv-renderer.vue.d.ts +10 -0
  15. package/dist/runtime/file-preview/components/renderers/fallback-renderer.d.vue.ts +10 -0
  16. package/dist/runtime/file-preview/components/renderers/fallback-renderer.vue +41 -0
  17. package/dist/runtime/file-preview/components/renderers/fallback-renderer.vue.d.ts +10 -0
  18. package/dist/runtime/file-preview/components/renderers/file-preview-code.d.vue.ts +9 -0
  19. package/dist/runtime/file-preview/components/renderers/file-preview-code.vue +83 -0
  20. package/dist/runtime/file-preview/components/renderers/file-preview-code.vue.d.ts +9 -0
  21. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.d.vue.ts +3 -0
  22. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.vue +14 -0
  23. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.vue.d.ts +3 -0
  24. package/dist/runtime/file-preview/components/renderers/image-renderer.d.vue.ts +10 -0
  25. package/dist/runtime/file-preview/components/renderers/image-renderer.vue +182 -0
  26. package/dist/runtime/file-preview/components/renderers/image-renderer.vue.d.ts +10 -0
  27. package/dist/runtime/file-preview/components/renderers/markdown-renderer.d.vue.ts +10 -0
  28. package/dist/runtime/file-preview/components/renderers/markdown-renderer.vue +78 -0
  29. package/dist/runtime/file-preview/components/renderers/markdown-renderer.vue.d.ts +10 -0
  30. package/dist/runtime/file-preview/components/renderers/media-controls.d.vue.ts +10 -0
  31. package/dist/runtime/file-preview/components/renderers/media-controls.vue +145 -0
  32. package/dist/runtime/file-preview/components/renderers/media-controls.vue.d.ts +10 -0
  33. package/dist/runtime/file-preview/components/renderers/pdf-renderer.d.vue.ts +10 -0
  34. package/dist/runtime/file-preview/components/renderers/pdf-renderer.vue +58 -0
  35. package/dist/runtime/file-preview/components/renderers/pdf-renderer.vue.d.ts +10 -0
  36. package/dist/runtime/file-preview/components/renderers/text-renderer.d.vue.ts +10 -0
  37. package/dist/runtime/file-preview/components/renderers/text-renderer.vue +41 -0
  38. package/dist/runtime/file-preview/components/renderers/text-renderer.vue.d.ts +10 -0
  39. package/dist/runtime/file-preview/components/renderers/video-renderer.d.vue.ts +10 -0
  40. package/dist/runtime/file-preview/components/renderers/video-renderer.vue +110 -0
  41. package/dist/runtime/file-preview/components/renderers/video-renderer.vue.d.ts +10 -0
  42. package/dist/runtime/file-preview/components/shell/file-preview-button.d.vue.ts +16 -0
  43. package/dist/runtime/file-preview/components/shell/file-preview-button.vue +40 -0
  44. package/dist/runtime/file-preview/components/shell/file-preview-button.vue.d.ts +16 -0
  45. package/dist/runtime/{form/components/renderer/form-field-error.d.vue.ts → file-preview/components/shell/file-preview-details.d.vue.ts} +4 -2
  46. package/dist/runtime/file-preview/components/shell/file-preview-details.vue +25 -0
  47. package/dist/runtime/{form/components/renderer/form-field-error.vue.d.ts → file-preview/components/shell/file-preview-details.vue.d.ts} +4 -2
  48. package/dist/runtime/file-preview/components/shell/file-preview-header.d.vue.ts +44 -0
  49. package/dist/runtime/file-preview/components/shell/file-preview-header.vue +163 -0
  50. package/dist/runtime/file-preview/components/shell/file-preview-header.vue.d.ts +44 -0
  51. package/dist/runtime/file-preview/components/shell/file-preview-message.d.vue.ts +21 -0
  52. package/dist/runtime/file-preview/components/shell/file-preview-message.vue +49 -0
  53. package/dist/runtime/file-preview/components/shell/file-preview-message.vue.d.ts +21 -0
  54. package/dist/runtime/file-preview/components/shell/file-preview-shell.d.vue.ts +17 -0
  55. package/dist/runtime/file-preview/components/shell/file-preview-shell.vue +280 -0
  56. package/dist/runtime/file-preview/components/shell/file-preview-shell.vue.d.ts +17 -0
  57. package/dist/runtime/file-preview/components/shell/file-preview-stage.d.vue.ts +37 -0
  58. package/dist/runtime/file-preview/components/shell/file-preview-stage.vue +173 -0
  59. package/dist/runtime/file-preview/components/shell/file-preview-stage.vue.d.ts +37 -0
  60. package/dist/runtime/file-preview/components/shell/file-preview-thumb.d.vue.ts +14 -0
  61. package/dist/runtime/file-preview/components/shell/file-preview-thumb.vue +69 -0
  62. package/dist/runtime/file-preview/components/shell/file-preview-thumb.vue.d.ts +14 -0
  63. package/dist/runtime/file-preview/components/shell/file-preview-tools.d.vue.ts +13 -0
  64. package/dist/runtime/file-preview/components/shell/file-preview-tools.vue +10 -0
  65. package/dist/runtime/file-preview/components/shell/file-preview-tools.vue.d.ts +13 -0
  66. package/dist/runtime/file-preview/composables/use-file-preview-api.d.ts +21 -0
  67. package/dist/runtime/file-preview/composables/use-file-preview-api.js +83 -0
  68. package/dist/runtime/file-preview/composables/use-file-preview-shell.d.ts +13 -0
  69. package/dist/runtime/file-preview/composables/use-file-preview-shell.js +30 -0
  70. package/dist/runtime/file-preview/composables/use-file-preview-source.d.ts +17 -0
  71. package/dist/runtime/file-preview/composables/use-file-preview-source.js +51 -0
  72. package/dist/runtime/file-preview/composables/use-file-preview-text.d.ts +14 -0
  73. package/dist/runtime/file-preview/composables/use-file-preview-text.js +79 -0
  74. package/dist/runtime/file-preview/index.d.ts +5 -0
  75. package/dist/runtime/file-preview/index.js +14 -0
  76. package/dist/runtime/file-preview/plugins/file-preview-api.d.ts +11 -0
  77. package/dist/runtime/file-preview/plugins/file-preview-api.js +10 -0
  78. package/dist/runtime/file-preview/types/api.d.ts +74 -0
  79. package/dist/runtime/file-preview/types/api.js +0 -0
  80. package/dist/runtime/file-preview/types/file.d.ts +77 -0
  81. package/dist/runtime/file-preview/types/file.js +0 -0
  82. package/dist/runtime/file-preview/types/index.d.ts +4 -0
  83. package/dist/runtime/file-preview/types/index.js +0 -0
  84. package/dist/runtime/file-preview/types/renderer.d.ts +49 -0
  85. package/dist/runtime/file-preview/types/renderer.js +0 -0
  86. package/dist/runtime/file-preview/types/shell.d.ts +12 -0
  87. package/dist/runtime/file-preview/types/shell.js +0 -0
  88. package/dist/runtime/file-preview/utils/container.d.ts +4 -0
  89. package/dist/runtime/file-preview/utils/container.js +10 -0
  90. package/dist/runtime/file-preview/utils/csv.d.ts +17 -0
  91. package/dist/runtime/file-preview/utils/csv.js +53 -0
  92. package/dist/runtime/file-preview/utils/download.d.ts +8 -0
  93. package/dist/runtime/file-preview/utils/download.js +40 -0
  94. package/dist/runtime/file-preview/utils/files.d.ts +7 -0
  95. package/dist/runtime/file-preview/utils/files.js +53 -0
  96. package/dist/runtime/file-preview/utils/format.d.ts +13 -0
  97. package/dist/runtime/file-preview/utils/format.js +40 -0
  98. package/dist/runtime/file-preview/utils/instance.d.ts +9 -0
  99. package/dist/runtime/file-preview/utils/instance.js +57 -0
  100. package/dist/runtime/file-preview/utils/renderers.d.ts +36 -0
  101. package/dist/runtime/file-preview/utils/renderers.js +247 -0
  102. package/dist/runtime/form/components/page/form-page-header.d.vue.ts +3 -1
  103. package/dist/runtime/form/components/page/form-page-header.vue +4 -4
  104. package/dist/runtime/form/components/page/form-page-header.vue.d.ts +3 -1
  105. package/dist/runtime/form/components/page/form-page-navigation.vue +2 -1
  106. package/dist/runtime/form/components/page/form-page-section.vue +15 -7
  107. package/dist/runtime/form/components/page/form-page.vue +0 -1
  108. package/dist/runtime/form/components/utils/form-file-preview.vue +5 -38
  109. package/dist/runtime/form/composables/use-field-control.d.ts +1 -1
  110. package/dist/runtime/form/composables/use-form-page.js +3 -1
  111. package/dist/runtime/form/composables/use-form-runtime.js +3 -0
  112. package/dist/runtime/form/composables/use-form-upload-registry.d.ts +2 -0
  113. package/dist/runtime/form/composables/use-form-upload-registry.js +11 -6
  114. package/dist/runtime/form/composables/use-form-validation.js +5 -1
  115. package/dist/runtime/form/fields/array-list/use-array-items.d.ts +1 -1
  116. package/dist/runtime/form/fields/array-table/array-table-cell.d.vue.ts +15 -0
  117. package/dist/runtime/form/fields/array-table/array-table-cell.vue +58 -0
  118. package/dist/runtime/form/fields/array-table/array-table-cell.vue.d.ts +15 -0
  119. package/dist/runtime/form/fields/array-table/component.vue +42 -26
  120. package/dist/runtime/form/fields/date/types.d.ts +6 -1
  121. package/dist/runtime/form/fields/date-family/component.vue +7 -1
  122. package/dist/runtime/form/fields/date-family/utils.d.ts +8 -1
  123. package/dist/runtime/form/fields/date-family/utils.js +10 -2
  124. package/dist/runtime/form/fields/hidden/types.d.ts +8 -1
  125. package/dist/runtime/form/fields/upload/component.vue +55 -176
  126. package/dist/runtime/form/fields/upload/types.d.ts +68 -3
  127. package/dist/runtime/form/fields/upload/upload-item.d.vue.ts +24 -0
  128. package/dist/runtime/form/fields/upload/upload-item.vue +158 -0
  129. package/dist/runtime/form/fields/upload/upload-item.vue.d.ts +24 -0
  130. package/dist/runtime/form/fields/upload/use-upload-items.d.ts +44 -0
  131. package/dist/runtime/form/fields/upload/use-upload-items.js +317 -0
  132. package/dist/runtime/form/types/callbacks.d.ts +8 -2
  133. package/dist/runtime/form/types/controller.d.ts +6 -3
  134. package/dist/runtime/form/types/field-base.d.ts +5 -5
  135. package/dist/runtime/form/types/field-output-utils.d.ts +12 -1
  136. package/dist/runtime/form/types/field-output.d.ts +1 -1
  137. package/dist/runtime/form/types/field.d.ts +3 -2
  138. package/dist/runtime/form/types/form-api.d.ts +4 -2
  139. package/dist/runtime/form/types/options-runtime.d.ts +8 -4
  140. package/dist/runtime/form/types/options.d.ts +11 -11
  141. package/dist/runtime/form/types/output.d.ts +33 -10
  142. package/dist/runtime/form/types/page.d.ts +7 -3
  143. package/dist/runtime/form/types/transform.d.ts +5 -2
  144. package/dist/runtime/form/types/ui.d.ts +7 -3
  145. package/dist/runtime/form/types/upload-runtime.d.ts +4 -0
  146. package/dist/runtime/form/utils/page.js +24 -5
  147. package/dist/runtime/form/utils/state.js +14 -11
  148. package/dist/runtime/i18n/locales/en.js +104 -1
  149. package/dist/runtime/i18n/locales/fr.js +104 -1
  150. package/dist/runtime/i18n/types.d.ts +121 -0
  151. package/dist/runtime/query-state/composables.d.ts +2 -2
  152. package/dist/runtime/shared/styles/tokens.css +1 -1
  153. package/dist/runtime/shared/types/utils.d.ts +12 -4
  154. package/dist/runtime/shared/utils/file.d.ts +11 -0
  155. package/dist/runtime/shared/utils/file.js +50 -0
  156. package/dist/runtime/table/components/table/table-renderer.vue +23 -3
  157. package/dist/runtime/table/components/utils/search-query-input.d.vue.ts +5 -1
  158. package/dist/runtime/table/components/utils/search-query-input.vue +17 -3
  159. package/dist/runtime/table/components/utils/search-query-input.vue.d.ts +5 -1
  160. package/package.json +56 -29
  161. package/dist/runtime/form/components/renderer/form-field-error.vue +0 -16
@@ -0,0 +1,158 @@
1
+ <script setup>
2
+ import UButton from "@nuxt/ui/components/Button.vue";
3
+ import UIcon from "@nuxt/ui/components/Icon.vue";
4
+ import UProgress from "@nuxt/ui/components/Progress.vue";
5
+ import { computed } from "vue";
6
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
7
+ import { fileExtensionLabel, fileIconName, formatFileSize } from "../../../shared/utils/file";
8
+ const props = defineProps({
9
+ item: { type: Object, required: true },
10
+ disabled: { type: Boolean, required: false },
11
+ compact: { type: Boolean, required: false },
12
+ replaceable: { type: Boolean, required: false }
13
+ });
14
+ const emit = defineEmits(["open", "start", "cancel", "retry", "remove", "replace"]);
15
+ const { code, t } = useUiToolsLocale();
16
+ const icon = computed(() => fileIconName(props.item.type));
17
+ const meta = computed(() => {
18
+ if (props.item.description) return props.item.description;
19
+ const extension = fileExtensionLabel({ name: props.item.name, type: props.item.type });
20
+ return props.item.size === null ? extension : `${extension} \xB7 ${formatFileSize(props.item.size, code.value)}`;
21
+ });
22
+ const status = computed(() => {
23
+ if (props.item.status === "uploading")
24
+ return props.item.progress === null ? t("form.fields.upload.uploading") : `${t("form.fields.upload.uploading")} ${Math.round(props.item.progress)} %`;
25
+ if (props.item.status === "queued") return t("form.fields.upload.queued");
26
+ return props.item.status === "failed" ? props.item.error : null;
27
+ });
28
+ </script>
29
+
30
+ <template>
31
+ <li
32
+ class="flex w-full min-w-0 items-center rounded-lg border bg-default"
33
+ :class="[
34
+ compact ? 'gap-2 px-2 py-1.5' : 'gap-3 px-3 py-2',
35
+ item.status === 'failed' ? 'border-error/40' : 'border-default'
36
+ ]"
37
+ :data-form-upload-item="item.status"
38
+ >
39
+ <img
40
+ v-if="item.thumbnail"
41
+ :src="item.thumbnail"
42
+ :alt="item.name"
43
+ class="shrink-0 rounded-md border border-default bg-white object-contain p-0.5"
44
+ :class="compact ? 'size-7' : 'size-10'"
45
+ />
46
+ <span
47
+ v-else
48
+ class="grid shrink-0 place-items-center rounded-md border border-default bg-elevated text-muted"
49
+ :class="compact ? 'size-7' : 'size-10'"
50
+ >
51
+ <UIcon :name="icon" :class="compact ? 'size-4' : 'size-5'" aria-hidden="true" />
52
+ </span>
53
+
54
+ <span class="min-w-0 flex-1" :aria-busy="item.resolving || void 0">
55
+ <template v-if="item.resolving">
56
+ <span class="block h-3.5 w-2/3 animate-pulse rounded bg-elevated" />
57
+ <span v-if="!compact" class="mt-1.5 block h-3 w-1/3 animate-pulse rounded bg-elevated" />
58
+ </template>
59
+ <template v-else>
60
+ <button
61
+ v-if="item.openable"
62
+ type="button"
63
+ class="block max-w-full truncate text-left text-sm font-medium text-highlighted underline-offset-2 outline-none hover:underline focus-visible:underline"
64
+ :title="t('form.fields.upload.open')"
65
+ data-form-upload-open=""
66
+ @click="emit('open')"
67
+ >
68
+ {{ item.name }}
69
+ </button>
70
+ <span
71
+ v-else
72
+ class="block truncate text-sm font-medium text-highlighted"
73
+ data-form-upload-name=""
74
+ >
75
+ {{ item.name }}
76
+ </span>
77
+ <span
78
+ v-if="!compact || status"
79
+ class="block truncate text-xs"
80
+ :class="item.status === 'failed' ? 'text-error' : 'text-muted'"
81
+ :role="item.status === 'failed' ? 'alert' : void 0"
82
+ data-form-upload-meta=""
83
+ >
84
+ {{ status ?? meta }}
85
+ </span>
86
+ </template>
87
+ <UProgress
88
+ v-if="item.status === 'uploading'"
89
+ :model-value="item.progress"
90
+ size="xs"
91
+ class="mt-1.5"
92
+ data-form-upload-progress=""
93
+ />
94
+ </span>
95
+
96
+ <span class="flex shrink-0 items-center gap-0.5">
97
+ <UButton
98
+ v-if="item.status === 'queued'"
99
+ size="xs"
100
+ color="neutral"
101
+ variant="ghost"
102
+ icon="i-lucide-upload"
103
+ :disabled="disabled"
104
+ :aria-label="t('form.fields.upload.upload')"
105
+ :title="t('form.fields.upload.upload')"
106
+ data-form-upload-start=""
107
+ @click="emit('start')"
108
+ />
109
+ <UButton
110
+ v-if="item.status === 'failed'"
111
+ size="xs"
112
+ color="neutral"
113
+ variant="ghost"
114
+ icon="i-lucide-rotate-cw"
115
+ :disabled="disabled"
116
+ :aria-label="t('form.fields.upload.retry')"
117
+ :title="t('form.fields.upload.retry')"
118
+ data-form-upload-retry=""
119
+ @click="emit('retry')"
120
+ />
121
+ <UButton
122
+ v-if="item.status === 'stored' && replaceable"
123
+ size="xs"
124
+ color="neutral"
125
+ variant="ghost"
126
+ icon="i-lucide-refresh-cw"
127
+ :disabled="disabled"
128
+ :aria-label="t('form.fields.upload.replace')"
129
+ :title="t('form.fields.upload.replace')"
130
+ data-form-upload-replace=""
131
+ @click="emit('replace')"
132
+ />
133
+ <UButton
134
+ v-if="item.status === 'uploading'"
135
+ size="xs"
136
+ color="neutral"
137
+ variant="ghost"
138
+ icon="i-lucide-x"
139
+ :aria-label="t('form.fields.upload.cancel')"
140
+ :title="t('form.fields.upload.cancel')"
141
+ data-form-upload-cancel=""
142
+ @click="emit('cancel')"
143
+ />
144
+ <UButton
145
+ v-else
146
+ size="xs"
147
+ color="neutral"
148
+ variant="ghost"
149
+ icon="i-lucide-trash-2"
150
+ :disabled="disabled"
151
+ :aria-label="t('form.fields.upload.remove')"
152
+ :title="t('form.fields.upload.remove')"
153
+ data-form-upload-remove=""
154
+ @click="emit('remove')"
155
+ />
156
+ </span>
157
+ </li>
158
+ </template>
@@ -0,0 +1,24 @@
1
+ import type { FormUploadItem } from './use-upload-items.js';
2
+ type __VLS_Props = {
3
+ item: FormUploadItem;
4
+ disabled?: boolean;
5
+ compact?: boolean;
6
+ replaceable?: boolean;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ replace: () => any;
10
+ retry: () => any;
11
+ start: () => any;
12
+ open: () => any;
13
+ remove: () => any;
14
+ cancel: () => any;
15
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ onReplace?: (() => any) | undefined;
17
+ onRetry?: (() => any) | undefined;
18
+ onStart?: (() => any) | undefined;
19
+ onOpen?: (() => any) | undefined;
20
+ onRemove?: (() => any) | undefined;
21
+ onCancel?: (() => any) | undefined;
22
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -0,0 +1,44 @@
1
+ import type { FormValue } from '../../types/index.js';
2
+ import type { FormUploadField, FormUploadResolvedFile, FormUploadValue } from './types.js';
3
+ export type FormUploadItemStatus = 'stored' | 'queued' | 'uploading' | 'failed';
4
+ export interface FormUploadItem {
5
+ key: string;
6
+ status: FormUploadItemStatus;
7
+ name: string;
8
+ description: string | null;
9
+ size: number | null;
10
+ type: string | null;
11
+ thumbnail: string | null;
12
+ openable: boolean;
13
+ resolving: boolean;
14
+ progress: number | null;
15
+ error: string | null;
16
+ }
17
+ export declare function isUploadValue(value: FormValue): value is FormUploadValue;
18
+ export declare function defaultUploadResolve(value: FormUploadValue): FormUploadResolvedFile;
19
+ /**
20
+ * Owns the upload lifecycle of one upload field: stored values resolved for display, files that
21
+ * are queued, uploading, or failed, and the form value written when an upload succeeds.
22
+ */
23
+ export declare function useUploadItems(field: () => FormUploadField, path: () => readonly string[]): {
24
+ busy: import("vue").ComputedRef<boolean>;
25
+ canSelect: import("vue").ComputedRef<boolean>;
26
+ cancel: (key: string, options?: {
27
+ keepPreview?: boolean;
28
+ }) => void;
29
+ items: import("vue").ComputedRef<readonly FormUploadItem[]>;
30
+ multiple: import("vue").ComputedRef<boolean>;
31
+ open: (key: string) => Promise<void>;
32
+ remove: (key: string) => Promise<void>;
33
+ retry: (key: string) => Promise<void>;
34
+ runtime: {
35
+ cancel: () => Promise<void>;
36
+ pending: () => boolean;
37
+ remove: (value?: FormValue) => Promise<void>;
38
+ retry: () => Promise<void>;
39
+ settle: () => Promise<void>;
40
+ start: () => Promise<void>;
41
+ };
42
+ select: (files: readonly File[]) => void;
43
+ start: (key: string) => Promise<void>;
44
+ };
@@ -0,0 +1,317 @@
1
+ import { computed, inject, onScopeDispose, shallowRef, toRaw, watch } from "vue";
2
+ import { filePreviewApiKey } from "../../../file-preview/composables/use-file-preview-api.js";
3
+ import { useUiToolsLocale } from "../../../i18n/use-locale.js";
4
+ import { fileNameFromUrl, isImageType } from "../../../shared/utils/file.js";
5
+ import { useResolvedFieldProps } from "../../composables/use-field-control.js";
6
+ import { useFormRuntimeContext } from "../../composables/use-form-runtime.js";
7
+ import { isNumber, isObject, isString } from "../../utils/predicate.js";
8
+ export function isUploadValue(value) {
9
+ return isString(value) || isObject(value) && value !== null && !Array.isArray(value);
10
+ }
11
+ export function defaultUploadResolve(value) {
12
+ if (isString(value)) return { name: fileNameFromUrl(value), url: value };
13
+ const url = readString(value, "url");
14
+ return {
15
+ name: readString(value, "name") ?? (url ? fileNameFromUrl(url) : "\u2014"),
16
+ size: readNumber(value, "size"),
17
+ thumbnail: readString(value, "thumbnail"),
18
+ type: readString(value, "type"),
19
+ url
20
+ };
21
+ }
22
+ function readString(value, key) {
23
+ const entry = value[key];
24
+ return isString(entry) && entry ? entry : null;
25
+ }
26
+ function readNumber(value, key) {
27
+ const entry = value[key];
28
+ return isNumber(entry) ? entry : null;
29
+ }
30
+ function clampProgress(percent) {
31
+ return Math.min(100, Math.max(0, percent));
32
+ }
33
+ function localItem(item) {
34
+ return {
35
+ description: null,
36
+ error: item.error,
37
+ key: item.key,
38
+ name: item.file.name,
39
+ openable: false,
40
+ progress: item.progress,
41
+ resolving: false,
42
+ size: item.file.size,
43
+ status: item.status,
44
+ thumbnail: item.thumbnail,
45
+ type: item.file.type || null
46
+ };
47
+ }
48
+ export function useUploadItems(field, path) {
49
+ const form = useFormRuntimeContext();
50
+ const fieldProps = useResolvedFieldProps(field, path);
51
+ const { t } = useUiToolsLocale();
52
+ const filePreview = inject(filePreviewApiKey, null);
53
+ const local = shallowRef([]);
54
+ const resolved = shallowRef(/* @__PURE__ */ new Map());
55
+ const tasks = /* @__PURE__ */ new Map();
56
+ const objectKeys = /* @__PURE__ */ new WeakMap();
57
+ const uploaded = /* @__PURE__ */ new Map();
58
+ let sequence = 0;
59
+ const multiple = computed(() => fieldProps.value.multiple === true);
60
+ const autoUpload = computed(() => fieldProps.value.autoUpload !== false);
61
+ const preview = computed(() => fieldProps.value.preview !== false);
62
+ const stored = computed(() => {
63
+ const value = form.getValue(path());
64
+ if (Array.isArray(value)) return value.filter(isUploadValue);
65
+ return isUploadValue(value) ? [value] : [];
66
+ });
67
+ const capacity = computed(() => {
68
+ if (!multiple.value) return 1;
69
+ const max = fieldProps.value.max;
70
+ return isNumber(max) ? Math.max(0, max - stored.value.length - local.value.length) : Infinity;
71
+ });
72
+ const items = computed(() => [
73
+ ...multiple.value || local.value.length === 0 ? stored.value.map(storedItem) : [],
74
+ ...local.value.map(localItem)
75
+ ]);
76
+ const canSelect = computed(
77
+ () => multiple.value ? capacity.value > 0 : items.value.length === 0
78
+ );
79
+ const busy = computed(() => local.value.some((item) => item.status === "uploading"));
80
+ function params() {
81
+ return form.getFieldCallbackParams(path(), field());
82
+ }
83
+ function valueKey(value) {
84
+ if (isString(value)) return value;
85
+ const known = objectKeys.get(value);
86
+ if (known !== void 0) return `#${known}`;
87
+ sequence += 1;
88
+ objectKeys.set(value, sequence);
89
+ return `#${sequence}`;
90
+ }
91
+ function storedItem(value, index) {
92
+ const state = resolved.value.get(value);
93
+ const file = state?.file ?? defaultUploadResolve(value);
94
+ const session = uploaded.get(toRaw(value));
95
+ const type = file.type ?? session?.file.type ?? null;
96
+ const thumbnail = file.thumbnail ?? session?.thumbnail ?? (isImageType(type) ? file.url ?? null : null);
97
+ return {
98
+ description: file.description ?? null,
99
+ error: null,
100
+ key: `stored:${index}:${valueKey(value)}`,
101
+ name: file.name || session?.file.name || "",
102
+ openable: file.openable ?? Boolean(field().upload.open ?? file.url ?? (filePreview?.mounted.value ? session : null)),
103
+ progress: null,
104
+ resolving: !session && (!state || state.pending),
105
+ size: file.size ?? session?.file.size ?? null,
106
+ status: "stored",
107
+ thumbnail: preview.value ? thumbnail : null,
108
+ type
109
+ };
110
+ }
111
+ async function resolveValue(value) {
112
+ const hook = field().upload.resolve;
113
+ let file = defaultUploadResolve(value);
114
+ if (hook) {
115
+ try {
116
+ file = await hook({ ...params(), value }) ?? file;
117
+ } catch {
118
+ file = defaultUploadResolve(value);
119
+ }
120
+ }
121
+ if (resolved.value.has(value))
122
+ resolved.value = new Map(resolved.value).set(value, { file, pending: false });
123
+ }
124
+ watch(
125
+ stored,
126
+ (values) => {
127
+ const next = /* @__PURE__ */ new Map();
128
+ for (const value of values) {
129
+ const known = resolved.value.get(value);
130
+ if (known) {
131
+ next.set(value, known);
132
+ continue;
133
+ }
134
+ next.set(value, { file: defaultUploadResolve(value), pending: true });
135
+ }
136
+ resolved.value = next;
137
+ for (const value of values) if (resolved.value.get(value)?.pending) void resolveValue(value);
138
+ },
139
+ { immediate: true }
140
+ );
141
+ function patch(key, changes) {
142
+ local.value = local.value.map((item) => item.key === key ? { ...item, ...changes } : item);
143
+ }
144
+ function drop(key, options = {}) {
145
+ const item = local.value.find((entry) => entry.key === key);
146
+ if (!item) return;
147
+ item.controller?.abort();
148
+ if (item.thumbnail && !options.keepPreview) URL.revokeObjectURL(item.thumbnail);
149
+ local.value = local.value.filter((entry) => entry.key !== key);
150
+ }
151
+ function forget(value) {
152
+ const session = uploaded.get(toRaw(value));
153
+ if (session?.thumbnail) URL.revokeObjectURL(session.thumbnail);
154
+ uploaded.delete(toRaw(value));
155
+ }
156
+ function select(files) {
157
+ const accepted = files.slice(0, capacity.value);
158
+ if (accepted.length === 0) return;
159
+ if (!multiple.value) for (const item of local.value) drop(item.key);
160
+ const added = accepted.map((file) => {
161
+ sequence += 1;
162
+ return {
163
+ controller: null,
164
+ error: null,
165
+ file,
166
+ key: `local:${sequence}`,
167
+ progress: null,
168
+ status: "queued",
169
+ thumbnail: preview.value && isImageType(file.type) ? URL.createObjectURL(file) : null
170
+ };
171
+ });
172
+ local.value = [...local.value, ...added];
173
+ if (autoUpload.value) for (const item of added) void run(item.key);
174
+ }
175
+ async function commit(key, result) {
176
+ const value = Array.isArray(result) ? result[0] ?? null : result;
177
+ if (value === null || !isUploadValue(value)) {
178
+ patch(key, { controller: null, error: t("form.fields.upload.failed"), status: "failed" });
179
+ return;
180
+ }
181
+ const replaced = multiple.value ? [] : stored.value;
182
+ const item = local.value.find((entry) => entry.key === key);
183
+ if (item) uploaded.set(toRaw(value), { file: item.file, thumbnail: item.thumbnail });
184
+ form.setValue(path(), multiple.value ? [...stored.value, value] : value);
185
+ drop(key, { keepPreview: true });
186
+ for (const previous of replaced) forget(previous);
187
+ for (const previous of replaced)
188
+ await field().upload.onDelete?.({ ...params(), value: previous });
189
+ }
190
+ async function upload(item, controller) {
191
+ try {
192
+ const result = await field().upload.handler({
193
+ ...params(),
194
+ files: [item.file],
195
+ onProgress: (percent) => {
196
+ if (!controller.signal.aborted) patch(item.key, { progress: clampProgress(percent) });
197
+ },
198
+ signal: controller.signal
199
+ });
200
+ if (!controller.signal.aborted) await commit(item.key, result);
201
+ } catch (error) {
202
+ if (controller.signal.aborted) return;
203
+ patch(item.key, {
204
+ controller: null,
205
+ error: error instanceof Error && error.message ? error.message : t("form.fields.upload.failed"),
206
+ progress: null,
207
+ status: "failed"
208
+ });
209
+ } finally {
210
+ tasks.delete(item.key);
211
+ }
212
+ }
213
+ function run(key) {
214
+ const item = local.value.find((entry) => entry.key === key);
215
+ if (!item || item.status === "uploading") return tasks.get(key) ?? Promise.resolve();
216
+ const controller = new AbortController();
217
+ patch(key, { controller, error: null, progress: null, status: "uploading" });
218
+ const task = upload(item, controller);
219
+ tasks.set(key, task);
220
+ return task;
221
+ }
222
+ function localKeys(status) {
223
+ return local.value.filter((item) => item.status === status).map((item) => item.key);
224
+ }
225
+ async function remove(key) {
226
+ if (key.startsWith("local:")) return drop(key);
227
+ const index = items.value.findIndex((item) => item.key === key);
228
+ const value = stored.value[index];
229
+ if (value === void 0) return;
230
+ await field().upload.onDelete?.({ ...params(), value });
231
+ form.setValue(
232
+ path(),
233
+ multiple.value ? stored.value.filter((_entry, position) => position !== index) : null
234
+ );
235
+ forget(value);
236
+ }
237
+ async function open(key) {
238
+ const index = items.value.findIndex((item) => item.key === key);
239
+ const value = stored.value[index];
240
+ if (value === void 0) return;
241
+ const file = resolved.value.get(value)?.file ?? defaultUploadResolve(value);
242
+ const hook = field().upload.open;
243
+ if (hook) return await hook({ ...params(), file, value });
244
+ if (filePreview?.mounted.value && openPreview(value)) return;
245
+ if (file.url) window.open(file.url, "_blank", "noopener");
246
+ }
247
+ function openPreview(value) {
248
+ const entries = stored.value.flatMap((entry) => {
249
+ const file = resolved.value.get(entry)?.file ?? defaultUploadResolve(entry);
250
+ const session = uploaded.get(toRaw(entry));
251
+ const src = session?.file ?? file.url;
252
+ if (!src) return [];
253
+ const described = {
254
+ mime: file.type ?? session?.file.type ?? void 0,
255
+ name: file.name || session?.file.name || void 0,
256
+ size: file.size ?? session?.file.size ?? void 0,
257
+ src,
258
+ thumbnail: file.thumbnail ?? session?.thumbnail ?? void 0
259
+ };
260
+ return [{ described, entry }];
261
+ });
262
+ const index = entries.findIndex((candidate) => candidate.entry === value);
263
+ if (index === -1 || !filePreview) return false;
264
+ filePreview.open(
265
+ entries.map((candidate) => candidate.described),
266
+ { index }
267
+ );
268
+ return true;
269
+ }
270
+ async function removeValue(value) {
271
+ for (const item of local.value) drop(item.key);
272
+ if (value === void 0) {
273
+ for (const entry of stored.value)
274
+ await field().upload.onDelete?.({ ...params(), value: entry });
275
+ form.setValue(path(), multiple.value ? [] : null);
276
+ return;
277
+ }
278
+ await field().upload.onDelete?.({ ...params(), value });
279
+ form.setValue(path(), multiple.value ? stored.value.filter((entry) => entry !== value) : null);
280
+ }
281
+ const runtime = {
282
+ cancel: async () => {
283
+ for (const key of localKeys("uploading")) drop(key);
284
+ },
285
+ pending: () => local.value.length > 0,
286
+ remove: removeValue,
287
+ retry: async () => {
288
+ await Promise.all(localKeys("failed").map(run));
289
+ },
290
+ settle: async () => {
291
+ await Promise.allSettled(tasks.values());
292
+ },
293
+ start: async () => {
294
+ await Promise.all(localKeys("queued").map(run));
295
+ }
296
+ };
297
+ onScopeDispose(() => {
298
+ for (const item of local.value) {
299
+ item.controller?.abort();
300
+ if (item.thumbnail) URL.revokeObjectURL(item.thumbnail);
301
+ }
302
+ for (const value of uploaded.keys()) forget(value);
303
+ });
304
+ return {
305
+ busy,
306
+ canSelect,
307
+ cancel: drop,
308
+ items,
309
+ multiple,
310
+ open,
311
+ remove,
312
+ retry: run,
313
+ runtime,
314
+ select,
315
+ start: run
316
+ };
317
+ }
@@ -1,9 +1,15 @@
1
1
  import type { FormValue } from './/index.js';
2
2
  import type { FormFieldApi } from './api.js';
3
+ import type { FormObject } from './utils.js';
4
+ /**
5
+ * Dependency values a field callback reads: each declared dependency under its target name, typed
6
+ * `FormValue` until the callback narrows it, since a reusable field can be mounted anywhere.
7
+ */
8
+ export type FormDependencyValues = Readonly<FormObject>;
3
9
  /**
4
10
  * Parameters passed to field-level callbacks.
5
11
  */
6
- export interface FormFieldCallbackParams<TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue = FormValue, TOption = FormValue> {
12
+ export interface FormFieldCallbackParams<TContext = NonNullable<unknown>, TDeps = FormDependencyValues, TValue = FormValue, TOption = FormValue> {
7
13
  /** Fully typed form-scoped context declared on the schema. */
8
14
  ctx: TContext;
9
15
  /** Values read from the field's dependency list. */
@@ -14,4 +20,4 @@ export interface FormFieldCallbackParams<TContext = NonNullable<unknown>, TDeps
14
20
  /**
15
21
  * Function receiving field callback parameters.
16
22
  */
17
- export type FormFieldCallback<TResult, TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TValue = FormValue, TOption = FormValue> = (params: FormFieldCallbackParams<TContext, TDeps, TValue, TOption>) => TResult;
23
+ export type FormFieldCallback<TResult, TContext = NonNullable<unknown>, TDeps = FormDependencyValues, TValue = FormValue, TOption = FormValue> = (params: FormFieldCallbackParams<TContext, TDeps, TValue, TOption>) => TResult;
@@ -1,7 +1,7 @@
1
1
  import type { ComputedRef, MaybeRefOrGetter } from 'vue';
2
2
  import type { FormValue } from './/index.js';
3
3
  import type { FormSubmitAction, FormSubmitHandler, FormSubmitHandlerResult } from './api.js';
4
- import type { ExtractFormInternalValue, ExtractFormOutput } from './output.js';
4
+ import type { ExtractFormInput, ExtractFormInternalValue, ExtractFormOutput } from './output.js';
5
5
  import type { FormRuntime, FormRuntimeStep } from './runtime.js';
6
6
  import type { ExtractFormContext } from './schema.js';
7
7
  import type { FormObject } from './utils.js';
@@ -24,8 +24,11 @@ import type { FormValidationError, FormValidationMode, FormValidationOptions } f
24
24
  export interface UseFormParams<TSchema, TSubmitData = FormValue> {
25
25
  /** Schema owned by this form controller and passed to `<NutForm :form="form" />`. */
26
26
  schema: MaybeRefOrGetter<TSchema>;
27
- /** Optional initial internal state. Dotted field keys are still normalized by the runtime. */
28
- input?: MaybeRefOrGetter<FormObject | undefined>;
27
+ /**
28
+ * Optional initial value, typed from the schema: fields may be missing or `null`, and a field
29
+ * with `transform.input` takes what that hook accepts, such as a record loaded from the API.
30
+ */
31
+ input?: MaybeRefOrGetter<ExtractFormInput<TSchema> | undefined>;
29
32
  /** Keeps later input changes synchronized into the mounted runtime. */
30
33
  syncInput?: MaybeRefOrGetter<boolean | readonly string[]>;
31
34
  /** Selects required, custom-rule, all, or no validation. */
@@ -1,6 +1,6 @@
1
1
  import type { FormValue } from './/index.js';
2
2
  import type { FormFieldApi } from './api.js';
3
- import type { FormFieldCallback } from './callbacks.js';
3
+ import type { FormDependencyValues, FormFieldCallback } from './callbacks.js';
4
4
  import type { FormField } from './field.js';
5
5
  import type { FormContainerLayout, FormItemLayout } from './layout.js';
6
6
  import type { FormTransformConfig } from './transform.js';
@@ -19,11 +19,11 @@ export interface FormFieldDescription {
19
19
  /**
20
20
  * Common stateful field properties.
21
21
  */
22
- export type FormFieldProps<TProps = FormObject, TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>> = FormDynamic<TProps & FormObject, {
22
+ export type FormFieldProps<TProps = FormObject, TContext = NonNullable<unknown>, TDeps = FormDependencyValues> = FormDynamic<TProps & FormObject, {
23
23
  ctx: TContext;
24
24
  deps: TDeps;
25
25
  }>;
26
- export interface FormStatefulFieldBase<TType extends FormFieldType, TValue, TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TProps = FormObject> {
26
+ export interface FormStatefulFieldBase<TType extends FormFieldType, TValue, TContext = NonNullable<unknown>, TDeps = FormDependencyValues, TProps = FormObject> {
27
27
  /** Raw path used to read/write the field value in form state. */
28
28
  key: string;
29
29
  /** Discriminant used by the field registry. */
@@ -101,7 +101,7 @@ export interface FormStatefulFieldBase<TType extends FormFieldType, TValue, TCon
101
101
  /**
102
102
  * Base properties for stateless visual fields.
103
103
  */
104
- export interface FormStatelessFieldBase<TType extends FormFieldType, TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TProps = FormObject> {
104
+ export interface FormStatelessFieldBase<TType extends FormFieldType, TContext = NonNullable<unknown>, TDeps = FormDependencyValues, TProps = FormObject> {
105
105
  /** Stable key for renderer identity. Stateless fields do not write form state. */
106
106
  key: string;
107
107
  /** Discriminant used by the field registry. */
@@ -118,7 +118,7 @@ export interface FormStatelessFieldBase<TType extends FormFieldType, TContext =
118
118
  /**
119
119
  * Base properties for structural fields that render children.
120
120
  */
121
- export interface FormContainerFieldBase<TType extends FormFieldType, TContext = NonNullable<unknown>, TDeps = NonNullable<unknown>, TProps = FormObject> {
121
+ export interface FormContainerFieldBase<TType extends FormFieldType, TContext = NonNullable<unknown>, TDeps = FormDependencyValues, TProps = FormObject> {
122
122
  /** Stable renderer key. */
123
123
  key: string;
124
124
  /** Discriminant used by the field registry. */
@@ -1,6 +1,7 @@
1
+ import type { RemoteOptionsLoader } from '../../shared/types/remote-options.js';
1
2
  import type { DataTag, QueryKey } from './context.js';
2
3
  import type { FormOptionValue } from './options.js';
3
- export type FormStateMode = 'internal' | 'output';
4
+ export type FormStateMode = 'internal' | 'output' | 'input';
4
5
  export type NullableValue = null;
5
6
  export type FieldProps<TField> = TField extends {
6
7
  readonly props: infer TProps;
@@ -18,9 +19,13 @@ type QueryFunctionValue<TValue> = TValue extends {
18
19
  export type QueryOptionsValue<TValue> = [TValue] extends [{
19
20
  queryKey: infer TQueryKey;
20
21
  }] ? [QuerySelectedValue<TValue>] extends [never] ? TQueryKey extends DataTag<QueryKey, infer TResult, infer _TError> ? TResult : QueryFunctionValue<TValue> : QuerySelectedValue<TValue> : TValue;
22
+ /** Options a remote loader resolves, so a remote select is typed by the values it offers. */
23
+ type RemoteLoaderOptions<TLoader> = TLoader extends RemoteOptionsLoader<infer TOption> ? readonly TOption[] : never;
21
24
  export type OptionSource<TField> = TField extends {
22
25
  options: infer TOptions;
23
26
  } ? TOptions extends {
27
+ loader: infer TLoader;
28
+ } ? RemoteLoaderOptions<TLoader> : TOptions extends {
24
29
  source: infer TSource;
25
30
  } ? TSource : TOptions : never;
26
31
  export type OptionSourceValue<TSource> = TSource extends (...params: infer _TParams) => infer TResult ? QueryOptionsValue<AwaitedValue<TResult>> : QueryOptionsValue<AwaitedValue<TSource>>;
@@ -40,4 +45,10 @@ export type TransformOutputValue<TField, TFallback> = TField extends {
40
45
  output: infer TOutput;
41
46
  };
42
47
  } ? FallbackNever<AwaitedValue<FunctionReturn<TOutput>>, TFallback> : TFallback;
48
+ /** Value a field accepts as input: what its `transform.input` takes, or its own value. */
49
+ export type TransformInputValue<TField, TFallback> = TField extends {
50
+ transform: {
51
+ input: (value: infer TInput, ...params: infer _TParams) => unknown;
52
+ };
53
+ } ? FallbackNever<TInput, TFallback> : TFallback;
43
54
  export {};
@@ -85,7 +85,7 @@ type ResolveNullableFormFieldValue<TField> = TField extends {
85
85
  type: 'rating';
86
86
  } ? RatingFieldOutput : TField extends {
87
87
  type: 'date';
88
- } ? DateFieldOutput : TField extends {
88
+ } ? DateFieldOutput<TField> : TField extends {
89
89
  type: 'datetime';
90
90
  } ? DateTimeFieldOutput : TField extends {
91
91
  type: 'daterange';