@finema/core 3.22.0 → 3.23.2

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 (125) hide show
  1. package/dist/module.d.mts +2 -0
  2. package/dist/module.json +2 -2
  3. package/dist/module.mjs +16 -2
  4. package/dist/runtime/components/App.vue +1 -1
  5. package/dist/runtime/components/DevToolsWindow/index.vue +1 -1
  6. package/dist/runtime/components/Dialog/index.d.vue.ts +1 -4
  7. package/dist/runtime/components/Dialog/index.vue +9 -10
  8. package/dist/runtime/components/Dialog/index.vue.d.ts +1 -4
  9. package/dist/runtime/components/Empty.d.vue.ts +0 -1
  10. package/dist/runtime/components/Empty.vue +6 -2
  11. package/dist/runtime/components/Empty.vue.d.ts +0 -1
  12. package/dist/runtime/components/FlexDeck/index.vue +86 -81
  13. package/dist/runtime/components/Form/FieldWrapper.vue +14 -14
  14. package/dist/runtime/components/Form/Fields.vue +10 -10
  15. package/dist/runtime/components/Form/InputCheckbox/index.vue +20 -19
  16. package/dist/runtime/components/Form/InputCheckboxGroup/index.vue +2 -1
  17. package/dist/runtime/components/Form/InputComponent/index.vue +2 -1
  18. package/dist/runtime/components/Form/InputCurrency/index.vue +51 -50
  19. package/dist/runtime/components/Form/InputDateTime/date_time_field.types.d.ts +1 -1
  20. package/dist/runtime/components/Form/InputDateTime/index.vue +69 -64
  21. package/dist/runtime/components/Form/InputDateTimeRange/date_range_time_field.types.d.ts +1 -1
  22. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +62 -57
  23. package/dist/runtime/components/Form/InputMonth/index.vue +10 -5
  24. package/dist/runtime/components/Form/InputMonth/types.d.ts +1 -1
  25. package/dist/runtime/components/Form/InputNumber/index.vue +22 -21
  26. package/dist/runtime/components/Form/InputRadio/index.d.vue.ts +1 -1
  27. package/dist/runtime/components/Form/InputRadio/index.vue +2 -1
  28. package/dist/runtime/components/Form/InputRadio/index.vue.d.ts +1 -1
  29. package/dist/runtime/components/Form/InputRating/index.vue +2 -1
  30. package/dist/runtime/components/Form/InputSearch/index.vue +7 -2
  31. package/dist/runtime/components/Form/InputSelect/index.vue +112 -111
  32. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +120 -119
  33. package/dist/runtime/components/Form/InputTags/index.vue +56 -55
  34. package/dist/runtime/components/Form/InputText/index.vue +88 -87
  35. package/dist/runtime/components/Form/InputTextarea/index.vue +20 -19
  36. package/dist/runtime/components/Form/InputTime/index.vue +45 -40
  37. package/dist/runtime/components/Form/InputTime/types.d.ts +1 -1
  38. package/dist/runtime/components/Form/InputToggle/index.vue +19 -18
  39. package/dist/runtime/components/Form/InputUploadDropzone/index.d.vue.ts +1 -4
  40. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +40 -35
  41. package/dist/runtime/components/Form/InputUploadDropzone/index.vue.d.ts +1 -4
  42. package/dist/runtime/components/Form/InputUploadDropzone/types.d.ts +2 -2
  43. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.d.vue.ts +0 -5
  44. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +63 -58
  45. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue.d.ts +0 -5
  46. package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +3 -3
  47. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.d.vue.ts +0 -5
  48. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +69 -60
  49. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue.d.ts +0 -5
  50. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/types.d.ts +3 -3
  51. package/dist/runtime/components/Form/InputUploadImageAuto/index.d.vue.ts +1 -6
  52. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +63 -58
  53. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue.d.ts +1 -6
  54. package/dist/runtime/components/Form/InputUploadImageAuto/types.d.ts +3 -3
  55. package/dist/runtime/components/Form/InputWYSIWYG/EditorImageUploadNode.vue +18 -18
  56. package/dist/runtime/components/Form/InputWYSIWYG/EditorLinkPopover.vue +65 -65
  57. package/dist/runtime/components/Form/InputWYSIWYG/README.md +96 -96
  58. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +43 -42
  59. package/dist/runtime/components/Form/fileState/EmptyState.vue +21 -21
  60. package/dist/runtime/components/Form/fileState/FailedState.vue +37 -33
  61. package/dist/runtime/components/Form/fileState/LoadingState.vue +24 -24
  62. package/dist/runtime/components/Form/fileState/MultipleFilesState.d.vue.ts +4 -4
  63. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue +79 -75
  64. package/dist/runtime/components/Form/fileState/MultipleFilesState.vue.d.ts +4 -4
  65. package/dist/runtime/components/Form/fileState/PreviewModal.vue +23 -23
  66. package/dist/runtime/components/Form/fileState/SuccessState.d.vue.ts +2 -2
  67. package/dist/runtime/components/Form/fileState/SuccessState.vue +8 -4
  68. package/dist/runtime/components/Form/fileState/SuccessState.vue.d.ts +2 -2
  69. package/dist/runtime/components/Form/fileState/useFilePicker.d.ts +34 -0
  70. package/dist/runtime/components/Form/fileState/useFilePicker.js +48 -0
  71. package/dist/runtime/components/Form/fileState/useFileState.d.ts +7 -9
  72. package/dist/runtime/components/Form/fileState/useFileState.js +40 -120
  73. package/dist/runtime/components/Form/fileState/useUploadImageState.d.ts +7 -8
  74. package/dist/runtime/components/Form/fileState/useUploadImageState.js +28 -300
  75. package/dist/runtime/components/Form/fileState/useUploadState.d.ts +11 -7
  76. package/dist/runtime/components/Form/fileState/useUploadState.js +58 -131
  77. package/dist/runtime/components/Form/fileState/useUploadStateMultiple.d.ts +12 -18
  78. package/dist/runtime/components/Form/fileState/useUploadStateMultiple.js +89 -222
  79. package/dist/runtime/components/Form/index.vue +5 -5
  80. package/dist/runtime/components/Form/types.d.ts +2 -0
  81. package/dist/runtime/components/Image.vue +28 -28
  82. package/dist/runtime/components/Loader.d.vue.ts +1 -0
  83. package/dist/runtime/components/Loader.vue.d.ts +1 -0
  84. package/dist/runtime/components/Table/Base.d.vue.ts +15 -1
  85. package/dist/runtime/components/Table/Base.vue +118 -118
  86. package/dist/runtime/components/Table/Base.vue.d.ts +15 -1
  87. package/dist/runtime/components/Table/ColumnDate.vue +1 -1
  88. package/dist/runtime/components/Table/ColumnDateTime.vue +1 -1
  89. package/dist/runtime/components/Table/ColumnImage.vue +4 -4
  90. package/dist/runtime/components/Table/ColumnText.d.vue.ts +2 -3
  91. package/dist/runtime/components/Table/ColumnText.vue +11 -11
  92. package/dist/runtime/components/Table/ColumnText.vue.d.ts +2 -3
  93. package/dist/runtime/components/Table/Pagination.vue +70 -67
  94. package/dist/runtime/components/Table/Simple.vue +19 -19
  95. package/dist/runtime/components/Table/index.d.vue.ts +1 -1
  96. package/dist/runtime/components/Table/index.vue +37 -33
  97. package/dist/runtime/components/Table/index.vue.d.ts +1 -1
  98. package/dist/runtime/composables/loaderList.d.ts +2 -1
  99. package/dist/runtime/composables/loaderList.js +2 -1
  100. package/dist/runtime/composables/loaderObject.d.ts +2 -1
  101. package/dist/runtime/composables/loaderObject.js +2 -1
  102. package/dist/runtime/composables/loaderPage.js +194 -344
  103. package/dist/runtime/composables/useConfig.js +4 -3
  104. package/dist/runtime/composables/useDialog.js +5 -1
  105. package/dist/runtime/composables/useForm.d.ts +1 -1
  106. package/dist/runtime/composables/useForm.js +1 -1
  107. package/dist/runtime/composables/useMessages.d.ts +10 -0
  108. package/dist/runtime/composables/useMessages.js +10 -0
  109. package/dist/runtime/helpers/apiPageHelper.d.ts +0 -1
  110. package/dist/runtime/helpers/uploadCore.d.ts +54 -0
  111. package/dist/runtime/helpers/uploadCore.js +84 -0
  112. package/dist/runtime/i18n/valibot.d.ts +5 -68
  113. package/dist/runtime/i18n/valibot.js +33 -29
  114. package/dist/runtime/locale/en.d.ts +2 -0
  115. package/dist/runtime/locale/en.js +42 -0
  116. package/dist/runtime/locale/index.d.ts +7 -0
  117. package/dist/runtime/locale/index.js +8 -0
  118. package/dist/runtime/locale/th.d.ts +43 -0
  119. package/dist/runtime/locale/th.js +42 -0
  120. package/dist/runtime/server/tsconfig.json +3 -3
  121. package/dist/runtime/utils/ParamHelper.js +1 -1
  122. package/dist/runtime/utils/StringHelper.js +1 -4
  123. package/dist/runtime/utils/TimeHelper.js +2 -2
  124. package/dist/runtime/utils/lodash.js +2 -2
  125. package/package.json +20 -12
