All files / src/gestures/drag/utils constraints.ts

84.13% Statements 53/63
85% Branches 34/40
76.92% Functions 10/13
83.05% Lines 49/59

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 22534x 34x                           34x   25x     25x   9x 16x   9x     25x                     34x               2x   2x               34x         25x                         34x   7x   7x                 34x       3x 3x       3x       1x     3x           34x                     34x                                           34x                         34x         2x 2x 2x           34x       16x   16x 7x     16x 7x     16x     34x       34x 34x   34x 6x 28x 2x     34x           34x         68x           34x       136x        
import { clamp, mix, progress as calcProgress } from "popmotion"
import { calcLength } from "../../../projection/geometry/delta-calc"
import {
    Axis,
    BoundingBox,
    Box,
    Point,
} from "../../../projection/geometry/types"
import { DragElastic, ResolvedConstraints } from "../types"
 
/**
 * Apply constraints to a point. These constraints are both physical along an
 * axis, and an elastic factor that determines how much to constrain the point
 * by if it does lie outside the defined parameters.
 */
export function applyConstraints(
    point: number,
    { min, max }: Partial<Axis>,
    elastic?: Axis
): number {
    if (min !== undefined && point < min) {
        // If we have a min point defined, and this is outside of that, constrain
        point = elastic ? mix(min, point, elastic.min) : Math.max(point, min)
    } else if (max !== undefined && point > max) {
        // If we have a max point defined, and this is outside of that, constrain
        point = elastic ? mix(max, point, elastic.max) : Math.min(point, max)
    }
 
    return point
}
 
/**
 * Calculates a min projection point based on a pointer, pointer progress
 * within the drag target, and constraints.
 *
 * For instance if an element was 100px width, we were dragging from 0.25
 * along this axis, the pointer is at 200px, and there were no constraints,
 * we would calculate a min projection point of 175px.
 */
export function calcConstrainedMinPoint(
    point: number,
    length: number,
    progress: number,
    constraints?: Partial<Axis>,
    elastic?: Axis
): number {
    // Calculate a min point for this axis and apply it to the current pointer
    const min = point - length * progress
 
    return constraints ? applyConstraints(min, constraints, elastic) : min
}
 
/**
 * Calculate constraints in terms of the viewport when defined relatively to the
 * measured axis. This is measured from the nearest edge, so a max constraint of 200
 * on an axis with a max value of 300 would return a constraint of 500 - axis length
 */
export function calcRelativeAxisConstraints(
    axis: Axis,
    min?: number,
    max?: number
): Partial<Axis> {
    return {
        min: min !== undefined ? axis.min + min : undefined,
        max:
            max !== undefined
                ? axis.max + max - (axis.max - axis.min)
                : undefined,
    }
}
 
/**
 * Calculate constraints in terms of the viewport when
 * defined relatively to the measured bounding box.
 */
export function calcRelativeConstraints(
    layoutBox: Box,
    { top, left, bottom, right }: Partial<BoundingBox>
): ResolvedConstraints {
    return {
        x: calcRelativeAxisConstraints(layoutBox.x, left, right),
        y: calcRelativeAxisConstraints(layoutBox.y, top, bottom),
    }
}
 
/**
 * Calculate viewport constraints when defined as another viewport-relative axis
 */
export function calcViewportAxisConstraints(
    layoutAxis: Axis,
    constraintsAxis: Axis
) {
    let min = constraintsAxis.min - layoutAxis.min
    let max = constraintsAxis.max - layoutAxis.max
 
    // If the constraints axis is actually smaller than the layout axis then we can
    // flip the constraints
    if (
        constraintsAxis.max - constraintsAxis.min <
        layoutAxis.max - layoutAxis.min
    ) {
        ;[min, max] = [max, min]
    }
 
    return { min, max }
}
 
/**
 * Calculate viewport constraints when defined as another viewport-relative box
 */
export function calcViewportConstraints(layoutBox: Box, constraintsBox: Box) {
    return {
        x: calcViewportAxisConstraints(layoutBox.x, constraintsBox.x),
        y: calcViewportAxisConstraints(layoutBox.y, constraintsBox.y),
    }
}
 
/**
 * Calculate a transform origin relative to the source axis, between 0-1, that results
 * in an asthetically pleasing scale/transform needed to project from source to target.
 */
export function calcOrigin(source: Axis, target: Axis): number {
    let origin = 0.5
    const sourceLength = calcLength(source)
    const targetLength = calcLength(target)
 
    if (targetLength > sourceLength) {
        origin = calcProgress(target.min, target.max - sourceLength, source.min)
    } else if (sourceLength > targetLength) {
        origin = calcProgress(source.min, source.max - targetLength, target.min)
    }
 
    return clamp(0, 1, origin)
}
 
/**
 * Calculate the relative progress of one constraints box relative to another.
 * Imagine a page scroll bar. At the top, this would return 0, at the bottom, 1.
 * Anywhere in-between, a value between 0 and 1.
 *
 * This also handles flipped constraints, for instance a draggable container within
 * a smaller viewport like a scrollable view.
 */
export function calcProgressWithinConstraints(
    layoutBox: Box,
    constraintsBox: Box
): Point {
    return {
        x: calcOrigin(layoutBox.x, constraintsBox.x),
        y: calcOrigin(layoutBox.y, constraintsBox.y),
    }
}
 
/**
 * Calculate the an axis position based on two axes and a progress value.
 */
export function calcPositionFromProgress(
    axis: Axis,
    constraints: Axis,
    progress: number
): Axis {
    const axisLength = axis.max - axis.min
    const min = mix(constraints.min, constraints.max - axisLength, progress)
    return { min, max: min + axisLength }
}
 
/**
 * Rebase the calculated viewport constraints relative to the layout.min point.
 */
export function rebaseAxisConstraints(
    layout: Axis,
    constraints: Partial<Axis>
) {
    const relativeConstraints: Partial<Axis> = {}
 
    if (constraints.min !== undefined) {
        relativeConstraints.min = constraints.min - layout.min
    }
 
    if (constraints.max !== undefined) {
        relativeConstraints.max = constraints.max - layout.min
    }
 
    return relativeConstraints
}
 
export const defaultElastic = 0.35
/**
 * Accepts a dragElastic prop and returns resolved elastic values for each axis.
 */
export function resolveDragElastic(
    IdragElastic: DragElastic = defaultElastic
): Box {
    if (dragElastic === false) {
        dragElastic = 0
    } else if (dragElastic === true) {
        dragElastic = defaultElastic
    }
 
    return {
        x: resolveAxisElastic(dragElastic, "left", "right"),
        y: resolveAxisElastic(dragElastic, "top", "bottom"),
    }
}
 
export function resolveAxisElastic(
    dragElastic: DragElastic,
    minLabel: string,
    maxLabel: string
): Axis {
    return {
        min: resolvePointElastic(dragElastic, minLabel),
        max: resolvePointElastic(dragElastic, maxLabel),
    }
}
 
export function resolvePointElastic(
    dragElastic: DragElastic,
    label: string
): number {
    return typeof dragElastic === "number"
        ? dragElastic
        : dragElastic[label] ?? 0
}