@finema/core 3.1.0 → 3.2.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 (44) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +1 -1
  3. package/dist/runtime/components/Form/FieldWrapper.vue +13 -13
  4. package/dist/runtime/components/Form/Fields.vue +18 -14
  5. package/dist/runtime/components/Form/InputCheckbox/index.vue +18 -18
  6. package/dist/runtime/components/Form/InputCurrency/index.vue +49 -49
  7. package/dist/runtime/components/Form/InputNumber/index.vue +20 -20
  8. package/dist/runtime/components/Form/InputSelect/index.vue +38 -38
  9. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +43 -43
  10. package/dist/runtime/components/Form/InputTags/index.vue +23 -23
  11. package/dist/runtime/components/Form/InputTextarea/index.vue +18 -18
  12. package/dist/runtime/components/Form/InputTime/index.vue +38 -38
  13. package/dist/runtime/components/Form/InputToggle/index.vue +17 -17
  14. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +30 -30
  15. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +50 -50
  16. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.d.vue.ts +25 -0
  17. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +110 -0
  18. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue.d.ts +25 -0
  19. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/types.d.ts +27 -0
  20. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/types.js +0 -0
  21. package/dist/runtime/components/Form/fileState/EmptyState.vue +21 -21
  22. package/dist/runtime/components/Form/fileState/FailedState.vue +33 -33
  23. package/dist/runtime/components/Form/fileState/LoadingState.vue +24 -24
  24. package/dist/runtime/components/Form/fileState/MultipleFilesState.d.vue.ts +30 -0
  25. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue +172 -0
  26. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue.d.ts +30 -0
  27. package/dist/runtime/components/Form/fileState/PreviewModal.vue +23 -23
  28. package/dist/runtime/components/Form/fileState/useUploadStateMultiple.d.ts +126 -0
  29. package/dist/runtime/components/Form/fileState/useUploadStateMultiple.js +238 -0
  30. package/dist/runtime/components/Form/index.vue +5 -5
  31. package/dist/runtime/components/Form/types.d.ts +2 -1
  32. package/dist/runtime/components/Image.vue +28 -28
  33. package/dist/runtime/components/Log/index.vue +17 -17
  34. package/dist/runtime/components/Table/ColumnDate.vue +1 -1
  35. package/dist/runtime/components/Table/ColumnDateTime.vue +1 -1
  36. package/dist/runtime/components/Table/ColumnImage.vue +4 -4
  37. package/dist/runtime/components/Table/ColumnText.vue +1 -1
  38. package/dist/runtime/components/Table/Pagination.vue +46 -46
  39. package/dist/runtime/components/Table/Simple.vue +17 -17
  40. package/dist/runtime/helpers/apiPageHelper.js +4 -4
  41. package/dist/runtime/server/tsconfig.json +3 -3
  42. package/dist/runtime/theme/uploadFileDropzone.d.ts +23 -0
  43. package/dist/runtime/theme/uploadFileDropzone.js +31 -3
  44. package/package.json +1 -1
