@octane-xplat/motion 0.9.0 → 0.11.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.
@@ -1,15 +1,23 @@
1
1
  import { createContext, defineUniversalComponent, hookSlots, universalChildren, universalComponent, universalContext, universalIf, universalPlan, universalProps, universalValue, useContext, useEffect, useLayoutEffect, useRef, useState, withSlot } from "@nativescript-community/octane";
2
- import { Pressable, Row, View } from "@octane-xplat/ui";
3
- import { Application, Utils } from "@nativescript/core";
2
+ import { HStack, Pressable, View } from "@octane-xplat/ui";
3
+ import { Application, Device, Utils } from "@nativescript/core";
4
4
  import { cancelAnimationFrame, requestAnimationFrame } from "@nativescript/core/animation-frame";
5
+ import { GestureHandlerStateEvent, GestureHandlerTouchEvent, GestureState, HandlerType, Manager } from "@nativescript-community/gesturehandler";
5
6
  //#region src/reduced-motion.ts
6
7
  function readReducedMotion() {
7
8
  const platform = globalThis;
8
9
  if (Application.ios) return Boolean(platform.UIAccessibilityIsReduceMotionEnabled?.());
10
+ if (!platform.android) return false;
11
+ if (Number(Device.sdkVersion) >= 26 && platform.android.animation?.ValueAnimator?.areAnimatorsEnabled?.() === false) return true;
9
12
  const context = Utils.android.getApplicationContext();
10
13
  return platform.android.provider.Settings.Global.getFloat(context.getContentResolver(), "animator_duration_scale", 1) === 0;
11
14
  }
