motion 12.8.0 → 12.9.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.
@@ -30,9 +30,6 @@ if (process.env.NODE_ENV !== "production") {
30
30
 
31
31
  const MotionGlobalConfig = {};
32
32
 
33
- /*#__NO_SIDE_EFFECTS__*/
34
- const noop = (any) => any;
35
-
36
33
  const LayoutGroupContext = react.createContext({});
37
34
 
38
35
  const LazyContext = react.createContext({ strict: false });
@@ -352,8 +349,6 @@ function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
352
349
  return { schedule, cancel, state, steps };
353
350
  }
354
351
 
355
- const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
356
-
357
352
  const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
358
353
  const isCSSVariableName =
359
354
  /*@__PURE__*/ checkStringStartsWith("--");
@@ -1111,25 +1106,10 @@ function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true
1111
1106
  attrs[keys.array] = `${pathLength} ${pathSpacing}`;
1112
1107
  }
1113
1108
 
1114
- function calcOrigin(origin, offset, size) {
1115
- return typeof origin === "string"
1116
- ? origin
1117
- : px.transform(offset + size * origin);
1118
- }
1119
- /**
1120
- * The SVG transform origin defaults are different to CSS and is less intuitive,
1121
- * so we use the measured dimensions of the SVG to reconcile these.
1122
- */
1123
- function calcSVGTransformOrigin(dimensions, originX, originY) {
1124
- const pxOriginX = calcOrigin(originX, dimensions.x, dimensions.width);
1125
- const pxOriginY = calcOrigin(originY, dimensions.y, dimensions.height);
1126
- return `${pxOriginX} ${pxOriginY}`;
1127
- }
1128
-
1129
1109
  /**
1130
1110
  * Build SVG visual attrbutes, like cx and style.transform
1131
1111
  */
