@kitlangton/solid-motion 0.1.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/LICENSE +48 -0
- package/README.md +54 -0
- package/dist/animation/animate/index.d.ts +23 -0
- package/dist/animation/animate/resolve-subjects.d.ts +3 -0
- package/dist/animation/animate/sequence.d.ts +3 -0
- package/dist/animation/animate/subject.d.ts +7 -0
- package/dist/animation/sequence/create.d.ts +6 -0
- package/dist/animation/sequence/types.d.ts +51 -0
- package/dist/animation/sequence/utils/calc-repeat-duration.d.ts +1 -0
- package/dist/animation/sequence/utils/calc-time.d.ts +2 -0
- package/dist/animation/sequence/utils/edit.d.ts +5 -0
- package/dist/animation/sequence/utils/normalize-times.d.ts +1 -0
- package/dist/animation/sequence/utils/sort.d.ts +2 -0
- package/dist/animation/utils/create-visual-element.d.ts +2 -0
- package/dist/animation/utils/is-dom-keyframes.d.ts +2 -0
- package/dist/components/AnimatePresence.d.ts +27 -0
- package/dist/components/AnimatePresenceContext.d.ts +23 -0
- package/dist/components/AnimatePresenceTypes.d.ts +57 -0
- package/dist/components/MotionConfig.d.ts +16 -0
- package/dist/context/MotionContext/create.d.ts +3 -0
- package/dist/context/MotionContext/utils.d.ts +3 -0
- package/dist/context/MotionContext.d.ts +7 -0
- package/dist/context/PresenceContext.d.ts +13 -0
- package/dist/events/add-pointer-event.d.ts +2 -0
- package/dist/events/event-info.d.ts +4 -0
- package/dist/gestures/drag/VisualElementDragControls.d.ts +86 -0
- package/dist/gestures/drag/index.d.ts +11 -0
- package/dist/gestures/drag/utils/constraints.d.ts +77 -0
- package/dist/gestures/focus.d.ts +8 -0
- package/dist/gestures/hover.d.ts +5 -0
- package/dist/gestures/pan/PanSession.d.ts +102 -0
- package/dist/gestures/pan/index.d.ts +15 -0
- package/dist/gestures/press.d.ts +5 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +2466 -0
- package/dist/index.js.map +1 -0
- package/dist/motion/features/animation/exit.d.ts +7 -0
- package/dist/motion/features/animation/index.d.ts +9 -0
- package/dist/motion/features/animations.d.ts +10 -0
- package/dist/motion/features/definitions.d.ts +3 -0
- package/dist/motion/features/feature-bundle.d.ts +34 -0
- package/dist/motion/features/gestures.d.ts +26 -0
- package/dist/motion/features/load-features.d.ts +1 -0
- package/dist/motion/index.d.ts +22 -0
- package/dist/motion/types.d.ts +27 -0
- package/dist/motion/utils/drag-projection.d.ts +3 -0
- package/dist/motion/utils/use-visual-state.d.ts +14 -0
- package/dist/motion/utils/valid-prop.d.ts +1 -0
- package/dist/render/dom/create-visual-element.d.ts +2 -0
- package/dist/render/dom/filter-props.d.ts +2 -0
- package/dist/render/dom/is-svg-component.d.ts +1 -0
- package/dist/render/dom/style.d.ts +1 -0
- package/dist/render/html/use-props.d.ts +7 -0
- package/dist/render/html/utils/create-render-state.d.ts +2 -0
- package/dist/render/svg/lowercase-elements.d.ts +1 -0
- package/dist/render/svg/use-props.d.ts +3 -0
- package/dist/render/svg/utils/create-render-state.d.ts +2 -0
- package/dist/render/types.d.ts +3 -0
- package/dist/server.js +2407 -0
- package/dist/server.js.map +1 -0
- package/dist/source/animation/animate/index.js +39 -0
- package/dist/source/animation/animate/resolve-subjects.js +19 -0
- package/dist/source/animation/animate/sequence.js +11 -0
- package/dist/source/animation/animate/subject.js +37 -0
- package/dist/source/animation/sequence/create.js +171 -0
- package/dist/source/animation/sequence/types.js +1 -0
- package/dist/source/animation/sequence/utils/calc-repeat-duration.js +3 -0
- package/dist/source/animation/sequence/utils/calc-time.js +17 -0
- package/dist/source/animation/sequence/utils/edit.js +21 -0
- package/dist/source/animation/sequence/utils/normalize-times.js +5 -0
- package/dist/source/animation/sequence/utils/sort.js +12 -0
- package/dist/source/animation/utils/create-visual-element.js +37 -0
- package/dist/source/animation/utils/is-dom-keyframes.js +3 -0
- package/dist/source/components/AnimatePresence.jsx +444 -0
- package/dist/source/components/AnimatePresenceContext.jsx +20 -0
- package/dist/source/components/AnimatePresenceTypes.js +1 -0
- package/dist/source/components/MotionConfig.jsx +13 -0
- package/dist/source/context/MotionContext/create.js +19 -0
- package/dist/source/context/MotionContext/utils.js +11 -0
- package/dist/source/context/MotionContext.js +2 -0
- package/dist/source/context/PresenceContext.js +2 -0
- package/dist/source/events/add-pointer-event.js +5 -0
- package/dist/source/events/event-info.js +16 -0
- package/dist/source/gestures/drag/VisualElementDragControls.js +531 -0
- package/dist/source/gestures/drag/index.js +47 -0
- package/dist/source/gestures/drag/utils/constraints.js +160 -0
- package/dist/source/gestures/focus.js +37 -0
- package/dist/source/gestures/hover.js +25 -0
- package/dist/source/gestures/pan/PanSession.js +273 -0
- package/dist/source/gestures/pan/index.js +46 -0
- package/dist/source/gestures/press.js +28 -0
- package/dist/source/index.js +11 -0
- package/dist/source/motion/features/animation/exit.js +33 -0
- package/dist/source/motion/features/animation/index.js +27 -0
- package/dist/source/motion/features/animations.js +10 -0
- package/dist/source/motion/features/definitions.js +29 -0
- package/dist/source/motion/features/feature-bundle.js +6 -0
- package/dist/source/motion/features/gestures.js +20 -0
- package/dist/source/motion/features/load-features.js +12 -0
- package/dist/source/motion/index.jsx +176 -0
- package/dist/source/motion/types.js +1 -0
- package/dist/source/motion/utils/drag-projection.js +16 -0
- package/dist/source/motion/utils/use-visual-state.js +53 -0
- package/dist/source/motion/utils/valid-prop.js +40 -0
- package/dist/source/render/dom/create-visual-element.js +10 -0
- package/dist/source/render/dom/filter-props.js +15 -0
- package/dist/source/render/dom/is-svg-component.js +10 -0
- package/dist/source/render/dom/style.js +12 -0
- package/dist/source/render/html/use-props.js +39 -0
- package/dist/source/render/html/utils/create-render-state.js +6 -0
- package/dist/source/render/svg/lowercase-elements.js +27 -0
- package/dist/source/render/svg/use-props.js +28 -0
- package/dist/source/render/svg/utils/create-render-state.js +5 -0
- package/dist/source/render/types.js +1 -0
- package/dist/source/utils/distance.js +7 -0
- package/dist/source/utils/get-context-window.js +4 -0
- package/dist/source/utils/is-ref-object.js +5 -0
- package/dist/source/utils/safe-react-types.js +1 -0
- package/dist/source/utils/use-reduced-motion.js +15 -0
- package/dist/source/value/use-motion-value-event.js +5 -0
- package/dist/source/value/use-motion-value.js +16 -0
- package/dist/source/value/use-spring.js +59 -0
- package/dist/source/value/use-transform.js +25 -0
- package/dist/source/value/use-velocity.js +17 -0
- package/dist/utils/distance.d.ts +3 -0
- package/dist/utils/get-context-window.d.ts +2 -0
- package/dist/utils/is-ref-object.d.ts +2 -0
- package/dist/utils/safe-react-types.d.ts +3 -0
- package/dist/utils/use-reduced-motion.d.ts +6 -0
- package/dist/value/use-motion-value-event.d.ts +3 -0
- package/dist/value/use-motion-value.d.ts +5 -0
- package/dist/value/use-spring.d.ts +5 -0
- package/dist/value/use-transform.d.ts +9 -0
- package/dist/value/use-velocity.d.ts +3 -0
- package/package.json +61 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { calcLength, mixNumber } from "motion-dom";
|
|
2
|
+
import { progress as calcProgress, clamp, } from "motion-utils";
|
|
3
|
+
/**
|
|
4
|
+
* Apply constraints to a point. These constraints are both physical along an
|
|
5
|
+
* axis, and an elastic factor that determines how much to constrain the point
|
|
6
|
+
* by if it does lie outside the defined parameters.
|
|
7
|
+
*/
|
|
8
|
+
export function applyConstraints(point, { min, max }, elastic) {
|
|
9
|
+
if (min !== undefined && point < min) {
|
|
10
|
+
// If we have a min point defined, and this is outside of that, constrain
|
|
11
|
+
point = elastic
|
|
12
|
+
? mixNumber(min, point, elastic.min)
|
|
13
|
+
: Math.max(point, min);
|
|
14
|
+
}
|
|
15
|
+
else if (max !== undefined && point > max) {
|
|
16
|
+
// If we have a max point defined, and this is outside of that, constrain
|
|
17
|
+
point = elastic
|
|
18
|
+
? mixNumber(max, point, elastic.max)
|
|
19
|
+
: Math.min(point, max);
|
|
20
|
+
}
|
|
21
|
+
return point;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Calculates a min projection point based on a pointer, pointer progress
|
|
25
|
+
* within the drag target, and constraints.
|
|
26
|
+
*
|
|
27
|
+
* For instance if an element was 100px width, we were dragging from 0.25
|
|
28
|
+
* along this axis, the pointer is at 200px, and there were no constraints,
|
|
29
|
+
* we would calculate a min projection point of 175px.
|
|
30
|
+
*/
|
|
31
|
+
export function calcConstrainedMinPoint(point, length, progress, constraints, elastic) {
|
|
32
|
+
// Calculate a min point for this axis and apply it to the current pointer
|
|
33
|
+
const min = point - length * progress;
|
|
34
|
+
return constraints ? applyConstraints(min, constraints, elastic) : min;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Calculate constraints in terms of the viewport when defined relatively to the
|
|
38
|
+
* measured axis. This is measured from the nearest edge, so a max constraint of 200
|
|
39
|
+
* on an axis with a max value of 300 would return a constraint of 500 - axis length
|
|
40
|
+
*/
|
|
41
|
+
export function calcRelativeAxisConstraints(axis, min, max) {
|
|
42
|
+
return {
|
|
43
|
+
min: min !== undefined ? axis.min + min : undefined,
|
|
44
|
+
max: max !== undefined
|
|
45
|
+
? axis.max + max - (axis.max - axis.min)
|
|
46
|
+
: undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Calculate constraints in terms of the viewport when
|
|
51
|
+
* defined relatively to the measured bounding box.
|
|
52
|
+
*/
|
|
53
|
+
export function calcRelativeConstraints(layoutBox, { top, left, bottom, right }) {
|
|
54
|
+
return {
|
|
55
|
+
x: calcRelativeAxisConstraints(layoutBox.x, left, right),
|
|
56
|
+
y: calcRelativeAxisConstraints(layoutBox.y, top, bottom),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Calculate viewport constraints when defined as another viewport-relative axis
|
|
61
|
+
*/
|
|
62
|
+
export function calcViewportAxisConstraints(layoutAxis, constraintsAxis) {
|
|
63
|
+
let min = constraintsAxis.min - layoutAxis.min;
|
|
64
|
+
let max = constraintsAxis.max - layoutAxis.max;
|
|
65
|
+
// If the constraints axis is actually smaller than the layout axis then we can
|
|
66
|
+
// flip the constraints
|
|
67
|
+
if (constraintsAxis.max - constraintsAxis.min <
|
|
68
|
+
layoutAxis.max - layoutAxis.min) {
|
|
69
|
+
;
|
|
70
|
+
[min, max] = [max, min];
|
|
71
|
+
}
|
|
72
|
+
return { min, max };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Calculate viewport constraints when defined as another viewport-relative box
|
|
76
|
+
*/
|
|
77
|
+
export function calcViewportConstraints(layoutBox, constraintsBox) {
|
|
78
|
+
return {
|
|
79
|
+
x: calcViewportAxisConstraints(layoutBox.x, constraintsBox.x),
|
|
80
|
+
y: calcViewportAxisConstraints(layoutBox.y, constraintsBox.y),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Calculate a transform origin relative to the source axis, between 0-1, that results
|
|
85
|
+
* in an asthetically pleasing scale/transform needed to project from source to target.
|
|
86
|
+
*/
|
|
87
|
+
export function calcOrigin(source, target) {
|
|
88
|
+
let origin = 0.5;
|
|
89
|
+
const sourceLength = calcLength(source);
|
|
90
|
+
const targetLength = calcLength(target);
|
|
91
|
+
if (targetLength > sourceLength) {
|
|
92
|
+
origin = calcProgress(target.min, target.max - sourceLength, source.min);
|
|
93
|
+
}
|
|
94
|
+
else if (sourceLength > targetLength) {
|
|
95
|
+
origin = calcProgress(source.min, source.max - targetLength, target.min);
|
|
96
|
+
}
|
|
97
|
+
return clamp(0, 1, origin);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Calculate the relative progress of one constraints box relative to another.
|
|
101
|
+
* Imagine a page scroll bar. At the top, this would return 0, at the bottom, 1.
|
|
102
|
+
* Anywhere in-between, a value between 0 and 1.
|
|
103
|
+
*
|
|
104
|
+
* This also handles flipped constraints, for instance a draggable container within
|
|
105
|
+
* a smaller viewport like a scrollable view.
|
|
106
|
+
*/
|
|
107
|
+
export function calcProgressWithinConstraints(layoutBox, constraintsBox) {
|
|
108
|
+
return {
|
|
109
|
+
x: calcOrigin(layoutBox.x, constraintsBox.x),
|
|
110
|
+
y: calcOrigin(layoutBox.y, constraintsBox.y),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Calculate the an axis position based on two axes and a progress value.
|
|
115
|
+
*/
|
|
116
|
+
export function calcPositionFromProgress(axis, constraints, progress) {
|
|
117
|
+
const axisLength = axis.max - axis.min;
|
|
118
|
+
const min = mixNumber(constraints.min, constraints.max - axisLength, progress);
|
|
119
|
+
return { min, max: min + axisLength };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Rebase the calculated viewport constraints relative to the layout.min point.
|
|
123
|
+
*/
|
|
124
|
+
export function rebaseAxisConstraints(layout, constraints) {
|
|
125
|
+
const relativeConstraints = {};
|
|
126
|
+
if (constraints.min !== undefined) {
|
|
127
|
+
relativeConstraints.min = constraints.min - layout.min;
|
|
128
|
+
}
|
|
129
|
+
if (constraints.max !== undefined) {
|
|
130
|
+
relativeConstraints.max = constraints.max - layout.min;
|
|
131
|
+
}
|
|
132
|
+
return relativeConstraints;
|
|
133
|
+
}
|
|
134
|
+
export const defaultElastic = 0.35;
|
|
135
|
+
/**
|
|
136
|
+
* Accepts a dragElastic prop and returns resolved elastic values for each axis.
|
|
137
|
+
*/
|
|
138
|
+
export function resolveDragElastic(dragElastic = defaultElastic) {
|
|
139
|
+
if (dragElastic === false) {
|
|
140
|
+
dragElastic = 0;
|
|
141
|
+
}
|
|
142
|
+
else if (dragElastic === true) {
|
|
143
|
+
dragElastic = defaultElastic;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
x: resolveAxisElastic(dragElastic, "left", "right"),
|
|
147
|
+
y: resolveAxisElastic(dragElastic, "top", "bottom"),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export function resolveAxisElastic(dragElastic, minLabel, maxLabel) {
|
|
151
|
+
return {
|
|
152
|
+
min: resolvePointElastic(dragElastic, minLabel),
|
|
153
|
+
max: resolvePointElastic(dragElastic, maxLabel),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function resolvePointElastic(dragElastic, label) {
|
|
157
|
+
return typeof dragElastic === "number"
|
|
158
|
+
? dragElastic
|
|
159
|
+
: dragElastic[label] || 0;
|
|
160
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Feature, addDomEvent } from "motion-dom";
|
|
2
|
+
import { pipe } from "motion-utils";
|
|
3
|
+
export class FocusGesture extends Feature {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(...arguments);
|
|
6
|
+
this.isActive = false;
|
|
7
|
+
}
|
|
8
|
+
onFocus() {
|
|
9
|
+
let isFocusVisible = false;
|
|
10
|
+
/**
|
|
11
|
+
* If this element doesn't match focus-visible then don't
|
|
12
|
+
* apply whileHover. But, if matches throws that focus-visible
|
|
13
|
+
* is not a valid selector then in that browser outline styles will be applied
|
|
14
|
+
* to the element by default and we want to match that behaviour with whileFocus.
|
|
15
|
+
*/
|
|
16
|
+
try {
|
|
17
|
+
isFocusVisible = this.node.current.matches(":focus-visible");
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
isFocusVisible = true;
|
|
21
|
+
}
|
|
22
|
+
if (!isFocusVisible || !this.node.animationState)
|
|
23
|
+
return;
|
|
24
|
+
this.node.animationState.setActive("whileFocus", true);
|
|
25
|
+
this.isActive = true;
|
|
26
|
+
}
|
|
27
|
+
onBlur() {
|
|
28
|
+
if (!this.isActive || !this.node.animationState)
|
|
29
|
+
return;
|
|
30
|
+
this.node.animationState.setActive("whileFocus", false);
|
|
31
|
+
this.isActive = false;
|
|
32
|
+
}
|
|
33
|
+
mount() {
|
|
34
|
+
this.unmount = pipe(addDomEvent(this.node.current, "focus", () => this.onFocus()), addDomEvent(this.node.current, "blur", () => this.onBlur()));
|
|
35
|
+
}
|
|
36
|
+
unmount() { }
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Feature, frame, hover } from "motion-dom";
|
|
2
|
+
import { extractEventInfo } from "../events/event-info";
|
|
3
|
+
function handleHoverEvent(node, event, lifecycle) {
|
|
4
|
+
const { props } = node;
|
|
5
|
+
if (node.animationState && props.whileHover) {
|
|
6
|
+
node.animationState.setActive("whileHover", lifecycle === "Start");
|
|
7
|
+
}
|
|
8
|
+
const eventName = ("onHover" + lifecycle);
|
|
9
|
+
const callback = props[eventName];
|
|
10
|
+
if (callback) {
|
|
11
|
+
frame.postRender(() => callback(event, extractEventInfo(event)));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class HoverGesture extends Feature {
|
|
15
|
+
mount() {
|
|
16
|
+
const { current } = this.node;
|
|
17
|
+
if (!current)
|
|
18
|
+
return;
|
|
19
|
+
this.unmount = hover(current, (_element, startEvent) => {
|
|
20
|
+
handleHoverEvent(this.node, startEvent, "Start");
|
|
21
|
+
return (endEvent) => handleHoverEvent(this.node, endEvent, "End");
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
unmount() { }
|
|
25
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { cancelFrame, frame, frameData, isPrimaryPointer } from "motion-dom";
|
|
2
|
+
import { millisecondsToSeconds, pipe, secondsToMilliseconds, } from "motion-utils";
|
|
3
|
+
import { addPointerEvent } from "../../events/add-pointer-event";
|
|
4
|
+
import { extractEventInfo } from "../../events/event-info";
|
|
5
|
+
import { distance2D } from "../../utils/distance";
|
|
6
|
+
const overflowStyles = /*#__PURE__*/ new Set(["auto", "scroll"]);
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export class PanSession {
|
|
11
|
+
constructor(event, handlers, { transformPagePoint, contextWindow = window, dragSnapToOrigin = false, distanceThreshold = 3, element, } = {}) {
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
this.history = [];
|
|
16
|
+
/**
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
this.startEvent = null;
|
|
20
|
+
/**
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
this.lastMoveEvent = null;
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
this.lastMoveEventInfo = null;
|
|
28
|
+
/**
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
this.handlers = {};
|
|
32
|
+
/**
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
this.removeListeners = () => { };
|
|
36
|
+
/**
|
|
37
|
+
* For determining if an animation should resume after it is interupted
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
this.dragSnapToOrigin = false;
|
|
42
|
+
/**
|
|
43
|
+
* The distance after which panning should start.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
this.distanceThreshold = 3;
|
|
48
|
+
/**
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
this.contextWindow = window;
|
|
52
|
+
/**
|
|
53
|
+
* Scroll positions of scrollable ancestors and window.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
this.scrollPositions = new Map();
|
|
57
|
+
/**
|
|
58
|
+
* Cleanup function for scroll listeners.
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
this.removeScrollListeners = null;
|
|
62
|
+
this.onElementScroll = (event) => {
|
|
63
|
+
this.handleScroll(event.target);
|
|
64
|
+
};
|
|
65
|
+
this.onWindowScroll = () => {
|
|
66
|
+
this.handleScroll(window);
|
|
67
|
+
};
|
|
68
|
+
this.updatePoint = () => {
|
|
69
|
+
if (!(this.lastMoveEvent && this.lastMoveEventInfo))
|
|
70
|
+
return;
|
|
71
|
+
const info = getPanInfo(this.lastMoveEventInfo, this.history);
|
|
72
|
+
const isPanStarted = this.startEvent !== null;
|
|
73
|
+
// Only start panning if the offset is larger than 3 pixels. If we make it
|
|
74
|
+
// any larger than this we'll want to reset the pointer history
|
|
75
|
+
// on the first update to avoid visual snapping to the cursor.
|
|
76
|
+
const isDistancePastThreshold = distance2D(info.offset, { x: 0, y: 0 }) >= this.distanceThreshold;
|
|
77
|
+
if (!isPanStarted && !isDistancePastThreshold)
|
|
78
|
+
return;
|
|
79
|
+
const { point } = info;
|
|
80
|
+
const { timestamp } = frameData;
|
|
81
|
+
this.history.push({ ...point, timestamp });
|
|
82
|
+
const { onStart, onMove } = this.handlers;
|
|
83
|
+
if (!isPanStarted) {
|
|
84
|
+
onStart && onStart(this.lastMoveEvent, info);
|
|
85
|
+
this.startEvent = this.lastMoveEvent;
|
|
86
|
+
}
|
|
87
|
+
onMove && onMove(this.lastMoveEvent, info);
|
|
88
|
+
};
|
|
89
|
+
this.handlePointerMove = (event, info) => {
|
|
90
|
+
this.lastMoveEvent = event;
|
|
91
|
+
this.lastMoveEventInfo = transformPoint(info, this.transformPagePoint);
|
|
92
|
+
// Throttle mouse move event to once per frame
|
|
93
|
+
frame.update(this.updatePoint, true);
|
|
94
|
+
};
|
|
95
|
+
this.handlePointerUp = (event, info) => {
|
|
96
|
+
this.end();
|
|
97
|
+
const { onEnd, onSessionEnd, resumeAnimation } = this.handlers;
|
|
98
|
+
// Resume animation if dragSnapToOrigin is set OR if no drag started (user just clicked)
|
|
99
|
+
// This ensures constraint animations continue when interrupted by a click
|
|
100
|
+
if (this.dragSnapToOrigin || !this.startEvent) {
|
|
101
|
+
resumeAnimation && resumeAnimation();
|
|
102
|
+
}
|
|
103
|
+
if (!(this.lastMoveEvent && this.lastMoveEventInfo))
|
|
104
|
+
return;
|
|
105
|
+
const panInfo = getPanInfo(event.type === "pointercancel"
|
|
106
|
+
? this.lastMoveEventInfo
|
|
107
|
+
: transformPoint(info, this.transformPagePoint), this.history);
|
|
108
|
+
if (this.startEvent && onEnd) {
|
|
109
|
+
onEnd(event, panInfo);
|
|
110
|
+
}
|
|
111
|
+
onSessionEnd && onSessionEnd(event, panInfo);
|
|
112
|
+
};
|
|
113
|
+
// If we have more than one touch, don't start detecting this gesture
|
|
114
|
+
if (!isPrimaryPointer(event))
|
|
115
|
+
return;
|
|
116
|
+
this.dragSnapToOrigin = dragSnapToOrigin;
|
|
117
|
+
this.handlers = handlers;
|
|
118
|
+
this.transformPagePoint = transformPagePoint;
|
|
119
|
+
this.distanceThreshold = distanceThreshold;
|
|
120
|
+
this.contextWindow = contextWindow || window;
|
|
121
|
+
const info = extractEventInfo(event);
|
|
122
|
+
const initialInfo = transformPoint(info, this.transformPagePoint);
|
|
123
|
+
const { point } = initialInfo;
|
|
124
|
+
const { timestamp } = frameData;
|
|
125
|
+
this.history = [{ ...point, timestamp }];
|
|
126
|
+
const { onSessionStart } = handlers;
|
|
127
|
+
onSessionStart &&
|
|
128
|
+
onSessionStart(event, getPanInfo(initialInfo, this.history));
|
|
129
|
+
this.removeListeners = pipe(addPointerEvent(this.contextWindow, "pointermove", this.handlePointerMove), addPointerEvent(this.contextWindow, "pointerup", this.handlePointerUp), addPointerEvent(this.contextWindow, "pointercancel", this.handlePointerUp));
|
|
130
|
+
// Start scroll tracking if element provided
|
|
131
|
+
if (element) {
|
|
132
|
+
this.startScrollTracking(element);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Start tracking scroll on ancestors and window.
|
|
137
|
+
*/
|
|
138
|
+
startScrollTracking(element) {
|
|
139
|
+
// Store initial scroll positions for scrollable ancestors
|
|
140
|
+
let current = element.parentElement;
|
|
141
|
+
while (current) {
|
|
142
|
+
const style = getComputedStyle(current);
|
|
143
|
+
if (overflowStyles.has(style.overflowX) ||
|
|
144
|
+
overflowStyles.has(style.overflowY)) {
|
|
145
|
+
this.scrollPositions.set(current, {
|
|
146
|
+
x: current.scrollLeft,
|
|
147
|
+
y: current.scrollTop,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
current = current.parentElement;
|
|
151
|
+
}
|
|
152
|
+
// Track window scroll
|
|
153
|
+
this.scrollPositions.set(window, {
|
|
154
|
+
x: window.scrollX,
|
|
155
|
+
y: window.scrollY,
|
|
156
|
+
});
|
|
157
|
+
// Capture listener catches element scroll events as they bubble
|
|
158
|
+
window.addEventListener("scroll", this.onElementScroll, {
|
|
159
|
+
capture: true,
|
|
160
|
+
passive: true,
|
|
161
|
+
});
|
|
162
|
+
// Direct window scroll listener (window scroll doesn't bubble)
|
|
163
|
+
window.addEventListener("scroll", this.onWindowScroll, {
|
|
164
|
+
passive: true,
|
|
165
|
+
});
|
|
166
|
+
this.removeScrollListeners = () => {
|
|
167
|
+
window.removeEventListener("scroll", this.onElementScroll, {
|
|
168
|
+
capture: true,
|
|
169
|
+
});
|
|
170
|
+
window.removeEventListener("scroll", this.onWindowScroll);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Handle scroll compensation during drag.
|
|
175
|
+
*
|
|
176
|
+
* For element scroll: adjusts history origin since pageX/pageY doesn't change.
|
|
177
|
+
* For window scroll: adjusts lastMoveEventInfo since pageX/pageY would change.
|
|
178
|
+
*/
|
|
179
|
+
handleScroll(target) {
|
|
180
|
+
const initial = this.scrollPositions.get(target);
|
|
181
|
+
if (!initial)
|
|
182
|
+
return;
|
|
183
|
+
const isWindow = target === window;
|
|
184
|
+
const current = isWindow
|
|
185
|
+
? { x: window.scrollX, y: window.scrollY }
|
|
186
|
+
: {
|
|
187
|
+
x: target.scrollLeft,
|
|
188
|
+
y: target.scrollTop,
|
|
189
|
+
};
|
|
190
|
+
const delta = { x: current.x - initial.x, y: current.y - initial.y };
|
|
191
|
+
if (delta.x === 0 && delta.y === 0)
|
|
192
|
+
return;
|
|
193
|
+
if (isWindow) {
|
|
194
|
+
// Window scroll: pageX/pageY changes, so update lastMoveEventInfo
|
|
195
|
+
if (this.lastMoveEventInfo) {
|
|
196
|
+
this.lastMoveEventInfo.point.x += delta.x;
|
|
197
|
+
this.lastMoveEventInfo.point.y += delta.y;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// Element scroll: pageX/pageY unchanged, so adjust history origin
|
|
202
|
+
if (this.history.length > 0) {
|
|
203
|
+
this.history[0].x -= delta.x;
|
|
204
|
+
this.history[0].y -= delta.y;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
this.scrollPositions.set(target, current);
|
|
208
|
+
frame.update(this.updatePoint, true);
|
|
209
|
+
}
|
|
210
|
+
updateHandlers(handlers) {
|
|
211
|
+
this.handlers = handlers;
|
|
212
|
+
}
|
|
213
|
+
end() {
|
|
214
|
+
this.removeListeners && this.removeListeners();
|
|
215
|
+
this.removeScrollListeners && this.removeScrollListeners();
|
|
216
|
+
this.scrollPositions.clear();
|
|
217
|
+
cancelFrame(this.updatePoint);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function transformPoint(info, transformPagePoint) {
|
|
221
|
+
return transformPagePoint ? { point: transformPagePoint(info.point) } : info;
|
|
222
|
+
}
|
|
223
|
+
function subtractPoint(a, b) {
|
|
224
|
+
return { x: a.x - b.x, y: a.y - b.y };
|
|
225
|
+
}
|
|
226
|
+
function getPanInfo({ point }, history) {
|
|
227
|
+
return {
|
|
228
|
+
point,
|
|
229
|
+
delta: subtractPoint(point, lastDevicePoint(history)),
|
|
230
|
+
offset: subtractPoint(point, startDevicePoint(history)),
|
|
231
|
+
velocity: getVelocity(history, 0.1),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function startDevicePoint(history) {
|
|
235
|
+
return history[0];
|
|
236
|
+
}
|
|
237
|
+
function lastDevicePoint(history) {
|
|
238
|
+
return history[history.length - 1];
|
|
239
|
+
}
|
|
240
|
+
function getVelocity(history, timeDelta) {
|
|
241
|
+
if (history.length < 2) {
|
|
242
|
+
return { x: 0, y: 0 };
|
|
243
|
+
}
|
|
244
|
+
let i = history.length - 1;
|
|
245
|
+
let timestampedPoint = null;
|
|
246
|
+
const lastPoint = lastDevicePoint(history);
|
|
247
|
+
while (i >= 0) {
|
|
248
|
+
timestampedPoint = history[i];
|
|
249
|
+
if (lastPoint.timestamp - timestampedPoint.timestamp >
|
|
250
|
+
secondsToMilliseconds(timeDelta)) {
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
i--;
|
|
254
|
+
}
|
|
255
|
+
if (!timestampedPoint) {
|
|
256
|
+
return { x: 0, y: 0 };
|
|
257
|
+
}
|
|
258
|
+
const time = millisecondsToSeconds(lastPoint.timestamp - timestampedPoint.timestamp);
|
|
259
|
+
if (time === 0) {
|
|
260
|
+
return { x: 0, y: 0 };
|
|
261
|
+
}
|
|
262
|
+
const currentVelocity = {
|
|
263
|
+
x: (lastPoint.x - timestampedPoint.x) / time,
|
|
264
|
+
y: (lastPoint.y - timestampedPoint.y) / time,
|
|
265
|
+
};
|
|
266
|
+
if (currentVelocity.x === Infinity) {
|
|
267
|
+
currentVelocity.x = 0;
|
|
268
|
+
}
|
|
269
|
+
if (currentVelocity.y === Infinity) {
|
|
270
|
+
currentVelocity.y = 0;
|
|
271
|
+
}
|
|
272
|
+
return currentVelocity;
|
|
273
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Feature, frame } from "motion-dom";
|
|
2
|
+
import { noop } from "motion-utils";
|
|
3
|
+
import { addPointerEvent } from "../../events/add-pointer-event";
|
|
4
|
+
import { getContextWindow } from "../../utils/get-context-window";
|
|
5
|
+
import { PanSession } from "./PanSession";
|
|
6
|
+
const asyncHandler = (handler) => (event, info) => {
|
|
7
|
+
if (handler) {
|
|
8
|
+
frame.postRender(() => handler(event, info));
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
export class PanGesture extends Feature {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(...arguments);
|
|
14
|
+
this.removePointerDownListener = noop;
|
|
15
|
+
}
|
|
16
|
+
onPointerDown(pointerDownEvent) {
|
|
17
|
+
this.session = new PanSession(pointerDownEvent, this.createPanHandlers(), {
|
|
18
|
+
transformPagePoint: this.node.getTransformPagePoint(),
|
|
19
|
+
contextWindow: getContextWindow(this.node),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
createPanHandlers() {
|
|
23
|
+
const { onPanSessionStart, onPanStart, onPan, onPanEnd } = this.node.getProps();
|
|
24
|
+
return {
|
|
25
|
+
onSessionStart: asyncHandler(onPanSessionStart),
|
|
26
|
+
onStart: asyncHandler(onPanStart),
|
|
27
|
+
onMove: onPan,
|
|
28
|
+
onEnd: (event, info) => {
|
|
29
|
+
delete this.session;
|
|
30
|
+
if (onPanEnd) {
|
|
31
|
+
frame.postRender(() => onPanEnd(event, info));
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
mount() {
|
|
37
|
+
this.removePointerDownListener = addPointerEvent(this.node.current, "pointerdown", (event) => this.onPointerDown(event));
|
|
38
|
+
}
|
|
39
|
+
update() {
|
|
40
|
+
this.session && this.session.updateHandlers(this.createPanHandlers());
|
|
41
|
+
}
|
|
42
|
+
unmount() {
|
|
43
|
+
this.removePointerDownListener();
|
|
44
|
+
this.session && this.session.end();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Feature, frame, press } from "motion-dom";
|
|
2
|
+
import { extractEventInfo } from "../events/event-info";
|
|
3
|
+
function handlePressEvent(node, event, lifecycle) {
|
|
4
|
+
const { props } = node;
|
|
5
|
+
if (node.current instanceof HTMLButtonElement && node.current.disabled) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (node.animationState && props.whileTap) {
|
|
9
|
+
node.animationState.setActive("whileTap", lifecycle === "Start");
|
|
10
|
+
}
|
|
11
|
+
const eventName = ("onTap" + (lifecycle === "End" ? "" : lifecycle));
|
|
12
|
+
const callback = props[eventName];
|
|
13
|
+
if (callback) {
|
|
14
|
+
frame.postRender(() => callback(event, extractEventInfo(event)));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class PressGesture extends Feature {
|
|
18
|
+
mount() {
|
|
19
|
+
const { current } = this.node;
|
|
20
|
+
if (!current)
|
|
21
|
+
return;
|
|
22
|
+
this.unmount = press(current, (_element, startEvent) => {
|
|
23
|
+
handlePressEvent(this.node, startEvent, "Start");
|
|
24
|
+
return (endEvent, { success }) => handlePressEvent(this.node, endEvent, success ? "End" : "Cancel");
|
|
25
|
+
}, { useGlobalTarget: this.node.props.globalTapTarget });
|
|
26
|
+
}
|
|
27
|
+
unmount() { }
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { motion, m, createMotionComponent } from "./motion";
|
|
2
|
+
export { MotionConfig } from "./components/MotionConfig";
|
|
3
|
+
export { AnimatePresence, usePresence, useIsPresent } from "./components/AnimatePresence";
|
|
4
|
+
export { useMotionValue } from "./value/use-motion-value";
|
|
5
|
+
export { useTransform } from "./value/use-transform";
|
|
6
|
+
export { useSpring } from "./value/use-spring";
|
|
7
|
+
export { useVelocity } from "./value/use-velocity";
|
|
8
|
+
export { useMotionValueEvent } from "./value/use-motion-value-event";
|
|
9
|
+
export { useReducedMotion } from "./utils/use-reduced-motion";
|
|
10
|
+
export { animate, createScopedAnimate } from "./animation/animate";
|
|
11
|
+
export * from "motion-dom";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Feature } from "motion-dom";
|
|
2
|
+
let id = 0;
|
|
3
|
+
export class ExitAnimationFeature extends Feature {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(...arguments);
|
|
6
|
+
this.id = id++;
|
|
7
|
+
}
|
|
8
|
+
update() {
|
|
9
|
+
if (!this.node.presenceContext)
|
|
10
|
+
return;
|
|
11
|
+
const { isPresent, onExitComplete } = this.node.presenceContext;
|
|
12
|
+
const { isPresent: prevIsPresent } = this.node.prevPresenceContext || {};
|
|
13
|
+
if (!this.node.animationState || isPresent === prevIsPresent) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const exitAnimation = this.node.animationState.setActive("exit", !isPresent);
|
|
17
|
+
if (onExitComplete && !isPresent) {
|
|
18
|
+
exitAnimation.then(() => {
|
|
19
|
+
onExitComplete(this.id);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
mount() {
|
|
24
|
+
const { register, onExitComplete } = this.node.presenceContext || {};
|
|
25
|
+
if (onExitComplete) {
|
|
26
|
+
onExitComplete(this.id);
|
|
27
|
+
}
|
|
28
|
+
if (register) {
|
|
29
|
+
this.unmount = register(this.id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
unmount() { }
|
|
33
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createAnimationState, Feature, isAnimationControls } from "motion-dom";
|
|
2
|
+
export class AnimationFeature extends Feature {
|
|
3
|
+
constructor(node) {
|
|
4
|
+
super(node);
|
|
5
|
+
node.animationState || (node.animationState = createAnimationState(node));
|
|
6
|
+
}
|
|
7
|
+
updateAnimationControlsSubscription() {
|
|
8
|
+
const { animate } = this.node.getProps();
|
|
9
|
+
if (isAnimationControls(animate)) {
|
|
10
|
+
this.unmountControls = animate.subscribe(this.node);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
mount() {
|
|
14
|
+
this.updateAnimationControlsSubscription();
|
|
15
|
+
}
|
|
16
|
+
update() {
|
|
17
|
+
const { animate } = this.node.getProps();
|
|
18
|
+
const { animate: prevAnimate } = this.node.prevProps || {};
|
|
19
|
+
if (animate !== prevAnimate) {
|
|
20
|
+
this.updateAnimationControlsSubscription();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
unmount() {
|
|
24
|
+
this.node.animationState?.reset();
|
|
25
|
+
this.unmountControls?.();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getFeatureDefinitions, setFeatureDefinitions } from "motion-dom";
|
|
2
|
+
const featureProps = {
|
|
3
|
+
animation: ["animate", "variants", "whileHover", "whileTap", "exit", "whileInView", "whileFocus", "whileDrag"],
|
|
4
|
+
exit: ["exit"],
|
|
5
|
+
drag: ["drag", "dragControls"],
|
|
6
|
+
focus: ["whileFocus"],
|
|
7
|
+
hover: ["whileHover", "onHoverStart", "onHoverEnd"],
|
|
8
|
+
tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
|
|
9
|
+
pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
|
|
10
|
+
inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
|
|
11
|
+
layout: ["layout", "layoutId"],
|
|
12
|
+
};
|
|
13
|
+
let isInitialized = false;
|
|
14
|
+
export function initFeatureDefinitions() {
|
|
15
|
+
if (isInitialized)
|
|
16
|
+
return;
|
|
17
|
+
const initialFeatureDefinitions = {};
|
|
18
|
+
for (const key in featureProps) {
|
|
19
|
+
initialFeatureDefinitions[key] = {
|
|
20
|
+
isEnabled: (props) => featureProps[key].some((name) => Boolean(props[name])),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
setFeatureDefinitions(initialFeatureDefinitions);
|
|
24
|
+
isInitialized = true;
|
|
25
|
+
}
|
|
26
|
+
export function getInitializedFeatureDefinitions() {
|
|
27
|
+
initFeatureDefinitions();
|
|
28
|
+
return getFeatureDefinitions();
|
|
29
|
+
}
|