@@ -0,0 +1,172 @@
1
+ <template>
2
+ <div :class="theme.multipleFilesWrapper()">
3
+ <div
4
+ v-for="(item, index) in fileItems"
5
+ :key="index"
6
+ :class="theme.fileItemWrapper()"
7
+ >
8
+ <!-- Uploading State -->
9
+ <div
10
+ v-if="item.state === UploadState.UPLOADING"
11
+ :class="theme.uploadingItemWrapper()"
12
+ >
13
+ <div :class="theme.uploadingIconWrapper()">
14
+ <Icon
15
+ :name="icons.filePreviewIcon"
16
+ :class="theme.uploadingIconClass()"
17
+ />
18
+ </div>
19
+ <div :class="theme.uploadingTextWrapper()">
20
+ <h1 class="truncate font-bold">
21
+ {{ item.file.name }}
22
+ </h1>
23
+ <div class="flex items-center gap-2">
24
+ <div :class="theme.progressBarWrapper()">
25
+ <div
26
+ :class="theme.progressBarFill()"
27
+ :style="{ width: `${item.progress}%` }"
28
+ />
29
+ </div>
30
+ <p class="text-sm text-gray-400">
31
+ {{ item.progress }}%
32
+ </p>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Success State -->
38
+ <div
39
+ v-else-if="item.state === UploadState.SUCCESS && item.value"
40
+ :class="theme.successItemWrapper()"
41
+ >
42
+ <div
43
+ v-if="isImageFromPath(item.value.path)"
44
+ :class="theme.successImgWrapper()"
45
+ >
46
+ <img
47
+ :src="item.value.url"
48
+ :class="theme.successImgClass()"
49
+ alt="img-preview"
50
+ />
51
+ </div>
52
+ <div
53
+ v-else
54
+ :class="theme.successFileWrapper()"
55
+ >
56
+ <Icon
57
+ :name="icons.filePreviewIcon"
58
+ :class="theme.successFileClass()"
59
+ />
60
+ </div>
61
+ <div :class="theme.successTextWrapper()">
62
+ <div class="truncate">
63
+ <h1 class="truncate font-bold">
64
+ {{ item.value.name }}
65
+ </h1>
66
+ <p class="truncate text-sm font-light text-gray-400">
67
+ {{ getFileSizeFromValue(item.value) }}
68
+ </p>
69
+ </div>
70
+ <div :class="theme.actionWrapper()">
71
+ <a
72
+ v-if="isPDFFromPath(item.value.path)"
73
+ :href="item.value.url"
74
+ target="_blank"
75
+ class="flex"
76
+ >
77
+ <Icon
78
+ :name="icons.actionPreviewIcon"
79
+ :class="theme.actionIconClass()"
80
+ title="ดูตัวอย่าง"
81
+ />
82
+ </a>
83
+ <Icon
84
+ v-if="isImageFromPath(item.value.path) || isVideoFromPath(item.value.path)"
85
+ :name="icons.actionPreviewIcon"
86
+ :class="theme.actionIconClass()"
87
+ title="ดูตัวอย่าง"
88
+ @click="$emit('preview', item.value)"
89
+ />
90
+ <Icon
91
+ :name="icons.actionDownloadIcon"
92
+ :class="theme.actionIconClass()"
93
+ title="ดาวน์โหลดไฟล์"
94
+ @click="$emit('download', item.value)"
95
+ />
96
+ <Icon
97
+ v-if="!disabled && !readonly"
98
+ :name="icons.actionDeleteIcon"
99
+ :class="theme.actionIconClass()"
100
+ title="ลบไฟล์"
101
+ @click="$emit('delete', index)"
102
+ />
103
+ </div>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Error State -->
108
+ <div
109
+ v-else-if="item.state === UploadState.ERROR"
110
+ :class="theme.errorItemWrapper()"
111
+ >
112
+ <div :class="theme.errorIconWrapper()">
113
+ <Icon
114
+ :name="icons.errorIcon"
115
+ :class="theme.errorIconClass()"
116
+ />
117
+ </div>
118
+ <div :class="theme.errorTextWrapper()">
119
+ <h1 class="truncate font-bold">
120
+ {{ item.file.name }}
121
+ </h1>
122
+ <p class="text-error-500 text-sm">
123
+ {{ item.error || uploadFailedLabel }}
124
+ </p>
125
+ </div>
126
+ <div :class="theme.errorActionWrapper()">
127
+ <Button
128
+ variant="link"
129
+ :icon="icons.actionRetryIcon"
130
+ :class="theme.actionRetryBtnClass()"
131
+ color="primary"
132
+ @click="$emit('retry', index)"
133
+ >
134
+ {{ retryLabel }}
135
+ </Button>
136
+ <Icon
137
+ v-if="!disabled && !readonly"
138
+ :name="icons.actionDeleteIcon"
139
+ :class="theme.actionIconClass()"
140
+ title="ลบไฟล์"
141
+ @click="$emit('delete', index)"
142
+ />
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </template>
148
+
149
+ <script setup>
150
+ import {
151
+ isImageFromPath,
152
+ isPDFFromPath,
153
+ isVideoFromPath,
154
+ useFileSize
155
+ } from "#core/helpers/componentHelper";
156
+ import { useUiIconConfig } from "#core/composables/useConfig";
157
+ import { UploadState } from "./useUploadStateMultiple";
158
+ defineEmits(["preview", "download", "delete", "retry"]);
159
+ defineProps({
160
+ theme: { type: null, required: true },
161
+ fileItems: { type: Array, required: true },
162
+ disabled: { type: Boolean, required: false },
163
+ readonly: { type: Boolean, required: false },
164
+ uploadFailedLabel: { type: String, required: false },
165
+ retryLabel: { type: String, required: false }
166
+ });
167
+ const icons = useUiIconConfig("uploadFileDropzone");
168
+ const getFileSizeFromValue = (fileValue) => {
169
+ const allocate = useFileSize(fileValue.size || 0);
170
+ return allocate.isSelectedFileUseMb.value ? `${allocate.selectedFileSizeMb.value} MB` : `${allocate.selectedFileSizeKb.value} KB`;
171
+ };
172
+ </script>
@@ -0,0 +1,30 @@
1
+ import type { IFileValue } from '#core/components/Form/types';
2
+ import { UploadState } from './useUploadStateMultiple.js';
3
+ interface FileUploadItem {
4
+ file: File;
5
+ state: UploadState;
6
+ progress: number;
7
+ value?: IFileValue;
8
+ error?: string;
9
+ }
10
+ interface Props {
11
+ theme: any;
12
+ fileItems: FileUploadItem[];
13
+ disabled?: boolean;
14
+ readonly?: boolean;
15
+ uploadFailedLabel?: string;
16
+ retryLabel?: string;
17
+ }
18
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ delete: (index: number) => any;
20
+ preview: (value: IFileValue) => any;
21
+ download: (value: IFileValue) => any;
22
+ retry: (index: number) => any;
23
+ }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
24
+ onDelete?: ((index: number) => any) | undefined;
25
+ onPreview?: ((value: IFileValue) => any) | undefined;
26
+ onDownload?: ((value: IFileValue) => any) | undefined;
27
+ onRetry?: ((index: number) => any) | undefined;
28
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
29
+ declare const _default: typeof __VLS_export;
30
+ export default _default;
@@ -1,29 +1,29 @@
1
1
  <template>
