Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
package.lib.components.VOverlay.locationStrategies.mjs Maven / Gradle / Ivy
// Composables
import { useToggleScope } from "../../composables/toggleScope.mjs"; // Utilities
import { computed, nextTick, onScopeDispose, ref, watch } from 'vue';
import { anchorToPoint, getOffset } from "./util/point.mjs";
import { clamp, consoleError, convertToUnit, destructComputed, flipAlign, flipCorner, flipSide, getAxis, getScrollParents, IN_BROWSER, isFixedPosition, nullifyTransforms, parseAnchor, propsFactory } from "../../util/index.mjs";
import { Box, getOverflow, getTargetBox } from "../../util/box.mjs"; // Types
const locationStrategies = {
static: staticLocationStrategy,
// specific viewport position, usually centered
connected: connectedLocationStrategy // connected to a certain element
};
export const makeLocationStrategyProps = propsFactory({
locationStrategy: {
type: [String, Function],
default: 'static',
validator: val => typeof val === 'function' || val in locationStrategies
},
location: {
type: String,
default: 'bottom'
},
origin: {
type: String,
default: 'auto'
},
offset: [Number, String, Array]
}, 'VOverlay-location-strategies');
export function useLocationStrategies(props, data) {
const contentStyles = ref({});
const updateLocation = ref();
if (IN_BROWSER) {
useToggleScope(() => !!(data.isActive.value && props.locationStrategy), reset => {
watch(() => props.locationStrategy, reset);
onScopeDispose(() => {
window.removeEventListener('resize', onResize);
updateLocation.value = undefined;
});
window.addEventListener('resize', onResize, {
passive: true
});
if (typeof props.locationStrategy === 'function') {
updateLocation.value = props.locationStrategy(data, props, contentStyles)?.updateLocation;
} else {
updateLocation.value = locationStrategies[props.locationStrategy](data, props, contentStyles)?.updateLocation;
}
});
}
function onResize(e) {
updateLocation.value?.(e);
}
return {
contentStyles,
updateLocation
};
}
function staticLocationStrategy() {
// TODO
}
/** Get size of element ignoring max-width/max-height */
function getIntrinsicSize(el, isRtl) {
// const scrollables = new Map()
// el.querySelectorAll('*').forEach(el => {
// const x = el.scrollLeft
// const y = el.scrollTop
// if (x || y) {
// scrollables.set(el, [x, y])
// }
// })
// const initialMaxWidth = el.style.maxWidth
// const initialMaxHeight = el.style.maxHeight
// el.style.removeProperty('max-width')
// el.style.removeProperty('max-height')
if (isRtl) {
el.style.removeProperty('left');
} else {
el.style.removeProperty('right');
}
/* eslint-disable-next-line sonarjs/prefer-immediate-return */
const contentBox = nullifyTransforms(el);
if (isRtl) {
contentBox.x += parseFloat(el.style.right || 0);
} else {
contentBox.x -= parseFloat(el.style.left || 0);
}
contentBox.y -= parseFloat(el.style.top || 0);
// el.style.maxWidth = initialMaxWidth
// el.style.maxHeight = initialMaxHeight
// scrollables.forEach((position, el) => {
// el.scrollTo(...position)
// })
return contentBox;
}
function connectedLocationStrategy(data, props, contentStyles) {
const activatorFixed = Array.isArray(data.target.value) || isFixedPosition(data.target.value);
if (activatorFixed) {
Object.assign(contentStyles.value, {
position: 'fixed',
top: 0,
[data.isRtl.value ? 'right' : 'left']: 0
});
}
const {
preferredAnchor,
preferredOrigin
} = destructComputed(() => {
const parsedAnchor = parseAnchor(props.location, data.isRtl.value);
const parsedOrigin = props.origin === 'overlap' ? parsedAnchor : props.origin === 'auto' ? flipSide(parsedAnchor) : parseAnchor(props.origin, data.isRtl.value);
// Some combinations of props may produce an invalid origin
if (parsedAnchor.side === parsedOrigin.side && parsedAnchor.align === flipAlign(parsedOrigin).align) {
return {
preferredAnchor: flipCorner(parsedAnchor),
preferredOrigin: flipCorner(parsedOrigin)
};
} else {
return {
preferredAnchor: parsedAnchor,
preferredOrigin: parsedOrigin
};
}
});
const [minWidth, minHeight, maxWidth, maxHeight] = ['minWidth', 'minHeight', 'maxWidth', 'maxHeight'].map(key => {
return computed(() => {
const val = parseFloat(props[key]);
return isNaN(val) ? Infinity : val;
});
});
const offset = computed(() => {
if (Array.isArray(props.offset)) {
return props.offset;
}
if (typeof props.offset === 'string') {
const offset = props.offset.split(' ').map(parseFloat);
if (offset.length < 2) offset.push(0);
return offset;
}
return typeof props.offset === 'number' ? [props.offset, 0] : [0, 0];
});
let observe = false;
const observer = new ResizeObserver(() => {
if (observe) updateLocation();
});
watch([data.target, data.contentEl], (_ref, _ref2) => {
let [newTarget, newContentEl] = _ref;
let [oldTarget, oldContentEl] = _ref2;
if (oldTarget && !Array.isArray(oldTarget)) observer.unobserve(oldTarget);
if (newTarget && !Array.isArray(newTarget)) observer.observe(newTarget);
if (oldContentEl) observer.unobserve(oldContentEl);
if (newContentEl) observer.observe(newContentEl);
}, {
immediate: true
});
onScopeDispose(() => {
observer.disconnect();
});
// eslint-disable-next-line max-statements
function updateLocation() {
observe = false;
requestAnimationFrame(() => observe = true);
if (!data.target.value || !data.contentEl.value) return;
const targetBox = getTargetBox(data.target.value);
const contentBox = getIntrinsicSize(data.contentEl.value, data.isRtl.value);
const scrollParents = getScrollParents(data.contentEl.value);
const viewportMargin = 12;
if (!scrollParents.length) {
scrollParents.push(document.documentElement);
if (!(data.contentEl.value.style.top && data.contentEl.value.style.left)) {
contentBox.x -= parseFloat(document.documentElement.style.getPropertyValue('--v-body-scroll-x') || 0);
contentBox.y -= parseFloat(document.documentElement.style.getPropertyValue('--v-body-scroll-y') || 0);
}
}
const viewport = scrollParents.reduce((box, el) => {
const rect = el.getBoundingClientRect();
const scrollBox = new Box({
x: el === document.documentElement ? 0 : rect.x,
y: el === document.documentElement ? 0 : rect.y,
width: el.clientWidth,
height: el.clientHeight
});
if (box) {
return new Box({
x: Math.max(box.left, scrollBox.left),
y: Math.max(box.top, scrollBox.top),
width: Math.min(box.right, scrollBox.right) - Math.max(box.left, scrollBox.left),
height: Math.min(box.bottom, scrollBox.bottom) - Math.max(box.top, scrollBox.top)
});
}
return scrollBox;
}, undefined);
viewport.x += viewportMargin;
viewport.y += viewportMargin;
viewport.width -= viewportMargin * 2;
viewport.height -= viewportMargin * 2;
let placement = {
anchor: preferredAnchor.value,
origin: preferredOrigin.value
};
function checkOverflow(_placement) {
const box = new Box(contentBox);
const targetPoint = anchorToPoint(_placement.anchor, targetBox);
const contentPoint = anchorToPoint(_placement.origin, box);
let {
x,
y
} = getOffset(targetPoint, contentPoint);
switch (_placement.anchor.side) {
case 'top':
y -= offset.value[0];
break;
case 'bottom':
y += offset.value[0];
break;
case 'left':
x -= offset.value[0];
break;
case 'right':
x += offset.value[0];
break;
}
switch (_placement.anchor.align) {
case 'top':
y -= offset.value[1];
break;
case 'bottom':
y += offset.value[1];
break;
case 'left':
x -= offset.value[1];
break;
case 'right':
x += offset.value[1];
break;
}
box.x += x;
box.y += y;
box.width = Math.min(box.width, maxWidth.value);
box.height = Math.min(box.height, maxHeight.value);
const overflows = getOverflow(box, viewport);
return {
overflows,
x,
y
};
}
let x = 0;
let y = 0;
const available = {
x: 0,
y: 0
};
const flipped = {
x: false,
y: false
};
let resets = -1;
while (true) {
if (resets++ > 10) {
consoleError('Infinite loop detected in connectedLocationStrategy');
break;
}
const {
x: _x,
y: _y,
overflows
} = checkOverflow(placement);
x += _x;
y += _y;
contentBox.x += _x;
contentBox.y += _y;
// flip
{
const axis = getAxis(placement.anchor);
const hasOverflowX = overflows.x.before || overflows.x.after;
const hasOverflowY = overflows.y.before || overflows.y.after;
let reset = false;
['x', 'y'].forEach(key => {
if (key === 'x' && hasOverflowX && !flipped.x || key === 'y' && hasOverflowY && !flipped.y) {
const newPlacement = {
anchor: {
...placement.anchor
},
origin: {
...placement.origin
}
};
const flip = key === 'x' ? axis === 'y' ? flipAlign : flipSide : axis === 'y' ? flipSide : flipAlign;
newPlacement.anchor = flip(newPlacement.anchor);
newPlacement.origin = flip(newPlacement.origin);
const {
overflows: newOverflows
} = checkOverflow(newPlacement);
if (newOverflows[key].before <= overflows[key].before && newOverflows[key].after <= overflows[key].after || newOverflows[key].before + newOverflows[key].after < (overflows[key].before + overflows[key].after) / 2) {
placement = newPlacement;
reset = flipped[key] = true;
}
}
});
if (reset) continue;
}
// shift
if (overflows.x.before) {
x += overflows.x.before;
contentBox.x += overflows.x.before;
}
if (overflows.x.after) {
x -= overflows.x.after;
contentBox.x -= overflows.x.after;
}
if (overflows.y.before) {
y += overflows.y.before;
contentBox.y += overflows.y.before;
}
if (overflows.y.after) {
y -= overflows.y.after;
contentBox.y -= overflows.y.after;
}
// size
{
const overflows = getOverflow(contentBox, viewport);
available.x = viewport.width - overflows.x.before - overflows.x.after;
available.y = viewport.height - overflows.y.before - overflows.y.after;
x += overflows.x.before;
contentBox.x += overflows.x.before;
y += overflows.y.before;
contentBox.y += overflows.y.before;
}
break;
}
const axis = getAxis(placement.anchor);
Object.assign(contentStyles.value, {
'--v-overlay-anchor-origin': `${placement.anchor.side} ${placement.anchor.align}`,
transformOrigin: `${placement.origin.side} ${placement.origin.align}`,
// transform: `translate(${pixelRound(x)}px, ${pixelRound(y)}px)`,
top: convertToUnit(pixelRound(y)),
left: data.isRtl.value ? undefined : convertToUnit(pixelRound(x)),
right: data.isRtl.value ? convertToUnit(pixelRound(-x)) : undefined,
minWidth: convertToUnit(axis === 'y' ? Math.min(minWidth.value, targetBox.width) : minWidth.value),
maxWidth: convertToUnit(pixelCeil(clamp(available.x, minWidth.value === Infinity ? 0 : minWidth.value, maxWidth.value))),
maxHeight: convertToUnit(pixelCeil(clamp(available.y, minHeight.value === Infinity ? 0 : minHeight.value, maxHeight.value)))
});
return {
available,
contentBox
};
}
watch(() => [preferredAnchor.value, preferredOrigin.value, props.offset, props.minWidth, props.minHeight, props.maxWidth, props.maxHeight], () => updateLocation());
nextTick(() => {
const result = updateLocation();
// TODO: overflowing content should only require a single updateLocation call
// Icky hack to make sure the content is positioned consistently
if (!result) return;
const {
available,
contentBox
} = result;
if (contentBox.height > available.y) {
requestAnimationFrame(() => {
updateLocation();
requestAnimationFrame(() => {
updateLocation();
});
});
}
});
return {
updateLocation
};
}
function pixelRound(val) {
return Math.round(val * devicePixelRatio) / devicePixelRatio;
}
function pixelCeil(val) {
return Math.ceil(val * devicePixelRatio) / devicePixelRatio;
}
//# sourceMappingURL=locationStrategies.mjs.map