@@ -1,82 +1,83 @@
1
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
- <!-- Error State -->
38
- <div
39
- v-else-if="item.state === UploadState.ERROR"
40
- :class="theme.errorItemWrapper()"
41
- >
42
- <div :class="theme.errorIconWrapper()">
43
- <Icon
44
- :name="icons.errorIcon"
45
- :class="theme.errorIconClass()"
46
- />
47
- </div>
48
- <div :class="theme.errorTextWrapper()">
49
- <h1 class="truncate font-bold">
50
- {{ item.file.name }}
51
- </h1>
52
- <p class="text-error-500 text-sm">
53
- {{ item.error || uploadFailedLabel }}
54
- </p>
55
- </div>
56
- <div :class="theme.errorActionWrapper()">
57
- <Button
58
- variant="link"
59
- :icon="icons.actionRetryIcon"
60
- :class="theme.actionRetryBtnClass()"
61
- color="primary"
62
- @click="$emit('retry', index)"
63
- >
64
- {{ retryLabel }}
65
- </Button>
66
- <Icon
67
- v-if="!disabled && !readonly"
68
- :name="icons.actionDeleteIcon"
69
- :class="theme.actionIconClass()"
70
- title="ลบไฟล์"
71
- @click="$emit('delete', index)"
72
- />
73
- </div>
74
- </div>
75
- </div>
76
- </div>
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
+ <!-- Error State -->
38
+ <div
39
+ v-else-if="item.state === UploadState.ERROR"
40
+ :class="theme.errorItemWrapper()"
41
+ >
42
+ <div :class="theme.errorIconWrapper()">
43
+ <Icon
44
+ :name="icons.errorIcon"
45
+ :class="theme.errorIconClass()"
46
+ />
47
+ </div>
48
+ <div :class="theme.errorTextWrapper()">
49
+ <h1 class="truncate font-bold">
50
+ {{ item.file.name }}
51
+ </h1>
52
+ <p class="text-error-500 text-sm">
53
+ {{ item.error || uploadFailedLabel }}
54
+ </p>
55
+ </div>
56
+ <div :class="theme.errorActionWrapper()">
57
+ <Button
58
+ variant="link"
59
+ :icon="icons.actionRetryIcon"
60
+ :class="theme.actionRetryBtnClass()"
61
+ color="primary"
62
+ @click="$emit('retry', index)"
63
+ >
64
+ {{ retryLabel }}
65
+ </Button>
66
+ <Icon
67
+ v-if="!disabled && !readonly"
68
+ :name="icons.actionDeleteIcon"
69
+ :class="theme.actionIconClass()"
70
+ :title="messages.upload.remove"
71
+ @click="$emit('delete', index)"
72
+ />
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
77
  </template>