2
- <Modal
3
- :close="{ onClick: () => emits('close', false) }"
4
- :dismissible="false"
5
- :title="value?.name"
2
+ <Modal
3
+ :close="{ onClick: () => emits('close', false) }"
4
+ :dismissible="false"
5
+ :title="value?.name"
6
6
  :ui="{
7
7
  content: 'max-w-3xl'
8
- }"
9
- >
10
- <template #body>
11
- <div class="flex justify-center">
12
- <img
13
- v-if="value && isImageFromPath(value.path)"
14
- :src="value.url"
15
- alt="img-preview"
16
- class="max-h-96 max-w-full rounded-lg"
17
- />
18
- <video
19
- v-else-if="value && isVideoFromPath(value.path)"
20
- :src="value.url"
21
- controls
22
- class="max-h-96 max-w-full"
23
- />
24
- </div>
25
- </template>
26
- </Modal>
8
+ }"
9
+ >
10
+ <template #body>
11
+ <div class="flex justify-center">
12
+ <img
13
+ v-if="value && isImageFromPath(value.path)"
14
+ :src="value.url"
15
+ alt="img-preview"
16
+ class="max-h-96 max-w-full rounded-lg"
17
+ />
18
+ <video
19
+ v-else-if="value && isVideoFromPath(value.path)"
20
+ :src="value.url"
21
+ controls
22
+ class="max-h-96 max-w-full"
23
+ />
24
+ </div>
25
+ </template>
26
+ </Modal>
27
27
  </template>
28
28
 
29
29
  <script setup>
