@redseed/redseed-ui-vue3 8.40.1 → 8.41.0
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, onMounted, onUnmounted, watch, useAttrs } from 'vue'
|
|
3
|
-
import { onClickOutside
|
|
3
|
+
import { onClickOutside } from '@vueuse/core'
|
|
4
4
|
import FormFieldSlot from './FormFieldSlot.vue'
|
|
5
5
|
import { ChevronDownIcon, CheckIcon } from '@heroicons/vue/24/outline'
|
|
6
6
|
import { useFormFieldA11y } from '../../composables/useFormFieldA11y.js'
|
|
@@ -326,40 +326,53 @@ onClickOutside(comboboxElement, () => {
|
|
|
326
326
|
}
|
|
327
327
|
})
|
|
328
328
|
|
|
329
|
-
const inputElementBounding = useElementBounding(inputElement)
|
|
330
|
-
|
|
331
329
|
function calculateDropdownPosition() {
|
|
332
|
-
if (!dropdownElement.value) return
|
|
330
|
+
if (!dropdownElement.value || !inputElement.value) return
|
|
333
331
|
|
|
332
|
+
const bounding = inputElement.value.getBoundingClientRect()
|
|
334
333
|
const viewportHeight = window.innerHeight
|
|
335
334
|
const dropdownElementHeight = dropdownElement.value.offsetHeight
|
|
336
|
-
const spaceAboveInput =
|
|
337
|
-
const spaceBelowInput = viewportHeight -
|
|
335
|
+
const spaceAboveInput = bounding.top
|
|
336
|
+
const spaceBelowInput = viewportHeight - bounding.bottom
|
|
338
337
|
|
|
339
|
-
dropdownElement.value.style.width = `${
|
|
340
|
-
dropdownElement.value.style.left = `${
|
|
338
|
+
dropdownElement.value.style.width = `${bounding.width}px`
|
|
339
|
+
dropdownElement.value.style.left = `${bounding.left}px`
|
|
341
340
|
|
|
342
341
|
if (spaceAboveInput <= dropdownElementHeight && spaceBelowInput <= dropdownElementHeight) {
|
|
343
342
|
dropdownElement.value.style.top = '0'
|
|
344
343
|
dropdownElement.value.style.bottom = 'auto'
|
|
345
344
|
return
|
|
346
345
|
} else if (spaceBelowInput > dropdownElementHeight) {
|
|
347
|
-
dropdownElement.value.style.top = `${
|
|
346
|
+
dropdownElement.value.style.top = `${bounding.bottom + window.scrollY}px`
|
|
348
347
|
dropdownElement.value.style.bottom = 'auto'
|
|
349
348
|
return
|
|
350
349
|
} else if (spaceAboveInput > dropdownElementHeight) {
|
|
351
350
|
dropdownElement.value.style.top = 'auto'
|
|
352
|
-
dropdownElement.value.style.bottom = `${spaceBelowInput +
|
|
351
|
+
dropdownElement.value.style.bottom = `${spaceBelowInput + bounding.height + 8 - window.scrollY}px`
|
|
353
352
|
return
|
|
354
353
|
}
|
|
355
354
|
}
|
|
356
355
|
|
|
356
|
+
function handleScroll(event) {
|
|
357
|
+
if (dropdownElement.value?.contains(event.target)) return
|
|
358
|
+
calculateDropdownPosition()
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
watch(isOpen, (nowOpen) => {
|
|
362
|
+
if (nowOpen) {
|
|
363
|
+
window.addEventListener('scroll', handleScroll, { capture: true, passive: true })
|
|
364
|
+
} else {
|
|
365
|
+
window.removeEventListener('scroll', handleScroll, { capture: true })
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
|
|
357
369
|
onMounted(() => {
|
|
358
370
|
window.addEventListener('resize', calculateDropdownPosition)
|
|
359
371
|
})
|
|
360
372
|
|
|
361
373
|
onUnmounted(() => {
|
|
362
374
|
window.removeEventListener('resize', calculateDropdownPosition)
|
|
375
|
+
window.removeEventListener('scroll', handleScroll, { capture: true })
|
|
363
376
|
})
|
|
364
377
|
|
|
365
378
|
defineExpose({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, onMounted, onUnmounted, watch, useAttrs } from 'vue'
|
|
3
|
-
import { onClickOutside
|
|
3
|
+
import { onClickOutside } from '@vueuse/core'
|
|
4
4
|
import FormFieldSlot from './FormFieldSlot.vue'
|
|
5
5
|
import { MagnifyingGlassIcon } from '@heroicons/vue/24/outline'
|
|
6
6
|
import { useFormFieldA11y } from '../../composables/useFormFieldA11y.js'
|
|
@@ -245,39 +245,52 @@ onClickOutside(rootElement, () => {
|
|
|
245
245
|
// Measure the whole field box (icon prefix + input), not the inner <input> —
|
|
246
246
|
// the input sits to the right of the prefix and is narrower, so anchoring to
|
|
247
247
|
// it would leave the dropdown shifted right and narrower than the field.
|
|
248
|
-
const fieldElementBounding = useElementBounding(fieldElement)
|
|
249
|
-
|
|
250
248
|
function calculateDropdownPosition() {
|
|
251
|
-
if (!dropdownElement.value) return
|
|
249
|
+
if (!dropdownElement.value || !fieldElement.value) return
|
|
252
250
|
|
|
251
|
+
const bounding = fieldElement.value.getBoundingClientRect()
|
|
253
252
|
const viewportHeight = window.innerHeight
|
|
254
253
|
const dropdownHeight = dropdownElement.value.offsetHeight
|
|
255
|
-
const spaceAbove =
|
|
256
|
-
const spaceBelow = viewportHeight -
|
|
254
|
+
const spaceAbove = bounding.top
|
|
255
|
+
const spaceBelow = viewportHeight - bounding.bottom
|
|
257
256
|
|
|
258
257
|
dropdownElement.value.style.width = props.dropdownWidth != null
|
|
259
258
|
? (typeof props.dropdownWidth === 'number' ? `${props.dropdownWidth}px` : props.dropdownWidth)
|
|
260
|
-
: `${
|
|
261
|
-
dropdownElement.value.style.left = `${
|
|
259
|
+
: `${bounding.width}px`
|
|
260
|
+
dropdownElement.value.style.left = `${bounding.left}px`
|
|
262
261
|
|
|
263
262
|
if (spaceAbove <= dropdownHeight && spaceBelow <= dropdownHeight) {
|
|
264
263
|
dropdownElement.value.style.top = '0'
|
|
265
264
|
dropdownElement.value.style.bottom = 'auto'
|
|
266
265
|
} else if (spaceBelow > dropdownHeight) {
|
|
267
|
-
dropdownElement.value.style.top = `${
|
|
266
|
+
dropdownElement.value.style.top = `${bounding.bottom + window.scrollY}px`
|
|
268
267
|
dropdownElement.value.style.bottom = 'auto'
|
|
269
268
|
} else if (spaceAbove > dropdownHeight) {
|
|
270
269
|
dropdownElement.value.style.top = 'auto'
|
|
271
|
-
dropdownElement.value.style.bottom = `${spaceBelow +
|
|
270
|
+
dropdownElement.value.style.bottom = `${spaceBelow + bounding.height + 8 - window.scrollY}px`
|
|
272
271
|
}
|
|
273
272
|
}
|
|
274
273
|
|
|
274
|
+
function handleScroll(event) {
|
|
275
|
+
if (dropdownElement.value?.contains(event.target)) return
|
|
276
|
+
calculateDropdownPosition()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
watch(isOpen, (nowOpen) => {
|
|
280
|
+
if (nowOpen) {
|
|
281
|
+
window.addEventListener('scroll', handleScroll, { capture: true, passive: true })
|
|
282
|
+
} else {
|
|
283
|
+
window.removeEventListener('scroll', handleScroll, { capture: true })
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
275
287
|
onMounted(() => {
|
|
276
288
|
window.addEventListener('resize', calculateDropdownPosition)
|
|
277
289
|
})
|
|
278
290
|
|
|
279
291
|
onUnmounted(() => {
|
|
280
292
|
window.removeEventListener('resize', calculateDropdownPosition)
|
|
293
|
+
window.removeEventListener('scroll', handleScroll, { capture: true })
|
|
281
294
|
})
|
|
282
295
|
|
|
283
296
|
defineExpose({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, watch, onMounted, onUnmounted, useAttrs } from 'vue'
|
|
3
|
-
import { onClickOutside
|
|
3
|
+
import { onClickOutside } from '@vueuse/core'
|
|
4
4
|
import FormFieldSlot from './FormFieldSlot.vue'
|
|
5
5
|
import { ChevronDownIcon, CheckIcon } from '@heroicons/vue/24/outline'
|
|
6
6
|
import { useFormFieldA11y } from '../../composables/useFormFieldA11y.js'
|
|
@@ -144,10 +144,14 @@ onClickOutside(formFieldSelectElement, () => close())
|
|
|
144
144
|
|
|
145
145
|
// Anchor dropdown to the visible trigger (button on desktop, select on mobile)
|
|
146
146
|
const anchorElement = computed(() => triggerElement.value || selectElement.value)
|
|
147
|
-
const anchorBounding = useElementBounding(anchorElement)
|
|
148
147
|
|
|
149
148
|
function calculateDropdownPosition() {
|
|
150
|
-
if (!dropdownElement.value) return
|
|
149
|
+
if (!dropdownElement.value || !anchorElement.value) return
|
|
150
|
+
|
|
151
|
+
// Preserve scrollTop before the reset so scroll-chaining (wheel past the
|
|
152
|
+
// list's boundary chains to the page, triggering this handler) cannot snap
|
|
153
|
+
// the list back to the top when maxHeight is cleared and a reflow occurs.
|
|
154
|
+
const savedScrollTop = dropdownElement.value.scrollTop
|
|
151
155
|
|
|
152
156
|
// Reset inline positioning before measuring so a previously-applied
|
|
153
157
|
// max-height (from an earlier constrained open) doesn't pollute the
|
|
@@ -157,12 +161,13 @@ function calculateDropdownPosition() {
|
|
|
157
161
|
dropdownElement.value.style.top = ''
|
|
158
162
|
dropdownElement.value.style.bottom = ''
|
|
159
163
|
|
|
164
|
+
const bounding = anchorElement.value.getBoundingClientRect()
|
|
160
165
|
const dropdownElementHeight = dropdownElement.value.offsetHeight
|
|
161
166
|
const viewportHeight = window.innerHeight
|
|
162
|
-
const spaceAbove =
|
|
163
|
-
const spaceBelow = viewportHeight -
|
|
167
|
+
const spaceAbove = bounding.top
|
|
168
|
+
const spaceBelow = viewportHeight - bounding.bottom
|
|
164
169
|
|
|
165
|
-
dropdownElement.value.style.minWidth = `${
|
|
170
|
+
dropdownElement.value.style.minWidth = `${bounding.width}px`
|
|
166
171
|
|
|
167
172
|
/**
|
|
168
173
|
* Clamp the dropdown to the viewport horizontally.
|
|
@@ -176,13 +181,13 @@ function calculateDropdownPosition() {
|
|
|
176
181
|
dropdownElement.value.style.maxWidth = `${viewportWidth - safeMargin * 2}px`
|
|
177
182
|
|
|
178
183
|
// Temporarily position at trigger left so we can measure actual width
|
|
179
|
-
dropdownElement.value.style.left = `${
|
|
184
|
+
dropdownElement.value.style.left = `${bounding.left}px`
|
|
180
185
|
const dropdownWidth = dropdownElement.value.offsetWidth
|
|
181
|
-
const rightOverflow = (
|
|
186
|
+
const rightOverflow = (bounding.left + dropdownWidth + safeMargin) - viewportWidth
|
|
182
187
|
|
|
183
188
|
// Shift left if the dropdown overflows the right edge
|
|
184
189
|
if (rightOverflow > 0) {
|
|
185
|
-
const clampedLeft = Math.max(safeMargin,
|
|
190
|
+
const clampedLeft = Math.max(safeMargin, bounding.left - rightOverflow)
|
|
186
191
|
dropdownElement.value.style.left = `${clampedLeft}px`
|
|
187
192
|
}
|
|
188
193
|
|
|
@@ -192,18 +197,21 @@ function calculateDropdownPosition() {
|
|
|
192
197
|
const verticalOffset = 16
|
|
193
198
|
|
|
194
199
|
if (spaceBelow > dropdownElementHeight) {
|
|
195
|
-
dropdownElement.value.style.top = `${
|
|
200
|
+
dropdownElement.value.style.top = `${bounding.bottom + window.scrollY}px`
|
|
196
201
|
dropdownElement.value.style.bottom = 'auto'
|
|
202
|
+
dropdownElement.value.scrollTop = savedScrollTop
|
|
197
203
|
return
|
|
198
204
|
} else if (spaceAbove > spaceBelow) {
|
|
199
205
|
dropdownElement.value.style.top = 'auto'
|
|
200
|
-
dropdownElement.value.style.bottom = `${spaceBelow +
|
|
206
|
+
dropdownElement.value.style.bottom = `${spaceBelow + bounding.height + 8 - window.scrollY}px`
|
|
201
207
|
dropdownElement.value.style.maxHeight = `${spaceAbove - verticalOffset}px`
|
|
208
|
+
dropdownElement.value.scrollTop = savedScrollTop
|
|
202
209
|
return
|
|
203
210
|
} else {
|
|
204
|
-
dropdownElement.value.style.top = `${
|
|
211
|
+
dropdownElement.value.style.top = `${bounding.bottom + window.scrollY}px`
|
|
205
212
|
dropdownElement.value.style.bottom = 'auto'
|
|
206
213
|
dropdownElement.value.style.maxHeight = `${spaceBelow - verticalOffset}px`
|
|
214
|
+
dropdownElement.value.scrollTop = savedScrollTop
|
|
207
215
|
return
|
|
208
216
|
}
|
|
209
217
|
}
|
|
@@ -212,6 +220,19 @@ function handleResize() {
|
|
|
212
220
|
calculateDropdownPosition()
|
|
213
221
|
}
|
|
214
222
|
|
|
223
|
+
function handleScroll(event) {
|
|
224
|
+
if (dropdownElement.value?.contains(event.target)) return
|
|
225
|
+
calculateDropdownPosition()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
watch(isOpen, (nowOpen) => {
|
|
229
|
+
if (nowOpen) {
|
|
230
|
+
window.addEventListener('scroll', handleScroll, { capture: true, passive: true })
|
|
231
|
+
} else {
|
|
232
|
+
window.removeEventListener('scroll', handleScroll, { capture: true })
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
|
|
215
236
|
onMounted(() => {
|
|
216
237
|
isMobileDevice.value = 'ontouchstart' in window
|
|
217
238
|
|| (navigator.maxTouchPoints && navigator.maxTouchPoints > 0)
|
|
@@ -220,6 +241,7 @@ onMounted(() => {
|
|
|
220
241
|
|
|
221
242
|
onUnmounted(() => {
|
|
222
243
|
window.removeEventListener('resize', handleResize)
|
|
244
|
+
window.removeEventListener('scroll', handleScroll, { capture: true })
|
|
223
245
|
})
|
|
224
246
|
|
|
225
247
|
defineExpose({
|