@kitlangton/solid-motion 0.1.0 → 0.2.1
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/README.md +4 -3
- package/dist/gestures/drag/VisualElementDragControls.d.ts +4 -2
- package/dist/gestures/drag/utils/constraints.d.ts +1 -1
- package/dist/gestures/pan/PanSession.d.ts +13 -2
- package/dist/gestures/pan/index.d.ts +1 -1
- package/dist/index.js +118 -59
- package/dist/index.js.map +1 -1
- package/dist/server.js +118 -59
- package/dist/server.js.map +1 -1
- package/dist/source/gestures/drag/VisualElementDragControls.js +102 -57
- package/dist/source/gestures/drag/utils/constraints.js +1 -1
- package/dist/source/gestures/pan/PanSession.js +43 -13
- package/dist/source/gestures/pan/index.js +2 -2
- package/dist/source/gestures/press.js +5 -1
- package/dist/source/motion/index.jsx +15 -3
- package/dist/source/value/use-spring.js +1 -1
- package/package.json +3 -3
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { addValueToWillChange, animateMotionValue, calcLength, convertBoundingBoxToBox, convertBoxToBoundingBox, createBox, eachAxis, frame, cancelFrame, measurePageBox, mixNumber, percent, setDragLock, } from "motion-dom";
|
|
1
|
+
import { addValueToWillChange, animateMotionValue, calcLength, convertBoundingBoxToBox, convertBoxToBoundingBox, createBox, eachAxis, frame, isElementTextInput, cancelFrame, measurePageBox, mixNumber, percent, resize, setDragLock, } from "motion-dom";
|
|
2
2
|
import { invariant } from "motion-utils";
|
|
3
3
|
import { addDomEvent } from "motion-dom";
|
|
4
4
|
import { addPointerEvent } from "../../events/add-pointer-event";
|
|
5
|
-
import { extractEventInfo } from "../../events/event-info";
|
|
6
5
|
import { getContextWindow } from "../../utils/get-context-window";
|
|
7
6
|
import { isRefObject } from "../../utils/is-ref-object";
|
|
8
7
|
import { PanSession } from "../pan/PanSession";
|
|
@@ -41,21 +40,11 @@ export class VisualElementDragControls {
|
|
|
41
40
|
if (presenceContext && presenceContext.isPresent === false)
|
|
42
41
|
return;
|
|
43
42
|
const onSessionStart = (event) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (snapToCursor) {
|
|
48
|
-
this.stopAnimation();
|
|
49
|
-
this.snapToCursor(extractEventInfo(event).point);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
this.pauseAnimation();
|
|
53
|
-
}
|
|
43
|
+
if (snapToCursor)
|
|
44
|
+
this.snapToCursor(event);
|
|
45
|
+
this.stopAnimation();
|
|
54
46
|
};
|
|
55
47
|
const onStart = (event, info) => {
|
|
56
|
-
// Stop any paused animation so motion values reflect true current position
|
|
57
|
-
// (pauseAnimation was called in onSessionStart to allow resume if no drag started)
|
|
58
|
-
this.stopAnimation();
|
|
59
48
|
// Attempt to grab the global drag gesture lock - maybe make this part of PanSession
|
|
60
49
|
const { drag, dragPropagation, onDragStart } = this.getProps();
|
|
61
50
|
if (drag && !dragPropagation) {
|
|
@@ -97,7 +86,7 @@ export class VisualElementDragControls {
|
|
|
97
86
|
});
|
|
98
87
|
// Fire onDragStart event
|
|
99
88
|
if (onDragStart) {
|
|
100
|
-
frame.
|
|
89
|
+
frame.update(() => onDragStart(event, info), false, true);
|
|
101
90
|
}
|
|
102
91
|
addValueToWillChange(this.visualElement, "transform");
|
|
103
92
|
const { animationState } = this.visualElement;
|
|
@@ -134,7 +123,9 @@ export class VisualElementDragControls {
|
|
|
134
123
|
* This must fire after the render call as it might trigger a state
|
|
135
124
|
* change which itself might trigger a layout update.
|
|
136
125
|
*/
|
|
137
|
-
|
|
126
|
+
if (onDrag) {
|
|
127
|
+
frame.update(() => onDrag(event, info), false, true);
|
|
128
|
+
}
|
|
138
129
|
};
|
|
139
130
|
const onSessionEnd = (event, info) => {
|
|
140
131
|
this.latestPointerEvent = event;
|
|
@@ -143,8 +134,12 @@ export class VisualElementDragControls {
|
|
|
143
134
|
this.latestPointerEvent = null;
|
|
144
135
|
this.latestPanInfo = null;
|
|
145
136
|
};
|
|
146
|
-
const resumeAnimation = () =>
|
|
147
|
-
this.
|
|
137
|
+
const resumeAnimation = () => {
|
|
138
|
+
const { dragSnapToOrigin: snap } = this.getProps();
|
|
139
|
+
if (snap || this.constraints) {
|
|
140
|
+
this.startAnimation({ x: 0, y: 0 });
|
|
141
|
+
}
|
|
142
|
+
};
|
|
148
143
|
const { dragSnapToOrigin } = this.getProps();
|
|
149
144
|
this.panSession = new PanSession(originEvent, {
|
|
150
145
|
onSessionStart,
|
|
@@ -246,9 +241,12 @@ export class VisualElementDragControls {
|
|
|
246
241
|
this.elastic = resolveDragElastic(dragElastic);
|
|
247
242
|
/**
|
|
248
243
|
* If we're outputting to external MotionValues, we want to rebase the measured constraints
|
|
249
|
-
* from viewport-relative to component-relative.
|
|
244
|
+
* from viewport-relative to component-relative. This only applies to relative (non-ref)
|
|
245
|
+
* constraints, as ref-based constraints from calcViewportConstraints are already in the
|
|
246
|
+
* correct coordinate space for the motion value transform offset.
|
|
250
247
|
*/
|
|
251
248
|
if (prevConstraints !== this.constraints &&
|
|
249
|
+
!isRefObject(dragConstraints) &&
|
|
252
250
|
layout &&
|
|
253
251
|
this.constraints &&
|
|
254
252
|
!this.hasMutatedConstraints) {
|
|
@@ -270,6 +268,20 @@ export class VisualElementDragControls {
|
|
|
270
268
|
// TODO
|
|
271
269
|
if (!projection || !projection.layout)
|
|
272
270
|
return false;
|
|
271
|
+
/**
|
|
272
|
+
* Refresh the root scroll offset so the constraint's viewport box
|
|
273
|
+
* translates to correct page coordinates. The scroll captured at
|
|
274
|
+
* drag mount can be stale if the document was scrolled afterwards —
|
|
275
|
+
* e.g. via the browser restoring scroll on refresh, or an ancestor
|
|
276
|
+
* layout effect running after this element's mount (#2829).
|
|
277
|
+
*
|
|
278
|
+
* Clear the cached scroll first so `updateScroll` bypasses its
|
|
279
|
+
* per-animationId cache and re-reads the live value.
|
|
280
|
+
*/
|
|
281
|
+
if (projection.root) {
|
|
282
|
+
projection.root.scroll = undefined;
|
|
283
|
+
projection.root.updateScroll();
|
|
284
|
+
}
|
|
273
285
|
const constraintsBox = measurePageBox(constraintsElement, projection.root, this.visualElement.getTransformPagePoint());
|
|
274
286
|
let measuredConstraints = calcViewportConstraints(projection.layout.layoutBox, constraintsBox);
|
|
275
287
|
/**
|
|
@@ -293,7 +305,8 @@ export class VisualElementDragControls {
|
|
|
293
305
|
return;
|
|
294
306
|
}
|
|
295
307
|
let transition = (constraints && constraints[axis]) || {};
|
|
296
|
-
if (dragSnapToOrigin
|
|
308
|
+
if (dragSnapToOrigin === true ||
|
|
309
|
+
dragSnapToOrigin === axis)
|
|
297
310
|
transition = { min: 0, max: 0 };
|
|
298
311
|
/**
|
|
299
312
|
* Overdamp the boundary spring if `dragElastic` is disabled. There's still a frame
|
|
@@ -330,12 +343,6 @@ export class VisualElementDragControls {
|
|
|
330
343
|
stopAnimation() {
|
|
331
344
|
eachAxis((axis) => this.getAxisMotionValue(axis).stop());
|
|
332
345
|
}
|
|
333
|
-
pauseAnimation() {
|
|
334
|
-
eachAxis((axis) => this.getAxisMotionValue(axis).animation?.pause());
|
|
335
|
-
}
|
|
336
|
-
getAnimationState(axis) {
|
|
337
|
-
return this.getAxisMotionValue(axis).animation?.state;
|
|
338
|
-
}
|
|
339
346
|
/**
|
|
340
347
|
* Drag works differently depending on which props are provided.
|
|
341
348
|
*
|
|
@@ -348,29 +355,23 @@ export class VisualElementDragControls {
|
|
|
348
355
|
const externalMotionValue = props[dragKey];
|
|
349
356
|
return externalMotionValue
|
|
350
357
|
? externalMotionValue
|
|
351
|
-
: this.visualElement.getValue(axis,
|
|
352
|
-
? props.initial[axis]
|
|
353
|
-
: undefined) || 0);
|
|
358
|
+
: this.visualElement.getValue(axis, this.visualElement.latestValues[axis] ?? 0);
|
|
354
359
|
}
|
|
355
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Measure the live box - projection.layout may or may not include the
|
|
362
|
+
* drag transform, depending on when it was measured.
|
|
363
|
+
*/
|
|
364
|
+
snapToCursor({ clientX, clientY }) {
|
|
365
|
+
const { drag } = this.getProps();
|
|
366
|
+
const point = { x: clientX, y: clientY };
|
|
367
|
+
const cursor = this.visualElement.getTransformPagePoint()?.(point) || point;
|
|
368
|
+
const box = this.visualElement.measureViewportBox();
|
|
356
369
|
eachAxis((axis) => {
|
|
357
|
-
const { drag } = this.getProps();
|
|
358
|
-
// If we're not dragging this axis, do an early return.
|
|
359
370
|
if (!shouldDrag(axis, drag, this.currentDirection))
|
|
360
371
|
return;
|
|
361
|
-
const { projection } = this.visualElement;
|
|
362
372
|
const axisValue = this.getAxisMotionValue(axis);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* The layout measurement includes the current transform value,
|
|
367
|
-
* so we need to add it back to get the correct snap position.
|
|
368
|
-
* This fixes an issue where elements with initial coordinates
|
|
369
|
-
* would snap to the wrong position on the first drag.
|
|
370
|
-
*/
|
|
371
|
-
const current = axisValue.get() || 0;
|
|
372
|
-
axisValue.set(point[axis] - mixNumber(min, max, 0.5) + current);
|
|
373
|
-
}
|
|
373
|
+
const { min, max } = box[axis];
|
|
374
|
+
axisValue.set((axisValue.get() || 0) + cursor[axis] - mixNumber(min, max, 0.5));
|
|
374
375
|
});
|
|
375
376
|
}
|
|
376
377
|
/**
|
|
@@ -394,13 +395,11 @@ export class VisualElementDragControls {
|
|
|
394
395
|
* Record the relative position of the dragged element relative to the
|
|
395
396
|
* constraints box and save as a progress value.
|
|
396
397
|
*/
|
|
398
|
+
const constraints = this.constraints;
|
|
397
399
|
const boxProgress = { x: 0, y: 0 };
|
|
398
400
|
eachAxis((axis) => {
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
const latest = axisValue.get();
|
|
402
|
-
boxProgress[axis] = calcOrigin({ min: latest, max: latest }, this.constraints[axis]);
|
|
403
|
-
}
|
|
401
|
+
const latest = this.getAxisMotionValue(axis).get();
|
|
402
|
+
boxProgress[axis] = calcOrigin({ min: latest, max: latest }, constraints[axis]);
|
|
404
403
|
});
|
|
405
404
|
/**
|
|
406
405
|
* Update the layout of this element and resolve the latest drag constraints
|
|
@@ -411,21 +410,36 @@ export class VisualElementDragControls {
|
|
|
411
410
|
: "none";
|
|
412
411
|
projection.root && projection.root.updateScroll();
|
|
413
412
|
projection.updateLayout();
|
|
413
|
+
/**
|
|
414
|
+
* Reset constraints so resolveConstraints() will recalculate them
|
|
415
|
+
* with the freshly measured layout rather than returning the cached value.
|
|
416
|
+
*/
|
|
417
|
+
this.constraints = false;
|
|
414
418
|
this.resolveConstraints();
|
|
415
419
|
/**
|
|
416
420
|
* For each axis, calculate the current progress of the layout axis
|
|
417
421
|
* within the new constraints.
|
|
418
422
|
*/
|
|
419
423
|
eachAxis((axis) => {
|
|
420
|
-
|
|
424
|
+
const axisValue = this.getAxisMotionValue(axis);
|
|
425
|
+
/**
|
|
426
|
+
* An element resting at its origin has no offset to preserve,
|
|
427
|
+
* and re-projecting 0 into the new constraints would move it.
|
|
428
|
+
*/
|
|
429
|
+
if (!shouldDrag(axis, drag, null) || !axisValue.get())
|
|
421
430
|
return;
|
|
422
431
|
/**
|
|
423
432
|
* Calculate a new transform based on the previous box progress
|
|
424
433
|
*/
|
|
425
|
-
const axisValue = this.getAxisMotionValue(axis);
|
|
426
434
|
const { min, max } = this.constraints[axis];
|
|
427
435
|
axisValue.set(mixNumber(min, max, boxProgress[axis]));
|
|
428
436
|
});
|
|
437
|
+
/**
|
|
438
|
+
* Flush the updated transform to the DOM synchronously to prevent
|
|
439
|
+
* a visual flash at the element's CSS layout position (0,0) when
|
|
440
|
+
* the transform was stripped for measurement.
|
|
441
|
+
*/
|
|
442
|
+
this.visualElement.render();
|
|
429
443
|
}
|
|
430
444
|
addListeners() {
|
|
431
445
|
if (!this.visualElement.current)
|
|
@@ -439,20 +453,32 @@ export class VisualElementDragControls {
|
|
|
439
453
|
const { drag, dragListener = true } = this.getProps();
|
|
440
454
|
const target = event.target;
|
|
441
455
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
456
|
+
* Only block drag if clicking on a text input child element
|
|
457
|
+
* (input, textarea, select, contenteditable) where users might
|
|
458
|
+
* want to select text or interact with the control.
|
|
459
|
+
*
|
|
460
|
+
* Buttons and links don't block drag since they don't have
|
|
461
|
+
* click-and-move actions of their own.
|
|
444
462
|
*/
|
|
445
|
-
const isClickingTextInputChild = target !== element &&
|
|
446
|
-
(Boolean(target.closest("input, textarea, select")) ||
|
|
447
|
-
(target instanceof HTMLElement && target.isContentEditable));
|
|
463
|
+
const isClickingTextInputChild = target !== element && isElementTextInput(target);
|
|
448
464
|
if (drag && dragListener && !isClickingTextInputChild) {
|
|
449
465
|
this.start(event);
|
|
450
466
|
}
|
|
451
467
|
});
|
|
468
|
+
/**
|
|
469
|
+
* If using ref-based constraints, observe both the draggable element
|
|
470
|
+
* and the constraint container for size changes via ResizeObserver.
|
|
471
|
+
* Setup is deferred because dragConstraints.current is null when
|
|
472
|
+
* addListeners first runs (React hasn't committed the ref yet).
|
|
473
|
+
*/
|
|
474
|
+
let stopResizeObservers;
|
|
452
475
|
const measureDragConstraints = () => {
|
|
453
476
|
const { dragConstraints } = this.getProps();
|
|
454
477
|
if (isRefObject(dragConstraints) && dragConstraints.current) {
|
|
455
478
|
this.constraints = this.resolveRefConstraints();
|
|
479
|
+
if (!stopResizeObservers) {
|
|
480
|
+
stopResizeObservers = startResizeObservers(element, dragConstraints.current, () => this.scalePositionWithinConstraints());
|
|
481
|
+
}
|
|
456
482
|
}
|
|
457
483
|
};
|
|
458
484
|
const { projection } = this.visualElement;
|
|
@@ -489,6 +515,7 @@ export class VisualElementDragControls {
|
|
|
489
515
|
stopPointerListener();
|
|
490
516
|
stopMeasureLayoutListener();
|
|
491
517
|
stopLayoutUpdateListener && stopLayoutUpdateListener();
|
|
518
|
+
stopResizeObservers && stopResizeObservers();
|
|
492
519
|
};
|
|
493
520
|
}
|
|
494
521
|
getProps() {
|
|
@@ -505,6 +532,24 @@ export class VisualElementDragControls {
|
|
|
505
532
|
};
|
|
506
533
|
}
|
|
507
534
|
}
|
|
535
|
+
function skipFirstCall(callback) {
|
|
536
|
+
let isFirst = true;
|
|
537
|
+
return () => {
|
|
538
|
+
if (isFirst) {
|
|
539
|
+
isFirst = false;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
callback();
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function startResizeObservers(element, constraintsElement, onResize) {
|
|
546
|
+
const stopElement = resize(element, skipFirstCall(onResize));
|
|
547
|
+
const stopContainer = resize(constraintsElement, skipFirstCall(onResize));
|
|
548
|
+
return () => {
|
|
549
|
+
stopElement();
|
|
550
|
+
stopContainer();
|
|
551
|
+
};
|
|
552
|
+
}
|
|
508
553
|
function shouldDrag(direction, drag, currentDirection) {
|
|
509
554
|
return ((drag === true || drag === direction) &&
|
|
510
555
|
(currentDirection === null || currentDirection === direction));
|
|
@@ -82,7 +82,7 @@ export function calcViewportConstraints(layoutBox, constraintsBox) {
|
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
84
|
* Calculate a transform origin relative to the source axis, between 0-1, that results
|
|
85
|
-
* in an
|
|
85
|
+
* in an aesthetically pleasing scale/transform needed to project from source to target.
|
|
86
86
|
*/
|
|
87
87
|
export function calcOrigin(source, target) {
|
|
88
88
|
let origin = 0.5;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cancelFrame, frame, frameData, isPrimaryPointer } from "motion-dom";
|
|
1
|
+
import { cancelFrame, frame, frameData, isPrimaryPointer, time, } from "motion-dom";
|
|
2
2
|
import { millisecondsToSeconds, pipe, secondsToMilliseconds, } from "motion-utils";
|
|
3
3
|
import { addPointerEvent } from "../../events/add-pointer-event";
|
|
4
4
|
import { extractEventInfo } from "../../events/event-info";
|
|
@@ -25,6 +25,12 @@ export class PanSession {
|
|
|
25
25
|
* @internal
|
|
26
26
|
*/
|
|
27
27
|
this.lastMoveEventInfo = null;
|
|
28
|
+
/**
|
|
29
|
+
* Raw (untransformed) event info, re-transformed each frame
|
|
30
|
+
* so transformPagePoint sees the current parent matrix.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
this.lastRawMoveEventInfo = null;
|
|
28
34
|
/**
|
|
29
35
|
* @internal
|
|
30
36
|
*/
|
|
@@ -34,7 +40,7 @@ export class PanSession {
|
|
|
34
40
|
*/
|
|
35
41
|
this.removeListeners = () => { };
|
|
36
42
|
/**
|
|
37
|
-
* For determining if an animation should resume after it is
|
|
43
|
+
* For determining if an animation should resume after it is interrupted
|
|
38
44
|
*
|
|
39
45
|
* @internal
|
|
40
46
|
*/
|
|
@@ -68,6 +74,12 @@ export class PanSession {
|
|
|
68
74
|
this.updatePoint = () => {
|
|
69
75
|
if (!(this.lastMoveEvent && this.lastMoveEventInfo))
|
|
70
76
|
return;
|
|
77
|
+
this.hasPendingMove = false;
|
|
78
|
+
// Re-transform raw point through current transformPagePoint so
|
|
79
|
+
// animated parent transforms (e.g. rotation) are picked up each frame
|
|
80
|
+
if (this.lastRawMoveEventInfo) {
|
|
81
|
+
this.lastMoveEventInfo = transformPoint(this.lastRawMoveEventInfo, this.transformPagePoint);
|
|
82
|
+
}
|
|
71
83
|
const info = getPanInfo(this.lastMoveEventInfo, this.history);
|
|
72
84
|
const isPanStarted = this.startEvent !== null;
|
|
73
85
|
// Only start panning if the offset is larger than 3 pixels. If we make it
|
|
@@ -77,8 +89,7 @@ export class PanSession {
|
|
|
77
89
|
if (!isPanStarted && !isDistancePastThreshold)
|
|
78
90
|
return;
|
|
79
91
|
const { point } = info;
|
|
80
|
-
|
|
81
|
-
this.history.push({ ...point, timestamp });
|
|
92
|
+
this.history.push({ ...point, timestamp: time.now() });
|
|
82
93
|
const { onStart, onMove } = this.handlers;
|
|
83
94
|
if (!isPanStarted) {
|
|
84
95
|
onStart && onStart(this.lastMoveEvent, info);
|
|
@@ -88,11 +99,16 @@ export class PanSession {
|
|
|
88
99
|
};
|
|
89
100
|
this.handlePointerMove = (event, info) => {
|
|
90
101
|
this.lastMoveEvent = event;
|
|
102
|
+
this.lastRawMoveEventInfo = info;
|
|
91
103
|
this.lastMoveEventInfo = transformPoint(info, this.transformPagePoint);
|
|
104
|
+
this.hasPendingMove = true;
|
|
92
105
|
// Throttle mouse move event to once per frame
|
|
93
106
|
frame.update(this.updatePoint, true);
|
|
94
107
|
};
|
|
95
108
|
this.handlePointerUp = (event, info) => {
|
|
109
|
+
// Browsers flush a coalesced pointermove immediately before
|
|
110
|
+
// pointerup, so the final move can still be waiting for a frame.
|
|
111
|
+
this.hasPendingMove && this.updatePoint();
|
|
96
112
|
this.end();
|
|
97
113
|
const { onEnd, onSessionEnd, resumeAnimation } = this.handlers;
|
|
98
114
|
// Resume animation if dragSnapToOrigin is set OR if no drag started (user just clicked)
|
|
@@ -126,7 +142,11 @@ export class PanSession {
|
|
|
126
142
|
const { onSessionStart } = handlers;
|
|
127
143
|
onSessionStart &&
|
|
128
144
|
onSessionStart(event, getPanInfo(initialInfo, this.history));
|
|
129
|
-
|
|
145
|
+
// Listen in the capture phase so a descendant calling
|
|
146
|
+
// stopPropagation() (e.g. in its own pointerup handler) can't
|
|
147
|
+
// prevent the gesture from ending. See #2794.
|
|
148
|
+
const eventOptions = { passive: true, capture: true };
|
|
149
|
+
this.removeListeners = pipe(addPointerEvent(this.contextWindow, "pointermove", this.handlePointerMove, eventOptions), addPointerEvent(this.contextWindow, "pointerup", this.handlePointerUp, eventOptions), addPointerEvent(this.contextWindow, "pointercancel", this.handlePointerUp, eventOptions));
|
|
130
150
|
// Start scroll tracking if element provided
|
|
131
151
|
if (element) {
|
|
132
152
|
this.startScrollTracking(element);
|
|
@@ -157,12 +177,9 @@ export class PanSession {
|
|
|
157
177
|
// Capture listener catches element scroll events as they bubble
|
|
158
178
|
window.addEventListener("scroll", this.onElementScroll, {
|
|
159
179
|
capture: true,
|
|
160
|
-
passive: true,
|
|
161
180
|
});
|
|
162
181
|
// Direct window scroll listener (window scroll doesn't bubble)
|
|
163
|
-
window.addEventListener("scroll", this.onWindowScroll
|
|
164
|
-
passive: true,
|
|
165
|
-
});
|
|
182
|
+
window.addEventListener("scroll", this.onWindowScroll);
|
|
166
183
|
this.removeScrollListeners = () => {
|
|
167
184
|
window.removeEventListener("scroll", this.onElementScroll, {
|
|
168
185
|
capture: true,
|
|
@@ -255,13 +272,26 @@ function getVelocity(history, timeDelta) {
|
|
|
255
272
|
if (!timestampedPoint) {
|
|
256
273
|
return { x: 0, y: 0 };
|
|
257
274
|
}
|
|
258
|
-
|
|
259
|
-
|
|
275
|
+
/**
|
|
276
|
+
* If the selected point is the pointer-down origin (history[0]),
|
|
277
|
+
* there are better movement points available, and the time gap
|
|
278
|
+
* is suspiciously large (>2x timeDelta), use the next point instead.
|
|
279
|
+
* This prevents stale pointer-down points from diluting velocity
|
|
280
|
+
* in hold-then-flick gestures.
|
|
281
|
+
*/
|
|
282
|
+
if (timestampedPoint === history[0] &&
|
|
283
|
+
history.length > 2 &&
|
|
284
|
+
lastPoint.timestamp - timestampedPoint.timestamp >
|
|
285
|
+
secondsToMilliseconds(timeDelta) * 2) {
|
|
286
|
+
timestampedPoint = history[1];
|
|
287
|
+
}
|
|
288
|
+
const seconds = millisecondsToSeconds(lastPoint.timestamp - timestampedPoint.timestamp);
|
|
289
|
+
if (seconds === 0) {
|
|
260
290
|
return { x: 0, y: 0 };
|
|
261
291
|
}
|
|
262
292
|
const currentVelocity = {
|
|
263
|
-
x: (lastPoint.x - timestampedPoint.x) /
|
|
264
|
-
y: (lastPoint.y - timestampedPoint.y) /
|
|
293
|
+
x: (lastPoint.x - timestampedPoint.x) / seconds,
|
|
294
|
+
y: (lastPoint.y - timestampedPoint.y) / seconds,
|
|
265
295
|
};
|
|
266
296
|
if (currentVelocity.x === Infinity) {
|
|
267
297
|
currentVelocity.x = 0;
|
|
@@ -5,7 +5,7 @@ import { getContextWindow } from "../../utils/get-context-window";
|
|
|
5
5
|
import { PanSession } from "./PanSession";
|
|
6
6
|
const asyncHandler = (handler) => (event, info) => {
|
|
7
7
|
if (handler) {
|
|
8
|
-
frame.
|
|
8
|
+
frame.update(() => handler(event, info), false, true);
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
export class PanGesture extends Feature {
|
|
@@ -24,7 +24,7 @@ export class PanGesture extends Feature {
|
|
|
24
24
|
return {
|
|
25
25
|
onSessionStart: asyncHandler(onPanSessionStart),
|
|
26
26
|
onStart: asyncHandler(onPanStart),
|
|
27
|
-
onMove: onPan,
|
|
27
|
+
onMove: asyncHandler(onPan),
|
|
28
28
|
onEnd: (event, info) => {
|
|
29
29
|
delete this.session;
|
|
30
30
|
if (onPanEnd) {
|
|
@@ -19,10 +19,14 @@ export class PressGesture extends Feature {
|
|
|
19
19
|
const { current } = this.node;
|
|
20
20
|
if (!current)
|
|
21
21
|
return;
|
|
22
|
+
const { globalTapTarget, propagate } = this.node.props;
|
|
22
23
|
this.unmount = press(current, (_element, startEvent) => {
|
|
23
24
|
handlePressEvent(this.node, startEvent, "Start");
|
|
24
25
|
return (endEvent, { success }) => handlePressEvent(this.node, endEvent, success ? "End" : "Cancel");
|
|
25
|
-
}, {
|
|
26
|
+
}, {
|
|
27
|
+
useGlobalTarget: globalTapTarget,
|
|
28
|
+
stopPropagation: propagate?.tap === false,
|
|
29
|
+
});
|
|
26
30
|
}
|
|
27
31
|
unmount() { }
|
|
28
32
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createEffect, createMemo, onCleanup, onMount, splitProps, useContext } from "solid-js";
|
|
2
2
|
import { Dynamic } from "solid-js/web";
|
|
3
|
-
import { scrapeHTMLMotionValuesFromProps, scrapeSVGMotionValuesFromProps } from "motion-dom";
|
|
3
|
+
import { isAnimationControls, scrapeHTMLMotionValuesFromProps, scrapeSVGMotionValuesFromProps, setTarget } from "motion-dom";
|
|
4
4
|
import { MotionConfigContext } from "../components/MotionConfig";
|
|
5
5
|
import { MotionContext } from "../context/MotionContext";
|
|
6
6
|
import { PresenceContext } from "../context/PresenceContext";
|
|
@@ -102,6 +102,7 @@ export function createMotionComponent(Component, { forwardMotionProps = false, t
|
|
|
102
102
|
return;
|
|
103
103
|
visualElement.scheduleRenderMicrotask();
|
|
104
104
|
});
|
|
105
|
+
let mounted = false;
|
|
105
106
|
createEffect(() => {
|
|
106
107
|
if (!visualElement)
|
|
107
108
|
return;
|
|
@@ -110,9 +111,20 @@ export function createMotionComponent(Component, { forwardMotionProps = false, t
|
|
|
110
111
|
visualElement.update(nextProps, nextPresenceContext);
|
|
111
112
|
prepareDragProjection(visualElement, nextProps);
|
|
112
113
|
visualElement.updateFeatures();
|
|
113
|
-
if (visualElement.current
|
|
114
|
-
|
|
114
|
+
if (!visualElement.current || !visualElement.animationState)
|
|
115
|
+
return;
|
|
116
|
+
if (!mounted) {
|
|
117
|
+
mounted = true;
|
|
118
|
+
// The visual state was built when the element was created, but in Solid an earlier
|
|
119
|
+
// effect in the same flush can change `animate` before it mounts. React can't render a
|
|
120
|
+
// stale target, so a blocked initial animation would otherwise skip the newer one.
|
|
121
|
+
const { animate } = nextProps;
|
|
122
|
+
const blocked = nextProps.initial === false || nextPresenceContext?.initial === false;
|
|
123
|
+
if (blocked && animate && typeof animate !== "boolean" && !isAnimationControls(animate)) {
|
|
124
|
+
setTarget(visualElement, animate);
|
|
125
|
+
}
|
|
115
126
|
}
|
|
127
|
+
visualElement.animationState.animateChanges();
|
|
116
128
|
});
|
|
117
129
|
onCleanup(() => {
|
|
118
130
|
visualElement?.unmount();
|
|
@@ -20,7 +20,7 @@ export function useSpring(source, options = {}) {
|
|
|
20
20
|
cancelFrame(start);
|
|
21
21
|
value.stop();
|
|
22
22
|
};
|
|
23
|
-
// motion-dom
|
|
23
|
+
// motion-dom's springValue queues an anonymous callback that survives
|
|
24
24
|
// destroy(). Keep the scheduled work owned and cancellable by this Solid scope.
|
|
25
25
|
const start = () => {
|
|
26
26
|
if (disposed)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitlangton/solid-motion",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Motion for SolidJS: springs, presence, drag and gestures on Motion's engine.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kit Langton",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"prepublishOnly": "bun run typecheck && bun run build"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"motion-dom": "
|
|
50
|
-
"motion-utils": "
|
|
49
|
+
"motion-dom": "14.0.0",
|
|
50
|
+
"motion-utils": "14.0.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"solid-js": "^1.8.0"
|