78
78
 
79
79
  <script setup>
80
+ import { useCoreMessages } from "#core/composables/useMessages";
80
81
  import { useUiIconConfig } from "#core/composables/useConfig";
81
82
  import { UploadState } from "./useUploadStateMultiple";
82
83
  defineEmits(["preview", "download", "delete", "retry"]);
@@ -89,4 +90,7 @@ defineProps({
89
90
  retryLabel: { type: String, required: false }
90
91
  });
91
92
  const icons = useUiIconConfig("uploadFileDropzone");
93
+ const {
94
+ messages
95
+ } = useCoreMessages();
92
96
  </script>
@@ -17,14 +17,14 @@ interface Props {
17
17
  }
18
18
  declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
19
  delete: (index: number) => any;
20
- download: (value: IFileValue) => any;
21
- preview: (value: IFileValue) => any;
22
20
  retry: (index: number) => any;
21
+ preview: (value: IFileValue) => any;
22
+ download: (value: IFileValue) => any;
23
23
  }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
24
24
  onDelete?: ((index: number) => any) | undefined;
25
- onDownload?: ((value: IFileValue) => any) | undefined;
26
- onPreview?: ((value: IFileValue) => any) | undefined;
27
25
  onRetry?: ((index: number) => any) | undefined;
26
+ onPreview?: ((value: IFileValue) => any) | undefined;
27
+ onDownload?: ((value: IFileValue) => any) | undefined;
28
28
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
29
29
  declare const _default: typeof __VLS_export;