1132
- function buildSVGAttrs(state, { attrX, attrY, attrScale, originX, originY, pathLength, pathSpacing = 1, pathOffset = 0,
1112
+ function buildSVGAttrs(state, { attrX, attrY, attrScale, pathLength, pathSpacing = 1, pathOffset = 0,
1133
1113
  // This is object creation, which we try to avoid per-frame.
1134
1114
  ...latest }, isSVGTag, transformTemplate) {
1135
1115
  buildHTMLStyles(state, latest, transformTemplate);
@@ -1145,20 +1125,26 @@ function buildSVGAttrs(state, { attrX, attrY, attrScale, originX, originY, pathL
1145
1125
  }
1146
1126
  state.attrs = state.style;
1147
1127
  state.style = {};
1148
- const { attrs, style, dimensions } = state;
1128
+ const { attrs, style } = state;
1149
1129
  /**
1150
- * However, we apply transforms as CSS transforms. So if we detect a transform we take it from attrs
1151
- * and copy it into style.
1130
+ * However, we apply transforms as CSS transforms.
1131
+ * So if we detect a transform, transformOrigin we take it from attrs and copy it into style.
1152
1132
  */
1153
1133
  if (attrs.transform) {
1154
- if (dimensions)
1155
- style.transform = attrs.transform;
1134
+ style.transform = attrs.transform;
1156
1135
  delete attrs.transform;
1157
1136
  }
1158
- // Parse transformOrigin
1159
- if (dimensions &&
1160
- (originX !== undefined || originY !== undefined || style.transform)) {
1161
- style.transformOrigin = calcSVGTransformOrigin(dimensions, originX !== undefined ? originX : 0.5, originY !== undefined ? originY : 0.5);
1137
+ if (style.transform || attrs.transformOrigin) {
1138
+ style.transformOrigin = attrs.transformOrigin ?? "50% 50%";
1139
+ delete attrs.transformOrigin;
1140
+ }
1141
+ if (style.transform) {
1142
+ /**
1143
+ * SVG's element transform-origin uses its own median as a reference.
1144
+ * Therefore, transformBox becomes a fill-box
1145
+ */
1146
+ style.transformBox = "fill-box";
1147
+ delete attrs.transformBox;
1162
1148
  }
1163
1149
  // Render attrX/attrY/attrScale as attributes
1164
1150
  if (attrX !== undefined)
@@ -1281,20 +1267,11 @@ function resolveMotionValue(value) {
1281
1267
  return isMotionValue(value) ? value.get() : value;
1282
1268
  }
1283
1269
 
1284
- function makeState({ scrapeMotionValuesFromProps, createRenderState, onUpdate, }, props, context, presenceContext) {
1270
+ function makeState({ scrapeMotionValuesFromProps, createRenderState, }, props, context, presenceContext) {
1285
1271
  const state = {
1286
1272
  latestValues: makeLatestValues(props, context, presenceContext, scrapeMotionValuesFromProps),
1287
1273
  renderState: createRenderState(),
1288
1274
  };
1289
- if (onUpdate) {
1290
- /**
1291
- * onMount works without the VisualElement because it could be
1292
- * called before the VisualElement payload has been hydrated.
1293
- * (e.g. if someone is using m components <m.circle />)
1294
- */
1295
- state.onMount = (instance) => onUpdate({ props, current: instance, ...state });
1296
- state.onUpdate = (visualElement) => onUpdate(visualElement);
1297
- }
1298
1275
  return state;
1299
1276
  }
1300
1277
  const makeUseVisualState = (config) => (props, isStatic) => {
@@ -1381,68 +1358,6 @@ const htmlMotionConfig = {
1381
1358
  }),
1382
1359
  };
1383
1360
 
1384
- function updateSVGDimensions(instance, renderState) {
1385
- try {
1386
- renderState.dimensions =
1387
- typeof instance.getBBox === "function"
1388
- ? instance.getBBox()
1389
- : instance.getBoundingClientRect();
1390
- }
1391
- catch (e) {
1392
- // Most likely trying to measure an unrendered element under Firefox
1393
- renderState.dimensions = {
1394
- x: 0,
1395
- y: 0,
1396
- width: 0,
1397
- height: 0,
1398
- };
1399
- }
1400
- }
1401
-
1402
- function renderHTML(element, { style, vars }, styleProp, projection) {
1403
- Object.assign(element.style, style, projection && projection.getProjectionStyles(styleProp));
1404
- // Loop over any CSS variables and assign those.
1405
- for (const key in vars) {
1406
- element.style.setProperty(key, vars[key]);
1407
- }
1408
- }
1409
-
1410
- /**
1411
- * A set of attribute names that are always read/written as camel case.
1412
- */
1413
- const camelCaseAttributes = new Set([
1414
- "baseFrequency",
1415
- "diffuseConstant",
1416
- "kernelMatrix",
1417
- "kernelUnitLength",
1418
- "keySplines",
1419
- "keyTimes",
1420
- "limitingConeAngle",
1421
- "markerHeight",
1422
- "markerWidth",
1423
- "numOctaves",
1424
- "targetX",
1425
- "targetY",
1426
- "surfaceScale",
1427
- "specularConstant",
1428
- "specularExponent",
1429
- "stdDeviation",
1430
- "tableValues",
1431
- "viewBox",
1432
- "gradientTransform",
1433
- "pathLength",
1434
- "startOffset",
1435
- "textLength",
1436
- "lengthAdjust",
1437
- ]);
1438
-
1439
- function renderSVG(element, renderState, _styleProp, projection) {
1440
- renderHTML(element, renderState, undefined, projection);
1441
- for (const key in renderState.attrs) {
1442
- element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]);
1443
- }
1444
- }
1445
-
1446
1361
  function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
1447
1362
  const newValues = scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
1448
1363
  for (const key in props) {
@@ -1457,49 +1372,10 @@ function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
1457
1372
  return newValues;
1458
1373
  }
1459
1374
 
1460
- const layoutProps = ["x", "y", "width", "height", "cx", "cy", "r"];
1461
1375
  const svgMotionConfig = {
1462
1376
  useVisualState: makeUseVisualState({
1463
1377
  scrapeMotionValuesFromProps: scrapeMotionValuesFromProps,
1464
1378
  createRenderState: createSvgRenderState,
1465
- onUpdate: ({ props, prevProps, current, renderState, latestValues, }) => {
1466
- if (!current)
1467
- return;
1468
- let hasTransform = !!props.drag;
1469
- if (!hasTransform) {
1470
- for (const key in latestValues) {
1471
- if (transformProps.has(key)) {
1472
- hasTransform = true;
1473
- break;
1474
- }
1475
- }
1476
- }
1477
- if (!hasTransform)
1478
- return;
1479
- let needsMeasure = !prevProps;
1480
- if (prevProps) {
1481
- /**
1482
- * Check the layout props for changes, if any are found we need to
1483
- * measure the element again.
1484
- */
1485
- for (let i = 0; i < layoutProps.length; i++) {
1486
- const key = layoutProps[i];
1487
- if (props[key] !==
1488
- prevProps[key]) {
1489
- needsMeasure = true;
1490
- }
1491
- }
1492
- }
1493
- if (!needsMeasure)
1494
- return;
1495
- frame.read(() => {
1496
- updateSVGDimensions(current, renderState);
1497
- frame.render(() => {
1498
- buildSVGAttrs(renderState, latestValues, isSVGTag(current.tagName), props.transformTemplate);
1499
- renderSVG(current, renderState);
1500
- });
1501
- });
1502
- },
1503
1379
  }),
1504
1380
  };
1505
1381
 
@@ -231,7 +231,7 @@ class NativeAnimation extends WithPromise {
231
231
  this.isStopped = false;
232
232
  if (!options)
233
233
  return;
234
- const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, } = options;
234
+ const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, onComplete, } = options;
235
235
  this.isPseudoElement = Boolean(pseudoElement);
236
236
  this.allowFlatten = allowFlatten;
237
237
  this.options = options;
@@ -257,8 +257,13 @@ class NativeAnimation extends WithPromise {
257
257
  }
258
258
  this.animation.cancel();
259
259
  }
260
+ onComplete?.();
260
261
  this.notifyFinished();
261
262
  };
