import { mix } from '../../utils/mix.mjs'; import { hasTransform } from '../utils/has-transform.mjs'; /** * Scales a point based on a factor and an originPoint */ function scalePoint(point, scale, originPoint) { const distanceFromOrigin = point - originPoint; const scaled = scale * distanceFromOrigin; return originPoint + scaled; } /** * Applies a translate/scale delta to a point */ function applyPointDelta(point, translate, scale, originPoint, boxScale) { if (boxScale !== undefined) { point = scalePoint(point, boxScale, originPoint); } return scalePoint(point, scale, originPoint) + translate; } /** * Applies a translate/scale delta to an axis */ function applyAxisDelta(axis, translate = 0, scale = 1, originPoint, boxScale) { axis.min = applyPointDelta(axis.min, translate, scale, originPoint, boxScale); axis.max = applyPointDelta(axis.max, translate, scale, originPoint, boxScale); } /** * Applies a translate/scale delta to a box */ function applyBoxDelta(box, { x, y }) { applyAxisDelta(box.x, x.translate, x.scale, x.originPoint); applyAxisDelta(box.y, y.translate, y.scale, y.originPoint); } /** * Apply a tree of deltas to a box. We do this to calculate the effect of all the transforms * in a tree upon our box before then calculating how to project it into our desired viewport-relative box * * This is the final nested loop within updateLayoutDelta for future refactoring */ function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) { const treeLength = treePath.length; if (!treeLength) return; // Reset the treeScale treeScale.x = treeScale.y = 1; let node; let delta; for (let i = 0; i < treeLength; i++) { node = treePath[i]; delta = node.projectionDelta; /** * TODO: Prefer to remove this, but currently we have motion components with * display: contents in Framer. */ const instance = node.instance; if (instance && instance.style && instance.style.display === "contents") { continue; } if (isSharedTransition && node.options.layoutScroll && node.scroll && node !== node.root) { transformBox(box, { x: -node.scroll.offset.x, y: -node.scroll.offset.y, }); } if (delta) { // Incoporate each ancestor's scale into a culmulative treeScale for this component treeScale.x *= delta.x.scale; treeScale.y *= delta.y.scale; // Apply each ancestor's calculated delta into this component's recorded layout box applyBoxDelta(box, delta); } if (isSharedTransition && hasTransform(node.latestValues)) { transformBox(box, node.latestValues); } } /** * Snap tree scale back to 1 if it's within a non-perceivable threshold. * This will help reduce useless scales getting rendered. */ treeScale.x = snapToDefault(treeScale.x); treeScale.y = snapToDefault(treeScale.y); } function snapToDefault(scale) { if (Number.isInteger(scale)) return scale; return scale > 1.0000000000001 || scale < 0.999999999999 ? scale : 1; } function translateAxis(axis, distance) { axis.min = axis.min + distance; axis.max = axis.max + distance; } /** * Apply a transform to an axis from the latest resolved motion values. * This function basically acts as a bridge between a flat motion value map * and applyAxisDelta */ function transformAxis(axis, transforms, [key, scaleKey, originKey]) { const axisOrigin = transforms[originKey] !== undefined ? transforms[originKey] : 0.5; const originPoint = mix(axis.min, axis.max, axisOrigin); // Apply the axis delta to the final axis applyAxisDelta(axis, transforms[key], transforms[scaleKey], originPoint, transforms.scale); } /** * The names of the motion values we want to apply as translation, scale and origin. */ const xKeys = ["x", "scaleX", "originX"]; const yKeys = ["y", "scaleY", "originY"]; /** * Apply a transform to a box from the latest resolved motion values. */ function transformBox(box, transform) { transformAxis(box.x, transform, xKeys); transformAxis(box.y, transform, yKeys); } export { applyAxisDelta, applyBoxDelta, applyPointDelta, applyTreeDeltas, scalePoint, transformAxis, transformBox, translateAxis };