@@ -0,0 +1,126 @@
1
+ import type { Ref, TemplateRef } from 'vue';
2
+ import type { IUploadDropzoneAutoMultipleProps } from '../InputUploadDropzoneAutoMultiple/types.js';
3
+ import type { IFileValue } from '#core/components/Form/types';
4
+ import { useUploadLoader } from '#core/composables/useUpload';
5
+ export declare enum UploadState {
6
+ EMPTY = "empty",
7
+ UPLOADING = "uploading",
8
+ SUCCESS = "success",
9
+ ERROR = "error"
10
+ }
11
+ export interface FileUploadItem {
12
+ file: File;
13
+ state: UploadState;
14
+ progress: number;
15
+ value?: IFileValue;
16
+ error?: string;
17
+ uploadLoader?: ReturnType<typeof useUploadLoader>;
18
+ percentRef?: Ref<number>;
19
+ }
20
+ export declare const useUploadStateMultiple: (props: IUploadDropzoneAutoMultipleProps, emits: any, onChange: (value: IFileValue[] | undefined) => void, setErrors: (error: string) => void, value: any, acceptedFileTypes: any, wrapperProps: any, dropzoneRef: TemplateRef<HTMLDivElement | undefined>) => {
21
+ fileItems: Ref<{
22
+ file: {
23
+ readonly lastModified: number;
24
+ readonly name: string;
25
+ readonly webkitRelativePath: string;
26
+ readonly size: number;
27
+ readonly type: string;
28
+ arrayBuffer: {
29
+ (): Promise<ArrayBuffer>;
30
+ (): Promise<ArrayBuffer>;
31
+ };
32
+ bytes: {
33
+ (): Promise<Uint8Array<ArrayBuffer>>;
34
+ (): Promise<Uint8Array<ArrayBuffer>>;
35
+ };
36
+ slice: {
37
+ (start?: number, end?: number, contentType?: string): Blob;
38
+ (start?: number, end?: number, contentType?: string): Blob;
39
+ };
40
+ stream: {
41
+ (): ReadableStream<Uint8Array<ArrayBuffer>>;
42
+ (): ReadableStream<Uint8Array<ArrayBuffer>>;
43
+ };
44
+ text: {
45
+ (): Promise<string>;
46
+ (): Promise<string>;
47
+ };
48
+ };
49
+ state: UploadState;
50
+ progress: number;
51
+ value?: {
52
+ url: string;
53
+ path?: string | undefined;
54
+ name?: string | undefined;
55
+ size?: number | undefined;
56
+ id?: string | undefined;
57
+ } | undefined;
58
+ error?: string | undefined;
59
+ uploadLoader?: {
60
+ status: import("#imports").IStatus;
61
+ data: any;
62
+ options: import("#imports").IAPIOptions;
63
+ run: (payload?: import("../../../helpers/apiObjectHelper.js").IObjectRunLoaderOptions<any, any> | undefined) => Promise<void>;
64
+ clear: () => void;
65
+ setLoading: () => void;
66
+ setData: (data: any) => void;
67
+ } | undefined;
68
+ percentRef?: number | undefined;
69
+ }[], FileUploadItem[] | {
70
+ file: {
71
+ readonly lastModified: number;
72
+ readonly name: string;
73
+ readonly webkitRelativePath: string;
74
+ readonly size: number;
75
+ readonly type: string;
76
+ arrayBuffer: {
77
+ (): Promise<ArrayBuffer>;
78
+ (): Promise<ArrayBuffer>;
79
+ };
80
+ bytes: {
81
+ (): Promise<Uint8Array<ArrayBuffer>>;
82
+ (): Promise<Uint8Array<ArrayBuffer>>;
83
+ };
84
+ slice: {
85
+ (start?: number, end?: number, contentType?: string): Blob;
86
+ (start?: number, end?: number, contentType?: string): Blob;
87
+ };
88
+ stream: {
89
+ (): ReadableStream<Uint8Array<ArrayBuffer>>;
90
+ (): ReadableStream<Uint8Array<ArrayBuffer>>;
91
+ };
92
+ text: {
93
+ (): Promise<string>;
94
+ (): Promise<string>;
95
+ };
96
+ };
97
+ state: UploadState;
98
+ progress: number;
99
+ value?: {
100
+ url: string;
101
+ path?: string | undefined;
102
+ name?: string | undefined;
103
+ size?: number | undefined;
104
+ id?: string | undefined;
105
+ } | undefined;
106
+ error?: string | undefined;
107
+ uploadLoader?: {
108
+ status: import("#imports").IStatus;
109
+ data: any;
110
+ options: import("#imports").IAPIOptions;
111
+ run: (payload?: import("../../../helpers/apiObjectHelper.js").IObjectRunLoaderOptions<any, any> | undefined) => Promise<void>;
112
+ clear: () => void;
113
+ setLoading: () => void;
114
+ setData: (data: any) => void;
115
+ } | undefined;
116
+ percentRef?: number | undefined;
117
+ }[]>;
118
+ isEmpty: import("vue").ComputedRef<boolean>;
119
+ hasUploading: import("vue").ComputedRef<boolean>;
120
+ allSuccess: import("vue").ComputedRef<boolean>;
121
+ dropzone: import("@vueuse/core").UseDropZoneReturn;
122
+ handleOpenFile: () => void;
123
+ handleDeleteFile: (index: number) => void;
124
+ handleRetryUpload: (index: number) => void;
125
+ handlePreview: (fileValue: IFileValue) => void;
126
+ };
@@ -0,0 +1,238 @@
1
+ import { useDropZone, useFileDialog } from "@vueuse/core";
2
+ import PreviewModal from "./PreviewModal.vue";
3
+ import { computed, ref, useOverlay, watch } from "#imports";
4
+ import { useUploadLoader } from "#core/composables/useUpload";
5
+ import { useFileAllocate, useFileProgress } from "#core/helpers/componentHelper";
6
+ import { StringHelper } from "#core/utils/StringHelper";
7
+ import { _get } from "#core/utils/lodash";
8
+ export var UploadState = /* @__PURE__ */ ((UploadState2) => {
9
+ UploadState2["EMPTY"] = "empty";
10
+ UploadState2["UPLOADING"] = "uploading";
11
+ UploadState2["SUCCESS"] = "success";
12
+ UploadState2["ERROR"] = "error";
13
+ return UploadState2;
14
+ })(UploadState || {});
15
+ export const useUploadStateMultiple = (props, emits, onChange, setErrors, value, acceptedFileTypes, wrapperProps, dropzoneRef) => {
16
+ const overlay = useOverlay();
17
+ const previewModal = overlay.create(PreviewModal);
18
+ const fileItems = ref([]);
19
+ const fileAllocate = useFileAllocate(computed(() => fileItems.value[0]?.file), props);
20
+ const validateFile = (file) => {
21
+ if (props.accept && fileAllocate.acceptFile.value) {
22
+ const acceptedTypes = fileAllocate.acceptFile.value;
23
+ const acceptedTypesList = acceptedTypes.split(",").map((type) => type.trim());
24
+ const fileExtension = file.name.toLowerCase().split(".").pop();
25
+ const isValidFileType = acceptedTypesList.some((acceptedType) => {
26
+ if (acceptedType.startsWith(".")) {
27
+ return fileExtension === acceptedType.slice(1).toLowerCase();
28
+ }
29
+ if (!acceptedType.includes("/") && !acceptedType.includes("*")) {
30
+ return fileExtension === acceptedType.toLowerCase();
31
+ }
32
+ if (acceptedType.endsWith("/*")) {
33
+ return file.type.startsWith(acceptedType.slice(0, -2) + "/");
34
+ }
35
+ return file.type === acceptedType;
36
+ });
37
+ if (!isValidFileType) {
38
+ return {
39
+ valid: false,
40
+ error: `\u0E1B\u0E23\u0E30\u0E40\u0E20\u0E17\u0E44\u0E1F\u0E25\u0E4C\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 (\u0E23\u0E2D\u0E07\u0E23\u0E31\u0E1A\u0E40\u0E09\u0E1E\u0E32\u0E30 ${acceptedTypesList.join(", ")})`
41
+ };
42
+ }
43
+ }
44
+ if (props.maxSize) {
45
+ const maxSizeBytes = (fileAllocate.acceptFileSizeKb.value || 0) * 1024;
46
+ if (file.size > maxSizeBytes) {
47
+ const sizeLabel = fileAllocate.isAcceptFileUseMb.value ? `${fileAllocate.acceptFileSizeMb.value} MB` : `${fileAllocate.acceptFileSizeKb.value} KB`;
48
+ return {
49
+ valid: false,
50
+ error: `\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${sizeLabel}`
51
+ };
52
+ }
53
+ }
54
+ return {
55
+ valid: true
56
+ };
57
+ };
58
+ const updateFileItem = (index, updates) => {
59
+ fileItems.value = fileItems.value.map(
60
+ (item, i) => i === index ? {
61
+ ...item,
62
+ ...updates
63
+ } : item
64
+ );
65
+ };
66
+ const setupUpload = (fileItem, index) => {
67
+ const {
68
+ percent,
69
+ onDownloadProgress,
70
+ onUploadProgress
71
+ } = useFileProgress();
72
+ const request = {
73
+ requestOptions: {
74
+ ...props.requestOptions,
75
+ onDownloadProgress,
76
+ onUploadProgress
77
+ },
78
+ pathURL: props.uploadPathURL
79
+ };
80
+ const uploadLoader = useUploadLoader(request);
81
+ updateFileItem(index, {
82
+ uploadLoader,
83
+ percentRef: percent
84
+ });
85
+ const stopProgressWatch = watch(percent, (newProgress) => {
86
+ updateFileItem(index, {
87
+ progress: newProgress
88
+ });
89
+ });
90
+ const stopSuccessWatch = watch(
91
+ () => uploadLoader.status.value.isSuccess,
92
+ (isSuccess) => {
93
+ if (isSuccess && uploadLoader.data.value) {
94
+ const fileValue = {
95
+ url: _get(uploadLoader.data.value, props.responseURL),
96
+ path: _get(uploadLoader.data.value, props.responsePath),
97
+ name: _get(uploadLoader.data.value, props.responseName) || fileItem.file.name,
98
+ size: Number(_get(uploadLoader.data.value, props.responseSize) || fileItem.file.size),
99
+ id: _get(uploadLoader.data.value, props.responseID)
100
+ };
101
+ updateFileItem(index, {
102
+ value: fileValue,
103
+ state: "success" /* SUCCESS */
104
+ });
105
+ updateFormValue();
106
+ emits("success", fileValue);
107
+ stopProgressWatch();
108
+ stopSuccessWatch();
109
+ stopErrorWatch();
110
+ }
111
+ }
112
+ );
113
+ const stopErrorWatch = watch(
114
+ () => uploadLoader.status.value.isError,
115
+ (isError) => {
116
+ if (isError) {
117
+ updateFileItem(index, {
118
+ state: "error" /* ERROR */,
119
+ error: "\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14: " + StringHelper.getError(
120
+ uploadLoader.status.value.errorData
121
+ )
122
+ });
123
+ stopProgressWatch();
124
+ stopSuccessWatch();
125
+ stopErrorWatch();
126
+ }
127
+ }
128
+ );
129
+ return uploadLoader;
130
+ };
131
+ const processFile = (file) => {
132
+ const validation = validateFile(file);
133
+ if (!validation.valid) {
134
+ setErrors(validation.error || "Invalid file");
135
+ return;
136
+ }
137
+ if (props.maxFiles && fileItems.value.length >= props.maxFiles) {
138
+ setErrors(`\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E2D\u0E31\u0E1E\u0E42\u0E2B\u0E25\u0E14\u0E44\u0E14\u0E49\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14 ${props.maxFiles} \u0E44\u0E1F\u0E25\u0E4C`);
139
+ return;
140
+ }
141
+ setErrors("");
142
+ const fileItem = {
143
+ file,
144
+ state: "uploading" /* UPLOADING */,
145
+ progress: 0
146
+ };
147
+ fileItems.value = [...fileItems.value, fileItem];
148
+ const newIndex = fileItems.value.length - 1;
149
+ const uploadLoader = setupUpload(fileItem, newIndex);
150
+ const formData = new FormData();
151
+ formData.append(props.bodyKey, file);
152
+ uploadLoader.run({
153
+ data: formData
154
+ });
155
+ emits("change", fileItems.value.map((item) => item.file));
156
+ };
157
+ const updateFormValue = () => {
158
+ const successfulFiles = fileItems.value.filter((item) => item.state === "success" /* SUCCESS */ && item.value).map((item) => item.value);
159
+ onChange(successfulFiles.length > 0 ? successfulFiles : void 0);
160
+ };
161
+ const handleFileDrop = (files) => {
162
+ if (wrapperProps.value.disabled || wrapperProps.value.readonly || !files?.length) return;
163
+ files.forEach((file) => processFile(file));
164
+ };
165
+ const fileDialog = useFileDialog({
166
+ accept: acceptedFileTypes.value || "",
167
+ directory: false,
168
+ multiple: true
169
+ });
170
+ const dropzone = useDropZone(dropzoneRef, {
171
+ onDrop: handleFileDrop,
172
+ multiple: true,
173
+ preventDefaultForUnhandled: false
174
+ });
175
+ const isEmpty = computed(() => fileItems.value.length === 0);
176
+ const hasUploading = computed(
177
+ () => fileItems.value.some((item) => item.state === "uploading" /* UPLOADING */)
178
+ );
179
+ const allSuccess = computed(
180
+ () => fileItems.value.length > 0 && fileItems.value.every((item) => item.state === "success" /* SUCCESS */)
181
+ );
182
+ fileDialog.onChange((files) => {
183
+ if (files?.length) {
184
+ Array.from(files).forEach((file) => processFile(file));
185
+ fileDialog.reset();
186
+ }
187
+ });
188
+ const handleOpenFile = () => {
189
+ if (props.maxFiles && fileItems.value.length >= props.maxFiles) {
190
+ setErrors(`\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E2D\u0E31\u0E1E\u0E42\u0E2B\u0E25\u0E14\u0E44\u0E14\u0E49\u0E2A\u0E39\u0E07\u0E2A\u0E38\u0E14 ${props.maxFiles} \u0E44\u0E1F\u0E25\u0E4C`);
191
+ return;
192
+ }
193
+ if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
194
+ fileDialog.open();
195
+ };
196
+ const handleDeleteFile = (index) => {
197
+ fileItems.value.splice(index, 1);
198
+ updateFormValue();
199
+ if (fileItems.value.length === 0) {
200
+ setErrors("");
201
+ }
202
+ emits("delete", index);
203
+ };
204
+ const handleRetryUpload = (index) => {
205
+ const fileItem = fileItems.value[index];
206
+ if (!fileItem) return;
207
+ updateFileItem(index, {
208
+ state: "uploading" /* UPLOADING */,
209
+ error: void 0,
210
+ progress: 0
211
+ });
212
+ const uploadLoader = setupUpload(fileItem, index);
213
+ const formData = new FormData();
214
+ formData.append(props.bodyKey, fileItem.file);
215
+ uploadLoader.run({
216
+ data: formData
217
+ });
218
+ };
219
+ const handlePreview = (fileValue) => {
220
+ previewModal.open({
221
+ value: fileValue
222
+ });
223
+ };
224
+ return {
225
+ // State
226
+ fileItems,
227
+ isEmpty,
228
+ hasUploading,
229
+ allSuccess,
230
+ // Upload utilities
231
+ dropzone,
232
+ // Handlers
233
+ handleOpenFile,
234
+ handleDeleteFile,
235
+ handleRetryUpload,
236
+ handlePreview
237
+ };
238
+ };
@@ -1,5 +1,5 @@
1
- <template>
2
- <form class="form">
3
- <slot />
4
- </form>
5
- </template>
1
+ <template>
2
+ <form class="form">
3
+ <slot />
4
+ </form>
5
+ </template>
@@ -2,6 +2,7 @@ import type { Component } from '@nuxt/schema';
2
2
  import type { FormContext } from 'vee-validate';
