solidjs-motion 0.1.1 → 0.1.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ All notable changes to `solidjs-motion` / `@solidjs-motion/motion` are documente
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.2] — 2026-05-20
9
+
10
+ ### Fixed
11
+
12
+ - **Drag now coexists with `initial` / `animate` / `exit`.** A draggable
13
+ element with an entrance animation (`<motion.X drag initial={{x:-300}}
14
+ animate={{x:0}}>`) used to break in two ways: animate's `x`/`y` never
15
+ reached the DOM, and on pointerdown the element snapped back to the
16
+ initial position. Three linked fixes:
17
+ - Drag's exclusion of `x`/`y` from the gesture-state winners is now
18
+ gated on `active.whileDrag` (pointer-engaged) instead of
19
+ `dragEnabled` (configured), matching motion-react. Animate's `x`/`y`
20
+ flow normally while drag is configured but idle; drag only claims
21
+ them during active interaction.
22
+ - The diff effect's removed-key fallback no longer reverts `x`/`y` to
23
+ `initial` when drag activates and excludes them. Drag *claiming* a
24
+ key is not the same as removing it.
25
+ - `handlePanStart` now syncs the x/y MotionValues to the element's
26
+ current visible translate (parsed from `getComputedStyle` +
27
+ `el.style.transform`) before capturing `dragStart`. motion's
28
+ `animate(el, target)` interpolates style.transform via WAAPI but
29
+ doesn't keep the visualElement's MVs in sync, so the MV would still
30
+ hold the entrance start value when drag began.
31
+
8
32
  ## [0.1.1] — 2026-05-20
9
33
 
10
34
  ### Docs
package/dist/index.js CHANGED
@@ -871,6 +871,91 @@ function ensureVisualElement(el) {
871
871
  return ve;
872
872
  }