263
+ /**
264
+ * TODO: In a breaking change, we should replace this with `.notifyCancel()`
265
+ */
266
+ this.animation.oncancel = () => this.notifyFinished();
262
267
  }
263
268
  play() {
264
269
  if (this.isStopped)
@@ -37,6 +37,17 @@ function animateTarget(visualElement, targetAndTransition, { delay = 0, transiti
37
37
  delay,
38
38
  ...getValueTransition(transition || {}, key),
39
39
  };
40
+ /**
41
+ * If the value is already at the defined target, skip the animation.
42
+ */
43
+ const currentValue = value.get();
44
+ if (currentValue !== undefined &&
45
+ !value.isAnimating &&
46
+ !Array.isArray(valueTarget) &&
47
+ valueTarget === currentValue &&
48
+ !valueTransition.velocity) {
49
+ continue;
50
+ }
40
51
  /**
41
52
  * If this is the first time a value is being animated, check
42
53
  * to see if we're handling off from an existing animation.
@@ -7,20 +7,11 @@ import { resolveVariantFromProps } from '../../render/utils/resolve-variants.mjs
7
7
  import { useConstant } from '../../utils/use-constant.mjs';
8
8
  import { resolveMotionValue } from '../../value/utils/resolve-motion-value.mjs';
9
9
 
10
- function makeState({ scrapeMotionValuesFromProps, createRenderState, onUpdate, }, props, context, presenceContext) {
10
+ function makeState({ scrapeMotionValuesFromProps, createRenderState, }, props, context, presenceContext) {
11
11
  const state = {
12
12
  latestValues: makeLatestValues(props, context, presenceContext, scrapeMotionValuesFromProps),
13
13
  renderState: createRenderState(),
14
14
  };
15
- if (onUpdate) {
16
- /**
17
- * onMount works without the VisualElement because it could be
18
- * called before the VisualElement payload has been hydrated.
19
- * (e.g. if someone is using m components <m.circle />)
20
- */
21
- state.onMount = (instance) => onUpdate({ props, current: instance, ...state });
22
- state.onUpdate = (visualElement) => onUpdate(visualElement);
23
- }
24
15
  return state;
25
16
  }
