phase 0.0.12 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -176,7 +176,7 @@ The main primitive. Composes a ticker, visibility observer, and reduced-motion l
176
176
  import { createLoop } from 'phase';
177
177
 
178
178
  const loop = createLoop({
179
- element: el,
179
+ target: el,
180
180
  onTick: (frame) => {
181
181
  // frame.time — browser rAF timestamp
182
182
  // frame.delta — ms since last tick (clamped to 40ms)
@@ -229,7 +229,7 @@ Controls the loop's response when quality degrades. Same three-value pattern as
229
229
 
230
230
  ```ts
231
231
  createLoop({
232
- element: el,
232
+ target: el,
233
233
  onTick: draw,
234
234
  degraded: 'throttle', // default
235
235
  degradedFps: 20, // only accepted when degraded is 'throttle'
@@ -240,7 +240,7 @@ createLoop({
240
240
 
241
241
  | Option | Type | Default | Description |
242
242
  | --------------- | ----------------------------------- | ------------ | ----------------------------------------- |
243
- | `element` | `Element` | required | Element to observe for visibility |
243
+ | `target` | `Element` | required | Element to observe for visibility |
244
244
  | `onTick` | `(frame: FrameState) => void` | required | Called each frame while running |
245
245
  | `fps` | `number` | — | Cap frames per second |
246
246
  | `reducedMotion` | `'pause' \| 'complete' \| 'ignore'` | `'pause'` | Behavior when user prefers reduced motion |
@@ -283,7 +283,7 @@ Answers one question: is this element visible right now? Combines `document.visi
283
283
  import { createSight } from 'phase';
284
284
 
285
285
  const sight = createSight({
286
- element: el,
286
+ target: el,
287
287
  onPhaseChange: (phase, reason) => {
288
288
  // phase: 'visible' | 'hidden' | 'unknown'
289
289
  // reason: 'initial' | 'viewport' | 'document' | 'bfcache' | 'all-hidden'
@@ -303,7 +303,7 @@ Use `createLifecycle` when you own your render loop (a three.js/WebGL renderer,
303
303
  import { createLifecycle } from 'phase';
304
304
 
305
305
  const lifecycle = createLifecycle({
306
- element: canvas,
306
+ target: canvas,
307
307
  onPhaseChange: (phase, reason) => {
308
308
  // phase: 'idle' | 'active' | 'paused' | 'stopped'
309
309
  // reason: 'started' | 'resumed' | 'sight' | 'reduced-motion' | 'manual' | 'disposed' | 'initial'
@@ -343,7 +343,7 @@ Reports what fraction of an element is currently visible in the viewport (0–1)
343
343
  import { createScrollProgress } from 'phase';
344
344
 
345
345
  const progress = createScrollProgress({
346
- element: el,
346
+ target: el,
347
347
  onProgress: (ratio) => {
348
348
  el.style.opacity = String(ratio);
349
349
  },
@@ -361,7 +361,7 @@ The `steps` option controls threshold granularity. Default `20` generates 21 eve
361
361
 
362
362
  | Option | Type | Default | Description |
363
363
  | ------------ | ----------------------------- | -------- | ---------------------------------- |
364
- | `element` | `Element` | required | Element to observe |
364
+ | `target` | `Element` | required | Element to observe |
365
365
  | `onProgress` | `(ratio: number) => void` | required | Called at each threshold crossing |
366
366
  | `steps` | `number` | `20` | Number of evenly-spaced thresholds |
367
367
  | `root` | `Element \| Document \| null` | — | IO root element |
@@ -377,7 +377,7 @@ Tracks a scroll container's offset and progress. Reads `scrollLeft`/`scrollTop`
377
377
  import { createScroll } from 'phase';
378
378
 
379
379
  const scroll = createScroll({
380
- element: viewport,
380
+ target: viewport,
381
381
  onScroll: (s) => {
382
382
  // thumb CSS needs `transform-origin: left` so scaleX anchors to the track start
383
383
  thumb.style.transform = `translateX(${s.progressX * (1 - s.visibleX) * 100}%) scaleX(${s.visibleX})`;
@@ -401,7 +401,7 @@ scroll.stop();
401
401
 
402
402
  | Option | Type | Default | Description |
403
403
  | --------------------- | ------------------------------ | --------- | -------------------------------------------------- |
404
- | `element` | `Element` | required | Scroll container to track |
404
+ | `target` | `Element` | required | Scroll container to track |
405
405
  | `onScroll` | `(state: ScrollState) => void` | required | Called once per rAF frame with position + progress |
406
406
  | `onPhaseChange` | `(phase, reason) => void` | — | Called on phase transitions |
407
407
  | `visibility` | `'pause' \| 'ignore'` | `'pause'` | Pause tracking when off-screen, or ignore |
@@ -481,7 +481,7 @@ Reports whether the browser is rendering an element or skipping it under `conten
481
481
  import { createRenderState } from 'phase';
482
482
 
483
483
  const renderState = createRenderState({
484
- element: el,
484
+ target: el,
485
485
  onPhaseChange: (phase) => {
486
486
  if (phase === 'skipped') clock.pause();
487
487
  else clock.resume();
@@ -518,7 +518,7 @@ A lifecycle-aware `MutationObserver`: records are coalesced into one callback pe
518
518
  import { createMutation } from 'phase';
519
519
 
520
520
  const mutation = createMutation({
521
- element: list,
521
+ target: list,
522
522
  mutation: { childList: true },
523
523
  onMutations: (records) => syncItems(records),
524
524
  });
@@ -536,7 +536,7 @@ Tracks pointer position relative to an element, batching high-frequency events i
536
536
  import { createPointer } from 'phase';
537
537
 
538
538
  const pointer = createPointer({
539
- element: surface,
539
+ target: surface,
540
540
  onPointer: (state) => {
541
541
  cursor.style.transform = `translate(${state.x}px, ${state.y}px)`;
542
542
  },
@@ -741,12 +741,12 @@ Animates a number from A to B over a duration. Calls `setState` per frame (appro
741
741
  ```tsx
742
742
  import { useTween } from 'phase/react';
743
743
 
744
- const opacity = useTween({ target: isVisible ? 1 : 0, duration: 300 });
744
+ const opacity = useTween({ to: isVisible ? 1 : 0, duration: 300 });
745
745
  ```
746
746
 
747
747
  Use `useTween` for single values where the render is cheap (counters, progress bars, opacity). Use `useLoop` when animating many elements or doing canvas work, since per-frame `setState` doesn't scale.
748
748
 
749
- Reduced motion default: `'complete'` (jumps to target instantly). The value still reaches its destination; it skips the animation.
749
+ Reduced motion default: `'complete'` (jumps to the destination instantly). The value still reaches its destination; it skips the animation.
750
750
 
751
751
  ### usePresence
752
752
 
@@ -1209,13 +1209,13 @@ Every error includes a machine-readable `code` and an actionable message.
1209
1209
  import { PhaseError, isPhaseError } from 'phase';
1210
1210
  ```
1211
1211
 
1212
- | Code | Trigger |
1213
- | ------------------ | ---------------------------------------------------- |
1214
- | `server_context` | Calling a browser-only primitive during SSR |
1215
- | `no_element` | Passing a null or undefined `element` to a primitive |
1216
- | `invalid_duration` | `useTween` duration is zero, negative, or NaN |
1217
- | `ticker_stopped` | Calling `start`/`resume` on a stopped ticker |
1218
- | `missing_context` | `<Swap.State>` used outside `<Swap>` |
1212
+ | Code | Trigger |
1213
+ | ------------------ | --------------------------------------------------- |
1214
+ | `server_context` | Calling a browser-only primitive during SSR |
1215
+ | `no_target` | Passing a null or undefined `target` to a primitive |
1216
+ | `invalid_duration` | `useTween` duration is zero, negative, or NaN |
1217
+ | `ticker_stopped` | Calling `start`/`resume` on a stopped ticker |
1218
+ | `missing_context` | `<Swap.State>` used outside `<Swap>` |
1219
1219
 
1220
1220
  ## Relationship to View Transitions
1221
1221
 
@@ -1232,18 +1232,18 @@ Minimal footprint is a core promise (see [Why phase](#why-phase)). Every export
1232
1232
  | Export | Size (min+brotli) |
1233
1233
  | ------------------------- | ----------------: |
1234
1234
  | **Core** | |
1235
- | `createTicker` | 846 B |
1236
- | `createSight` | 967 B |
1235
+ | `createTicker` | 847 B |
1236
+ | `createSight` | 970 B |
1237
1237
  | `createLifecycle` | 1.48 kB |
1238
- | `createLoop` | 2.62 kB |
1239
- | `createScrollProgress` | 866 B |
1240
- | `createRenderState` | 495 B |
1238
+ | `createLoop` | 2.6 kB |
1239
+ | `createScrollProgress` | 865 B |
1240
+ | `createRenderState` | 490 B |
1241
1241
  | `createDevicePixelRatio` | 544 B |
1242
- | `createMutation` | 1.18 kB |
1243
- | `createPointer` | 1.27 kB |
1244
- | `createScroll` | 1.45 kB |
1245
- | `createThrottle` | 657 B |
1246
- | `createDebounce` | 559 B |
1242
+ | `createMutation` | 1.17 kB |
1243
+ | `createPointer` | 1.26 kB |
1244
+ | `createScroll` | 1.46 kB |
1245
+ | `createThrottle` | 659 B |
1246
+ | `createDebounce` | 557 B |
1247
1247
  | `whenIdle` | 409 B |
1248
1248
  | `prefersReducedMotion` | 101 B |
1249
1249
  | **Ease** | |
@@ -1253,14 +1253,14 @@ Minimal footprint is a core promise (see [Why phase](#why-phase)). Every export
1253
1253
  | `useLifecycle` | 1.68 kB |
1254
1254
  | `useSight` | 1.18 kB |
1255
1255
  | `useCanvas` | 3.47 kB |
1256
- | `useMutation` | 1.36 kB |
1257
- | `usePointer` | 1.47 kB |
1256
+ | `useMutation` | 1.37 kB |
1257
+ | `usePointer` | 1.48 kB |
1258
1258
  | `useScroll` | 1.72 kB |
1259
1259
  | `useThrottledCallback` | 797 B |
1260
- | `useDebouncedCallback` | 688 B |
1260
+ | `useDebouncedCallback` | 687 B |
1261
1261
  | `useTween` | 655 B |
1262
1262
  | `usePresence` | 591 B |
1263
- | `useScrollProgress` | 997 B |
1263
+ | `useScrollProgress` | 999 B |
1264
1264
  | `useSize` | 378 B |
1265
1265
  | `useContainerQuery` | 384 B |
1266
1266
  | `useMediaQuery` | 246 B |
@@ -1269,13 +1269,13 @@ Minimal footprint is a core promise (see [Why phase](#why-phase)). Every export
1269
1269
  | `useSyncedRef` | 22 B |
1270
1270
  | `useStableCallback` | 39 B |
1271
1271
  | `Presence` | 741 B |
1272
- | `WhenVisible` | 1.44 kB |
1272
+ | `WhenVisible` | 1.43 kB |
1273
1273
  | `WhenIdle` | 593 B |
1274
1274
  | `Defer` | 86 B |
1275
1275
  | `useIdle` | 435 B |
1276
- | `useWhenIdle` | 445 B |
1277
- | `useRenderState` | 527 B |
1278
- | `Swap` | 1.13 kB |
1276
+ | `useWhenIdle` | 446 B |
1277
+ | `useRenderState` | 521 B |
1278
+ | `Swap` | 1.12 kB |
1279
1279
 
1280
1280
  <!-- SIZE-TABLE:END -->
1281
1281
 
@@ -49,10 +49,10 @@ function serverContextError(fn) {
49
49
  fix: "Move into a useEffect or client-only module."
50
50
  });
51
51
  }
52
- function noElementError(fn) {
53
- throw new PhaseError(`${fn}() requires a DOM element.`, {
54
- code: "no_element",
55
- reason: "The element was null or undefined.",
52
+ function noTargetError(fn) {
53
+ throw new PhaseError(`${fn}() requires a target.`, {
54
+ code: "no_target",
55
+ reason: "The target was null or undefined.",
56
56
  fix: "Pass a mounted Element, or use the React hook which manages the ref."
57
57
  });
58
58
  }
@@ -280,7 +280,7 @@ const createPoolEntry$1 = (options) => {
280
280
  *
281
281
  * @example
282
282
  * const sight = createSight({
283
- * element: el,
283
+ * target: el,
284
284
  * onPhaseChange: (phase) => phase === 'visible' ? loop.start() : loop.pause(),
285
285
  * });
286
286
  * // cleanup:
@@ -291,8 +291,8 @@ const createPoolEntry$1 = (options) => {
291
291
  */
292
292
  function createSight(options) {
293
293
  if (typeof document === "undefined") serverContextError("createSight");
294
- const { element, intersectionOptions, onPhaseChange, signal } = options;
295
- if (!element) noElementError("createSight");
294
+ const { target, intersectionOptions, onPhaseChange, signal } = options;
295
+ if (!target) noTargetError("createSight");
296
296
  let _phase = "unknown";
297
297
  let _reason = "initial";
298
298
  let stopped = false;
@@ -323,7 +323,7 @@ function createSight(options) {
323
323
  document.addEventListener("visibilitychange", onVisibilityChange);
324
324
  window.addEventListener("pageshow", onPageShow);
325
325
  const unobserveIO = observeIntersection({
326
- element,
326
+ element: target,
327
327
  onIntersect: onIntersection,
328
328
  ...intersectionOptions
329
329
  });
@@ -416,7 +416,7 @@ const REDUCED_MOTION_QUERY$1 = "(prefers-reduced-motion: reduce)";
416
416
  *
417
417
  * @example
418
418
  * const lifecycle = createLifecycle({
419
- * element: canvas,
419
+ * target: canvas,
420
420
  * onPhaseChange: (phase) => {
421
421
  * if (phase === 'active') renderer.start();
422
422
  * else renderer.stop();
@@ -427,8 +427,8 @@ const REDUCED_MOTION_QUERY$1 = "(prefers-reduced-motion: reduce)";
427
427
  */
428
428
  function createLifecycle(options) {
429
429
  if (typeof document === "undefined") serverContextError("createLifecycle");
430
- const { element, reducedMotion = "pause", intersectionOptions, start: startMode = "auto", onPhaseChange, signal } = options;
431
- if (!element) noElementError("createLifecycle");
430
+ const { target, reducedMotion = "pause", intersectionOptions, start: startMode = "auto", onPhaseChange, signal } = options;
431
+ if (!target) noTargetError("createLifecycle");
432
432
  let _phase = "idle";
433
433
  let _reason = "initial";
434
434
  let sightVisible = false;
@@ -468,7 +468,7 @@ function createLifecycle(options) {
468
468
  reconcile();
469
469
  }
470
470
  const sight = createSight({
471
- element,
471
+ target,
472
472
  intersectionOptions,
473
473
  onPhaseChange: onSightChange
474
474
  });
@@ -545,7 +545,7 @@ const RECOVERY_RETRY_MS = 2e3;
545
545
  *
546
546
  * @example
547
547
  * const loop = createLoop({
548
- * element: el,
548
+ * target: el,
549
549
  * onTick: (frame) => draw(ctx, frame),
550
550
  * });
551
551
  * // cleanup:
@@ -553,8 +553,8 @@ const RECOVERY_RETRY_MS = 2e3;
553
553
  */
554
554
  function createLoop(options) {
555
555
  if (typeof requestAnimationFrame === "undefined") serverContextError("createLoop");
556
- const { element, onTick, fps: baseFps, reducedMotion = "pause", degraded = "throttle", degradedFps: configuredDegradedFps, intersectionOptions, start: startMode = "auto", onPhaseChange, signal } = options;
557
- if (!element) noElementError("createLoop");
556
+ const { target, onTick, fps: baseFps, reducedMotion = "pause", degraded = "throttle", degradedFps: configuredDegradedFps, intersectionOptions, start: startMode = "auto", onPhaseChange, signal } = options;
557
+ if (!target) noTargetError("createLoop");
558
558
  const degradedFps = degraded === "throttle" ? configuredDegradedFps : void 0;
559
559
  let _phase = "idle";
560
560
  let _reason = "initial";
@@ -675,7 +675,7 @@ function createLoop(options) {
675
675
  reconcileQuality();
676
676
  }
677
677
  const lifecycle = createLifecycle({
678
- element,
678
+ target,
679
679
  reducedMotion: reducedMotion === "pause" ? "pause" : "ignore",
680
680
  intersectionOptions,
681
681
  start: "manual",
@@ -745,7 +745,7 @@ function buildThresholds(steps) {
745
745
  *
746
746
  * @example
747
747
  * const progress = createScrollProgress({
748
- * element: el,
748
+ * target: el,
749
749
  * onProgress: (ratio) => {
750
750
  * el.style.opacity = String(ratio);
751
751
  * },
@@ -755,8 +755,8 @@ function buildThresholds(steps) {
755
755
  */
756
756
  function createScrollProgress(options) {
757
757
  if (typeof IntersectionObserver === "undefined") serverContextError("createScrollProgress");
758
- const { element, onProgress, steps = DEFAULT_STEPS, root, rootMargin, signal } = options;
759
- if (!element) noElementError("createScrollProgress");
758
+ const { target: element, onProgress, steps = DEFAULT_STEPS, root, rootMargin, signal } = options;
759
+ if (!element) noTargetError("createScrollProgress");
760
760
  let _ratio = 0;
761
761
  let stopped = false;
762
762
  const unobserve = observeIntersection({
@@ -802,7 +802,7 @@ function createScrollProgress(options) {
802
802
  *
803
803
  * @example
804
804
  * const render = createRenderState({
805
- * element: el,
805
+ * target: el,
806
806
  * onPhaseChange: (phase) => phase === 'skipped' ? clock.pause() : clock.resume(),
807
807
  * });
808
808
  * // cleanup:
@@ -817,8 +817,8 @@ function createScrollProgress(options) {
817
817
  */
818
818
  function createRenderState(options) {
819
819
  if (typeof document === "undefined") serverContextError("createRenderState");
820
- const { element, onPhaseChange, signal } = options;
821
- if (!element) noElementError("createRenderState");
820
+ const { target: element, onPhaseChange, signal } = options;
821
+ if (!element) noTargetError("createRenderState");
822
822
  let _phase = "rendered";
823
823
  let stopped = false;
824
824
  function onStateChange(event) {
@@ -947,8 +947,8 @@ function prefersReducedMotion() {
947
947
  /** Lifecycle-aware MutationObserver with rAF-coalesced callbacks. */
948
948
  function createMutation(options) {
949
949
  if (typeof document === "undefined") serverContextError("createMutation");
950
- const { element, mutation: mutationInit, onMutations, onPhaseChange, visibility = "pause", intersectionOptions, signal } = options;
951
- if (!element) noElementError("createMutation");
950
+ const { target: element, mutation: mutationInit, onMutations, onPhaseChange, visibility = "pause", intersectionOptions, signal } = options;
951
+ if (!element) noTargetError("createMutation");
952
952
  warnReflowStorm(mutationInit);
953
953
  let _phase = "paused";
954
954
  let _reason = "initial";
@@ -1065,8 +1065,8 @@ function warnReflowStorm(opts) {
1065
1065
  /** Lifecycle-aware pointer tracker with rAF-batched `getBoundingClientRect`. */
1066
1066
  function createPointer(options) {
1067
1067
  if (typeof document === "undefined") serverContextError("createPointer");
1068
- const { element, onPointer, onPhaseChange, visibility = "pause", intersectionOptions, signal } = options;
1069
- if (!element) noElementError("createPointer");
1068
+ const { target: element, onPointer, onPhaseChange, visibility = "pause", intersectionOptions, signal } = options;
1069
+ if (!element) noTargetError("createPointer");
1070
1070
  let _phase = "idle";
1071
1071
  let _reason = "initial";
1072
1072
  let stopped = false;
@@ -1254,8 +1254,8 @@ function getObserver() {
1254
1254
  */
1255
1255
  function createScroll(options) {
1256
1256
  if (typeof document === "undefined") serverContextError("createScroll");
1257
- const { element, onScroll, onPhaseChange, visibility = "pause", intersectionOptions, signal } = options;
1258
- if (!element) noElementError("createScroll");
1257
+ const { target, onScroll, onPhaseChange, visibility = "pause", intersectionOptions, signal } = options;
1258
+ if (!target) noTargetError("createScroll");
1259
1259
  let _phase = "paused";
1260
1260
  let _reason = "initial";
1261
1261
  let stopped = false;
@@ -1281,8 +1281,8 @@ function createScroll(options) {
1281
1281
  }
1282
1282
  function computePosition() {
1283
1283
  const { maxX, maxY } = _state;
1284
- const x = element.scrollLeft;
1285
- const y = element.scrollTop;
1284
+ const x = target.scrollLeft;
1285
+ const y = target.scrollTop;
1286
1286
  _state.x = x < 0 ? 0 : x > maxX ? maxX : x;
1287
1287
  _state.y = y < 0 ? 0 : y > maxY ? maxY : y;
1288
1288
  _state.progressX = maxX > 0 ? _state.x / maxX : 0;
@@ -1290,10 +1290,10 @@ function createScroll(options) {
1290
1290
  }
1291
1291
  function measure() {
1292
1292
  if (stopped || !listenersAttached) return;
1293
- const scrollWidth = element.scrollWidth;
1294
- const clientWidth = element.clientWidth;
1295
- const scrollHeight = element.scrollHeight;
1296
- const clientHeight = element.clientHeight;
1293
+ const scrollWidth = target.scrollWidth;
1294
+ const clientWidth = target.clientWidth;
1295
+ const scrollHeight = target.scrollHeight;
1296
+ const clientHeight = target.clientHeight;
1297
1297
  const maxX = scrollWidth - clientWidth;
1298
1298
  const maxY = scrollHeight - clientHeight;
1299
1299
  _state.maxX = maxX > 0 ? maxX : 0;
@@ -1333,14 +1333,14 @@ function createScroll(options) {
1333
1333
  function attachListeners() {
1334
1334
  if (listenersAttached) return;
1335
1335
  listenersAttached = true;
1336
- element.addEventListener("scroll", onScrollEvent, { passive: true });
1337
- unobserveRO = observeResize(element, onROResize);
1336
+ target.addEventListener("scroll", onScrollEvent, { passive: true });
1337
+ unobserveRO = observeResize(target, onROResize);
1338
1338
  measure();
1339
1339
  }
1340
1340
  function detachListeners() {
1341
1341
  if (!listenersAttached) return;
1342
1342
  listenersAttached = false;
1343
- element.removeEventListener("scroll", onScrollEvent);
1343
+ target.removeEventListener("scroll", onScrollEvent);
1344
1344
  unobserveRO?.();
1345
1345
  unobserveRO = void 0;
1346
1346
  cancelFlush();
@@ -1372,7 +1372,7 @@ function createScroll(options) {
1372
1372
  document.addEventListener("visibilitychange", onVisChange);
1373
1373
  window.addEventListener("pageshow", onPageShow);
1374
1374
  const unobserveIO = observeIntersection({
1375
- element,
1375
+ element: target,
1376
1376
  onIntersect: (entry) => {
1377
1377
  elementInView = entry.isIntersecting;
1378
1378
  recompute();
@@ -1618,4 +1618,4 @@ function createDebounce(options) {
1618
1618
  //#endregion
1619
1619
  export { missingContextError as C, isPhaseError as S, linkAbortSignal as T, subscribeMediaQuery as _, createPointer as a, PhaseError as b, prefersReducedMotion as c, subscribeDpr as d, createRenderState as f, readMediaQuery as g, createLifecycle as h, observeResize as i, whenIdle as l, createLoop as m, createThrottle as n, createMutation as o, createScrollProgress as p, createScroll as r, REDUCED_MOTION_QUERY as s, createDebounce as t, readDpr as u, createSight as v, serverContextError as w, invalidDurationError as x, createTicker as y };
1620
1620
 
1621
- //# sourceMappingURL=debounce-C0SgiTfm.js.map
1621
+ //# sourceMappingURL=debounce-BX3NrBak.js.map