@finema/core 2.22.0 → 2.23.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 (45) hide show
  1. package/README.md +79 -79
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -1
  4. package/dist/runtime/components/Form/FieldWrapper.vue +13 -13
  5. package/dist/runtime/components/Form/Fields.vue +18 -14
  6. package/dist/runtime/components/Form/InputCheckbox/index.vue +18 -18
  7. package/dist/runtime/components/Form/InputNumber/index.vue +20 -20
  8. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +43 -43
  9. package/dist/runtime/components/Form/InputText/index.vue +74 -74
  10. package/dist/runtime/components/Form/InputTextarea/index.vue +18 -18
  11. package/dist/runtime/components/Form/InputToggle/index.vue +17 -17
  12. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +105 -0
  13. package/dist/runtime/components/Form/InputUploadDropzone/index.vue.d.ts +12 -0
  14. package/dist/runtime/components/Form/InputUploadDropzone/types.d.ts +11 -0
  15. package/dist/runtime/components/Form/InputUploadDropzone/types.js +0 -0
  16. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +6 -13
  17. package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/EmptyState.vue +22 -22
  18. package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/FailedState.vue +34 -34
  19. package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/LoadingState.vue +25 -25
  20. package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/PreviewModal.vue +23 -23
  21. package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/SuccessState.vue +13 -6
  22. package/dist/runtime/components/Form/fileState/useFileState.d.ts +23 -0
  23. package/dist/runtime/components/Form/fileState/useFileState.js +154 -0
  24. package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/useUploadState.d.ts +1 -1
  25. package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/useUploadState.js +12 -13
  26. package/dist/runtime/components/Form/index.vue +5 -5
  27. package/dist/runtime/components/Form/types.d.ts +2 -1
  28. package/dist/runtime/components/Image.vue +28 -28
  29. package/dist/runtime/components/Log/index.vue +17 -17
  30. package/dist/runtime/components/Table/ColumnDate.vue +1 -1
  31. package/dist/runtime/components/Table/ColumnDateTime.vue +1 -1
  32. package/dist/runtime/components/Table/ColumnImage.vue +4 -4
  33. package/dist/runtime/components/Table/ColumnText.vue +1 -1
  34. package/dist/runtime/components/Table/index.vue +89 -89
  35. package/dist/runtime/composables/useFileIcon.d.ts +1 -0
  36. package/dist/runtime/composables/useFileIcon.js +9 -0
  37. package/dist/runtime/server/tsconfig.json +3 -3
  38. package/dist/runtime/theme/uploadFileDropzone.d.ts +4 -3
  39. package/dist/runtime/theme/uploadFileDropzone.js +4 -3
  40. package/package.json +2 -2
  41. /package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/EmptyState.vue.d.ts +0 -0
  42. /package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/FailedState.vue.d.ts +0 -0
  43. /package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/LoadingState.vue.d.ts +0 -0
  44. /package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/PreviewModal.vue.d.ts +0 -0
  45. /package/dist/runtime/components/Form/{InputUploadDropzoneAuto → fileState}/SuccessState.vue.d.ts +0 -0
@@ -1,21 +1,21 @@
1
1
  <template>
2
- <FieldWrapper
3
- v-bind="wrapperProps"
4
- label=""
5
- description=""
6
- >
7
- <Switch
8
- :model-value="value"
9
- :disabled="wrapperProps.disabled"
10
- :name="name"
11
- :ui="ui"
12
- :label="label"
13
- :description="description"
14
- :loading="loading"
15
- :loading-icon="loadingIcon"
16
- @update:modelValue="onChange"
17
- />
18
- </FieldWrapper>
2
+ <FieldWrapper
3
+ v-bind="wrapperProps"
4
+ label=""
5
+ description=""
6
+ >
7
+ <Switch
8
+ :model-value="value"
9
+ :disabled="wrapperProps.disabled"
10
+ :name="name"
11
+ :ui="ui"
12
+ :label="label"
13
+ :description="description"
14
+ :loading="loading"
15
+ :loading-icon="loadingIcon"
16
+ @update:modelValue="onChange"
17
+ />
18
+ </FieldWrapper>
19
19
  </template>