30
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>
@@ -7,12 +7,12 @@ interface Props {
7
7
  }
8
8
  declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
9
  delete: () => any;
10
- download: () => any;
11
10
  preview: () => any;
11
+ download: () => any;
12
12
  }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
13
13
  onDelete?: (() => any) | undefined;
14
- onDownload?: (() => any) | undefined;
15
14
  onPreview?: (() => any) | undefined;
15
+ onDownload?: (() => any) | undefined;
16
16
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
17
  declare const _default: typeof __VLS_export;
18
18
  export default _default;
@@ -44,27 +44,27 @@
44
44
  <Icon
45
45
  :name="icons.actionPreviewIcon"
46
46
  :class="theme.actionIconClass()"
47
- title="ดูตัวอย่าง"
47
+ :title="messages.upload.preview"
48
48
  />
49
49
  </a>
50
50
  <Icon
51
51
  v-if="isImageFromPath(value.path) || isVideoFromPath(value.path)"
52
52
  :name="icons.actionPreviewIcon"
53
53
  :class="theme.actionIconClass()"
54
- title="ดูตัวอย่าง"
54
+ :title="messages.upload.preview"
55
55
  @click="$emit('preview')"
56
56
  />
57
57
  <Icon
58
58
  :name="icons.actionDownloadIcon"
59
59
  :class="theme.actionIconClass()"
60
- title="ดาวน์โหลดไฟล์"
60
+ :title="messages.upload.download"
61
61
  @click="$emit('download')"
62
62
  />
63
63
  <Icon
64
64
  v-if="!disabled && !readonly"
65
65
  :name="icons.actionDeleteIcon"
66
66
  :class="theme.actionIconClass()"
67
- title="ลบไฟล์"
67
+ :title="messages.upload.remove"
68
68
  @click="$emit('delete')"
69
69
  />
70
70
  </div>
@@ -73,6 +73,7 @@
73
73
  </template>
74
74
 
75
75
  <script setup>
76
+ import { useCoreMessages } from "#core/composables/useMessages";
76
77
  import {
77
78
  isImageFromPath,
78
79
  isPDFFromPath,
@@ -92,4 +93,7 @@ const getFileSizeFromValue = (fileValue) => {
92
93
  const allocate = useFileSize(fileValue.size || 0);
93
94
  return allocate.isSelectedFileUseMb.value ? `${allocate.selectedFileSizeMb.value} MB` : `${allocate.selectedFileSizeKb.value} KB`;
94
95
  };
96
+ const {
97
+ messages
98
+ } = useCoreMessages();
95
99
  </script>
@@ -7,12 +7,12 @@ interface Props {
7
7
  }
8
8
  declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
9
  delete: () => any;
10
- download: () => any;
11
10
  preview: () => any;
11
+ download: () => any;
12
12
  }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
13
13
  onDelete?: (() => any) | undefined;
14
- onDownload?: (() => any) | undefined;
15
14
  onPreview?: (() => any) | undefined;
15
+ onDownload?: (() => any) | undefined;
16
16
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
17
  declare const _default: typeof __VLS_export;
18
18
  export default _default;
