@weni/unnnic-system 3.2.9-alpha.6 → 3.2.9-alpha.7
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/package.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
{
|
|
6
6
|
'unnnic-select-option--disabled': props.disabled,
|
|
7
7
|
'unnnic-select-option--active': props.active,
|
|
8
|
+
'unnnic-select-option--focused': props.focused,
|
|
8
9
|
},
|
|
9
10
|
]"
|
|
10
11
|
>
|
|
@@ -21,11 +22,13 @@ interface SelectOptionProps {
|
|
|
21
22
|
label: string;
|
|
22
23
|
disabled?: boolean;
|
|
23
24
|
active?: boolean;
|
|
25
|
+
focused?: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const props = withDefaults(defineProps<SelectOptionProps>(), {
|
|
27
29
|
disabled: false,
|
|
28
30
|
active: false,
|
|
31
|
+
focused: false,
|
|
29
32
|
});
|
|
30
33
|
</script>
|
|
31
34
|
|
|
@@ -43,6 +46,11 @@ const props = withDefaults(defineProps<SelectOptionProps>(), {
|
|
|
43
46
|
padding: $unnnic-space-2 $unnnic-space-4;
|
|
44
47
|
font: $unnnic-font-emphasis;
|
|
45
48
|
|
|
49
|
+
&:hover:not(&--active):not(&--disabled),
|
|
50
|
+
&--focused {
|
|
51
|
+
background-color: $unnnic-color-bg-soft;
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
&--active {
|
|
47
55
|
background-color: $unnnic-color-bg-active;
|
|
48
56
|
color: $unnnic-color-fg-inverted;
|
|
@@ -237,13 +237,13 @@ describe('UnnnicSelect.vue', () => {
|
|
|
237
237
|
describe('computed properties', () => {
|
|
238
238
|
test('calculatedMaxHeight returns correct value', () => {
|
|
239
239
|
const maxHeight = wrapper.vm.calculatedMaxHeight;
|
|
240
|
-
expect(maxHeight).toBe('
|
|
240
|
+
expect(maxHeight).toBe('235px');
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
test('calculatedMaxHeight includes search height when enabled', async () => {
|
|
244
244
|
await wrapper.setProps({ enableSearch: true });
|
|
245
245
|
const maxHeight = wrapper.vm.calculatedMaxHeight;
|
|
246
|
-
expect(maxHeight).toBe('
|
|
246
|
+
expect(maxHeight).toBe('289px');
|
|
247
247
|
});
|
|
248
248
|
|
|
249
249
|
test('calculatedMaxHeight returns unset when no options', async () => {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="unnnic-select"
|
|
4
|
+
@keydown="handleKeyDown"
|
|
5
|
+
>
|
|
3
6
|
<UnnnicPopover
|
|
4
7
|
v-model="openPopover"
|
|
5
8
|
:popoverBalloonProps="{ maxHeight: calculatedMaxHeight }"
|
|
@@ -30,12 +33,14 @@
|
|
|
30
33
|
@update:modelValue="handleSearch"
|
|
31
34
|
/>
|
|
32
35
|
<UnnnicSelectOption
|
|
33
|
-
v-for="option in filteredOptions"
|
|
36
|
+
v-for="(option, index) in filteredOptions"
|
|
34
37
|
:key="option[props.itemValue]"
|
|
38
|
+
:data-option-index="index"
|
|
35
39
|
:label="option[props.itemLabel]"
|
|
36
40
|
:active="
|
|
37
41
|
option[props.itemValue] === selectedItem?.[props.itemValue]
|
|
38
42
|
"
|
|
43
|
+
:focused="focusedOptionIndex === index"
|
|
39
44
|
:disabled="option.disabled"
|
|
40
45
|
@click="handleSelectOption(option)"
|
|
41
46
|
/>
|
|
@@ -46,7 +51,7 @@
|
|
|
46
51
|
</template>
|
|
47
52
|
|
|
48
53
|
<script setup lang="ts">
|
|
49
|
-
import { computed, ref, watch } from 'vue';
|
|
54
|
+
import { computed, ref, watch, nextTick } from 'vue';
|
|
50
55
|
import UnnnicInput from '../Input/Input.vue';
|
|
51
56
|
import UnnnicPopover from '../Popover/index.vue';
|
|
52
57
|
import UnnnicSelectOption from './SelectOption.vue';
|
|
@@ -102,13 +107,64 @@ const openPopover = ref(false);
|
|
|
102
107
|
watch(openPopover, () => {
|
|
103
108
|
if (!openPopover.value) {
|
|
104
109
|
handleSearch('');
|
|
110
|
+
} else {
|
|
111
|
+
focusedOptionIndex.value = -1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (openPopover.value && props.modelValue) {
|
|
115
|
+
const selectedOptionIndex = props.options.findIndex(
|
|
116
|
+
(option) =>
|
|
117
|
+
option[props.itemValue] === selectedItem.value[props.itemValue],
|
|
118
|
+
);
|
|
119
|
+
scrollToOption(selectedOptionIndex, 'instant', 'center');
|
|
105
120
|
}
|
|
106
121
|
});
|
|
107
122
|
|
|
123
|
+
const handleKeyDown = (event) => {
|
|
124
|
+
const { key } = event;
|
|
125
|
+
const validKeys = ['ArrowUp', 'ArrowDown', 'Enter'];
|
|
126
|
+
if (validKeys.includes(key)) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
if (key === 'ArrowUp') {
|
|
129
|
+
if (focusedOptionIndex.value === 0) return;
|
|
130
|
+
focusedOptionIndex.value--;
|
|
131
|
+
scrollToOption(focusedOptionIndex.value);
|
|
132
|
+
}
|
|
133
|
+
if (key === 'ArrowDown') {
|
|
134
|
+
if (focusedOptionIndex.value === filteredOptions.value.length - 1) return;
|
|
135
|
+
focusedOptionIndex.value++;
|
|
136
|
+
scrollToOption(focusedOptionIndex.value);
|
|
137
|
+
}
|
|
138
|
+
if (key === 'Enter') {
|
|
139
|
+
handleSelectOption(filteredOptions.value[focusedOptionIndex.value]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const focusedOptionIndex = ref<number>(-1);
|
|
145
|
+
|
|
146
|
+
const scrollToOption = (
|
|
147
|
+
index: number,
|
|
148
|
+
behavior: 'smooth' | 'instant' = 'smooth',
|
|
149
|
+
block: 'center' | 'start' | 'end' | 'nearest' = 'center',
|
|
150
|
+
) => {
|
|
151
|
+
nextTick(() => {
|
|
152
|
+
const option = document.querySelector(`[data-option-index="${index}"]`);
|
|
153
|
+
if (option) {
|
|
154
|
+
option.scrollIntoView({ behavior, block });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
108
159
|
const calculatedMaxHeight = computed(() => {
|
|
109
160
|
if (!props.options || props.options.length === 0) return 'unset';
|
|
110
|
-
const
|
|
111
|
-
|
|
161
|
+
const popoverPadding = 32;
|
|
162
|
+
const popoverGap = 4;
|
|
163
|
+
// 37 = 21px (height) + 16px (padding)
|
|
164
|
+
const fieldsHeight = 37 * props.optionsLines;
|
|
165
|
+
const size =
|
|
166
|
+
fieldsHeight + popoverPadding + (popoverGap * props.optionsLines - 2);
|
|
167
|
+
return `${props.enableSearch ? size + 54 : size}px`;
|
|
112
168
|
});
|
|
113
169
|
|
|
114
170
|
const selectedItem = computed(() => {
|