@ramathibodi/nuxt-commons 0.1.13 → 0.1.15

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 (59) hide show
  1. package/README.md +96 -96
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/Alert.vue +53 -53
  4. package/dist/runtime/components/BarcodeReader.vue +98 -98
  5. package/dist/runtime/components/ExportCSV.vue +55 -55
  6. package/dist/runtime/components/FileBtn.vue +62 -62
  7. package/dist/runtime/components/ImportCSV.vue +64 -64
  8. package/dist/runtime/components/SplitterPanel.vue +59 -59
  9. package/dist/runtime/components/TabsGroup.vue +32 -32
  10. package/dist/runtime/components/TextBarcode.vue +52 -52
  11. package/dist/runtime/components/dialog/Confirm.vue +100 -100
  12. package/dist/runtime/components/dialog/Index.vue +72 -72
  13. package/dist/runtime/components/dialog/Loading.vue +39 -39
  14. package/dist/runtime/components/document/TemplateBuilder.vue +203 -216
  15. package/dist/runtime/components/form/Birthdate.vue +216 -199
  16. package/dist/runtime/components/form/CodeEditor.vue +37 -37
  17. package/dist/runtime/components/form/Date.vue +163 -163
  18. package/dist/runtime/components/form/DateTime.vue +107 -107
  19. package/dist/runtime/components/form/Dialog.vue +138 -138
  20. package/dist/runtime/components/form/File.vue +187 -187
  21. package/dist/runtime/components/form/Hidden.vue +32 -32
  22. package/dist/runtime/components/form/Login.vue +131 -131
  23. package/dist/runtime/components/form/Pad.vue +217 -217
  24. package/dist/runtime/components/form/SignPad.vue +186 -186
  25. package/dist/runtime/components/form/Table.vue +266 -266
  26. package/dist/runtime/components/form/Time.vue +158 -158
  27. package/dist/runtime/components/form/images/Capture.vue +231 -0
  28. package/dist/runtime/components/form/images/Edit.vue +117 -143
  29. package/dist/runtime/components/label/Date.vue +29 -29
  30. package/dist/runtime/components/label/Field.vue +42 -29
  31. package/dist/runtime/components/label/FormatMoney.vue +29 -29
  32. package/dist/runtime/components/label/Mask.vue +38 -38
  33. package/dist/runtime/components/master/Autocomplete.vue +159 -159
  34. package/dist/runtime/components/master/Combobox.vue +84 -84
  35. package/dist/runtime/components/master/RadioGroup.vue +78 -78
  36. package/dist/runtime/components/master/Select.vue +82 -82
  37. package/dist/runtime/components/model/Pad.vue +122 -122
  38. package/dist/runtime/components/model/Table.vue +242 -240
  39. package/dist/runtime/components/model/iterator.vue +312 -312
  40. package/dist/runtime/components/pdf/Print.vue +63 -63
  41. package/dist/runtime/components/pdf/View.vue +104 -104
  42. package/dist/runtime/composables/graphqlModel.d.ts +4 -1
  43. package/dist/runtime/composables/graphqlModel.mjs +5 -5
  44. package/dist/runtime/composables/graphqlModelOperation.mjs +7 -4
  45. package/dist/runtime/labs/Calendar.vue +99 -99
  46. package/dist/runtime/labs/form/EditMobile.vue +152 -152
  47. package/dist/runtime/labs/form/TextFieldMask.vue +43 -43
  48. package/dist/runtime/types/alert.d.ts +11 -11
  49. package/dist/runtime/types/formDialog.d.ts +4 -4
  50. package/dist/runtime/types/graphqlOperation.d.ts +23 -23
  51. package/dist/runtime/types/menu.d.ts +25 -25
  52. package/dist/runtime/types/modules.d.ts +7 -7
  53. package/package.json +120 -118
  54. package/scripts/postInstall.cjs +70 -70
  55. package/templates/.codegen/codegen.ts +32 -32
  56. package/templates/.codegen/plugin-schema-object.js +154 -154
  57. package/dist/runtime/components/Camera.vue +0 -129
  58. package/dist/runtime/components/form/images/CameraCrop.vue +0 -58
  59. package/dist/runtime/components/form/images/Preview.vue +0 -48
