@milaboratories/uikit 2.1.1 → 2.1.3
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 +14 -0
- package/dist/pl-uikit.js +3279 -2982
- package/dist/pl-uikit.umd.cjs +10 -10
- package/dist/src/components/PlDropdown/PlDropdown.vue.d.ts +5 -1
- package/dist/src/components/PlDropdownLegacy/PlDropdownLegacy.vue.d.ts +78 -0
- package/dist/src/components/PlDropdownLegacy/__tests__/PlDropdownLegacy.spec.d.ts +1 -0
- package/dist/src/components/PlDropdownLegacy/index.d.ts +1 -0
- package/dist/src/components/PlDropdownRef/PlDropdownRef.vue.d.ts +12 -2
- package/dist/src/composition/useEventListener.d.ts +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/assets/base.scss +4 -0
- package/src/assets/variables.scss +3 -1
- package/src/components/PlCheckbox/pl-checkbox.scss +2 -0
- package/src/components/PlChip/PlChip.vue +5 -5
- package/src/components/PlChip/pl-chip.scss +2 -2
- package/src/components/PlDropdown/PlDropdown.vue +86 -27
- package/src/components/PlDropdown/__tests__/PlDropdown.spec.ts +7 -6
- package/src/components/PlDropdown/pl-dropdown.scss +76 -68
- package/src/components/PlDropdownLegacy/PlDropdownLegacy.vue +370 -0
- package/src/components/PlDropdownLegacy/__tests__/PlDropdownLegacy.spec.ts +33 -0
- package/src/components/PlDropdownLegacy/index.ts +1 -0
- package/src/components/PlDropdownLegacy/pl-dropdown-legacy.scss +260 -0
- package/src/components/PlDropdownLine/PlDropdownLine.vue +81 -42
- package/src/components/PlDropdownLine/pl-dropdown-line.scss +5 -5
- package/src/components/PlDropdownMulti/PlDropdownMulti.vue +62 -27
- package/src/components/PlDropdownMulti/__tests__/PlDropdownMulti.spec.ts +12 -7
- package/src/components/PlDropdownMulti/pl-dropdown-multi.scss +11 -8
- package/src/components/PlDropdownRef/PlDropdownRef.vue +16 -3
- package/src/components/PlDropdownRef/__tests__/PlDropdownRef.spec.ts +11 -8
- package/src/composition/useEventListener.ts +3 -3
- package/src/composition/usePosition.ts +2 -2
- package/src/index.ts +1 -0
|
@@ -12,6 +12,7 @@ import DropdownListItem from '@/components/DropdownListItem.vue';
|
|
|
12
12
|
import TabItem from '@/components/TabItem.vue';
|
|
13
13
|
import type { ListOption } from '@/types';
|
|
14
14
|
import { normalizeListOptions } from '@/helpers/utils';
|
|
15
|
+
import { useElementPosition } from '@/composition/usePosition';
|
|
15
16
|
|
|
16
17
|
const emit = defineEmits(['update:modelValue']); // at the top always
|
|
17
18
|
|
|
@@ -38,6 +39,7 @@ const props = withDefaults(
|
|
|
38
39
|
const data = reactive({
|
|
39
40
|
isOpen: false,
|
|
40
41
|
activeOption: -1,
|
|
42
|
+
optionsHeight: 0,
|
|
41
43
|
});
|
|
42
44
|
|
|
43
45
|
const container = ref<HTMLElement>();
|
|
@@ -85,7 +87,7 @@ const placeholderVal = computed(() => {
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
return modelText.value
|
|
90
|
+
return modelText.value ?? '...';
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
useClickOutside(container, () => {
|
|
@@ -169,13 +171,14 @@ function isItemSelected(item: ListOption): boolean {
|
|
|
169
171
|
return deepEqual(item.value, props.modelValue);
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
const onFocusOut = (event: FocusEvent) => {
|
|
175
|
+
const relatedTarget = event.relatedTarget as Node | null;
|
|
176
|
+
|
|
177
|
+
if (!container.value?.contains(relatedTarget) && !list.value?.contains(relatedTarget)) {
|
|
176
178
|
searchPhrase.value = '';
|
|
179
|
+
data.isOpen = false;
|
|
177
180
|
}
|
|
178
|
-
}
|
|
181
|
+
};
|
|
179
182
|
|
|
180
183
|
function handleKeydown(e: { code: string; preventDefault(): void }) {
|
|
181
184
|
const { activeOption } = data;
|
|
@@ -223,6 +226,33 @@ function scrollIntoActive() {
|
|
|
223
226
|
function clearModel() {
|
|
224
227
|
emit('update:modelValue', undefined);
|
|
225
228
|
}
|
|
229
|
+
|
|
230
|
+
const optionsStyle = reactive({
|
|
231
|
+
top: '0px',
|
|
232
|
+
left: '0px',
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
watch(list, (el) => {
|
|
236
|
+
if (el) {
|
|
237
|
+
const rect = el.getBoundingClientRect();
|
|
238
|
+
data.optionsHeight = rect.height;
|
|
239
|
+
window.dispatchEvent(new CustomEvent('adjust'));
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
useElementPosition(container, (pos) => {
|
|
244
|
+
const gap = 2;
|
|
245
|
+
|
|
246
|
+
const downTopOffset = pos.top + pos.height + gap;
|
|
247
|
+
|
|
248
|
+
if (downTopOffset + data.optionsHeight > pos.clientHeight) {
|
|
249
|
+
optionsStyle.top = pos.top - data.optionsHeight - gap + 'px';
|
|
250
|
+
} else {
|
|
251
|
+
optionsStyle.top = downTopOffset + 'px';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
optionsStyle.left = pos.left + 'px';
|
|
255
|
+
});
|
|
226
256
|
</script>
|
|
227
257
|
|
|
228
258
|
<template>
|
|
@@ -231,53 +261,62 @@ function clearModel() {
|
|
|
231
261
|
ref="container"
|
|
232
262
|
tabindex="0"
|
|
233
263
|
:class="classes"
|
|
234
|
-
class="
|
|
264
|
+
class="pl-line-dropdown uc-pointer"
|
|
235
265
|
@keydown="handleKeydown"
|
|
236
|
-
@focusout="
|
|
266
|
+
@focusout="onFocusOut"
|
|
237
267
|
@click="toggleList"
|
|
238
268
|
>
|
|
239
|
-
<div class="
|
|
269
|
+
<div class="pl-line-dropdown__prefix">{{ props?.prefix }}</div>
|
|
240
270
|
|
|
241
|
-
<ResizableInput v-model="inputModel" :placeholder="placeholderVal" :disabled="props.disabled" class="
|
|
271
|
+
<ResizableInput v-model="inputModel" :placeholder="placeholderVal" :disabled="props.disabled" class="pl-line-dropdown__input" />
|
|
242
272
|
|
|
243
|
-
<div class="
|
|
244
|
-
<div v-show="!canShowClearBtn" class="
|
|
245
|
-
<div v-show="canShowClearBtn" class="
|
|
273
|
+
<div class="pl-line-dropdown__icon-wrapper">
|
|
274
|
+
<div v-show="!canShowClearBtn" class="pl-line-dropdown__icon" />
|
|
275
|
+
<div v-show="canShowClearBtn" class="pl-line-dropdown__icon-clear" @click="clearModel" />
|
|
246
276
|
</div>
|
|
247
|
-
<
|
|
248
|
-
<
|
|
249
|
-
<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
:is-selected="isItemSelected(item)"
|
|
254
|
-
:is-hovered="data.activeOption == index"
|
|
255
|
-
@click.stop="selectItem(item)"
|
|
256
|
-
>
|
|
257
|
-
<DropdownListItem
|
|
258
|
-
:option="item"
|
|
277
|
+
<Teleport v-if="data.isOpen" to="body">
|
|
278
|
+
<div v-if="props.mode === 'list'" ref="list" :style="optionsStyle" tabindex="-1" class="pl-line-dropdown__items" @focusout="onFocusOut">
|
|
279
|
+
<template v-for="(item, index) in options" :key="index">
|
|
280
|
+
<slot
|
|
281
|
+
name="item"
|
|
282
|
+
:item="item"
|
|
259
283
|
:text-item="'text'"
|
|
260
284
|
:is-selected="isItemSelected(item)"
|
|
261
285
|
:is-hovered="data.activeOption == index"
|
|
262
|
-
size="medium"
|
|
263
286
|
@click.stop="selectItem(item)"
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
287
|
+
>
|
|
288
|
+
<DropdownListItem
|
|
289
|
+
:option="item"
|
|
290
|
+
:text-item="'text'"
|
|
291
|
+
:is-selected="isItemSelected(item)"
|
|
292
|
+
:is-hovered="data.activeOption == index"
|
|
293
|
+
size="medium"
|
|
294
|
+
@click.stop="selectItem(item)"
|
|
295
|
+
/>
|
|
296
|
+
</slot>
|
|
297
|
+
</template>
|
|
298
|
+
|
|
299
|
+
<div v-if="options.length === 0" class="pl-line-dropdown__no-item">
|
|
300
|
+
<div class="pl-line-dropdown__no-item-title text-s">Didn't find anything that matched</div>
|
|
301
|
+
</div>
|
|
270
302
|
</div>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
<
|
|
303
|
+
<div
|
|
304
|
+
v-else-if="props.mode === 'tabs'"
|
|
305
|
+
ref="list"
|
|
306
|
+
:style="optionsStyle"
|
|
307
|
+
tabindex="-1"
|
|
308
|
+
class="pl-line-dropdown__items-tabs"
|
|
309
|
+
@focusout="onFocusOut"
|
|
310
|
+
>
|
|
311
|
+
<template v-for="(item, index) in options" :key="index">
|
|
312
|
+
<slot name="item" :item="item" :is-selected="isItemSelected(item)" :is-hovered="data.activeOption == index" @click.stop="selectItem(item)">
|
|
313
|
+
<TabItem :option="item" :is-selected="isItemSelected(item)" :is-hovered="data.activeOption == index" @click.stop="selectItem(item)" />
|
|
314
|
+
</slot>
|
|
315
|
+
</template>
|
|
316
|
+
<div v-if="options.length === 0" class="pl-line-dropdown__no-item">
|
|
317
|
+
<div class="pl-line-dropdown__no-item-title text-s">Didn't find anything that matched</div>
|
|
318
|
+
</div>
|
|
280
319
|
</div>
|
|
281
|
-
</
|
|
320
|
+
</Teleport>
|
|
282
321
|
</div>
|
|
283
322
|
</template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@import "@/assets/mixins";
|
|
2
2
|
|
|
3
|
-
.
|
|
3
|
+
.pl-line-dropdown {
|
|
4
4
|
display: flex;
|
|
5
5
|
align-items: center;
|
|
6
6
|
width: fit-content;
|
|
@@ -113,8 +113,8 @@
|
|
|
113
113
|
|
|
114
114
|
&__items {
|
|
115
115
|
position: absolute;
|
|
116
|
-
top:
|
|
117
|
-
z-index:
|
|
116
|
+
top: 0;
|
|
117
|
+
z-index: var(--z-dropdown-options);
|
|
118
118
|
border-radius: 6px;
|
|
119
119
|
padding: 12px 0;
|
|
120
120
|
border: 1px solid var(--color-div-grey);
|
|
@@ -130,8 +130,8 @@
|
|
|
130
130
|
&__items-tabs {
|
|
131
131
|
display: flex;
|
|
132
132
|
position: absolute;
|
|
133
|
-
top:
|
|
134
|
-
z-index:
|
|
133
|
+
top: 0;
|
|
134
|
+
z-index: var(--z-dropdown-options);
|
|
135
135
|
background-color: var(--color-div-bw);
|
|
136
136
|
overflow-x: scroll;
|
|
137
137
|
max-width: 400px;
|
|
@@ -20,6 +20,7 @@ import { scrollIntoView } from '@/helpers/dom';
|
|
|
20
20
|
import DropdownListItem from '@/components/DropdownListItem.vue';
|
|
21
21
|
import { deepEqual, deepIncludes } from '@/helpers/objects';
|
|
22
22
|
import { normalizeListOptions } from '@/helpers/utils';
|
|
23
|
+
import { useElementPosition } from '@/composition/usePosition';
|
|
23
24
|
|
|
24
25
|
const emit = defineEmits<{
|
|
25
26
|
(e: 'update:modelValue', v: M[]): void;
|
|
@@ -83,6 +84,7 @@ const data = reactive({
|
|
|
83
84
|
search: '',
|
|
84
85
|
activeOption: -1,
|
|
85
86
|
open: false,
|
|
87
|
+
optionsHeight: 0,
|
|
86
88
|
});
|
|
87
89
|
|
|
88
90
|
const selectedValuesRef = computed(() => (Array.isArray(props.modelValue) ? props.modelValue : []));
|
|
@@ -149,7 +151,9 @@ const setFocusOnInput = () => input.value?.focus();
|
|
|
149
151
|
const toggleModel = () => (data.open = !data.open);
|
|
150
152
|
|
|
151
153
|
const onFocusOut = (event: FocusEvent) => {
|
|
152
|
-
|
|
154
|
+
const relatedTarget = event.relatedTarget as Node | null;
|
|
155
|
+
|
|
156
|
+
if (!rootRef.value?.contains(relatedTarget) && !list.value?.contains(relatedTarget)) {
|
|
153
157
|
data.search = '';
|
|
154
158
|
data.open = false;
|
|
155
159
|
}
|
|
@@ -220,20 +224,49 @@ watchPostEffect(() => {
|
|
|
220
224
|
scrollIntoActive();
|
|
221
225
|
}
|
|
222
226
|
});
|
|
227
|
+
|
|
228
|
+
const optionsStyle = reactive({
|
|
229
|
+
top: '0px',
|
|
230
|
+
left: '0px',
|
|
231
|
+
width: '0px',
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
watch(list, (el) => {
|
|
235
|
+
if (el) {
|
|
236
|
+
const rect = el.getBoundingClientRect();
|
|
237
|
+
data.optionsHeight = rect.height;
|
|
238
|
+
window.dispatchEvent(new CustomEvent('adjust'));
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
useElementPosition(rootRef, (pos) => {
|
|
243
|
+
const focusWidth = 5; // see css
|
|
244
|
+
|
|
245
|
+
const downTopOffset = pos.top + pos.height + focusWidth;
|
|
246
|
+
|
|
247
|
+
if (downTopOffset + data.optionsHeight > pos.clientHeight) {
|
|
248
|
+
optionsStyle.top = pos.top - data.optionsHeight - focusWidth + 'px';
|
|
249
|
+
} else {
|
|
250
|
+
optionsStyle.top = downTopOffset + 'px';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
optionsStyle.left = pos.left + 'px';
|
|
254
|
+
optionsStyle.width = pos.width + 'px';
|
|
255
|
+
});
|
|
223
256
|
</script>
|
|
224
257
|
|
|
225
258
|
<template>
|
|
226
|
-
<div class="
|
|
259
|
+
<div class="pl-multi-dropdown__envelope">
|
|
227
260
|
<div
|
|
228
261
|
ref="rootRef"
|
|
229
262
|
:tabindex="tabindex"
|
|
230
|
-
class="
|
|
263
|
+
class="pl-multi-dropdown"
|
|
231
264
|
:class="{ open: data.open, error, disabled }"
|
|
232
265
|
@keydown="handleKeydown"
|
|
233
266
|
@focusout="onFocusOut"
|
|
234
267
|
>
|
|
235
|
-
<div class="
|
|
236
|
-
<div class="
|
|
268
|
+
<div class="pl-multi-dropdown__container">
|
|
269
|
+
<div class="pl-multi-dropdown__field">
|
|
237
270
|
<input
|
|
238
271
|
ref="input"
|
|
239
272
|
v-model="data.search"
|
|
@@ -251,7 +284,7 @@ watchPostEffect(() => {
|
|
|
251
284
|
</PlChip>
|
|
252
285
|
</div>
|
|
253
286
|
<div class="arrow" @click.stop="toggleModel" />
|
|
254
|
-
<div class="
|
|
287
|
+
<div class="pl-multi-dropdown__append">
|
|
255
288
|
<slot name="append" />
|
|
256
289
|
</div>
|
|
257
290
|
</div>
|
|
@@ -264,29 +297,31 @@ watchPostEffect(() => {
|
|
|
264
297
|
</template>
|
|
265
298
|
</PlTooltip>
|
|
266
299
|
</label>
|
|
267
|
-
<
|
|
268
|
-
<div class="
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
300
|
+
<Teleport v-if="data.open" to="body">
|
|
301
|
+
<div ref="list" class="pl-multi-dropdown__options" :style="optionsStyle" tabindex="-1" @focusout="onFocusOut">
|
|
302
|
+
<div class="pl-multi-dropdown__open-chips-container">
|
|
303
|
+
<PlChip v-for="(opt, i) in selectedOptionsRef" :key="i" closeable small @close="unselectOption(opt.value)">
|
|
304
|
+
{{ opt.label || opt.value }}
|
|
305
|
+
</PlChip>
|
|
306
|
+
</div>
|
|
307
|
+
<DropdownListItem
|
|
308
|
+
v-for="(item, index) in filteredOptionsRef"
|
|
309
|
+
:key="index"
|
|
310
|
+
:option="item"
|
|
311
|
+
:text-item="'text'"
|
|
312
|
+
:is-selected="item.selected"
|
|
313
|
+
:is-hovered="data.activeOption == index"
|
|
314
|
+
size="medium"
|
|
315
|
+
use-checkbox
|
|
316
|
+
@click.stop="selectOption(item.value)"
|
|
317
|
+
/>
|
|
318
|
+
<div v-if="!filteredOptionsRef.length" class="nothing-found">Nothing found</div>
|
|
272
319
|
</div>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
:key="index"
|
|
276
|
-
:option="item"
|
|
277
|
-
:text-item="'text'"
|
|
278
|
-
:is-selected="item.selected"
|
|
279
|
-
:is-hovered="data.activeOption == index"
|
|
280
|
-
size="medium"
|
|
281
|
-
use-checkbox
|
|
282
|
-
@click.stop="selectOption(item.value)"
|
|
283
|
-
/>
|
|
284
|
-
<div v-if="!filteredOptionsRef.length" class="nothing-found">Nothing found</div>
|
|
285
|
-
</div>
|
|
286
|
-
<DoubleContour class="ui-multi-dropdown__contour" />
|
|
320
|
+
</Teleport>
|
|
321
|
+
<DoubleContour class="pl-multi-dropdown__contour" />
|
|
287
322
|
</div>
|
|
288
323
|
</div>
|
|
289
|
-
<div v-if="error" class="
|
|
290
|
-
<div v-else-if="helper" class="
|
|
324
|
+
<div v-if="error" class="pl-multi-dropdown__error">{{ error }}</div>
|
|
325
|
+
<div v-else-if="helper" class="pl-multi-dropdown__helper">{{ helper }}</div>
|
|
291
326
|
</div>
|
|
292
327
|
</template>
|
|
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
import { mount } from '@vue/test-utils';
|
|
4
4
|
import PlDropdown from '../PlDropdownMulti.vue';
|
|
5
|
+
import { delay } from '@milaboratories/helpers';
|
|
5
6
|
|
|
6
7
|
describe('PlDropdownMulti', () => {
|
|
7
8
|
it('modelValue', async () => {
|
|
@@ -18,16 +19,20 @@ describe('PlDropdownMulti', () => {
|
|
|
18
19
|
|
|
19
20
|
await wrapper.find('input').trigger('focus');
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
const getOptions = () => [...document.body.querySelectorAll('.dropdown-list-item')] as HTMLElement[];
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const options = getOptions();
|
|
25
|
+
|
|
26
|
+
console.log('options', options);
|
|
27
|
+
|
|
28
|
+
expect(options.length).toBe(2);
|
|
29
|
+
|
|
30
|
+
options[1].click();
|
|
31
|
+
|
|
32
|
+
await delay(20);
|
|
28
33
|
|
|
29
34
|
expect(wrapper.props('modelValue')).toEqual([1, 2]);
|
|
30
35
|
|
|
31
|
-
expect(
|
|
36
|
+
expect(getOptions().length).toBe(2); // options are not closed after click
|
|
32
37
|
});
|
|
33
38
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@import "@/assets/mixins";
|
|
2
2
|
|
|
3
|
-
.
|
|
3
|
+
.pl-multi-dropdown {
|
|
4
4
|
$root: &;
|
|
5
5
|
|
|
6
6
|
--contour-color: var(--txt-01);
|
|
@@ -53,11 +53,14 @@
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
&__options {
|
|
56
|
-
position:
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
position: absolute;
|
|
57
|
+
top: 0;
|
|
58
|
+
z-index: var(--z-dropdown-options);
|
|
59
|
+
border: 1px solid var(--border-color-div-grey);
|
|
60
|
+
background-color: var(--pl-dropdown-options-bg);
|
|
61
|
+
border-radius: 6px;
|
|
59
62
|
max-height: 244px;
|
|
60
|
-
|
|
63
|
+
box-shadow: 0px 4px 12px -2px rgba(15, 36, 77, 0.08), 0px 6px 24px -2px rgba(15, 36, 77, 0.08);
|
|
61
64
|
@include scrollbar;
|
|
62
65
|
|
|
63
66
|
.nothing-found {
|
|
@@ -83,7 +86,7 @@
|
|
|
83
86
|
--base-icon: url('@/assets/images/24_checkbox-base.svg');
|
|
84
87
|
--checked-icon: url('@/assets/images/24_checkbox-checked.svg');
|
|
85
88
|
|
|
86
|
-
.
|
|
89
|
+
.pl-multi-dropdown__checkmark {
|
|
87
90
|
cursor: pointer;
|
|
88
91
|
outline: none;
|
|
89
92
|
border-radius: 4px;
|
|
@@ -106,7 +109,7 @@
|
|
|
106
109
|
&.selected {
|
|
107
110
|
background-color: var(--color-active-select);
|
|
108
111
|
|
|
109
|
-
.
|
|
112
|
+
.pl-multi-dropdown__checkmark {
|
|
110
113
|
@include icon(var(--checked-icon), 24px);
|
|
111
114
|
}
|
|
112
115
|
}
|
|
@@ -290,7 +293,7 @@
|
|
|
290
293
|
&__open-chips-container {
|
|
291
294
|
padding: 12px;
|
|
292
295
|
|
|
293
|
-
.
|
|
296
|
+
.pl-chip {
|
|
294
297
|
margin-right: 4px;
|
|
295
298
|
margin-bottom: 4px;
|
|
296
299
|
}
|
|
@@ -32,11 +32,15 @@ const props = withDefaults(
|
|
|
32
32
|
/**
|
|
33
33
|
* List of available ref options for the dropdown
|
|
34
34
|
*/
|
|
35
|
-
options
|
|
35
|
+
options?: Readonly<RefOption[]>;
|
|
36
36
|
/**
|
|
37
37
|
* A helper text displayed below the dropdown when there are no errors (optional).
|
|
38
38
|
*/
|
|
39
39
|
helper?: string;
|
|
40
|
+
/**
|
|
41
|
+
* A helper text displayed below the dropdown when there are no options yet or options is undefined (optional).
|
|
42
|
+
*/
|
|
43
|
+
loadingOptionsHelper?: string;
|
|
40
44
|
/**
|
|
41
45
|
* Error message displayed below the dropdown (optional)
|
|
42
46
|
*/
|
|
@@ -65,6 +69,7 @@ const props = withDefaults(
|
|
|
65
69
|
{
|
|
66
70
|
label: '',
|
|
67
71
|
helper: undefined,
|
|
72
|
+
loadingOptionsHelper: undefined,
|
|
68
73
|
error: undefined,
|
|
69
74
|
placeholder: '...',
|
|
70
75
|
clearable: false,
|
|
@@ -72,11 +77,12 @@ const props = withDefaults(
|
|
|
72
77
|
disabled: false,
|
|
73
78
|
arrowIcon: undefined,
|
|
74
79
|
optionSize: 'small',
|
|
80
|
+
options: undefined,
|
|
75
81
|
},
|
|
76
82
|
);
|
|
77
83
|
|
|
78
84
|
const options = computed(() =>
|
|
79
|
-
props.options
|
|
85
|
+
props.options?.map((opt) => ({
|
|
80
86
|
label: opt.label,
|
|
81
87
|
value: opt.ref,
|
|
82
88
|
})),
|
|
@@ -86,5 +92,12 @@ const arrowIcon = computed(() => (props.disabled ? 'icon-link-disabled' : 'icon-
|
|
|
86
92
|
</script>
|
|
87
93
|
|
|
88
94
|
<template>
|
|
89
|
-
<PlDropdown
|
|
95
|
+
<PlDropdown
|
|
96
|
+
v-bind="props"
|
|
97
|
+
:options="options"
|
|
98
|
+
:loading-options-helper="loadingOptionsHelper"
|
|
99
|
+
:arrow-icon-large="arrowIcon"
|
|
100
|
+
@update:model-value="$emit('update:modelValue', $event)"
|
|
101
|
+
>
|
|
102
|
+
</PlDropdown>
|
|
90
103
|
</template>
|
|
@@ -2,6 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
import { mount } from '@vue/test-utils';
|
|
4
4
|
import PlDropdownRef from '../PlDropdownRef.vue';
|
|
5
|
+
import { delay } from '@milaboratories/helpers';
|
|
5
6
|
|
|
6
7
|
describe('PlDropdownRef', () => {
|
|
7
8
|
it('modelValue', async () => {
|
|
@@ -9,8 +10,8 @@ describe('PlDropdownRef', () => {
|
|
|
9
10
|
props: {
|
|
10
11
|
modelValue: {
|
|
11
12
|
__isRef: true as const,
|
|
12
|
-
blockId: '
|
|
13
|
-
name: 'Ref to block
|
|
13
|
+
blockId: '1',
|
|
14
|
+
name: 'Ref to block 1',
|
|
14
15
|
},
|
|
15
16
|
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
|
|
16
17
|
options: [
|
|
@@ -36,13 +37,15 @@ describe('PlDropdownRef', () => {
|
|
|
36
37
|
|
|
37
38
|
await wrapper.find('input').trigger('focus');
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
const options = [...document.body.querySelectorAll('.dropdown-list-item')] as HTMLElement[];
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
expect(options.length).toBe(2);
|
|
43
|
+
|
|
44
|
+
expect(options.length).toBe(2);
|
|
45
|
+
|
|
46
|
+
options[1].click();
|
|
47
|
+
|
|
48
|
+
await delay(20);
|
|
46
49
|
|
|
47
50
|
expect(wrapper.props('modelValue')).toStrictEqual({
|
|
48
51
|
__isRef: true as const,
|
|
@@ -7,8 +7,8 @@ export function useEventListener<T extends EventTarget, E extends EventMap[K], K
|
|
|
7
7
|
target: MaybeRef<T | undefined>,
|
|
8
8
|
type: K,
|
|
9
9
|
callback: (this: T, evt: E) => void,
|
|
10
|
-
|
|
10
|
+
options?: AddEventListenerOptions | boolean,
|
|
11
11
|
) {
|
|
12
|
-
onMounted(() => unref(target)?.addEventListener(type, callback as (this: T, evt: Event) => void,
|
|
13
|
-
onUnmounted(() => unref(target)?.removeEventListener(type, callback as (this: T, evt: Event) => void,
|
|
12
|
+
onMounted(() => unref(target)?.addEventListener(type, callback as (this: T, evt: Event) => void, options));
|
|
13
|
+
onUnmounted(() => unref(target)?.removeEventListener(type, callback as (this: T, evt: Event) => void, options));
|
|
14
14
|
}
|
|
@@ -33,9 +33,9 @@ export function useElementPosition(el: Ref<HTMLElement | undefined>, cb: (pos: E
|
|
|
33
33
|
|
|
34
34
|
onMounted(handle);
|
|
35
35
|
|
|
36
|
-
useEventListener(window, 'scroll', handle, true);
|
|
36
|
+
useEventListener(window, 'scroll', handle, { capture: true, passive: true });
|
|
37
37
|
|
|
38
|
-
useEventListener(window, 'resize', handle, true);
|
|
38
|
+
useEventListener(window, 'resize', handle, { passive: true });
|
|
39
39
|
|
|
40
40
|
useEventListener(window, 'adjust', handle, true);
|
|
41
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * from './components/PlTextArea';
|
|
|
30
30
|
export * from './components/PlDropdown';
|
|
31
31
|
export * from './components/PlDropdownRef';
|
|
32
32
|
export * from './components/PlDropdownLine';
|
|
33
|
+
export * from './components/PlDropdownLegacy';
|
|
33
34
|
export * from './components/PlTooltip';
|
|
34
35
|
export * from './components/PlProgressBar';
|
|
35
36
|
export * from './components/PlNumberField';
|