sprintify-ui 0.0.96 → 0.0.99
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/sprintify-ui.es.js +8035 -7945
- package/dist/style.css +2 -2
- package/dist/types/src/components/BaseAutocomplete.vue.d.ts +32 -10
- package/dist/types/src/components/BaseAutocompleteDropdown.vue.d.ts +88 -0
- package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +20 -5
- package/dist/types/src/components/BaseBelongsTo.vue.d.ts +18 -3
- package/dist/types/src/components/BaseDatePicker.vue.d.ts +1 -1
- package/dist/types/src/components/BaseHasMany.vue.d.ts +17 -0
- package/dist/types/src/components/BaseLoadingCover.vue.d.ts +1 -1
- package/dist/types/src/components/BaseSwitch.vue.d.ts +1 -1
- package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +66 -6
- package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +19 -2
- package/dist/types/src/composables/clickOutside.d.ts +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/package.json +10 -10
- package/src/assets/main.css +0 -1
- package/src/components/BaseAutocomplete.vue +53 -183
- package/src/components/BaseAutocompleteDropdown.vue +344 -0
- package/src/components/BaseAutocompleteFetch.vue +8 -3
- package/src/components/BaseDatePicker.vue +155 -1
- package/src/components/BaseDropdown.stories.js +25 -0
- package/src/components/BaseDropdown.vue +2 -2
- package/src/components/BaseModalSide.stories.js +1 -3
- package/src/components/BaseModalSide.vue +5 -5
- package/src/components/BaseTagAutocomplete.stories.js +46 -1
- package/src/components/BaseTagAutocomplete.vue +184 -253
- package/src/components/BaseTagAutocompleteFetch.stories.js +4 -4
- package/src/components/BaseTagAutocompleteFetch.vue +10 -5
- package/src/composables/clickOutside.ts +13 -8
- package/src/index.ts +3 -1
- package/src/assets/pikaday.css +0 -134
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PropType } from 'vue';
|
|
1
|
+
import { PropType, ComputedRef } from 'vue';
|
|
2
2
|
import { NormalizedOption, Option } from '@/types';
|
|
3
3
|
declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
|
|
4
4
|
modelValue: {
|
|
@@ -29,6 +29,10 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
|
|
|
29
29
|
default: boolean;
|
|
30
30
|
type: BooleanConstructor;
|
|
31
31
|
};
|
|
32
|
+
loadingBottom: {
|
|
33
|
+
default: boolean;
|
|
34
|
+
type: BooleanConstructor;
|
|
35
|
+
};
|
|
32
36
|
required: {
|
|
33
37
|
default: boolean;
|
|
34
38
|
type: BooleanConstructor;
|
|
@@ -49,7 +53,25 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
|
|
|
49
53
|
default: boolean;
|
|
50
54
|
type: BooleanConstructor;
|
|
51
55
|
};
|
|
52
|
-
|
|
56
|
+
inline: {
|
|
57
|
+
default: boolean;
|
|
58
|
+
type: BooleanConstructor;
|
|
59
|
+
};
|
|
60
|
+
size: {
|
|
61
|
+
default: string;
|
|
62
|
+
type: PropType<"base" | "xs" | "sm">;
|
|
63
|
+
};
|
|
64
|
+
dropdownShow: {
|
|
65
|
+
default: string;
|
|
66
|
+
type: PropType<"focus" | "always">;
|
|
67
|
+
};
|
|
68
|
+
}, {
|
|
69
|
+
focus: () => void;
|
|
70
|
+
blur: () => void;
|
|
71
|
+
close: () => void;
|
|
72
|
+
open: () => void;
|
|
73
|
+
setKeywords: (input: string) => void;
|
|
74
|
+
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "close" | "scrollBottom" | "typing" | "open")[], "update:modelValue" | "close" | "scrollBottom" | "typing" | "open", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
|
|
53
75
|
modelValue: {
|
|
54
76
|
required: true;
|
|
55
77
|
type: PropType<Option[] | null>;
|
|
@@ -78,6 +100,10 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
|
|
|
78
100
|
default: boolean;
|
|
79
101
|
type: BooleanConstructor;
|
|
80
102
|
};
|
|
103
|
+
loadingBottom: {
|
|
104
|
+
default: boolean;
|
|
105
|
+
type: BooleanConstructor;
|
|
106
|
+
};
|
|
81
107
|
required: {
|
|
82
108
|
default: boolean;
|
|
83
109
|
type: BooleanConstructor;
|
|
@@ -98,27 +124,61 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
|
|
|
98
124
|
default: boolean;
|
|
99
125
|
type: BooleanConstructor;
|
|
100
126
|
};
|
|
127
|
+
inline: {
|
|
128
|
+
default: boolean;
|
|
129
|
+
type: BooleanConstructor;
|
|
130
|
+
};
|
|
131
|
+
size: {
|
|
132
|
+
default: string;
|
|
133
|
+
type: PropType<"base" | "xs" | "sm">;
|
|
134
|
+
};
|
|
135
|
+
dropdownShow: {
|
|
136
|
+
default: string;
|
|
137
|
+
type: PropType<"focus" | "always">;
|
|
138
|
+
};
|
|
101
139
|
}>> & {
|
|
102
|
-
|
|
140
|
+
onScrollBottom?: ((...args: any[]) => any) | undefined;
|
|
103
141
|
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
|
|
142
|
+
onClose?: ((...args: any[]) => any) | undefined;
|
|
104
143
|
onTyping?: ((...args: any[]) => any) | undefined;
|
|
105
|
-
|
|
144
|
+
onOpen?: ((...args: any[]) => any) | undefined;
|
|
106
145
|
}, {
|
|
107
146
|
filter: (option: NormalizedOption) => boolean;
|
|
108
147
|
required: boolean;
|
|
109
148
|
name: string;
|
|
110
|
-
placeholder: string;
|
|
111
149
|
loading: boolean;
|
|
150
|
+
loadingBottom: boolean;
|
|
151
|
+
size: "base" | "xs" | "sm";
|
|
152
|
+
inline: boolean;
|
|
153
|
+
placeholder: string;
|
|
112
154
|
disabled: boolean;
|
|
113
155
|
hasError: boolean;
|
|
156
|
+
dropdownShow: "focus" | "always";
|
|
114
157
|
max: number;
|
|
115
158
|
}>, {
|
|
116
|
-
empty: (_: {
|
|
159
|
+
empty: (_: {
|
|
160
|
+
focus: () => void;
|
|
161
|
+
blur: () => void;
|
|
162
|
+
open: () => void;
|
|
163
|
+
close: () => void;
|
|
164
|
+
keywords: ComputedRef<string>;
|
|
165
|
+
}) => any;
|
|
117
166
|
option: (_: {
|
|
167
|
+
focus: () => void;
|
|
168
|
+
blur: () => void;
|
|
169
|
+
open: () => void;
|
|
170
|
+
close: () => void;
|
|
171
|
+
keywords: ComputedRef<string>;
|
|
118
172
|
option: Option;
|
|
173
|
+
selected: boolean;
|
|
119
174
|
active: boolean;
|
|
120
175
|
}) => any;
|
|
121
176
|
footer: (_: {
|
|
177
|
+
focus: () => void;
|
|
178
|
+
blur: () => void;
|
|
179
|
+
open: () => void;
|
|
180
|
+
close: () => void;
|
|
181
|
+
keywords: ComputedRef<string>;
|
|
122
182
|
options: NormalizedOption[];
|
|
123
183
|
}) => any;
|
|
124
184
|
}>;
|
|
@@ -41,7 +41,7 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
|
|
|
41
41
|
default: boolean;
|
|
42
42
|
type: BooleanConstructor;
|
|
43
43
|
};
|
|
44
|
-
}, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "focus" | "
|
|
44
|
+
}, {}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "focus" | "scrollBottom" | "typing")[], "update:modelValue" | "focus" | "scrollBottom" | "typing", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
|
|
45
45
|
modelValue: {
|
|
46
46
|
required: true;
|
|
47
47
|
type: PropType<Option[]>;
|
|
@@ -84,9 +84,9 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
|
|
|
84
84
|
};
|
|
85
85
|
}>> & {
|
|
86
86
|
onFocus?: ((...args: any[]) => any) | undefined;
|
|
87
|
+
onScrollBottom?: ((...args: any[]) => any) | undefined;
|
|
87
88
|
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
|
|
88
89
|
onTyping?: ((...args: any[]) => any) | undefined;
|
|
89
|
-
onScrollBottom?: ((...args: any[]) => any) | undefined;
|
|
90
90
|
}, {
|
|
91
91
|
required: boolean;
|
|
92
92
|
placeholder: string;
|
|
@@ -96,15 +96,32 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
|
|
|
96
96
|
max: number;
|
|
97
97
|
}>, {
|
|
98
98
|
option: (_: {
|
|
99
|
+
focus: () => void;
|
|
100
|
+
blur: () => void;
|
|
101
|
+
open: () => void;
|
|
102
|
+
close: () => void;
|
|
103
|
+
keywords: import("vue").ComputedRef<string>;
|
|
99
104
|
option: Option;
|
|
105
|
+
selected: boolean;
|
|
100
106
|
active: boolean;
|
|
101
107
|
}) => any;
|
|
102
108
|
footer: (_: {
|
|
109
|
+
focus: () => void;
|
|
110
|
+
blur: () => void;
|
|
111
|
+
open: () => void;
|
|
112
|
+
close: () => void;
|
|
113
|
+
keywords: import("vue").ComputedRef<string>;
|
|
103
114
|
options: import("@/types").NormalizedOption[];
|
|
104
115
|
} & {
|
|
105
116
|
keywords: string;
|
|
106
117
|
}) => any;
|
|
107
118
|
empty: (_: {
|
|
119
|
+
focus: () => void;
|
|
120
|
+
blur: () => void;
|
|
121
|
+
open: () => void;
|
|
122
|
+
close: () => void;
|
|
123
|
+
keywords: import("vue").ComputedRef<string>;
|
|
124
|
+
} & {
|
|
108
125
|
firstSearch: boolean;
|
|
109
126
|
}) => any;
|
|
110
127
|
}>;
|
|
@@ -2,7 +2,7 @@ import { MaybeElementRef } from '@vueuse/core';
|
|
|
2
2
|
interface UseClickOutsideOptions {
|
|
3
3
|
includes?: MaybeElementRef[];
|
|
4
4
|
}
|
|
5
|
-
export declare function useClickOutside(element: MaybeElementRef, callback: (
|
|
5
|
+
export declare function useClickOutside(element: MaybeElementRef, callback: () => void, options?: UseClickOutsideOptions): {
|
|
6
6
|
stop: () => void;
|
|
7
7
|
};
|
|
8
8
|
export {};
|
|
@@ -4,6 +4,7 @@ import { I18n } from 'vue-i18n';
|
|
|
4
4
|
import { useDialogsStore } from './stores/dialogs';
|
|
5
5
|
import { useNotificationsStore } from './stores/notifications';
|
|
6
6
|
import { useSystemAlertStore } from './stores/systemAlerts';
|
|
7
|
+
import { useClickOutside } from './composables/clickOutside';
|
|
7
8
|
declare const messages: {
|
|
8
9
|
en: {
|
|
9
10
|
sui: {
|
|
@@ -294,3 +295,4 @@ export { config };
|
|
|
294
295
|
export { useDialogsStore };
|
|
295
296
|
export { useNotificationsStore };
|
|
296
297
|
export { useSystemAlertStore };
|
|
298
|
+
export { useClickOutside };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprintify-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.99",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build": "rimraf dist && vue-tsc && vite build",
|
|
6
6
|
"build-fast": "rimraf dist && vite build",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@types/scroll-lock": "^2.1.0",
|
|
59
59
|
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
|
60
60
|
"@typescript-eslint/parser": "^5.42.1",
|
|
61
|
-
"@vitejs/plugin-vue": "^
|
|
61
|
+
"@vitejs/plugin-vue": "^4.0.0",
|
|
62
62
|
"@vue/eslint-config-typescript": "^11.0.2",
|
|
63
63
|
"@vueup/vue-quill": "^1.0.0",
|
|
64
64
|
"@vueuse/core": "^9.5.0",
|
|
@@ -80,10 +80,10 @@
|
|
|
80
80
|
"object-to-formdata": "^4.4.2",
|
|
81
81
|
"pikaday": "^1.8.2",
|
|
82
82
|
"pinia": "^2.0.23",
|
|
83
|
-
"postcss": "^8.4.
|
|
84
|
-
"postcss-import": "^15.
|
|
83
|
+
"postcss": "^8.4.20",
|
|
84
|
+
"postcss-import": "^15.1.0",
|
|
85
85
|
"prettier": "^2.7.1",
|
|
86
|
-
"prettier-plugin-tailwindcss": "^0.1
|
|
86
|
+
"prettier-plugin-tailwindcss": "^0.2.1",
|
|
87
87
|
"qs": "^6.11.0",
|
|
88
88
|
"rimraf": "^3.0.2",
|
|
89
89
|
"rollup-plugin-analyzer": "^4.0.0",
|
|
@@ -91,14 +91,14 @@
|
|
|
91
91
|
"storybook-addon-mock": "^3.2.0",
|
|
92
92
|
"tailwindcss": "^3.2.4",
|
|
93
93
|
"typescript": "^4.4.4",
|
|
94
|
-
"unplugin-auto-import": "^0.
|
|
95
|
-
"vite": "^
|
|
94
|
+
"unplugin-auto-import": "^0.12.1",
|
|
95
|
+
"vite": "^4.0.0",
|
|
96
96
|
"vitepress": "^0.21.6",
|
|
97
|
-
"vue": "^3.2.
|
|
97
|
+
"vue": "^3.2.45",
|
|
98
98
|
"vue-i18n": "^9.2.2",
|
|
99
|
-
"vue-loader": "^
|
|
99
|
+
"vue-loader": "^17.0.1",
|
|
100
100
|
"vue-router": "^4.1.6",
|
|
101
|
-
"vue-tsc": "^1.0.
|
|
101
|
+
"vue-tsc": "^1.0.18"
|
|
102
102
|
},
|
|
103
103
|
"files": [
|
|
104
104
|
"src",
|
package/src/assets/main.css
CHANGED
|
@@ -61,92 +61,34 @@
|
|
|
61
61
|
<div class="relative">
|
|
62
62
|
<div
|
|
63
63
|
v-show="opened || dropdownShow == 'always'"
|
|
64
|
-
class="
|
|
64
|
+
class="w-full overflow-hidden"
|
|
65
65
|
:class="[
|
|
66
66
|
inline
|
|
67
|
-
? 'relative'
|
|
68
|
-
: 'absolute top-1 z-menu rounded border border-slate-300 bg-white shadow-md',
|
|
67
|
+
? 'relative mt-1'
|
|
68
|
+
: 'absolute top-1 z-menu min-h-[110px] w-full overflow-hidden rounded border border-slate-300 bg-white shadow-md',
|
|
69
69
|
]"
|
|
70
70
|
>
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
<BaseAutocompleteDropdown
|
|
72
|
+
:selected="normalizedModelValue"
|
|
73
|
+
:options="filteredNormalizedOptions"
|
|
74
|
+
:size="size"
|
|
75
|
+
:loading="loading"
|
|
76
|
+
:loading-bottom="loadingBottom"
|
|
77
|
+
:dropdown-class="inline ? '' : 'p-1'"
|
|
78
|
+
:keywords="keywords"
|
|
79
|
+
@select="onSelect"
|
|
80
|
+
@scroll-bottom="emit('scrollBottom')"
|
|
75
81
|
>
|
|
76
|
-
<
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
v-for="(option, index) in filteredNormalizedOptions"
|
|
87
|
-
:key="option.value"
|
|
88
|
-
class="block"
|
|
89
|
-
>
|
|
90
|
-
<button
|
|
91
|
-
class="block w-full cursor-pointer appearance-none border-none text-left focus:outline-none"
|
|
92
|
-
type="button"
|
|
93
|
-
tabindex="-1"
|
|
94
|
-
@click="onSelect(option)"
|
|
95
|
-
@mouseenter="selectionIndex = index"
|
|
96
|
-
>
|
|
97
|
-
<slot
|
|
98
|
-
name="option"
|
|
99
|
-
:option="option.option"
|
|
100
|
-
:selected="isSelected(option)"
|
|
101
|
-
:active="optionActive && optionActive.value == option.value"
|
|
102
|
-
>
|
|
103
|
-
<div
|
|
104
|
-
class="flex items-center rounded px-2 py-1 text-sm"
|
|
105
|
-
:class="[optionClass(option), optionSizeClass]"
|
|
106
|
-
>
|
|
107
|
-
<div class="grow">
|
|
108
|
-
{{ option.label }}
|
|
109
|
-
</div>
|
|
110
|
-
<div class="shrink-0">
|
|
111
|
-
<BaseIcon
|
|
112
|
-
v-if="isSelected(option)"
|
|
113
|
-
icon="heroicons:check-20-solid"
|
|
114
|
-
:class="iconClass"
|
|
115
|
-
></BaseIcon>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
</slot>
|
|
119
|
-
</button>
|
|
120
|
-
</li>
|
|
121
|
-
</ul>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
<div>
|
|
125
|
-
<div v-if="$slots.footer" class="bg-white">
|
|
126
|
-
<slot
|
|
127
|
-
:options="filteredNormalizedOptions"
|
|
128
|
-
:keywords="keywords"
|
|
129
|
-
name="footer"
|
|
130
|
-
/>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
|
|
134
|
-
<div
|
|
135
|
-
v-if="loading"
|
|
136
|
-
class="absolute inset-0 h-full w-full space-y-1 bg-white p-2"
|
|
137
|
-
>
|
|
138
|
-
<div class="space-y-1">
|
|
139
|
-
<BaseSkeleton class="h-7 w-full" delay="0ms"></BaseSkeleton>
|
|
140
|
-
<BaseSkeleton
|
|
141
|
-
class="h-7 w-full opacity-70"
|
|
142
|
-
delay="50ms"
|
|
143
|
-
></BaseSkeleton>
|
|
144
|
-
<BaseSkeleton
|
|
145
|
-
class="h-7 w-full opacity-30"
|
|
146
|
-
delay="100ms"
|
|
147
|
-
></BaseSkeleton>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
82
|
+
<template #empty="emptyProps">
|
|
83
|
+
<slot name="empty" v-bind="{ ...emptyProps, ...slotProps }" />
|
|
84
|
+
</template>
|
|
85
|
+
<template #option="optionProps">
|
|
86
|
+
<slot name="option" v-bind="{ ...optionProps, ...slotProps }" />
|
|
87
|
+
</template>
|
|
88
|
+
<template #footer="footerProps">
|
|
89
|
+
<slot name="footer" v-bind="{ ...footerProps, ...slotProps }" />
|
|
90
|
+
</template>
|
|
91
|
+
</BaseAutocompleteDropdown>
|
|
150
92
|
</div>
|
|
151
93
|
</div>
|
|
152
94
|
</div>
|
|
@@ -156,12 +98,11 @@
|
|
|
156
98
|
import { get } from 'lodash';
|
|
157
99
|
import { PropType, Ref, ComputedRef } from 'vue';
|
|
158
100
|
import { NormalizedOption, Option } from '@/types';
|
|
159
|
-
import { useInfiniteScroll } from '@vueuse/core';
|
|
160
|
-
import BaseSkeleton from '@/components/BaseSkeleton.vue';
|
|
161
101
|
import { useHasOptions } from '@/composables/hasOptions';
|
|
162
102
|
import { useField } from '@/composables/field';
|
|
163
103
|
import { BaseIcon } from './index';
|
|
164
104
|
import { useClickOutside } from '@/composables/clickOutside';
|
|
105
|
+
import BaseAutocompleteDropdown from './BaseAutocompleteDropdown.vue';
|
|
165
106
|
|
|
166
107
|
const props = defineProps({
|
|
167
108
|
modelValue: {
|
|
@@ -192,6 +133,10 @@ const props = defineProps({
|
|
|
192
133
|
default: false,
|
|
193
134
|
type: Boolean,
|
|
194
135
|
},
|
|
136
|
+
loadingBottom: {
|
|
137
|
+
default: false,
|
|
138
|
+
type: Boolean,
|
|
139
|
+
},
|
|
195
140
|
required: {
|
|
196
141
|
default: false,
|
|
197
142
|
type: Boolean,
|
|
@@ -233,12 +178,10 @@ const props = defineProps({
|
|
|
233
178
|
const emit = defineEmits([
|
|
234
179
|
'update:modelValue',
|
|
235
180
|
'typing',
|
|
236
|
-
'blur',
|
|
237
|
-
'focus',
|
|
238
|
-
'scrollBottom',
|
|
239
181
|
'clear',
|
|
240
182
|
'open',
|
|
241
183
|
'close',
|
|
184
|
+
'scrollBottom',
|
|
242
185
|
]);
|
|
243
186
|
|
|
244
187
|
const { hasErrorInternal, emitUpdate } = useField({
|
|
@@ -248,15 +191,6 @@ const { hasErrorInternal, emitUpdate } = useField({
|
|
|
248
191
|
emit: emit,
|
|
249
192
|
});
|
|
250
193
|
|
|
251
|
-
let timerId = 0;
|
|
252
|
-
const keywords = ref('');
|
|
253
|
-
const selectionIndex = ref(0);
|
|
254
|
-
const autocomplete = ref(null) as Ref<HTMLElement | null>;
|
|
255
|
-
const inputElement = ref(null) as Ref<HTMLInputElement | null>;
|
|
256
|
-
const dropdown = ref(null) as Ref<HTMLElement | null>;
|
|
257
|
-
const shouldFilter = ref(false);
|
|
258
|
-
const opened = ref(false);
|
|
259
|
-
|
|
260
194
|
const hasOptions = useHasOptions(
|
|
261
195
|
computed(() => props.modelValue),
|
|
262
196
|
computed(() => props.options),
|
|
@@ -265,7 +199,13 @@ const hasOptions = useHasOptions(
|
|
|
265
199
|
computed(() => false)
|
|
266
200
|
);
|
|
267
201
|
|
|
268
|
-
|
|
202
|
+
let timerId = 0;
|
|
203
|
+
const keywords = ref('');
|
|
204
|
+
const autocomplete = ref(null) as Ref<HTMLElement | null>;
|
|
205
|
+
const inputElement = ref(null) as Ref<HTMLInputElement | null>;
|
|
206
|
+
const shouldFilter = ref(false);
|
|
207
|
+
const opened = ref(false);
|
|
208
|
+
|
|
269
209
|
const normalizedOptions = hasOptions.normalizedOptions;
|
|
270
210
|
const normalizedModelValue =
|
|
271
211
|
hasOptions.normalizedModelValue as ComputedRef<NormalizedOption | null>;
|
|
@@ -285,30 +225,15 @@ const filteredNormalizedOptions = computed((): NormalizedOption[] => {
|
|
|
285
225
|
});
|
|
286
226
|
});
|
|
287
227
|
|
|
288
|
-
|
|
289
|
-
return (
|
|
290
|
-
filteredNormalizedOptions.value[
|
|
291
|
-
Math.min(selectionIndex.value, filteredNormalizedOptions.value.length - 1)
|
|
292
|
-
] ?? null
|
|
293
|
-
);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// Update the keywords + selectionIndex when the model value changes
|
|
228
|
+
// Update the keywords when the model value changes
|
|
297
229
|
watch(
|
|
298
230
|
() => normalizedModelValue.value,
|
|
299
231
|
() => {
|
|
300
232
|
if (normalizedModelValue.value) {
|
|
301
|
-
const index = filteredNormalizedOptions.value.findIndex(
|
|
302
|
-
(option) => option.value === normalizedModelValue.value?.value
|
|
303
|
-
);
|
|
304
|
-
if (index >= 0) {
|
|
305
|
-
selectionIndex.value = index;
|
|
306
|
-
}
|
|
307
233
|
if (props.modelValueShow) {
|
|
308
234
|
setKeywords(normalizedModelValue.value?.label);
|
|
309
235
|
}
|
|
310
236
|
} else {
|
|
311
|
-
selectionIndex.value = 0;
|
|
312
237
|
if (props.modelValueShow) {
|
|
313
238
|
setKeywords('');
|
|
314
239
|
}
|
|
@@ -317,10 +242,8 @@ watch(
|
|
|
317
242
|
{ immediate: true }
|
|
318
243
|
);
|
|
319
244
|
|
|
320
|
-
useClickOutside(autocomplete, (
|
|
321
|
-
|
|
322
|
-
close();
|
|
323
|
-
}
|
|
245
|
+
useClickOutside(autocomplete, () => {
|
|
246
|
+
close();
|
|
324
247
|
});
|
|
325
248
|
|
|
326
249
|
function open() {
|
|
@@ -335,6 +258,7 @@ function open() {
|
|
|
335
258
|
}
|
|
336
259
|
|
|
337
260
|
function close() {
|
|
261
|
+
shouldFilter.value = false;
|
|
338
262
|
opened.value = false;
|
|
339
263
|
blur();
|
|
340
264
|
timerId = setTimeout(() => {
|
|
@@ -349,13 +273,10 @@ function close() {
|
|
|
349
273
|
const onTextInput = (event: Event) => {
|
|
350
274
|
open();
|
|
351
275
|
shouldFilter.value = true;
|
|
352
|
-
selectionIndex.value = 0;
|
|
353
276
|
setKeywords(get(event, 'target.value') + '');
|
|
354
277
|
emit('typing', keywords.value);
|
|
355
|
-
dropdown.value?.scrollTo({
|
|
356
|
-
top: 0,
|
|
357
|
-
});
|
|
358
278
|
|
|
279
|
+
// If keywords is empty, emit null
|
|
359
280
|
if (keywords.value == '') {
|
|
360
281
|
update(null);
|
|
361
282
|
}
|
|
@@ -364,38 +285,15 @@ const onTextInput = (event: Event) => {
|
|
|
364
285
|
const onTextKeydown = (event: KeyboardEvent) => {
|
|
365
286
|
const key = event.key;
|
|
366
287
|
|
|
367
|
-
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (key === 'ArrowDown') {
|
|
372
|
-
event.preventDefault();
|
|
373
|
-
if (selectionIndex.value < filteredNormalizedOptions.value.length - 1) {
|
|
374
|
-
selectionIndex.value++;
|
|
375
|
-
} else {
|
|
376
|
-
selectionIndex.value = 0;
|
|
377
|
-
}
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
288
|
+
// Prevent default behavior for up/down arrows
|
|
380
289
|
|
|
381
290
|
if (key === 'ArrowUp') {
|
|
382
291
|
event.preventDefault();
|
|
383
|
-
if (selectionIndex.value > 0) {
|
|
384
|
-
selectionIndex.value--;
|
|
385
|
-
} else {
|
|
386
|
-
selectionIndex.value = Math.max(
|
|
387
|
-
0,
|
|
388
|
-
filteredNormalizedOptions.value.length - 1
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
292
|
return;
|
|
392
293
|
}
|
|
393
294
|
|
|
394
|
-
if (key === '
|
|
295
|
+
if (key === 'ArrowDown') {
|
|
395
296
|
event.preventDefault();
|
|
396
|
-
if (optionActive.value) {
|
|
397
|
-
onSelect(optionActive.value);
|
|
398
|
-
}
|
|
399
297
|
return;
|
|
400
298
|
}
|
|
401
299
|
};
|
|
@@ -414,14 +312,10 @@ function onSelect(normalizedModelValue: Option | null | undefined) {
|
|
|
414
312
|
}
|
|
415
313
|
|
|
416
314
|
function update(normalizedSelection: Option | null | undefined) {
|
|
417
|
-
|
|
418
|
-
if (selection) {
|
|
419
|
-
const index = filteredNormalizedOptions.value.findIndex(
|
|
420
|
-
(option) => option.value == selection.value
|
|
421
|
-
);
|
|
422
|
-
selectionIndex.value = index;
|
|
423
|
-
}
|
|
315
|
+
// Re-activate filter
|
|
424
316
|
shouldFilter.value = false;
|
|
317
|
+
// Emit update
|
|
318
|
+
const selection = normalizedSelection ? normalizedSelection.option : null;
|
|
425
319
|
emitUpdate(selection);
|
|
426
320
|
}
|
|
427
321
|
|
|
@@ -437,27 +331,15 @@ function blur() {
|
|
|
437
331
|
inputElement.value?.blur();
|
|
438
332
|
}
|
|
439
333
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
return 'bg-slate-200';
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return 'bg-white';
|
|
334
|
+
const slotProps = {
|
|
335
|
+
focus,
|
|
336
|
+
blur,
|
|
337
|
+
open,
|
|
338
|
+
close,
|
|
339
|
+
keywords: computed(() => keywords.value),
|
|
450
340
|
};
|
|
451
341
|
|
|
452
|
-
|
|
453
|
-
if (props.size == 'xs') {
|
|
454
|
-
return 'text-xs';
|
|
455
|
-
}
|
|
456
|
-
if (props.size == 'sm') {
|
|
457
|
-
return 'text-sm';
|
|
458
|
-
}
|
|
459
|
-
return 'text-sm';
|
|
460
|
-
});
|
|
342
|
+
// Element Classes
|
|
461
343
|
|
|
462
344
|
const inputClass = computed(() => {
|
|
463
345
|
if (props.size == 'xs') {
|
|
@@ -489,18 +371,6 @@ const iconWrapClass = computed(() => {
|
|
|
489
371
|
return 'pl-2.5';
|
|
490
372
|
});
|
|
491
373
|
|
|
492
|
-
// Infinite scroll
|
|
493
|
-
|
|
494
|
-
onMounted(() => {
|
|
495
|
-
useInfiniteScroll(
|
|
496
|
-
dropdown.value,
|
|
497
|
-
() => {
|
|
498
|
-
emit('scrollBottom');
|
|
499
|
-
},
|
|
500
|
-
{ distance: 60 }
|
|
501
|
-
);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
374
|
defineExpose({
|
|
505
375
|
focus,
|
|
506
376
|
blur,
|