@finema/core 3.2.1 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/Form/Fields.vue +10 -1
- package/dist/runtime/components/Form/InputCheckboxGroup/index.d.vue.ts +8 -0
- package/dist/runtime/components/Form/InputCheckboxGroup/index.vue +59 -0
- package/dist/runtime/components/Form/InputCheckboxGroup/index.vue.d.ts +8 -0
- package/dist/runtime/components/Form/InputCheckboxGroup/types.d.ts +21 -0
- package/dist/runtime/components/Form/InputCheckboxGroup/types.js +0 -0
- package/dist/runtime/components/Form/InputDateTime/index.vue +1 -0
- package/dist/runtime/components/Form/InputDateTimeRange/index.vue +1 -0
- package/dist/runtime/components/Form/InputSelect/index.vue +9 -1
- package/dist/runtime/components/Form/InputSelectMultiple/index.vue +20 -1
- package/dist/runtime/components/Form/InputTags/index.d.vue.ts +2 -0
- package/dist/runtime/components/Form/InputTags/index.vue +177 -23
- package/dist/runtime/components/Form/InputTags/index.vue.d.ts +2 -0
- package/dist/runtime/components/Form/InputTags/types.d.ts +5 -3
- package/dist/runtime/components/Form/InputUploadImageAuto/index.d.vue.ts +26 -0
- package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +131 -0
- package/dist/runtime/components/Form/InputUploadImageAuto/index.vue.d.ts +26 -0
- package/dist/runtime/components/Form/InputUploadImageAuto/types.d.ts +37 -0
- package/dist/runtime/components/Form/InputUploadImageAuto/types.js +0 -0
- package/dist/runtime/components/Form/InputWYSIWYG/types.d.ts +15 -1
- package/dist/runtime/components/Form/fileState/useUploadImageState.d.ts +25 -0
- package/dist/runtime/components/Form/fileState/useUploadImageState.js +257 -0
- package/dist/runtime/components/Form/types.d.ts +4 -1
- package/dist/runtime/components/Form/types.js +1 -0
- package/dist/runtime/components/Table/ColumnText.d.vue.ts +5 -1
- package/dist/runtime/components/Table/ColumnText.vue.d.ts +5 -1
- package/dist/runtime/styles/main.css +1 -1
- package/dist/runtime/theme/selectMenu.js +2 -2
- package/dist/runtime/theme/table.js +1 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import FormInputNumber from "./InputNumber/index.vue";
|
|
|
28
28
|
import FormInputCurrency from "./InputCurrency/index.vue";
|
|
29
29
|
import FormInputToggle from "./InputToggle/index.vue";
|
|
30
30
|
import FormInputCheckbox from "./InputCheckbox/index.vue";
|
|
31
|
+
import FormInputCheckboxGroup from "./InputCheckboxGroup/index.vue";
|
|
31
32
|
import FormInputSelect from "./InputSelect/index.vue";
|
|
32
33
|
import FormInputSelectMultiple from "./InputSelectMultiple/index.vue";
|
|
33
34
|
import FormInputComponent from "./InputComponent/index.vue";
|
|
@@ -39,6 +40,7 @@ import FormInputDateTimeRange from "./InputDateTimeRange/index.vue";
|
|
|
39
40
|
import FormInputUploadDropzoneAuto from "./InputUploadDropzoneAuto/index.vue";
|
|
40
41
|
import FormInputUploadDropzoneAutoMultiple from "./InputUploadDropzoneAutoMultiple/index.vue";
|
|
41
42
|
import FormInputUploadDropzone from "./InputUploadDropzone/index.vue";
|
|
43
|
+
import FormInputUploadImageAuto from "./InputUploadImageAuto/index.vue";
|
|
42
44
|
import FormInputTag from "./InputTags/index.vue";
|
|
43
45
|
import FormInputWYSIWYG from "./InputWYSIWYG/index.vue";
|
|
44
46
|
import { INPUT_TYPES } from "#core/components/Form/types";
|
|
@@ -72,6 +74,10 @@ const componentMap = {
|
|
|
72
74
|
component: FormInputCheckbox,
|
|
73
75
|
props: {}
|
|
74
76
|
},
|
|
77
|
+
[INPUT_TYPES.CHECKBOX_GROUP]: {
|
|
78
|
+
component: FormInputCheckboxGroup,
|
|
79
|
+
props: {}
|
|
80
|
+
},
|
|
75
81
|
[INPUT_TYPES.SELECT]: {
|
|
76
82
|
component: FormInputSelect,
|
|
77
83
|
props: {}
|
|
@@ -142,7 +148,10 @@ const componentMap = {
|
|
|
142
148
|
},
|
|
143
149
|
[INPUT_TYPES.UPLOAD_FILE_CLASSIC]: void 0,
|
|
144
150
|
[INPUT_TYPES.UPLOAD_FILE_CLASSIC_AUTO]: void 0,
|
|
145
|
-
[INPUT_TYPES.UPLOAD_IMAGE_AUTO]:
|
|
151
|
+
[INPUT_TYPES.UPLOAD_IMAGE_AUTO]: {
|
|
152
|
+
component: FormInputUploadImageAuto,
|
|
153
|
+
props: {}
|
|
154
|
+
},
|
|
146
155
|
[INPUT_TYPES.UPLOAD_DROPZONE]: {
|
|
147
156
|
component: FormInputUploadDropzone,
|
|
148
157
|
props: {}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ICheckboxGroupFieldProps } from '#core/components/Form/InputCheckboxGroup/types';
|
|
2
|
+
declare const __VLS_export: import("vue").DefineComponent<ICheckboxGroupFieldProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
3
|
+
change: (...args: any[]) => void;
|
|
4
|
+
}, string, import("vue").PublicProps, Readonly<ICheckboxGroupFieldProps> & Readonly<{
|
|
5
|
+
onChange?: ((...args: any[]) => any) | undefined;
|
|
6
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FieldWrapper
|
|
3
|
+
v-bind="wrapperProps"
|
|
4
|
+
label=""
|
|
5
|
+
description=""
|
|
6
|
+
>
|
|
7
|
+
<CheckboxGroup
|
|
8
|
+
:model-value="value"
|
|
9
|
+
:disabled="wrapperProps.disabled"
|
|
10
|
+
:name="name"
|
|
11
|
+
:label="label"
|
|
12
|
+
:items="options"
|
|
13
|
+
:description="description"
|
|
14
|
+
:required="required"
|
|
15
|
+
:variant="variant"
|
|
16
|
+
:indicator="indicator"
|
|
17
|
+
:value-key="valueKey"
|
|
18
|
+
:orientation="orientation"
|
|
19
|
+
:ui="ui"
|
|
20
|
+
@update:model-value="onChange"
|
|
21
|
+
/>
|
|
22
|
+
</FieldWrapper>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup>
|
|
26
|
+
import { useFieldHOC } from "#core/composables/useForm";
|
|
27
|
+
import FieldWrapper from "#core/components/Form/FieldWrapper.vue";
|
|
28
|
+
const emits = defineEmits(["change"]);
|
|
29
|
+
const props = defineProps({
|
|
30
|
+
label: { type: String, required: false },
|
|
31
|
+
description: { type: String, required: false },
|
|
32
|
+
variant: { type: String, required: false },
|
|
33
|
+
orientation: { type: String, required: false },
|
|
34
|
+
indicator: { type: String, required: false },
|
|
35
|
+
options: { type: Array, required: true },
|
|
36
|
+
valueKey: { type: String, required: false },
|
|
37
|
+
form: { type: Object, required: false },
|
|
38
|
+
name: { type: String, required: true },
|
|
39
|
+
errorMessage: { type: String, required: false },
|
|
40
|
+
hint: { type: String, required: false },
|
|
41
|
+
rules: { type: null, required: false },
|
|
42
|
+
autoFocus: { type: Boolean, required: false },
|
|
43
|
+
placeholder: { type: String, required: false },
|
|
44
|
+
disabled: { type: Boolean, required: false },
|
|
45
|
+
readonly: { type: Boolean, required: false },
|
|
46
|
+
required: { type: Boolean, required: false },
|
|
47
|
+
help: { type: String, required: false },
|
|
48
|
+
ui: { type: null, required: false }
|
|
49
|
+
});
|
|
50
|
+
const {
|
|
51
|
+
value,
|
|
52
|
+
wrapperProps,
|
|
53
|
+
handleChange
|
|
54
|
+
} = useFieldHOC(props);
|
|
55
|
+
const onChange = (value2) => {
|
|
56
|
+
handleChange(value2);
|
|
57
|
+
emits("change", value2);
|
|
58
|
+
};
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ICheckboxGroupFieldProps } from '#core/components/Form/InputCheckboxGroup/types';
|
|
2
|
+
declare const __VLS_export: import("vue").DefineComponent<ICheckboxGroupFieldProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
3
|
+
change: (...args: any[]) => void;
|
|
4
|
+
}, string, import("vue").PublicProps, Readonly<ICheckboxGroupFieldProps> & Readonly<{
|
|
5
|
+
onChange?: ((...args: any[]) => any) | undefined;
|
|
6
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IFieldProps, IFormFieldBase, INPUT_TYPES } from '#core/components/Form/types';
|
|
2
|
+
import type { IOption } from '#core/types/common';
|
|
3
|
+
export type CheckboxOption = IOption & {
|
|
4
|
+
label?: string;
|
|
5
|
+
value?: any;
|
|
6
|
+
description?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
class?: any;
|
|
9
|
+
};
|
|
10
|
+
export interface ICheckboxGroupFieldProps extends IFieldProps {
|
|
11
|
+
label?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
variant?: 'list' | 'card' | 'table';
|
|
14
|
+
orientation?: 'horizontal' | 'vertical';
|
|
15
|
+
indicator?: 'start' | 'end' | 'hidden';
|
|
16
|
+
options: CheckboxOption[];
|
|
17
|
+
valueKey?: string;
|
|
18
|
+
}
|
|
19
|
+
export type ICheckboxGroupField = IFormFieldBase<INPUT_TYPES.CHECKBOX_GROUP, ICheckboxGroupFieldProps, {
|
|
20
|
+
change?: (value: any) => void;
|
|
21
|
+
}>;
|
|
File without changes
|
|
@@ -17,7 +17,15 @@
|
|
|
17
17
|
@update:modelValue="onChange"
|
|
18
18
|
@update:searchTerm="onSearch"
|
|
19
19
|
>
|
|
20
|
-
<template #default="{ modelValue }">
|
|
20
|
+
<template #default="{ modelValue, ui: selectMenuUi }">
|
|
21
|
+
<Chip
|
|
22
|
+
v-if="options.find((item) => item.value === modelValue)?.chip"
|
|
23
|
+
v-bind="options.find((item) => item.value === modelValue)?.chip"
|
|
24
|
+
inset
|
|
25
|
+
standalone
|
|
26
|
+
:size="selectMenuUi.itemLeadingChipSize()"
|
|
27
|
+
:class="selectMenuUi.itemLeadingChip()"
|
|
28
|
+
/>
|
|
21
29
|
<div
|
|
22
30
|
v-if="value"
|
|
23
31
|
:class="theme.selectedWrapper({
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
@update:model-value="onChange"
|
|
18
18
|
@update:searchTerm="onSearch"
|
|
19
19
|
>
|
|
20
|
-
<template #default="{ modelValue }">
|
|
20
|
+
<template #default="{ modelValue, ui: selectMenuUi }">
|
|
21
21
|
<div
|
|
22
22
|
v-if="!ArrayHelper.isEmpty(value)"
|
|
23
23
|
:class="theme.tagsWrapper({
|
|
@@ -31,6 +31,25 @@
|
|
|
31
31
|
class: [ui?.tagsItem]
|
|
32
32
|
})"
|
|
33
33
|
>
|
|
34
|
+
<Chip
|
|
35
|
+
v-if="options.find((item) => item.value === _value)?.chip"
|
|
36
|
+
v-bind="options.find((item) => item.value === _value)?.chip"
|
|
37
|
+
inset
|
|
38
|
+
standalone
|
|
39
|
+
:size="selectMenuUi.itemLeadingChipSize()"
|
|
40
|
+
class="p-1"
|
|
41
|
+
/>
|
|
42
|
+
<Icon
|
|
43
|
+
v-if="options.find((item) => item.value === _value)?.icon"
|
|
44
|
+
:name="options.find((item) => item.value === _value)?.icon"
|
|
45
|
+
class="size-4"
|
|
46
|
+
/>
|
|
47
|
+
<Avatar
|
|
48
|
+
v-if="options.find((item) => item.value === _value)?.avatar"
|
|
49
|
+
v-bind="options.find((item) => item.value === _value)?.avatar"
|
|
50
|
+
:class="selectMenuUi.itemLeadingAvatar()"
|
|
51
|
+
size="2xs"
|
|
52
|
+
/>
|
|
34
53
|
<div
|
|
35
54
|
:class="theme.tagsItemText({
|
|
36
55
|
class: [ui?.tagsItemText]
|
|
@@ -2,10 +2,12 @@ import type { ITagsFieldProps } from '#core/components/Form/InputTags/types';
|
|
|
2
2
|
declare const __VLS_export: import("vue").DefineComponent<ITagsFieldProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
3
3
|
add: (...args: any[]) => void;
|
|
4
4
|
change: (...args: any[]) => void;
|
|
5
|
+
selected: (...args: any[]) => void;
|
|
5
6
|
remove: (...args: any[]) => void;
|
|
6
7
|
}, string, import("vue").PublicProps, Readonly<ITagsFieldProps> & Readonly<{
|
|
7
8
|
onAdd?: ((...args: any[]) => any) | undefined;
|
|
8
9
|
onChange?: ((...args: any[]) => any) | undefined;
|
|
10
|
+
onSelected?: ((...args: any[]) => any) | undefined;
|
|
9
11
|
onRemove?: ((...args: any[]) => any) | undefined;
|
|
10
12
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
11
13
|
declare const _default: typeof __VLS_export;
|
|
@@ -1,43 +1,82 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<FieldWrapper v-bind="wrapperProps">
|
|
3
|
-
<
|
|
4
|
-
:
|
|
5
|
-
:
|
|
6
|
-
:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
3
|
+
<Popover
|
|
4
|
+
v-model:open="showSuggestions"
|
|
5
|
+
:dismissible="false"
|
|
6
|
+
:ui="{ content: 'w-(--reka-popper-anchor-width)' }"
|
|
7
|
+
>
|
|
8
|
+
<template #anchor>
|
|
9
|
+
<InputTags
|
|
10
|
+
:model-value="value"
|
|
11
|
+
:disabled="wrapperProps.disabled"
|
|
12
|
+
:leading-icon="leadingIcon"
|
|
13
|
+
:max-length="maxLength"
|
|
14
|
+
:variant="variant"
|
|
15
|
+
:delete-icon="deleteIcon"
|
|
16
|
+
:size="size"
|
|
17
|
+
:trailing-icon="trailingIcon"
|
|
18
|
+
:loading="loading"
|
|
19
|
+
:loading-icon="loadingIcon"
|
|
20
|
+
:name="name"
|
|
21
|
+
:placeholder="wrapperProps.placeholder"
|
|
22
|
+
:autofocus="!!autoFocus"
|
|
23
|
+
:icon="icon"
|
|
24
|
+
:readonly="readonly"
|
|
25
|
+
:ui="ui"
|
|
26
|
+
@update:model-value="onChange"
|
|
27
|
+
@addTag="onAdd"
|
|
28
|
+
@removeTag="onRemove"
|
|
29
|
+
@focus="onFocus"
|
|
30
|
+
@blur="onBlur"
|
|
31
|
+
@keydown="onKeydown"
|
|
32
|
+
@input="onInput"
|
|
33
|
+
/>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<template #content>
|
|
37
|
+
<div
|
|
38
|
+
v-if="showSuggestions && filteredSuggestions.length > 0"
|
|
39
|
+
ref="suggestionsContainerRef"
|
|
40
|
+
:class="theme.suggestionsContainer()"
|
|
41
|
+
>
|
|
42
|
+
<div
|
|
43
|
+
v-for="(suggestion, index) in filteredSuggestions"
|
|
44
|
+
:key="suggestion"
|
|
45
|
+
:ref="(el) => setSuggestionItemRef(el, index)"
|
|
46
|
+
:class="[
|
|
47
|
+
theme.suggestionItem(),
|
|
48
|
+
{
|
|
49
|
+
[theme.suggestionItemActive()]: index === selectedSuggestionIndex
|
|
50
|
+
}
|
|
51
|
+
]"
|
|
52
|
+
@mousedown.prevent="selectSuggestion(suggestion, index)"
|
|
53
|
+
@mouseenter="selectedSuggestionIndex = index"
|
|
54
|
+
>
|
|
55
|
+
{{ suggestion }}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
</Popover>
|
|
24
60
|
</FieldWrapper>
|
|
25
61
|
</template>
|
|
26
62
|
|
|
27
63
|
<script setup>
|
|
28
64
|
import { useFieldHOC } from "#core/composables/useForm";
|
|
29
65
|
import FieldWrapper from "#core/components/Form/FieldWrapper.vue";
|
|
30
|
-
|
|
66
|
+
import { ref, computed, nextTick, useUiConfig } from "#imports";
|
|
67
|
+
import { inputTheme } from "#core/theme/input";
|
|
68
|
+
const emits = defineEmits(["change", "add", "remove", "selected"]);
|
|
31
69
|
const props = defineProps({
|
|
32
70
|
leadingIcon: { type: null, required: false },
|
|
33
71
|
trailingIcon: { type: null, required: false },
|
|
34
72
|
loading: { type: Boolean, required: false },
|
|
35
73
|
loadingIcon: { type: null, required: false },
|
|
36
74
|
icon: { type: String, required: false },
|
|
37
|
-
maxLength: { type:
|
|
75
|
+
maxLength: { type: Number, required: false },
|
|
38
76
|
variant: { type: String, required: false },
|
|
39
77
|
size: { type: String, required: false },
|
|
40
78
|
deleteIcon: { type: String, required: false },
|
|
79
|
+
suggestions: { type: Array, required: false },
|
|
41
80
|
form: { type: Object, required: false },
|
|
42
81
|
name: { type: String, required: true },
|
|
43
82
|
errorMessage: { type: String, required: false },
|
|
@@ -53,11 +92,18 @@ const props = defineProps({
|
|
|
53
92
|
help: { type: String, required: false },
|
|
54
93
|
ui: { type: null, required: false }
|
|
55
94
|
});
|
|
95
|
+
const theme = computed(() => useUiConfig(inputTheme, "input")());
|
|
56
96
|
const {
|
|
57
97
|
value,
|
|
58
98
|
wrapperProps,
|
|
59
99
|
handleChange
|
|
60
100
|
} = useFieldHOC(props);
|
|
101
|
+
const showSuggestions = ref(false);
|
|
102
|
+
const selectedSuggestionIndex = ref(-1);
|
|
103
|
+
const suggestionsContainerRef = ref();
|
|
104
|
+
const suggestionItemRefs = ref([]);
|
|
105
|
+
const inputRef = ref();
|
|
106
|
+
const currentInput = ref("");
|
|
61
107
|
const onChange = (value2) => {
|
|
62
108
|
handleChange(value2);
|
|
63
109
|
emits("change", value2);
|
|
@@ -68,4 +114,112 @@ const onAdd = (value2) => {
|
|
|
68
114
|
const onRemove = (value2) => {
|
|
69
115
|
emits("remove", value2);
|
|
70
116
|
};
|
|
117
|
+
const onInput = (event) => {
|
|
118
|
+
const target = event.target;
|
|
119
|
+
currentInput.value = target.value.toLowerCase();
|
|
120
|
+
};
|
|
121
|
+
const setSuggestionItemRef = (el, index) => {
|
|
122
|
+
if (suggestionItemRefs.value) {
|
|
123
|
+
suggestionItemRefs.value[index] = el;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const filteredSuggestions = computed(() => {
|
|
127
|
+
if (!props.suggestions) return [];
|
|
128
|
+
const inputVal = currentInput.value?.trim().toLowerCase() || "";
|
|
129
|
+
if (!inputVal) {
|
|
130
|
+
return props.suggestions.filter((s) => !value.value?.includes(s));
|
|
131
|
+
}
|
|
132
|
+
return props.suggestions.filter(
|
|
133
|
+
(suggestion) => suggestion.toLowerCase().includes(inputVal) && !value.value?.includes(suggestion)
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
const onFocus = () => {
|
|
137
|
+
if (props.suggestions && props.suggestions.length > 0) {
|
|
138
|
+
showSuggestions.value = true;
|
|
139
|
+
selectedSuggestionIndex.value = -1;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const onBlur = (event) => {
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
showSuggestions.value = false;
|
|
145
|
+
selectedSuggestionIndex.value = -1;
|
|
146
|
+
}, 150);
|
|
147
|
+
};
|
|
148
|
+
const onKeydown = (event) => {
|
|
149
|
+
if (!showSuggestions.value || filteredSuggestions.value.length === 0) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
switch (event.key) {
|
|
153
|
+
case "ArrowDown":
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
selectedSuggestionIndex.value = selectedSuggestionIndex.value < filteredSuggestions.value.length - 1 ? selectedSuggestionIndex.value + 1 : 0;
|
|
156
|
+
scrollToSelectedSuggestion();
|
|
157
|
+
break;
|
|
158
|
+
case "ArrowUp":
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
selectedSuggestionIndex.value = selectedSuggestionIndex.value > 0 ? selectedSuggestionIndex.value - 1 : filteredSuggestions.value.length - 1;
|
|
161
|
+
scrollToSelectedSuggestion();
|
|
162
|
+
break;
|
|
163
|
+
case "Enter":
|
|
164
|
+
if (selectedSuggestionIndex.value === -1) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
if (selectedSuggestionIndex.value >= 0) {
|
|
169
|
+
const suggestion = filteredSuggestions.value[selectedSuggestionIndex.value];
|
|
170
|
+
if (suggestion) {
|
|
171
|
+
selectSuggestion(suggestion, selectedSuggestionIndex.value);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
case "Escape":
|
|
176
|
+
showSuggestions.value = false;
|
|
177
|
+
selectedSuggestionIndex.value = -1;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const selectSuggestion = (suggestion, index) => {
|
|
182
|
+
if (index !== void 0) {
|
|
183
|
+
scrollToSuggestionByIndex(index);
|
|
184
|
+
}
|
|
185
|
+
const newValue = [...value.value || [], suggestion];
|
|
186
|
+
handleChange(newValue);
|
|
187
|
+
emits("selected", suggestion);
|
|
188
|
+
emits("change", newValue);
|
|
189
|
+
showSuggestions.value = false;
|
|
190
|
+
selectedSuggestionIndex.value = -1;
|
|
191
|
+
currentInput.value = "";
|
|
192
|
+
nextTick(() => {
|
|
193
|
+
if (inputRef.value) {
|
|
194
|
+
inputRef.value.$el.querySelector("input")?.focus();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
const scrollToSelectedSuggestion = () => {
|
|
199
|
+
nextTick(() => {
|
|
200
|
+
if (selectedSuggestionIndex.value >= 0) {
|
|
201
|
+
scrollToSuggestionByIndex(selectedSuggestionIndex.value);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
const scrollToSuggestionByIndex = (index) => {
|
|
206
|
+
if (!suggestionsContainerRef.value || !suggestionItemRefs.value[index]) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const container = suggestionsContainerRef.value;
|
|
210
|
+
const item = suggestionItemRefs.value[index];
|
|
211
|
+
if (item) {
|
|
212
|
+
const containerRect = container.getBoundingClientRect();
|
|
213
|
+
const itemRect = item.getBoundingClientRect();
|
|
214
|
+
const isAboveView = itemRect.top < containerRect.top;
|
|
215
|
+
const isBelowView = itemRect.bottom > containerRect.bottom;
|
|
216
|
+
if (isAboveView || isBelowView) {
|
|
217
|
+
item.scrollIntoView({
|
|
218
|
+
behavior: "smooth",
|
|
219
|
+
block: "nearest",
|
|
220
|
+
inline: "nearest"
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
71
225
|
</script>
|
|
@@ -2,10 +2,12 @@ import type { ITagsFieldProps } from '#core/components/Form/InputTags/types';
|
|
|
2
2
|
declare const __VLS_export: import("vue").DefineComponent<ITagsFieldProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
3
3
|
add: (...args: any[]) => void;
|
|
4
4
|
change: (...args: any[]) => void;
|
|
5
|
+
selected: (...args: any[]) => void;
|
|
5
6
|
remove: (...args: any[]) => void;
|
|
6
7
|
}, string, import("vue").PublicProps, Readonly<ITagsFieldProps> & Readonly<{
|
|
7
8
|
onAdd?: ((...args: any[]) => any) | undefined;
|
|
8
9
|
onChange?: ((...args: any[]) => any) | undefined;
|
|
10
|
+
onSelected?: ((...args: any[]) => any) | undefined;
|
|
9
11
|
onRemove?: ((...args: any[]) => any) | undefined;
|
|
10
12
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
11
13
|
declare const _default: typeof __VLS_export;
|
|
@@ -5,13 +5,15 @@ export interface ITagsFieldProps extends IFieldProps {
|
|
|
5
5
|
loading?: boolean;
|
|
6
6
|
loadingIcon?: any;
|
|
7
7
|
icon?: string;
|
|
8
|
-
maxLength?:
|
|
9
|
-
variant?:
|
|
10
|
-
size?:
|
|
8
|
+
maxLength?: number;
|
|
9
|
+
variant?: 'outline' | 'soft' | 'subtle' | 'ghost' | 'none';
|
|
10
|
+
size?: 'xl' | 'xs' | 'sm' | 'md' | 'lg';
|
|
11
11
|
deleteIcon?: string;
|
|
12
|
+
suggestions?: string[];
|
|
12
13
|
}
|
|
13
14
|
export type ITagsField = IFormFieldBase<INPUT_TYPES.TAGS, ITagsFieldProps, {
|
|
14
15
|
change?: (value: string[]) => void;
|
|
15
16
|
add?: (value: string) => void;
|
|
16
17
|
remove?: (value: string) => void;
|
|
18
|
+
selected?: (value: string) => void;
|
|
17
19
|
}>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IUploadImageAutoProps } from './types.js';
|
|
2
|
+
import type { IFileValue } from '#core/components/Form/types';
|
|
3
|
+
declare const __VLS_export: import("vue").DefineComponent<IUploadImageAutoProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
4
|
+
success: (res: IFileValue) => any;
|
|
5
|
+
delete: () => any;
|
|
6
|
+
change: (value: File | undefined) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<IUploadImageAutoProps> & Readonly<{
|
|
8
|
+
onSuccess?: ((res: IFileValue) => any) | undefined;
|
|
9
|
+
onDelete?: (() => any) | undefined;
|
|
10
|
+
onChange?: ((value: File | undefined) => any) | undefined;
|
|
11
|
+
}>, {
|
|
12
|
+
selectFileLabel: string;
|
|
13
|
+
selectFileSubLabel: string;
|
|
14
|
+
uploadingLabel: string;
|
|
15
|
+
uploadFailedLabel: string;
|
|
16
|
+
retryLabel: string;
|
|
17
|
+
bodyKey: string;
|
|
18
|
+
responseURL: string;
|
|
19
|
+
responsePath: string;
|
|
20
|
+
responseName: string;
|
|
21
|
+
responseSize: string;
|
|
22
|
+
responseID: string;
|
|
23
|
+
accept: string[] | string;
|
|
24
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
25
|
+
declare const _default: typeof __VLS_export;
|
|
26
|
+
export default _default;
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
<!-- Loading State -->
|
|
19
|
+
<LoadingState
|
|
20
|
+
v-if="uploadState.isUploading.value"
|
|
21
|
+
:theme="theme"
|
|
22
|
+
:selected-file="uploadState.selectedFile.value"
|
|
23
|
+
:percent="uploadState.percent.value"
|
|
24
|
+
:uploading-label="uploadingLabel"
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
<!-- Success State -->
|
|
28
|
+
<SuccessState
|
|
29
|
+
v-if="uploadState.isSuccess.value"
|
|
30
|
+
:theme="theme"
|
|
31
|
+
:value="value"
|
|
32
|
+
:disabled="wrapperProps.disabled"
|
|
33
|
+
:readonly="wrapperProps.readonly"
|
|
34
|
+
@preview="uploadState.handlePreview"
|
|
35
|
+
@download="handleDownloadFile"
|
|
36
|
+
@delete="uploadState.handleDeleteFile"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<!-- Failed State -->
|
|
40
|
+
<FailedState
|
|
41
|
+
v-if="uploadState.isError.value"
|
|
42
|
+
:theme="theme"
|
|
43
|
+
:selected-file="uploadState.selectedFile.value"
|
|
44
|
+
:upload-failed-label="uploadFailedLabel"
|
|
45
|
+
:retry-label="retryLabel"
|
|
46
|
+
@retry="uploadState.handleRetryUpload"
|
|
47
|
+
@delete="uploadState.handleDeleteFile"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</FieldWrapper>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script setup>
|
|
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 { useUploadImageState } from "../fileState/useUploadImageState";
|
|
60
|
+
import { computed, useTemplateRef } from "#imports";
|
|
61
|
+
import FieldWrapper from "#core/components/Form/FieldWrapper.vue";
|
|
62
|
+
import { useFieldHOC } from "#core/composables/useForm";
|
|
63
|
+
import { uploadFileDropzoneTheme } from "#core/theme/uploadFileDropzone";
|
|
64
|
+
import { useUiConfig } from "#core/composables/useConfig";
|
|
65
|
+
import { downloadFileFromURL } from "#core/helpers/componentHelper";
|
|
66
|
+
const emits = defineEmits(["change", "success", "delete"]);
|
|
67
|
+
const props = defineProps({
|
|
68
|
+
requestOptions: { type: Object, required: true },
|
|
69
|
+
uploadPathURL: { type: String, required: false },
|
|
70
|
+
bodyKey: { type: String, required: false, default: "file" },
|
|
71
|
+
responseURL: { type: String, required: false, default: "url" },
|
|
72
|
+
responsePath: { type: String, required: false, default: "path" },
|
|
73
|
+
responseName: { type: String, required: false, default: "name" },
|
|
74
|
+
responseSize: { type: String, required: false, default: "size" },
|
|
75
|
+
responseID: { type: String, required: false, default: "id" },
|
|
76
|
+
accept: { type: [Array, String], required: false, default: () => ["image/*"] },
|
|
77
|
+
maxSize: { type: Number, required: false },
|
|
78
|
+
dimensions: { type: Object, required: false },
|
|
79
|
+
selectFileLabel: { type: String, required: false, default: "\u0E04\u0E25\u0E34\u0E01\u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E" },
|
|
80
|
+
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" },
|
|
81
|
+
uploadingLabel: { type: String, required: false, default: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E2D\u0E31\u0E1E\u0E42\u0E2B\u0E25\u0E14..." },
|
|
82
|
+
uploadFailedLabel: { type: String, required: false, default: "\u0E2D\u0E31\u0E1E\u0E42\u0E2B\u0E25\u0E14\u0E25\u0E49\u0E21\u0E40\u0E2B\u0E25\u0E27, \u0E01\u0E23\u0E38\u0E13\u0E32\u0E25\u0E2D\u0E07\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07" },
|
|
83
|
+
retryLabel: { type: String, required: false, default: "\u0E25\u0E2D\u0E07\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07" },
|
|
84
|
+
form: { type: Object, required: false },
|
|
85
|
+
name: { type: String, required: true },
|
|
86
|
+
errorMessage: { type: String, required: false },
|
|
87
|
+
label: { type: null, required: false },
|
|
88
|
+
description: { type: String, required: false },
|
|
89
|
+
hint: { type: String, required: false },
|
|
90
|
+
rules: { type: null, required: false },
|
|
91
|
+
autoFocus: { type: Boolean, required: false },
|
|
92
|
+
placeholder: { type: String, required: false },
|
|
93
|
+
disabled: { type: Boolean, required: false },
|
|
94
|
+
readonly: { type: Boolean, required: false },
|
|
95
|
+
required: { type: Boolean, required: false },
|
|
96
|
+
help: { type: String, required: false },
|
|
97
|
+
ui: { type: null, required: false }
|
|
98
|
+
});
|
|
99
|
+
const {
|
|
100
|
+
wrapperProps,
|
|
101
|
+
handleChange: onChange,
|
|
102
|
+
setErrors,
|
|
103
|
+
value
|
|
104
|
+
} = useFieldHOC(props);
|
|
105
|
+
const acceptedFileTypes = computed(
|
|
106
|
+
() => typeof props.accept === "string" ? props.accept : props.accept?.join(",")
|
|
107
|
+
);
|
|
108
|
+
const dropzoneRef = useTemplateRef("dropzoneRef");
|
|
109
|
+
const uploadState = useUploadImageState(
|
|
110
|
+
props,
|
|
111
|
+
emits,
|
|
112
|
+
onChange,
|
|
113
|
+
setErrors,
|
|
114
|
+
value,
|
|
115
|
+
acceptedFileTypes,
|
|
116
|
+
wrapperProps,
|
|
117
|
+
dropzoneRef
|
|
118
|
+
);
|
|
119
|
+
const theme = computed(
|
|
120
|
+
() => useUiConfig(uploadFileDropzoneTheme, "uploadFileDropzone")({
|
|
121
|
+
dragover: uploadState.dropzone.isOverDropZone.value && uploadState.isEmpty.value,
|
|
122
|
+
disabled: wrapperProps.value.disabled,
|
|
123
|
+
failed: uploadState.upload.status.value.isError
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
const handleDownloadFile = () => {
|
|
127
|
+
if (value.value?.url && value.value?.name) {
|
|
128
|
+
downloadFileFromURL(value.value.url, value.value.name);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IUploadImageAutoProps } from './types.js';
|
|
2
|
+
import type { IFileValue } from '#core/components/Form/types';
|
|
3
|
+
declare const __VLS_export: import("vue").DefineComponent<IUploadImageAutoProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
4
|
+
success: (res: IFileValue) => any;
|
|
5
|
+
delete: () => any;
|
|
6
|
+
change: (value: File | undefined) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<IUploadImageAutoProps> & Readonly<{
|
|
8
|
+
onSuccess?: ((res: IFileValue) => any) | undefined;
|
|
9
|
+
onDelete?: (() => any) | undefined;
|
|
10
|
+
onChange?: ((value: File | undefined) => any) | undefined;
|
|
11
|
+
}>, {
|
|
12
|
+
selectFileLabel: string;
|
|
13
|
+
selectFileSubLabel: string;
|
|
14
|
+
uploadingLabel: string;
|
|
15
|
+
uploadFailedLabel: string;
|
|
16
|
+
retryLabel: string;
|
|
17
|
+
bodyKey: string;
|
|
18
|
+
responseURL: string;
|
|
19
|
+
responsePath: string;
|
|
20
|
+
responseName: string;
|
|
21
|
+
responseSize: string;
|
|
22
|
+
responseID: string;
|
|
23
|
+
accept: string[] | string;
|
|
24
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
25
|
+
declare const _default: typeof __VLS_export;
|
|
26
|
+
export default _default;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AxiosRequestConfig } from 'axios';
|
|
2
|
+
import type { IFieldProps, IFormFieldBase, INPUT_TYPES } from '../types.js';
|
|
3
|
+
export interface IDimensionValidation {
|
|
4
|
+
width?: number;
|
|
5
|
+
height?: number;
|
|
6
|
+
minWidth?: number;
|
|
7
|
+
minHeight?: number;
|
|
8
|
+
maxWidth?: number;
|
|
9
|
+
maxHeight?: number;
|
|
10
|
+
aspectRatio?: number;
|
|
11
|
+
aspectRatioTolerance?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface IUploadImageAutoProps extends IFieldProps {
|
|
14
|
+
requestOptions: Omit<AxiosRequestConfig, 'baseURL'> & {
|
|
15
|
+
baseURL: string;
|
|
16
|
+
};
|
|
17
|
+
uploadPathURL?: string;
|
|
18
|
+
bodyKey?: string;
|
|
19
|
+
responseURL?: string;
|
|
20
|
+
responsePath?: string;
|
|
21
|
+
responseName?: string;
|
|
22
|
+
responseSize?: string;
|
|
23
|
+
responseID?: string;
|
|
24
|
+
accept?: string[] | string;
|
|
25
|
+
maxSize?: number;
|
|
26
|
+
dimensions?: IDimensionValidation;
|
|
27
|
+
selectFileLabel?: string;
|
|
28
|
+
selectFileSubLabel?: string;
|
|
29
|
+
uploadingLabel?: string;
|
|
30
|
+
uploadFailedLabel?: string;
|
|
31
|
+
retryLabel?: string;
|
|
32
|
+
}
|
|
33
|
+
export type IUploadImageAutoField = IFormFieldBase<INPUT_TYPES.UPLOAD_IMAGE_AUTO, IUploadImageAutoProps, {
|
|
34
|
+
change: (value: File | undefined) => void;
|
|
35
|
+
success: (res: any) => void;
|
|
36
|
+
delete: () => void;
|
|
37
|
+
}>;
|
|
File without changes
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Editor } from '@tiptap/vue-3';
|
|
2
2
|
import type { IFieldProps, IFormFieldBase, INPUT_TYPES } from '#core/components/Form/types';
|
|
3
|
+
import type { AxiosRequestConfig } from 'axios';
|
|
3
4
|
export interface IWYSIWYGFieldProps extends IFieldProps {
|
|
4
5
|
editable?: boolean;
|
|
5
6
|
autofocus?: boolean;
|
|
@@ -18,7 +19,20 @@ export interface IWYSIWYGFieldProps extends IFieldProps {
|
|
|
18
19
|
codeBlock?: boolean;
|
|
19
20
|
horizontalRule?: boolean;
|
|
20
21
|
link?: boolean;
|
|
21
|
-
image?:
|
|
22
|
+
image?: {
|
|
23
|
+
requestOptions?: Omit<AxiosRequestConfig, 'baseURL'> & {
|
|
24
|
+
baseURL: string;
|
|
25
|
+
};
|
|
26
|
+
uploadPathURL?: string;
|
|
27
|
+
bodyKey?: string;
|
|
28
|
+
responseURL?: string;
|
|
29
|
+
responsePath?: string;
|
|
30
|
+
responseName?: string;
|
|
31
|
+
responseSize?: string;
|
|
32
|
+
responseID?: string;
|
|
33
|
+
accept?: string[] | string;
|
|
34
|
+
maxSize?: number;
|
|
35
|
+
} | boolean;
|
|
22
36
|
youtube?: boolean;
|
|
23
37
|
textAlign?: boolean;
|
|
24
38
|
undo?: boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TemplateRef } from 'vue';
|
|
2
|
+
import type { IUploadImageAutoProps } from '../InputUploadImageAuto/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 useUploadImageState: (props: IUploadImageAutoProps, 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>;
|
|
12
|
+
isEmpty: import("vue").ComputedRef<boolean>;
|
|
13
|
+
isUploading: import("vue").ComputedRef<boolean>;
|
|
14
|
+
isSuccess: import("vue").ComputedRef<boolean>;
|
|
15
|
+
isError: import("vue").ComputedRef<boolean>;
|
|
16
|
+
selectedFile: import("vue").Ref<File | undefined, File | undefined>;
|
|
17
|
+
upload: import("../../../helpers/apiObjectHelper.js").IUseObjectLoader<any, any, Record<string, any>>;
|
|
18
|
+
dropzone: import("@vueuse/core").UseDropZoneReturn;
|
|
19
|
+
percent: import("vue").Ref<number, number>;
|
|
20
|
+
handleInputChange: (event: Event) => void;
|
|
21
|
+
handleOpenFile: () => void;
|
|
22
|
+
handleDeleteFile: () => void;
|
|
23
|
+
handleRetryUpload: () => void;
|
|
24
|
+
handlePreview: () => void;
|
|
25
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { useDropZone, useFileDialog } from "@vueuse/core";
|
|
2
|
+
import { useWatchTrue } from "#core/composables/useWatch";
|
|
3
|
+
import PreviewModal from "./PreviewModal.vue";
|
|
4
|
+
import { computed, ref, useOverlay } from "#imports";
|
|
5
|
+
import { useUploadLoader } from "#core/composables/useUpload";
|
|
6
|
+
import { useFileAllocate, useFileProgress } from "#core/helpers/componentHelper";
|
|
7
|
+
import { StringHelper } from "#core/utils/StringHelper";
|
|
8
|
+
import { _get } from "#core/utils/lodash";
|
|
9
|
+
export var UploadState = /* @__PURE__ */ ((UploadState2) => {
|
|
10
|
+
UploadState2["EMPTY"] = "empty";
|
|
11
|
+
UploadState2["UPLOADING"] = "uploading";
|
|
12
|
+
UploadState2["SUCCESS"] = "success";
|
|
13
|
+
UploadState2["ERROR"] = "error";
|
|
14
|
+
return UploadState2;
|
|
15
|
+
})(UploadState || {});
|
|
16
|
+
export const useUploadImageState = (props, emits, onChange, setErrors, value, acceptedFileTypes, wrapperProps, dropzoneRef) => {
|
|
17
|
+
const overlay = useOverlay();
|
|
18
|
+
const previewModal = overlay.create(PreviewModal);
|
|
19
|
+
const selectedFile = ref();
|
|
20
|
+
const fileAllocate = useFileAllocate(selectedFile, props);
|
|
21
|
+
const {
|
|
22
|
+
percent,
|
|
23
|
+
onDownloadProgress,
|
|
24
|
+
onUploadProgress
|
|
25
|
+
} = useFileProgress();
|
|
26
|
+
const request = {
|
|
27
|
+
requestOptions: {
|
|
28
|
+
...props.requestOptions,
|
|
29
|
+
onDownloadProgress,
|
|
30
|
+
onUploadProgress
|
|
31
|
+
},
|
|
32
|
+
pathURL: props.uploadPathURL
|
|
33
|
+
};
|
|
34
|
+
const upload = useUploadLoader(request);
|
|
35
|
+
const validateImageDimensions = (file) => {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
if (!props.dimensions) {
|
|
38
|
+
resolve(true);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const img = new Image();
|
|
42
|
+
const url = URL.createObjectURL(file);
|
|
43
|
+
img.onload = () => {
|
|
44
|
+
URL.revokeObjectURL(url);
|
|
45
|
+
const {
|
|
46
|
+
width,
|
|
47
|
+
height
|
|
48
|
+
} = img;
|
|
49
|
+
const dimensions = props.dimensions;
|
|
50
|
+
if (dimensions.width !== void 0 && width !== dimensions.width) {
|
|
51
|
+
setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E01\u0E27\u0E49\u0E32\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 ${dimensions.width}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${width}px)`);
|
|
52
|
+
resolve(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (dimensions.height !== void 0 && height !== dimensions.height) {
|
|
56
|
+
setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E39\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E40\u0E1B\u0E47\u0E19 ${dimensions.height}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${height}px)`);
|
|
57
|
+
resolve(false);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (dimensions.minWidth !== void 0 && width < dimensions.minWidth) {
|
|
61
|
+
setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E01\u0E27\u0E49\u0E32\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E19\u0E49\u0E2D\u0E22\u0E01\u0E27\u0E48\u0E32 ${dimensions.minWidth}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${width}px)`);
|
|
62
|
+
resolve(false);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (dimensions.minHeight !== void 0 && height < dimensions.minHeight) {
|
|
66
|
+
setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E39\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E19\u0E49\u0E2D\u0E22\u0E01\u0E27\u0E48\u0E32 ${dimensions.minHeight}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${height}px)`);
|
|
67
|
+
resolve(false);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (dimensions.maxWidth !== void 0 && width > dimensions.maxWidth) {
|
|
71
|
+
setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E01\u0E27\u0E49\u0E32\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${dimensions.maxWidth}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${width}px)`);
|
|
72
|
+
resolve(false);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (dimensions.maxHeight !== void 0 && height > dimensions.maxHeight) {
|
|
76
|
+
setErrors(`\u0E04\u0E27\u0E32\u0E21\u0E2A\u0E39\u0E07\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${dimensions.maxHeight}px (\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${height}px)`);
|
|
77
|
+
resolve(false);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (dimensions.aspectRatio !== void 0) {
|
|
81
|
+
const actualRatio = width / height;
|
|
82
|
+
const tolerance = dimensions.aspectRatioTolerance ?? 0.01;
|
|
83
|
+
const expectedRatio = dimensions.aspectRatio;
|
|
84
|
+
if (Math.abs(actualRatio - expectedRatio) > tolerance) {
|
|
85
|
+
setErrors(`\u0E2D\u0E31\u0E15\u0E23\u0E32\u0E2A\u0E48\u0E27\u0E19\u0E02\u0E2D\u0E07\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 (\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23: ${expectedRatio.toFixed(2)}, \u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19: ${actualRatio.toFixed(2)})`);
|
|
86
|
+
resolve(false);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
setErrors("");
|
|
91
|
+
resolve(true);
|
|
92
|
+
};
|
|
93
|
+
img.onerror = () => {
|
|
94
|
+
URL.revokeObjectURL(url);
|
|
95
|
+
setErrors("\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E42\u0E2B\u0E25\u0E14\u0E23\u0E39\u0E1B\u0E20\u0E32\u0E1E\u0E44\u0E14\u0E49");
|
|
96
|
+
resolve(false);
|
|
97
|
+
};
|
|
98
|
+
img.src = url;
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const validateFile = async (file) => {
|
|
102
|
+
if (props.accept && fileAllocate.acceptFile.value) {
|
|
103
|
+
const acceptedTypes = fileAllocate.acceptFile.value;
|
|
104
|
+
const acceptedTypesList = acceptedTypes.split(",").map((type) => type.trim());
|
|
105
|
+
const fileExtension = file.name.toLowerCase().split(".").pop();
|
|
106
|
+
const isValidFileType = acceptedTypesList.some((acceptedType) => {
|
|
107
|
+
if (acceptedType.startsWith(".")) {
|
|
108
|
+
const extension = acceptedType.slice(1).toLowerCase();
|
|
109
|
+
return fileExtension === extension;
|
|
110
|
+
} else if (!acceptedType.includes("/") && !acceptedType.includes("*")) {
|
|
111
|
+
return fileExtension === acceptedType.toLowerCase();
|
|
112
|
+
}
|
|
113
|
+
if (acceptedType.endsWith("/*")) {
|
|
114
|
+
const baseType = acceptedType.slice(0, -2);
|
|
115
|
+
return file.type.startsWith(baseType + "/");
|
|
116
|
+
}
|
|
117
|
+
return file.type === acceptedType;
|
|
118
|
+
});
|
|
119
|
+
if (!isValidFileType) {
|
|
120
|
+
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(", ") + ")");
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (props.maxSize) {
|
|
125
|
+
const maxSizeBytes = (fileAllocate.acceptFileSizeKb.value || 0) * 1024;
|
|
126
|
+
if (file.size > maxSizeBytes) {
|
|
127
|
+
if (fileAllocate.isAcceptFileUseMb.value) {
|
|
128
|
+
setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeMb.value} MB`);
|
|
129
|
+
} else {
|
|
130
|
+
setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeKb.value} KB`);
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const isDimensionsValid = await validateImageDimensions(file);
|
|
136
|
+
if (!isDimensionsValid) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
setErrors("");
|
|
140
|
+
return true;
|
|
141
|
+
};
|
|
142
|
+
const processFile = async (file) => {
|
|
143
|
+
const isValid = await validateFile(file);
|
|
144
|
+
if (!isValid) return;
|
|
145
|
+
selectedFile.value = file;
|
|
146
|
+
emits("change", file);
|
|
147
|
+
const formData = new FormData();
|
|
148
|
+
formData.append(props.bodyKey, file);
|
|
149
|
+
upload.run({
|
|
150
|
+
data: formData
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
const handleFileDrop = (files) => {
|
|
154
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly || !files?.length || !isEmpty.value) return;
|
|
155
|
+
const file = files[0];
|
|
156
|
+
if (file) {
|
|
157
|
+
processFile(file);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const fileDialog = useFileDialog({
|
|
161
|
+
accept: acceptedFileTypes.value || "image/*",
|
|
162
|
+
directory: false,
|
|
163
|
+
multiple: false
|
|
164
|
+
});
|
|
165
|
+
const dropzone = useDropZone(dropzoneRef, {
|
|
166
|
+
onDrop: handleFileDrop,
|
|
167
|
+
multiple: false,
|
|
168
|
+
preventDefaultForUnhandled: false
|
|
169
|
+
});
|
|
170
|
+
const currentState = computed(() => {
|
|
171
|
+
if (value.value) return "success" /* SUCCESS */;
|
|
172
|
+
if (selectedFile.value && upload.status.value.isLoading) return "uploading" /* UPLOADING */;
|
|
173
|
+
if (selectedFile.value && upload.status.value.isError) return "error" /* ERROR */;
|
|
174
|
+
return "empty" /* EMPTY */;
|
|
175
|
+
});
|
|
176
|
+
const isEmpty = computed(() => currentState.value === "empty" /* EMPTY */);
|
|
177
|
+
const isUploading = computed(() => currentState.value === "uploading" /* UPLOADING */);
|
|
178
|
+
const isSuccess = computed(() => currentState.value === "success" /* SUCCESS */);
|
|
179
|
+
const isError = computed(() => currentState.value === "error" /* ERROR */);
|
|
180
|
+
const handleInputChange = (event) => {
|
|
181
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
|
|
182
|
+
const file = event.target.files?.[0];
|
|
183
|
+
if (file) {
|
|
184
|
+
processFile(file);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
fileDialog.onChange((files) => {
|
|
188
|
+
const file = files?.[0];
|
|
189
|
+
if (file) {
|
|
190
|
+
processFile(file);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
const handleOpenFile = () => {
|
|
194
|
+
if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
|
|
195
|
+
fileDialog.open();
|
|
196
|
+
};
|
|
197
|
+
const handleDeleteFile = () => {
|
|
198
|
+
fileDialog.reset();
|
|
199
|
+
upload.clear();
|
|
200
|
+
selectedFile.value = void 0;
|
|
201
|
+
onChange(void 0);
|
|
202
|
+
emits("delete");
|
|
203
|
+
};
|
|
204
|
+
const handleRetryUpload = () => {
|
|
205
|
+
if (selectedFile.value) {
|
|
206
|
+
const formData = new FormData();
|
|
207
|
+
formData.append(props.bodyKey, selectedFile.value);
|
|
208
|
+
upload.run(formData);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const handlePreview = () => {
|
|
212
|
+
previewModal.open({
|
|
213
|
+
value: value.value
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
useWatchTrue(
|
|
217
|
+
() => upload.status.value.isSuccess,
|
|
218
|
+
() => {
|
|
219
|
+
if (upload.data.value) {
|
|
220
|
+
const fileValue = {
|
|
221
|
+
url: _get(upload.data.value, props.responseURL),
|
|
222
|
+
path: _get(upload.data.value, props.responsePath),
|
|
223
|
+
name: _get(upload.data.value, props.responseName) || selectedFile.value?.name,
|
|
224
|
+
size: Number(_get(upload.data.value, props.responseSize) || selectedFile.value?.size),
|
|
225
|
+
id: _get(upload.data.value, props.responseID)
|
|
226
|
+
};
|
|
227
|
+
value.value = fileValue;
|
|
228
|
+
emits("success", fileValue);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
useWatchTrue(
|
|
233
|
+
() => upload.status.value.isError,
|
|
234
|
+
() => {
|
|
235
|
+
setErrors("\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14: " + StringHelper.getError(upload.status.value.errorData));
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
return {
|
|
239
|
+
// State
|
|
240
|
+
currentState,
|
|
241
|
+
isEmpty,
|
|
242
|
+
isUploading,
|
|
243
|
+
isSuccess,
|
|
244
|
+
isError,
|
|
245
|
+
selectedFile,
|
|
246
|
+
// Upload utilities
|
|
247
|
+
upload,
|
|
248
|
+
dropzone,
|
|
249
|
+
percent,
|
|
250
|
+
// Handlers
|
|
251
|
+
handleInputChange,
|
|
252
|
+
handleOpenFile,
|
|
253
|
+
handleDeleteFile,
|
|
254
|
+
handleRetryUpload,
|
|
255
|
+
handlePreview
|
|
256
|
+
};
|
|
257
|
+
};
|
|
@@ -3,6 +3,7 @@ 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
5
|
import type { IUploadDropzoneAutoMultipleField } from './InputUploadDropzoneAutoMultiple/types.js';
|
|
6
|
+
import type { IUploadImageAutoField } from './InputUploadImageAuto/types.js';
|
|
6
7
|
import type { IDateTimeRangeField } from './InputDateTimeRange/date_range_time_field.types.js';
|
|
7
8
|
import type { ITextField } from '#core/components/Form/InputText/types';
|
|
8
9
|
import type { ISearchField } from '#core/components/Form/InputSearch/types';
|
|
@@ -20,6 +21,7 @@ import type { IWYSIWYGField } from '#core/components/Form/InputWYSIWYG/types';
|
|
|
20
21
|
import type { IComponentField } from '#core/components/Form/InputComponent/types';
|
|
21
22
|
import type { ITagsField } from '#core/components/Form/InputTags/types';
|
|
22
23
|
import type { ICurrencyField } from '#core/components/Form/InputCurrency/types';
|
|
24
|
+
import type { ICheckboxGroupField } from '#core/components/Form/InputCheckboxGroup/types';
|
|
23
25
|
export declare enum INPUT_TYPES {
|
|
24
26
|
TEXT = "TEXT",
|
|
25
27
|
SEARCH = "SEARCH",
|
|
@@ -35,6 +37,7 @@ export declare enum INPUT_TYPES {
|
|
|
35
37
|
SELECT_MULTIPLE = "SELECT_MULTIPLE",
|
|
36
38
|
RADIO = "RADIO",
|
|
37
39
|
CHECKBOX = "CHECKBOX",
|
|
40
|
+
CHECKBOX_GROUP = "CHECKBOX_GROUP",
|
|
38
41
|
DATE_TIME = "DATE_TIME",
|
|
39
42
|
TIME = "TIME",
|
|
40
43
|
DATE = "DATE",
|
|
@@ -76,7 +79,7 @@ export interface IFormFieldBase<I extends INPUT_TYPES, P extends IFieldProps, O>
|
|
|
76
79
|
props: P;
|
|
77
80
|
on?: O;
|
|
78
81
|
}
|
|
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>;
|
|
82
|
+
export type IFormField = ITextField | ISearchField | INumberField | ICurrencyField | ITextareaField | IToggleField | ISelectField | ICheckboxField | ICheckboxGroupField | ISelectMultipleField | IRadioField | IDateTimeField | ITimeField | IMonthField | IDateTimeRangeField | IUploadDropzoneField | IUploadDropzoneAutoField | IUploadDropzoneAutoMultipleField | IUploadImageAutoField | IWYSIWYGField | IComponentField | ITagsField | IFormFieldBase<INPUT_TYPES.COMPONENT, any, any>;
|
|
80
83
|
export interface IFileValue {
|
|
81
84
|
url: string;
|
|
82
85
|
path?: string;
|
|
@@ -13,6 +13,7 @@ export var INPUT_TYPES = /* @__PURE__ */ ((INPUT_TYPES2) => {
|
|
|
13
13
|
INPUT_TYPES2["SELECT_MULTIPLE"] = "SELECT_MULTIPLE";
|
|
14
14
|
INPUT_TYPES2["RADIO"] = "RADIO";
|
|
15
15
|
INPUT_TYPES2["CHECKBOX"] = "CHECKBOX";
|
|
16
|
+
INPUT_TYPES2["CHECKBOX_GROUP"] = "CHECKBOX_GROUP";
|
|
16
17
|
INPUT_TYPES2["DATE_TIME"] = "DATE_TIME";
|
|
17
18
|
INPUT_TYPES2["TIME"] = "TIME";
|
|
18
19
|
INPUT_TYPES2["DATE"] = "DATE";
|
|
@@ -2,7 +2,11 @@ import type { TableColumn } from '@nuxt/ui';
|
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
value: any;
|
|
4
4
|
row: any;
|
|
5
|
-
column: TableColumn<any
|
|
5
|
+
column: TableColumn<any> & {
|
|
6
|
+
meta: {
|
|
7
|
+
max: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
6
10
|
};
|
|
7
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
12
|
declare const _default: typeof __VLS_export;
|
|
@@ -2,7 +2,11 @@ import type { TableColumn } from '@nuxt/ui';
|
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
value: any;
|
|
4
4
|
row: any;
|
|
5
|
-
column: TableColumn<any
|
|
5
|
+
column: TableColumn<any> & {
|
|
6
|
+
meta: {
|
|
7
|
+
max: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
6
10
|
};
|
|
7
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
12
|
declare const _default: typeof __VLS_export;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
@import "tailwindcss";@import "@nuxt/ui";@plugin "@tailwindcss/typography";@source inline("prose");@theme{--font-sans:"Noto Sans Thai","Noto Sans Thai Looped","Public Sans",sans-serif;--font-display:"Noto Sans Thai","Noto Sans Thai Looped","Public Sans",sans-serif}:root{--ui-text:var(--ui-color-neutral-800);--color-main:#1570ef;--color-main-50:#
|
|
1
|
+
@import "tailwindcss";@import "@nuxt/ui";@plugin "@tailwindcss/typography";@source inline("prose");@theme{--font-sans:"Noto Sans Thai","Noto Sans Thai Looped","Public Sans",sans-serif;--font-display:"Noto Sans Thai","Noto Sans Thai Looped","Public Sans",sans-serif}:root{--ui-text:var(--ui-color-neutral-800);--color-main:#1570ef;--color-main-50:#eff8ff;--color-main-100:#d1e9ff;--color-main-200:#b2ddff;--color-main-300:#84caff;--color-main-400:#53b1fd;--color-main-500:#1570ef;--color-main-600:#1570ef;--color-main-700:#175cd3;--color-main-800:#1849a9;--color-main-900:#194185;--color-main-950:#102a56;--color-warning:#f79009;--color-warning-50:#fffaeb;--color-warning-100:#fef0c7;--color-warning-200:#fedf89;--color-warning-300:#fec84b;--color-warning-400:#fdb022;--color-warning-500:#f79009;--color-warning-600:#dc6803;--color-warning-700:#b54708;--color-warning-800:#93370d;--color-warning-900:#7a2e0e;--color-warning-950:#4e1d09;--color-success:#17b26a;--color-success-50:#ecfdf3;--color-success-100:#dcfae6;--color-success-200:#abefc6;--color-success-300:#75e0a7;--color-success-400:#47cd89;--color-success-500:#17b26a;--color-success-600:#079455;--color-success-700:#067647;--color-success-800:#085d3a;--color-success-900:#074d31;--color-success-950:#053321;--color-error:#e11d48;--color-error-50:#fef2f2;--color-error-100:#fee2e2;--color-error-200:#f0899f;--color-error-300:#eb6582;--color-error-400:#e64065;--color-error-500:#e11d48;--color-error-600:#af1738;--color-error-700:#7e1028;--color-error-800:#4c0a18;--color-error-900:#1a0308;--color-error-950:#010000;--color-info:#2563eb;--color-info-50:#dde9ff;--color-info-100:#c8dfff;--color-info-200:#a1c4ff;--color-info-300:#7aa9ff;--color-info-400:#538eff;--color-info-500:#2563eb;--color-info-600:#1a4aaf;--color-info-700:#0f318a;--color-info-800:#081f65;--color-info-900:#020b3a;--color-info-950:#000;--color-white:#fff;--color-white-50:#fff;--color-white-100:#fff;--color-white-200:#fff;--color-white-300:#fff;--color-white-400:#fff;--color-white-500:#fff;--color-white-600:#e3e3e3;--color-white-700:#c7c7c7;--color-white-800:#ababab;--color-white-900:#8f8f8f;--color-white-950:#818181}html{@apply text-sm lg:text-base;font-family:Noto Sans Thai,Noto Sans Thai Looped,Public Sans,sans-serif}::-webkit-scrollbar{-webkit-appearance:none;height:10px;width:10px}::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.3);border-radius:4px;box-shadow:0 0 1px hsla(0,0%,100%,.5)}:root{--dp-font-family:inherit!important}.dp__theme_light{--dp-primary-color:var(--color-main)!important;--dp-primary-disabled-color:var(--color-main-200)!important}.dp__outer_menu_wrap{@apply ring-1 ring-gray-200}[role=dialog]{pointer-events:auto!important}#__nuxt,body,html{@apply w-full h-full}.dp__main{display:block!important}.dp__menu{border:none!important}.dp__pointer{height:44px!important}.dp__outer_menu_wrap{box-shadow:none!important}.dp--menu-wrapper{@apply ring-1 ring-slate-300}
|
|
@@ -7,10 +7,10 @@ export const selectMenuTheme = {
|
|
|
7
7
|
clearIcon: "size-6 bg-gray-400 hover:bg-gray-400/75",
|
|
8
8
|
item: "cursor-pointer max-sm:h-14",
|
|
9
9
|
tagsWrapper: "flex flex-wrap gap-x-2 gap-y-1",
|
|
10
|
-
tagsItem: "px-1.5 py-0.5 rounded-sm inline-flex items-center gap-0.5
|
|
10
|
+
tagsItem: "px-1.5 py-0.5 rounded-sm inline-flex items-center gap-0.5 ring-1 ring-gray-300 bg-white data-disabled:cursor-not-allowed data-disabled:opacity-75",
|
|
11
11
|
tagsItemText: "flex items-center gap-x-1 text-sm",
|
|
12
12
|
tagsItemDelete: [
|
|
13
|
-
"inline-flex items-center text-
|
|
13
|
+
"inline-flex items-center hover:text-gray-500 disabled:pointer-events-none",
|
|
14
14
|
"transition-colors cursor-pointer"
|
|
15
15
|
],
|
|
16
16
|
tagsItemDeleteIcon: "ph:x"
|