@finema/core 2.19.1 → 2.21.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/DevToolsWindow/index.vue +98 -95
- package/dist/runtime/components/FlexDeck/index.vue +5 -5
- package/dist/runtime/components/FlexDeck/types.d.ts +1 -1
- package/dist/runtime/components/Form/InputText/index.vue +214 -77
- package/dist/runtime/components/Form/InputText/types.d.ts +1 -0
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/EmptyState.vue +1 -1
- package/dist/runtime/components/Form/InputUploadDropzoneAuto/FailedState.vue +1 -1
- package/dist/runtime/components/Image.vue +1 -1
- package/dist/runtime/components/Table/Base.vue +3 -3
- package/dist/runtime/components/Table/ColumnNumber.vue +3 -3
- package/dist/runtime/components/Table/index.vue +6 -4
- package/dist/runtime/components/Table/types.d.ts +1 -1
- package/dist/runtime/composables/useFlexDeck.js +1 -1
- package/dist/runtime/composables/useTable.js +1 -1
- package/dist/runtime/helpers/componentHelper.js +3 -11
- package/dist/runtime/theme/input.d.ts +3 -0
- package/dist/runtime/theme/input.js +4 -1
- package/dist/runtime/utils/NumberHelper.d.ts +6 -0
- package/dist/runtime/utils/NumberHelper.js +25 -0
- package/dist/runtime/utils/StringHelper.d.ts +0 -2
- package/dist/runtime/utils/StringHelper.js +0 -9
- package/package.json +2 -6
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,99 +1,102 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
v-show="isShowDevTools"
|
|
4
|
-
ref="devToolsRef"
|
|
5
|
-
class="
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
2
|
+
<div
|
|
3
|
+
v-show="isShowDevTools"
|
|
4
|
+
ref="devToolsRef"
|
|
5
|
+
class="
|
|
6
|
+
fixed z-50 overflow-hidden rounded-lg border border-neutral-300 bg-white
|
|
7
|
+
opacity-80 shadow-2xl
|
|
8
|
+
"
|
|
9
|
+
:style="devToolsDynamicStyles"
|
|
10
|
+
>
|
|
11
|
+
<!-- Draggable Title Bar -->
|
|
12
|
+
<div class="flex items-center justify-between px-2 py-1 select-none">
|
|
13
|
+
<p
|
|
14
|
+
class="flex-grow cursor-move text-sm font-semibold"
|
|
15
|
+
@mousedown.prevent="handleDragStart"
|
|
16
|
+
>
|
|
17
|
+
Debug Tools
|
|
18
|
+
</p>
|
|
19
|
+
<div class="flex items-center">
|
|
20
|
+
<Button
|
|
21
|
+
icon="i-heroicons-arrow-path"
|
|
22
|
+
size="xs"
|
|
23
|
+
color="neutral"
|
|
24
|
+
variant="ghost"
|
|
25
|
+
class="mr-1"
|
|
26
|
+
title="Reset Position & Size"
|
|
27
|
+
@click.stop="resetDevToolsState"
|
|
28
|
+
/>
|
|
29
|
+
<Button
|
|
30
|
+
icon="i-heroicons-x-mark"
|
|
31
|
+
size="xs"
|
|
32
|
+
color="neutral"
|
|
33
|
+
variant="ghost"
|
|
34
|
+
title="Close DevTools"
|
|
35
|
+
@click.stop="closeDevTools"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<hr class="text-neutral-300" />
|
|
40
|
+
<!-- Content Area Target for Logs -->
|
|
41
|
+
<div
|
|
42
|
+
id="dev-logs"
|
|
43
|
+
class="flex flex-1 flex-col space-y-1 overflow-auto p-2"
|
|
44
|
+
:style="{ height: `calc(${devToolsHeight} - 40px)` }"
|
|
45
|
+
/>
|
|
46
|
+
|
|
47
|
+
<!-- Resize Handles -->
|
|
48
|
+
<div
|
|
49
|
+
v-if="!isDragging"
|
|
50
|
+
class="resize-handles"
|
|
51
|
+
>
|
|
52
|
+
<div
|
|
53
|
+
class="resize-handle top-left"
|
|
54
|
+
@mousedown.prevent="handleResizeStart('top-left', $event)"
|
|
55
|
+
/>
|
|
56
|
+
<div
|
|
57
|
+
class="resize-handle top-center"
|
|
58
|
+
@mousedown.prevent="handleResizeStart('top', $event)"
|
|
59
|
+
/>
|
|
60
|
+
<div
|
|
61
|
+
class="resize-handle top-right"
|
|
62
|
+
@mousedown.prevent="handleResizeStart('top-right', $event)"
|
|
63
|
+
/>
|
|
64
|
+
<div
|
|
65
|
+
class="resize-handle middle-left"
|
|
66
|
+
@mousedown.prevent="handleResizeStart('left', $event)"
|
|
67
|
+
/>
|
|
68
|
+
<div
|
|
69
|
+
class="resize-handle middle-right"
|
|
70
|
+
@mousedown.prevent="handleResizeStart('right', $event)"
|
|
71
|
+
/>
|
|
72
|
+
<div
|
|
73
|
+
class="resize-handle bottom-left"
|
|
74
|
+
@mousedown.prevent="handleResizeStart('bottom-left', $event)"
|
|
75
|
+
/>
|
|
76
|
+
<div
|
|
77
|
+
class="resize-handle bottom-center"
|
|
78
|
+
@mousedown.prevent="handleResizeStart('bottom', $event)"
|
|
79
|
+
/>
|
|
80
|
+
<div
|
|
81
|
+
class="resize-handle bottom-right"
|
|
82
|
+
@mousedown.prevent="handleResizeStart('bottom-right', $event)"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Toggle button for this DevToolsWindow -->
|
|
88
|
+
<div
|
|
89
|
+
class="fixed right-1 bottom-1 z-[99999]"
|
|
90
|
+
>
|
|
91
|
+
<Button
|
|
92
|
+
:icon="isShowDevTools ? 'heroicons:x-mark' : 'heroicons:information-circle'"
|
|
93
|
+
color="info"
|
|
94
|
+
square
|
|
95
|
+
size="sm"
|
|
96
|
+
:ui="{ base: 'rounded-full' }"
|
|
97
|
+
@click="toggleDevTools"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
97
100
|
</template>
|
|
98
101
|
|
|
99
102
|
<script setup>
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
{{ pageBetween }} รายการ จากทั้งหมด {{ totalCountWithComma }} รายการ
|
|
54
54
|
</p>
|
|
55
55
|
<Pagination
|
|
56
|
-
v-if="options.pageOptions.totalPage > 1
|
|
57
|
-
:to="options.
|
|
56
|
+
v-if="options.pageOptions.totalPage > 1"
|
|
57
|
+
:to="options.isRouteChange ? to : void 0"
|
|
58
58
|
:default-page="options.pageOptions?.currentPage || 1"
|
|
59
59
|
:items-per-page="options.pageOptions.limit"
|
|
60
60
|
:total="options.pageOptions.totalCount"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
<script setup>
|
|
68
68
|
import { computed, ref, watch } from "vue";
|
|
69
69
|
import { useElementVisibility } from "@vueuse/core";
|
|
70
|
-
import {
|
|
70
|
+
import { NumberHelper } from "#core/utils/NumberHelper";
|
|
71
71
|
import { _debounce, useRouter, useWatchChange, useWatchTrue } from "#imports";
|
|
72
72
|
import { useCoreConfig } from "#core/composables/useConfig";
|
|
73
73
|
import Empty from "#core/components/Empty.vue";
|
|
@@ -108,7 +108,7 @@ const to = (page2) => {
|
|
|
108
108
|
};
|
|
109
109
|
};
|
|
110
110
|
useWatchChange(() => props.options?.pageOptions?.request?.params, () => {
|
|
111
|
-
if (props.options?.
|
|
111
|
+
if (props.options?.isRouteChange) return;
|
|
112
112
|
router.replace({
|
|
113
113
|
query: props.options?.pageOptions?.request?.params || {}
|
|
114
114
|
});
|
|
@@ -123,7 +123,7 @@ const pageBetween = computed(() => {
|
|
|
123
123
|
return `${start} - ${end}`;
|
|
124
124
|
});
|
|
125
125
|
const totalCountWithComma = computed(() => {
|
|
126
|
-
return !props.options.pageOptions.totalCount ? "0" :
|
|
126
|
+
return !props.options.pageOptions.totalCount ? "0" : NumberHelper.withComma(props.options.pageOptions.totalCount);
|
|
127
127
|
});
|
|
128
128
|
const totalInnerRawData = computed(() => {
|
|
129
129
|
return innerRawData.value?.length || 0;
|
|
@@ -6,7 +6,7 @@ export interface IFlexDeckOptions<T = object> {
|
|
|
6
6
|
pageOptions: IPageOptions;
|
|
7
7
|
isEnabledSearch?: boolean;
|
|
8
8
|
searchPlaceholder?: string;
|
|
9
|
-
|
|
9
|
+
isRouteChange: boolean;
|
|
10
10
|
isHidePagination?: boolean;
|
|
11
11
|
isEnableInfiniteScroll?: boolean;
|
|
12
12
|
}
|
|
@@ -1,60 +1,90 @@
|
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
3
|
+
<div class="relative">
|
|
4
|
+
<Input
|
|
5
|
+
v-if="type === 'password'"
|
|
6
|
+
ref="inputRef"
|
|
7
|
+
v-maska="activeMaskOptions"
|
|
8
|
+
:model-value="value"
|
|
9
|
+
:disabled="wrapperProps.disabled"
|
|
10
|
+
:leading-icon="leadingIcon"
|
|
11
|
+
:trailing-icon="trailingIcon"
|
|
12
|
+
:loading="loading"
|
|
13
|
+
:loading-icon="loadingIcon"
|
|
14
|
+
:name="name"
|
|
15
|
+
:placeholder="wrapperProps.placeholder"
|
|
16
|
+
:type="isShowPassword ? 'text' : 'password'"
|
|
17
|
+
:autofocus="!!autoFocus"
|
|
18
|
+
:icon="icon"
|
|
19
|
+
:readonly="readonly"
|
|
20
|
+
:ui="defu(ui, { icon: { trailing: { pointer: '' } } })"
|
|
21
|
+
@update:model-value="onChange"
|
|
22
|
+
@focus="onFocus"
|
|
23
|
+
@blur="onBlur"
|
|
24
|
+
@keydown="onKeydown"
|
|
25
|
+
>
|
|
26
|
+
<template #trailing>
|
|
27
|
+
<Button
|
|
28
|
+
color="neutral"
|
|
29
|
+
variant="link"
|
|
30
|
+
:icon="isShowPassword ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'"
|
|
31
|
+
:padded="false"
|
|
32
|
+
@click="isShowPassword = !isShowPassword"
|
|
33
|
+
/>
|
|
34
|
+
</template>
|
|
35
|
+
</Input>
|
|
36
|
+
<Input
|
|
37
|
+
v-else
|
|
38
|
+
ref="inputRef"
|
|
39
|
+
v-maska="activeMaskOptions"
|
|
40
|
+
:model-value="value"
|
|
41
|
+
:disabled="wrapperProps.disabled"
|
|
42
|
+
:leading-icon="leadingIcon"
|
|
43
|
+
:trailing-icon="trailingIcon"
|
|
44
|
+
:loading="loading"
|
|
45
|
+
:loading-icon="loadingIcon"
|
|
46
|
+
:name="name"
|
|
47
|
+
:placeholder="wrapperProps.placeholder"
|
|
48
|
+
:type="type"
|
|
49
|
+
:autofocus="!!autoFocus"
|
|
50
|
+
:icon="icon"
|
|
51
|
+
:readonly="readonly"
|
|
52
|
+
:ui="ui"
|
|
53
|
+
@update:model-value="onChange"
|
|
54
|
+
@focus="onFocus"
|
|
55
|
+
@blur="onBlur"
|
|
56
|
+
@keydown="onKeydown"
|
|
57
|
+
/>
|
|
58
|
+
<div
|
|
59
|
+
v-if="showSuggestions && filteredSuggestions.length > 0"
|
|
60
|
+
ref="suggestionsContainerRef"
|
|
61
|
+
:class="theme.suggestionsContainer()"
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
v-for="(suggestion, index) in filteredSuggestions"
|
|
65
|
+
:key="suggestion"
|
|
66
|
+
:ref="(el) => setSuggestionItemRef(el, index)"
|
|
67
|
+
:class="[
|
|
68
|
+
theme.suggestionItem(),
|
|
69
|
+
{ [theme.suggestionItemActive()]: index === selectedSuggestionIndex }
|
|
70
|
+
]"
|
|
71
|
+
@mousedown.prevent="selectSuggestion(suggestion, index)"
|
|
72
|
+
@mouseenter="selectedSuggestionIndex = index"
|
|
73
|
+
>
|
|
74
|
+
{{ suggestion }}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
49
78
|
</FieldWrapper>
|
|
50
79
|
</template>
|
|
51
80
|
|
|
52
81
|
<script setup>
|
|
53
82
|
import { vMaska } from "maska/vue";
|
|
54
83
|
import { defu } from "defu";
|
|
55
|
-
import { ref, computed } from "#imports";
|
|
84
|
+
import { ref, computed, nextTick, useUiConfig } from "#imports";
|
|
56
85
|
import { useFieldHOC } from "#core/composables/useForm";
|
|
57
86
|
import FieldWrapper from "#core/components/Form/FieldWrapper.vue";
|
|
87
|
+
import { inputTheme } from "#core/theme/input";
|
|
58
88
|
const emits = defineEmits(["change"]);
|
|
59
89
|
const props = defineProps({
|
|
60
90
|
type: { type: String, required: false, default: "text" },
|
|
@@ -69,6 +99,7 @@ const props = defineProps({
|
|
|
69
99
|
maskEager: { type: Boolean, required: false },
|
|
70
100
|
maskTokensReplace: { type: Boolean, required: false },
|
|
71
101
|
maskReversed: { type: Boolean, required: false },
|
|
102
|
+
suggestions: { type: Array, required: false },
|
|
72
103
|
form: { type: Object, required: false },
|
|
73
104
|
name: { type: String, required: true },
|
|
74
105
|
errorMessage: { type: String, required: false },
|
|
@@ -84,52 +115,158 @@ const props = defineProps({
|
|
|
84
115
|
help: { type: String, required: false },
|
|
85
116
|
ui: { type: null, required: false }
|
|
86
117
|
});
|
|
118
|
+
const theme = computed(() => useUiConfig(inputTheme, "input")());
|
|
87
119
|
const {
|
|
88
120
|
value,
|
|
89
121
|
wrapperProps,
|
|
90
122
|
handleChange
|
|
91
123
|
} = useFieldHOC(props);
|
|
92
124
|
const isShowPassword = ref(false);
|
|
125
|
+
const inputRef = ref();
|
|
126
|
+
const showSuggestions = ref(false);
|
|
127
|
+
const selectedSuggestionIndex = ref(-1);
|
|
128
|
+
const suggestionsContainerRef = ref();
|
|
129
|
+
const suggestionItemRefs = ref([]);
|
|
130
|
+
const setSuggestionItemRef = (el, index) => {
|
|
131
|
+
if (suggestionItemRefs.value) {
|
|
132
|
+
suggestionItemRefs.value[index] = el;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
93
135
|
const onChange = (value2) => {
|
|
136
|
+
if (props.suggestions && props.suggestions.length > 0) {
|
|
137
|
+
showSuggestions.value = true;
|
|
138
|
+
selectedSuggestionIndex.value = -1;
|
|
139
|
+
}
|
|
94
140
|
handleChange(value2);
|
|
95
141
|
emits("change", value2);
|
|
96
142
|
};
|
|
97
|
-
const
|
|
98
|
-
if (props.
|
|
99
|
-
|
|
100
|
-
return props.maskOptions.mask;
|
|
101
|
-
}
|
|
102
|
-
return props.maskOptions;
|
|
143
|
+
const filteredSuggestions = computed(() => {
|
|
144
|
+
if (!props.suggestions || !value.value) {
|
|
145
|
+
return props.suggestions || [];
|
|
103
146
|
}
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
147
|
+
const inputValue = value.value.toLowerCase();
|
|
148
|
+
return props.suggestions.filter(
|
|
149
|
+
(suggestion) => suggestion.toLowerCase().includes(inputValue)
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
const onFocus = () => {
|
|
153
|
+
if (props.suggestions && props.suggestions.length > 0) {
|
|
154
|
+
showSuggestions.value = true;
|
|
155
|
+
selectedSuggestionIndex.value = -1;
|
|
109
156
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
157
|
+
};
|
|
158
|
+
const onBlur = (event) => {
|
|
159
|
+
setTimeout(() => {
|
|
160
|
+
showSuggestions.value = false;
|
|
161
|
+
selectedSuggestionIndex.value = -1;
|
|
162
|
+
}, 150);
|
|
163
|
+
};
|
|
164
|
+
const onKeydown = (event) => {
|
|
165
|
+
if (!showSuggestions.value || filteredSuggestions.value.length === 0) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
switch (event.key) {
|
|
169
|
+
case "ArrowDown":
|
|
170
|
+
event.preventDefault();
|
|
171
|
+
selectedSuggestionIndex.value = selectedSuggestionIndex.value < filteredSuggestions.value.length - 1 ? selectedSuggestionIndex.value + 1 : 0;
|
|
172
|
+
scrollToSelectedSuggestion();
|
|
173
|
+
break;
|
|
174
|
+
case "ArrowUp":
|
|
175
|
+
event.preventDefault();
|
|
176
|
+
selectedSuggestionIndex.value = selectedSuggestionIndex.value > 0 ? selectedSuggestionIndex.value - 1 : filteredSuggestions.value.length - 1;
|
|
177
|
+
scrollToSelectedSuggestion();
|
|
178
|
+
break;
|
|
179
|
+
case "Enter":
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
if (selectedSuggestionIndex.value >= 0) {
|
|
182
|
+
selectSuggestion(filteredSuggestions.value[selectedSuggestionIndex.value], selectedSuggestionIndex.value);
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
case "Escape":
|
|
186
|
+
showSuggestions.value = false;
|
|
187
|
+
selectedSuggestionIndex.value = -1;
|
|
188
|
+
break;
|
|
113
189
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
190
|
+
};
|
|
191
|
+
const selectSuggestion = (suggestion, index) => {
|
|
192
|
+
if (index !== void 0) {
|
|
193
|
+
scrollToSuggestionByIndex(index);
|
|
117
194
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
195
|
+
handleChange(suggestion);
|
|
196
|
+
emits("change", suggestion);
|
|
197
|
+
showSuggestions.value = false;
|
|
198
|
+
selectedSuggestionIndex.value = -1;
|
|
199
|
+
nextTick(() => {
|
|
200
|
+
if (inputRef.value) {
|
|
201
|
+
inputRef.value.$el.querySelector("input")?.focus();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
const scrollToSelectedSuggestion = () => {
|
|
206
|
+
nextTick(() => {
|
|
207
|
+
if (selectedSuggestionIndex.value >= 0) {
|
|
208
|
+
scrollToSuggestionByIndex(selectedSuggestionIndex.value);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
const scrollToSuggestionByIndex = (index) => {
|
|
213
|
+
if (!suggestionsContainerRef.value || !suggestionItemRefs.value[index]) {
|
|
214
|
+
return;
|
|
121
215
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
216
|
+
const container = suggestionsContainerRef.value;
|
|
217
|
+
const item = suggestionItemRefs.value[index];
|
|
218
|
+
if (item) {
|
|
219
|
+
const containerRect = container.getBoundingClientRect();
|
|
220
|
+
const itemRect = item.getBoundingClientRect();
|
|
221
|
+
const isAboveView = itemRect.top < containerRect.top;
|
|
222
|
+
const isBelowView = itemRect.bottom > containerRect.bottom;
|
|
223
|
+
if (isAboveView || isBelowView) {
|
|
224
|
+
item.scrollIntoView({
|
|
225
|
+
behavior: "smooth",
|
|
226
|
+
block: "nearest",
|
|
227
|
+
inline: "nearest"
|
|
228
|
+
});
|
|
229
|
+
}
|
|
125
230
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
231
|
+
};
|
|
232
|
+
const activeMaskOptions = computed(
|
|
233
|
+
() => {
|
|
234
|
+
if (props.maskOptions && Object.keys(props.maskOptions).length > 0) {
|
|
235
|
+
if (typeof props.maskOptions.mask === "string" && Object.keys(props.maskOptions).length === 1) {
|
|
236
|
+
return props.maskOptions.mask;
|
|
237
|
+
}
|
|
238
|
+
return props.maskOptions;
|
|
239
|
+
}
|
|
240
|
+
const options = {};
|
|
241
|
+
let hasIndividualProps = false;
|
|
242
|
+
if (props.mask !== void 0) {
|
|
243
|
+
options.mask = props.mask;
|
|
244
|
+
hasIndividualProps = true;
|
|
245
|
+
}
|
|
246
|
+
if (props.maskTokens !== void 0) {
|
|
247
|
+
options.tokens = props.maskTokens;
|
|
248
|
+
hasIndividualProps = true;
|
|
130
249
|
}
|
|
131
|
-
|
|
250
|
+
if (props.maskEager !== void 0) {
|
|
251
|
+
options.eager = props.maskEager;
|
|
252
|
+
hasIndividualProps = true;
|
|
253
|
+
}
|
|
254
|
+
if (props.maskReversed !== void 0) {
|
|
255
|
+
options.reversed = props.maskReversed;
|
|
256
|
+
hasIndividualProps = true;
|
|
257
|
+
}
|
|
258
|
+
if (props.maskTokensReplace !== void 0) {
|
|
259
|
+
options.tokensReplace = props.maskTokensReplace;
|
|
260
|
+
hasIndividualProps = true;
|
|
261
|
+
}
|
|
262
|
+
if (hasIndividualProps) {
|
|
263
|
+
const keys = Object.keys(options);
|
|
264
|
+
if (keys.length === 1 && keys[0] === "mask" && typeof options.mask === "string") {
|
|
265
|
+
return options.mask;
|
|
266
|
+
}
|
|
267
|
+
return options;
|
|
268
|
+
}
|
|
269
|
+
return void 0;
|
|
132
270
|
}
|
|
133
|
-
|
|
134
|
-
});
|
|
271
|
+
);
|
|
135
272
|
</script>
|
|
@@ -13,6 +13,7 @@ export interface ITextFieldProps extends IFieldProps {
|
|
|
13
13
|
maskEager?: boolean;
|
|
14
14
|
maskTokensReplace?: boolean;
|
|
15
15
|
maskReversed?: boolean;
|
|
16
|
+
suggestions?: string[];
|
|
16
17
|
}
|
|
17
18
|
export type ITextField = IFormFieldBase<INPUT_TYPES.TEXT | INPUT_TYPES.PASSWORD | INPUT_TYPES.EMAIL, ITextFieldProps, {
|
|
18
19
|
change?: (value: string) => void;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<div class="flex h-60 items-center justify-center">
|
|
19
19
|
<Icon
|
|
20
20
|
name="i-svg-spinners:180-ring-with-bg"
|
|
21
|
-
class="size-8
|
|
21
|
+
class="text-primary size-8"
|
|
22
22
|
/>
|
|
23
23
|
</div>
|
|
24
24
|
</template>
|
|
@@ -76,7 +76,7 @@ import { computed } from "vue";
|
|
|
76
76
|
import { COLUMN_TYPES } from "#core/components/Table/types";
|
|
77
77
|
import ColumnNumber from "#core/components/Table/ColumnNumber.vue";
|
|
78
78
|
import ColumnImage from "#core/components/Table/ColumnImage.vue";
|
|
79
|
-
import {
|
|
79
|
+
import { NumberHelper } from "#core/utils/NumberHelper";
|
|
80
80
|
import { ref, watch } from "#imports";
|
|
81
81
|
import ColumnDateTime from "#core/components/Table/ColumnDateTime.vue";
|
|
82
82
|
import Empty from "#core/components/Empty.vue";
|
|
@@ -148,7 +148,7 @@ const transformValue = (column, row) => {
|
|
|
148
148
|
return column.transform ? column.transform(value, row, column) : value;
|
|
149
149
|
};
|
|
150
150
|
const totalCountWithComma = computed(() => {
|
|
151
|
-
return !props.pageOptions?.totalCount ? "0" :
|
|
151
|
+
return !props.pageOptions?.totalCount ? "0" : NumberHelper.withComma(props.pageOptions.totalCount);
|
|
152
152
|
});
|
|
153
153
|
watch(page, () => {
|
|
154
154
|
emits("pageChange", page.value);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
{{ getValue }}
|
|
2
|
+
{{ getValue }}
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script setup>
|
|
6
6
|
import { computed } from "vue";
|
|
7
|
-
import {
|
|
7
|
+
import { NumberHelper } from "#core/utils/NumberHelper";
|
|
8
8
|
const props = defineProps({
|
|
9
9
|
value: { type: null, required: true },
|
|
10
10
|
row: { type: null, required: true },
|
|
11
11
|
column: { type: null, required: true }
|
|
12
12
|
});
|
|
13
|
-
const getValue = computed(() =>
|
|
13
|
+
const getValue = computed(() => NumberHelper.withComma(props.value));
|
|
14
14
|
</script>
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
name="error"
|
|
33
33
|
>
|
|
34
34
|
<div
|
|
35
|
-
class="
|
|
35
|
+
class="
|
|
36
|
+
text-error-400 flex h-[200px] items-center justify-center text-2xl
|
|
37
|
+
"
|
|
36
38
|
>
|
|
37
39
|
{{ StringHelper.getError(options.status.errorData) }}
|
|
38
40
|
</div>
|
|
@@ -88,7 +90,7 @@
|
|
|
88
90
|
:default-page="options.pageOptions?.currentPage || 1"
|
|
89
91
|
:items-per-page="options.pageOptions.limit"
|
|
90
92
|
:total="options.pageOptions.totalCount"
|
|
91
|
-
:to="options.
|
|
93
|
+
:to="options.isRouteChange ? to : void 0"
|
|
92
94
|
@update:page="onPageChange"
|
|
93
95
|
/>
|
|
94
96
|
</div>
|
|
@@ -99,7 +101,7 @@
|
|
|
99
101
|
import { computed } from "vue";
|
|
100
102
|
import { COLUMN_TYPES } from "#core/components/Table/types";
|
|
101
103
|
import { _debounce, ref, useUiConfig, watch, useWatchChange, useRouter } from "#imports";
|
|
102
|
-
import {
|
|
104
|
+
import { NumberHelper } from "#core/utils/NumberHelper";
|
|
103
105
|
import ColumnDate from "#core/components/Table/ColumnDate.vue";
|
|
104
106
|
import ColumnDateTime from "#core/components/Table/ColumnDateTime.vue";
|
|
105
107
|
import ColumnImage from "#core/components/Table/ColumnImage.vue";
|
|
@@ -168,7 +170,7 @@ const totalCountWithComma = computed(() => {
|
|
|
168
170
|
if (!total || total <= 0) {
|
|
169
171
|
return "0";
|
|
170
172
|
}
|
|
171
|
-
return
|
|
173
|
+
return NumberHelper.withComma(total);
|
|
172
174
|
});
|
|
173
175
|
const transformValue = (column, row) => {
|
|
174
176
|
return column.cell ? column.cell({
|
|
@@ -28,7 +28,7 @@ export interface ITableOptions<T extends Record<string, any> = Record<string, an
|
|
|
28
28
|
isHideToolbar?: boolean;
|
|
29
29
|
isEnabledSearch?: boolean;
|
|
30
30
|
searchPlaceholder?: string;
|
|
31
|
-
|
|
31
|
+
isRouteChange: boolean;
|
|
32
32
|
}
|
|
33
33
|
export interface ISimpleTableOptions<T extends Record<string, any> = Record<string, any>> extends IBaseTableOptions<T> {
|
|
34
34
|
limit?: number;
|
|
@@ -7,7 +7,7 @@ export const createFlexDeckOptions = (repo, options) => {
|
|
|
7
7
|
pageOptions: get(repo.fetch.options),
|
|
8
8
|
status: get(repo.fetch.status),
|
|
9
9
|
primary: get(repo.fetch.options).primary || config.default_primary_key,
|
|
10
|
-
|
|
10
|
+
isRouteChange: false,
|
|
11
11
|
...options
|
|
12
12
|
};
|
|
13
13
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, ref
|
|
1
|
+
import { computed, ref } from "#imports";
|
|
2
2
|
export const checkMaxSize = (file, acceptFileSize = 0) => {
|
|
3
3
|
if (acceptFileSize) {
|
|
4
4
|
return file.size / 1e3 <= acceptFileSize;
|
|
@@ -90,22 +90,14 @@ export const useFileProgress = () => {
|
|
|
90
90
|
percent.value = 100;
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
|
-
percent.value =
|
|
94
|
-
StringHelper.withFixed(
|
|
95
|
-
(Math.floor(progressEvent.loaded * 100 / progressEvent.total) || 0) * 0.8
|
|
96
|
-
)
|
|
97
|
-
);
|
|
93
|
+
percent.value = (Math.floor(progressEvent.loaded * 100 / progressEvent.total) || 0) * 0.8;
|
|
98
94
|
};
|
|
99
95
|
const onDownloadProgress = (progressEvent) => {
|
|
100
96
|
if (!progressEvent.total || progressEvent.total === 0) {
|
|
101
97
|
percent.value = 100;
|
|
102
98
|
return;
|
|
103
99
|
}
|
|
104
|
-
percent.value =
|
|
105
|
-
StringHelper.withFixed(
|
|
106
|
-
Math.floor(progressEvent.loaded * 100 / progressEvent.total) * 0.2 + 80
|
|
107
|
-
)
|
|
108
|
-
);
|
|
100
|
+
percent.value = Math.floor(progressEvent.loaded * 100 / progressEvent.total) * 0.2 + 80;
|
|
109
101
|
};
|
|
110
102
|
return {
|
|
111
103
|
percent,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export const inputTheme = {
|
|
2
2
|
slots: {
|
|
3
|
-
root: "w-full"
|
|
3
|
+
root: "w-full",
|
|
4
|
+
suggestionsContainer: "absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-md shadow max-h-60 overflow-y-auto",
|
|
5
|
+
suggestionItem: "px-3 py-3 text-sm cursor-pointer hover:bg-(--ui-color-primary-100) truncate",
|
|
6
|
+
suggestionItemActive: "bg-(--ui-color-primary-100)"
|
|
4
7
|
},
|
|
5
8
|
defaultVariants: {
|
|
6
9
|
size: "lg"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class NumberHelper {
|
|
2
|
+
static withComma: (value?: number | string) => string;
|
|
3
|
+
static withFixed: (value?: number | string) => string;
|
|
4
|
+
static toCurrency: (value?: number | string, currency?: string) => string;
|
|
5
|
+
static toNumber: (value: any) => number;
|
|
6
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class NumberHelper {
|
|
2
|
+
static withComma = (value = 0) => {
|
|
3
|
+
return (+(value || 0)).toLocaleString();
|
|
4
|
+
};
|
|
5
|
+
static withFixed = (value = 0) => {
|
|
6
|
+
return (+(value || 0)).toLocaleString(void 0, {
|
|
7
|
+
minimumFractionDigits: 0,
|
|
8
|
+
maximumFractionDigits: 2
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
static toCurrency = (value = 0, currency = "") => {
|
|
12
|
+
const options = {
|
|
13
|
+
minimumFractionDigits: 2,
|
|
14
|
+
maximumFractionDigits: 2
|
|
15
|
+
};
|
|
16
|
+
if (currency) {
|
|
17
|
+
options.style = "currency";
|
|
18
|
+
options.currency = currency;
|
|
19
|
+
}
|
|
20
|
+
return (+(value || 0)).toLocaleString(void 0, options);
|
|
21
|
+
};
|
|
22
|
+
static toNumber = (value) => {
|
|
23
|
+
return Number(value) || 0;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export declare class StringHelper {
|
|
2
2
|
static genString: (length?: number) => string;
|
|
3
|
-
static withComma: (value?: number | string) => string;
|
|
4
|
-
static withFixed: (value?: number | string) => string;
|
|
5
3
|
static split: (str: string | null | undefined, separator: string | RegExp) => string[];
|
|
6
4
|
static joinURL: (...parts: string[]) => string;
|
|
7
5
|
static truncate: (str: any, num?: number) => any;
|
|
@@ -9,15 +9,6 @@ export class StringHelper {
|
|
|
9
9
|
}
|
|
10
10
|
return result;
|
|
11
11
|
};
|
|
12
|
-
static withComma = (value = 0) => {
|
|
13
|
-
return (+(value || 0)).toLocaleString();
|
|
14
|
-
};
|
|
15
|
-
static withFixed = (value = 0) => {
|
|
16
|
-
return (+(value || 0)).toLocaleString(void 0, {
|
|
17
|
-
minimumFractionDigits: 0,
|
|
18
|
-
maximumFractionDigits: 2
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
12
|
static split = (str, separator) => {
|
|
22
13
|
return `${str || ""}`.split(separator).filter((item) => item).map((item) => item.trim());
|
|
23
14
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finema/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.21.0",
|
|
4
4
|
"repository": "https://gitlab.finema.co/finema/ui-kit",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Finema Dev Core Team",
|
|
@@ -72,7 +72,6 @@
|
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@eslint/js": "^9.26.0",
|
|
75
|
-
"@hyoban/eslint-plugin-tailwindcss": "^4.0.0-alpha.12",
|
|
76
75
|
"@nuxt/devtools": "^2.4.1",
|
|
77
76
|
"@nuxt/eslint-config": "^1.3.1",
|
|
78
77
|
"@nuxt/module-builder": "^1.0.1",
|
|
@@ -81,11 +80,8 @@
|
|
|
81
80
|
"@types/node": "latest",
|
|
82
81
|
"changelogen": "^0.5.7",
|
|
83
82
|
"eslint": "^9.26.0",
|
|
84
|
-
"eslint-config-prettier": "^9.1.0",
|
|
85
|
-
"eslint-import-resolver-typescript": "^3.7.0",
|
|
86
|
-
"eslint-plugin-prettier": "^5.2.1",
|
|
87
|
-
"eslint-plugin-tailwindcss": "^3.17.5",
|
|
88
83
|
"eslint-plugin-unused-imports": "^4.1.4",
|
|
84
|
+
"eslint-plugin-better-tailwindcss": "^3.3.0",
|
|
89
85
|
"husky": "^9.1.7",
|
|
90
86
|
"lint-staged": "^16.0.0",
|
|
91
87
|
"nuxt": "3.17.3",
|