@weasel-js/clock 0.0.0-stage → 1.13.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 orochi235
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,53 @@
1
- # Temporary Holding Version
1
+ # @weasel-js/clock
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A clock for a run of time: a position (`elapsed`) moved by a signed `rate`,
4
+ across one pass or many, with ramps, seeking and an optional mix that follows it.
5
+
6
+ Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
7
+ scene-graph canvas kit for React. See the
8
+ [API reference](https://orochi235.github.io/weasel/#pkg/clock).
9
+
10
+ ```sh
11
+ npm install @weasel-js/clock
12
+ ```
13
+
14
+ `createClock` and `createClockRegistry` are the root entry and need no React or
15
+ DOM. `useClockLoop`, the one frame loop that syncs a registry's clocks, is in
16
+ `@weasel-js/clock/react`.
17
+
18
+ ## Shown time
19
+
20
+ A clock's shown time is `origin` plus how far it stands into its pass.
21
+ `origin` (in `ClockSpec`) is where a pass starts on the timeline. On a looping
22
+ clock the shown time is per pass: `shownInPass` reads it, and a time on a pass
23
+ boundary reads as the end of the pass before.
24
+
25
+ `ClockHandle.declare(spec)` takes up a new `duration`, `origin` and `speed`,
26
+ and `loop` when the declared `loop` changed since the last declaration, so a
27
+ loop toggled since survives an edit that only moves the span. It keeps the pass
28
+ and the shown time within it, clamped to the new pass and run.
29
+ `ClockHandle.override(span)` replaces the declared `duration` and `origin` until
30
+ called with `null`, keeping the shown time the same way; a `declare` under it
31
+ changes what lies beneath.
32
+
33
+ `holdAt(handle, shown)` pauses a clock at a time shown within a pass, placed in
34
+ the first pass, and keeps placing it there as the span moves; a time past the
35
+ pass's end waits at the end until the span reaches it. Its limit: a foreign
36
+ seek to exactly the held, clamped value looks the same as the hold's own, so it
37
+ does not release it. `holdElapsed(handle, position)` holds the same way at a
38
+ recorded `elapsed` across the whole run, so a clock of several passes comes back
39
+ in the pass it stood in; a position recorded under another `duration` or
40
+ `origin` keeps its pass and the time it showed within it.
41
+
42
+ ## Visibility
43
+
44
+ A registry runs one frame loop for all its clocks. `register(id, handle,
45
+ target)` may name a target, which resolves the element showing the clock, and
46
+ `targets(handle)` lists them. A clock is gated, meaning not advanced, only when
47
+ all three hold:
48
+
49
+ - it is not the registry's shared clock;
50
+ - every registration of it gave a visibility target;
51
+ - none of those targets is in view.
52
+
53
+ A gated clock is rebased when it comes back, so it resumes without a jump.
@@ -0,0 +1,44 @@
1
+ import { C as Clock, a as ClockPosition, b as ClockSpec, c as ClockHandle } from './registry-C2h_prHD.js';
2
+ export { d as ClockRegistry, e as ClockTarget, f as ClockedMix, g as createClockRegistry } from './registry-C2h_prHD.js';
3
+
4
+ /** Where `clock` stands, for a record to reopen it at; `declared` is the
5
+ * duration its spec gave, and `declaredOrigin` the origin. */
6
+ declare function clockPosition(clock: Clock, declared: number | undefined, declaredOrigin?: number): ClockPosition;
7
+ /** A clock, opened from its declaration and, for a run reopened, where it
8
+ * stood. */
9
+ declare function createClock(spec: ClockSpec, at?: ClockPosition): ClockHandle;
10
+
11
+ /** The time a clock standing `elapsed` into a run of `loop` passes shows
12
+ * within its pass: a time on a pass boundary past the start shows as the end
13
+ * of the pass before, and one past the run's end runs on in its last pass. */
14
+ declare function shownInPass(origin: number, elapsed: number, duration: number, loop?: boolean | number): number;
15
+ /** A clock held at a time its span may not reach yet. */
16
+ interface PlayheadHold {
17
+ /** The held time while the clock stands elsewhere (clamped), else null. */
18
+ pending(): number | null;
19
+ /** Stop holding. Idempotent. */
20
+ release(): void;
21
+ }
22
+ /**
23
+ * Pauses `handle`'s clock at `shown`, a time shown within a pass (`null`: the
24
+ * span's start), placed in the first pass, and keeps seeking there as `declare`
25
+ * or a `duration` write moves the span. A time past the pass's end waits at it.
26
+ * Any other change (a rate, a seek of someone else's) releases the hold. A
27
+ * clock that is not seekable is never held.
28
+ */
29
+ declare function holdAt(handle: ClockHandle, shown: number | null): PlayheadHold;
30
+ /** A recorded position `holdElapsed` holds at. */
31
+ type HeldPosition = Pick<ClockPosition, 'elapsed' | 'passEnd' | 'duration' | 'origin'>;
32
+ /**
33
+ * As {@link holdAt}, but at a recorded position across the whole run, so a
34
+ * clock of several passes comes back in the pass it stood in. A position
35
+ * recorded under another `duration` or `origin` keeps its pass and the time it
36
+ * showed within it. `pending` reports the `elapsed` it holds for, on the
37
+ * clock's current span.
38
+ */
39
+ declare function holdElapsed(handle: ClockHandle, at: HeldPosition): PlayheadHold;
40
+
41
+ /** The speeds a transport offers by default, unsigned. */
42
+ declare const CLOCK_RATES: readonly [0.25, 0.5, 1, 2, 4];
43
+
44
+ export { CLOCK_RATES, Clock, ClockHandle, ClockPosition, ClockSpec, type HeldPosition, type PlayheadHold, clockPosition, createClock, holdAt, holdElapsed, shownInPass };
package/dist/index.js ADDED
@@ -0,0 +1,458 @@
1
+ import { createEmitter } from '@weasel-js/registry';
2
+
3
+ // src/clock.ts
4
+
5
+ // src/mixFollow.ts
6
+ function followMix(mix) {
7
+ let host = 0;
8
+ mix?.sync(host);
9
+ return (elapsed) => {
10
+ if (!mix) return;
11
+ const ahead = elapsed - mix.now;
12
+ if (ahead > 0) {
13
+ host += ahead;
14
+ mix.sync(host);
15
+ } else if (ahead < 0) mix.seek(elapsed);
16
+ };
17
+ }
18
+
19
+ // src/place.ts
20
+ function placeOf(elapsed, duration, passes, edge) {
21
+ if (!(duration > 0) || !Number.isFinite(duration)) return { pass: 0, phase: 0 };
22
+ if (elapsed >= duration * passes) return { pass: passes - 1, phase: 1 };
23
+ const pass = Math.floor(elapsed / duration);
24
+ if (edge && pass > 0 && elapsed === pass * duration) return { pass: pass - 1, phase: 1 };
25
+ return { pass, phase: elapsed % duration / duration };
26
+ }
27
+ var passesOf = (loop) => loop === true ? Number.POSITIVE_INFINITY : loop === false ? 1 : Math.max(1, loop);
28
+
29
+ // src/ramp.ts
30
+ function stepRamp(rate, ramp, dt) {
31
+ if (!ramp) return { area: rate * dt, rate, ramp: null, settled: false };
32
+ if (dt >= ramp.left) {
33
+ const area = (rate + ramp.to) / 2 * ramp.left + ramp.to * (dt - ramp.left);
34
+ return { area, rate: ramp.to, ramp: null, settled: true };
35
+ }
36
+ const next = rate + (ramp.to - rate) * dt / ramp.left;
37
+ return {
38
+ area: (rate + next) / 2 * dt,
39
+ rate: next,
40
+ ramp: { to: ramp.to, left: ramp.left - dt },
41
+ settled: false
42
+ };
43
+ }
44
+
45
+ // src/rates.ts
46
+ var CLOCK_RATES = [0.25, 0.5, 1, 2, 4];
47
+
48
+ // src/validate.ts
49
+ var checkDuration = (duration) => {
50
+ if (!(duration > 0)) throw new RangeError(`a clock's duration is positive, not ${duration}`);
51
+ };
52
+ var ratesOf = (spec) => {
53
+ const rates = [...spec.rates ?? CLOCK_RATES].sort((a, b) => a - b);
54
+ if (rates.length === 0 || rates.some((r) => !(r > 0) || !Number.isFinite(r)))
55
+ throw new RangeError("a clock offers at least one rate, each finite and positive");
56
+ return rates;
57
+ };
58
+ var checkRate = (rate, seekable) => {
59
+ if (!Number.isFinite(rate)) throw new RangeError(`a clock's rate is finite, not ${rate}`);
60
+ if (rate < 0 && !seekable)
61
+ throw new RangeError("a clock that is not seekable cannot run backward");
62
+ };
63
+ var checkSpeed = (speed) => {
64
+ if (!(speed > 0) || !Number.isFinite(speed))
65
+ throw new RangeError(`a clock's speed is finite and positive, not ${speed}`);
66
+ };
67
+ var checkOrigin = (origin) => {
68
+ if (!Number.isFinite(origin)) throw new RangeError(`a clock's origin is finite, not ${origin}`);
69
+ };
70
+
71
+ // src/clock.ts
72
+ function clockPosition(clock, declared, declaredOrigin = 0) {
73
+ const duration = declared ?? Number.POSITIVE_INFINITY;
74
+ return {
75
+ elapsed: clock.elapsed,
76
+ rate: clock.rate,
77
+ ...clock.duration !== duration ? { duration: clock.duration } : {},
78
+ ...clock.origin !== declaredOrigin ? { origin: clock.origin } : {},
79
+ ...clock.phase === 1 && !clock.ended ? { passEnd: true } : {}
80
+ };
81
+ }
82
+ var clamp = (run, ms) => Math.min(Math.max(0, ms), run.span);
83
+ var isInert = (run) => run.rate === 0 && run.ramp === null && !run.changed;
84
+ var passAt = (run) => placeOf(run.elapsed, run.duration, passesOf(run.loop), run.edge).pass;
85
+ var phaseAt = (run) => placeOf(run.elapsed, run.duration, passesOf(run.loop), run.edge).phase;
86
+ var restingSpeed = (rate) => Math.abs(rate) || 1;
87
+ function passOf(run) {
88
+ const { pass } = placeOf(run.elapsed, run.duration, passesOf(run.loop), run.edge);
89
+ return { pass, into: pass === 0 ? run.elapsed : run.elapsed - pass * run.duration };
90
+ }
91
+ function placeIn(run, pass, into) {
92
+ const last = passesOf(run.loop) - 1;
93
+ const within = Math.min(Math.max(0, into), run.duration);
94
+ run.elapsed = clamp(run, Math.min(pass, last) * run.duration + within);
95
+ run.edge = run.elapsed > 0 && run.elapsed % run.duration === 0;
96
+ }
97
+ function change(run, hub) {
98
+ run.changed = true;
99
+ if (run.wakeFired) return;
100
+ run.wakeFired = true;
101
+ hub.wakes.emit();
102
+ }
103
+ function setDuration(run, hub, next) {
104
+ checkDuration(next);
105
+ if (next === run.duration) return;
106
+ if (Number.isFinite(run.duration) && Number.isFinite(next)) {
107
+ const ended = run.elapsed >= run.span;
108
+ const at = passAt(run) + phaseAt(run);
109
+ run.duration = next;
110
+ run.span = run.duration * passesOf(run.loop);
111
+ run.elapsed = ended ? run.span : clamp(run, at * run.duration);
112
+ } else {
113
+ run.duration = next;
114
+ run.span = run.duration * passesOf(run.loop);
115
+ run.elapsed = clamp(run, run.elapsed);
116
+ }
117
+ change(run, hub);
118
+ hub.subs.emit();
119
+ }
120
+ function declaredOf(run, spec) {
121
+ return {
122
+ duration: spec.duration ?? Number.POSITIVE_INFINITY,
123
+ origin: spec.origin ?? 0,
124
+ loop: spec.loop ?? false,
125
+ speed: spec.speed ?? restingSpeed(run.rate)
126
+ };
127
+ }
128
+ function applyRun(run, hub, declared, override) {
129
+ const duration = override?.duration ?? declared.duration;
130
+ const origin = override?.origin ?? declared.origin;
131
+ checkDuration(duration);
132
+ checkOrigin(origin);
133
+ checkSpeed(declared.speed);
134
+ const finite = Number.isFinite(run.duration) && Number.isFinite(duration);
135
+ const { pass, into } = finite ? passOf(run) : { pass: 0, into: run.elapsed };
136
+ const shown = run.origin + into;
137
+ run.duration = duration;
138
+ run.loop = declared.loop;
139
+ run.span = duration * passesOf(run.loop);
140
+ run.origin = origin;
141
+ run.speed = declared.speed;
142
+ if (Number.isFinite(duration)) placeIn(run, pass, shown - origin);
143
+ else {
144
+ run.elapsed = clamp(run, shown - origin);
145
+ run.edge = false;
146
+ }
147
+ change(run, hub);
148
+ hub.subs.emit();
149
+ }
150
+ function advance(run, hub, dt) {
151
+ const step = stepRamp(run.rate, run.ramp, dt);
152
+ run.rate = step.rate;
153
+ run.ramp = step.ramp;
154
+ if (step.settled) hub.subs.emit();
155
+ return step.area;
156
+ }
157
+ function syncTo(run, hub, timestamp) {
158
+ const wasChanged = run.changed;
159
+ const before = run.elapsed;
160
+ if (!Number.isNaN(run.last) && !run.slept) {
161
+ run.elapsed += advance(run, hub, Math.max(0, timestamp - run.last));
162
+ if (run.elapsed >= run.span && run.rate > 0) {
163
+ run.elapsed = run.span;
164
+ run.rate = 0;
165
+ run.ramp = null;
166
+ hub.subs.emit();
167
+ } else if (run.elapsed <= 0 && run.rate < 0) {
168
+ run.elapsed = 0;
169
+ run.rate = 0;
170
+ run.ramp = null;
171
+ hub.subs.emit();
172
+ }
173
+ }
174
+ run.last = timestamp;
175
+ run.changed = false;
176
+ run.wakeFired = false;
177
+ run.slept = isInert(run);
178
+ if (run.elapsed !== before) run.edge = false;
179
+ if (run.elapsed !== before || wasChanged) {
180
+ hub.follow(run.elapsed);
181
+ hub.frames.emit(run.elapsed, passAt(run));
182
+ }
183
+ }
184
+ function clockOf(run, hub, seekable, rates) {
185
+ const clock = {
186
+ get elapsed() {
187
+ return run.elapsed;
188
+ },
189
+ get pass() {
190
+ return passAt(run);
191
+ },
192
+ get phase() {
193
+ return phaseAt(run);
194
+ },
195
+ get duration() {
196
+ return run.duration;
197
+ },
198
+ set duration(next) {
199
+ setDuration(run, hub, next);
200
+ },
201
+ get origin() {
202
+ return run.origin;
203
+ },
204
+ get speed() {
205
+ return run.speed;
206
+ },
207
+ rates,
208
+ seekable,
209
+ get ended() {
210
+ return Number.isFinite(run.span) && run.elapsed >= run.span;
211
+ },
212
+ get rate() {
213
+ return run.rate;
214
+ },
215
+ set rate(next) {
216
+ checkRate(next, seekable);
217
+ run.rate = next;
218
+ run.ramp = null;
219
+ change(run, hub);
220
+ hub.subs.emit();
221
+ },
222
+ get loop() {
223
+ return run.loop;
224
+ },
225
+ set loop(next) {
226
+ const { pass, into } = passOf(run);
227
+ run.loop = next;
228
+ run.span = run.duration * passesOf(run.loop);
229
+ if (run.elapsed > run.span) placeIn(run, pass, into);
230
+ change(run, hub);
231
+ hub.subs.emit();
232
+ },
233
+ ramp(to, over) {
234
+ checkRate(to, seekable);
235
+ if (!(over > 0)) {
236
+ clock.rate = to;
237
+ return;
238
+ }
239
+ run.ramp = { to, left: over };
240
+ change(run, hub);
241
+ hub.subs.emit();
242
+ },
243
+ seek(target, line = "start") {
244
+ if (!seekable) throw new Error("this clock is not seekable");
245
+ run.elapsed = clamp(run, target);
246
+ run.edge = line === "end";
247
+ change(run, hub);
248
+ hub.subs.emit();
249
+ },
250
+ sync: (timestamp) => syncTo(run, hub, timestamp),
251
+ rebase() {
252
+ run.last = Number.NaN;
253
+ },
254
+ get inert() {
255
+ return isInert(run);
256
+ },
257
+ onWake: hub.wakes.subscribe,
258
+ subscribe: hub.subs.subscribe
259
+ };
260
+ return clock;
261
+ }
262
+ function createClock(spec, at) {
263
+ const duration = at?.duration ?? spec.duration ?? Number.POSITIVE_INFINITY;
264
+ checkDuration(duration);
265
+ const seekable = spec.seekable ?? true;
266
+ const openRate = spec.rate ?? 0;
267
+ checkRate(openRate, seekable);
268
+ const rates = ratesOf(spec);
269
+ const origin = at?.origin ?? spec.origin ?? 0;
270
+ checkOrigin(origin);
271
+ if (spec.speed !== void 0) checkSpeed(spec.speed);
272
+ const loop = spec.loop ?? false;
273
+ const run = {
274
+ duration,
275
+ loop,
276
+ span: duration * passesOf(loop),
277
+ origin,
278
+ speed: spec.speed ?? restingSpeed(openRate),
279
+ rate: at ? at.rate : openRate,
280
+ elapsed: 0,
281
+ edge: at?.passEnd ?? false,
282
+ ramp: null,
283
+ last: Number.NaN,
284
+ changed: false,
285
+ slept: false,
286
+ wakeFired: false
287
+ };
288
+ const opening = () => {
289
+ if (spec.start !== "end") return clamp(run, spec.start ?? 0);
290
+ if (!Number.isFinite(run.span))
291
+ throw new RangeError("a run that never ends has no 'end' to start at");
292
+ return run.span;
293
+ };
294
+ run.elapsed = at ? clamp(run, at.elapsed) : opening();
295
+ const mix = spec.mix?.() ?? null;
296
+ const hub = {
297
+ wakes: createEmitter(),
298
+ subs: createEmitter(),
299
+ frames: createEmitter(),
300
+ follow: followMix(mix),
301
+ opening
302
+ };
303
+ const clock = clockOf(run, hub, seekable, rates);
304
+ hub.follow(run.elapsed);
305
+ let declared = {
306
+ duration: spec.duration ?? Number.POSITIVE_INFINITY,
307
+ origin: spec.origin ?? 0,
308
+ loop: run.loop,
309
+ speed: run.speed
310
+ };
311
+ let override = null;
312
+ return {
313
+ clock,
314
+ mix,
315
+ step: spec.step,
316
+ reset() {
317
+ run.elapsed = hub.opening();
318
+ run.edge = false;
319
+ run.rate = openRate;
320
+ run.ramp = null;
321
+ change(run, hub);
322
+ hub.subs.emit();
323
+ },
324
+ declare(next) {
325
+ const was = declared.loop;
326
+ declared = declaredOf(run, next);
327
+ applyRun(run, hub, { ...declared, loop: declared.loop !== was ? declared.loop : run.loop }, override);
328
+ },
329
+ override(next) {
330
+ override = next ? { ...next } : null;
331
+ applyRun(run, hub, { ...declared, loop: run.loop }, override);
332
+ },
333
+ onFrame: hub.frames.subscribe
334
+ };
335
+ }
336
+
337
+ // src/hold.ts
338
+ function shownInPass(origin, elapsed, duration, loop = true) {
339
+ if (!Number.isFinite(duration)) return origin + elapsed;
340
+ const last = (passesOf(loop) - 1) * duration;
341
+ if (elapsed > last + duration) return origin + elapsed - last;
342
+ const into = elapsed % duration;
343
+ return origin + (into === 0 && elapsed > 0 ? duration : into);
344
+ }
345
+ function holdAt(handle, shown) {
346
+ return holdWhere(
347
+ handle,
348
+ (clock) => {
349
+ const within = Math.min(shown === null ? 0 : shown - clock.origin, clock.duration);
350
+ return [within, within > 0 && within === clock.duration ? "end" : "start"];
351
+ },
352
+ (clock) => shown === null || clock.origin + clock.elapsed === shown ? null : shown
353
+ );
354
+ }
355
+ function holdElapsed(handle, at) {
356
+ return holdWhere(
357
+ handle,
358
+ (clock) => elapsedOn(clock, at),
359
+ (clock) => {
360
+ const [target] = elapsedOn(clock, at);
361
+ return clock.elapsed === target ? null : target;
362
+ }
363
+ );
364
+ }
365
+ function elapsedOn(clock, at) {
366
+ const line = at.passEnd ? "end" : "start";
367
+ const from = at.duration ?? clock.duration;
368
+ const origin = at.origin ?? clock.origin;
369
+ if (from === clock.duration && origin === clock.origin) return [at.elapsed, line];
370
+ if (!Number.isFinite(from) || !Number.isFinite(clock.duration))
371
+ return [at.elapsed + origin - clock.origin, line];
372
+ let pass = Math.floor(at.elapsed / from);
373
+ let into = at.elapsed - pass * from;
374
+ if (at.passEnd && into === 0 && pass > 0) {
375
+ pass -= 1;
376
+ into = from;
377
+ }
378
+ const within = Math.min(Math.max(0, origin + into - clock.origin), clock.duration);
379
+ const end = within > 0 && within === clock.duration;
380
+ return [pass * clock.duration + within, end ? "end" : "start"];
381
+ }
382
+ function holdWhere(handle, where, wanted) {
383
+ const { clock } = handle;
384
+ let released = false;
385
+ let busy = false;
386
+ let unsub = null;
387
+ let duration = clock.duration;
388
+ let origin = clock.origin;
389
+ let loop = clock.loop;
390
+ let set = clock.elapsed;
391
+ const release = () => {
392
+ released = true;
393
+ unsub?.();
394
+ unsub = null;
395
+ };
396
+ const place = () => {
397
+ busy = true;
398
+ try {
399
+ clock.seek(...where(clock));
400
+ } finally {
401
+ busy = false;
402
+ }
403
+ duration = clock.duration;
404
+ origin = clock.origin;
405
+ loop = clock.loop;
406
+ set = clock.elapsed;
407
+ };
408
+ if (!clock.seekable) {
409
+ released = true;
410
+ } else {
411
+ clock.rate = 0;
412
+ place();
413
+ unsub = clock.subscribe(() => {
414
+ if (busy || released) return;
415
+ if (clock.rate !== 0) release();
416
+ else if (clock.duration !== duration || clock.origin !== origin || clock.loop !== loop) place();
417
+ else if (clock.elapsed !== set) release();
418
+ });
419
+ }
420
+ return {
421
+ pending: () => released ? null : wanted(clock),
422
+ release
423
+ };
424
+ }
425
+ function createClockRegistry(shared = null) {
426
+ const entries = /* @__PURE__ */ new Map();
427
+ const changed = createEmitter();
428
+ const notify = changed.emit;
429
+ return {
430
+ register(id, handle, target) {
431
+ const entry = { handle, target };
432
+ entries.set(id, entry);
433
+ notify();
434
+ return () => {
435
+ if (entries.get(id) !== entry) return;
436
+ entries.delete(id);
437
+ notify();
438
+ };
439
+ },
440
+ get: (id) => entries.get(id)?.handle ?? null,
441
+ targets(handle) {
442
+ const found = [];
443
+ for (const entry of entries.values()) {
444
+ if (entry.handle !== handle) continue;
445
+ if (!entry.target) return [];
446
+ found.push(entry.target);
447
+ }
448
+ return found;
449
+ },
450
+ shared,
451
+ all: () => /* @__PURE__ */ new Set([...shared ? [shared] : [], ...[...entries.values()].map((e) => e.handle)]),
452
+ subscribe: changed.subscribe
453
+ };
454
+ }
455
+
456
+ export { CLOCK_RATES, clockPosition, createClock, createClockRegistry, holdAt, holdElapsed, shownInPass };
457
+ //# sourceMappingURL=index.js.map
458
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mixFollow.ts","../src/place.ts","../src/ramp.ts","../src/rates.ts","../src/validate.ts","../src/clock.ts","../src/hold.ts","../src/registry.ts"],"names":["createEmitter"],"mappings":"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","file":"index.js","sourcesContent":["import type { ClockedMix } from './types';\n\n/** Keeps `mix` at the clock's time: its host clock only goes forward, by what\n * the clock moved forward, and it seeks back when the clock does. */\nexport function followMix(mix: ClockedMix | null): (elapsed: number) => void {\n let host = 0;\n mix?.sync(host);\n return (elapsed) => {\n if (!mix) return;\n const ahead = elapsed - mix.now;\n if (ahead > 0) {\n host += ahead;\n mix.sync(host);\n } else if (ahead < 0) mix.seek(elapsed);\n };\n}\n","/** Which pass `elapsed` falls in and how far through it. `edge` reads the\n * start of a pass after the first as the end of the one before. */\nexport function placeOf(\n elapsed: number,\n duration: number,\n passes: number,\n edge: boolean,\n): { pass: number; phase: number } {\n if (!(duration > 0) || !Number.isFinite(duration)) return { pass: 0, phase: 0 };\n if (elapsed >= duration * passes) return { pass: passes - 1, phase: 1 };\n const pass = Math.floor(elapsed / duration);\n if (edge && pass > 0 && elapsed === pass * duration) return { pass: pass - 1, phase: 1 };\n return { pass, phase: (elapsed % duration) / duration };\n}\n\n/** How many passes `loop` makes. */\nexport const passesOf = (loop: boolean | number): number =>\n loop === true ? Number.POSITIVE_INFINITY : loop === false ? 1 : Math.max(1, loop);\n","/** A rate on its way to `to`, `left` real ms from arriving. */\nexport interface Ramp {\n to: number;\n left: number;\n}\n\n/** What one step of `dt` real ms did: the ms of clock time it covered, the rate\n * after it, the ramp still running (`null` once settled), and whether it settled. */\nexport interface RampStep {\n area: number;\n rate: number;\n ramp: Ramp | null;\n settled: boolean;\n}\n\n/** Steps `rate` over `dt` real ms along `ramp` (none: constant), integrating the\n * clock time covered. Does not mutate `ramp`. */\nexport function stepRamp(rate: number, ramp: Ramp | null, dt: number): RampStep {\n if (!ramp) return { area: rate * dt, rate, ramp: null, settled: false };\n if (dt >= ramp.left) {\n const area = ((rate + ramp.to) / 2) * ramp.left + ramp.to * (dt - ramp.left);\n return { area, rate: ramp.to, ramp: null, settled: true };\n }\n const next = rate + ((ramp.to - rate) * dt) / ramp.left;\n return {\n area: ((rate + next) / 2) * dt,\n rate: next,\n ramp: { to: ramp.to, left: ramp.left - dt },\n settled: false,\n };\n}\n","/** The speeds a transport offers by default, unsigned. */\nexport const CLOCK_RATES = [0.25, 0.5, 1, 2, 4] as const;\n","import { CLOCK_RATES } from './rates';\nimport type { ClockSpec } from './types';\n\nexport const checkDuration = (duration: number): void => {\n if (!(duration > 0)) throw new RangeError(`a clock's duration is positive, not ${duration}`);\n};\n\nexport const ratesOf = (spec: ClockSpec): number[] => {\n const rates = [...(spec.rates ?? CLOCK_RATES)].sort((a, b) => a - b);\n if (rates.length === 0 || rates.some((r) => !(r > 0) || !Number.isFinite(r)))\n throw new RangeError('a clock offers at least one rate, each finite and positive');\n return rates;\n};\n\nexport const checkRate = (rate: number, seekable: boolean): void => {\n if (!Number.isFinite(rate)) throw new RangeError(`a clock's rate is finite, not ${rate}`);\n if (rate < 0 && !seekable)\n throw new RangeError('a clock that is not seekable cannot run backward');\n};\n\nexport const checkSpeed = (speed: number): void => {\n if (!(speed > 0) || !Number.isFinite(speed))\n throw new RangeError(`a clock's speed is finite and positive, not ${speed}`);\n};\n\nexport const checkOrigin = (origin: number): void => {\n if (!Number.isFinite(origin)) throw new RangeError(`a clock's origin is finite, not ${origin}`);\n};\n","import { createEmitter } from '@weasel-js/registry';\nimport { followMix } from './mixFollow';\nimport { passesOf, placeOf } from './place';\nimport { type Ramp, stepRamp } from './ramp';\nimport type { Clock, ClockHandle, ClockPosition, ClockSpec } from './types';\nimport { checkDuration, checkOrigin, checkRate, checkSpeed, ratesOf } from './validate';\n\n/** Where `clock` stands, for a record to reopen it at; `declared` is the\n * duration its spec gave, and `declaredOrigin` the origin. */\nexport function clockPosition(\n clock: Clock,\n declared: number | undefined,\n declaredOrigin = 0,\n): ClockPosition {\n const duration = declared ?? Number.POSITIVE_INFINITY;\n return {\n elapsed: clock.elapsed,\n rate: clock.rate,\n ...(clock.duration !== duration ? { duration: clock.duration } : {}),\n ...(clock.origin !== declaredOrigin ? { origin: clock.origin } : {}),\n ...(clock.phase === 1 && !clock.ended ? { passEnd: true as const } : {}),\n };\n}\n\n/** A clock's moving parts, read and written by the functions below. */\ninterface Run {\n duration: number;\n loop: boolean | number;\n span: number;\n origin: number;\n speed: number;\n rate: number;\n elapsed: number;\n edge: boolean;\n ramp: Ramp | null;\n last: number;\n /** Changed since the last sync, so the next one is due even when paused. */\n changed: boolean;\n /** Inert as of the last sync: the next one only records the time. */\n slept: boolean;\n wakeFired: boolean;\n}\n\ninterface Hub {\n wakes: ReturnType<typeof createEmitter>;\n subs: ReturnType<typeof createEmitter>;\n frames: ReturnType<typeof createEmitter<[elapsed: number, pass: number]>>;\n follow: (elapsed: number) => void;\n /** Where a new run opens. */\n opening: () => number;\n}\n\nconst clamp = (run: Run, ms: number): number => Math.min(Math.max(0, ms), run.span);\nconst isInert = (run: Run): boolean => run.rate === 0 && run.ramp === null && !run.changed;\nconst passAt = (run: Run): number =>\n placeOf(run.elapsed, run.duration, passesOf(run.loop), run.edge).pass;\nconst phaseAt = (run: Run): number =>\n placeOf(run.elapsed, run.duration, passesOf(run.loop), run.edge).phase;\n/** The speed a transport plays a paused clock at, when none is declared. */\nconst restingSpeed = (rate: number): number => Math.abs(rate) || 1;\n\n/** Which pass `elapsed` falls in and how far into it, in ms; a pass's end stands as its own. */\nfunction passOf(run: Run): { pass: number; into: number } {\n const { pass } = placeOf(run.elapsed, run.duration, passesOf(run.loop), run.edge);\n return { pass, into: pass === 0 ? run.elapsed : run.elapsed - pass * run.duration };\n}\n\n/** Places `into` ms into pass `pass` of the run, the pass clamped to the run's last and\n * `into` to the pass; a time on a boundary stands as the end of the pass before. */\nfunction placeIn(run: Run, pass: number, into: number): void {\n const last = passesOf(run.loop) - 1;\n const within = Math.min(Math.max(0, into), run.duration);\n run.elapsed = clamp(run, Math.min(pass, last) * run.duration + within);\n run.edge = run.elapsed > 0 && run.elapsed % run.duration === 0;\n}\n\nfunction change(run: Run, hub: Hub): void {\n run.changed = true;\n if (run.wakeFired) return;\n run.wakeFired = true;\n hub.wakes.emit();\n}\n\nfunction setDuration(run: Run, hub: Hub, next: number): void {\n checkDuration(next);\n if (next === run.duration) return;\n if (Number.isFinite(run.duration) && Number.isFinite(next)) {\n const ended = run.elapsed >= run.span;\n const at = passAt(run) + phaseAt(run);\n run.duration = next;\n run.span = run.duration * passesOf(run.loop);\n run.elapsed = ended ? run.span : clamp(run, at * run.duration);\n } else {\n run.duration = next;\n run.span = run.duration * passesOf(run.loop);\n run.elapsed = clamp(run, run.elapsed);\n }\n change(run, hub);\n hub.subs.emit();\n}\n\n/** The four fields `declare` takes up, resolved. */\ninterface Declared {\n duration: number;\n origin: number;\n loop: boolean | number;\n speed: number;\n}\n\n/** A runtime replacement for the declared `duration` and `origin`; see `ClockHandle.override`. */\ntype SpanOverride = { duration?: number; origin?: number } | null;\n\nfunction declaredOf(run: Run, spec: ClockSpec): Declared {\n return {\n duration: spec.duration ?? Number.POSITIVE_INFINITY,\n origin: spec.origin ?? 0,\n loop: spec.loop ?? false,\n speed: spec.speed ?? restingSpeed(run.rate),\n };\n}\n\n/** Takes up `declared` with `override` laid over it, keeping the pass and the\n * shown time within it, clamped. */\nfunction applyRun(run: Run, hub: Hub, declared: Declared, override: SpanOverride): void {\n const duration = override?.duration ?? declared.duration;\n const origin = override?.origin ?? declared.origin;\n checkDuration(duration);\n checkOrigin(origin);\n checkSpeed(declared.speed);\n const finite = Number.isFinite(run.duration) && Number.isFinite(duration);\n const { pass, into } = finite ? passOf(run) : { pass: 0, into: run.elapsed };\n const shown = run.origin + into;\n run.duration = duration;\n run.loop = declared.loop;\n run.span = duration * passesOf(run.loop);\n run.origin = origin;\n run.speed = declared.speed;\n if (Number.isFinite(duration)) placeIn(run, pass, shown - origin);\n else {\n run.elapsed = clamp(run, shown - origin);\n run.edge = false;\n }\n change(run, hub);\n hub.subs.emit();\n}\n\n/** ms of clock time over `dt` real ms, ramp included; settles the ramp. */\nfunction advance(run: Run, hub: Hub, dt: number): number {\n const step = stepRamp(run.rate, run.ramp, dt);\n run.rate = step.rate;\n run.ramp = step.ramp;\n if (step.settled) hub.subs.emit();\n return step.area;\n}\n\nfunction syncTo(run: Run, hub: Hub, timestamp: number): void {\n const wasChanged = run.changed;\n const before = run.elapsed;\n if (!Number.isNaN(run.last) && !run.slept) {\n run.elapsed += advance(run, hub, Math.max(0, timestamp - run.last));\n if (run.elapsed >= run.span && run.rate > 0) {\n run.elapsed = run.span;\n run.rate = 0;\n run.ramp = null;\n hub.subs.emit();\n } else if (run.elapsed <= 0 && run.rate < 0) {\n run.elapsed = 0;\n run.rate = 0;\n run.ramp = null;\n hub.subs.emit();\n }\n }\n run.last = timestamp;\n run.changed = false;\n run.wakeFired = false;\n run.slept = isInert(run);\n if (run.elapsed !== before) run.edge = false;\n if (run.elapsed !== before || wasChanged) {\n hub.follow(run.elapsed);\n hub.frames.emit(run.elapsed, passAt(run));\n }\n}\n\nfunction clockOf(run: Run, hub: Hub, seekable: boolean, rates: readonly number[]): Clock {\n const clock: Clock = {\n get elapsed() {\n return run.elapsed;\n },\n get pass() {\n return passAt(run);\n },\n get phase() {\n return phaseAt(run);\n },\n get duration() {\n return run.duration;\n },\n set duration(next: number) {\n setDuration(run, hub, next);\n },\n get origin() {\n return run.origin;\n },\n get speed() {\n return run.speed;\n },\n rates,\n seekable,\n get ended() {\n return Number.isFinite(run.span) && run.elapsed >= run.span;\n },\n get rate() {\n return run.rate;\n },\n set rate(next: number) {\n checkRate(next, seekable);\n run.rate = next;\n run.ramp = null;\n change(run, hub);\n hub.subs.emit();\n },\n get loop() {\n return run.loop;\n },\n set loop(next: boolean | number) {\n const { pass, into } = passOf(run);\n run.loop = next;\n run.span = run.duration * passesOf(run.loop);\n if (run.elapsed > run.span) placeIn(run, pass, into);\n change(run, hub);\n hub.subs.emit();\n },\n ramp(to, over) {\n checkRate(to, seekable);\n if (!(over > 0)) {\n clock.rate = to;\n return;\n }\n run.ramp = { to, left: over };\n change(run, hub);\n hub.subs.emit();\n },\n seek(target, line = 'start') {\n if (!seekable) throw new Error('this clock is not seekable');\n run.elapsed = clamp(run, target);\n run.edge = line === 'end';\n change(run, hub);\n hub.subs.emit();\n },\n sync: (timestamp) => syncTo(run, hub, timestamp),\n rebase() {\n run.last = Number.NaN;\n },\n get inert() {\n return isInert(run);\n },\n onWake: hub.wakes.subscribe,\n subscribe: hub.subs.subscribe,\n };\n return clock;\n}\n\n/** A clock, opened from its declaration and, for a run reopened, where it\n * stood. */\nexport function createClock(spec: ClockSpec, at?: ClockPosition): ClockHandle {\n const duration = at?.duration ?? spec.duration ?? Number.POSITIVE_INFINITY;\n checkDuration(duration);\n const seekable = spec.seekable ?? true;\n const openRate = spec.rate ?? 0;\n checkRate(openRate, seekable);\n const rates = ratesOf(spec);\n\n const origin = at?.origin ?? spec.origin ?? 0;\n checkOrigin(origin);\n if (spec.speed !== undefined) checkSpeed(spec.speed);\n const loop = spec.loop ?? false;\n const run: Run = {\n duration,\n loop,\n span: duration * passesOf(loop),\n origin,\n speed: spec.speed ?? restingSpeed(openRate),\n rate: at ? at.rate : openRate,\n elapsed: 0,\n edge: at?.passEnd ?? false,\n ramp: null,\n last: Number.NaN,\n changed: false,\n slept: false,\n wakeFired: false,\n };\n const opening = (): number => {\n if (spec.start !== 'end') return clamp(run, spec.start ?? 0);\n if (!Number.isFinite(run.span))\n throw new RangeError(\"a run that never ends has no 'end' to start at\");\n return run.span;\n };\n run.elapsed = at ? clamp(run, at.elapsed) : opening();\n\n const mix = spec.mix?.() ?? null;\n const hub: Hub = {\n wakes: createEmitter(),\n subs: createEmitter(),\n frames: createEmitter<[elapsed: number, pass: number]>(),\n follow: followMix(mix),\n opening,\n };\n const clock = clockOf(run, hub, seekable, rates);\n hub.follow(run.elapsed);\n\n let declared: Declared = {\n duration: spec.duration ?? Number.POSITIVE_INFINITY,\n origin: spec.origin ?? 0,\n loop: run.loop,\n speed: run.speed,\n };\n let override: SpanOverride = null;\n\n return {\n clock,\n mix,\n step: spec.step,\n reset() {\n run.elapsed = hub.opening();\n run.edge = false;\n run.rate = openRate;\n run.ramp = null;\n change(run, hub);\n hub.subs.emit();\n },\n declare(next) {\n const was = declared.loop;\n declared = declaredOf(run, next);\n // A declaration that keeps its loop leaves one set since, as a transport's toggle.\n applyRun(run, hub, { ...declared, loop: declared.loop !== was ? declared.loop : run.loop }, override);\n },\n override(next) {\n override = next ? { ...next } : null;\n applyRun(run, hub, { ...declared, loop: run.loop }, override);\n },\n onFrame: hub.frames.subscribe,\n };\n}\n","import { passesOf } from './place';\nimport type { Clock, ClockHandle, ClockPosition } from './types';\n\n/** The time a clock standing `elapsed` into a run of `loop` passes shows\n * within its pass: a time on a pass boundary past the start shows as the end\n * of the pass before, and one past the run's end runs on in its last pass. */\nexport function shownInPass(\n origin: number,\n elapsed: number,\n duration: number,\n loop: boolean | number = true,\n): number {\n if (!Number.isFinite(duration)) return origin + elapsed;\n const last = (passesOf(loop) - 1) * duration;\n if (elapsed > last + duration) return origin + elapsed - last;\n const into = elapsed % duration;\n return origin + (into === 0 && elapsed > 0 ? duration : into);\n}\n\n/** A clock held at a time its span may not reach yet. */\nexport interface PlayheadHold {\n /** The held time while the clock stands elsewhere (clamped), else null. */\n pending(): number | null;\n /** Stop holding. Idempotent. */\n release(): void;\n}\n\n/**\n * Pauses `handle`'s clock at `shown`, a time shown within a pass (`null`: the\n * span's start), placed in the first pass, and keeps seeking there as `declare`\n * or a `duration` write moves the span. A time past the pass's end waits at it.\n * Any other change (a rate, a seek of someone else's) releases the hold. A\n * clock that is not seekable is never held.\n */\nexport function holdAt(handle: ClockHandle, shown: number | null): PlayheadHold {\n return holdWhere(\n handle,\n (clock) => {\n const within = Math.min(shown === null ? 0 : shown - clock.origin, clock.duration);\n return [within, within > 0 && within === clock.duration ? 'end' : 'start'];\n },\n (clock) => (shown === null || clock.origin + clock.elapsed === shown ? null : shown),\n );\n}\n\n/** A recorded position `holdElapsed` holds at. */\nexport type HeldPosition = Pick<ClockPosition, 'elapsed' | 'passEnd' | 'duration' | 'origin'>;\n\n/**\n * As {@link holdAt}, but at a recorded position across the whole run, so a\n * clock of several passes comes back in the pass it stood in. A position\n * recorded under another `duration` or `origin` keeps its pass and the time it\n * showed within it. `pending` reports the `elapsed` it holds for, on the\n * clock's current span.\n */\nexport function holdElapsed(handle: ClockHandle, at: HeldPosition): PlayheadHold {\n return holdWhere(\n handle,\n (clock) => elapsedOn(clock, at),\n (clock) => {\n const [target] = elapsedOn(clock, at);\n return clock.elapsed === target ? null : target;\n },\n );\n}\n\n/** Where `at` stands on `clock`'s span: the same pass, and the same time shown in it. */\nfunction elapsedOn(clock: Clock, at: HeldPosition): [elapsed: number, line: Line] {\n const line: Line = at.passEnd ? 'end' : 'start';\n const from = at.duration ?? clock.duration;\n const origin = at.origin ?? clock.origin;\n if (from === clock.duration && origin === clock.origin) return [at.elapsed, line];\n if (!Number.isFinite(from) || !Number.isFinite(clock.duration))\n return [at.elapsed + origin - clock.origin, line];\n let pass = Math.floor(at.elapsed / from);\n let into = at.elapsed - pass * from;\n if (at.passEnd && into === 0 && pass > 0) {\n pass -= 1;\n into = from;\n }\n const within = Math.min(Math.max(0, origin + into - clock.origin), clock.duration);\n const end = within > 0 && within === clock.duration;\n return [pass * clock.duration + within, end ? 'end' : 'start'];\n}\n\ntype Line = 'start' | 'end';\n\nfunction holdWhere(\n handle: ClockHandle,\n where: (clock: Clock) => [elapsed: number, line: Line],\n wanted: (clock: Clock) => number | null,\n): PlayheadHold {\n const { clock } = handle;\n let released = false;\n let busy = false;\n let unsub: (() => void) | null = null;\n let duration = clock.duration;\n let origin = clock.origin;\n let loop = clock.loop;\n let set = clock.elapsed;\n\n const release = (): void => {\n released = true;\n unsub?.();\n unsub = null;\n };\n\n const place = (): void => {\n busy = true;\n try {\n clock.seek(...where(clock));\n } finally {\n busy = false;\n }\n duration = clock.duration;\n origin = clock.origin;\n loop = clock.loop;\n set = clock.elapsed;\n };\n\n if (!clock.seekable) {\n released = true;\n } else {\n clock.rate = 0;\n place();\n unsub = clock.subscribe(() => {\n if (busy || released) return;\n if (clock.rate !== 0) release();\n else if (clock.duration !== duration || clock.origin !== origin || clock.loop !== loop) place();\n else if (clock.elapsed !== set) release();\n });\n }\n\n return {\n pending: () => (released ? null : wanted(clock)),\n release,\n };\n}\n","import { createEmitter } from '@weasel-js/registry';\nimport type { ClockHandle } from './types';\n\n/** Resolves the view showing a clock: an `Element` where there is a DOM, and\n * opaque to the registry, so its root entry needs no DOM. */\nexport type ClockTarget = () => object | null;\n\n/** A host's clocks: each run's, by id, and one shared among them. An id has at\n * most one, and runs playing on the shared clock all hold that one. */\nexport interface ClockRegistry {\n /** Publish `handle` under `id` and return its release. `target` names the\n * element showing it, resolved when read, so a ref filled later works. */\n register(id: string, handle: ClockHandle, target?: ClockTarget): () => void;\n get(id: string): ClockHandle | null;\n /** The clock shared among the runs, when the host declares one. */\n readonly shared: ClockHandle | null;\n /** The elements showing `handle`, one per registration. Empty when any\n * registration named none, since that one may be seen anywhere. */\n targets(handle: ClockHandle): readonly ClockTarget[];\n /** Every clock once, however many ids hold it. */\n all(): Iterable<ClockHandle>;\n /** Fires after any register or release. Shaped for `useSyncExternalStore`. */\n subscribe(listener: () => void): () => void;\n}\n\n/** An in-memory `ClockRegistry`, holding the shared clock when given one. */\nexport function createClockRegistry(shared: ClockHandle | null = null): ClockRegistry {\n interface Entry {\n handle: ClockHandle;\n target: ClockTarget | undefined;\n }\n const entries = new Map<string, Entry>();\n const changed = createEmitter();\n const notify = changed.emit;\n return {\n register(id, handle, target) {\n const entry: Entry = { handle, target };\n entries.set(id, entry);\n notify();\n return () => {\n if (entries.get(id) !== entry) return;\n entries.delete(id);\n notify();\n };\n },\n get: (id) => entries.get(id)?.handle ?? null,\n targets(handle) {\n const found: ClockTarget[] = [];\n for (const entry of entries.values()) {\n if (entry.handle !== handle) continue;\n if (!entry.target) return [];\n found.push(entry.target);\n }\n return found;\n },\n shared,\n all: () => new Set([...(shared ? [shared] : []), ...[...entries.values()].map((e) => e.handle)]),\n subscribe: changed.subscribe,\n };\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { VisibleRafOptions } from '@weasel-js/react';
2
+ import { d as ClockRegistry } from './registry-C2h_prHD.js';
3
+
4
+ /**
5
+ * The one frame loop for every clock in a registry: syncs each clock that is not
6
+ * inert, and sleeps once every clock is, until one wakes. A clock registered with
7
+ * a target is skipped, and rebased, while every target of it is out of the
8
+ * viewport. Shaped like a blits `ticker`, so one could take its place.
9
+ *
10
+ * `options` supplies the frame clock, for tests.
11
+ */
12
+ declare function useClockLoop(registry: ClockRegistry, options?: Pick<VisibleRafOptions, 'requestFrame' | 'cancelFrame'>): void;
13
+
14
+ export { useClockLoop };
package/dist/react.js ADDED
@@ -0,0 +1,109 @@
1
+ import { useVisibleRaf } from '@weasel-js/react';
2
+ import { useRef, useEffect } from 'react';
3
+
4
+ // src/useClockLoop.ts
5
+ var entriesOf = (registry) => Array.from(registry.all(), (handle) => ({
6
+ handle,
7
+ shared: handle === registry.shared,
8
+ targets: registry.targets(handle)
9
+ }));
10
+ function outOfSight(entry, seen) {
11
+ if (entry.shared || entry.targets.length === 0) return false;
12
+ return entry.targets.every((target) => {
13
+ const el = target();
14
+ return el instanceof Element && seen.get(el) === false;
15
+ });
16
+ }
17
+ function syncObserved(entries, io, seen, named) {
18
+ if (!io) return;
19
+ named.clear();
20
+ for (const { targets } of entries) {
21
+ for (const target of targets) {
22
+ const el = target();
23
+ if (el instanceof Element) named.add(el);
24
+ }
25
+ }
26
+ for (const el of seen.keys()) {
27
+ if (named.has(el)) continue;
28
+ io.unobserve(el);
29
+ seen.delete(el);
30
+ }
31
+ for (const el of named) {
32
+ if (seen.has(el)) continue;
33
+ seen.set(el, true);
34
+ io.observe(el);
35
+ }
36
+ }
37
+ function useClockLoop(registry, options) {
38
+ const seen = useRef(/* @__PURE__ */ new Map());
39
+ const named = useRef(/* @__PURE__ */ new Set());
40
+ const observer = useRef(null);
41
+ const entries = useRef([]);
42
+ const loop = useVisibleRaf(
43
+ (timestamp, loop2) => {
44
+ syncObserved(entries.current, observer.current, seen.current, named.current);
45
+ let live = false;
46
+ for (const entry of entries.current) {
47
+ const { clock } = entry.handle;
48
+ if (outOfSight(entry, seen.current)) {
49
+ clock.rebase();
50
+ continue;
51
+ }
52
+ if (clock.inert) continue;
53
+ clock.sync(timestamp);
54
+ if (!clock.inert) live = true;
55
+ }
56
+ if (live) loop2.request();
57
+ },
58
+ {
59
+ ...options,
60
+ onResume: () => {
61
+ for (const { handle } of entries.current) handle.clock.rebase();
62
+ }
63
+ }
64
+ );
65
+ useEffect(() => {
66
+ const seenNow = seen.current;
67
+ if (typeof IntersectionObserver !== "undefined") {
68
+ observer.current = new IntersectionObserver((entries2) => {
69
+ let returned = false;
70
+ for (const entry of entries2) {
71
+ if (!seenNow.has(entry.target)) continue;
72
+ if (entry.isIntersecting && seenNow.get(entry.target) === false) returned = true;
73
+ seenNow.set(entry.target, entry.isIntersecting);
74
+ }
75
+ if (returned) loop.request();
76
+ });
77
+ }
78
+ return () => {
79
+ observer.current?.disconnect();
80
+ observer.current = null;
81
+ seenNow.clear();
82
+ };
83
+ }, [loop]);
84
+ useEffect(() => {
85
+ let unwakes = [];
86
+ const attach = () => {
87
+ for (const off2 of unwakes) off2();
88
+ unwakes = [];
89
+ entries.current = entriesOf(registry);
90
+ syncObserved(entries.current, observer.current, seen.current, named.current);
91
+ for (const { handle } of entries.current) {
92
+ const { clock } = handle;
93
+ unwakes.push(clock.onWake(() => loop.request()));
94
+ if (!clock.inert) loop.request();
95
+ }
96
+ };
97
+ attach();
98
+ const off = registry.subscribe(attach);
99
+ return () => {
100
+ off();
101
+ for (const unwake of unwakes) unwake();
102
+ entries.current = [];
103
+ };
104
+ }, [registry, loop]);
105
+ }
106
+
107
+ export { useClockLoop };
108
+ //# sourceMappingURL=react.js.map
109
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useClockLoop.ts"],"names":["loop","entries","off"],"mappings":"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","file":"react.js","sourcesContent":["import { useVisibleRaf, type VisibleRafOptions } from '@weasel-js/react';\nimport { useEffect, useRef } from 'react';\nimport type { ClockRegistry, ClockTarget } from './registry';\nimport type { ClockHandle } from './types';\n\n/** A clock the loop syncs, and the targets its registrations name, as of the\n * registry's last change. */\ninterface Entry {\n handle: ClockHandle;\n shared: boolean;\n targets: readonly ClockTarget[];\n}\n\nconst entriesOf = (registry: ClockRegistry): Entry[] =>\n Array.from(registry.all(), (handle) => ({\n handle,\n shared: handle === registry.shared,\n targets: registry.targets(handle),\n }));\n\n/** Whether `entry`'s clock is out of sight: not the shared clock, every\n * registration of it names an element, and none intersects the viewport. A\n * target not yet resolved, not an element, or not yet reported counts as seen. */\nfunction outOfSight(entry: Entry, seen: ReadonlyMap<Element, boolean>): boolean {\n if (entry.shared || entry.targets.length === 0) return false;\n return entry.targets.every((target) => {\n const el = target();\n return el instanceof Element && seen.get(el) === false;\n });\n}\n\n/** Has `io` observe every element a registration names, and lets go of the\n * rest; `named` is scratch, reused across calls. */\nfunction syncObserved(\n entries: readonly Entry[],\n io: IntersectionObserver | null,\n seen: Map<Element, boolean>,\n named: Set<Element>,\n): void {\n if (!io) return;\n named.clear();\n for (const { targets } of entries) {\n for (const target of targets) {\n const el = target();\n if (el instanceof Element) named.add(el);\n }\n }\n for (const el of seen.keys()) {\n if (named.has(el)) continue;\n io.unobserve(el);\n seen.delete(el);\n }\n for (const el of named) {\n if (seen.has(el)) continue;\n seen.set(el, true);\n io.observe(el);\n }\n}\n\n/**\n * The one frame loop for every clock in a registry: syncs each clock that is not\n * inert, and sleeps once every clock is, until one wakes. A clock registered with\n * a target is skipped, and rebased, while every target of it is out of the\n * viewport. Shaped like a blits `ticker`, so one could take its place.\n *\n * `options` supplies the frame clock, for tests.\n */\nexport function useClockLoop(\n registry: ClockRegistry,\n options?: Pick<VisibleRafOptions, 'requestFrame' | 'cancelFrame'>,\n): void {\n const seen = useRef(new Map<Element, boolean>());\n const named = useRef(new Set<Element>());\n const observer = useRef<IntersectionObserver | null>(null);\n const entries = useRef<readonly Entry[]>([]);\n\n const loop = useVisibleRaf(\n (timestamp, loop) => {\n syncObserved(entries.current, observer.current, seen.current, named.current);\n let live = false;\n for (const entry of entries.current) {\n const { clock } = entry.handle;\n if (outOfSight(entry, seen.current)) {\n clock.rebase();\n continue;\n }\n if (clock.inert) continue;\n clock.sync(timestamp);\n if (!clock.inert) live = true;\n }\n if (live) loop.request();\n },\n {\n ...options,\n onResume: () => {\n for (const { handle } of entries.current) handle.clock.rebase();\n },\n },\n );\n\n useEffect(() => {\n const seenNow = seen.current;\n if (typeof IntersectionObserver !== 'undefined') {\n observer.current = new IntersectionObserver((entries) => {\n let returned = false;\n for (const entry of entries) {\n if (!seenNow.has(entry.target)) continue;\n if (entry.isIntersecting && seenNow.get(entry.target) === false) returned = true;\n seenNow.set(entry.target, entry.isIntersecting);\n }\n if (returned) loop.request();\n });\n }\n return () => {\n observer.current?.disconnect();\n observer.current = null;\n seenNow.clear();\n };\n }, [loop]);\n\n useEffect(() => {\n let unwakes: (() => void)[] = [];\n const attach = (): void => {\n for (const off of unwakes) off();\n unwakes = [];\n entries.current = entriesOf(registry);\n syncObserved(entries.current, observer.current, seen.current, named.current);\n for (const { handle } of entries.current) {\n const { clock } = handle;\n unwakes.push(clock.onWake(() => loop.request()));\n if (!clock.inert) loop.request();\n }\n };\n attach();\n const off = registry.subscribe(attach);\n return () => {\n off();\n for (const unwake of unwakes) unwake();\n entries.current = [];\n };\n }, [registry, loop]);\n}\n"]}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * What a caller declares to get a clock: how long a pass runs, how many
3
+ * passes, and how it opens. Shaped like a blits voice spec.
4
+ */
5
+ interface ClockSpec {
6
+ /** ms per pass. Omitted, the run never ends. `Clock.duration` changes
7
+ * it later, for a run whose length follows its content. */
8
+ duration?: number;
9
+ /** Passes: `true` endless, `false` one, `n` that many. Default `false`. */
10
+ loop?: boolean | number;
11
+ /** The rate it opens at; `0`, the default, opens paused. */
12
+ rate?: number;
13
+ /** Where a new run opens, in ms, clamped to the run; `'end'` is the end of
14
+ * its last pass. Default `0`. A reopened run opens where it stood. */
15
+ start?: number | 'end';
16
+ /** The time `elapsed` 0 is shown as, in ms. Display only: nothing in the
17
+ * run moves with it. Default `0`. */
18
+ origin?: number;
19
+ /** The unsigned speed a transport plays a paused clock at. Default `|rate|`,
20
+ * or `1` for a clock that opens paused. */
21
+ speed?: number;
22
+ /** The speeds a transport offers, unsigned. Default `CLOCK_RATES`. */
23
+ rates?: readonly number[];
24
+ /** `false` for a clock whose state is built up by running, so time can
25
+ * only move by running: no seek and no negative rate. Default `true`. */
26
+ seekable?: boolean;
27
+ /** ms a frame when the host runs the clock forward with nothing shown, which
28
+ * is how a still of a clock that is not seekable reaches its time. Omitted,
29
+ * such a still stays where the clock opens. */
30
+ step?: number;
31
+ /** Makes a blits mix, which then plays on this clock: its mix time is kept
32
+ * at `elapsed`, synced forward and sought back, so scrubbing and reverse
33
+ * reach it. Give it `history` with a `tape` reaching back over the whole
34
+ * run, or a seek past it throws. Leave the mix's own `rate` alone; the
35
+ * clock's is the one that plays. */
36
+ mix?: () => ClockedMix;
37
+ }
38
+ /** What a clock needs of a blits `Mix`. */
39
+ interface ClockedMix {
40
+ sync(timestamp: number): void;
41
+ seek(time: number): void;
42
+ readonly now: number;
43
+ }
44
+ /**
45
+ * A run of time. A position (`elapsed`) moved by a signed `rate`, synced by a
46
+ * frame loop once a frame. Named after blits' owner handle and `Ticked`, so a
47
+ * blits ticker can drive one and blits voices can play under it.
48
+ */
49
+ interface Clock {
50
+ /** ms the run has played, rate applied, counted across passes. */
51
+ readonly elapsed: number;
52
+ /** Which pass `elapsed` falls in, 0 first; the last once the run is over. */
53
+ readonly pass: number;
54
+ /** How far through its pass, 0 to 1; 1 once the run is over. 0 with no duration. */
55
+ readonly phase: number;
56
+ /** ms per pass; `Infinity` for a run that never ends. A write keeps `pass`
57
+ * and `phase`, so the playhead holds its place in the content. */
58
+ duration: number;
59
+ /** The time `elapsed` 0 is shown as; display only. */
60
+ readonly origin: number;
61
+ /** The unsigned speed a transport plays a paused clock at. */
62
+ readonly speed: number;
63
+ /** The speeds a transport offers, unsigned and ascending. */
64
+ readonly rates: readonly number[];
65
+ /** Whether `seek` and a negative `rate` are allowed. */
66
+ readonly seekable: boolean;
67
+ /** A finite run has played to its end. */
68
+ readonly ended: boolean;
69
+ /** Signed: 0 pauses and a negative rate plays backward. A write changes speed
70
+ * at once and replaces any ramp. */
71
+ rate: number;
72
+ /** Passes, as `ClockSpec.loop`. A write that shortens the run past `elapsed`
73
+ * keeps the time within the pass, in the last pass left. */
74
+ loop: boolean | number;
75
+ /** Move `rate` to `to` linearly over `over` real ms; position stays continuous. */
76
+ ramp(to: number, over: number): void;
77
+ /** Move to `elapsed` ms from the start, clamped to the run. Never runs state
78
+ * forward to meet it. Throws on a clock that is not seekable. A time on the
79
+ * line between two passes is the start of the later one; `'end'` stands at
80
+ * the end of the earlier, `phase` 1, until the clock next moves. */
81
+ seek(elapsed: number, line?: 'start' | 'end'): void;
82
+ /** The frame loop reports the time, once a frame. */
83
+ sync(timestamp: number): void;
84
+ /** The time between now and the next sync does not count. */
85
+ rebase(): void;
86
+ /** Another frame would change nothing: paused, no ramp, nothing changed since
87
+ * the last sync. */
88
+ readonly inert: boolean;
89
+ /** Called when a change needs frames again, once until the next sync. */
90
+ onWake(fn: () => void): () => void;
91
+ /** Called on a rate, seek or loop change, and when the run ends; never per frame. */
92
+ subscribe(fn: () => void): () => void;
93
+ }
94
+ /** Where a clock stood, as a record keeps it. */
95
+ interface ClockPosition {
96
+ elapsed: number;
97
+ rate: number;
98
+ /** Absent when the clock still ran at its declared duration. */
99
+ duration?: number;
100
+ /** Absent when the clock still showed its declared origin. */
101
+ origin?: number;
102
+ /** It stood at the end of a pass another follows, as `seek(…, 'end')` leaves it. */
103
+ passEnd?: true;
104
+ }
105
+ /** A clock, and what its host does with it. */
106
+ interface ClockHandle {
107
+ clock: Clock;
108
+ /** The mix `ClockSpec.mix` made, playing on `clock`. */
109
+ mix: ClockedMix | null;
110
+ /** `ClockSpec.step`. */
111
+ step?: number | undefined;
112
+ /** Back to where it opened, at the declared rate — a Reset,
113
+ * allowed on a clock that is not seekable. */
114
+ reset(): void;
115
+ /** Takes up `spec`'s `duration`, `origin` and `speed`, and its `loop` when
116
+ * that differs from the last declaration's. Keeps the pass and the time
117
+ * shown within it (`origin` plus ms into the pass), clamped to the new pass
118
+ * and run; play state and rate are left alone. Notifies like a seek. Unlike
119
+ * assigning `duration`, this does not keep `phase`. */
120
+ declare(spec: ClockSpec): void;
121
+ /** Replaces the declared `duration` and `origin` with `span`'s, where it
122
+ * gives them, until called with `null`; a later `declare` changes what lies
123
+ * under it, not the override. Keeps the pass and shown time like `declare`. */
124
+ override(span: {
125
+ duration?: number;
126
+ origin?: number;
127
+ } | null): void;
128
+ /** Called after each sync that moved the clock or followed a change. */
129
+ onFrame(fn: (elapsed: number, pass: number) => void): () => void;
130
+ }
131
+
132
+ /** Resolves the view showing a clock: an `Element` where there is a DOM, and
133
+ * opaque to the registry, so its root entry needs no DOM. */
134
+ type ClockTarget = () => object | null;
135
+ /** A host's clocks: each run's, by id, and one shared among them. An id has at
136
+ * most one, and runs playing on the shared clock all hold that one. */
137
+ interface ClockRegistry {
138
+ /** Publish `handle` under `id` and return its release. `target` names the
139
+ * element showing it, resolved when read, so a ref filled later works. */
140
+ register(id: string, handle: ClockHandle, target?: ClockTarget): () => void;
141
+ get(id: string): ClockHandle | null;
142
+ /** The clock shared among the runs, when the host declares one. */
143
+ readonly shared: ClockHandle | null;
144
+ /** The elements showing `handle`, one per registration. Empty when any
145
+ * registration named none, since that one may be seen anywhere. */
146
+ targets(handle: ClockHandle): readonly ClockTarget[];
147
+ /** Every clock once, however many ids hold it. */
148
+ all(): Iterable<ClockHandle>;
149
+ /** Fires after any register or release. Shaped for `useSyncExternalStore`. */
150
+ subscribe(listener: () => void): () => void;
151
+ }
152
+ /** An in-memory `ClockRegistry`, holding the shared clock when given one. */
153
+ declare function createClockRegistry(shared?: ClockHandle | null): ClockRegistry;
154
+
155
+ export { type Clock as C, type ClockPosition as a, type ClockSpec as b, type ClockHandle as c, type ClockRegistry as d, type ClockTarget as e, type ClockedMix as f, createClockRegistry as g };
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "@weasel-js/clock",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.13.0",
4
+ "description": "A clock for a run of time: a position moved by a signed rate, with passes, ramps and seeking. The root entry has no React and no DOM.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./react": {
17
+ "import": "./dist/react.js",
18
+ "types": "./dist/react.d.ts"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=18"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "react": {
27
+ "optional": true
28
+ }
29
+ },
30
+ "dependencies": {
31
+ "@weasel-js/react": "1.13.0",
32
+ "@weasel-js/registry": "1.13.0"
33
+ },
34
+ "devDependencies": {
35
+ "@msb235/blits": "0.12.0",
36
+ "@weasel-js/history": "1.13.0"
37
+ },
38
+ "author": "orochi235",
39
+ "homepage": "https://orochi235.github.io/weasel/",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/orochi235/weasel.git",
43
+ "directory": "packages/clock"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/orochi235/weasel/issues"
47
+ },
48
+ "engines": {
49
+ "node": ">=22"
50
+ },
51
+ "files": [
52
+ "dist",
53
+ "README.md",
54
+ "LICENSE"
55
+ ],
56
+ "scripts": {
57
+ "build": "tsup"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public",
61
+ "provenance": true
62
+ }
63
+ }