26
17
  const makeUseVisualState = (config) => (props, isStatic) => {
@@ -114,8 +114,7 @@ class VisualElement {
114
114
  frame.render(this.render, false, true);
115
115
  }
116
116
  };
117
- const { latestValues, renderState, onUpdate } = visualState;
118
- this.onUpdate = onUpdate;
117
+ const { latestValues, renderState } = visualState;
119
118
  this.latestValues = latestValues;
120
119
  this.baseTarget = { ...latestValues };
121
120
  this.initialValues = props.initial ? { ...latestValues } : {};
@@ -317,7 +316,6 @@ class VisualElement {
317
316
  if (this.handleChildMotionValue) {
318
317
  this.handleChildMotionValue();
319
318
  }
320
- this.onUpdate && this.onUpdate(this);
321
319
  }
322
320
  getProps() {
323
321
  return this.props;
@@ -4,12 +4,10 @@ import { camelToDash } from '../dom/utils/camel-to-dash.mjs';
4
4
  import { buildSVGAttrs } from './utils/build-attrs.mjs';
5
5
  import { camelCaseAttributes } from './utils/camel-case-attrs.mjs';
6
6
  import { isSVGTag } from './utils/is-svg-tag.mjs';
7
- import { updateSVGDimensions } from './utils/measure.mjs';
8
7
  import { renderSVG } from './utils/render.mjs';
9
8
  import { scrapeMotionValuesFromProps } from './utils/scrape-motion-values.mjs';
10
9
  import { transformProps } from '../../../../../motion-dom/dist/es/render/utils/keys-transform.mjs';
11
10
  import { getDefaultValueType } from '../../../../../motion-dom/dist/es/value/types/maps/defaults.mjs';
12
- import { frame } from '../../../../../motion-dom/dist/es/frameloop/frame.mjs';
13
11
 
14
12
  class SVGVisualElement extends DOMVisualElement {
15
13
  constructor() {
@@ -17,11 +15,6 @@ class SVGVisualElement extends DOMVisualElement {
17
15
  this.type = "svg";
18
16
  this.isSVGTag = false;
19
17
  this.measureInstanceViewportBox = createBox;
20
- this.updateDimensions = () => {
21
- if (this.current && !this.renderState.dimensions) {
22
- updateSVGDimensions(this.current, this.renderState);
23
- }
24
- };
25
18
  }
26
19
  getBaseTargetFromProps(props, key) {
27
20
  return props[key];
@@ -37,11 +30,6 @@ class SVGVisualElement extends DOMVisualElement {
37
30
  scrapeMotionValuesFromProps(props, prevProps, visualElement) {
38
31
  return scrapeMotionValuesFromProps(props, prevProps, visualElement);
39
32
  }
40
- onBindTransform() {
41
- if (this.current && !this.renderState.dimensions) {
42
- frame.postRender(this.updateDimensions);
43
- }
44
- }
45
33
  build(renderState, latestValues, props) {
46
34
  buildSVGAttrs(renderState, latestValues, this.isSVGTag, props.transformTemplate);
47
35
  }
@@ -1,56 +1,11 @@
1
1
  import { makeUseVisualState } from '../../motion/utils/use-visual-state.mjs';
2
- import { buildSVGAttrs } from './utils/build-attrs.mjs';
3
2
  import { createSvgRenderState } from './utils/create-render-state.mjs';
4
- import { isSVGTag } from './utils/is-svg-tag.mjs';
5
- import { updateSVGDimensions } from './utils/measure.mjs';
6
- import { renderSVG } from './utils/render.mjs';
7
3
  import { scrapeMotionValuesFromProps } from './utils/scrape-motion-values.mjs';
8
- import { transformProps } from '../../../../../motion-dom/dist/es/render/utils/keys-transform.mjs';
9
- import { frame } from '../../../../../motion-dom/dist/es/frameloop/frame.mjs';
10
4
 
11
- const layoutProps = ["x", "y", "width", "height", "cx", "cy", "r"];
12
5
  const svgMotionConfig = {
13
6
  useVisualState: makeUseVisualState({
14
7
  scrapeMotionValuesFromProps: scrapeMotionValuesFromProps,
15
8
  createRenderState: createSvgRenderState,
16
- onUpdate: ({ props, prevProps, current, renderState, latestValues, }) => {
17
- if (!current)
18
- return;
19
- let hasTransform = !!props.drag;
20
- if (!hasTransform) {
21
- for (const key in latestValues) {
22
- if (transformProps.has(key)) {
23
- hasTransform = true;
24
- break;
25
- }
26
- }
27
- }
28
- if (!hasTransform)
29
- return;
30
- let needsMeasure = !prevProps;
31
- if (prevProps) {
32
- /**
33
- * Check the layout props for changes, if any are found we need to
34
- * measure the element again.
35
- */
36
- for (let i = 0; i < layoutProps.length; i++) {
37
- const key = layoutProps[i];
38
- if (props[key] !==
39
- prevProps[key]) {
40
- needsMeasure = true;
41
- }
42
- }
43
- }
44
- if (!needsMeasure)
45
- return;
46
- frame.read(() => {
47
- updateSVGDimensions(current, renderState);
48
- frame.render(() => {
49
- buildSVGAttrs(renderState, latestValues, isSVGTag(current.tagName), props.transformTemplate);
50
- renderSVG(current, renderState);
51
- });
52
- });
53
- },
54
9
  }),
55
10
  };
56
11
 
@@ -1,11 +1,10 @@
1
1
  import { buildHTMLStyles } from '../../html/utils/build-styles.mjs';
2
2
  import { buildSVGPath } from './path.mjs';
3
- import { calcSVGTransformOrigin } from './transform-origin.mjs';
4
3
 
5
4
  /**
6
5
  * Build SVG visual attrbutes, like cx and style.transform
7
6
  */
8
- function buildSVGAttrs(state, { attrX, attrY, attrScale, originX, originY, pathLength, pathSpacing = 1, pathOffset = 0,
7
+ function buildSVGAttrs(state, { attrX, attrY, attrScale, pathLength, pathSpacing = 1, pathOffset = 0,
9
8
  // This is object creation, which we try to avoid per-frame.
10
9
  ...latest }, isSVGTag, transformTemplate) {
11
10
  buildHTMLStyles(state, latest, transformTemplate);
@@ -21,20 +20,26 @@ function buildSVGAttrs(state, { attrX, attrY, attrScale, originX, originY, pathL
21
20
  }
22
21
  state.attrs = state.style;
23
22
  state.style = {};
24
- const { attrs, style, dimensions } = state;
23
+ const { attrs, style } = state;
25
24
  /**
26
- * However, we apply transforms as CSS transforms. So if we detect a transform we take it from attrs
27
- * and copy it into style.
25
+ * However, we apply transforms as CSS transforms.
26
+ * So if we detect a transform, transformOrigin we take it from attrs and copy it into style.
28
27
  */
29
28
  if (attrs.transform) {
30
- if (dimensions)
31
- style.transform = attrs.transform;
29
+ style.transform = attrs.transform;
32
30
  delete attrs.transform;
33
31
  }
34
- // Parse transformOrigin
35
- if (dimensions &&
36
- (originX !== undefined || originY !== undefined || style.transform)) {
37
- style.transformOrigin = calcSVGTransformOrigin(dimensions, originX !== undefined ? originX : 0.5, originY !== undefined ? originY : 0.5);
32
+ if (style.transform || attrs.transformOrigin) {
33
+ style.transformOrigin = attrs.transformOrigin ?? "50% 50%";
34
+ delete attrs.transformOrigin;
35
+ }
36
+ if (style.transform) {
37
+ /**
38
+ * SVG's element transform-origin uses its own median as a reference.
39
+ * Therefore, transformBox becomes a fill-box
40
+ */
41
+ style.transformBox = "fill-box";
42
+ delete attrs.transformBox;
38
43
  }
39
44
  // Render attrX/attrY/attrScale as attributes
40
45
  if (attrX !== undefined)
@@ -17,7 +17,7 @@ function updateMotionValuesFromProps(element, next, prev) {
17
17
  * and warn against mismatches.
18
18
  */
19
19
  if (process.env.NODE_ENV === "development") {
20
- warnOnce(nextValue.version === "12.8.0", `Attempting to mix Motion versions ${nextValue.version} with 12.8.0 may not work as expected.`);
20
+ warnOnce(nextValue.version === "12.9.0", `Attempting to mix Motion versions ${nextValue.version} with 12.9.0 may not work as expected.`);
21
21
  }
22
22
  }
23
23
  else if (isMotionValue(prevValue)) {
@@ -39,6 +39,7 @@ export { supportsPartialKeyframes } from '../../motion-dom/dist/es/animation/waa
39
39
  export { supportsBrowserAnimation } from '../../motion-dom/dist/es/animation/waapi/supports/waapi.mjs';
40
40
  export { acceleratedValues } from '../../motion-dom/dist/es/animation/waapi/utils/accelerated-values.mjs';
41
41
  export { generateLinearEasing } from '../../motion-dom/dist/es/animation/waapi/utils/linear.mjs';
42
+ export { styleEffect } from '../../motion-dom/dist/es/effects/style-effect.mjs';
42
43
  export { createRenderBatcher } from '../../motion-dom/dist/es/frameloop/batcher.mjs';
43
44
  export { cancelMicrotask, microtask } from '../../motion-dom/dist/es/frameloop/microtask.mjs';
44
45
  export { time } from '../../motion-dom/dist/es/frameloop/sync-time.mjs';
@@ -138,6 +138,7 @@ export { supportsPartialKeyframes } from '../../motion-dom/dist/es/animation/waa
138
138
  export { supportsBrowserAnimation } from '../../motion-dom/dist/es/animation/waapi/supports/waapi.mjs';
139
139
  export { acceleratedValues } from '../../motion-dom/dist/es/animation/waapi/utils/accelerated-values.mjs';
140
140
  export { generateLinearEasing } from '../../motion-dom/dist/es/animation/waapi/utils/linear.mjs';
141
+ export { styleEffect } from '../../motion-dom/dist/es/effects/style-effect.mjs';
141
142
  export { createRenderBatcher } from '../../motion-dom/dist/es/frameloop/batcher.mjs';
142
143
  export { cancelMicrotask, microtask } from '../../motion-dom/dist/es/frameloop/microtask.mjs';
143
144
  export { time } from '../../motion-dom/dist/es/frameloop/sync-time.mjs';
@@ -52,7 +52,7 @@ class AsyncMotionValueAnimation extends WithPromise {
52
52
  }
53
53
  onKeyframesResolved(keyframes, finalKeyframe, options, sync) {
54
54
  this.keyframeResolver = undefined;
55
- const { name, type, velocity, delay, isHandoff, onUpdate, onComplete } = options;
55
+ const { name, type, velocity, delay, isHandoff, onUpdate } = options;
56
56
  this.resolvedAt = time.now();
57
57
  /**
58
58
  * If we can't animate this value with the resolved keyframes
@@ -102,12 +102,7 @@ class AsyncMotionValueAnimation extends WithPromise {
102
102
  element: resolvedOptions.motionValue.owner.current,
103
103
  })
104
104
  : new JSAnimation(resolvedOptions);
105
- animation.finished
106
- .then(() => {
107
- onComplete?.();
108
- this.notifyFinished();
109
- })
110
- .catch(noop);
105
+ animation.finished.then(() => this.notifyFinished()).catch(noop);
111
106
  if (this.pendingTimeline) {
112
107
  this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
113
108
  this.pendingTimeline = undefined;
@@ -302,7 +302,6 @@ class JSAnimation extends WithPromise {
302
302
  this.holdTime = null;
303
303
  }
304
304
  finish() {
305
- this.notifyFinished();
306
305
  this.teardown();
307
306
  this.state = "finished";
308
307
  const { onComplete } = this.options;
@@ -315,6 +314,7 @@ class JSAnimation extends WithPromise {
315
314
  this.teardown();
316
315
  }
317
316
  teardown() {
317
+ this.notifyFinished();
318
318
  this.state = "idle";
319
319
  this.stopDriver();
320
320
  this.startTime = this.holdTime = null;
@@ -18,7 +18,7 @@ class NativeAnimation extends WithPromise {
18
18
  this.isStopped = false;
19
19
  if (!options)
20
20
  return;
21
- const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, } = options;
21
+ const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, onComplete, } = options;
22
22
  this.isPseudoElement = Boolean(pseudoElement);
23
23
  this.allowFlatten = allowFlatten;
24
24
  this.options = options;
@@ -44,8 +44,13 @@ class NativeAnimation extends WithPromise {
44
44
  }
45
45
  this.animation.cancel();
46
46
  }
47
+ onComplete?.();
47
48
  this.notifyFinished();
48
49
  };
50
+ /**
51
+ * TODO: In a breaking change, we should replace this with `.notifyCancel()`
52
+ */
53
+ this.animation.oncancel = () => this.notifyFinished();
49
54
  }
50
55
  play() {
51
56
  if (this.isStopped)
@@ -0,0 +1,36 @@
1
+ import { resolveElements } from '../utils/resolve-elements.mjs';
2
+ import { frame, cancelFrame } from '../frameloop/frame.mjs';
3
+
4
+ function styleEffect(subject, values) {
5
+ const elements = resolveElements(subject);
6
+ const subscriptions = [];
7
+ for (let i = 0; i < elements.length; i++) {
8
+ const element = elements[i];
9
+ for (const key in values) {
10
+ const value = values[key];
11
+ /**
12
+ * TODO: Get specific setters for combined props (like x)
13
+ * or values with default types (like color)
14
+ *
15
+ * TODO: CSS variable support
16
+ */
17
+ const updateStyle = () => {
18
+ element.style[key] = value.get();
19
+ };
20
+ const scheduleUpdate = () => frame.render(updateStyle);
21
+ const cancel = value.on("change", scheduleUpdate);
22
+ scheduleUpdate();
23
+ subscriptions.push(() => {
24
+ cancel();
25
+ cancelFrame(updateStyle);
26
+ });
27
+ }
28
+ }
29
+ return () => {
30
+ for (const cancel of subscriptions) {
31
+ cancel();
32
+ }
33
+ };
34
+ }
35
+
36
+ export { styleEffect };
@@ -32,7 +32,7 @@ class MotionValue {
32
32
  * This will be replaced by the build step with the latest version number.
33
33
  * When MotionValues are provided to motion components, warn if versions are mixed.
34
34
  */
35
- this.version = "12.8.0";
35
+ this.version = "12.9.0";
36
36
  /**
37
37
  * Tracks whether this value can output a velocity. Currently this is only true
38
38
  * if the value is numerical, but we might be able to widen the scope here and support
@@ -58,12 +58,12 @@ class MotionValue {
58
58
  this.prev = this.current;
59
59
  this.setCurrent(v);
60
60
  // Update update subscribers
61
- if (this.current !== this.prev && this.events.change) {
62
- this.events.change.notify(this.current);
61
+ if (this.current !== this.prev) {
62
+ this.events.change?.notify(this.current);
63
63
  }
64
64
  // Update render subscribers
65
- if (render && this.events.renderRequest) {
66
- this.events.renderRequest.notify(this.current);
65
+ if (render) {
66
+ this.events.renderRequest?.notify(this.current);
67
67
  }
68
68
  };
69
69
  this.hasAnimated = false;
@@ -176,8 +176,6 @@ class MotionValue {
176
176
  * @public
177
177
  */
178
178
  set(v, render = true) {
179
- if (v === "none")
180
- console.trace();
181
179
  if (!render || !this.passiveEffect) {
182
180
  this.updateAndNotify(v, render);
183
181
  }