upwwwards 0.1.6-beta.1 → 0.1.8-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,13 +8,14 @@ declare namespace CallStack {
8
8
  type CALLBACK = (...args: any) => void;
9
9
  }
10
10
  /**
11
- * Callstack: an ordered list of synchronous callbacks that are invoked together, in insertion order,
12
- * with the same arguments (similar to a lightweight event emitter for a single event).
11
+ * Ordered list of synchronous callbacks invoked together, in insertion order, with the same arguments
12
+ * (a lightweight single-event emitter).
13
13
  *
14
- * Callbacks run either all at once with `loop`, or one at a time with `gen`, which returns a generator
15
- * that pauses after each callback (used by the frame scheduler `Thread` to spread work across frames).
16
- * Callbacks may be added or removed while the stack is being iterated: removals are applied immediately
17
- * without skipping the next callback, and additions only run from the next iteration.
14
+ * Run them all at once with {@link CallStack.loop}, or one-by-one with {@link CallStack.gen} (yields after each
15
+ * callback so a caller such as `Up.Thread` can spread work across frames).
16
+ * You may {@link CallStack.add}, {@link CallStack.remove}, or {@link CallStack.clear} while an iteration is running:
17
+ * removals apply immediately without skipping the next callback, additions run from the next iteration,
18
+ * and `clear` skips any callback that has not run yet.
18
19
  *
19
20
  * If a callback throws, the error is logged with `Logs.error` and the remaining callbacks of that
20
21
  * iteration are skipped; the error is not rethrown.
@@ -93,6 +94,12 @@ declare class CallStack<T extends CallStack.CALLBACK> {
93
94
  * ```
94
95
  */
95
96
  gen(...args: Parameters<T>): Thread.GENERATOR;
97
+ /**
98
+ * Removes every callback and resets iteration counters (`length` becomes `0`).
99
+ * Safe to call during {@link CallStack.loop} / {@link CallStack.gen}: callbacks that have not run yet are skipped.
100
+ * When no iteration is in progress, any outstanding generator created by {@link CallStack.gen} is closed.
101
+ */
102
+ clear(): void;
96
103
  }
97
104
 
