@v1nt1248/3nclient-lib 0.3.29 → 0.3.30
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/dist/components/ui3n-autocomplete/types.d.ts +5 -0
- package/dist/components/ui3n-menu/types.d.ts +5 -0
- package/dist/components/ui3n-menu/ui3n-menu.d.ts +1 -0
- package/dist/style.css +1 -1
- package/dist/ui3n-components.js +512 -463
- package/package.json +1 -1
- package/src/components/ui3n-autocomplete/types.ts +5 -0
- package/src/components/ui3n-autocomplete/ui3n-autocomplete.vue +7 -4
- package/src/components/ui3n-menu/types.ts +5 -0
- package/src/components/ui3n-menu/ui3n-menu.vue +126 -7
package/package.json
CHANGED
|
@@ -57,6 +57,11 @@ export interface Ui3nAutocompleteProps<T extends Ui3nAutocompleteOptionBase> {
|
|
|
57
57
|
* @default false
|
|
58
58
|
*/
|
|
59
59
|
hideSelected?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Whether to lock scroll when menu is open
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
lockScroll?: boolean;
|
|
60
65
|
/**
|
|
61
66
|
* Items to display
|
|
62
67
|
*/
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const vUi3nHtml = Ui3nHtml;
|
|
20
20
|
|
|
21
21
|
const props = withDefaults(defineProps<Ui3nAutocompleteProps<T>>(), {
|
|
22
|
+
lockScroll: false,
|
|
22
23
|
clearOnSelect: false,
|
|
23
24
|
filterKeys: () => [],
|
|
24
25
|
hideSelected: false,
|
|
@@ -133,7 +134,7 @@
|
|
|
133
134
|
function handlePressingDownKey() {
|
|
134
135
|
if (activeItemsIndex.value === null) {
|
|
135
136
|
activeItemsIndex.value = 0;
|
|
136
|
-
menuBodyEl.value && menuBodyEl.value.focus();
|
|
137
|
+
menuBodyEl.value && menuBodyEl.value.focus({ preventScroll: true });
|
|
137
138
|
return;
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -145,7 +146,7 @@
|
|
|
145
146
|
function handlePressingUpKey() {
|
|
146
147
|
if (activeItemsIndex.value === null) {
|
|
147
148
|
activeItemsIndex.value = size(filteredItems.value) - 1;
|
|
148
|
-
menuBodyEl.value && menuBodyEl.value.focus();
|
|
149
|
+
menuBodyEl.value && menuBodyEl.value.focus({ preventScroll: true });
|
|
149
150
|
return;
|
|
150
151
|
}
|
|
151
152
|
|
|
@@ -167,7 +168,7 @@
|
|
|
167
168
|
if (activeItemsIndex.value === null && !isMenuOpen.value) {
|
|
168
169
|
isMenuOpen.value = true;
|
|
169
170
|
activeItemsIndex.value = 0;
|
|
170
|
-
menuBodyEl.value && menuBodyEl.value.focus();
|
|
171
|
+
menuBodyEl.value && menuBodyEl.value.focus({ preventScroll: true });
|
|
171
172
|
return;
|
|
172
173
|
}
|
|
173
174
|
|
|
@@ -322,8 +323,10 @@
|
|
|
322
323
|
|
|
323
324
|
<ui3n-menu
|
|
324
325
|
v-model="isMenuOpen"
|
|
326
|
+
:lock-scroll="lockScroll"
|
|
325
327
|
:offset-x="2"
|
|
326
328
|
:offset-y="4"
|
|
329
|
+
position-strategy="fixed"
|
|
327
330
|
position-autoupdate
|
|
328
331
|
:content-border-radius="[0, 0, 8, 8]"
|
|
329
332
|
:disabled="disabled"
|
|
@@ -381,7 +384,7 @@
|
|
|
381
384
|
:placeholder="isEmpty(modelValue) && placeholder ? placeholder : ''"
|
|
382
385
|
:disabled="disabled"
|
|
383
386
|
@input="onInput"
|
|
384
|
-
@focusin="emits('update:focused', true)"
|
|
387
|
+
@focusin.prevent="emits('update:focused', true)"
|
|
385
388
|
@focusout="onBlur"
|
|
386
389
|
@keydown.down="onKeydown($event, 'down')"
|
|
387
390
|
@keydown.up="onKeydown($event, 'up')"
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { computed, ref, watch, useTemplateRef } from 'vue';
|
|
2
|
+
import { computed, nextTick, onUnmounted, ref, watch, useTemplateRef } from 'vue';
|
|
3
3
|
import { autoUpdate, flip, useFloating, offset, shift } from '@floating-ui/vue';
|
|
4
4
|
import { default as vClickOutside } from '../../directives/ui3n-click-outside';
|
|
5
5
|
import { Nullable } from '@/types';
|
|
6
6
|
import type { Ui3nMenuEmits, Ui3nMenuProps, Ui3nMenuSlots, Ui3nMenuExpose } from './types';
|
|
7
7
|
|
|
8
8
|
const props = withDefaults(defineProps<Ui3nMenuProps>(), {
|
|
9
|
+
lockScroll: false,
|
|
9
10
|
positionStrategy: 'absolute',
|
|
10
11
|
offsetX: 0,
|
|
11
12
|
offsetY: 0,
|
|
@@ -23,6 +24,8 @@
|
|
|
23
24
|
const menuContentElement = useTemplateRef<HTMLDivElement>('menu-content-element');
|
|
24
25
|
const isShow = ref(false);
|
|
25
26
|
|
|
27
|
+
let targetScrollElement: HTMLElement | null = null;
|
|
28
|
+
|
|
26
29
|
const outerTriggerElement = ref<Nullable<HTMLElement>>(props.triggerElement || null);
|
|
27
30
|
|
|
28
31
|
const usedTriggerElement = computed(() => outerTriggerElement.value || menuTriggerElement.value);
|
|
@@ -72,6 +75,109 @@
|
|
|
72
75
|
};
|
|
73
76
|
});
|
|
74
77
|
|
|
78
|
+
function preventScrollEvent(e: Event) {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getVerticalScrollParent(element: HTMLElement | null): HTMLElement {
|
|
83
|
+
if (!element) {
|
|
84
|
+
return document.body;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let parent: HTMLElement | null = element.parentElement;
|
|
88
|
+
|
|
89
|
+
while (parent && parent !== document.body && parent !== document.documentElement) {
|
|
90
|
+
const style = window.getComputedStyle(parent);
|
|
91
|
+
|
|
92
|
+
if (/(auto|scroll)/.test(style.overflowY || style.overflow)) {
|
|
93
|
+
if (parent.scrollHeight > parent.clientHeight) {
|
|
94
|
+
return parent;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
parent = parent.parentElement;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return document.body;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function lockScroll() {
|
|
104
|
+
if (!props.lockScroll) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const trigger = usedTriggerElement.value;
|
|
109
|
+
targetScrollElement = getVerticalScrollParent(trigger);
|
|
110
|
+
|
|
111
|
+
if (targetScrollElement) {
|
|
112
|
+
const el = targetScrollElement;
|
|
113
|
+
|
|
114
|
+
const currentLocks = Number(el.dataset.scrollLocksCount || '0');
|
|
115
|
+
|
|
116
|
+
// If this is the FIRST menu opened for this container
|
|
117
|
+
if (currentLocks === 0) {
|
|
118
|
+
el.dataset.scrollLocksCount = '1';
|
|
119
|
+
|
|
120
|
+
// We remember the original overscroll-behavior-y in data-original-overscroll
|
|
121
|
+
el.dataset.originalOverscroll = el.style.overscrollBehaviorY || '';
|
|
122
|
+
|
|
123
|
+
// Isolating scrolling
|
|
124
|
+
el.style.overscrollBehaviorY = 'contain';
|
|
125
|
+
|
|
126
|
+
if (targetScrollElement === document.body) {
|
|
127
|
+
document.addEventListener('wheel', preventScrollEvent, { passive: false });
|
|
128
|
+
document.addEventListener('touchmove', preventScrollEvent, { passive: false });
|
|
129
|
+
} else {
|
|
130
|
+
targetScrollElement.addEventListener('wheel', preventScrollEvent, { passive: false });
|
|
131
|
+
targetScrollElement.addEventListener('touchmove', preventScrollEvent, { passive: false });
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// If the container is already blocked by another menu, simply increment the counter
|
|
135
|
+
el.dataset.scrollLocksCount = String(currentLocks + 1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function unlockScroll() {
|
|
141
|
+
if (!targetScrollElement) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const el = targetScrollElement;
|
|
146
|
+
const currentLocks = Number(el.dataset.scrollLocksCount || '0');
|
|
147
|
+
|
|
148
|
+
if (currentLocks > 0) {
|
|
149
|
+
const newLocks = currentLocks - 1;
|
|
150
|
+
|
|
151
|
+
// Release the lock only when the LAST menu holding this container has closed
|
|
152
|
+
if (newLocks === 0) {
|
|
153
|
+
const originalOverscroll = el.dataset.originalOverscroll;
|
|
154
|
+
|
|
155
|
+
if (originalOverscroll) {
|
|
156
|
+
el.style.overscrollBehaviorY = originalOverscroll;
|
|
157
|
+
} else {
|
|
158
|
+
el.style.removeProperty('overscroll-behavior-y');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (targetScrollElement === document.body) {
|
|
162
|
+
document.removeEventListener('wheel', preventScrollEvent);
|
|
163
|
+
document.removeEventListener('touchmove', preventScrollEvent);
|
|
164
|
+
} else {
|
|
165
|
+
targetScrollElement.removeEventListener('wheel', preventScrollEvent);
|
|
166
|
+
targetScrollElement.removeEventListener('touchmove', preventScrollEvent);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// We completely remove data attributes from the DOM tree to avoid leaving garbage.
|
|
170
|
+
delete el.dataset.scrollLocksCount;
|
|
171
|
+
delete el.dataset.originalOverscroll;
|
|
172
|
+
} else {
|
|
173
|
+
// If there are still open menus, simply keep the reduced counter
|
|
174
|
+
el.dataset.scrollLocksCount = String(newLocks);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
targetScrollElement = null;
|
|
179
|
+
}
|
|
180
|
+
|
|
75
181
|
function toggleMenu() {
|
|
76
182
|
if (props.disabled) {
|
|
77
183
|
return;
|
|
@@ -101,13 +207,13 @@
|
|
|
101
207
|
}
|
|
102
208
|
}
|
|
103
209
|
|
|
104
|
-
watch(isPositioned, val => {
|
|
105
|
-
val ? emits('opened') : emits('closed');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
210
|
watch(
|
|
109
211
|
() => props.modelValue,
|
|
110
|
-
val => {
|
|
212
|
+
async val => {
|
|
213
|
+
if (!val) {
|
|
214
|
+
unlockScroll();
|
|
215
|
+
}
|
|
216
|
+
|
|
111
217
|
if (isShow.value !== val) {
|
|
112
218
|
isShow.value = val;
|
|
113
219
|
}
|
|
@@ -115,6 +221,15 @@
|
|
|
115
221
|
{ immediate: true },
|
|
116
222
|
);
|
|
117
223
|
|
|
224
|
+
watch(isPositioned, val => {
|
|
225
|
+
if (val) {
|
|
226
|
+
lockScroll();
|
|
227
|
+
emits('opened');
|
|
228
|
+
} else {
|
|
229
|
+
emits('closed');
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
118
233
|
watch(
|
|
119
234
|
() => props.triggerElement,
|
|
120
235
|
val => {
|
|
@@ -131,6 +246,10 @@
|
|
|
131
246
|
}
|
|
132
247
|
});
|
|
133
248
|
|
|
249
|
+
onUnmounted(() => {
|
|
250
|
+
unlockScroll();
|
|
251
|
+
});
|
|
252
|
+
|
|
134
253
|
defineExpose<Ui3nMenuExpose>({
|
|
135
254
|
isPositioned,
|
|
136
255
|
update,
|
|
@@ -187,7 +306,7 @@
|
|
|
187
306
|
|
|
188
307
|
.ui3nMenuContent {
|
|
189
308
|
position: absolute;
|
|
190
|
-
border-radius: var(--ui3n-menu-content-border-
|
|
309
|
+
border-radius: var(--ui3n-menu-content-border-radius);
|
|
191
310
|
background-color: var(--ui3n-menu-content-bg);
|
|
192
311
|
z-index: var(--ui3n-menu-zIndex, 1000);
|
|
193
312
|
overflow: hidden;
|