ketekny-ui-kit 1.0.17 → 1.0.18
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 +3 -2
- package/src/ui/kSelect.vue +233 -229
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ketekny-ui-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.18",
|
|
5
5
|
"description": "A Vue 3 UI component library with Tailwind CSS styling",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -40,13 +40,14 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@primeuix/themes": "^1.1.1",
|
|
42
42
|
"@vuepic/vue-datepicker": "^11.0.2",
|
|
43
|
-
"json-editor-vue": "^0.18.1",
|
|
44
43
|
"he-tree-vue": "^3.1.2",
|
|
44
|
+
"json-editor-vue": "^0.18.1",
|
|
45
45
|
"lucide-vue-next": "^0.511.0",
|
|
46
46
|
"moment": "^2.30.1",
|
|
47
47
|
"primeicons": "^7.0.0",
|
|
48
48
|
"primevue": "^4.3.4",
|
|
49
49
|
"quill": "^2.0.3",
|
|
50
|
+
"reka-ui": "^2.8.2",
|
|
50
51
|
"simple-code-editor": "^2.0.9",
|
|
51
52
|
"vue-router": "^4.6.4",
|
|
52
53
|
"vue3-easy-data-table": "^1.5.47"
|
package/src/ui/kSelect.vue
CHANGED
|
@@ -1,102 +1,101 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative w-full">
|
|
3
|
-
<!-- Label -->
|
|
4
3
|
<label v-if="label != null" :for="computedId" class="inputLabel" :class="hasError ? 'text-red-500' : 'text-gray-700'">
|
|
5
4
|
{{ label }}
|
|
6
5
|
</label>
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
:aria-disabled="disabled.toString()"
|
|
18
|
-
:aria-controls="dropdownId"
|
|
19
|
-
:role="'combobox'"
|
|
20
|
-
:tabindex="disabled ? -1 : 0"
|
|
21
|
-
>
|
|
22
|
-
<span class="block pr-10 truncate">{{ selectedLabel || placeholder }}</span>
|
|
7
|
+
<SelectRoot v-model="internalValue" v-model:open="open" :disabled="disabled">
|
|
8
|
+
<SelectTrigger as-child>
|
|
9
|
+
<div
|
|
10
|
+
:id="computedId"
|
|
11
|
+
class="relative w-full px-3 py-2 text-gray-700 bg-white border rounded-lg cursor-pointer trigger-box"
|
|
12
|
+
:class="[defaultStyle, hasError ? errorStyle : '', disabled ? disabledStyle : '']"
|
|
13
|
+
:aria-label="label || 'Select option'"
|
|
14
|
+
>
|
|
15
|
+
<SelectValue :placeholder="placeholder" class="block pr-10 truncate" />
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<!-- Dropdown icon -->
|
|
35
|
-
<span class="absolute text-gray-400 -translate-y-1/2 pointer-events-none right-3 top-1/2">
|
|
36
|
-
<ChevronDown class="w-5 h-5" v-if="!isOpen" />
|
|
37
|
-
<ChevronUp class="w-5 h-5" v-else />
|
|
38
|
-
</span>
|
|
39
|
-
</div>
|
|
17
|
+
<button
|
|
18
|
+
v-if="clearable && isSelectionSet && !disabled"
|
|
19
|
+
type="button"
|
|
20
|
+
class="absolute text-gray-400 -translate-y-1/2 right-8 top-1/2 hover:text-red-500"
|
|
21
|
+
aria-label="Clear selection"
|
|
22
|
+
@pointerdown.stop.prevent="clearSelection"
|
|
23
|
+
@click.stop.prevent="clearSelection"
|
|
24
|
+
>
|
|
25
|
+
<X class="w-5 h-5 mr-2" />
|
|
26
|
+
</button>
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
ref="dropdown"
|
|
46
|
-
:id="dropdownId"
|
|
47
|
-
class="absolute z-[9999] mt-1 overflow-auto bg-white border border-gray-200 rounded-lg shadow-[0_20px_45px_-15px_rgba(15,23,42,0.35),0_8px_18px_-10px_rgba(15,23,42,0.25)] ring-1 ring-slate-900/10"
|
|
48
|
-
:class="dropdownHeight"
|
|
49
|
-
:style="dropdownPositionStyle"
|
|
50
|
-
role="listbox"
|
|
51
|
-
:aria-label="label || 'Select options'"
|
|
52
|
-
>
|
|
53
|
-
<!-- Search -->
|
|
54
|
-
<div class="sticky top-0 z-10 pt-2 pb-2 mx-2 bg-white">
|
|
55
|
-
<input
|
|
56
|
-
ref="searchInput"
|
|
57
|
-
type="text"
|
|
58
|
-
v-model="searchQuery"
|
|
59
|
-
placeholder="Αναζήτηση..."
|
|
60
|
-
class="w-full px-3 py-2 border border-gray-200 rounded-md focus:outline-none"
|
|
61
|
-
role="searchbox"
|
|
62
|
-
aria-label="Search options"
|
|
63
|
-
@keydown.esc.stop.prevent="closeDropdown"
|
|
64
|
-
/>
|
|
28
|
+
<span class="absolute text-gray-400 -translate-y-1/2 pointer-events-none right-3 top-1/2">
|
|
29
|
+
<ChevronDown class="w-5 h-5" v-if="!open" />
|
|
30
|
+
<ChevronUp class="w-5 h-5" v-else />
|
|
31
|
+
</span>
|
|
65
32
|
</div>
|
|
33
|
+
</SelectTrigger>
|
|
66
34
|
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<Check v-if="option[optionValue] === selectedValue" class="w-4 h-4 shrink-0 text-primary" />
|
|
86
|
-
</div>
|
|
35
|
+
<SelectPortal>
|
|
36
|
+
<SelectContent
|
|
37
|
+
:id="dropdownId"
|
|
38
|
+
position="popper"
|
|
39
|
+
:side-offset="6"
|
|
40
|
+
class="z-[9999] overflow-hidden bg-white border border-gray-200 rounded-lg shadow-[0_20px_45px_-15px_rgba(15,23,42,0.35),0_8px_18px_-10px_rgba(15,23,42,0.25)] ring-1 ring-slate-900/10"
|
|
41
|
+
>
|
|
42
|
+
<div v-if="searchable" data-select-search-header class="sticky top-0 z-10 pt-2 pb-2 mx-2 bg-white">
|
|
43
|
+
<input
|
|
44
|
+
ref="searchInput"
|
|
45
|
+
type="text"
|
|
46
|
+
v-model="searchQuery"
|
|
47
|
+
placeholder="Αναζήτηση..."
|
|
48
|
+
class="w-full px-3 py-2 border border-gray-200 rounded-md focus:outline-none"
|
|
49
|
+
role="searchbox"
|
|
50
|
+
aria-label="Search options"
|
|
51
|
+
@keydown.stop="handleSearchKeydown"
|
|
52
|
+
/>
|
|
87
53
|
</div>
|
|
88
|
-
</div>
|
|
89
54
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
55
|
+
<div class="relative">
|
|
56
|
+
<SelectViewport
|
|
57
|
+
class="p-2 overflow-y-auto"
|
|
58
|
+
:class="canScrollAny ? 'pt-8 pb-8' : ''"
|
|
59
|
+
:style="{ maxHeight: viewportMaxHeight }"
|
|
60
|
+
@scroll="updateScrollState"
|
|
61
|
+
>
|
|
62
|
+
<div class="flex flex-col gap-0">
|
|
63
|
+
<SelectItem
|
|
64
|
+
v-for="(option, index) in filteredOptions"
|
|
65
|
+
:key="`${option[optionValue]}-${index}`"
|
|
66
|
+
:value="option[optionValue]"
|
|
67
|
+
class="px-3 py-2 rounded cursor-pointer outline-none text-slate-700 hover:bg-primary/20 hover:text-primary data-[highlighted]:bg-primary data-[highlighted]:text-white data-[state=checked]:text-primary data-[state=checked]:data-[highlighted]:text-white"
|
|
68
|
+
>
|
|
69
|
+
<div class="flex items-center justify-between gap-2">
|
|
70
|
+
<SelectItemText class="truncate">{{ option[optionLabel] }}</SelectItemText>
|
|
71
|
+
<span class="inline-flex items-center justify-center w-4 h-4 shrink-0">
|
|
72
|
+
<SelectItemIndicator>
|
|
73
|
+
<Check class="w-4 h-4" />
|
|
74
|
+
</SelectItemIndicator>
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
</SelectItem>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div v-if="filteredOptions.length === 0" class="px-3 py-2 text-gray-400">Δεν βρέθηκαν επιλογές</div>
|
|
81
|
+
</SelectViewport>
|
|
82
|
+
|
|
83
|
+
<SelectScrollUpButton class="absolute top-0 left-0 right-0 z-10 flex items-center justify-center h-6 text-gray-500 bg-white/95">
|
|
84
|
+
<ChevronUp class="w-4 h-4" />
|
|
85
|
+
</SelectScrollUpButton>
|
|
86
|
+
|
|
87
|
+
<SelectScrollDownButton class="absolute bottom-0 left-0 right-0 z-10 flex items-center justify-center h-6 text-gray-500 bg-white/95">
|
|
88
|
+
<ChevronDown class="w-4 h-4" />
|
|
89
|
+
</SelectScrollDownButton>
|
|
90
|
+
</div>
|
|
91
|
+
</SelectContent>
|
|
92
|
+
</SelectPortal>
|
|
93
|
+
</SelectRoot>
|
|
93
94
|
|
|
94
|
-
<!-- Error -->
|
|
95
95
|
<div class="mt-1 text-red-500" v-if="hasError && error !== true && error !== ''">
|
|
96
96
|
{{ error }}
|
|
97
97
|
</div>
|
|
98
98
|
|
|
99
|
-
<!-- Info -->
|
|
100
99
|
<div class="mt-1 text-gray-500" v-if="info">
|
|
101
100
|
{{ info }}
|
|
102
101
|
</div>
|
|
@@ -104,163 +103,168 @@
|
|
|
104
103
|
</template>
|
|
105
104
|
|
|
106
105
|
<script setup>
|
|
106
|
+
import { computed, nextTick, ref, watch } from "vue";
|
|
107
|
+
import {
|
|
108
|
+
SelectContent,
|
|
109
|
+
SelectItem,
|
|
110
|
+
SelectItemIndicator,
|
|
111
|
+
SelectItemText,
|
|
112
|
+
SelectPortal,
|
|
113
|
+
SelectRoot,
|
|
114
|
+
SelectScrollDownButton,
|
|
115
|
+
SelectScrollUpButton,
|
|
116
|
+
SelectTrigger,
|
|
117
|
+
SelectValue,
|
|
118
|
+
SelectViewport,
|
|
119
|
+
} from "reka-ui";
|
|
107
120
|
import { X, ChevronDown, ChevronUp, Check } from "lucide-vue-next";
|
|
108
|
-
</script>
|
|
109
121
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
const props = defineProps({
|
|
123
|
+
options: { type: Array, required: true },
|
|
124
|
+
modelValue: [String, Number],
|
|
125
|
+
optionValue: { type: String, default: "value" },
|
|
126
|
+
optionLabel: { type: String, default: "label" },
|
|
127
|
+
label: String,
|
|
128
|
+
info: String,
|
|
129
|
+
error: [String, Boolean],
|
|
130
|
+
disabled: { type: Boolean, default: false },
|
|
131
|
+
placeholder: { type: String, default: "Επιλέξτε μία τιμή" },
|
|
132
|
+
id: { type: String, default: null },
|
|
133
|
+
clearable: { type: Boolean, default: true },
|
|
134
|
+
searchable: { type: Boolean, default: true },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
138
|
+
|
|
139
|
+
const open = ref(false);
|
|
140
|
+
const searchQuery = ref("");
|
|
141
|
+
const searchInput = ref(null);
|
|
142
|
+
const canScrollAny = ref(false);
|
|
143
|
+
const generatedId = `select-${Math.random().toString(36).substr(2, 9)}`;
|
|
144
|
+
const viewportMaxHeight = "400px";
|
|
145
|
+
const defaultStyle = "w-full px-3 py-2 border rounded-lg transition shadow-sm focus:outline-none text-gray-700 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white placeholder-gray-400";
|
|
146
|
+
const errorStyle = "border-red-500 focus:ring focus:ring-red-300";
|
|
147
|
+
const disabledStyle = "!bg-gray-100 !text-gray-400 !cursor-not-allowed";
|
|
148
|
+
|
|
149
|
+
const computedId = computed(() => (props.id != null && props.id !== "" ? props.id : generatedId));
|
|
150
|
+
const dropdownId = computed(() => `${computedId.value}-listbox`);
|
|
151
|
+
|
|
152
|
+
const hasError = computed(() => props.error != null && props.error !== false);
|
|
153
|
+
const isSelectionSet = computed(() => props.modelValue !== null && props.modelValue !== undefined && props.modelValue !== "");
|
|
154
|
+
|
|
155
|
+
const internalValue = computed({
|
|
156
|
+
get() {
|
|
157
|
+
return props.modelValue === null ? undefined : props.modelValue;
|
|
137
158
|
},
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return this.id != null && this.id !== "" ? this.id : this.generatedId;
|
|
141
|
-
},
|
|
142
|
-
dropdownId() {
|
|
143
|
-
return `${this.computedId}-listbox`;
|
|
144
|
-
},
|
|
145
|
-
selectedValue() {
|
|
146
|
-
return this.modelValue;
|
|
147
|
-
},
|
|
148
|
-
isSelectionSet() {
|
|
149
|
-
return this.selectedValue !== null && this.selectedValue !== undefined && this.selectedValue !== "";
|
|
150
|
-
},
|
|
151
|
-
selectedLabel() {
|
|
152
|
-
const match = this.options.find((o) => o[this.optionValue] === this.modelValue);
|
|
153
|
-
return match ? match[this.optionLabel] : "";
|
|
154
|
-
},
|
|
155
|
-
filteredOptions() {
|
|
156
|
-
const q = this.searchQuery.trim().toLowerCase();
|
|
157
|
-
return this.options.filter((o) => String(o[this.optionLabel] ?? "").toLowerCase().includes(q));
|
|
158
|
-
},
|
|
159
|
-
hasError() {
|
|
160
|
-
return this.error != null && this.error !== false;
|
|
161
|
-
},
|
|
159
|
+
set(val) {
|
|
160
|
+
emit("update:modelValue", val ?? null);
|
|
162
161
|
},
|
|
163
|
-
|
|
164
|
-
toggleDropdown() {
|
|
165
|
-
if (this.disabled) return;
|
|
166
|
-
this.isOpen = !this.isOpen;
|
|
167
|
-
if (this.isOpen) {
|
|
168
|
-
this.$nextTick(() => {
|
|
169
|
-
this.getDropdownPosition();
|
|
170
|
-
this.$refs.searchInput?.focus();
|
|
171
|
-
});
|
|
172
|
-
} else {
|
|
173
|
-
this.searchQuery = "";
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
closeDropdown() {
|
|
177
|
-
this.isOpen = false;
|
|
178
|
-
this.searchQuery = "";
|
|
179
|
-
},
|
|
180
|
-
selectOption(option) {
|
|
181
|
-
this.$emit("update:modelValue", option[this.optionValue]);
|
|
182
|
-
this.closeDropdown();
|
|
183
|
-
},
|
|
184
|
-
clearSelection() {
|
|
185
|
-
this.$emit("update:modelValue", null);
|
|
186
|
-
this.closeDropdown();
|
|
187
|
-
},
|
|
188
|
-
handleTriggerKeydown(event) {
|
|
189
|
-
if (this.disabled) return;
|
|
190
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
191
|
-
event.preventDefault();
|
|
192
|
-
this.toggleDropdown();
|
|
193
|
-
} else if (event.key === "ArrowDown" && !this.isOpen) {
|
|
194
|
-
event.preventDefault();
|
|
195
|
-
this.toggleDropdown();
|
|
196
|
-
} else if (event.key === "Escape" && this.isOpen) {
|
|
197
|
-
event.preventDefault();
|
|
198
|
-
this.closeDropdown();
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
closeOnClickOutside(e) {
|
|
202
|
-
const clickedOutside = !this.$el.contains(e.target) && !(this.$refs.dropdown && this.$refs.dropdown.contains(e.target));
|
|
162
|
+
});
|
|
203
163
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.getDropdownPosition();
|
|
211
|
-
},
|
|
164
|
+
const filteredOptions = computed(() => {
|
|
165
|
+
if (!props.searchable) return props.options;
|
|
166
|
+
const q = searchQuery.value.trim().toLowerCase();
|
|
167
|
+
if (!q) return props.options;
|
|
168
|
+
return props.options.filter((o) => String(o?.[props.optionLabel] ?? "").toLowerCase().includes(q));
|
|
169
|
+
});
|
|
212
170
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
171
|
+
watch(open, (val) => {
|
|
172
|
+
if (val) {
|
|
173
|
+
nextTick(() => {
|
|
174
|
+
if (props.searchable) searchInput.value?.focus();
|
|
175
|
+
updateScrollState();
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
searchQuery.value = "";
|
|
180
|
+
canScrollAny.value = false;
|
|
181
|
+
});
|
|
217
182
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const viewportWidth = window.innerWidth;
|
|
223
|
-
const pad = 8;
|
|
183
|
+
watch(filteredOptions, () => {
|
|
184
|
+
if (!open.value) return;
|
|
185
|
+
nextTick(() => updateScrollState());
|
|
186
|
+
});
|
|
224
187
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const desiredHeight = Math.min(contentHeight, maxViewportHeight);
|
|
188
|
+
function clearSelection() {
|
|
189
|
+
emit("update:modelValue", null);
|
|
190
|
+
open.value = false;
|
|
191
|
+
}
|
|
230
192
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
193
|
+
function focusFirstOrLastOption(last = false) {
|
|
194
|
+
const container = document.getElementById(dropdownId.value);
|
|
195
|
+
if (!container) return;
|
|
196
|
+
const nodes = Array.from(container.querySelectorAll('[role="option"]:not([data-disabled])'));
|
|
197
|
+
if (!nodes.length) return;
|
|
198
|
+
const target = last ? nodes[nodes.length - 1] : nodes[0];
|
|
199
|
+
target.focus();
|
|
200
|
+
}
|
|
234
201
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
202
|
+
function moveOptionFocus(step) {
|
|
203
|
+
const container = document.getElementById(dropdownId.value);
|
|
204
|
+
if (!container) return;
|
|
205
|
+
const nodes = Array.from(container.querySelectorAll('[role="option"]:not([data-disabled])'));
|
|
206
|
+
if (!nodes.length) return;
|
|
207
|
+
const activeIndex = nodes.findIndex((node) => node === document.activeElement);
|
|
208
|
+
const baseIndex = activeIndex >= 0 ? activeIndex : step > 0 ? -1 : 0;
|
|
209
|
+
const nextIndex = Math.min(Math.max(baseIndex + step, 0), nodes.length - 1);
|
|
210
|
+
nodes[nextIndex].focus();
|
|
211
|
+
}
|
|
239
212
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
213
|
+
function handleSearchKeydown(event) {
|
|
214
|
+
if (event.key === "Escape") {
|
|
215
|
+
event.preventDefault();
|
|
216
|
+
event.stopPropagation();
|
|
217
|
+
open.value = false;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
244
220
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
221
|
+
if (event.key === "ArrowDown") {
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
event.stopPropagation();
|
|
224
|
+
moveOptionFocus(1);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (event.key === "ArrowUp") {
|
|
229
|
+
event.preventDefault();
|
|
230
|
+
event.stopPropagation();
|
|
231
|
+
moveOptionFocus(-1);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (event.key === "Home") {
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
event.stopPropagation();
|
|
238
|
+
focusFirstOrLastOption(false);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (event.key === "End") {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
event.stopPropagation();
|
|
245
|
+
focusFirstOrLastOption(true);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (event.key === "Enter") {
|
|
250
|
+
const container = document.getElementById(dropdownId.value);
|
|
251
|
+
if (!container) return;
|
|
252
|
+
const active = document.activeElement;
|
|
253
|
+
if (active && container.contains(active) && active.getAttribute("role") === "option") {
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
event.stopPropagation();
|
|
256
|
+
active.click();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function updateScrollState() {
|
|
262
|
+
const container = document.getElementById(dropdownId.value);
|
|
263
|
+
const viewport = container?.querySelector("[data-reka-select-viewport]");
|
|
264
|
+
if (!viewport) {
|
|
265
|
+
canScrollAny.value = false;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
canScrollAny.value = viewport.scrollHeight > viewport.clientHeight + 1;
|
|
269
|
+
}
|
|
266
270
|
</script>
|