12
15
  function observeReducedMotion(notify) {
16
+ if (Application.ios) {
17
+ const name = globalThis.UIAccessibilityReduceMotionStatusDidChangeNotification;
18
+ const observer = Application.ios.addNotificationObserver(name, notify);
19
+ return () => Application.ios.removeNotificationObserver(observer, name);
20
+ }
13
21
  let previous = readReducedMotion();
14
22
  const check = () => {
15
23
  const next = readReducedMotion();
@@ -165,12 +173,12 @@ var Presence = defineUniversalComponent("nativescript", function Presence(props)
165
173
  const [retained, setRetained] = useState(props.present, _h$0$2);
166
174
  const latest = useRef(props, _h$1$2);
167
175
  latest.current = props;
168
- const ref = useRef(null, _h$2$2);
169
- if (!ref.current) ref.current = new PresenceState(props.present, () => {
176
+ const stateRef = useRef(null, _h$2$2);
177
+ if (!stateRef.current) stateRef.current = new PresenceState(props.present, () => {
170
178
  setRetained(false);
171
179
  latest.current.onExitComplete?.();
172
180
  });
173
- const state = ref.current;
181
+ const state = stateRef.current;
174
182
  const host = useRef(null, _h$3$2);
175
183
  const restoreInput = useRef(null, _h$4$2);
176
184
  const syncInput = () => {
@@ -191,59 +199,208 @@ var Presence = defineUniversalComponent("nativescript", function Presence(props)
191
199
  restoreInput.current = null;
192
200
  host.current = null;
193
201
  }, [], _h$6$2);
194
- const { present, onExitComplete, children, bind, ...viewProps } = props;
202
+ const { present, onExitComplete, children, ref, ...viewProps } = props;
195
203
  return universalValue(__octaneUniversalPlan2$1, [universalIf(props.present || retained, () => {
196
204
  return universalValue(__octaneUniversalPlan1$1, [universalComponent("nativescript", View, universalProps([["spread", viewProps], [
197
205
  "set",
198
- "bind",
199
- (node) => {
206
+ "ref",
207
+ [(node) => {
200
208
  if (host.current !== node) {
201
209
  restoreInput.current?.();
202
210
  restoreInput.current = null;
203
211
  host.current = node;
204
212
  }
205
213
  syncInput();
206
- bind?.(node);
207
- }
214
+ }, ref]
208
215
  ]], universalChildren("nativescript", () => {
209
216
  return universalValue(__octaneUniversalPlan0$1, [((__octaneContextProps) => universalContext(PresenceContext, __octaneContextProps.value, children))({ "value": state })]);
210
217
  })))]);
211
218
  })]);
212
219
  }, { "module": "@nativescript-community/octane" });
213
220
  //#endregion
221
+ //#region src/drag.ts
222
+ function validateDrag(props) {
223
+ if (props.drag !== void 0 && ![
224
+ true,
225
+ false,
226
+ "x",
227
+ "y"
228
+ ].includes(props.drag)) throw new Error("motion: drag must be boolean, x, or y");
229
+ const bounds = props.dragConstraints ?? {};
230
+ for (const [key, value] of Object.entries(bounds)) if (![
231
+ "left",
232
+ "right",
233
+ "top",
234
+ "bottom"
235
+ ].includes(key) || !Number.isFinite(value)) throw new Error("motion: dragConstraints requires finite numeric edges; refs are deferred");
236
+ if ((bounds.left ?? -Infinity) > (bounds.right ?? Infinity) || (bounds.top ?? -Infinity) > (bounds.bottom ?? Infinity)) throw new Error("motion: dragConstraints edges are reversed");
237
+ if (typeof props.dragElastic !== "boolean" && props.dragElastic !== void 0 && (!Number.isFinite(props.dragElastic) || props.dragElastic < 0 || props.dragElastic > 1)) throw new Error("motion: dragElastic must be boolean or a number from 0 to 1");
238
+ if (props.drag) for (const axis of ["x", "y"]) {
239
+ if (props.drag !== true && props.drag !== axis) continue;
240
+ for (const target of [
241
+ props.animate,
242
+ props.whileTap,
243
+ props.whileFocus
244
+ ]) if (target && typeof target === "object" && !Array.isArray(target) && target[axis] !== void 0) throw new Error(`motion: drag and animation both own ${axis}`);
245
+ }
246
+ }
247
+ var clamp$1 = (value, min, max) => Math.min(max, Math.max(min, value));
248
+ /** Gesture writes and release springs bypass host delegation. */
249
+ function attachDrag(adapter, controller, props, valueFor, reduced, callbacks) {
250
+ if (!props.drag) return () => {};
251
+ if (!adapter.pan) throw new Error("motion: this host does not support drag");
252
+ const axes = ["x", "y"].filter((axis) => props.drag === true || props.drag === axis);
253
+ const values = new Map(axes.map((axis) => [axis, valueFor(axis)]));
254
+ const value = (axis) => values.get(axis);
255
+ const bounds = props.dragConstraints ?? {};
256
+ const elastic = typeof props.dragElastic === "number" ? props.dragElastic : props.dragElastic === false ? 0 : .35;
257
+ const origin = {
258
+ x: 0,
259
+ y: 0
260
+ };
261
+ let active = false;
262
+ const limits = (axis) => axis === "x" ? [bounds.left ?? -Infinity, bounds.right ?? Infinity] : [bounds.top ?? -Infinity, bounds.bottom ?? Infinity];
263
+ const move = (info) => {
264
+ for (const axis of axes) {
265
+ const [min, max] = limits(axis);
266
+ const next = origin[axis] + info.offset[axis];
267
+ const bounded = clamp$1(next, min, max);
268
+ value(axis).jump(bounded + (next - bounded) * elastic);
269
+ }
270
+ };
271
+ const off = adapter.pan(props.drag, {
272
+ start(event, info) {
273
+ controller.stop();
274
+ active = true;
275
+ for (const axis of axes) {
276
+ const channel = value(axis);
277
+ channel.stop();
278
+ origin[axis] = channel.get();
279
+ }
280
+ callbacks().onDragStart?.(event, info);
281
+ },
282
+ move(event, info) {
283
+ if (!active) return;
284
+ move(info);
285
+ callbacks().onDrag?.(event, info);
286
+ },
287
+ end(event, info) {
288
+ if (!active) return;
289
+ active = false;
290
+ move(info);
291
+ for (const axis of axes) {
292
+ const channel = value(axis);
293
+ const velocity = info.cancelled || props.dragMomentum === false ? 0 : info.velocity[axis];
294
+ const [min, max] = limits(axis);
295
+ const target = clamp$1(channel.get() + velocity * .2, min, max);
296
+ if (elastic === 0) channel.jump(clamp$1(channel.get(), min, max));
297
+ if (reduced()) channel.jump(target);
298
+ else if (target !== channel.get() || velocity !== 0) channel.animate(target, {
299
+ type: "spring",
300
+ stiffness: 200,
301
+ damping: 30,
302
+ velocity
303
+ }, elastic === 0 ? (next) => clamp$1(next, min, max) : void 0);
304
+ }
305
+ callbacks().onDragEnd?.(event, info);
306
+ }
307
+ });
308
+ return () => {
309
+ active = false;
310
+ off();
311
+ for (const channel of values.values()) channel.stop();
312
+ };
313
+ }
314
+ //#endregion
214
315
  //#region src/clock.ts
316
+ var platform$1 = globalThis;
215
317
  var clock = {
216
- now: () => Date.now(),
318
+ now: Application.ios ? platform$1.CACurrentMediaTime ? () => platform$1.CACurrentMediaTime() * 1e3 : platform$1.__time ?? Date.now : platform$1.java?.lang?.System ? () => platform$1.java.lang.System.nanoTime() / 1e6 : Date.now,
217
319
  request: requestAnimationFrame,
218
320
  cancel: cancelAnimationFrame
219
321
  };
220
322
  //#endregion
221
- //#region src/host.ts
222
- function attachHost(node) {
223
- const original = {
224
- opacity: node.opacity ?? 1,
225
- x: node.translateX ?? 0,
226
- y: node.translateY ?? 0,
227
- scaleX: node.scaleX ?? 1,
228
- scaleY: node.scaleY ?? 1,
229
- rotate: node.rotate ?? 0
323
+ //#region src/pan.ts
324
+ var tag = 7e8;
325
+ function attachPan(node, axis, callbacks) {
326
+ const androidHost = Boolean(globalThis.android);
327
+ const slop = androidHost ? Utils.layout.toDevicePixels(8) : 8;
328
+ const handler = Manager.getInstance().createGestureHandler(HandlerType.PAN, tag++, {
329
+ shouldCancelWhenOutside: false,
330
+ ...androidHost && axis !== true ? { minDist: 1e9 } : {},
331
+ ...axis === true ? { minDist: slop } : axis === "x" ? {
332
+ activeOffsetXStart: -slop,
333
+ activeOffsetXEnd: slop,
334
+ failOffsetYStart: -slop,
335
+ failOffsetYEnd: slop
336
+ } : {
337
+ activeOffsetYStart: -slop,
338
+ activeOffsetYEnd: slop,
339
+ failOffsetXStart: -slop,
340
+ failOffsetXEnd: slop
341
+ }
342
+ });
343
+ let active = false;
344
+ let last = {
345
+ x: 0,
346
+ y: 0
230
347
  };
231
- if (original.x || original.y || original.rotate || original.scaleX !== 1 || original.scaleY !== 1) throw new Error("motion: put existing transforms on an outer container");
232
- const write = (values) => {
233
- if (values.opacity !== void 0) node.opacity = Math.min(1, Math.max(0, values.opacity));
234
- if (values.x !== void 0) node.translateX = values.x;
235
- if (values.y !== void 0) node.translateY = values.y;
236
- if (values.rotate !== void 0) node.rotate = values.rotate;
237
- if (values.scale !== void 0 || values.scaleX !== void 0) node.scaleX = (values.scale ?? 1) * (values.scaleX ?? 1);
238
- if (values.scale !== void 0 || values.scaleY !== void 0) node.scaleY = (values.scale ?? 1) * (values.scaleY ?? 1);
348
+ const sample = (args, cancelled = false) => {
349
+ const extra = args.data.extraData ?? {};
350
+ const offset = {
351
+ x: extra.translationX ?? last.x,
352
+ y: extra.translationY ?? last.y
353
+ };
354
+ const delta = {
355
+ x: offset.x - last.x,
356
+ y: offset.y - last.y
357
+ };
358
+ last = offset;
359
+ return {
360
+ point: {
361
+ x: extra.absoluteX ?? extra.x ?? 0,
362
+ y: extra.absoluteY ?? extra.y ?? 0
363
+ },
364
+ delta,
365
+ offset,
366
+ velocity: {
367
+ x: extra.velocityX ?? 0,
368
+ y: extra.velocityY ?? 0
369
+ },
370
+ cancelled
371
+ };
239
372
  };
240
- return {
241
- read: () => ({
242
- ...original,
243
- scale: 1
244
- }),
245
- write,
246
- restore: () => write(original)
373
+ const state = (args) => {
374
+ const next = args.data.state;
375
+ if (next === GestureState.ACTIVE && !active) {
376
+ last = {
377
+ x: 0,
378
+ y: 0
379
+ };
380
+ active = true;
381
+ const info = sample(args);
382
+ callbacks.start(args, info);
383
+ callbacks.move(args, info);
384
+ } else if (active && [
385
+ GestureState.END,
386
+ GestureState.CANCELLED,
387
+ GestureState.FAILED
388
+ ].includes(next)) {
389
+ active = false;
390
+ callbacks.end(args, sample(args, next !== GestureState.END));
391
+ }
392
+ };
393
+ const touch = (args) => {
394
+ if (active && args.data.state === GestureState.ACTIVE) callbacks.move(args, sample(args));
395
+ };
396
+ handler.on(GestureHandlerStateEvent, state);
397
+ handler.on(GestureHandlerTouchEvent, touch);
398
+ handler.attachToView(node);
399
+ return () => {
400
+ active = false;
401
+ handler.off(GestureHandlerStateEvent, state);
402
+ handler.off(GestureHandlerTouchEvent, touch);
403
+ handler.detachFromView(node);
247
404
  };
248
405
  }
249
406
  //#endregion
@@ -1185,13 +1342,79 @@ function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "e
1185
1342
  }
1186
1343
  //#endregion
1187
1344
  //#region src/engine.ts
1345
+ var MOTION_KEYS = [
1346
+ "opacity",
1347
+ "x",
1348
+ "y",
1349
+ "scale",
1350
+ "scaleX",
1351
+ "scaleY",
1352
+ "rotate"
1353
+ ];
1354
+ /** Platform animators take one timing curve per run — cubic-bezier only. */
1355
+ var BEZIER_EASES = /* @__PURE__ */ new Set([
1356
+ "linear",
1357
+ "easeIn",
1358
+ "easeOut",
1359
+ "easeInOut"
1360
+ ]);
1361
+ function isBezierEase(ease) {
1362
+ return ease === void 0 || Array.isArray(ease) || BEZIER_EASES.has(ease);
1363
+ }
1364
+ var BEZIER_POINTS = {
1365
+ linear: [
1366
+ 0,
1367
+ 0,
1368
+ 1,
1369
+ 1
1370
+ ],
1371
+ easeIn: [
1372
+ .42,
1373
+ 0,
1374
+ 1,
1375
+ 1
1376
+ ],
1377
+ easeOut: [
1378
+ 0,
1379
+ 0,
1380
+ .58,
1381
+ 1
1382
+ ],
1383
+ easeInOut: [
1384
+ .42,
1385
+ 0,
1386
+ .58,
1387
+ 1
1388
+ ]
1389
+ };
1390
+ /** CSS cubic-bezier control points for a delegatable ease; tuples pass through. */
1391
+ function bezierPoints(ease) {
1392
+ return Array.isArray(ease) ? ease : BEZIER_POINTS[ease ?? "easeInOut"];
1393
+ }
1394
+ function isOrchestrated(t) {
1395
+ if (!t || typeof t !== "object") return false;
1396
+ return "default" in t || MOTION_KEYS.some((key) => key in t);
1397
+ }
1398
+ function resolveTransition(t, key) {
1399
+ return isOrchestrated(t) ? {
1400
+ ...t.default,
1401
+ ...t[key]
1402
+ } : t;
1403
+ }
1188
1404
  function validateTransition(t) {
1189
- if (t.type === "spring" && (t.duration !== void 0 || t.ease !== void 0)) throw new Error("motion: physical springs do not accept duration or ease");
1405
+ if (t.type === "spring" && t.ease !== void 0) throw new Error("motion: physical springs do not accept ease");
1190
1406
  const allowed = [
1407
+ "staggerChildren",
1408
+ "delayChildren",
1409
+ "when",
1191
1410
  "type",
1192
1411
  "duration",
1193
1412
  "delay",
1194
1413
  "ease",
1414
+ "repeat",
1415
+ "repeatType",
1416
+ "repeatDelay",
1417
+ "bounce",
1195
1418
  "stiffness",
1196
1419
  "damping",
1197
1420
  "mass",
@@ -1200,10 +1423,15 @@ function validateTransition(t) {
1200
1423
  "restDelta"
1201
1424
  ];
1202
1425
  for (const key of Object.keys(t)) if (!allowed.includes(key)) throw new Error(`motion: unsupported transition ${key}`);
1426
+ if (t.when !== void 0 && !["beforeChildren", "afterChildren"].includes(t.when)) throw new Error("motion: unsupported when");
1203
1427
  if (t.type !== void 0 && t.type !== "tween" && t.type !== "spring") throw new Error("motion: unsupported transition type");
1204
1428
  for (const key of [
1429
+ "staggerChildren",
1430
+ "delayChildren",
1205
1431
  "duration",
1206
1432
  "delay",
1433
+ "repeatDelay",
1434
+ "bounce",
1207
1435
  "stiffness",
1208
1436
  "damping",
1209
1437
  "mass",
@@ -1214,6 +1442,12 @@ function validateTransition(t) {
1214
1442
  const value = t[key];
1215
1443
  if (value !== void 0 && (!Number.isFinite(value) || key !== "velocity" && value < 0)) throw new Error(`motion: invalid ${key}`);
1216
1444
  }
1445
+ if (t.repeat !== void 0 && (t.repeat < 0 || !Number.isInteger(t.repeat) && t.repeat !== Infinity)) throw new Error("motion: repeat must be a non-negative integer or Infinity");
1446
+ if (t.repeatType !== void 0 && ![
1447
+ "loop",
1448
+ "reverse",
1449
+ "mirror"
1450
+ ].includes(t.repeatType)) throw new Error("motion: unsupported repeatType");
1217
1451
  for (const key of [
1218
1452
  "mass",
1219
1453
  "stiffness",
@@ -1228,10 +1462,31 @@ function validateTransition(t) {
1228
1462
  "linear",
1229
1463
  "easeIn",
1230
1464
  "easeOut",
1231
- "easeInOut"
1465
+ "easeInOut",
1466
+ "circIn",
1467
+ "circOut",
1468
+ "circInOut",
1469
+ "backIn",
1470
+ "backOut",
1471
+ "backInOut",
1472
+ "anticipate"
1232
1473
  ].includes(t.ease)) throw new Error("motion: unsupported easing");
1233
1474
  }
1234
1475
  }
1476
+ function validateTransitionInput(t) {
1477
+ if (isOrchestrated(t)) {
1478
+ const { staggerChildren, delayChildren, when, ...channels } = t;
1479
+ validateTransition({
1480
+ staggerChildren,
1481
+ delayChildren,
1482
+ when
1483
+ });
1484
+ for (const [key, value] of Object.entries(channels)) {
1485
+ if (key !== "default" && !MOTION_KEYS.includes(key)) throw new Error(`motion: unsupported transition ${key}`);
1486
+ if (value) validateTransition(value);
1487
+ }
1488
+ } else validateTransition(t);
1489
+ }
1235
1490
  function runAnimation(clock, from, to, transition, update, complete) {
1236
1491
  validateTransition(transition);
1237
1492
  let frame = 0;
@@ -1248,34 +1503,53 @@ function runAnimation(clock, from, to, transition, update, complete) {
1248
1503
  if (result === "finished") complete?.();
1249
1504
  };
1250
1505
  const duration = transition.duration ?? .3;
1251
- const generator = transition.type === "spring" ? spring({
1252
- keyframes: [from, to],
1506
+ const repeat = transition.repeat ?? 0;
1507
+ const repeatType = transition.repeatType ?? "loop";
1508
+ const repeatDelay = (transition.repeatDelay ?? 0) * 1e3;
1509
+ const makeGenerator = (legFrom, legTo) => transition.type === "spring" ? spring({
1510
+ keyframes: [legFrom, legTo],
1253
1511
  stiffness: transition.stiffness ?? 100,
1254
1512
  damping: transition.damping ?? 10,
1255
1513
  mass: transition.mass ?? 1,
1256
1514
  velocity: transition.velocity ?? 0,
1257
1515
  restSpeed: transition.restSpeed,
1258
- restDelta: transition.restDelta
1516
+ restDelta: transition.restDelta,
1517
+ duration: transition.duration !== void 0 ? duration * 1e3 : void 0,
1518
+ bounce: transition.bounce
1259
1519
  }) : keyframes({
1260
- keyframes: [from, to],
1520
+ keyframes: [legFrom, legTo],
1261
1521
  duration: duration * 1e3,
1262
1522
  ease: transition.ease ?? "easeInOut"
1263
1523
  });
1264
- const start = clock.now() + (transition.delay ?? 0) * 1e3;
1524
+ let leg = 0;
1525
+ let generator = makeGenerator(from, to);
1526
+ let legStart = clock.now() + (transition.delay ?? 0) * 1e3;
1265
1527
  const tick = () => {
1266
1528
  if (settled) return;
1267
- const elapsed = Math.max(0, clock.now() - start);
1268
- if (clock.now() >= start) {
1269
- const state = generator.next(elapsed);
1270
- update(state.done ? to : state.value);
1271
- if (state.done) {
1272
- settle("finished");
1529
+ const now = clock.now();
1530
+ if (now < legStart) {
1531
+ frame = clock.request(tick);
1532
+ return;
1533
+ }
1534
+ const elapsed = Math.max(0, now - legStart);
1535
+ const state = generator.next(elapsed);
1536
+ const legEnd = repeatType === "loop" || leg % 2 === 0 ? to : from;
1537
+ update(state.done ? legEnd : state.value);
1538
+ if (state.done) {
1539
+ if (leg < repeat) {
1540
+ leg++;
1541
+ const [legFrom, legTo] = repeatType === "loop" || leg % 2 === 0 ? [from, to] : [to, from];
1542
+ generator = makeGenerator(legFrom, legTo);
1543
+ legStart = now + repeatDelay;
1544
+ frame = clock.request(tick);
1273
1545
  return;
1274
1546
  }
1547
+ settle("finished");
1548
+ return;
1275
1549
  }
1276
1550
  if (!settled) frame = clock.request(tick);
1277
1551
  };
1278
- if (transition.type !== "spring" && duration === 0 && !transition.delay) {
1552
+ if (transition.type !== "spring" && duration === 0 && !transition.delay && repeat === 0) {
1279
1553
  update(to);
1280
1554
  settle("finished");
1281
1555
  } else frame = clock.request(tick);
@@ -1285,6 +1559,214 @@ function runAnimation(clock, from, to, transition, update, complete) {
1285
1559
  };
1286
1560
  }
1287
1561
  //#endregion
1562
+ //#region src/delegation.ts
1563
+ function delegationStats() {
1564
+ const platform = globalThis;
1565
+ return platform.__xplatMotionDelegations ??= {
1566
+ started: 0,
1567
+ finished: 0,
1568
+ cancelled: 0,
1569
+ fallback: 0
1570
+ };
1571
+ }
1572
+ /** Count a delegated run and fold its settled result into the counters. */
1573
+ function trackDelegatedRun(run) {
1574
+ delegationStats().started++;
1575
+ const finished = run.finished.then((result) => {
1576
+ if (result === "finished") delegationStats().finished++;
1577
+ else delegationStats().cancelled++;
1578
+ return result;
1579
+ });
1580
+ return {
1581
+ ...run,
1582
+ finished
1583
+ };
1584
+ }
1585
+ //#endregion
1586
+ //#region src/driver.ts
1587
+ var platform = globalThis;
1588
+ function iosRun(node, req, write) {
1589
+ const view = node.ios ?? node.nativeViewProtected;
1590
+ if (!view?.layer || !platform.UIViewPropertyAnimator) return null;
1591
+ const { eff, target, transition: t } = req;
1592
+ const [x1, y1, x2, y2] = bezierPoints(t.ease);
1593
+ const params = platform.UICubicTimingParameters.alloc().initWithControlPoint1ControlPoint2(platform.CGPointMake(x1, y1), platform.CGPointMake(x2, y2));
1594
+ const animator = platform.UIViewPropertyAnimator.alloc().initWithDurationTimingParameters(t.duration ?? .3, params);
1595
+ const radians = (eff.rotate ?? 0) * Math.PI / 180;
1596
+ const dest = platform.CGAffineTransformScale(platform.CGAffineTransformRotate(platform.CGAffineTransformMakeTranslation(eff.x ?? 0, eff.y ?? 0), radians), eff.scaleX ?? 1, eff.scaleY ?? 1);
1597
+ const sample = () => {
1598
+ const layer = view.layer.presentationLayer?.() ?? view.layer;
1599
+ const tr = layer.transform;
1600
+ return {
1601
+ opacity: layer.opacity,
1602
+ x: tr.m41,
1603
+ y: tr.m42,
1604
+ scaleX: Math.hypot(tr.m11, tr.m12),
1605
+ scaleY: Math.hypot(tr.m21, tr.m22),
1606
+ rotate: Math.atan2(tr.m12, tr.m11) * 180 / Math.PI
1607
+ };
1608
+ };
1609
+ let resolve;
1610
+ const finished = new Promise((done) => {
1611
+ resolve = done;
1612
+ });
1613
+ let settled = false;
1614
+ const settle = (result) => {
1615
+ if (!settled) {
1616
+ settled = true;
1617
+ resolve(result);
1618
+ }
1619
+ };
1620
+ animator.addAnimations(() => {
1621
+ if (target.x !== void 0 || target.y !== void 0 || target.rotate !== void 0 || target.scale !== void 0 || target.scaleX !== void 0 || target.scaleY !== void 0) view.transform = dest;
1622
+ if (target.opacity !== void 0) view.alpha = Math.min(1, Math.max(0, eff.opacity ?? target.opacity));
1623
+ });
1624
+ animator.addCompletion((position) => {
1625
+ if (position === 0) {
1626
+ write(req.dest);
1627
+ settle("finished");
1628
+ } else settle("cancelled");
1629
+ });
1630
+ if (t.delay) animator.startAnimationAfterDelay(t.delay);
1631
+ else animator.startAnimation();
1632
+ return {
1633
+ finished,
1634
+ sample,
1635
+ cancel() {
1636
+ const current = sample();
1637
+ animator.stopAnimation(true);
1638
+ write({
1639
+ opacity: current.opacity,
1640
+ x: current.x,
1641
+ y: current.y,
1642
+ scaleX: current.scaleX,
1643
+ scaleY: current.scaleY,
1644
+ rotate: current.rotate
1645
+ });
1646
+ settle("cancelled");
1647
+ }
1648
+ };
1649
+ }
1650
+ function androidRun(node, req, write) {
1651
+ const view = node.nativeViewProtected ?? node.android;
1652
+ const android = platform.android;
1653
+ const androidx = platform.androidx;
1654
+ if (!view?.animate || !android?.animation || !androidx?.core?.view?.animation?.PathInterpolatorCompat) return null;
1655
+ const { eff, target, transition: t } = req;
1656
+ const animator = view.animate();
1657
+ const [x1, y1, x2, y2] = bezierPoints(t.ease);
1658
+ animator.setInterpolator(androidx.core.view.animation.PathInterpolatorCompat.create(x1, y1, x2, y2));
1659
+ animator.setDuration(Math.round((t.duration ?? .3) * 1e3));
1660
+ animator.setStartDelay(Math.round((t.delay ?? 0) * 1e3));
1661
+ if (target.x !== void 0) animator.translationX(eff.x);
1662
+ if (target.y !== void 0) animator.translationY(eff.y);
1663
+ if (target.scale !== void 0 || target.scaleX !== void 0) animator.scaleX(eff.scaleX);
1664
+ if (target.scale !== void 0 || target.scaleY !== void 0) animator.scaleY(eff.scaleY);
1665
+ if (target.rotate !== void 0) animator.rotation(eff.rotate);
1666
+ if (target.opacity !== void 0) animator.alpha(Math.min(1, Math.max(0, eff.opacity ?? target.opacity)));
1667
+ let resolve;
1668
+ const finished = new Promise((done) => {
1669
+ resolve = done;
1670
+ });
1671
+ let settled = false;
1672
+ const settle = (result) => {
1673
+ if (!settled) {
1674
+ settled = true;
1675
+ resolve(result);
1676
+ }
1677
+ };
1678
+ let cancelled = false;
1679
+ animator.setListener(new android.animation.Animator.AnimatorListener({
1680
+ onAnimationCancel() {
1681
+ cancelled = true;
1682
+ settle("cancelled");
1683
+ },
1684
+ onAnimationEnd() {
1685
+ if (!cancelled) write(req.dest);
1686
+ settle(cancelled ? "cancelled" : "finished");
1687
+ },
1688
+ onAnimationStart() {},
1689
+ onAnimationRepeat() {}
1690
+ }));
1691
+ animator.start();
1692
+ return {
1693
+ finished,
1694
+ sample: () => ({
1695
+ opacity: view.getAlpha(),
1696
+ x: view.getTranslationX(),
1697
+ y: view.getTranslationY(),
1698
+ scaleX: view.getScaleX(),
1699
+ scaleY: view.getScaleY(),
1700
+ rotate: view.getRotation()
1701
+ }),
1702
+ cancel() {
1703
+ animator.cancel();
1704
+ }
1705
+ };
1706
+ }
1707
+ function delegatedRun(node, req, write) {
1708
+ if (!isBezierEase(req.transition.ease)) {
1709
+ delegationStats().fallback++;
1710
+ return null;
1711
+ }
1712
+ const run = node.ios && platform.UIViewPropertyAnimator ? iosRun(node, req, write) : platform.android?.animation && (node.nativeViewProtected ?? node.android) ? androidRun(node, req, write) : null;
1713
+ if (!run) {
1714
+ delegationStats().fallback++;
1715
+ return null;
1716
+ }
1717
+ return trackDelegatedRun(run);
1718
+ }
1719
+ //#endregion
1720
+ //#region src/host.ts
1721
+ function attachHost(node) {
1722
+ const original = {
1723
+ opacity: node.opacity ?? 1,
1724
+ x: node.translateX ?? 0,
1725
+ y: node.translateY ?? 0,
1726
+ scaleX: node.scaleX ?? 1,
1727
+ scaleY: node.scaleY ?? 1,
1728
+ rotate: node.rotate ?? 0
1729
+ };
1730
+ if (original.x || original.y || original.rotate || original.scaleX !== 1 || original.scaleY !== 1) throw new Error("motion: put existing transforms on an outer container");
1731
+ const write = (values) => {
1732
+ if (values.opacity !== void 0) node.opacity = Math.min(1, Math.max(0, values.opacity));
1733
+ if (values.x !== void 0) node.translateX = values.x;
1734
+ if (values.y !== void 0) node.translateY = values.y;
1735
+ if (values.rotate !== void 0) node.rotate = values.rotate;
1736
+ if (values.scale !== void 0 || values.scaleX !== void 0) node.scaleX = (values.scale ?? 1) * (values.scaleX ?? 1);
1737
+ if (values.scale !== void 0 || values.scaleY !== void 0) node.scaleY = (values.scale ?? 1) * (values.scaleY ?? 1);
1738
+ };
1739
+ return {
1740
+ pan: (axis, callbacks) => attachPan(node, axis, callbacks),
1741
+ read: () => ({
1742
+ ...original,
1743
+ scale: 1
1744
+ }),
1745
+ write,
1746
+ restore: () => write(original),
1747
+ delegate: (req) => delegatedRun(node, req, write),
1748
+ gesture(kind, callbacks) {
1749
+ if (typeof node.on !== "function" || typeof node.off !== "function") return null;
1750
+ if (kind === "press") {
1751
+ const onTouch = (event) => {
1752
+ if (event.action === "down") callbacks.start();
1753
+ else if (event.action === "up" || event.action === "cancel") callbacks.end();
1754
+ };
1755
+ node.on("touch", onTouch);
1756
+ return () => node.off("touch", onTouch);
1757
+ }
1758
+ const onFocus = () => callbacks.start();
1759
+ const onBlur = () => callbacks.end();
1760
+ node.on("focus", onFocus);
1761
+ node.on("blur", onBlur);
1762
+ return () => {
1763
+ node.off("focus", onFocus);
1764
+ node.off("blur", onBlur);
1765
+ };
1766
+ }
1767
+ };
1768
+ }
1769
+ //#endregion
1288
1770
  //#region src/value.ts
1289
1771
  /** Numeric value with direct subscriptions; it does not subscribe Octane renders. */
1290
1772
  var MotionValue = class {
@@ -1349,6 +1831,13 @@ var MotionValue = class {
1349
1831
  for (const callback of [...this.listeners.get(event) ?? []]) callback(value);
1350
1832
  }
1351
1833
  write(value) {
1834
+ this.track(value);
1835
+ if (!this.disposed && value !== this.previous) this.emit("change", value);
1836
+ }
1837
+ /** Update position bookkeeping without emitting — platform-delegated runs
1838
+ * track presentation state so interruption reads fresh value/velocity. */
1839
+ track(value) {
1840
+ this.assert(value);
1352
1841
  if (this.disposed) return;
1353
1842
  const now = this.clock.now();
1354
1843
  const elapsed = now - this.updated;
@@ -1356,10 +1845,9 @@ var MotionValue = class {
1356
1845
  this.previous = this.current;
1357
1846
  this.current = value;
1358
1847
  this.updated = now;
1359
- if (value !== this.previous) this.emit("change", value);
1360
1848
  }
1361
1849
  /** Animate to a destination. New playback replaces old playback. */
1362
- animate(target, transition = {}) {
1850
+ animate(target, transition = {}, clampSample) {
1363
1851
  this.assert(target);
1364
1852
  validateTransition(transition);
1365
1853
  if (this.disposed) return {
@@ -1373,7 +1861,7 @@ var MotionValue = class {
1373
1861
  const controls = runAnimation(this.clock, this.current, target, {
1374
1862
  ...transition,
1375
1863
  velocity
1376
- }, (value) => this.write(value));
1864
+ }, (value) => this.write(clampSample ? clampSample(value) : value));
1377
1865
  this.controls = controls;
1378
1866
  controls.finished.then((result) => {
1379
1867
  if (generation !== this.generation || this.disposed) return;
@@ -1434,7 +1922,10 @@ function validateTarget(target) {
1434
1922
  var Controller = class {
1435
1923
  clock;
1436
1924
  values = /* @__PURE__ */ new Map();
1437
- write;
1925
+ /** Per-write snapshot callback (rAF-rate during delegated and JS runs). */
1926
+ onUpdate;
1927
+ adapter;
1928
+ delegated;
1438
1929
  generation = 0;
1439
1930
  constructor(clock) {
1440
1931
  this.clock = clock;
@@ -1448,15 +1939,24 @@ var Controller = class {
1448
1939
  }
1449
1940
  return value;
1450
1941
  }
1451
- attach(write) {
1452
- this.write = write;
1942
+ attach(adapter) {
1943
+ this.adapter = adapter;
1453
1944
  this.flush();
1454
1945
  }
1946
+ cancelDelegated() {
1947
+ if (!this.delegated) return;
1948
+ this.clock.cancel(this.delegated.frame);
1949
+ this.delegated.trackSample();
1950
+ this.delegated.run.cancel();
1951
+ this.delegated = void 0;
1952
+ }
1455
1953
  snapshot() {
1456
1954
  return Object.fromEntries([...this.values].map(([key, value]) => [key, value.get()]));
1457
1955
  }
1458
1956
  flush() {
1459
- this.write?.(Object.fromEntries([...this.values].map(([key, value]) => [key, value.get()])));
1957
+ const snapshot = Object.fromEntries([...this.values].map(([key, value]) => [key, value.get()]));
1958
+ this.adapter?.write(snapshot);
1959
+ this.onUpdate?.(snapshot);
1460
1960
  }
1461
1961
  set(key, next) {
1462
1962
  this.value(key).jump(next);
@@ -1468,9 +1968,65 @@ var Controller = class {
1468
1968
  async animate(target, transition, reduced) {
1469
1969
  validateTarget(target);
1470
1970
  const generation = ++this.generation;
1971
+ this.cancelDelegated();
1471
1972
  for (const value of this.values.values()) value.stop("replaced");
1973
+ if (!reduced && !isOrchestrated(transition) && transition.type !== "spring" && (transition.duration ?? .3) > 0 && (transition.repeat ?? 0) === 0 && isBezierEase(transition.ease) && this.adapter?.delegate) {
1974
+ const dest = {
1975
+ ...this.snapshot(),
1976
+ ...target
1977
+ };
1978
+ const run = this.adapter.delegate({
1979
+ target,
1980
+ dest,
1981
+ eff: {
1982
+ opacity: dest.opacity,
1983
+ x: dest.x,
1984
+ y: dest.y,
1985
+ scaleX: (dest.scale ?? 1) * (dest.scaleX ?? 1),
1986
+ scaleY: (dest.scale ?? 1) * (dest.scaleY ?? 1),
1987
+ rotate: dest.rotate
1988
+ },
1989
+ transition
1990
+ });
1991
+ if (run) {
1992
+ const trackSample = () => {
1993
+ const eff = run.sample();
1994
+ for (const key of keys) {
1995
+ if (target[key] === void 0) continue;
1996
+ let next;
1997
+ if (key === "scale") next = (eff.scaleX ?? 1) / (dest.scaleX || 1);
1998
+ else if (key === "scaleX") next = (eff.scaleX ?? 1) / (dest.scale || 1);
1999
+ else if (key === "scaleY") next = (eff.scaleY ?? 1) / (dest.scale || 1);
2000
+ else next = eff[key];
2001
+ if (next !== void 0 && Number.isFinite(next)) this.value(key).track(next);
2002
+ }
2003
+ this.onUpdate?.(this.snapshot());
2004
+ };
2005
+ const entry = {
2006
+ run,
2007
+ frame: 0,
2008
+ trackSample
2009
+ };
2010
+ const tick = () => {
2011
+ if (this.delegated !== entry) return;
2012
+ trackSample();
2013
+ entry.frame = this.clock.request(tick);
2014
+ };
2015
+ entry.frame = this.clock.request(tick);
2016
+ this.delegated = entry;
2017
+ const result = await run.finished;
2018
+ if (this.delegated === entry) {
2019
+ this.clock.cancel(entry.frame);
2020
+ this.delegated = void 0;
2021
+ }
2022
+ if (generation !== this.generation) return "replaced";
2023
+ if (result !== "finished") return "cancelled";
2024
+ for (const key of keys) if (dest[key] !== void 0) this.value(key).jump(dest[key]);
2025
+ return "finished";
2026
+ }
2027
+ }
1472
2028
  const jobs = Object.entries(target).map(([key, to]) => {
1473
- return this.value(key).animate(to, reduced && key !== "opacity" ? { duration: 0 } : transition).finished;
2029
+ return this.value(key).animate(to, reduced && key !== "opacity" ? { duration: 0 } : resolveTransition(transition, key)).finished;
1474
2030
  });
1475
2031
  const results = await Promise.all(jobs);
1476
2032
  if (generation !== this.generation) return "replaced";
@@ -1478,18 +2034,135 @@ var Controller = class {
1478
2034
  }
1479
2035
  stop() {
1480
2036
  ++this.generation;
2037
+ this.cancelDelegated();
1481
2038
  for (const value of this.values.values()) value.stop();
1482
2039
  }
1483
2040
  destroy() {
1484
2041
  this.stop();
1485
2042
  for (const value of this.values.values()) value.destroy();
1486
2043
  this.values.clear();
1487
- this.write = void 0;
2044
+ this.adapter = void 0;
2045
+ }
2046
+ };
2047
+ //#endregion
2048
+ //#region src/variants.ts
2049
+ function isLabels(value) {
2050
+ return typeof value === "string" || Array.isArray(value) && value.every((label) => typeof label === "string");
2051
+ }
2052
+ function resolveVariant(definition, variants, custom) {
2053
+ const target = {};
2054
+ let transition;
2055
+ if (definition === void 0 || definition === false) return {
2056
+ target,
2057
+ transition
2058
+ };
2059
+ const entries = isLabels(definition) ? (typeof definition === "string" ? [definition] : definition).map((label) => Object.hasOwn(variants ?? {}, label) ? variants[label] : void 0) : [definition];
2060
+ for (const entry of entries) {
2061
+ const resolved = typeof entry === "function" ? entry(custom) : entry;
2062
+ if (!resolved) continue;
2063
+ const { transition: override, ...values } = resolved;
2064
+ validateTarget(values);
2065
+ Object.assign(target, values);
2066
+ if (override !== void 0) {
2067
+ validateTransitionInput(override);
2068
+ transition = override;
2069
+ }
2070
+ }
2071
+ return {
2072
+ target,
2073
+ transition
2074
+ };
2075
+ }
2076
+ /** Keep tree timing out of Controller and add inherited delay to every channel. */
2077
+ function playbackTransition(input, delay = 0) {
2078
+ if (isOrchestrated(input)) {
2079
+ const { staggerChildren: _staggerChildren, delayChildren: _delayChildren, when: _when, ...timing } = input;
2080
+ const result = { default: {
2081
+ ...timing.default,
2082
+ delay: (timing.default?.delay ?? 0) + delay
2083
+ } };
2084
+ for (const [key, value] of Object.entries(timing)) if (key !== "default" && value) result[key] = {
2085
+ ...value,
2086
+ delay: (value.delay ?? timing.default?.delay ?? 0) + delay
2087
+ };
2088
+ return result;
2089
+ }
2090
+ const { staggerChildren: _staggerChildren, delayChildren: _delayChildren, when: _when, ...timing } = input;
2091
+ return {
2092
+ ...timing,
2093
+ delay: (timing.delay ?? 0) + delay
2094
+ };
2095
+ }
2096
+ /** Root-local tree of inherited runs. Generations prevent queued work after replacement/disposal. */
2097
+ var VariantNode = class {
2098
+ children = /* @__PURE__ */ new Set();
2099
+ generation = 0;
2100
+ active;
2101
+ initial;
2102
+ animate;
2103
+ resolve;
2104
+ play;
2105
+ stopOwn;
2106
+ register(child) {
2107
+ this.children.add(child);
2108
+ const active = this.active;
2109
+ if (active && isLabels(active.definition)) active.finished.then((result) => {
2110
+ if (result !== "finished" || active.generation !== this.generation || !this.children.has(child)) return;
2111
+ const { transition } = this.resolve(active.definition);
2112
+ child.run(active.definition, (transition.delayChildren ?? 0) + [...this.children].indexOf(child) * (transition.staggerChildren ?? 0));
2113
+ });
2114
+ return () => {
2115
+ this.children.delete(child);
2116
+ child.stop();
2117
+ };
2118
+ }
2119
+ /** Invalidate queued phases without stopping independently registered Presence exits. */
2120
+ cancelPending() {
2121
+ this.generation++;
2122
+ for (const child of this.children) child.cancelPending();
2123
+ }
2124
+ stop() {
2125
+ this.generation++;
2126
+ this.stopOwn?.();
2127
+ for (const child of this.children) child.stop();
2128
+ }
2129
+ run(definition, delay = 0) {
2130
+ this.animate = isLabels(definition) ? definition : void 0;
2131
+ this.stop();
2132
+ const generation = this.generation;
2133
+ const finished = this.runTree(definition, delay, generation);
2134
+ this.active = {
2135
+ definition,
2136
+ finished,
2137
+ generation
2138
+ };
2139
+ return finished;
2140
+ }
2141
+ async runTree(definition, delay, generation) {
2142
+ const { target, transition } = this.resolve(definition);
2143
+ const participants = [...this.children];
2144
+ const own = () => this.play(definition, target, playbackTransition(transition, transition.when === "afterChildren" && participants.length && isLabels(definition) ? 0 : delay));
2145
+ const children = () => Promise.all(isLabels(definition) ? participants.filter((child) => this.children.has(child)).map((child, index) => child.run(definition, (transition.when === "beforeChildren" ? 0 : delay) + (transition.delayChildren ?? 0) + index * (transition.staggerChildren ?? 0))) : []);
2146
+ let results;
2147
+ if (transition.when === "beforeChildren") {
2148
+ const result = await own();
2149
+ if (generation !== this.generation || result !== "finished") return "cancelled";
2150
+ results = [result, ...await children()];
2151
+ } else if (transition.when === "afterChildren") {
2152
+ results = await children();
2153
+ if (generation !== this.generation || results.some((result) => result !== "finished")) return "cancelled";
2154
+ results.push(await own());
2155
+ } else {
2156
+ const parent = own();
2157
+ const descendants = children();
2158
+ results = [await parent, ...await descendants];
2159
+ }
2160
+ return generation === this.generation && results.every((result) => result === "finished") ? "finished" : "cancelled";
1488
2161
  }
1489
2162
  };
1490
2163
  //#endregion
1491
2164
  //#region src/components.tsrx
1492
- var _hs$$1 = /* @__PURE__ */ hookSlots(19);
2165
+ var _hs$$1 = /* @__PURE__ */ hookSlots(24);
1493
2166
  var _h$0$1 = /* @__PURE__ */ Symbol(_hs$$1);
1494
2167
  var _h$1$1 = /* @__PURE__ */ Symbol(_hs$$1 + 1);
1495
2168
  var _h$2$1 = /* @__PURE__ */ Symbol(_hs$$1 + 2);
@@ -1498,10 +2171,10 @@ var _h$4$1 = /* @__PURE__ */ Symbol(_hs$$1 + 4);
1498
2171
  var _h$5$1 = /* @__PURE__ */ Symbol(_hs$$1 + 5);
1499
2172
  var _h$6$1 = /* @__PURE__ */ Symbol(_hs$$1 + 6);
1500
2173
  var _h$7$1 = /* @__PURE__ */ Symbol(_hs$$1 + 7);
1501
- var _h$8 = /* @__PURE__ */ Symbol(_hs$$1 + 8);
1502
- var _h$9 = /* @__PURE__ */ Symbol(_hs$$1 + 9);
1503
- var _h$10 = /* @__PURE__ */ Symbol(_hs$$1 + 10);
1504
- var _h$11 = /* @__PURE__ */ Symbol(_hs$$1 + 11);
2174
+ var _h$8$1 = /* @__PURE__ */ Symbol(_hs$$1 + 8);
2175
+ var _h$9$1 = /* @__PURE__ */ Symbol(_hs$$1 + 9);
2176
+ var _h$10$1 = /* @__PURE__ */ Symbol(_hs$$1 + 10);
2177
+ var _h$11$1 = /* @__PURE__ */ Symbol(_hs$$1 + 11);
1505
2178
  var _h$12 = /* @__PURE__ */ Symbol(_hs$$1 + 12);
1506
2179
  var _h$13 = /* @__PURE__ */ Symbol(_hs$$1 + 13);
1507
2180
  var _h$14 = /* @__PURE__ */ Symbol(_hs$$1 + 14);
@@ -1509,6 +2182,11 @@ var _h$15 = /* @__PURE__ */ Symbol(_hs$$1 + 15);
1509
2182
  var _h$16 = /* @__PURE__ */ Symbol(_hs$$1 + 16);
1510
2183
  var _h$17 = /* @__PURE__ */ Symbol(_hs$$1 + 17);
1511
2184
  var _h$18 = /* @__PURE__ */ Symbol(_hs$$1 + 18);
2185
+ var _h$19 = /* @__PURE__ */ Symbol(_hs$$1 + 19);
2186
+ var _h$20 = /* @__PURE__ */ Symbol(_hs$$1 + 20);
2187
+ var _h$21 = /* @__PURE__ */ Symbol(_hs$$1 + 21);
2188
+ var _h$22 = /* @__PURE__ */ Symbol(_hs$$1 + 22);
2189
+ var _h$23 = /* @__PURE__ */ Symbol(_hs$$1 + 23);
1512
2190
  var __octaneUniversalPlan0 = universalPlan("nativescript", {
1513
2191
  "kind": "slot",
1514
2192
  "slot": 0
@@ -1521,40 +2199,88 @@ var __octaneUniversalPlan2 = universalPlan("nativescript", {
1521
2199
  "kind": "slot",
1522
2200
  "slot": 0
1523
2201
  });
2202
+ var __octaneUniversalPlan3 = universalPlan("nativescript", {
2203
+ "kind": "slot",
2204
+ "slot": 0
2205
+ });
2206
+ var __octaneUniversalPlan4 = universalPlan("nativescript", {
2207
+ "kind": "slot",
2208
+ "slot": 0
2209
+ });
2210
+ var __octaneUniversalPlan5 = universalPlan("nativescript", {
2211
+ "kind": "slot",
2212
+ "slot": 0
2213
+ });
2214
+ var __octaneUniversalPlan6 = universalPlan("nativescript", {
2215
+ "kind": "slot",
2216
+ "slot": 0
2217
+ });
2218
+ var __octaneUniversalPlan7 = universalPlan("nativescript", {
2219
+ "kind": "slot",
2220
+ "slot": 0
2221
+ });
2222
+ var VariantContext = createContext(null);
2223
+ var controllers = /* @__PURE__ */ new WeakMap();
2224
+ var GESTURE_PROPS = {
2225
+ press: {
2226
+ target: "whileTap",
2227
+ transition: "whileTapTransition"
2228
+ },
2229
+ focus: {
2230
+ target: "whileFocus",
2231
+ transition: "whileFocusTransition"
2232
+ }
2233
+ };
1524
2234
  function useMotion(props) {
1525
2235
  for (const name of [
1526
- "drag",
1527
2236
  "layout",
1528
2237
  "layoutId",
1529
- "variants",
1530
- "whileTap",
2238
+ "inherit",
1531
2239
  "whileHover",
1532
- "whileFocus",
1533
- "whileInView"
2240
+ "whileInView",
2241
+ "whileDrag"
1534
2242
  ]) if (name in props) throw new Error(`motion: ${name} is outside the supported subset`);
2243
+ validateDrag(props);
2244
+ const parentVariant = useContext(VariantContext);
2245
+ const inherits = props.animate === void 0 && parentVariant !== null;
2246
+ const initialDefinition = props.initial ?? parentVariant?.initial;
2247
+ const animateDefinition = props.animate ?? parentVariant?.animate;
2248
+ const resolve = (definition, source = props) => {
2249
+ const resolved = resolveVariant(definition, source.variants, source.custom);
2250
+ for (const key of keys) if (resolved.target[key] !== void 0 && source.style?.[key] instanceof MotionValue) throw new Error(`motion: ${key} has both an animate target and a MotionValue writer`);
2251
+ return resolved;
2252
+ };
2253
+ const resolvedAnimate = resolve(animateDefinition);
2254
+ const resolvedInitial = resolve(initialDefinition);
2255
+ const resolvedExit = resolve(props.exit);
1535
2256
  const presence = useContext(PresenceContext);
1536
2257
  if (props.exit && !presence) throw new Error("motion: exit requires a mounted Presence boundary");
1537
2258
  const config = useContext(ConfigContext);
1538
2259
  const systemReduced = withSlot(_h$0$1, useReducedMotion);
1539
2260
  const reduced = config.reducedMotion === "always" || config.reducedMotion === "user" && systemReduced;
1540
2261
  const transition = props.transition ?? config.transition ?? {};
1541
- validateTransition(transition);
1542
- validateTarget(props.animate);
1543
- validateTarget(props.exit);
1544
- if (props.initial !== false) validateTarget(props.initial);
2262
+ validateTransitionInput(transition);
2263
+ resolve(props.whileTap);
2264
+ resolve(props.whileFocus);
2265
+ if (props.whileTapTransition) validateTransitionInput(props.whileTapTransition);
2266
+ if (props.whileFocusTransition) validateTransitionInput(props.whileFocusTransition);
1545
2267
  const latest = useRef(props, _h$1$1);
1546
2268
  latest.current = props;
1547
2269
  const state = useRef(null, _h$2$1);
1548
2270
  if (!state.current) state.current = {
1549
2271
  controller: new Controller(clock),
1550
- initialized: false
2272
+ initialized: false,
2273
+ preGesture: {
2274
+ press: void 0,
2275
+ focus: void 0
2276
+ }
1551
2277
  };
1552
2278
  const current = state.current;
1553
2279
  const style = {};
1554
2280
  const bindings = [];
1555
2281
  for (const [key, value] of Object.entries(props.style ?? {})) if (keys.includes(key)) {
1556
2282
  if (!(value instanceof MotionValue) && (typeof value !== "number" || !Number.isFinite(value))) throw new Error(`motion: invalid style ${key}`);
1557
- if (value instanceof MotionValue && (props.animate?.[key] !== void 0 || props.exit?.[key] !== void 0 || props.initial && props.initial[key] !== void 0)) throw new Error(`motion: ${key} has both an animate target and a MotionValue writer`);
2283
+ if (value instanceof MotionValue && (resolvedAnimate.target[key] !== void 0 || resolvedExit.target[key] !== void 0 || resolvedInitial.target[key] !== void 0)) throw new Error(`motion: ${key} has both an animate target and a MotionValue writer`);
1558
2284
  bindings.push([key, value]);
1559
2285
  } else {
1560
2286
  if (value instanceof MotionValue || [
@@ -1570,11 +2296,36 @@ function useMotion(props) {
1570
2296
  const boundKeys = useRef([], _h$4$1);
1571
2297
  const firstBinding = useRef(true, _h$5$1);
1572
2298
  const firstAnimation = useRef(true, _h$6$1);
2299
+ const inheritedRun = useRef(null, _h$7$1);
1573
2300
  if (previous.current.length !== bindings.length || previous.current.some(([key, value], i) => key !== bindings[i][0] || value !== bindings[i][1])) previous.current = bindings;
1574
- const bind = useRef(() => {}, _h$7$1);
1575
- bind.current = (host) => {
2301
+ current.controller.onUpdate = (snapshot) => latest.current.onUpdate?.(snapshot);
2302
+ const gesture = (kind, phase) => {
2303
+ const shape = GESTURE_PROPS[kind];
2304
+ const definition = latest.current[shape.target];
2305
+ if (!definition) return;
2306
+ const { target, transition: override } = resolve(definition, latest.current);
2307
+ const gestureTransition = playbackTransition(override ?? latest.current[shape.transition] ?? playback.current.transition);
2308
+ if (phase === "start") {
2309
+ const snapshot = current.controller.snapshot();
2310
+ const rest = {};
2311
+ for (const key of Object.keys(target)) rest[key] = snapshot[key];
2312
+ current.preGesture[kind] = rest;
2313
+ current.controller.animate(target, gestureTransition, playback.current.reduced);
2314
+ } else {
2315
+ const snap = current.preGesture[kind] ?? {};
2316
+ current.preGesture[kind] = void 0;
2317
+ const rest = {};
2318
+ for (const key of Object.keys(target)) rest[key] = resolve(latest.current.animate ?? parentVariant?.animate, latest.current).target[key] ?? snap[key] ?? defaults[key];
2319
+ current.controller.animate(rest, gestureTransition, playback.current.reduced);
2320
+ }
2321
+ };
2322
+ const ref = useRef(() => {}, _h$8$1);
2323
+ ref.current = (host) => {
1576
2324
  if (!host) return;
1577
2325
  if (current.host !== host) {
2326
+ current.gestureOff?.();
2327
+ current.dragOff?.();
2328
+ current.gestureOff = void 0;
1578
2329
  current.adapter?.restore();
1579
2330
  current.host = host;
1580
2331
  current.adapter = attachHost(host);
@@ -1582,49 +2333,83 @@ function useMotion(props) {
1582
2333
  const baseline = current.adapter.read();
1583
2334
  for (const [key, value] of previous.current) baseline[key] = value instanceof MotionValue ? value.get() : value;
1584
2335
  current.controller.seed(baseline);
1585
- current.controller.seed(props.initial === false ? props.animate ?? {} : props.initial ?? {});
2336
+ current.controller.seed(initialDefinition === false ? resolvedAnimate.target : resolvedInitial.target);
1586
2337
  current.initialized = true;
1587
2338
  }
1588
- current.controller.attach(current.adapter.write);
2339
+ current.controller.attach(current.adapter);
2340
+ controllers.set(host, current.controller);
2341
+ configureDrag();
2342
+ if (current.adapter.gesture) {
2343
+ const offs = ["press", "focus"].map((kind) => current.adapter.gesture(kind, {
2344
+ start: () => gesture(kind, "start"),
2345
+ end: () => gesture(kind, "end")
2346
+ })).filter((off) => typeof off === "function");
2347
+ current.gestureOff = () => offs.forEach((off) => off());
2348
+ }
1589
2349
  }
1590
2350
  current.controller.flush();
1591
- latest.current.bind?.(host);
1592
2351
  };
1593
2352
  const playback = useRef({
1594
2353
  transition,
1595
2354
  reduced
1596
- }, _h$8);
2355
+ }, _h$9$1);
1597
2356
  playback.current = {
1598
2357
  transition,
1599
2358
  reduced
1600
2359
  };
1601
- const returning = useRef(null, _h$9);
1602
- const member = useRef(null, _h$10);
2360
+ const nodeRef = useRef(null, _h$10$1);
2361
+ if (!nodeRef.current) nodeRef.current = new VariantNode();
2362
+ const node = nodeRef.current;
2363
+ node.initial = initialDefinition === false || isLabels(initialDefinition) ? initialDefinition : void 0;
2364
+ node.animate = isLabels(animateDefinition) ? animateDefinition : void 0;
2365
+ node.resolve = (definition) => {
2366
+ const resolved = resolve(definition, latest.current);
2367
+ return {
2368
+ target: resolved.target,
2369
+ transition: resolved.transition ?? playback.current.transition
2370
+ };
2371
+ };
2372
+ node.stopOwn = () => current.controller.stop();
2373
+ node.play = async (definition, target, timing) => {
2374
+ latest.current.onAnimationStart?.(definition);
2375
+ const result = await current.controller.animate(target, timing, playback.current.reduced);
2376
+ if (result === "finished" && current.host) latest.current.onAnimationComplete?.();
2377
+ return result;
2378
+ };
2379
+ useLayoutEffect(() => inherits ? parentVariant.register(node) : void 0, [inherits, parentVariant], _h$11$1);
2380
+ const returning = useRef(null, _h$12);
2381
+ const member = useRef(null, _h$13);
1603
2382
  if (!member.current) member.current = {
1604
2383
  exit() {
2384
+ node.cancelPending();
1605
2385
  const snapshot = current.controller.snapshot();
1606
2386
  returning.current ??= {};
1607
- for (const key of Object.keys(latest.current.exit ?? {})) if (!(key in returning.current)) returning.current[key] = snapshot[key];
1608
- return current.controller.animate(latest.current.exit ?? {}, playback.current.transition, playback.current.reduced);
2387
+ const resolved = resolve(latest.current.exit, latest.current);
2388
+ for (const key of Object.keys(resolved.target)) if (!(key in returning.current)) returning.current[key] = snapshot[key];
2389
+ return current.controller.animate(resolved.target, playbackTransition(resolved.transition ?? playback.current.transition), playback.current.reduced);
1609
2390
  },
1610
2391
  enter() {
2392
+ const resolved = resolve(latest.current.animate ?? parentVariant?.animate, latest.current);
1611
2393
  const target = {
1612
2394
  ...returning.current,
1613
- ...latest.current.animate
2395
+ ...resolved.target
1614
2396
  };
1615
2397
  returning.current = null;
1616
- current.controller.animate(target, playback.current.transition, playback.current.reduced).then((result) => {
2398
+ current.controller.animate(target, playbackTransition(resolved.transition ?? playback.current.transition), playback.current.reduced).then((result) => {
1617
2399
  if (result === "finished" && current.host && presence?.present) latest.current.onAnimationComplete?.();
1618
2400
  });
1619
2401
  }
1620
2402
  };
1621
- useLayoutEffect(() => presence?.register(member.current), [presence], _h$11);
1622
- const stableBind = useRef((host) => bind.current(host), _h$12).current;
2403
+ useLayoutEffect(() => presence?.register(member.current), [presence], _h$14);
2404
+ const hostRef = useRef((host) => ref.current(host), _h$15).current;
1623
2405
  useLayoutEffect(() => () => {
2406
+ node.stop();
2407
+ current.dragOff?.();
2408
+ current.gestureOff?.();
1624
2409
  current.controller.destroy();
1625
2410
  current.host = void 0;
1626
2411
  current.adapter = void 0;
1627
- }, [], _h$13);
2412
+ }, [], _h$16);
1628
2413
  useLayoutEffect(() => {
1629
2414
  const off = [];
1630
2415
  for (const key of boundKeys.current) if (!previous.current.some(([next]) => key === next)) current.controller.set(key, defaults[key]);
@@ -1635,44 +2420,106 @@ function useMotion(props) {
1635
2420
  }
1636
2421
  firstBinding.current = false;
1637
2422
  return () => off.forEach((unsubscribe) => unsubscribe());
1638
- }, [previous.current], _h$14);
2423
+ }, [previous.current], _h$17);
2424
+ const configureDrag = () => {
2425
+ current.dragOff?.();
2426
+ current.dragOff = void 0;
2427
+ if (!current.adapter) return;
2428
+ current.dragOff = attachDrag(current.adapter, current.controller, latest.current, (axis) => {
2429
+ const binding = previous.current.find(([key]) => key === axis)?.[1];
2430
+ return binding instanceof MotionValue ? binding : current.controller.value(axis);
2431
+ }, () => playback.current.reduced, () => latest.current);
2432
+ };
1639
2433
  useLayoutEffect(() => {
1640
- let active = true;
1641
- const skip = firstAnimation.current && props.initial === false;
2434
+ configureDrag();
2435
+ }, [
2436
+ props.drag,
2437
+ JSON.stringify(props.dragConstraints),
2438
+ props.dragElastic,
2439
+ props.dragMomentum,
2440
+ previous.current
2441
+ ], _h$18);
2442
+ useLayoutEffect(() => {
2443
+ const skip = firstAnimation.current && initialDefinition === false;
1642
2444
  firstAnimation.current = false;
1643
2445
  if (presence && !presence.present) presence.restart(member.current);
1644
- else if (props.animate && !skip) current.controller.animate(props.animate, transition, reduced).then((result) => {
1645
- if (active && result === "finished") latest.current.onAnimationComplete?.();
1646
- });
2446
+ else if (animateDefinition !== void 0 && !skip) {
2447
+ if (!inherits) node.run(animateDefinition);
2448
+ else {
2449
+ const label = JSON.stringify(animateDefinition);
2450
+ const resolved = JSON.stringify({
2451
+ resolvedAnimate,
2452
+ transition,
2453
+ reduced
2454
+ });
2455
+ const previousRun = inheritedRun.current;
2456
+ if (previousRun?.label === label && previousRun.resolved !== resolved) {
2457
+ const parentTiming = parentVariant.resolve(animateDefinition).transition;
2458
+ node.run(animateDefinition, (parentTiming.delayChildren ?? 0) + [...parentVariant.children].indexOf(node) * (parentTiming.staggerChildren ?? 0));
2459
+ }
2460
+ inheritedRun.current = {
2461
+ label,
2462
+ resolved
2463
+ };
2464
+ }
2465
+ }
1647
2466
  return () => {
1648
- active = false;
1649
- current.controller.stop();
2467
+ if (!inherits) node.stop();
1650
2468
  };
1651
2469
  }, [
1652
- JSON.stringify(props.animate),
1653
- JSON.stringify(props.exit),
2470
+ JSON.stringify(resolvedAnimate),
2471
+ JSON.stringify(resolvedExit),
2472
+ JSON.stringify(animateDefinition),
1654
2473
  JSON.stringify(transition),
1655
- reduced
1656
- ], _h$15);
1657
- const { initial, exit, animate, transition: ignoredTransition, onAnimationComplete, style: ignoredStyle, bind: ignoredBind, ...rest } = props;
2474
+ reduced,
2475
+ inherits
2476
+ ], _h$19);
2477
+ const { drag, dragConstraints, dragElastic, dragMomentum, onDragStart, onDrag, onDragEnd, variants, custom, initial, exit, animate, transition: ignoredTransition, whileTap, whileTapTransition, whileFocus, whileFocusTransition, onAnimationStart, onAnimationComplete, onUpdate, style: ignoredStyle, ref: ignoredRef, ...rest } = props;
1658
2478
  return {
1659
- ...rest,
1660
- style,
1661
- bind: stableBind
2479
+ hostProps: {
2480
+ ...rest,
2481
+ style,
2482
+ ref: [hostRef, props.ref]
2483
+ },
2484
+ node
2485
+ };
2486
+ }
2487
+ var MotionLeaf = defineUniversalComponent("nativescript", function MotionLeaf(props) {
2488
+ const Component = props.component;
2489
+ const { hostProps, node } = withSlot(_h$20, useMotion, props);
2490
+ return universalValue(__octaneUniversalPlan1, [((__octaneContextProps) => universalContext(VariantContext, __octaneContextProps.value, universalChildren("nativescript", () => {
2491
+ return universalValue(__octaneUniversalPlan0, [universalComponent("nativescript", Component, universalProps([["spread", hostProps]], props.children))]);
2492
+ })))({ "value": node })]);
2493
+ }, { "module": "@nativescript-community/octane" });
2494
+ function createMotion(Component) {
2495
+ return function MotionCreated(props, __s, __extra) {
2496
+ return MotionLeaf({
2497
+ ...props,
2498
+ component: Component
2499
+ }, __s, __extra);
1662
2500
  };
1663
2501
  }
1664
2502
  var MotionView = defineUniversalComponent("nativescript", function MotionView(props) {
1665
- return universalValue(__octaneUniversalPlan0, [universalComponent("nativescript", View, universalProps([["spread", withSlot(_h$16, useMotion, props)]], props.children))]);
2503
+ const { hostProps, node } = withSlot(_h$21, useMotion, props);
2504
+ return universalValue(__octaneUniversalPlan3, [((__octaneContextProps) => universalContext(VariantContext, __octaneContextProps.value, universalChildren("nativescript", () => {
2505
+ return universalValue(__octaneUniversalPlan2, [universalComponent("nativescript", View, universalProps([["spread", hostProps]], props.children))]);
2506
+ })))({ "value": node })]);
1666
2507
  }, { "module": "@nativescript-community/octane" });
1667
2508
  var MotionRow = defineUniversalComponent("nativescript", function MotionRow(props) {
1668
- return universalValue(__octaneUniversalPlan1, [universalComponent("nativescript", Row, universalProps([["spread", withSlot(_h$17, useMotion, props)]], props.children))]);
2509
+ const { hostProps, node } = withSlot(_h$22, useMotion, props);
2510
+ return universalValue(__octaneUniversalPlan5, [((__octaneContextProps) => universalContext(VariantContext, __octaneContextProps.value, universalChildren("nativescript", () => {
2511
+ return universalValue(__octaneUniversalPlan4, [universalComponent("nativescript", HStack, universalProps([["spread", hostProps]], props.children))]);
2512
+ })))({ "value": node })]);
1669
2513
  }, { "module": "@nativescript-community/octane" });
1670
2514
  var MotionPressable = defineUniversalComponent("nativescript", function MotionPressable(props) {
1671
- return universalValue(__octaneUniversalPlan2, [universalComponent("nativescript", Pressable, universalProps([["spread", withSlot(_h$18, useMotion, props)]], props.children))]);
2515
+ const { hostProps, node } = withSlot(_h$23, useMotion, props);
2516
+ return universalValue(__octaneUniversalPlan7, [((__octaneContextProps) => universalContext(VariantContext, __octaneContextProps.value, universalChildren("nativescript", () => {
2517
+ return universalValue(__octaneUniversalPlan6, [universalComponent("nativescript", Pressable, universalProps([["spread", hostProps]], props.children))]);
2518
+ })))({ "value": node })]);
1672
2519
  }, { "module": "@nativescript-community/octane" });
1673
2520
  //#endregion
1674
2521
  //#region src/hooks.tsrx
1675
- var _hs$ = /* @__PURE__ */ hookSlots(8);
2522
+ var _hs$ = /* @__PURE__ */ hookSlots(12);
1676
2523
  var _h$0 = /* @__PURE__ */ Symbol(_hs$);
1677
2524
  var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
1678
2525
  var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
@@ -1681,6 +2528,10 @@ var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
1681
2528
  var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
1682
2529
  var _h$6 = /* @__PURE__ */ Symbol(_hs$ + 6);
1683
2530
  var _h$7 = /* @__PURE__ */ Symbol(_hs$ + 7);
2531
+ var _h$8 = /* @__PURE__ */ Symbol(_hs$ + 8);
2532
+ var _h$9 = /* @__PURE__ */ Symbol(_hs$ + 9);
2533
+ var _h$10 = /* @__PURE__ */ Symbol(_hs$ + 10);
2534
+ var _h$11 = /* @__PURE__ */ Symbol(_hs$ + 11);
1684
2535
  function useMotionValue(initial) {
1685
2536
  const ref = useRef(null, _h$0);
1686
2537
  if (!ref.current) ref.current = new MotionValue(initial, clock);
@@ -1735,13 +2586,30 @@ function useSpring(source, options = {}) {
1735
2586
  }, [source instanceof MotionValue ? source : null, JSON.stringify(options)], _h$7);
1736
2587
  return value;
1737
2588
  }
2589
+ function useAnimate() {
2590
+ const scope = useRef(null, _h$8);
2591
+ const config = useContext(ConfigContext);
2592
+ const systemReduced = withSlot(_h$9, useReducedMotion);
2593
+ const reducedRef = useRef(config.reducedMotion === "always" || config.reducedMotion === "user" && systemReduced, _h$10);
2594
+ reducedRef.current = config.reducedMotion === "always" || config.reducedMotion === "user" && systemReduced;
2595
+ return [scope, useRef((target, transitionOrTo, transition) => {
2596
+ if (target instanceof MotionValue) return target.animate(transitionOrTo, transition);
2597
+ const controller = target && typeof target === "object" && controllers.has(target) ? controllers.get(target) : scope.current ? controllers.get(scope.current) : void 0;
2598
+ if (!controller) return Promise.resolve("cancelled");
2599
+ const destination = controllers.has(target) ? transitionOrTo ?? {} : target;
2600
+ const spec = controllers.has(target) ? transition ?? {} : transitionOrTo ?? {};
2601
+ return controller.animate(destination, spec, reducedRef.current);
2602
+ }, _h$11).current];
2603
+ }
1738
2604
  //#endregion
1739
2605
  //#region src/index.ts
1740
- /** Bounded motion hosts for shared xplat UI primitives. */
2606
+ /** Bounded motion hosts for shared xplat UI primitives; `motion.create` wraps
2607
+ * any component that accepts `ref`/`style`/`children`. */
1741
2608
  var motion = {
1742
2609
  View: MotionView,
1743
2610
  Row: MotionRow,
1744
- Pressable: MotionPressable
2611
+ Pressable: MotionPressable,
2612
+ create: createMotion
1745
2613
  };
1746
2614
  //#endregion
1747
- export { MotionConfig, Presence, motion, useMotionValue, useMotionValueEvent, useReducedMotion, useSpring, useTransform };
2615
+ export { MotionConfig, Presence, motion, useAnimate, useMotionValue, useMotionValueEvent, useReducedMotion, useSpring, useTransform };