873
873
  /**
874
+ * Parse the visible translate from an element's transform — reads both
875
+ * `getComputedStyle(el).transform` (which real browsers normalize to
876
+ * matrix form) AND `el.style.transform` (which preserves the raw syntax
877
+ * motion-dom's writer emits, e.g. `translate3d(50px, 0px, 0)`). Used by
878
+ * drag's pan-start to sync the x/y MotionValues to what the user is
879
+ * actually seeing.
880
+ *
881
+ * Why both sources: motion's `animate(el, target)` interpolates style.
882
+ * transform via WAAPI but DOESN'T update the VE's MVs during the tween,
883
+ * so after `initial: {x:-300} → animate: {x:0}` the MV still holds -300.
884
+ * Reading the current transform recovers the truth. We prefer computed
885
+ * (post-animation, post-WAAPI-commit value) and fall back to inline
886
+ * (covers jsdom + cases where motion's writer wrote inline but the
887
+ * browser hasn't run a style-resolve pass yet).
888
+ *
889
+ * Supported syntaxes:
890
+ * - `"none"` / empty → {0, 0}
891
+ * - `matrix(a, b, c, d, tx, ty)`
892
+ * - `matrix3d(..., tx, ty, ...)`
893
+ * - `translateX(Npx)` / `translateY(Npx)` / `translate(tx, ty)`
894
+ * - `translate3d(tx, ty, tz)`
895
+ * - Any of the above mixed with other transform functions (regex-
896
+ * based extraction picks just the translate components).
897
+ */
898
+ function readVisibleTranslate(el) {
899
+ const fromString = (transform) => {
900
+ if (!transform || transform === "none") return null;
901
+ if (transform.startsWith("matrix3d(")) {
902
+ const values = transform.slice(9, -1).split(",");
903
+ const tx = Number.parseFloat(values[12] ?? "0");
904
+ const ty = Number.parseFloat(values[13] ?? "0");
905
+ if (!Number.isFinite(tx) && !Number.isFinite(ty)) return null;
906
+ return {
907
+ x: Number.isFinite(tx) ? tx : 0,
908
+ y: Number.isFinite(ty) ? ty : 0
909
+ };
910
+ }
911
+ if (transform.startsWith("matrix(")) {
912
+ const values = transform.slice(7, -1).split(",");
913
+ const tx = Number.parseFloat(values[4] ?? "0");
914
+ const ty = Number.parseFloat(values[5] ?? "0");
915
+ if (!Number.isFinite(tx) && !Number.isFinite(ty)) return null;
916
+ return {
917
+ x: Number.isFinite(tx) ? tx : 0,
918
+ y: Number.isFinite(ty) ? ty : 0
919
+ };
920
+ }
921
+ let x = 0;
922
+ let y = 0;
923
+ let found = false;
924
+ const translate3d = transform.match(/translate3d\(\s*([-\d.]+)px\s*,\s*([-\d.]+)px/);
925
+ if (translate3d) {
926
+ x = Number.parseFloat(translate3d[1] ?? "0");
927
+ y = Number.parseFloat(translate3d[2] ?? "0");
928
+ found = true;
929
+ } else {
930
+ const translate2d = transform.match(/translate\(\s*([-\d.]+)px\s*(?:,\s*([-\d.]+)px)?/);
931
+ if (translate2d) {
932
+ x = Number.parseFloat(translate2d[1] ?? "0");
933
+ y = Number.parseFloat(translate2d[2] ?? "0");
934
+ found = true;
935
+ }
936
+ const translateX = transform.match(/translateX\(\s*([-\d.]+)px/);
937
+ if (translateX) {
938
+ x = Number.parseFloat(translateX[1] ?? "0");
939
+ found = true;
940
+ }
941
+ const translateY = transform.match(/translateY\(\s*([-\d.]+)px/);
942
+ if (translateY) {
943
+ y = Number.parseFloat(translateY[1] ?? "0");
944
+ found = true;
945
+ }
946
+ }
947
+ if (!found) return null;
948
+ return {
949
+ x: Number.isFinite(x) ? x : 0,
950
+ y: Number.isFinite(y) ? y : 0
951
+ };
952
+ };
953
+ return fromString(getComputedStyle(el).transform) ?? fromString(el.style.transform) ?? {
954
+ x: 0,
955
+ y: 0
956
+ };
957
+ }
958
+ /**
874
959
  * Compute the `touch-action` CSS value for an element being dragged.
875
960
  * Disabling touch-action prevents the browser from interpreting the gesture
876
961
  * as a scroll. Axis-locked drags leave the unused axis available for scroll
@@ -1008,14 +1093,27 @@ function createDrag(el, getOpts, setActive) {
1008
1093
  for (const ctrl of momentumControls) ctrl.stop();
1009
1094
  momentumControls = [];
1010
1095
  }
1011
- const handlePanStart = (event, info) => {
1096
+ const handlePanStart = (event, info, mvIsAuthoritative = false) => {
1012
1097
  if (!isDragEnabled()) return;
1013
1098
  stopMomentum();
1014
1099
  const ve = ensureVisualElement(el);
1015
1100
  xMV = ve.getValue("x", 0);
1016
1101
  yMV = ve.getValue("y", 0);
1017
- dragStartX = xMV.get();
1018
- dragStartY = yMV.get();
1102
+ const axis = getOpts().drag;
1103
+ if (mvIsAuthoritative) {
1104
+ dragStartX = xMV.get();
1105
+ dragStartY = yMV.get();
1106
+ } else {
1107
+ const visible = readVisibleTranslate(el);
1108
+ if (axis !== "y") {
1109
+ if (visible.x !== xMV.get()) xMV.set(visible.x);
1110
+ dragStartX = visible.x;
1111
+ } else dragStartX = xMV.get();
1112
+ if (axis !== "x") {
1113
+ if (visible.y !== yMV.get()) yMV.set(visible.y);
1114
+ dragStartY = visible.y;
1115
+ } else dragStartY = yMV.get();
1116
+ }
1019
1117
  sessionBounds = resolveConstraints(getOpts().dragConstraints, el, dragStartX, dragStartY);
1020
1118
  savedUserSelect = document.body.style.userSelect;
1021
1119
  savedTouchAction = el.style.touchAction;
@@ -1184,7 +1282,7 @@ function createDrag(el, getOpts, setActive) {
1184
1282
  x: 0,
1185
1283
  y: 0
1186
1284
  }
1187
- });
1285
+ }, Boolean(options.snapToCursor));
1188
1286
  const sessionStartPoint = {
1189
1287
  x: event.clientX,
1190
1288
  y: event.clientY
@@ -1493,7 +1591,7 @@ function createGestureStateMachine(deps) {
1493
1591
  });
1494
1592
  const winners = createMemo(() => {
1495
1593
  const targets = stateTargets();
1496
- const dragEnabled = Boolean(getOpts().drag);
1594
+ const dragActive = active.whileDrag;
1497
1595
  const out = {};
1498
1596
  for (const stateName of PRIORITY_HIGH_TO_LOW) {
1499
1597
  if (!isStateActive(stateName, active, parentVariantCtx)) continue;
@@ -1502,7 +1600,7 @@ function createGestureStateMachine(deps) {
1502
1600
  for (const key in target) {
1503
1601
  if (key === "transition") continue;
1504
1602
  if (key in out) continue;
1505
- if (!active.exit && dragEnabled && (key === "x" || key === "y")) continue;
1603
+ if (!active.exit && dragActive && (key === "x" || key === "y")) continue;
1506
1604
  out[key] = {
1507
1605
  value: target[key],
1508
1606
  transition: target.transition,
@@ -1545,6 +1643,7 @@ function createGestureStateMachine(deps) {
1545
1643
  }
1546
1644
  for (const key in lastApplied) {
1547
1645
  if (key in next) continue;
1646
+ if (active.whileDrag && (key === "x" || key === "y")) continue;
1548
1647
  const initialValue = initialTarget && key in initialTarget ? initialTarget[key] : void 0;
1549
1648
  changes[key] = initialValue !== void 0 ? initialValue : getMotionDefault(key);
1550
1649
  }