98
105
  declare namespace Arrays {
@@ -294,16 +301,6 @@ declare const
294
301
  * Key of the method returning the active cache (browser map or request-scoped server map).
295
302
  */
296
303
  getCache: unique symbol;
297
- declare const /**
298
- * Key of the modifier refreshing viewport metrics.
299
- * @internal
300
- */
301
- updateScreenVars: unique symbol;
302
- declare const /**
303
- * Key of the modifier refreshing scroll metrics.
304
- * @internal
305
- */
306
- updateScrollVars: unique symbol;
307
304
  declare namespace App {
308
305
  /**
309
306
  * Operating system / device family detected from `navigator.userAgent`.
@@ -332,8 +329,9 @@ declare namespace App {
332
329
  /**
333
330
  * Application runtime: device and browser detection, viewport and scroll metrics, an application cache,
334
331
  * and a wait queue that delays the `LOAD` event (see {@link Event}).
335
- * @note Metrics are client-only. They are read once by `Up.set`, then refreshed by Thread modifiers before
336
- * `resize` (flag `8`) and `scroll` (flag `32`) tasks run.
332
+ * @note Metrics are client-only. They are read once by `Up.set`, then refreshed by Thread modifiers on
333
+ * `resize` (flag `8`) and `scroll` (flag `32`) when those flags are armed ({@link Thread.sub} / {@link Thread.arm}).
334
+ * With the Scroll plugin, Lenis arms the scroll flag after writing `window.scrollY`.
337
335
  */
338
336
  declare abstract class App {
339
337
  #private;
@@ -489,15 +487,16 @@ declare abstract class App {
489
487
  */
490
488
  static remove(key: App.CACHE_KEY): boolean;
491
489
  /**
492
- * Thread modifier refreshing viewport metrics (`w`, `cw`, `sw`, `lw`, `h`, `ch`, `sh`, `lh`).
493
- * @internal
490
+ * Refreshes viewport metrics (`w`, `cw`, `sw`, `lw`, `h`, `ch`, `sh`, `lh`).
491
+ * Registered as a Thread modifier on {@link Thread.RESIZE_FLAG}; call it yourself only if you need an immediate read outside that phase.
494
492
  */
495
- static [updateScreenVars](): void;
493
+ static updateScreenVars(): void;
496
494
  /**
497
- * Thread modifier refreshing scroll metrics (`y`, `dY`, `downwards`).
498
- * @internal
495
+ * Refreshes scroll metrics (`y`, `dY`, `downwards`) from `window.scrollY`.
496
+ * Registered as a Thread modifier on {@link Thread.RESIZE_FLAG} and {@link Thread.SCROLL_FLAG}; no-op when `y` is unchanged.
497
+ * Prefer letting Thread arm those flags (or the Scroll plugin) rather than calling this every frame by hand.
499
498
  */
500
- static [updateScrollVars](): void;
499
+ static updateScrollVars(): void;
501
500
  /**
502
501
  * Clears the application cache and pending wait queue. Called from `Up.destroy` in the browser.
503
502
  * @internal
@@ -518,15 +517,15 @@ declare namespace Thread {
518
517
  * Thread scheduler configuration, declared in `up.thread` of the Vite initializer.
519
518
  */
520
519
  type CONFIG = {
521
- /**
522
- * Soft time budget in ms per frame for flow (one-shot) tasks. Must be greater than `0`. Default: `8`.
523
- */
524
- flowBudget?: number;
525
520
  /**
526
521
  * Soft time budget in ms per frame for forever tasks. Must be greater than `0`. Default: `4`.
527
522
  * Forever tasks always run every frame; the budget only limits how long their generators may run.
528
523
  */
529
524
  foreverBudget?: number;
525
+ /**
526
+ * Soft time budget in ms per frame for flow (one-shot) tasks. Must be greater than `0`. Default: `8`.
527
+ */
528
+ flowBudget?: number;
530
529
  };
531
530
  /**
532
531
  * Flag of `EARTHQUAKE` event tasks (`2`).
@@ -553,10 +552,20 @@ declare namespace Thread {
553
552
  */
554
553
  type SYNTHRA_FLAG = 2_048;
555
554
  /**
556
- * Priority flag of a task or modifier: a power of 2 from `1` to `4096`.
557
- * Within a frame, lower flags run first; tasks sharing a flag run in insertion order.
555
+ * Default flag of {@link Thread.sub} when `flag` is omitted (`8192`).
556
+ * Runs before {@link Thread.MAX_FLAG} (end-of-frame work such as Anime `updateRoot`).
557
+ */
558
+ type DEFAULT_FLAG = 8_192;
559
+ /**
560
+ * Highest allowed priority flag (`32768`). Reserved for end-of-frame work (e.g. Anime advances GSAP here).
561
+ * Not the default of {@link Thread.sub} — see {@link Thread.DEFAULT_FLAG}.
558
562
  */
559
- type FLAG = 1 | EARTHQUAKE_FLAG | 4 | RESIZE_FLAG | 16 | SCROLL_FLAG | 64 | EVENT_FLAG | 256 | OBSERVER_FLAG | 1_024 | SYNTHRA_FLAG | 4_096;
563
+ type MAX_FLAG = 32_768;
564
+ /**
565
+ * Priority flag of a task or modifier: a power of 2 from `1` to {@link Thread.MAX_FLAG} (`32768`).
566
+ * Within a frame, lower flags run first; for each flag, forever tasks run before flow tasks.
567
+ */
568
+ type FLAG = 1 | EARTHQUAKE_FLAG | 4 | RESIZE_FLAG | 16 | SCROLL_FLAG | 64 | EVENT_FLAG | 256 | OBSERVER_FLAG | 1_024 | SYNTHRA_FLAG | 4_096 | DEFAULT_FLAG | 16_384 | MAX_FLAG;
560
569
  /**
561
570
  * Modifier callback. It is called without arguments and its return value is ignored.
562
571
  */
@@ -602,10 +611,12 @@ declare namespace Thread {
602
611
  ];
603
612
  }
604
613
  /**
605
- * Frame scheduler of the runtime. Each frame (`requestAnimationFrame`, or an external ticker such as GSAP) runs, in order:
606
- * modifiers, then flow tasks (run once, then removed), then forever tasks (run every frame).
607
- * Work is grouped by priority flags (see {@link Thread.FLAG}) and limited by a soft time budget ({@link Thread.CONFIG});
608
- * flow tasks that do not fit are postponed to the next frame, and generator tasks are resumed across frames.
614
+ * Frame scheduler of the runtime (`Up.Thread`). Each frame (`requestAnimationFrame`, or an external ticker such as GSAP)
615
+ * runs, in order: **drivers** → **modifiers** → **tasks by ascending flag** (forever then flow for each flag).
616
+ * Drivers (see {@link Thread.addDriver}) write frame inputs early (e.g. Lenis `raf`). Modifiers refresh shared data
617
+ * before tasks; arm them with {@link Thread.sub} or {@link Thread.arm}. Soft budgets ({@link Thread.CONFIG}) apply
618
+ * separately to forever and flow. Prefer `Up.Event.sub(['TICK', …])` for simple per-frame work; use Thread for
619
+ * budgets, priorities, drivers, or end-of-frame forever tasks on {@link Thread.MAX_FLAG}.
609
620
  * @note Client-only: the frame loop starts with `Up.set`. The Thread is paused while the page is hidden.
610
621
  */
611
622
  declare abstract class Thread {
@@ -632,10 +643,15 @@ declare abstract class Thread {
632
643
  */
633
644
  static get App(): typeof App;
634
645
  /**
635
- * Highest allowed flag, also the default flag of {@link Thread.sub}.
636
- * @returns `4096`.
646
+ * Default priority flag of {@link Thread.sub} when `flag` is omitted (`8192`).
647
+ * @returns `8192`.
637
648
  */
638
- static get MAX_FLAG(): Thread.FLAG;
649
+ static get DEFAULT_FLAG(): Thread.DEFAULT_FLAG;
650
+ /**
651
+ * Highest allowed priority flag (`32768`). Use for end-of-frame forever work (e.g. Anime `gsap.updateRoot`).
652
+ * @returns `32768`.
653
+ */
654
+ static get MAX_FLAG(): Thread.MAX_FLAG;
639
655
  /**
640
656
  * Timestamp (ms) of the last processed frame, in the clock of the frame driver:
641
657
  * `requestAnimationFrame` time by default, or the external ticker time when one is attached (e.g. GSAP ticker time × `1000`).
@@ -662,16 +678,32 @@ declare abstract class Thread {
662
678
  */
663
679
  static [set](): void;
664
680
  /**
665
- * Registers a modifier: a lightweight hook that refreshes shared data before tasks use it.
666
- * After {@link Thread.sub} is called with the same `flag`, the modifier runs once at the start of the next processed frame,
667
- * before any task (at most once per frame). Modifiers run by ascending flag, then in registration order.
668
- * Registering the same callback twice on the same flag logs a warning and does nothing.
681
+ * Registers a **driver**: a sync callback run at the **start** of every processed frame, before modifiers and tasks.
682
+ * Use it for external clocks that write frame inputs (e.g. Scroll registers Lenis `raf` here with `autoRaf: false`).
683
+ * Callbacks run in registration order with no arguments; prefer sync functions. The same reference can be
684
+ * registered only once (a duplicate logs a warning and is ignored).
685
+ * @param callback Function called at the start of the frame (reads {@link Thread.timestamp} if needed).
686
+ * @example
687
+ * ```ts
688
+ * const step = (): void => { lenis.raf(Up.Thread.timestamp) }
689
+ *
690
+ * Up.Thread.addDriver(step)
691
+ * // later
692
+ * Up.Thread.removeDriver(step)
693
+ * ```
694
+ */
695
+ static addDriver(callback: Thread.CALLBACK): void;
696
+ /**
697
+ * Registers a modifier: a lightweight hook that refreshes shared data before tasks of the same `flag`.
698
+ * After {@link Thread.sub} or {@link Thread.arm} with that flag, the modifier runs once in the modifiers phase
699
+ * of the next processed frame (at most once per frame), before forever/flow tasks. Modifiers run by ascending
700
+ * flag, then in registration order. Registering the same callback twice on the same flag logs a warning.
669
701
  * @note Keep modifiers cheap: they may run on many frames.
670
702
  * @note The `App` metric modifiers (on {@link Thread.RESIZE_FLAG} and {@link Thread.SCROLL_FLAG}) are registered first,
671
703
  * so they run before user modifiers of the same flag.
672
704
  * @param callback Function called without arguments.
673
- * @param flag Priority flag, a power of 2 from `1` to `4096`.
674
- * @throws If `flag` is not a power of 2 between `1` and `4096`.
705
+ * @param flag Priority flag, a power of 2 from `1` to {@link Thread.MAX_FLAG}.
706
+ * @throws If `flag` is not a power of 2 between `1` and {@link Thread.MAX_FLAG}.
675
707
  * @example
676
708
  * ```ts
677
709
  * let ratio = 1
@@ -680,15 +712,20 @@ declare abstract class Thread {
680
712
  * ```
681
713
  */
682
714
  static addModifier(callback: Thread.CALLBACK, flag: Thread.FLAG): void;
715
+ /**
716
+ * Unregisters a driver added with {@link Thread.addDriver}. Does nothing if it is not registered.
717
+ * @param callback The same function reference passed to `addDriver`.
718
+ */
719
+ static removeDriver(callback: Thread.CALLBACK): void;
683
720
  /**
684
721
  * Removes a modifier registered with {@link Thread.addModifier}. Does nothing if it is not registered on `flag`.
685
722
  * @param callback The same function reference passed to `addModifier`.
686
723
  * @param flag The flag used when adding the modifier.
687
- * @throws If `flag` is not a power of 2 between `1` and `4096`.
724
+ * @throws If `flag` is not a power of 2 between `1` and {@link Thread.MAX_FLAG}.
688
725
  */
689
726
  static removeModifier(callback: Thread.CALLBACK, flag: Thread.FLAG): void;
690
727
  /**
691
- * Clears all modifiers and tasks and stops the internal frame loop. Called from `Up.destroy` in the browser.
728
+ * Clears all modifiers, tasks, and drivers, and stops the internal frame loop. Called from `Up.destroy` in the browser.
692
729
  * @internal
693
730
  */
694
731
  static [destroy](): void;
@@ -722,9 +759,9 @@ declare abstract class Thread {
722
759
  * (the task stays scheduled until the promise settles).
723
760
  * @template T Task type, used to type `args`.
724
761
  * @param task The task function, identified by reference.
725
- * @param flag Priority flag, a power of 2 from `1` to `4096`. Default: {@link Thread.MAX_FLAG} (`4096`).
762
+ * @param flag Priority flag, a power of 2 from `1` to {@link Thread.MAX_FLAG}. Default: {@link Thread.DEFAULT_FLAG} (`8192`).
726
763
  * @param args Arguments passed to the task when it runs (the latest call wins).
727
- * @throws If `flag` is not a power of 2 between `1` and `4096`.
764
+ * @throws If `flag` is not a power of 2 between `1` and {@link Thread.MAX_FLAG} (`32768`).
728
765
  * @example
729
766
  * ```ts
730
767
  * function* build(items: HTMLElement[]): Generator<void, void, void>
@@ -747,6 +784,18 @@ declare abstract class Thread {
747
784
  * @param task The same function reference passed to {@link Thread.sub}.
748
785
  */
749
786
  static unsub(task: Thread.TASK): void;
787
+ /**
788
+ * Arms modifiers of `flag` for the next modifiers phase (same frame if called from a driver or before modifiers run).
789
+ * No-op when no modifiers are registered on `flag`. {@link Thread.sub} calls this automatically; call it yourself
790
+ * when an external writer updates shared state without scheduling a task (e.g. Scroll arms {@link Thread.SCROLL_FLAG}
791
+ * after Lenis writes `window.scrollY`, so `App.updateScrollVars` and ScrollTrigger modifiers run).
792
+ * @param flag Priority flag whose modifiers should run.
793
+ * @example
794
+ * ```ts
795
+ * lenis.on('scroll', () => Up.Thread.arm(32))
796
+ * ```
797
+ */
798
+ static arm(flag: Thread.FLAG): void;
750
799
  /**
751
800
  * Returns a promise that resolves when a scheduled task completes its next (or current) run, or when it is unsubscribed.
752
801
  * @param task The same function reference passed to {@link Thread.sub}.
@@ -781,4 +830,4 @@ declare abstract class Thread {
781
830
  static unmarkForever(task: Thread.TASK): void;
782
831
  }
783
832
 
784
- export { Arrays as A, CallStack as C, Thread as T, App as a, type AppCache as b, updateScrollVars as c, updateScreenVars as u };
833
+ export { Arrays as A, CallStack as C, Thread as T, App as a, type AppCache as b };
@@ -298,8 +298,8 @@ class Event {
298
298
  if (config.enhance) Event.#constructData(config.enhance);
299
299
  }
300
300
  for (const [KEY, DATA] of Event.#DATA) {
301
- const CALLSTACK = [], caller = Event.#caller.bind(null, CALLSTACK), callback = DATA.crushed ? caller : DATA.callback.bind(null, caller);
302
- Event.#MANAGER.set(KEY, CALLSTACK);
301
+ const WRAPPERS = [], caller = Event.#caller.bind(null, WRAPPERS), callback = DATA.crushed ? caller : DATA.callback.bind(null, caller);
302
+ Event.#MANAGER.set(KEY, WRAPPERS);
303
303
  if (DATA.forever) {
304
304
  Event.Thread.markForever(callback);
305
305
  Event.#getThreadSubscription(KEY, callback, DATA.threadFlag)();
@@ -359,18 +359,26 @@ class Event {
359
359
  static #getWaiter(task, waitState) {
360
360
  return waitState > 0 ? Wait.debounce(task, waitState, false) : Wait.throttle(task, waitState * -1, true, false);
361
361
  }
362
+ static #getIndex(wrappers, callback) {
363
+ for (let i = 0, max = wrappers.length; i < max; i++) if (wrappers[i].original === callback) return i;
364
+ return -1;
365
+ }
362
366
  // ##ADD
363
367
  /**
364
368
  * Adds `callback` to the callstack of `key` (no duplicates), or calls it immediately (see {@link Event.sub}).
365
369
  */
366
- static #add(key, callback) {
367
- if (key === "LOAD" && Event.#status & Event.#PRE_LOAD) {
370
+ static #add(key, callback, options) {
371
+ const WRAPPERS = Event.#MANAGER.get(key);
372
+ if (!WRAPPERS || key === "LOAD" && Event.#status & Event.#PRE_LOAD) {
368
373
  callback();
369
374
  return;
370
- }
371
- const CALLSTACK = Event.#MANAGER.get(key);
372
- if (!CALLSTACK) callback();
373
- else if (!CALLSTACK.includes(callback)) CALLSTACK.push(callback);
375
+ } else if (~Event.#getIndex(WRAPPERS, callback)) return;
376
+ let original = callback;
377
+ if (options?.once) callback = (...args) => {
378
+ Event.#remove(key, original);
379
+ return original(...args);
380
+ };
381
+ WRAPPERS.push({ original, callback });
374
382
  }
375
383
  // ##REMOVE
376
384
  /**
@@ -378,10 +386,10 @@ class Event {
378
386
  */
379
387
  static #remove(key, callback) {
380
388
  Wait.cancel(callback);
381
- const CALLSTACK = Event.#MANAGER.get(key);
382
- if (!CALLSTACK) return;
383
- const INDEX = CALLSTACK.indexOf(callback);
384
- if (~INDEX) CALLSTACK.splice(INDEX, 1);
389
+ const WRAPPERS = Event.#MANAGER.get(key);
390
+ if (!WRAPPERS) return;
391
+ const INDEX = Event.#getIndex(WRAPPERS, callback);
392
+ if (~INDEX) WRAPPERS.splice(INDEX, 1);
385
393
  }
386
394
  // ##UPDATES
387
395
  /**
@@ -530,20 +538,28 @@ class Event {
530
538
  }
531
539
  // ##UTILS
532
540
  /**
533
- * Subscribes one or several tickets.
534
- * Without `subscriber`, each `[key, callback]` ticket ({@link Event.TICKET}) is added to the Event manager.
535
- * Subscribing the same callback twice to the same key has no effect, and subscribers of an event run in reverse subscription order.
536
- * With `subscriber`, each `[key, callback, options?]` ticket ({@link Event.TICKET_CUSTOM}) is passed to `subscriber.addEventListener`.
537
- * @note Without `subscriber`, the callback is called once immediately, with no arguments, and is not stored when:
538
- * the key is unknown or not registered (including on the server), or the key is `LOAD` and the current `LOAD` phase has already started.
541
+ * Subscribes one or several handlers.
542
+ *
543
+ * **Event manager** (no second argument) — pass {@link Event.TICKET} tuples:
544
+ * - `['LIFE', onLife]`
545
+ * - `['LOAD', onLoad, { once: true }]` — runs once for that key, then auto-unsubscribes
546
+ *
547
+ * Same handler twice on the same key is ignored. Handlers for a given key run in **reverse** subscription order.
548
+ *
549
+ * **DOM target** — pass a `subscriber` (`window`, `document`, an element, …) and {@link Event.TICKET_CUSTOM}
550
+ * tuples; the third field is native `AddEventListenerOptions` (`passive`, DOM `once`, …).
551
+ *
552
+ * @note Without a `subscriber`, the handler is called **immediately** (no args) and **not stored** when:
553
+ * the key is unknown / not registered (including on the server), or the key is `LOAD` and the current `LOAD`
554
+ * phase has already started.
539
555
  * @param pass A ticket or an array of tickets.
540
- * @param subscriber Optional listener target (`window`, `document`, an `Element`, …).
556
+ * @param subscriber Optional listener target for DOM (or DOM-like) events.
541
557
  * @example
542
558
  * ```ts
543
559
  * const onResize = (): void => console.log(Up.App.w, Up.App.h)
544
560
  * const onClick = (e: MouseEvent): void => console.log(e.clientX)
545
561
  *
546
- * Up.Event.sub([['LOAD', () => console.log('Page ready')], ['resize', onResize]])
562
+ * Up.Event.sub([['LOAD', () => console.log('Page ready'), { once: true }], ['resize', onResize]])
547
563
  * Up.Event.sub(['click', onClick, Up.Event.PASSIVE], document.body)
548
564
  * ```
549
565
  */
@@ -554,8 +570,9 @@ class Event {
554
570
  );
555
571
  }
556
572
  /**
557
- * Unsubscribes one or several tickets previously passed to {@link Event.sub}.
558
- * Callbacks are matched by reference, so pass the same function (and the same `subscriber`) used to subscribe.
573
+ * Unsubscribes handlers previously passed to {@link Event.sub}.
574
+ * Match by **function reference** (and the same `subscriber` if you used one).
575
+ * For Event-manager tickets, pass the same handler you subscribed — including when it was registered with `{ once: true }`.
559
576
  * @param pass A ticket or an array of tickets.
560
577
  * @param subscriber The listener target used when subscribing, if any.
561
578
  * @example
@@ -572,11 +589,12 @@ class Event {
572
589
  }
573
590
  // ##GENERATORS
574
591
  /**
575
- * Calls each subscriber of `callstack` with `args`, from last to first, yielding after each call.
592
+ * Invokes each subscriber with `args`, from last to first, yielding after each call.
593
+ * @internal
576
594
  */
577
- static *#caller(callstack, ...args) {
578
- for (let i = callstack.length - 1; i > -1; i--) {
579
- callstack[i](...args);
595
+ static *#caller(wrappers, ...args) {
596
+ for (let i = wrappers.length - 1; i > -1; i--) {
597
+ wrappers[i].callback(...args);
580
598
  yield;
581
599
  }
582
600
  }