// Utilities import { effectScope, nextTick, onScopeDispose, watchEffect } from 'vue'; import { requestNewFrame } from "./requestNewFrame.mjs"; import { convertToUnit, getScrollParents, hasScrollbar, IN_BROWSER, propsFactory } from "../../util/index.mjs"; // Types const scrollStrategies = { none: null, close: closeScrollStrategy, block: blockScrollStrategy, reposition: repositionScrollStrategy }; export const makeScrollStrategyProps = propsFactory({ scrollStrategy: { type: [String, Function], default: 'block', validator: val => typeof val === 'function' || val in scrollStrategies } }, 'VOverlay-scroll-strategies'); export function useScrollStrategies(props, data) { if (!IN_BROWSER) return; let scope; watchEffect(async () => { scope?.stop(); if (!(data.isActive.value && props.scrollStrategy)) return; scope = effectScope(); await nextTick(); scope.active && scope.run(() => { if (typeof props.scrollStrategy === 'function') { props.scrollStrategy(data, props, scope); } else { scrollStrategies[props.scrollStrategy]?.(data, props, scope); } }); }); onScopeDispose(() => { scope?.stop(); }); } function closeScrollStrategy(data) { function onScroll(e) { data.isActive.value = false; } bindScroll(data.targetEl.value ?? data.contentEl.value, onScroll); } function blockScrollStrategy(data, props) { const offsetParent = data.root.value?.offsetParent; const scrollElements = [...new Set([...getScrollParents(data.targetEl.value, props.contained ? offsetParent : undefined), ...getScrollParents(data.contentEl.value, props.contained ? offsetParent : undefined)])].filter(el => !el.classList.contains('v-overlay-scroll-blocked')); const scrollbarWidth = window.innerWidth - document.documentElement.offsetWidth; const scrollableParent = (el => hasScrollbar(el) && el)(offsetParent || document.documentElement); if (scrollableParent) { data.root.value.classList.add('v-overlay--scroll-blocked'); } scrollElements.forEach((el, i) => { el.style.setProperty('--v-body-scroll-x', convertToUnit(-el.scrollLeft)); el.style.setProperty('--v-body-scroll-y', convertToUnit(-el.scrollTop)); if (el !== document.documentElement) { el.style.setProperty('--v-scrollbar-offset', convertToUnit(scrollbarWidth)); } el.classList.add('v-overlay-scroll-blocked'); }); onScopeDispose(() => { scrollElements.forEach((el, i) => { const x = parseFloat(el.style.getPropertyValue('--v-body-scroll-x')); const y = parseFloat(el.style.getPropertyValue('--v-body-scroll-y')); const scrollBehavior = el.style.scrollBehavior; el.style.scrollBehavior = 'auto'; el.style.removeProperty('--v-body-scroll-x'); el.style.removeProperty('--v-body-scroll-y'); el.style.removeProperty('--v-scrollbar-offset'); el.classList.remove('v-overlay-scroll-blocked'); el.scrollLeft = -x; el.scrollTop = -y; el.style.scrollBehavior = scrollBehavior; }); if (scrollableParent) { data.root.value.classList.remove('v-overlay--scroll-blocked'); } }); } function repositionScrollStrategy(data, props, scope) { let slow = false; let raf = -1; let ric = -1; function update(e) { requestNewFrame(() => { const start = performance.now(); data.updateLocation.value?.(e); const time = performance.now() - start; slow = time / (1000 / 60) > 2; }); } ric = (typeof requestIdleCallback === 'undefined' ? cb => cb() : requestIdleCallback)(() => { scope.run(() => { bindScroll(data.targetEl.value ?? data.contentEl.value, e => { if (slow) { // If the position calculation is slow, // defer updates until scrolling is finished. // Browsers usually fire one scroll event per frame so // we just wait until we've got two frames without an event cancelAnimationFrame(raf); raf = requestAnimationFrame(() => { raf = requestAnimationFrame(() => { update(e); }); }); } else { update(e); } }); }); }); onScopeDispose(() => { typeof cancelIdleCallback !== 'undefined' && cancelIdleCallback(ric); cancelAnimationFrame(raf); }); } /** @private */ function bindScroll(el, onScroll) { const scrollElements = [document, ...getScrollParents(el)]; scrollElements.forEach(el => { el.addEventListener('scroll', onScroll, { passive: true }); }); onScopeDispose(() => { scrollElements.forEach(el => { el.removeEventListener('scroll', onScroll); }); }); } //# sourceMappingURL=scrollStrategies.mjs.map