3
3
  import type { IUploadDropzoneField } from './InputUploadDropzone/types.js';
4
4
  import type { IUploadDropzoneAutoField } from './InputUploadDropzoneAuto/types.js';
5
+ import type { IUploadDropzoneAutoMultipleField } from './InputUploadDropzoneAutoMultiple/types.js';
5
6
  import type { IDateTimeRangeField } from './InputDateTimeRange/date_range_time_field.types.js';
6
7
  import type { ITextField } from '#core/components/Form/InputText/types';
7
8
  import type { ISearchField } from '#core/components/Form/InputSearch/types';
@@ -75,7 +76,7 @@ export interface IFormFieldBase<I extends INPUT_TYPES, P extends IFieldProps, O>
75
76
  props: P;
76
77
  on?: O;
77
78
  }
78
- export type IFormField = ITextField | ISearchField | INumberField | ICurrencyField | ITextareaField | IToggleField | ISelectField | ICheckboxField | ISelectMultipleField | IRadioField | IDateTimeField | ITimeField | IMonthField | IDateTimeRangeField | IUploadDropzoneField | IUploadDropzoneAutoField | IWYSIWYGField | IComponentField | ITagsField | IFormFieldBase<INPUT_TYPES.COMPONENT, any, any>;
79
+ export type IFormField = ITextField | ISearchField | INumberField | ICurrencyField | ITextareaField | IToggleField | ISelectField | ICheckboxField | ISelectMultipleField | IRadioField | IDateTimeField | ITimeField | IMonthField | IDateTimeRangeField | IUploadDropzoneField | IUploadDropzoneAutoField | IUploadDropzoneAutoMultipleField | IWYSIWYGField | IComponentField | ITagsField | IFormFieldBase<INPUT_TYPES.COMPONENT, any, any>;
79
80
  export interface IFileValue {
80
81
  url: string;
81
82
  path?: string;