@milaboratories/uikit 2.2.95 → 2.2.96
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/.turbo/turbo-build.log +29 -24
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +9 -0
- package/dist/components/DataTable/TableComponent.vue.js +1 -1
- package/dist/components/PlAccordion/PlAccordionSection.vue2.js +21 -21
- package/dist/components/PlAccordion/PlAccordionSection.vue2.js.map +1 -1
- package/dist/components/PlAutocomplete/PlAutocomplete.vue.js.map +1 -1
- package/dist/components/PlDropdown/OptionList.vue.d.ts +77 -0
- package/dist/components/PlDropdown/OptionList.vue.d.ts.map +1 -0
- package/dist/components/PlDropdown/OptionList.vue.js +88 -0
- package/dist/components/PlDropdown/OptionList.vue.js.map +1 -0
- package/dist/components/PlDropdown/OptionList.vue2.js +5 -0
- package/dist/components/PlDropdown/OptionList.vue2.js.map +1 -0
- package/dist/components/PlDropdown/PlDropdown.vue.d.ts.map +1 -1
- package/dist/components/PlDropdown/PlDropdown.vue.js +110 -122
- package/dist/components/PlDropdown/PlDropdown.vue.js.map +1 -1
- package/dist/components/PlDropdown/types.d.ts +7 -0
- package/dist/components/PlDropdown/types.d.ts.map +1 -0
- package/dist/components/PlDropdown/useGroupBy.d.ts +7 -0
- package/dist/components/PlDropdown/useGroupBy.d.ts.map +1 -0
- package/dist/components/PlDropdown/useGroupBy.js +36 -0
- package/dist/components/PlDropdown/useGroupBy.js.map +1 -0
- package/dist/components/PlDropdownRef/PlDropdownRef.vue.d.ts +1 -1
- package/dist/components/PlDropdownRef/PlDropdownRef.vue.d.ts.map +1 -1
- package/dist/components/PlDropdownRef/PlDropdownRef.vue.js +11 -10
- package/dist/components/PlDropdownRef/PlDropdownRef.vue.js.map +1 -1
- package/dist/components/PlSlideModal/PlSlideModal.vue.js +1 -1
- package/dist/helpers/utils.d.ts +1 -0
- package/dist/helpers/utils.d.ts.map +1 -1
- package/dist/helpers/utils.js +2 -1
- package/dist/helpers/utils.js.map +1 -1
- package/dist/sdk/model/dist/index.js +1 -1
- package/dist/sdk/model/dist/index.js.map +1 -1
- package/dist/types.d.ts +4 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/TextLabel.vue.d.ts +18 -0
- package/dist/utils/TextLabel.vue.d.ts.map +1 -0
- package/dist/utils/TextLabel.vue.js +26 -0
- package/dist/utils/TextLabel.vue.js.map +1 -0
- package/dist/utils/TextLabel.vue2.js +13 -0
- package/dist/utils/TextLabel.vue2.js.map +1 -0
- package/package.json +3 -3
- package/src/components/PlAccordion/PlAccordionSection.vue +3 -3
- package/src/components/PlAutocomplete/PlAutocomplete.vue +1 -1
- package/src/components/PlDropdown/OptionList.vue +71 -0
- package/src/components/PlDropdown/PlDropdown.vue +29 -25
- package/src/components/PlDropdown/pl-dropdown.scss +4 -0
- package/src/components/PlDropdown/types.ts +3 -0
- package/src/components/PlDropdown/useGroupBy.ts +63 -0
- package/src/components/PlDropdownRef/PlDropdownRef.vue +1 -0
- package/src/helpers/utils.ts +1 -0
- package/src/types.ts +5 -15
- package/src/utils/TextLabel.vue +43 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/uikit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.96",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"sortablejs": "^1.15.6",
|
|
20
20
|
"vue": "^3.5.13",
|
|
21
21
|
"d3": "^7.9.0",
|
|
22
|
-
"@
|
|
23
|
-
"@
|
|
22
|
+
"@platforma-sdk/model": "^1.37.14",
|
|
23
|
+
"@milaboratories/helpers": "^1.6.16"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/d3": "^7.4.3",
|
|
@@ -4,15 +4,15 @@ import { PlMaskIcon16 } from '../PlMaskIcon16';
|
|
|
4
4
|
import { PlSectionSeparator } from '../PlSectionSeparator';
|
|
5
5
|
import ExpandTransition from './ExpandTransition.vue';
|
|
6
6
|
import type { Ref } from 'vue';
|
|
7
|
-
import { computed, inject } from 'vue';
|
|
7
|
+
import { computed, inject, toRef } from 'vue';
|
|
8
8
|
|
|
9
|
-
const $m = inject<Ref<string>>('pl-accordion-model');
|
|
9
|
+
const $m = inject<Ref<string>>('pl-accordion-model', () => toRef(''), true);
|
|
10
10
|
|
|
11
11
|
const $p = inject<
|
|
12
12
|
Ref<{
|
|
13
13
|
multiple?: boolean;
|
|
14
14
|
}>
|
|
15
|
-
>('pl-accordion-props');
|
|
15
|
+
>('pl-accordion-props', () => toRef({ multiple: false }), true);
|
|
16
16
|
|
|
17
17
|
const model = defineModel<boolean>();
|
|
18
18
|
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
|
|
10
10
|
<script lang="ts" setup generic="M = unknown">
|
|
11
11
|
import './pl-autocomplete.scss';
|
|
12
|
-
import { computed, reactive, ref, unref,
|
|
12
|
+
import { computed, reactive, ref, unref, useTemplateRef, watch, watchPostEffect } from 'vue';
|
|
13
13
|
import { tap } from '../../helpers/functions';
|
|
14
14
|
import { PlTooltip } from '../PlTooltip';
|
|
15
15
|
import DoubleContour from '../../utils/DoubleContour.vue';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import DropdownListItem from '../DropdownListItem.vue';
|
|
3
|
+
import { DropdownOverlay } from '../../utils/DropdownOverlay';
|
|
4
|
+
import TextLabel from '../../utils/TextLabel.vue';
|
|
5
|
+
import { computed, useTemplateRef } from 'vue';
|
|
6
|
+
import type { LOption } from './types';
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
rootRef: HTMLElement;
|
|
10
|
+
groups: Map<string, LOption[]>;
|
|
11
|
+
rest: LOption[];
|
|
12
|
+
optionSize: 'small' | 'medium';
|
|
13
|
+
selectOption: (v: unknown) => void;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const overlay = useTemplateRef('overlay');
|
|
17
|
+
|
|
18
|
+
const listRef = computed(() => overlay.value?.listRef);
|
|
19
|
+
|
|
20
|
+
const hasGroups = computed(() => props.groups.size > 0);
|
|
21
|
+
|
|
22
|
+
const optionsLength = computed(() => {
|
|
23
|
+
let totalGroupItems = 0;
|
|
24
|
+
for (const items of props.groups.values()) {
|
|
25
|
+
totalGroupItems += items.length;
|
|
26
|
+
}
|
|
27
|
+
return totalGroupItems + props.rest.length;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const scrollIntoActive = () => {
|
|
31
|
+
overlay.value?.scrollIntoActive();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
defineExpose({
|
|
35
|
+
scrollIntoActive,
|
|
36
|
+
listRef,
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<DropdownOverlay ref="overlay" :root="rootRef" class="pl-dropdown__options" tabindex="-1" :gap="3">
|
|
42
|
+
<div v-for="[group, items] in groups.entries()" :key="group" :class="{ 'group-container': hasGroups }">
|
|
43
|
+
<TextLabel>{{ group }}</TextLabel>
|
|
44
|
+
<div>
|
|
45
|
+
<DropdownListItem
|
|
46
|
+
v-for="(item, index) in items"
|
|
47
|
+
:key="index"
|
|
48
|
+
:option="item"
|
|
49
|
+
:is-selected="item.isSelected"
|
|
50
|
+
:is-hovered="item.isActive"
|
|
51
|
+
:size="optionSize"
|
|
52
|
+
@click.stop="selectOption(item.value)"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div v-if="rest.length" :class="{ 'group-container': hasGroups }">
|
|
57
|
+
<TextLabel />
|
|
58
|
+
<div>
|
|
59
|
+
<DropdownListItem
|
|
60
|
+
v-for="(item, index) in rest"
|
|
61
|
+
:key="index" :option="item"
|
|
62
|
+
:is-selected="item.isSelected"
|
|
63
|
+
:is-hovered="item.isActive"
|
|
64
|
+
:size="optionSize"
|
|
65
|
+
@click.stop="selectOption(item.value)"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div v-if="!optionsLength" class="nothing-found">Nothing found</div>
|
|
70
|
+
</DropdownOverlay>
|
|
71
|
+
</template>
|
|
@@ -9,21 +9,22 @@ export default {
|
|
|
9
9
|
|
|
10
10
|
<script lang="ts" setup generic="M = unknown">
|
|
11
11
|
import './pl-dropdown.scss';
|
|
12
|
-
import { computed, reactive, ref, unref,
|
|
12
|
+
import { computed, reactive, ref, unref, useTemplateRef, watch, watchPostEffect } from 'vue';
|
|
13
13
|
import { tap } from '../../helpers/functions';
|
|
14
14
|
import { PlTooltip } from '../PlTooltip';
|
|
15
15
|
import DoubleContour from '../../utils/DoubleContour.vue';
|
|
16
16
|
import { useLabelNotch } from '../../utils/useLabelNotch';
|
|
17
17
|
import type { ListOption, ListOptionNormalized } from '../../types';
|
|
18
18
|
import { deepEqual } from '../../helpers/objects';
|
|
19
|
-
import DropdownListItem from '../DropdownListItem.vue';
|
|
20
19
|
import LongText from '../LongText.vue';
|
|
21
20
|
import { normalizeListOptions } from '../../helpers/utils';
|
|
22
21
|
import { PlIcon16 } from '../PlIcon16';
|
|
23
22
|
import { PlMaskIcon24 } from '../PlMaskIcon24';
|
|
24
|
-
import { DropdownOverlay } from '../../utils/DropdownOverlay';
|
|
25
23
|
import SvgRequired from '../../generated/components/svg/images/SvgRequired.vue';
|
|
26
24
|
import { getErrorMessage } from '../../helpers/error.ts';
|
|
25
|
+
import OptionList from './OptionList.vue';
|
|
26
|
+
import { useGroupBy } from './useGroupBy';
|
|
27
|
+
import type { LOption } from './types';
|
|
27
28
|
|
|
28
29
|
const emit = defineEmits<{
|
|
29
30
|
/**
|
|
@@ -110,7 +111,7 @@ const slots = defineSlots<{
|
|
|
110
111
|
const rootRef = ref<HTMLElement | undefined>();
|
|
111
112
|
const input = ref<HTMLInputElement | undefined>();
|
|
112
113
|
|
|
113
|
-
const
|
|
114
|
+
const optionListRef = useTemplateRef<InstanceType<typeof OptionList>>('optionListRef');
|
|
114
115
|
|
|
115
116
|
const data = reactive({
|
|
116
117
|
search: '',
|
|
@@ -121,7 +122,7 @@ const data = reactive({
|
|
|
121
122
|
|
|
122
123
|
const findActiveIndex = () =>
|
|
123
124
|
tap(
|
|
124
|
-
|
|
125
|
+
orderedRef.value.findIndex((o) => deepEqual(o.value, props.modelValue)),
|
|
125
126
|
(v) => (v < 0 ? 0 : v),
|
|
126
127
|
);
|
|
127
128
|
|
|
@@ -159,7 +160,7 @@ const computedError = computed(() => {
|
|
|
159
160
|
return undefined;
|
|
160
161
|
});
|
|
161
162
|
|
|
162
|
-
const optionsRef = computed(() =>
|
|
163
|
+
const optionsRef = computed<LOption<M>[]>(() =>
|
|
163
164
|
normalizeListOptions(props.options ?? []).map((opt, index) => ({
|
|
164
165
|
...opt,
|
|
165
166
|
index,
|
|
@@ -214,6 +215,8 @@ const filteredRef = computed(() => {
|
|
|
214
215
|
return options;
|
|
215
216
|
});
|
|
216
217
|
|
|
218
|
+
const { orderedRef, groupsRef, restRef } = useGroupBy(filteredRef, 'group');
|
|
219
|
+
|
|
217
220
|
const tabindex = computed(() => (isDisabled.value ? undefined : '0'));
|
|
218
221
|
|
|
219
222
|
const selectOption = (v: M | undefined) => {
|
|
@@ -223,6 +226,10 @@ const selectOption = (v: M | undefined) => {
|
|
|
223
226
|
rootRef?.value?.focus();
|
|
224
227
|
};
|
|
225
228
|
|
|
229
|
+
const selectOptionWrapper = (v: unknown) => {
|
|
230
|
+
selectOption(v as M | undefined);
|
|
231
|
+
};
|
|
232
|
+
|
|
226
233
|
const clear = () => emit('update:modelValue', undefined);
|
|
227
234
|
|
|
228
235
|
const setFocusOnInput = () => input.value?.focus();
|
|
@@ -239,7 +246,7 @@ const onInputFocus = () => (data.open = true);
|
|
|
239
246
|
const onFocusOut = (event: FocusEvent) => {
|
|
240
247
|
const relatedTarget = event.relatedTarget as Node | null;
|
|
241
248
|
|
|
242
|
-
if (!rootRef.value?.contains(relatedTarget) && !
|
|
249
|
+
if (!rootRef.value?.contains(relatedTarget) && !optionListRef.value?.listRef?.contains(relatedTarget)) {
|
|
243
250
|
data.search = '';
|
|
244
251
|
data.open = false;
|
|
245
252
|
}
|
|
@@ -266,25 +273,25 @@ const handleKeydown = (e: { code: string; preventDefault(): void }) => {
|
|
|
266
273
|
rootRef.value?.focus();
|
|
267
274
|
}
|
|
268
275
|
|
|
269
|
-
const
|
|
276
|
+
const ordered = orderedRef.value;
|
|
270
277
|
|
|
271
|
-
const { length } =
|
|
278
|
+
const { length } = ordered;
|
|
272
279
|
|
|
273
280
|
if (!length) {
|
|
274
281
|
return;
|
|
275
282
|
}
|
|
276
283
|
|
|
277
284
|
if (e.code === 'Enter') {
|
|
278
|
-
selectOption(
|
|
285
|
+
selectOption(ordered.find((it) => it.index === activeIndex)?.value);
|
|
279
286
|
}
|
|
280
287
|
|
|
281
|
-
const localIndex =
|
|
288
|
+
const localIndex = ordered.findIndex((it) => it.index === activeIndex) ?? -1;
|
|
282
289
|
|
|
283
290
|
const delta = e.code === 'ArrowDown' ? 1 : e.code === 'ArrowUp' ? -1 : 0;
|
|
284
291
|
|
|
285
292
|
const newIndex = Math.abs(localIndex + delta + length) % length;
|
|
286
293
|
|
|
287
|
-
data.activeIndex =
|
|
294
|
+
data.activeIndex = ordered[newIndex].index ?? -1;
|
|
288
295
|
};
|
|
289
296
|
|
|
290
297
|
useLabelNotch(rootRef);
|
|
@@ -301,7 +308,7 @@ watchPostEffect(() => {
|
|
|
301
308
|
data.search; // to watch
|
|
302
309
|
|
|
303
310
|
if (data.activeIndex >= 0 && data.open) {
|
|
304
|
-
|
|
311
|
+
optionListRef.value?.scrollIntoActive();
|
|
305
312
|
}
|
|
306
313
|
});
|
|
307
314
|
</script>
|
|
@@ -354,18 +361,15 @@ watchPostEffect(() => {
|
|
|
354
361
|
</template>
|
|
355
362
|
</PlTooltip>
|
|
356
363
|
</label>
|
|
357
|
-
<
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
/>
|
|
367
|
-
<div v-if="!filteredRef.length" class="nothing-found">Nothing found</div>
|
|
368
|
-
</DropdownOverlay>
|
|
364
|
+
<OptionList
|
|
365
|
+
v-if="data.open"
|
|
366
|
+
ref="optionListRef"
|
|
367
|
+
:root-ref="rootRef!"
|
|
368
|
+
:groups="groupsRef"
|
|
369
|
+
:rest="restRef"
|
|
370
|
+
:option-size="optionSize"
|
|
371
|
+
:select-option="selectOptionWrapper"
|
|
372
|
+
/>
|
|
369
373
|
<DoubleContour class="pl-dropdown__contour" />
|
|
370
374
|
</div>
|
|
371
375
|
</div>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
|
|
4
|
+
function groupBy<T, K extends keyof T>(
|
|
5
|
+
list: T[],
|
|
6
|
+
groupBy: K,
|
|
7
|
+
): {
|
|
8
|
+
grouped: Map<NonNullable<T[K]>, T[]>;
|
|
9
|
+
rest: T[];
|
|
10
|
+
ordered: T[];
|
|
11
|
+
} {
|
|
12
|
+
const grouped: Map<NonNullable<T[K]>, T[]> = new Map();
|
|
13
|
+
|
|
14
|
+
if (!list) {
|
|
15
|
+
return {
|
|
16
|
+
grouped,
|
|
17
|
+
rest: [],
|
|
18
|
+
ordered: [],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Group items by the specified key
|
|
23
|
+
for (const item of list) {
|
|
24
|
+
const key = item[groupBy];
|
|
25
|
+
if (key === undefined) continue;
|
|
26
|
+
if (key === null) continue;
|
|
27
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
28
|
+
grouped.get(key)?.push(item);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Items without a group key
|
|
32
|
+
const rest = list.filter((item: T) => {
|
|
33
|
+
const key = item[groupBy];
|
|
34
|
+
return key === undefined || key === null;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const ordered = [...Array.from(grouped.values()).flat(), ...rest];
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
grouped,
|
|
41
|
+
rest,
|
|
42
|
+
ordered,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useGroupBy<T, K extends keyof T>(
|
|
47
|
+
list: Ref<T[]>,
|
|
48
|
+
byKey: K,
|
|
49
|
+
) {
|
|
50
|
+
const result = computed(() => groupBy(list.value, byKey));
|
|
51
|
+
|
|
52
|
+
const orderedRef = computed(() => result.value.ordered);
|
|
53
|
+
|
|
54
|
+
const groupsRef = computed(() => result.value.grouped);
|
|
55
|
+
|
|
56
|
+
const restRef = computed(() => result.value.rest);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
orderedRef,
|
|
60
|
+
groupsRef,
|
|
61
|
+
restRef,
|
|
62
|
+
};
|
|
63
|
+
}
|
package/src/helpers/utils.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -32,31 +32,21 @@ export type SimpleOption<T = unknown> =
|
|
|
32
32
|
value: T;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export type
|
|
36
|
-
label: string;
|
|
37
|
-
description?: string;
|
|
38
|
-
value: T;
|
|
39
|
-
};
|
|
35
|
+
export type ListOptionNormalized<T = unknown> = ListOptionBase<T>;
|
|
40
36
|
|
|
37
|
+
// @TODO: remove `text` support
|
|
41
38
|
export type ListOption<T = unknown> =
|
|
42
|
-
| {
|
|
39
|
+
| Omit<ListOptionBase<T>, 'label'> & {
|
|
43
40
|
text: string;
|
|
44
|
-
description?: string;
|
|
45
|
-
value: T;
|
|
46
41
|
}
|
|
47
|
-
|
|
|
48
|
-
label: string;
|
|
49
|
-
description?: string;
|
|
50
|
-
value: T;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export type ListOptionNormalized<T = unknown> = ListOptionBase<T>;
|
|
42
|
+
| ListOptionBase<T>;
|
|
54
43
|
|
|
55
44
|
export type { ModelRef };
|
|
56
45
|
|
|
57
46
|
export type RefOption = {
|
|
58
47
|
readonly label: string;
|
|
59
48
|
readonly ref: ModelRef;
|
|
49
|
+
readonly group?: string;
|
|
60
50
|
};
|
|
61
51
|
|
|
62
52
|
export type ListOptionType<Type> = Type extends ListOption<infer X>[] ? X : never;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.textLabel">
|
|
3
|
+
<div :class="$style.label">
|
|
4
|
+
<div :class="$style.labelText">
|
|
5
|
+
<slot />
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<style lang="scss" module>
|
|
12
|
+
.textLabel {
|
|
13
|
+
padding: 0px 12px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.label {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 4px;
|
|
20
|
+
color: var(--txt-03);
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
font-weight: 500;
|
|
23
|
+
line-height: 16px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.labelText {
|
|
27
|
+
display: inline-block;
|
|
28
|
+
max-width: 100%;
|
|
29
|
+
min-width: 0;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
text-overflow: ellipsis;
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.label::after {
|
|
36
|
+
display: block;
|
|
37
|
+
flex: 1;
|
|
38
|
+
content: '';
|
|
39
|
+
height: 1px;
|
|
40
|
+
background-color: var(--border-color-div-grey);
|
|
41
|
+
width: 100%;
|
|
42
|
+
}
|
|
43
|
+
</style>
|