@@ -1,138 +1,138 @@
1
- <script lang="ts" setup>
2
- import {computed, defineModel, ref, watch, watchEffect} from 'vue'
3
- import {cloneDeep, isEqual} from 'lodash-es'
4
- import type {FormDialogCallback} from '../../types/formDialog'
5
-
6
- interface Props {
7
- maxWidth?: number | string
8
- fullscreen?: boolean
9
- title?: string
10
- initialData?: object
11
- formData?: object
12
- saveCaption?: string
13
- cancelCaption?: string
14
- }
15
-
16
- const props = withDefaults(defineProps<Props>(), {
17
- saveCaption: 'บันทึก',
18
- cancelCaption: 'ยกเลิก',
19
- })
20
-
21
- const isShowing = defineModel<boolean>({ default: false })
22
- const isSaving = ref<boolean>(false)
23
- const formPadRef = ref()
24
- const formData = ref<object>({})
25
-
26
- const emit = defineEmits(['create', 'update'])
27
-
28
- function save() {
29
- if (formPadRef.value.isValid) {
30
- isSaving.value = true
31
- emit((isCreating.value) ? 'create' : 'update', cloneDeep(formData.value), callback)
32
- }
33
- }
34
-
35
- function cancel() {
36
- isShowing.value = false
37
- }
38
-
39
- const callback: FormDialogCallback = {
40
- done: function () {
41
- isSaving.value = false
42
- isShowing.value = false
43
- },
44
- error: function () {
45
- isSaving.value = false
46
- },
47
- }
48
-
49
- const isDataChange = computed(() => {
50
- return !((isCreating.value) ? isEqual(formData.value, createOriginalValue.value) : isEqual(formData.value, props.formData))
51
- })
52
-
53
- const isCreating = computed(() => {
54
- return !props.formData
55
- })
56
-
57
- const createOriginalValue = computed(() => {
58
- return Object.assign({}, props.initialData)
59
- })
60
-
61
- const loadFormData = () => {
62
- if (props.formData) {
63
- formData.value = cloneDeep(props.formData)
64
- }
65
- else {
66
- formData.value = Object.assign({}, props.initialData)
67
- }
68
- }
69
-
70
- watchEffect(loadFormData)
71
-
72
- watch(() => isShowing.value, (newValue) => {
73
- if (!newValue) formPadRef.value.reset()
74
- else loadFormData()
75
- })
76
- </script>
77
-
78
- <template>
79
- <v-dialog
80
- v-model="isShowing"
81
- :fullscreen="fullscreen"
82
- :max-width="maxWidth"
83
- persistent
84
- scrollable
85
- >
86
- <VCard>
87
- <VToolbar>
88
- <VToolbarTitle>
89
- <slot name="title">
90
- {{ (isCreating) ? "New" : "Edit" }} {{ title }}
91
- </slot>
92
- </VToolbarTitle>
93
- <VSpacer />
94
- <VToolbarItems>
95
- <VBtn
96
- icon="mdi mdi-close"
97
- @click="cancel"
98
- />
99
- </VToolbarItems>
100
- </VToolbar>
101
- <VCardText>
102
- <form-pad
103
- ref="formPadRef"
104
- v-model="formData"
105
- isolated
106
- >
107
- <template #default="slotData">
108
- <slot
109
- v-bind="slotData"
110
- :is-creating="isCreating"
111
- :is-data-change="isDataChange"
112
- />
113
- </template>
114
- </form-pad>
115
- </VCardText>
116
- <VCardActions>
117
- <VSpacer />
118
- <VBtn
119
- color="primary"
120
- variant="flat"
121
- :loading="isSaving"
122
- :disabled="!isDataChange"
123
- @click="save"
124
- >
125
- {{ saveCaption }}
126
- </VBtn>
127
- <VBtn
128
- color="error"
129
- variant="flat"
130
- :disabled="isSaving"
131
- @click="cancel"
132
- >
133
- {{ cancelCaption }}
134
- </VBtn>
135
- </VCardActions>
136
- </VCard>
137
- </v-dialog>
138
- </template>
1
+ <script lang="ts" setup>
2
+ import {computed, defineModel, ref, watch, watchEffect} from 'vue'
3
+ import {cloneDeep, isEqual} from 'lodash-es'
4
+ import type {FormDialogCallback} from '../../types/formDialog'
5
+
6
+ interface Props {
7
+ maxWidth?: number | string
8
+ fullscreen?: boolean
9
+ title?: string
10
+ initialData?: object
11
+ formData?: object
12
+ saveCaption?: string
13
+ cancelCaption?: string
14
+ }
15
+
16
+ const props = withDefaults(defineProps<Props>(), {
17
+ saveCaption: 'บันทึก',
18
+ cancelCaption: 'ยกเลิก',
19
+ })
20
+
21
+ const isShowing = defineModel<boolean>({ default: false })
22
+ const isSaving = ref<boolean>(false)
23
+ const formPadRef = ref()
24
+ const formData = ref<object>({})
25
+
26
+ const emit = defineEmits(['create', 'update'])
27
+
28
+ function save() {
29
+ if (formPadRef.value.isValid) {
30
+ isSaving.value = true
31
+ emit((isCreating.value) ? 'create' : 'update', cloneDeep(formData.value), callback)
32
+ }
33
+ }
34
+
35
+ function cancel() {
36
+ isShowing.value = false
37
+ }
38
+
39
+ const callback: FormDialogCallback = {
40
+ done: function () {
41
+ isSaving.value = false
42
+ isShowing.value = false
43
+ },
44
+ error: function () {
45
+ isSaving.value = false
46
+ },
47
+ }
48
+
49
+ const isDataChange = computed(() => {
50
+ return !((isCreating.value) ? isEqual(formData.value, createOriginalValue.value) : isEqual(formData.value, props.formData))
51
+ })
52
+
53
+ const isCreating = computed(() => {
54
+ return !props.formData
55
+ })
56
+
57
+ const createOriginalValue = computed(() => {
58
+ return Object.assign({}, props.initialData)
59
+ })
60
+
61
+ const loadFormData = () => {
62
+ if (props.formData) {
63
+ formData.value = cloneDeep(props.formData)
64
+ }
65
+ else {
66
+ formData.value = Object.assign({}, props.initialData)
67
+ }
68
+ }
69
+
70
+ watchEffect(loadFormData)
71
+
72
+ watch(() => isShowing.value, (newValue) => {
73
+ if (!newValue) formPadRef.value.reset()
74
+ else loadFormData()
75
+ })
76
+ </script>
77
+
78
+ <template>
79
+ <v-dialog
80
+ v-model="isShowing"
81
+ :fullscreen="fullscreen"
82
+ :max-width="maxWidth"
83
+ persistent
84
+ scrollable
85
+ >
86
+ <VCard>
87
+ <VToolbar>
88
+ <VToolbarTitle>
89
+ <slot name="title">
90
+ {{ (isCreating) ? "New" : "Edit" }} {{ title }}
91
+ </slot>
92
+ </VToolbarTitle>
93
+ <VSpacer />
94
+ <VToolbarItems>
95
+ <VBtn
96
+ icon="mdi mdi-close"
97
+ @click="cancel"
98
+ />
99
+ </VToolbarItems>
100
+ </VToolbar>
101
+ <VCardText>
102
+ <form-pad
103
+ ref="formPadRef"
104
+ v-model="formData"
105
+ isolated
106
+ >
107
+ <template #default="slotData">
108
+ <slot
109
+ v-bind="slotData"
110
+ :is-creating="isCreating"
111
+ :is-data-change="isDataChange"
112
+ />
113
+ </template>
114
+ </form-pad>
115
+ </VCardText>
116
+ <VCardActions>
117
+ <VSpacer />
118
+ <VBtn
119
+ color="primary"
120
+ variant="flat"
121
+ :loading="isSaving"
122
+ :disabled="!isDataChange"
123
+ @click="save"
124
+ >
125
+ {{ saveCaption }}
126
+ </VBtn>
127
+ <VBtn
128
+ color="error"
129
+ variant="flat"
130
+ :disabled="isSaving"
131
+ @click="cancel"
132
+ >
133
+ {{ cancelCaption }}
134
+ </VBtn>
135
+ </VCardActions>
136
+ </VCard>
137
+ </v-dialog>
138
+ </template>
@@ -1,187 +1,187 @@
1
- <script lang="ts" setup>
2
- import { uniqWith, isEqual } from 'lodash-es'
3
- import { ref, watch } from 'vue'
4
- import { VTextField } from 'vuetify/components/VTextField'
5
- import { useAlert } from '../../composables/alert'
6
-
7
- const alert = useAlert()
8
-
9
- interface Base64String {
10
- base64String?: string
11
- fileName: string
12
- id?: number
13
- }
14
-
15
- interface Props extends /* @vue-ignore */ InstanceType<typeof VTextField['$props']> {
16
- accept?: string
17
- multiple?: boolean
18
- maxSize?: number
19
- modelValue?: Base64String[]
20
- }
21
-
22
- const props = withDefaults(defineProps<Props>(), {
23
- accept: '*',
24
- multiple: false,
25
- maxSize: 5,
26
- })
27
-
28
- const emit = defineEmits<{
29
- (e: 'update:modelValue', value: Base64String[]): void
30
- }>()
31
-
32
- const allFiles = ref<File[]>([])
33
- const allAssets = ref<Base64String[]>([])
34
- const combinedBase64String = ref<Base64String[]>([])
35
- const fileInput = ref()
36
-
37
- function openWindowUpload() {
38
- if (props.multiple || (!allFiles.value?.length && !allAssets.value?.length)) {
39
- fileInput.value?.click()
40
- }
41
- }
42
-
43
- function addFiles(files: File | File[]) {
44
- if (Array.isArray(files)) allFiles.value?.push(...files)
45
- else allFiles.value?.push(files)
46
- }
47
-
48
- function removeFileByIndex(i: number | string) {
49
- const index = Number(i)
50
- if (Array.isArray(allFiles.value) && allFiles.value.length) {
51
- if (index >= 0 && index < allFiles.value.length) allFiles.value.splice(index, 1)
52
- }
53
- }
54
-
55
- function removeAssetByIndex(i: number | string) {
56
- const index = Number(i)
57
- if (Array.isArray(allAssets.value) && allAssets.value.length) {
58
- if (index >= 0 && index < allAssets.value.length) allAssets.value.splice(index, 1)
59
- }
60
- }
61
-
62
- function fileToBase64(file: File) {
63
- const maxSize = props.maxSize * 1048576
64
-
65
- return new Promise<Base64String>((resolve, reject) => {
66
- if (file.size > maxSize) reject (`File (${file.name}) size exceeds the ${props.maxSize} MB limit.`)
67
-
68
- const reader = new FileReader()
69
- reader.onload = function (event) {
70
- resolve({ fileName: file.name, base64String: event.target?.result as string })
71
- }
72
- reader.onerror = function (error) {
73
- reject(error)
74
- }
75
- reader.readAsDataURL(file)
76
- })
77
- }
78
-
79
- function base64ToFile(base64Data: string, filename: string) {
80
- // Extract content type and base64 payload from the Base64 string
81
- const matchResult = base64Data.match(/data:([^;]*);base64,(.*)/)
82
- if (matchResult === null) {
83
- return undefined
84
- }
85
- const [contentType, base64Payload] = matchResult.slice(1)
86
-
87
- // Convert base64 to a Uint8Array
88
- const binaryStr = atob(base64Payload)
89
- const len = binaryStr.length
90
- const bytes = new Uint8Array(len)
91
- for (let i = 0; i < len; i++) {
92
- bytes[i] = binaryStr.charCodeAt(i)
93
- }
94
-
95
- return new File([bytes], filename, { type: contentType })
96
- }
97
-
98
- watch(
99
- () => props.modelValue,
100
- () => {
101
- if (props.modelValue && Array.isArray(props.modelValue)) {
102
- allAssets.value = props.modelValue.filter((item: Base64String) => item.id !== undefined)
103
- allFiles.value = props.modelValue.filter((item: Base64String) => item.id === undefined && item.base64String !== undefined).map((base64: Base64String) => base64ToFile(base64.base64String as string, base64.fileName)).filter((item: File | undefined) => item !== undefined) as File[]
104
- }
105
- else {
106
- allAssets.value = []
107
- allFiles.value = []
108
- }
109
- },
110
- { deep: true, immediate: true },
111
- )
112
-
113
- watch([allAssets, allFiles], () => {
114
- if (allFiles.value && allFiles.value?.length) {
115
- const base64Promises = allFiles.value?.map(file => fileToBase64(file))
116
-
117
- Promise.all(base64Promises).then((base64Strings) => {
118
- combinedBase64String.value = [...allAssets.value, ...base64Strings]
119
- }).catch((error) => {
120
- alert?.addAlert({ message: error, alertType: 'error' })
121
- allFiles.value = []
122
- })
123
- }
124
- else {
125
- combinedBase64String.value = [...allAssets.value]
126
- }
127
- }, { deep: true, immediate: true })
128
-
129
- watch(combinedBase64String, (newValue, oldValue) => {
130
- if (!isEqual(newValue, oldValue)) {
131
- emit('update:modelValue', uniqWith(newValue, isEqual))
132
- }
133
- }, { deep: true })
134
- </script>
135
-
136
- <template>
137
- <v-text-field
138
- v-bind="$attrs"
139
- label="Upload files"
140
- readonly
141
- :dirty="combinedBase64String.length>0"
142
- v-on="(combinedBase64String.length>0) ? {} : { click: openWindowUpload }"
143
- >
144
- <template #default>
145
- <v-chip
146
- v-for="(asset, index) in allAssets"
147
- :key="asset"
148
- color="green"
149
- variant="flat"
150
- closable
151
- @click:close="removeAssetByIndex(index)"
152
- >
153
- {{ asset.fileName }}
154
- </v-chip>
155
- <v-chip
156
- v-for="(file, index) in allFiles"
157
- :key="file"
158
- color="primary"
159
- variant="flat"
160
- closable
161
- @click:close="removeFileByIndex(index)"
162
- >
163
- {{ file.name }}
164
- </v-chip>
165
- </template>
166
-
167
- <template
168
- v-if="combinedBase64String.length>0 && props.multiple"
169
- #append-inner
170
- >
171
- <VBtn
172
- variant="text"
173
- :icon="true"
174
- @click="openWindowUpload"
175
- >
176
- <v-icon>mdi mdi-plus</v-icon>
177
- </VBtn>
178
- </template>
179
- </v-text-field>
180
- <v-file-input
181
- ref="fileInput"
182
- :accept="props.accept"
183
- :multiple="props.multiple"
184
- style="display: none"
185
- @update:model-value="addFiles"
186
- />
187
- </template>
1
+ <script lang="ts" setup>
2
+ import { uniqWith, isEqual } from 'lodash-es'
3
+ import { ref, watch } from 'vue'
4
+ import { VTextField } from 'vuetify/components/VTextField'
5
+ import { useAlert } from '../../composables/alert'
6
+
7
+ const alert = useAlert()
8
+
9
+ interface Base64String {
10
+ base64String?: string
11
+ fileName: string
12
+ id?: number
13
+ }
14
+
15
+ interface Props extends /* @vue-ignore */ InstanceType<typeof VTextField['$props']> {
16
+ accept?: string
17
+ multiple?: boolean
18
+ maxSize?: number
19
+ modelValue?: Base64String[]
20
+ }
21
+
22
+ const props = withDefaults(defineProps<Props>(), {
23
+ accept: '*',
24
+ multiple: false,
25
+ maxSize: 5,
26
+ })
27
+
28
+ const emit = defineEmits<{
29
+ (e: 'update:modelValue', value: Base64String[]): void
30
+ }>()
31
+
32
+ const allFiles = ref<File[]>([])
33
+ const allAssets = ref<Base64String[]>([])
34
+ const combinedBase64String = ref<Base64String[]>([])
35
+ const fileInput = ref()
36
+
37
+ function openWindowUpload() {
38
+ if (props.multiple || (!allFiles.value?.length && !allAssets.value?.length)) {
39
+ fileInput.value?.click()
40
+ }
41
+ }
42
+
43
+ function addFiles(files: File | File[]) {
44
+ if (Array.isArray(files)) allFiles.value?.push(...files)
45
+ else allFiles.value?.push(files)
46
+ }
47
+
48
+ function removeFileByIndex(i: number | string) {
49
+ const index = Number(i)
50
+ if (Array.isArray(allFiles.value) && allFiles.value.length) {
51
+ if (index >= 0 && index < allFiles.value.length) allFiles.value.splice(index, 1)
52
+ }
53
+ }
54
+
55
+ function removeAssetByIndex(i: number | string) {
56
+ const index = Number(i)
57
+ if (Array.isArray(allAssets.value) && allAssets.value.length) {
58
+ if (index >= 0 && index < allAssets.value.length) allAssets.value.splice(index, 1)
59
+ }
60
+ }
61
+
62
+ function fileToBase64(file: File) {
63
+ const maxSize = props.maxSize * 1048576
64
+
65
+ return new Promise<Base64String>((resolve, reject) => {
66
+ if (file.size > maxSize) reject (`File (${file.name}) size exceeds the ${props.maxSize} MB limit.`)
67
+
68
+ const reader = new FileReader()
69
+ reader.onload = function (event) {
70
+ resolve({ fileName: file.name, base64String: event.target?.result as string })
71
+ }
72
+ reader.onerror = function (error) {
73
+ reject(error)
74
+ }
75
+ reader.readAsDataURL(file)
76
+ })
77
+ }
78
+
79
+ function base64ToFile(base64Data: string, filename: string) {
80
+ // Extract content type and base64 payload from the Base64 string
81
+ const matchResult = base64Data.match(/data:([^;]*);base64,(.*)/)
82
+ if (matchResult === null) {
83
+ return undefined
84
+ }
85
+ const [contentType, base64Payload] = matchResult.slice(1)
86
+
87
+ // Convert base64 to a Uint8Array
88
+ const binaryStr = atob(base64Payload)
89
+ const len = binaryStr.length
90
+ const bytes = new Uint8Array(len)
91
+ for (let i = 0; i < len; i++) {
92
+ bytes[i] = binaryStr.charCodeAt(i)
93
+ }
94
+
95
+ return new File([bytes], filename, { type: contentType })
96
+ }
97
+
98
+ watch(
99
+ () => props.modelValue,
100
+ () => {
101
+ if (props.modelValue && Array.isArray(props.modelValue)) {
102
+ allAssets.value = props.modelValue.filter((item: Base64String) => item.id !== undefined)
103
+ allFiles.value = props.modelValue.filter((item: Base64String) => item.id === undefined && item.base64String !== undefined).map((base64: Base64String) => base64ToFile(base64.base64String as string, base64.fileName)).filter((item: File | undefined) => item !== undefined) as File[]
104
+ }
105
+ else {
106
+ allAssets.value = []
107
+ allFiles.value = []
108
+ }
109
+ },
110
+ { deep: true, immediate: true },
111
+ )
112
+
113
+ watch([allAssets, allFiles], () => {
114
+ if (allFiles.value && allFiles.value?.length) {
115
+ const base64Promises = allFiles.value?.map(file => fileToBase64(file))
116
+
117
+ Promise.all(base64Promises).then((base64Strings) => {
118
+ combinedBase64String.value = [...allAssets.value, ...base64Strings]
119
+ }).catch((error) => {
120
+ alert?.addAlert({ message: error, alertType: 'error' })
121
+ allFiles.value = []
122
+ })
123
+ }
124
+ else {
125
+ combinedBase64String.value = [...allAssets.value]
126
+ }
127
+ }, { deep: true, immediate: true })
128
+
129
+ watch(combinedBase64String, (newValue, oldValue) => {
130
+ if (!isEqual(newValue, oldValue)) {
131
+ emit('update:modelValue', uniqWith(newValue, isEqual))
132
+ }
133
+ }, { deep: true })
134
+ </script>
135
+
136
+ <template>
137
+ <v-text-field
138
+ v-bind="$attrs"
139
+ label="Upload files"
140
+ readonly
141
+ :dirty="combinedBase64String.length>0"
142
+ v-on="(combinedBase64String.length>0) ? {} : { click: openWindowUpload }"
143
+ >
144
+ <template #default>
145
+ <v-chip
146
+ v-for="(asset, index) in allAssets"
147
+ :key="asset"
148
+ color="green"
149
+ variant="flat"
150
+ closable
151
+ @click:close="removeAssetByIndex(index)"
152
+ >
153
+ {{ asset.fileName }}
154
+ </v-chip>
155
+ <v-chip
156
+ v-for="(file, index) in allFiles"
157
+ :key="file"
158
+ color="primary"
159
+ variant="flat"
160
+ closable
161
+ @click:close="removeFileByIndex(index)"
162
+ >
163
+ {{ file.name }}
164
+ </v-chip>
165
+ </template>
166
+
167
+ <template
168
+ v-if="combinedBase64String.length>0 && props.multiple"
169
+ #append-inner
170
+ >
171
+ <VBtn
172
+ variant="text"
173
+ :icon="true"
174
+ @click="openWindowUpload"
175
+ >
176
+ <v-icon>mdi mdi-plus</v-icon>
177
+ </VBtn>
178
+ </template>
179
+ </v-text-field>
180
+ <v-file-input
181
+ ref="fileInput"
182
+ :accept="props.accept"
183
+ :multiple="props.multiple"
184
+ style="display: none"
185
+ @update:model-value="addFiles"
186
+ />
187
+ </template>