@@ -0,0 +1,34 @@
1
+ import type { ComputedRef, Ref, TemplateRef } from 'vue';
2
+ import type { IFieldProps, IFileValue } from '#core/components/Form/types';
3
+ /**
4
+ * What an upload field hands to its state composable
5
+ */
6
+ export interface IUploadFieldContext<Props, Value> {
7
+ props: Props;
8
+ emits: (event: any, ...args: any[]) => void;
9
+ onChange: (value: Value | undefined) => void;
10
+ setErrors: (error: string) => void;
11
+ value: Ref<any>;
12
+ acceptedFileTypes: ComputedRef<string | undefined>;
13
+ wrapperProps: ComputedRef<IFieldProps>;
14
+ dropzoneRef: TemplateRef<HTMLDivElement | undefined>;
15
+ }
16
+ interface IFilePickerOptions {
17
+ multiple: boolean;
18
+ defaultAccept?: string;
19
+ /** Dropped files are ignored while this is false (e.g. a single-file field that already has a file) */
20
+ canDrop?: () => boolean;
21
+ onFiles: (files: File[]) => void | Promise<void>;
22
+ }
23
+ /**
24
+ * The part every upload field shares: file dialog, dropzone, preview, and the disabled/readonly guard
25
+ */
26
+ export declare const useFilePicker: (context: Pick<IUploadFieldContext<unknown, unknown>, "acceptedFileTypes" | "wrapperProps" | "dropzoneRef">, options: IFilePickerOptions) => {
27
+ fileDialog: import("@vueuse/core").UseFileDialogReturn;
28
+ dropzone: import("@vueuse/core").UseDropZoneReturn;
29
+ isLocked: () => boolean;
30
+ handleInputChange: (event: Event) => void;
31
+ handleOpenFile: () => void;
32
+ openPreview: (fileValue: IFileValue | File) => void;
33
+ };
34
+ export {};
@@ -0,0 +1,48 @@
1
+ import { useDropZone, useFileDialog } from "@vueuse/core";
2
+ import PreviewModal from "./PreviewModal.vue";
3
+ import { useOverlay } from "#imports";
4
+ export const useFilePicker = (context, options) => {
5
+ const previewModal = useOverlay().create(PreviewModal);
6
+ const isLocked = () => !!(context.wrapperProps.value.disabled || context.wrapperProps.value.readonly);
7
+ const pick = (files) => options.onFiles(options.multiple ? files : files.slice(0, 1));
8
+ const fileDialog = useFileDialog({
9
+ accept: context.acceptedFileTypes.value || options.defaultAccept || "",
10
+ directory: false,
11
+ multiple: options.multiple
12
+ });
13
+ const dropzone = useDropZone(context.dropzoneRef, {
14
+ onDrop: (files) => {
15
+ if (isLocked() || !files?.length || options.canDrop?.() === false) return;
16
+ pick(files);
17
+ },
18
+ multiple: options.multiple,
19
+ preventDefaultForUnhandled: false
20
+ });
21
+ fileDialog.onChange(async (files) => {
22
+ if (!files?.length) return;
23
+ await pick(Array.from(files));
24
+ if (options.multiple) fileDialog.reset();
25
+ });
26
+ const handleInputChange = (event) => {
27
+ if (isLocked()) return;
28
+ const files = event.target.files;
29
+ if (files?.length) pick(Array.from(files));
30
+ };
31
+ const handleOpenFile = () => {
32
+ if (isLocked()) return;
33
+ fileDialog.open();
34
+ };
35
+ const openPreview = (fileValue) => {
36
+ previewModal.open({
37
+ value: fileValue
38
+ });
39
+ };
40
+ return {
41
+ fileDialog,
42
+ dropzone,
43
+ isLocked,
44
+ handleInputChange,
45
+ handleOpenFile,
46
+ openPreview
47
+ };
48
+ };
@@ -1,13 +1,11 @@
1
- import type { TemplateRef } from 'vue';
2
1
  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>) => {
2
+ import { type IUploadFieldContext } from './useFilePicker.js';
3
+ import { UploadState } from '#core/helpers/uploadCore';
4
+ export { UploadState };
5
+ /**
6
+ * Keeps the picked `File` itself as the field value (nothing is uploaded)
7
+ */
8
+ export declare const useFileState: (context: IUploadFieldContext<IUploadDropzoneProps, File>) => {
11
9
  currentState: import("vue").ComputedRef<UploadState.EMPTY | UploadState.SUCCESS>;
12
10
  isEmpty: import("vue").ComputedRef<boolean>;
13
11
  isSuccess: import("vue").ComputedRef<boolean>;
@@ -1,138 +1,58 @@
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) => {
1
+ import { useFilePicker } from "./useFilePicker.js";
2
+ import { UploadState, validateFile } from "#core/helpers/uploadCore";
3
+ import { useCoreMessages } from "#core/composables/useMessages";
4
+ import { computed, onScopeDispose, ref } from "#imports";
5
+ export { UploadState };
6
+ export const useFileState = (context) => {
7
+ const {
8
+ props,
9
+ emits,
10
+ onChange,
11
+ setErrors,
12
+ value
13
+ } = context;
14
+ const {
15
+ messages
16
+ } = useCoreMessages();
15
17
  const selectedFile = ref();
16
18
  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;
19
+ const currentState = computed(() => value.value ? UploadState.SUCCESS : UploadState.EMPTY);
20
+ const isEmpty = computed(() => currentState.value === UploadState.EMPTY);
21
+ const isSuccess = computed(() => currentState.value === UploadState.SUCCESS);
22
+ const cleanup = () => {
23
+ if (!currentObjectUrl.value) return;
24
+ URL.revokeObjectURL(currentObjectUrl.value);
25
+ currentObjectUrl.value = "";
54
26
  };
55
27
  const processFile = (file) => {
56
- if (!validateFile(file)) return;
28
+ const error = validateFile(file, props, messages.value.upload);
29
+ setErrors(error ?? "");
30
+ if (error) return;
57
31
  selectedFile.value = file;
58
32
  emits("change", file);
59
- if (currentObjectUrl.value) {
60
- URL.revokeObjectURL(currentObjectUrl.value);
61
- }
62
- const objectUrl = URL.createObjectURL(file);
63
- currentObjectUrl.value = objectUrl;
33
+ cleanup();
34
+ currentObjectUrl.value = URL.createObjectURL(file);
64
35
  value.value = file;
65
36
  emits("success", file);
66
37
  };
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,
38
+ const picker = useFilePicker(context, {
82
39
  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]);
40
+ canDrop: () => isEmpty.value,
41
+ onFiles: ([file]) => {
42
+ if (file) processFile(file);
101
43
  }
102
44
  });
103
- const handleOpenFile = () => {
104
- if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
105
- fileDialog.open();
106
- };
107
45
  const handleDeleteFile = () => {
108
- fileDialog.reset();
109
- if (currentObjectUrl.value) {
110
- URL.revokeObjectURL(currentObjectUrl.value);
111
- currentObjectUrl.value = "";
112
- }
46
+ picker.fileDialog.reset();
47
+ cleanup();
113
48
  selectedFile.value = void 0;
114
49
  onChange(void 0);
115
50
  emits("delete");
116
51
  };
117
52
  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
- }
53
+ if (selectedFile.value) processFile(selectedFile.value);
132
54
  };