20
20
 
21
21
  <script setup>
@@ -0,0 +1,105 @@
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div
4
+ ref="dropzoneRef"
5
+ :class="theme.base()"
6
+ >
7
+ <div :class="theme.wrapper()">
8
+ <!-- Empty State -->
9
+ <EmptyState
10
+ v-if="uploadState.isEmpty.value"
11
+ :theme="theme"
12
+ :select-file-label="selectFileLabel"
13
+ :select-file-sub-label="selectFileSubLabel"
14
+ :placeholder="placeholder"
15
+ @open-file="uploadState.handleOpenFile"
16
+ />
17
+
18
+ <!-- Success State -->
19
+ <SuccessState
20
+ v-if="uploadState.isSuccess.value"
21
+ :theme="theme"
22
+ :value="value"
23
+ :disabled="wrapperProps.disabled"
24
+ :readonly="wrapperProps.readonly"
25
+ @preview="uploadState.handlePreview"
26
+ @download="handleDownloadFile"
27
+ @delete="uploadState.handleDeleteFile"
28
+ />
29
+ </div>
30
+ </div>
31
+ </FieldWrapper>
32
+ </template>
33
+
34
+ <script setup>
35
+ import EmptyState from "../fileState/EmptyState.vue";
36
+ import SuccessState from "../fileState/SuccessState.vue";
37
+ import { useFileState } from "../fileState/useFileState";
38
+ import { computed, useTemplateRef, useWatchChange } from "#imports";
39
+ import FieldWrapper from "#core/components/Form/FieldWrapper.vue";
40
+ import { useFieldHOC } from "#core/composables/useForm";
41
+ import { uploadFileDropzoneTheme } from "#core/theme/uploadFileDropzone";
42
+ import { useUiConfig } from "#core/composables/useConfig";
43
+ const props = defineProps({
44
+ accept: { type: [Array, String], required: false },
45
+ maxSize: { type: Number, required: false },
46
+ selectFileLabel: { type: String, required: false, default: "\u0E04\u0E25\u0E34\u0E01\u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E44\u0E1F\u0E25\u0E4C" },
47
+ selectFileSubLabel: { type: String, required: false, default: "\u0E2B\u0E23\u0E37\u0E2D \u0E25\u0E32\u0E01\u0E41\u0E25\u0E30\u0E27\u0E32\u0E07\u0E17\u0E35\u0E48\u0E19\u0E35\u0E48" },
48
+ form: { type: Object, required: false },
49
+ name: { type: String, required: true },
50
+ errorMessage: { type: String, required: false },
51
+ label: { type: null, required: false },
52
+ description: { type: String, required: false },
53
+ hint: { type: String, required: false },
54
+ rules: { type: null, required: false },
55
+ autoFocus: { type: Boolean, required: false },
56
+ placeholder: { type: String, required: false },
57
+ disabled: { type: Boolean, required: false },
58
+ readonly: { type: Boolean, required: false },
59
+ required: { type: Boolean, required: false },
60
+ help: { type: String, required: false },
61
+ ui: { type: null, required: false }
62
+ });
63
+ const emits = defineEmits(["change", "delete"]);
64
+ const {
65
+ wrapperProps,
66
+ handleChange: onChange,
67
+ setErrors,
68
+ value
69
+ } = useFieldHOC(props);
70
+ const acceptedFileTypes = computed(
71
+ () => typeof props.accept === "string" ? props.accept : props.accept?.join(",")
72
+ );
73
+ const dropzoneRef = useTemplateRef("dropzoneRef");
74
+ const uploadState = useFileState(
75
+ props,
76
+ emits,
77
+ onChange,
78
+ setErrors,
79
+ value,
80
+ acceptedFileTypes,
81
+ wrapperProps,
82
+ dropzoneRef
83
+ );
84
+ useWatchChange(() => value.value, (newValue) => {
85
+ if (typeof newValue === "object" && newValue !== null) {
86
+ onChange(newValue);
87
+ } else {
88
+ onChange(void 0);
89
+ }
90
+ });
91
+ const theme = computed(
92
+ () => useUiConfig(uploadFileDropzoneTheme, "uploadFileDropzone")({
93
+ dragover: uploadState.dropzone.isOverDropZone.value && uploadState.isEmpty.value,
94
+ disabled: wrapperProps.value.disabled
95
+ })
96
+ );
97
+ const handleDownloadFile = () => {
98
+ if (value.value?.url && value.value?.name) {
99
+ const a = document.createElement("a");
100
+ a.href = value.value.url;
101
+ a.download = value.value.name;
102
+ a.click();
103
+ }
104
+ };
105
+ </script>
@@ -0,0 +1,12 @@
1
+ import type { IUploadDropzoneProps } from './types.js';
2
+ declare const _default: import("vue").DefineComponent<IUploadDropzoneProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
3
+ delete: () => any;
4
+ change: (value: File | undefined) => any;
5
+ }, string, import("vue").PublicProps, Readonly<IUploadDropzoneProps> & Readonly<{
6
+ onDelete?: (() => any) | undefined;
7
+ onChange?: ((value: File | undefined) => any) | undefined;
8
+ }>, {
9
+ selectFileLabel: string;
10
+ selectFileSubLabel: string;
11
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import type { IFieldProps, IFormFieldBase, INPUT_TYPES } from '../types.js';
2
+ export interface IUploadDropzoneProps extends IFieldProps {
3
+ accept?: string[] | string;
4
+ maxSize?: number;
5
+ selectFileLabel?: string;
6
+ selectFileSubLabel?: string;
7
+ }
8
+ export type IUploadDropzoneField = IFormFieldBase<INPUT_TYPES.UPLOAD_DROPZONE, IUploadDropzoneProps, {
9
+ change: (value: File | undefined) => void;
10
+ delete: () => void;
11
+ }>;
@@ -52,12 +52,12 @@
52
52
  </template>
53
53
 
54
54
  <script setup>
55
- import EmptyState from "./EmptyState.vue";
56
- import LoadingState from "./LoadingState.vue";
57
- import SuccessState from "./SuccessState.vue";
58
- import FailedState from "./FailedState.vue";
59
- import { useUploadState } from "./useUploadState";
60
- import { computed, useTemplateRef, useWatchChange } from "#imports";
55
+ import EmptyState from "../fileState/EmptyState.vue";
56
+ import SuccessState from "../fileState/SuccessState.vue";
57
+ import LoadingState from "../fileState/LoadingState.vue";
58
+ import FailedState from "../fileState/FailedState.vue";
59
+ import { useUploadState } from "../fileState/useUploadState";
60
+ import { computed, useTemplateRef } from "#imports";
61
61
  import FieldWrapper from "#core/components/Form/FieldWrapper.vue";
62
62
  import { useFieldHOC } from "#core/composables/useForm";
63
63
  import { uploadFileDropzoneTheme } from "#core/theme/uploadFileDropzone";
@@ -114,13 +114,6 @@ const uploadState = useUploadState(
114
114
  wrapperProps,
115
115
  dropzoneRef
116
116
  );
117
- useWatchChange(() => value.value, (newValue) => {
118
- if (typeof newValue === "object" && newValue !== null) {
119
- onChange(newValue);
120
- } else {
121
- onChange(void 0);
122
- }
123
- });
124
117
  const theme = computed(
125
118
  () => useUiConfig(uploadFileDropzoneTheme, "uploadFileDropzone")({
126
119
  dragover: uploadState.dropzone.isOverDropZone.value && uploadState.isEmpty.value,
@@ -1,29 +1,29 @@
1
1
  <template>
2
- <div :class="theme.placeholderWrapper()">
3
- <Icon
4
- :name="icons.uploadIcon"
5
- :class="theme.labelIcon()"
6
- />
7
- <div :class="theme.labelWrapper()">
8
- <p
9
- class="text-primary cursor-pointer font-bold"
10
- @click="$emit('openFile')"
11
- >
12
- {{ selectFileLabel }}
13
- </p>
14
- <p>{{ selectFileSubLabel }}</p>
15
- </div>
16
- <p
17
- v-if="placeholder"
18
- :class="theme.placeholder()"
19
- >
20
- {{ placeholder }}
21
- </p>
22
- </div>
2
+ <div :class="theme.placeholderWrapper()">
3
+ <Icon
4
+ :name="icons.uploadIcon"
5
+ :class="theme.labelIcon()"
6
+ />
7
+ <div :class="theme.labelWrapper()">
8
+ <p
9
+ class="text-primary cursor-pointer font-bold"
10
+ @click="$emit('openFile')"
11
+ >
12
+ {{ selectFileLabel }}
13
+ </p>
14
+ <p>{{ selectFileSubLabel }}</p>
15
+ </div>
16
+ <p
17
+ v-if="placeholder"
18
+ :class="theme.placeholder()"
19
+ >
20
+ {{ placeholder }}
21
+ </p>
22
+ </div>
23
23
  </template>
24
24
 
25
25
  <script setup>
26
- import { useUiIconConfig } from "#imports";
26
+ import { useUiIconConfig } from "#core/composables/useConfig";
27
27
  defineProps({
28
28
  theme: { type: null, required: true },
29
29
  selectFileLabel: { type: String, required: true },
@@ -1,42 +1,42 @@
1
1
  <template>
2
- <div :class="theme.onFailedWrapper()">
3
- <div :class="theme.onFailedFailedImgWrapper()">
4
- <Icon
5
- :name="getFileIcon(selectedFile)"
6
- :class="theme.onFailedFailedIconClass()"
7
- />
8
- </div>
9
- <div :class="theme.onFailedTextWrapper()">
10
- <div class="truncate">
11
- <h1 class="truncate font-bold">
12
- {{ selectedFile.name }}
13
- </h1>
14
- <p class="text-error truncate font-light">
15
- {{ uploadFailedLabel }}
16
- </p>
17
- <Button
18
- variant="link"
19
- :icon="icons.actionRetryIcon"
20
- :class="theme.actionRetryBtnClass()"
21
- color="primary"
22
- @click="$emit('retry')"
23
- >
24
- {{ retryLabel }}
25
- </Button>
26
- </div>
27
- <Icon
28
- :name="icons.actionDeleteIcon"
29
- :class="theme.actionDeleteIconClass()"
30
- title="ลบไฟล์"
31
- @click="$emit('delete')"
32
- />
33
- </div>
34
- </div>
2
+ <div :class="theme.onFailedWrapper()">
3
+ <div :class="theme.onFailedFailedImgWrapper()">
4
+ <Icon
5
+ :name="getFileIcon(selectedFile)"
6
+ :class="theme.onFailedFailedIconClass()"
7
+ />
8
+ </div>
9
+ <div :class="theme.onFailedTextWrapper()">
10
+ <div class="truncate">
11
+ <h1 class="truncate font-bold">
12
+ {{ selectedFile.name }}
13
+ </h1>
14
+ <p class="text-error truncate font-light">
15
+ {{ uploadFailedLabel }}
16
+ </p>
17
+ <Button
18
+ variant="link"
19
+ :icon="icons.actionRetryIcon"
20
+ :class="theme.actionRetryBtnClass()"
21
+ color="primary"
22
+ @click="$emit('retry')"
23
+ >
24
+ {{ retryLabel }}
25
+ </Button>
26
+ </div>
27
+ <Icon
28
+ :name="icons.actionDeleteIcon"
29
+ :class="theme.actionDeleteIconClass()"
30
+ title="ลบไฟล์"
31
+ @click="$emit('delete')"
32
+ />
33
+ </div>
34
+ </div>
35
35
  </template>
36
36
 
37
37
  <script setup>
38
38
  import { isImage } from "#core/helpers/componentHelper";
39
- import { useUiIconConfig } from "#imports";
39
+ import { useUiIconConfig } from "#core/composables/useConfig";
40
40
  const props = defineProps({
41
41
  theme: { type: null, required: true },
42
42
  selectedFile: { type: null, required: true },
@@ -1,33 +1,33 @@
1
1
  <template>
2
- <div :class="theme.onLoadingWrapper()">
3
- <div :class="theme.onLoadingPlaceholderWrapper()">
4
- <Icon
5
- :name="getFileIcon(selectedFile)"
6
- :class="theme.onLoadingPlaceholderIconClass()"
7
- />
8
- </div>
9
- <div :class="theme.onLoadingTextWrapper()">
10
- <div class="truncate">
11
- <h1 class="truncate font-bold">
12
- {{ selectedFile.name }}
13
- </h1>
14
- <p class="truncate font-light text-gray-400">
15
- {{ getFileSize(selectedFile) }} - {{ percent }}% {{ uploadingLabel }}
16
- </p>
17
- </div>
18
- <div>
19
- <Icon
20
- :name="icons.loadingIcon"
21
- :class="theme.onLoadingLoadingIconClass()"
22
- />
23
- </div>
24
- </div>
25
- </div>
2
+ <div :class="theme.onLoadingWrapper()">
3
+ <div :class="theme.onLoadingPlaceholderWrapper()">
4
+ <Icon
5
+ :name="getFileIcon(selectedFile)"
6
+ :class="theme.onLoadingPlaceholderIconClass()"
7
+ />
8
+ </div>
9
+ <div :class="theme.onLoadingTextWrapper()">
10
+ <div class="truncate">
11
+ <h1 class="truncate font-bold">
12
+ {{ selectedFile.name }}
13
+ </h1>
14
+ <p class="truncate font-light text-gray-400">
15
+ {{ getFileSize(selectedFile) }} - {{ percent }}% {{ uploadingLabel }}
16
+ </p>
17
+ </div>
18
+ <div>
19
+ <Icon
20
+ :name="icons.loadingIcon"
21
+ :class="theme.onLoadingLoadingIconClass()"
22
+ />
23
+ </div>
24
+ </div>
25
+ </div>
26
26
  </template>
27
27
 
28
28
  <script setup>
29
29
  import { isImage } from "#core/helpers/componentHelper";
30
- import { useUiIconConfig } from "#imports";
30
+ import { useUiIconConfig } from "#core/composables/useConfig";
31
31
  const props = defineProps({
32
32
  theme: { type: null, required: true },
33
33
  selectedFile: { type: null, required: true },
@@ -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>
@@ -1,20 +1,27 @@
1
1
  <template>
2
2
  <div :class="theme.onPreviewWrapper()">
3
- <div :class="theme.onPreviewPreviewImgWrapper()">
3
+ <div
4
+ v-if="isImageFromPath(value.path)"
5
+ :class="theme.onPreviewImgWrapper()"
6
+ >
4
7
  <div
5
- v-if="isImageFromPath(value.path)"
6
8
  class="size-full overflow-hidden"
7
9
  >
8
10
  <img
9
11
  :src="value.url"
10
- :class="theme.onPreviewPreviewImgClass()"
12
+ :class="theme.onPreviewImgClass()"
11
13
  alt="img-preview"
12
14
  />
13
15
  </div>
14
- <div v-else>
16
+ </div>
17
+ <div
18
+ v-else
19
+ :class="theme.onPreviewFileWrapper()"
20
+ >
21
+ <div>
15
22
  <Icon
16
23
  :name="icons.filePreviewIcon"
17
- :class="theme.onPreviewPreviewFileClass()"
24
+ :class="theme.onPreviewFileClass()"
18
25
  />
19
26
  </div>
20
27
  </div>
@@ -72,7 +79,7 @@ import {
72
79
  isVideoFromPath,
73
80
  useFileSize
74
81
  } from "#core/helpers/componentHelper";
75
- import { useUiIconConfig } from "#imports";
82
+ import { useUiIconConfig } from "#core/composables/useConfig";
76
83
  defineProps({
77
84
  theme: { type: null, required: true },
78
85
  value: { type: Object, required: true },
@@ -0,0 +1,23 @@
1
+ import type { TemplateRef } from 'vue';
2
+ import type { IUploadDropzoneProps } from '../InputUploadDropzone/types.js';
3
+ import type { IFileValue } from '#core/components/Form/types';
4
+ export declare enum UploadState {
5
+ EMPTY = "empty",
6
+ UPLOADING = "uploading",
7
+ SUCCESS = "success",
8
+ ERROR = "error"
9
+ }
10
+ export declare const useFileState: (props: IUploadDropzoneProps, emits: any, onChange: (value: IFileValue | undefined) => void, setErrors: (error: string) => void, value: any, acceptedFileTypes: any, wrapperProps: any, dropzoneRef: TemplateRef<HTMLDivElement | undefined>) => {
11
+ currentState: import("vue").ComputedRef<UploadState.EMPTY | UploadState.SUCCESS>;
12
+ isEmpty: import("vue").ComputedRef<boolean>;
13
+ isSuccess: import("vue").ComputedRef<boolean>;
14
+ selectedFile: import("vue").Ref<File | undefined, File | undefined>;
15
+ currentObjectUrl: import("vue").Ref<string, string>;
16
+ dropzone: import("@vueuse/core").UseDropZoneReturn;
17
+ handleInputChange: (event: Event) => void;
18
+ handleOpenFile: () => void;
19
+ handleDeleteFile: () => void;
20
+ handleRetryUpload: () => void;
21
+ handlePreview: () => void;
22
+ cleanup: () => void;
23
+ };
@@ -0,0 +1,154 @@
1
+ import { useDropZone, useFileDialog } from "@vueuse/core";
2
+ import PreviewModal from "../fileState/PreviewModal.vue";
3
+ import { computed, ref, useOverlay } from "#imports";
4
+ import { useFileAllocate } from "#core/helpers/componentHelper";
5
+ const overlay = useOverlay();
6
+ const previewModal = overlay.create(PreviewModal);
7
+ export var UploadState = /* @__PURE__ */ ((UploadState2) => {
8
+ UploadState2["EMPTY"] = "empty";
9
+ UploadState2["UPLOADING"] = "uploading";
10
+ UploadState2["SUCCESS"] = "success";
11
+ UploadState2["ERROR"] = "error";
12
+ return UploadState2;
13
+ })(UploadState || {});
14
+ export const useFileState = (props, emits, onChange, setErrors, value, acceptedFileTypes, wrapperProps, dropzoneRef) => {
15
+ const selectedFile = ref();
16
+ const currentObjectUrl = ref("");
17
+ const fileAllocate = useFileAllocate(selectedFile, props);
18
+ const validateFile = (file) => {
19
+ if (props.accept && fileAllocate.acceptFile.value) {
20
+ const acceptedTypes = fileAllocate.acceptFile.value;
21
+ const acceptedTypesList = acceptedTypes.split(",").map((type) => type.trim());
22
+ const fileExtension = file.name.toLowerCase().split(".").pop();
23
+ const isValidFileType = acceptedTypesList.some((acceptedType) => {
24
+ if (acceptedType.startsWith(".")) {
25
+ const extension = acceptedType.slice(1).toLowerCase();
26
+ return fileExtension === extension;
27
+ } else if (!acceptedType.includes("/") && !acceptedType.includes("*")) {
28
+ return fileExtension === acceptedType.toLowerCase();
29
+ }
30
+ if (acceptedType.endsWith("/*")) {
31
+ const baseType = acceptedType.slice(0, -2);
32
+ return file.type.startsWith(baseType + "/");
33
+ }
34
+ return file.type === acceptedType;
35
+ });
36
+ if (!isValidFileType) {
37
+ setErrors("\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(", ") + ")");
38
+ return false;
39
+ }
40
+ }
41
+ if (props.maxSize) {
42
+ const maxSizeBytes = (fileAllocate.acceptFileSizeKb.value || 0) * 1024;
43
+ if (file.size > maxSizeBytes) {
44
+ if (fileAllocate.isAcceptFileUseMb.value) {
45
+ setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeMb.value} MB`);
46
+ } else {
47
+ setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeKb.value} KB`);
48
+ }
49
+ return false;
50
+ }
51
+ }
52
+ setErrors("");
53
+ return true;
54
+ };
55
+ const processFile = (file) => {
56
+ if (!validateFile(file)) return;
57
+ selectedFile.value = file;
58
+ emits("change", file);
59
+ if (currentObjectUrl.value) {
60
+ URL.revokeObjectURL(currentObjectUrl.value);
61
+ }
62
+ const objectUrl = URL.createObjectURL(file);
63
+ currentObjectUrl.value = objectUrl;
64
+ value.value = file;
65
+ emits("success", file);
66
+ };
67
+ const handleFileDrop = (files) => {
68
+ if (wrapperProps.value.disabled || wrapperProps.value.readonly || !files?.length || !isEmpty.value) return;
69
+ const file = files[0];
70
+ if (file) {
71
+ processFile(file);
72
+ }
73
+ };
74
+ const fileDialog = useFileDialog({
75
+ accept: acceptedFileTypes.value || "",
76
+ directory: false,
77
+ multiple: false
78
+ });
79
+ const dropzone = useDropZone(dropzoneRef, {
80
+ onDrop: handleFileDrop,
81
+ // dataTypes: typeof props.accept === 'string' ? [props.accept] : props.accept,
82
+ multiple: false,
83
+ preventDefaultForUnhandled: false
84
+ });
85
+ const currentState = computed(() => {
86
+ if (value.value) return "success" /* SUCCESS */;
87
+ return "empty" /* EMPTY */;
88
+ });
89
+ const isEmpty = computed(() => currentState.value === "empty" /* EMPTY */);
90
+ const isSuccess = computed(() => currentState.value === "success" /* SUCCESS */);
91
+ const handleInputChange = (event) => {
92
+ if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
93
+ const file = event.target.files?.[0];
94
+ if (file) {
95
+ processFile(file);
96
+ }
97
+ };
98
+ fileDialog.onChange((files) => {
99
+ if (files?.length) {
100
+ processFile(files[0]);
101
+ }
102
+ });
103
+ const handleOpenFile = () => {
104
+ if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
105
+ fileDialog.open();
106
+ };
107
+ const handleDeleteFile = () => {
108
+ fileDialog.reset();
109
+ if (currentObjectUrl.value) {
110
+ URL.revokeObjectURL(currentObjectUrl.value);
111
+ currentObjectUrl.value = "";
112
+ }
113
+ selectedFile.value = void 0;
114
+ onChange(void 0);
115
+ emits("delete");
116
+ };
117
+ const handleRetryUpload = () => {
118
+ if (selectedFile.value) {
119
+ processFile(selectedFile.value);
120
+ }
121
+ };
122
+ const handlePreview = () => {
123
+ previewModal.open({
124
+ value: value.value
125
+ });
126
+ };
127
+ const cleanup = () => {
128
+ if (currentObjectUrl.value) {
129
+ URL.revokeObjectURL(currentObjectUrl.value);
130
+ currentObjectUrl.value = "";
131
+ }
132
+ };
133
+ if (typeof window !== "undefined") {
134
+ window.addEventListener("beforeunload", cleanup);
135
+ }
136
+ return {
137
+ // State
138
+ currentState,
139
+ isEmpty,
140
+ isSuccess,
141
+ selectedFile,
142
+ currentObjectUrl,
143
+ // Upload utilities
144
+ dropzone,
145
+ // Handlers
146
+ handleInputChange,
147
+ handleOpenFile,
148
+ handleDeleteFile,
149
+ handleRetryUpload,
150
+ handlePreview,
151
+ // Cleanup
152
+ cleanup
153
+ };
154
+ };