@milaboratories/uikit 2.2.65 → 2.2.67
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/CHANGELOG.md +12 -0
- package/dist/pl-uikit.js +4527 -4192
- package/dist/pl-uikit.js.map +1 -1
- package/dist/pl-uikit.umd.cjs +10 -10
- package/dist/pl-uikit.umd.cjs.map +1 -1
- package/dist/src/components/PlAutocomplete/PlAutocomplete.vue.d.ts +85 -0
- package/dist/src/components/PlAutocomplete/PlAutocomplete.vue.d.ts.map +1 -0
- package/dist/src/components/PlAutocomplete/__tests__/PlAutocomplete.spec.d.ts +2 -0
- package/dist/src/components/PlAutocomplete/__tests__/PlAutocomplete.spec.d.ts.map +1 -0
- package/dist/src/components/PlAutocomplete/index.d.ts +2 -0
- package/dist/src/components/PlAutocomplete/index.d.ts.map +1 -0
- package/dist/src/components/PlDropdown/PlDropdown.vue.d.ts.map +1 -1
- package/dist/src/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts.map +1 -1
- package/dist/src/composition/useWatchFetch.d.ts +28 -0
- package/dist/src/composition/useWatchFetch.d.ts.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/components/PlAutocomplete/PlAutocomplete.vue +413 -0
- package/src/components/PlAutocomplete/__tests__/PlAutocomplete.spec.ts +41 -0
- package/src/components/PlAutocomplete/index.ts +1 -0
- package/src/components/PlAutocomplete/pl-autocomplete.scss +277 -0
- package/src/components/PlDropdown/PlDropdown.vue +13 -6
- package/src/components/PlDropdown/pl-dropdown.scss +13 -3
- package/src/components/PlDropdownMulti/PlDropdownMulti.vue +14 -6
- package/src/components/PlDropdownMulti/pl-dropdown-multi.scss +39 -25
- package/src/composition/useWatchFetch.ts +68 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
@use "@/assets/mixins" as *;
|
|
2
|
+
|
|
3
|
+
.pl-autocomplete__options {
|
|
4
|
+
--option-hover-bg: var(--btn-sec-hover-grey);
|
|
5
|
+
|
|
6
|
+
z-index: var(--z-dropdown-options);
|
|
7
|
+
border: 1px solid var(--border-color-div-grey);
|
|
8
|
+
position: absolute;
|
|
9
|
+
background-color: var(--pl-dropdown-options-bg);
|
|
10
|
+
border-radius: 6px;
|
|
11
|
+
max-height: 244px;
|
|
12
|
+
box-shadow: 0px 4px 12px -2px rgba(15, 36, 77, 0.08), 0px 6px 24px -2px rgba(15, 36, 77, 0.08);
|
|
13
|
+
|
|
14
|
+
@include scrollbar;
|
|
15
|
+
|
|
16
|
+
.nothing-found {
|
|
17
|
+
padding: 0 10px;
|
|
18
|
+
height: var(--control-height);
|
|
19
|
+
line-height: var(--control-height);
|
|
20
|
+
background-color: #fff;
|
|
21
|
+
opacity: 0.5;
|
|
22
|
+
font-style: italic;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.option {
|
|
26
|
+
position: relative;
|
|
27
|
+
padding: 0 30px 0 10px;
|
|
28
|
+
height: var(--control-height);
|
|
29
|
+
line-height: var(--control-height);
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
user-select: none;
|
|
32
|
+
|
|
33
|
+
.checkmark {
|
|
34
|
+
position: absolute;
|
|
35
|
+
display: none;
|
|
36
|
+
right: 10px;
|
|
37
|
+
@include abs-center-y();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
>span {
|
|
41
|
+
display: block;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
max-width: 100%;
|
|
45
|
+
text-overflow: ellipsis;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.selected {
|
|
49
|
+
background-color: var(--color-active-select);
|
|
50
|
+
|
|
51
|
+
.checkmark {
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&.active:not(.selected) {
|
|
57
|
+
background-color: var(--option-hover-bg);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&:hover {
|
|
61
|
+
background-color: var(--option-hover-bg);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.pl-autocomplete {
|
|
67
|
+
$root: &;
|
|
68
|
+
|
|
69
|
+
--contour-color: var(--txt-01);
|
|
70
|
+
--contour-border-width: 1px;
|
|
71
|
+
|
|
72
|
+
--label-offset-left-x: 8px;
|
|
73
|
+
--label-offset-right-x: 8px;
|
|
74
|
+
--label-color: var(--txt-01);
|
|
75
|
+
|
|
76
|
+
position: relative;
|
|
77
|
+
outline: none;
|
|
78
|
+
min-height: var(--control-height);
|
|
79
|
+
border-radius: 6px;
|
|
80
|
+
font-family: var(--font-family-base);
|
|
81
|
+
font-size: var(--font-size-base);
|
|
82
|
+
font-weight: var(--font-weigh-base);
|
|
83
|
+
|
|
84
|
+
&__envelope {
|
|
85
|
+
font-family: var(--control-font-family);
|
|
86
|
+
min-width: 160px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
label {
|
|
90
|
+
@include outlined-control-label();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&__container {
|
|
94
|
+
position: absolute;
|
|
95
|
+
top: 0;
|
|
96
|
+
left: 0;
|
|
97
|
+
right: 0;
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
min-height: var(--control-height);
|
|
100
|
+
color: var(--txt-01);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&__contour {
|
|
104
|
+
border-radius: var(--border-radius-control);
|
|
105
|
+
border: var(--contour-border-width) solid var(--contour-color);
|
|
106
|
+
box-shadow: var(--contour-box-shadow);
|
|
107
|
+
z-index: 0;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&__field {
|
|
112
|
+
position: relative;
|
|
113
|
+
border-radius: 6px;
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
background: transparent;
|
|
116
|
+
padding-left: 11px;
|
|
117
|
+
|
|
118
|
+
min-height: var(--control-height);
|
|
119
|
+
line-height: var(--control-height);
|
|
120
|
+
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: row;
|
|
123
|
+
align-items: center;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
|
|
126
|
+
.input-value {
|
|
127
|
+
position: absolute;
|
|
128
|
+
top: 0;
|
|
129
|
+
left: 0;
|
|
130
|
+
bottom: 0;
|
|
131
|
+
right: 0;
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: row;
|
|
134
|
+
align-items: center;
|
|
135
|
+
padding: 0 60px 0 11px; // @TODO padding-right based on controls width
|
|
136
|
+
pointer-events: none;
|
|
137
|
+
line-height: 20px;
|
|
138
|
+
color: var(--txt-01);
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
white-space: pre;
|
|
141
|
+
text-overflow: ellipsis;
|
|
142
|
+
cursor: inherit;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
input {
|
|
146
|
+
min-height: calc(var(--control-height) - 2px);
|
|
147
|
+
line-height: 20px;
|
|
148
|
+
font-family: inherit;
|
|
149
|
+
font-size: inherit;
|
|
150
|
+
background-color: transparent;
|
|
151
|
+
border: none;
|
|
152
|
+
padding: 0;
|
|
153
|
+
width: calc(100% - 40px);
|
|
154
|
+
color: var(--txt-01);
|
|
155
|
+
caret-color: var(--border-color-focus);
|
|
156
|
+
|
|
157
|
+
&:focus {
|
|
158
|
+
outline: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
&:placeholder-shown {
|
|
162
|
+
text-overflow: ellipsis;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
&::placeholder {
|
|
166
|
+
color: var(--color-placeholder);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&__helper {
|
|
172
|
+
@include field-helper();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
&__error {
|
|
176
|
+
@include field-error();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
&__controls {
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: row;
|
|
182
|
+
align-items: center;
|
|
183
|
+
min-height: var(--control-height);
|
|
184
|
+
gap: 6px;
|
|
185
|
+
|
|
186
|
+
margin-left: auto;
|
|
187
|
+
|
|
188
|
+
.icon-delete-clear {
|
|
189
|
+
cursor: pointer;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.mask-16,
|
|
193
|
+
.mask-24 {
|
|
194
|
+
background-color: var(--control-mask-fill);
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.mask-loading {
|
|
199
|
+
animation: spin 2.5s linear infinite;
|
|
200
|
+
background-color: #07AD3E;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
&__arrow-wrapper {
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
min-height: var(--control-height);
|
|
208
|
+
padding-right: 11px;
|
|
209
|
+
}
|
|
210
|
+
.arrow-icon {
|
|
211
|
+
cursor: pointer;
|
|
212
|
+
|
|
213
|
+
// Default "arrow" icon (16x16)
|
|
214
|
+
&.arrow-icon-default {
|
|
215
|
+
transition: transform .2s;
|
|
216
|
+
background-color: var(--control-mask-fill);
|
|
217
|
+
@include mask(url('@/assets/images/16_chevron-down.svg'), 16px);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
&.open,
|
|
222
|
+
&:focus-within {
|
|
223
|
+
z-index: 1;
|
|
224
|
+
--label-color: var(--txt-focus);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
&.open {
|
|
228
|
+
#{$root}__container {
|
|
229
|
+
z-index: 1000;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#{$root}__field {
|
|
233
|
+
border-radius: 6px 6px 0 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.arrow-icon {
|
|
237
|
+
&.arrow-icon-default {
|
|
238
|
+
background-color: var(--control-mask-fill);
|
|
239
|
+
transform: rotate(-180deg);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
&:hover {
|
|
245
|
+
--contour-color: var(--control-hover-color);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
&:focus-within:not(.error) {
|
|
249
|
+
--label-color: var(--txt-focus);
|
|
250
|
+
--contour-color: var(--border-color-focus);
|
|
251
|
+
--contour-border-width: 2px;
|
|
252
|
+
--contour-box-shadow: 0 0 0 4px var(--border-color-focus-shadow);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
&:focus-within.error {
|
|
256
|
+
--contour-border-width: 2px;
|
|
257
|
+
--contour-box-shadow: 0 0 0 4px var(--color-error-shadow);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
&.error {
|
|
261
|
+
--contour-color: var(--txt-error);
|
|
262
|
+
--label-color: var(--txt-error);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
&.disabled {
|
|
266
|
+
--contour-color: var(--color-dis-01);
|
|
267
|
+
--control-mask-fill: var(--color-dis-01);
|
|
268
|
+
--label-color: var(--color-dis-01);
|
|
269
|
+
cursor: not-allowed;
|
|
270
|
+
pointer-events: none;
|
|
271
|
+
user-select: none;
|
|
272
|
+
|
|
273
|
+
.input-value {
|
|
274
|
+
color: var(--dis-01);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -223,7 +223,12 @@ const clear = () => emit('update:modelValue', undefined);
|
|
|
223
223
|
|
|
224
224
|
const setFocusOnInput = () => input.value?.focus();
|
|
225
225
|
|
|
226
|
-
const toggleOpen = () =>
|
|
226
|
+
const toggleOpen = () => {
|
|
227
|
+
data.open = !data.open;
|
|
228
|
+
if (!data.open) {
|
|
229
|
+
data.search = '';
|
|
230
|
+
}
|
|
231
|
+
};
|
|
227
232
|
|
|
228
233
|
const onInputFocus = () => (data.open = true);
|
|
229
234
|
|
|
@@ -298,7 +303,7 @@ watchPostEffect(() => {
|
|
|
298
303
|
</script>
|
|
299
304
|
|
|
300
305
|
<template>
|
|
301
|
-
<div class="pl-dropdown__envelope">
|
|
306
|
+
<div class="pl-dropdown__envelope" @click="setFocusOnInput">
|
|
302
307
|
<div
|
|
303
308
|
ref="rootRef"
|
|
304
309
|
:tabindex="tabindex"
|
|
@@ -321,7 +326,7 @@ watchPostEffect(() => {
|
|
|
321
326
|
@focus="onInputFocus"
|
|
322
327
|
/>
|
|
323
328
|
|
|
324
|
-
<div v-if="!data.open" class="input-value"
|
|
329
|
+
<div v-if="!data.open" class="input-value">
|
|
325
330
|
<LongText> {{ textValue }} </LongText>
|
|
326
331
|
</div>
|
|
327
332
|
|
|
@@ -329,9 +334,11 @@ watchPostEffect(() => {
|
|
|
329
334
|
<PlMaskIcon24 v-if="isLoadingOptions" name="loading" />
|
|
330
335
|
<PlIcon16 v-if="clearable && hasValue" name="delete-clear" @click.stop="clear" />
|
|
331
336
|
<slot name="append" />
|
|
332
|
-
<div
|
|
333
|
-
|
|
334
|
-
|
|
337
|
+
<div class="pl-dropdown__arrow-wrapper" @click.stop="toggleOpen">
|
|
338
|
+
<div v-if="arrowIconLarge" class="arrow-icon" :class="[`icon-24 ${arrowIconLarge}`]" />
|
|
339
|
+
<div v-else-if="arrowIcon" class="arrow-icon" :class="[`icon-16 ${arrowIcon}`]" />
|
|
340
|
+
<div v-else class="arrow-icon arrow-icon-default" />
|
|
341
|
+
</div>
|
|
335
342
|
</div>
|
|
336
343
|
</div>
|
|
337
344
|
<label v-if="label">
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
border-radius: 6px;
|
|
114
114
|
overflow: hidden;
|
|
115
115
|
background: transparent;
|
|
116
|
-
padding:
|
|
116
|
+
padding-left: 11px;
|
|
117
117
|
|
|
118
118
|
min-height: var(--control-height);
|
|
119
119
|
line-height: var(--control-height);
|
|
@@ -199,11 +199,19 @@
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
&__arrow-wrapper {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
min-height: var(--control-height);
|
|
206
|
+
padding-right: 11px;
|
|
207
|
+
}
|
|
208
|
+
|
|
202
209
|
.arrow-icon {
|
|
203
210
|
cursor: pointer;
|
|
204
211
|
|
|
205
212
|
// Default "arrow" icon (16x16)
|
|
206
213
|
&.arrow-icon-default {
|
|
214
|
+
transition: transform .2s;
|
|
207
215
|
background-color: var(--control-mask-fill);
|
|
208
216
|
@include mask(url('@/assets/images/16_chevron-down.svg'), 16px);
|
|
209
217
|
}
|
|
@@ -225,8 +233,10 @@
|
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
.arrow-icon {
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
&.arrow-icon-default {
|
|
237
|
+
background-color: var(--control-mask-fill);
|
|
238
|
+
transform: rotate(-180deg);
|
|
239
|
+
}
|
|
230
240
|
}
|
|
231
241
|
}
|
|
232
242
|
|
|
@@ -164,7 +164,12 @@ const unselectOption = (d: M) => emitModel(unref(selectedValuesRef).filter((v) =
|
|
|
164
164
|
|
|
165
165
|
const setFocusOnInput = () => input.value?.focus();
|
|
166
166
|
|
|
167
|
-
const toggleModel = () =>
|
|
167
|
+
const toggleModel = () => {
|
|
168
|
+
data.open = !data.open;
|
|
169
|
+
if (!data.open) {
|
|
170
|
+
data.search = '';
|
|
171
|
+
}
|
|
172
|
+
};
|
|
168
173
|
|
|
169
174
|
const onFocusOut = (event: FocusEvent) => {
|
|
170
175
|
const relatedTarget = event.relatedTarget as Node | null;
|
|
@@ -232,7 +237,7 @@ watchPostEffect(() => {
|
|
|
232
237
|
</script>
|
|
233
238
|
|
|
234
239
|
<template>
|
|
235
|
-
<div class="pl-dropdown-multi__envelope">
|
|
240
|
+
<div class="pl-dropdown-multi__envelope" @click="setFocusOnInput">
|
|
236
241
|
<div
|
|
237
242
|
ref="rootRef"
|
|
238
243
|
:tabindex="tabindex"
|
|
@@ -254,15 +259,18 @@ watchPostEffect(() => {
|
|
|
254
259
|
autocomplete="chrome-off"
|
|
255
260
|
@focus="data.open = true"
|
|
256
261
|
/>
|
|
257
|
-
<div v-if="!data.open" class="chips-container"
|
|
262
|
+
<div v-if="!data.open" class="chips-container">
|
|
258
263
|
<PlChip v-for="(opt, i) in selectedOptionsRef" :key="i" closeable small @click.stop="data.open = true" @close="unselectOption(opt.value)">
|
|
259
264
|
{{ opt.label || opt.value }}
|
|
260
265
|
</PlChip>
|
|
261
266
|
</div>
|
|
262
|
-
|
|
263
|
-
<div
|
|
264
|
-
|
|
267
|
+
|
|
268
|
+
<div class="pl-dropdown-multi__controls">
|
|
269
|
+
<PlMaskIcon24 v-if="isLoadingOptions" name="loading" />
|
|
265
270
|
<slot name="append" />
|
|
271
|
+
<div class="pl-dropdown-multi__arrow-wrapper" @click.stop="toggleModel">
|
|
272
|
+
<div class="arrow-icon arrow-icon-default" />
|
|
273
|
+
</div>
|
|
266
274
|
</div>
|
|
267
275
|
</div>
|
|
268
276
|
<label v-if="label">
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
border-radius: 6px;
|
|
130
130
|
overflow: hidden;
|
|
131
131
|
background: transparent;
|
|
132
|
-
padding:
|
|
132
|
+
padding-left: 11px;
|
|
133
133
|
|
|
134
134
|
min-height: var(--control-height);
|
|
135
135
|
line-height: 20px;
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
bottom: 0;
|
|
147
147
|
right: 30px;
|
|
148
148
|
overflow: hidden;
|
|
149
|
-
padding: 0 11px;
|
|
149
|
+
padding: 0 60px 0 11px;
|
|
150
150
|
line-height: 20px;
|
|
151
151
|
color: var(--contour-color);
|
|
152
152
|
display: flex;
|
|
@@ -186,43 +186,57 @@
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
&
|
|
190
|
-
@include field-helper();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
&__error {
|
|
194
|
-
@include field-error();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
&__append {
|
|
198
|
-
padding-right: 24px;
|
|
189
|
+
&__controls {
|
|
199
190
|
display: flex;
|
|
200
191
|
flex-direction: row;
|
|
201
192
|
align-items: center;
|
|
202
|
-
gap:
|
|
193
|
+
gap: 6px;
|
|
194
|
+
margin-left: auto;
|
|
203
195
|
|
|
204
|
-
.
|
|
196
|
+
.mask-16,
|
|
197
|
+
.mask-24 {
|
|
198
|
+
background-color: var(--control-mask-fill);
|
|
205
199
|
cursor: pointer;
|
|
206
200
|
}
|
|
207
201
|
|
|
208
|
-
.mask {
|
|
202
|
+
.mask-loading {
|
|
203
|
+
animation: spin 2.5s linear infinite;
|
|
204
|
+
background-color: #07AD3E;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
&__arrow-wrapper {
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
min-height: var(--control-height);
|
|
212
|
+
padding-right: 11px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.arrow-icon {
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
|
|
218
|
+
// Default "arrow" icon (16x16)
|
|
219
|
+
&.arrow-icon-default {
|
|
220
|
+
transition: transform .2s;
|
|
209
221
|
background-color: var(--control-mask-fill);
|
|
210
|
-
|
|
222
|
+
@include mask(url('@/assets/images/16_chevron-down.svg'), 16px);
|
|
211
223
|
}
|
|
212
224
|
}
|
|
213
225
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
@include
|
|
226
|
+
&__helper {
|
|
227
|
+
@include field-helper();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
&__error {
|
|
231
|
+
@include field-error();
|
|
220
232
|
}
|
|
221
233
|
|
|
222
234
|
&.open {
|
|
223
|
-
.arrow {
|
|
224
|
-
|
|
225
|
-
|
|
235
|
+
.arrow-icon {
|
|
236
|
+
&.arrow-icon-default {
|
|
237
|
+
background-color: var(--control-mask-fill);
|
|
238
|
+
transform: rotate(-180deg);
|
|
239
|
+
}
|
|
226
240
|
}
|
|
227
241
|
}
|
|
228
242
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { WatchSource, WatchOptions } from 'vue';
|
|
2
|
+
import { reactive, watch, ref, computed } from 'vue';
|
|
3
|
+
import { exclusiveRequest } from '@milaboratories/helpers';
|
|
4
|
+
|
|
5
|
+
export type FetchResult<V, E = unknown> = {
|
|
6
|
+
loading: boolean;
|
|
7
|
+
value: V | undefined;
|
|
8
|
+
error: E;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// TODO Should we keep the old value while fetching the new value?
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Watch any reactive source and perform an asynchronous operation
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const v = useWatchFetch(
|
|
19
|
+
* watchSource,
|
|
20
|
+
* async (sourceValue) => {
|
|
21
|
+
* return await fetchDataFromApi(sourceValue);
|
|
22
|
+
* }
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* // Usage in a template
|
|
26
|
+
* <template>
|
|
27
|
+
* <div v-if="v.loading">Loading...</div>
|
|
28
|
+
* <div v-else-if="v.error">Error: {{ v.error.message }}</div>
|
|
29
|
+
* <div v-else>Data: {{ v.value }}</div>
|
|
30
|
+
* </template>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function useWatchFetch<S, V>(watchSource: WatchSource<S>, doFetch: (s: S) => Promise<V>, watchOptions?: WatchOptions): FetchResult<V> {
|
|
34
|
+
const loadingRef = ref(0);
|
|
35
|
+
|
|
36
|
+
const data = reactive({
|
|
37
|
+
loading: computed(() => loadingRef.value > 0),
|
|
38
|
+
loadingRef,
|
|
39
|
+
value: undefined as V,
|
|
40
|
+
error: undefined,
|
|
41
|
+
}) as FetchResult<V>;
|
|
42
|
+
|
|
43
|
+
const exclusive = exclusiveRequest(doFetch);
|
|
44
|
+
|
|
45
|
+
watch(
|
|
46
|
+
watchSource,
|
|
47
|
+
async (s) => {
|
|
48
|
+
data.error = undefined;
|
|
49
|
+
loadingRef.value++;
|
|
50
|
+
exclusive(s)
|
|
51
|
+
.then((res) => {
|
|
52
|
+
if (res.ok) {
|
|
53
|
+
data.value = res.value;
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.catch((err) => {
|
|
57
|
+
data.value = undefined;
|
|
58
|
+
data.error = err;
|
|
59
|
+
})
|
|
60
|
+
.finally(() => {
|
|
61
|
+
loadingRef.value--;
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
Object.assign({ immediate: true, deep: true }, watchOptions ?? {}),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return data;
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ export * from './components/PlStatusTag';
|
|
|
53
53
|
export * from './components/PlLoaderCircular';
|
|
54
54
|
export * from './components/PlSplash';
|
|
55
55
|
export * from './components/PlProgressCell';
|
|
56
|
+
export * from './components/PlAutocomplete';
|
|
56
57
|
|
|
57
58
|
export * from './components/PlFileDialog';
|
|
58
59
|
export * from './components/PlFileInput';
|
|
@@ -96,6 +97,7 @@ export { useFormState } from './composition/useFormState';
|
|
|
96
97
|
export { useQuery } from './composition/useQuery.ts';
|
|
97
98
|
export { useDraggable } from './composition/useDraggable';
|
|
98
99
|
export { useComponentProp } from './composition/useComponentProp';
|
|
100
|
+
export * from './composition/useWatchFetch';
|
|
99
101
|
|
|
100
102
|
/**
|
|
101
103
|
* Utils/Partials
|