133
- if (typeof window !== "undefined") {
134
- window.addEventListener("beforeunload", cleanup);
135
- }
55
+ onScopeDispose(cleanup);
136
56
  return {
137
57
  // State
138
58
  currentState,
@@ -141,13 +61,13 @@ export const useFileState = (props, emits, onChange, setErrors, value, acceptedF
141
61
  selectedFile,
142
62
  currentObjectUrl,
143
63
  // Upload utilities
144
- dropzone,
64
+ dropzone: picker.dropzone,
145
65
  // Handlers
146
- handleInputChange,
147
- handleOpenFile,
66
+ handleInputChange: picker.handleInputChange,
67
+ handleOpenFile: picker.handleOpenFile,
148
68
  handleDeleteFile,
149
69
  handleRetryUpload,
150
- handlePreview,
70
+ handlePreview: () => picker.openPreview(value.value),
151
71
  // Cleanup
152
72
  cleanup
153
73
  };
@@ -1,13 +1,12 @@
1
- import type { TemplateRef } from 'vue';
2
1
  import type { IUploadImageAutoProps } from '../InputUploadImageAuto/types.js';
2
+ import type { IUploadFieldContext } from './useFilePicker.js';
3
+ import { UploadState } from './useUploadState.js';
3
4
  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 useUploadImageState: (props: IUploadImageAutoProps, emits: any, onChange: (value: IFileValue | undefined) => void, setErrors: (error: string) => void, value: any, acceptedFileTypes: any, wrapperProps: any, dropzoneRef: TemplateRef<HTMLDivElement | undefined>) => {
5
+ export { UploadState };
6
+ /**
7
+ * `useUploadState` for images: defaults to `image/*` and checks `props.dimensions` before uploading
8
+ */
9
+ export declare const useUploadImageState: (context: IUploadFieldContext<IUploadImageAutoProps, IFileValue>) => {
11
10
  currentState: import("vue").ComputedRef<UploadState>;
12
11
  isEmpty: import("vue").ComputedRef<boolean>;
13
12
  isUploading: import("vue").ComputedRef<boolean>;