aeon-dom 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 +52 -0
- package/dist/animationFrames.d.ts +15 -0
- package/dist/animationFrames.d.ts.map +1 -0
- package/dist/behaviors.d.ts +37 -0
- package/dist/behaviors.d.ts.map +1 -0
- package/dist/events.d.ts +18 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/index.cjs +346 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +341 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +10 -0
- package/dist/internal.d.ts.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# aeon-dom
|
|
2
|
+
|
|
3
|
+
DOM event sources and continuous-time browser Behaviors for [Aeon](https://github.com/joshburgess/aeon).
|
|
4
|
+
|
|
5
|
+
This package provides:
|
|
6
|
+
|
|
7
|
+
- **`fromDOMEvent(type, target, options?)`** — convert any DOM `EventTarget` event into an `Event<E, never>`
|
|
8
|
+
- **`animationFrames(scheduler)`** — `Event<DOMHighResTimeStamp, never>` driven by `requestAnimationFrame`
|
|
9
|
+
- **`mousePosition(scheduler)`** — `Behavior<{x, y}, never>` tracking the cursor
|
|
10
|
+
- **`windowSize(scheduler)`** — `Behavior<{width, height}, never>` tracking viewport size
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add aeon-core aeon-scheduler aeon-dom
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { observe } from "aeon-core";
|
|
22
|
+
import { DefaultScheduler } from "aeon-scheduler";
|
|
23
|
+
import { fromDOMEvent } from "aeon-dom";
|
|
24
|
+
|
|
25
|
+
const scheduler = new DefaultScheduler();
|
|
26
|
+
|
|
27
|
+
const clicks = fromDOMEvent("click", document.body);
|
|
28
|
+
observe((e) => console.log("clicked at", e.clientX, e.clientY), clicks, scheduler);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Continuous Behaviors
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { mousePosition, windowSize } from "aeon-dom";
|
|
35
|
+
import { liftA2B, readBehavior } from "aeon-core";
|
|
36
|
+
import { toTime } from "aeon-types";
|
|
37
|
+
|
|
38
|
+
const mouse = mousePosition(scheduler);
|
|
39
|
+
const size = windowSize(scheduler);
|
|
40
|
+
|
|
41
|
+
const relativeX = liftA2B((m, s) => m.x / s.width, mouse, size);
|
|
42
|
+
console.log(readBehavior(relativeX, toTime(performance.now())));
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Documentation
|
|
46
|
+
|
|
47
|
+
- [Main README](https://github.com/joshburgess/aeon#readme)
|
|
48
|
+
- [Getting Started](https://github.com/joshburgess/aeon/blob/main/docs/getting-started.md)
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation frame Event source.
|
|
3
|
+
*
|
|
4
|
+
* Emits a DOMHighResTimeStamp on each requestAnimationFrame callback.
|
|
5
|
+
*/
|
|
6
|
+
import type { Event as PulseEvent } from "aeon-types";
|
|
7
|
+
/**
|
|
8
|
+
* An Event that emits a DOMHighResTimeStamp on each animation frame.
|
|
9
|
+
*
|
|
10
|
+
* Denotation: `[(t, timestamp) | each requestAnimationFrame callback]`
|
|
11
|
+
*
|
|
12
|
+
* Cancels the animation frame loop when disposed.
|
|
13
|
+
*/
|
|
14
|
+
export declare const animationFrames: PulseEvent<DOMHighResTimeStamp, never>;
|
|
15
|
+
//# sourceMappingURL=animationFrames.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animationFrames.d.ts","sourceRoot":"","sources":["../src/animationFrames.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAc,KAAK,IAAI,UAAU,EAA2B,MAAM,YAAY,CAAC;AA2B3F;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,UAAU,CAAC,mBAAmB,EAAE,KAAK,CAC9B,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM Behaviors — continuous time-varying values derived from the DOM.
|
|
3
|
+
*
|
|
4
|
+
* These create Behaviors that are push-updated from DOM events and
|
|
5
|
+
* pull-sampled when read. Each returns [Behavior, Disposable] since
|
|
6
|
+
* they subscribe to DOM events internally.
|
|
7
|
+
*/
|
|
8
|
+
import type { Behavior, Disposable, Scheduler } from "aeon-types";
|
|
9
|
+
/** 2D point for mouse coordinates. */
|
|
10
|
+
export interface Point {
|
|
11
|
+
readonly x: number;
|
|
12
|
+
readonly y: number;
|
|
13
|
+
}
|
|
14
|
+
/** Dimensions for window size. */
|
|
15
|
+
export interface Size {
|
|
16
|
+
readonly width: number;
|
|
17
|
+
readonly height: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A Behavior holding the current mouse position.
|
|
21
|
+
*
|
|
22
|
+
* Denotation: `t => { x: mouseX(t), y: mouseY(t) }`
|
|
23
|
+
*
|
|
24
|
+
* Push-updated from mousemove events on the given target (defaults to document).
|
|
25
|
+
* Returns [Behavior, Disposable] — dispose to stop listening.
|
|
26
|
+
*/
|
|
27
|
+
export declare const mousePosition: (scheduler: Scheduler, target?: EventTarget) => [Behavior<Point, never>, Disposable];
|
|
28
|
+
/**
|
|
29
|
+
* A Behavior holding the current window dimensions.
|
|
30
|
+
*
|
|
31
|
+
* Denotation: `t => { width: innerWidth(t), height: innerHeight(t) }`
|
|
32
|
+
*
|
|
33
|
+
* Push-updated from resize events on window.
|
|
34
|
+
* Returns [Behavior, Disposable] — dispose to stop listening.
|
|
35
|
+
*/
|
|
36
|
+
export declare const windowSize: (scheduler: Scheduler) => [Behavior<Size, never>, Disposable];
|
|
37
|
+
//# sourceMappingURL=behaviors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"behaviors.d.ts","sourceRoot":"","sources":["../src/behaviors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGlE,sCAAsC;AACtC,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,kCAAkC;AAClC,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GACxB,WAAW,SAAS,EACpB,SAAQ,WAAsB,KAC7B,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,UAAU,CAMrC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAI,WAAW,SAAS,KAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,UAAU,CAWnF,CAAC"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM Event sources.
|
|
3
|
+
*
|
|
4
|
+
* Creates pulse Event streams from DOM EventTarget events.
|
|
5
|
+
*/
|
|
6
|
+
import type { Event as PulseEvent } from "aeon-types";
|
|
7
|
+
/**
|
|
8
|
+
* Create a pulse Event from a DOM event.
|
|
9
|
+
*
|
|
10
|
+
* Denotation: `[(t, domEvent) | domEvent fires on target at time t]`
|
|
11
|
+
*
|
|
12
|
+
* Automatically removes the event listener when disposed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function fromDOMEvent<K extends keyof HTMLElementEventMap>(type: K, target: HTMLElement, options?: AddEventListenerOptions): PulseEvent<HTMLElementEventMap[K], never>;
|
|
15
|
+
export declare function fromDOMEvent<K extends keyof WindowEventMap>(type: K, target: Window, options?: AddEventListenerOptions): PulseEvent<WindowEventMap[K], never>;
|
|
16
|
+
export declare function fromDOMEvent<K extends keyof DocumentEventMap>(type: K, target: Document, options?: AddEventListenerOptions): PulseEvent<DocumentEventMap[K], never>;
|
|
17
|
+
export declare function fromDOMEvent(type: string, target: EventTarget, options?: AddEventListenerOptions): PulseEvent<Event, never>;
|
|
18
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAc,KAAK,IAAI,UAAU,EAA2B,MAAM,YAAY,CAAC;AA2B3F;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,mBAAmB,EAC9D,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE,uBAAuB,GAChC,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC7C,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,cAAc,EACzD,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,uBAAuB,GAChC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACxC,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAC3D,IAAI,EAAE,CAAC,EACP,MAAM,EAAE,QAAQ,EAChB,OAAO,CAAC,EAAE,uBAAuB,GAChC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC1C,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE,uBAAuB,GAChC,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Internal helpers for aeon-dom.
|
|
5
|
+
*
|
|
6
|
+
* At runtime, Event<A, E> IS Source<A, E> — the opaque type brand is
|
|
7
|
+
* purely type-level. This mirrors the zero-cost identity cast in aeon-core.
|
|
8
|
+
*/ /** Create an opaque Event from a Source. Zero-cost identity cast. */ const createEvent = (source)=>source;
|
|
9
|
+
|
|
10
|
+
let DOMEventSource = class DOMEventSource {
|
|
11
|
+
constructor(type, target, options){
|
|
12
|
+
this.type = type;
|
|
13
|
+
this.target = target;
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
run(sink, scheduler) {
|
|
17
|
+
const handler = (e)=>{
|
|
18
|
+
sink.event(scheduler.currentTime(), e);
|
|
19
|
+
};
|
|
20
|
+
this.target.addEventListener(this.type, handler, this.options);
|
|
21
|
+
return {
|
|
22
|
+
dispose: ()=>{
|
|
23
|
+
this.target.removeEventListener(this.type, handler, this.options);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function fromDOMEvent(type, target, options) {
|
|
29
|
+
return createEvent(new DOMEventSource(type, target, options));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let AnimationFrameSource = class AnimationFrameSource {
|
|
33
|
+
run(sink, scheduler) {
|
|
34
|
+
let id = 0;
|
|
35
|
+
let disposed = false;
|
|
36
|
+
const tick = (timestamp)=>{
|
|
37
|
+
if (disposed) return;
|
|
38
|
+
sink.event(scheduler.currentTime(), timestamp);
|
|
39
|
+
id = requestAnimationFrame(tick);
|
|
40
|
+
};
|
|
41
|
+
id = requestAnimationFrame(tick);
|
|
42
|
+
return {
|
|
43
|
+
dispose () {
|
|
44
|
+
disposed = true;
|
|
45
|
+
cancelAnimationFrame(id);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const ANIMATION_FRAME_SOURCE = new AnimationFrameSource();
|
|
51
|
+
/**
|
|
52
|
+
* An Event that emits a DOMHighResTimeStamp on each animation frame.
|
|
53
|
+
*
|
|
54
|
+
* Denotation: `[(t, timestamp) | each requestAnimationFrame callback]`
|
|
55
|
+
*
|
|
56
|
+
* Cancels the animation frame loop when disposed.
|
|
57
|
+
*/ const animationFrames = createEvent(ANIMATION_FRAME_SOURCE);
|
|
58
|
+
|
|
59
|
+
/** A no-op disposable. */ const disposeNone = {
|
|
60
|
+
dispose () {}
|
|
61
|
+
};
|
|
62
|
+
/** Create an opaque Event from a Source. Zero-cost identity cast. */ const _createEvent = (source)=>source;
|
|
63
|
+
/** Extract the Source from an opaque Event. Zero-cost identity cast. */ const _getSource = (event)=>event;
|
|
64
|
+
// --- Source classes for V8 hidden class stability ---
|
|
65
|
+
let EmptySource = class EmptySource {
|
|
66
|
+
constructor(){
|
|
67
|
+
this._sync = true;
|
|
68
|
+
}
|
|
69
|
+
run(sink, scheduler) {
|
|
70
|
+
sink.end(scheduler.currentTime());
|
|
71
|
+
return disposeNone;
|
|
72
|
+
}
|
|
73
|
+
syncIterate(_emit) {}
|
|
74
|
+
};
|
|
75
|
+
let NowSource = class NowSource {
|
|
76
|
+
constructor(value){
|
|
77
|
+
this.value = value;
|
|
78
|
+
this._sync = true;
|
|
79
|
+
}
|
|
80
|
+
run(sink, scheduler) {
|
|
81
|
+
const t = scheduler.currentTime();
|
|
82
|
+
sink.event(t, this.value);
|
|
83
|
+
sink.end(t);
|
|
84
|
+
return disposeNone;
|
|
85
|
+
}
|
|
86
|
+
syncIterate(emit) {
|
|
87
|
+
emit(this.value);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
// --- Singletons for empty/never ---
|
|
91
|
+
const EMPTY_SOURCE = new EmptySource();
|
|
92
|
+
/**
|
|
93
|
+
* Pipe base class for sinks that forward error/end unchanged.
|
|
94
|
+
*
|
|
95
|
+
* With ES2022 target, `extends Pipe` compiles to native `class extends` —
|
|
96
|
+
* zero helpers, zero overhead. V8 devirtualizes the shared error/end
|
|
97
|
+
* methods across all sink subtypes.
|
|
98
|
+
*/ let Pipe = class Pipe {
|
|
99
|
+
constructor(sink){
|
|
100
|
+
this.sink = sink;
|
|
101
|
+
}
|
|
102
|
+
error(time, err) {
|
|
103
|
+
this.sink.error(time, err);
|
|
104
|
+
}
|
|
105
|
+
end(time) {
|
|
106
|
+
this.sink.end(time);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
// --- Sink classes ---
|
|
110
|
+
/** Map sink: applies f to each value. */ let MapSink = class MapSink extends Pipe {
|
|
111
|
+
constructor(f, sink){
|
|
112
|
+
super(sink);
|
|
113
|
+
this.f = f;
|
|
114
|
+
}
|
|
115
|
+
event(time, value) {
|
|
116
|
+
const f = this.f;
|
|
117
|
+
this.sink.event(time, f(value));
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
/** Filter sink: only forwards values that pass the predicate. */ let FilterSink = class FilterSink extends Pipe {
|
|
121
|
+
constructor(predicate, sink){
|
|
122
|
+
super(sink);
|
|
123
|
+
this.predicate = predicate;
|
|
124
|
+
}
|
|
125
|
+
event(time, value) {
|
|
126
|
+
const p = this.predicate;
|
|
127
|
+
if (p(value)) {
|
|
128
|
+
this.sink.event(time, value);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/** Combined filter+map sink: filter then map in one node. */ let FilterMapSink = class FilterMapSink extends Pipe {
|
|
133
|
+
constructor(predicate, f, sink){
|
|
134
|
+
super(sink);
|
|
135
|
+
this.predicate = predicate;
|
|
136
|
+
this.f = f;
|
|
137
|
+
}
|
|
138
|
+
event(time, value) {
|
|
139
|
+
const p = this.predicate;
|
|
140
|
+
if (p(value)) {
|
|
141
|
+
const f = this.f;
|
|
142
|
+
this.sink.event(time, f(value));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
/** Combined map+filter sink: map then filter in one node. */ let MapFilterSink = class MapFilterSink extends Pipe {
|
|
147
|
+
constructor(f, predicate, sink){
|
|
148
|
+
super(sink);
|
|
149
|
+
this.f = f;
|
|
150
|
+
this.predicate = predicate;
|
|
151
|
+
}
|
|
152
|
+
event(time, value) {
|
|
153
|
+
const f = this.f;
|
|
154
|
+
const mapped = f(value);
|
|
155
|
+
const p = this.predicate;
|
|
156
|
+
if (p(mapped)) {
|
|
157
|
+
this.sink.event(time, mapped);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
// --- Source classes (for instanceof fusion detection) ---
|
|
162
|
+
/** A map source, tagged for fusion detection via instanceof. */ let MapSource = class MapSource {
|
|
163
|
+
constructor(f, source){
|
|
164
|
+
this.f = f;
|
|
165
|
+
this.source = source;
|
|
166
|
+
}
|
|
167
|
+
run(sink, scheduler) {
|
|
168
|
+
return this.source.run(new MapSink(this.f, sink), scheduler);
|
|
169
|
+
}
|
|
170
|
+
/** Factory with fusion and algebraic simplification. */ static create(f, source) {
|
|
171
|
+
// map(f, empty()) → empty()
|
|
172
|
+
if (source instanceof EmptySource) {
|
|
173
|
+
return EMPTY_SOURCE;
|
|
174
|
+
}
|
|
175
|
+
// map(f, now(x)) → now(f(x)) — constant folding
|
|
176
|
+
if (source instanceof NowSource) {
|
|
177
|
+
return new NowSource(f(source.value));
|
|
178
|
+
}
|
|
179
|
+
// map(f, map(g, s)) → map(f∘g, s)
|
|
180
|
+
if (source instanceof MapSource) {
|
|
181
|
+
const inner = source;
|
|
182
|
+
return new MapSource((x)=>f(inner.f(x)), inner.source);
|
|
183
|
+
}
|
|
184
|
+
// map(f, filter(p, s)) → filterMap(p, f, s)
|
|
185
|
+
if (source instanceof FilterSource) {
|
|
186
|
+
const inner = source;
|
|
187
|
+
return new FilterMapSource(inner.predicate, f, inner.source);
|
|
188
|
+
}
|
|
189
|
+
return new MapSource(f, source);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
/** A filter source, tagged for fusion detection via instanceof. */ let FilterSource = class FilterSource {
|
|
193
|
+
constructor(predicate, source){
|
|
194
|
+
this.predicate = predicate;
|
|
195
|
+
this.source = source;
|
|
196
|
+
}
|
|
197
|
+
run(sink, scheduler) {
|
|
198
|
+
return this.source.run(new FilterSink(this.predicate, sink), scheduler);
|
|
199
|
+
}
|
|
200
|
+
/** Factory with fusion and algebraic simplification. */ static create(predicate, source) {
|
|
201
|
+
// filter(p, empty()) → empty()
|
|
202
|
+
if (source instanceof EmptySource) {
|
|
203
|
+
return EMPTY_SOURCE;
|
|
204
|
+
}
|
|
205
|
+
// filter(p, now(x)) → p(x) ? now(x) : empty() — constant folding
|
|
206
|
+
if (source instanceof NowSource) {
|
|
207
|
+
const val = source.value;
|
|
208
|
+
return predicate(val) ? source : EMPTY_SOURCE;
|
|
209
|
+
}
|
|
210
|
+
// filter(p, filter(q, s)) → filter(x => q(x) && p(x), s)
|
|
211
|
+
if (source instanceof FilterSource) {
|
|
212
|
+
const inner = source;
|
|
213
|
+
return new FilterSource((x)=>inner.predicate(x) && predicate(x), inner.source);
|
|
214
|
+
}
|
|
215
|
+
// filter(p, map(f, s)) → mapFilter(f, p, s)
|
|
216
|
+
if (source instanceof MapSource) {
|
|
217
|
+
const inner = source;
|
|
218
|
+
return new MapFilterSource(inner.f, predicate, inner.source);
|
|
219
|
+
}
|
|
220
|
+
return new FilterSource(predicate, source);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
/** Fused filter-then-map source. */ let FilterMapSource = class FilterMapSource {
|
|
224
|
+
constructor(predicate, f, source){
|
|
225
|
+
this.predicate = predicate;
|
|
226
|
+
this.f = f;
|
|
227
|
+
this.source = source;
|
|
228
|
+
}
|
|
229
|
+
run(sink, scheduler) {
|
|
230
|
+
return this.source.run(new FilterMapSink(this.predicate, this.f, sink), scheduler);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
/** Fused map-then-filter source. */ let MapFilterSource = class MapFilterSource {
|
|
234
|
+
constructor(f, predicate, source){
|
|
235
|
+
this.f = f;
|
|
236
|
+
this.predicate = predicate;
|
|
237
|
+
this.source = source;
|
|
238
|
+
}
|
|
239
|
+
run(sink, scheduler) {
|
|
240
|
+
return this.source.run(new MapFilterSink(this.f, this.predicate, sink), scheduler);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
// --- Public API ---
|
|
244
|
+
/**
|
|
245
|
+
* Create a fusible map Event. Detects map∘map and composes functions.
|
|
246
|
+
*/ const fusedMap = (f, event)=>{
|
|
247
|
+
const source = _getSource(event);
|
|
248
|
+
return _createEvent(MapSource.create(f, source));
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Transform each value in an Event stream.
|
|
252
|
+
*
|
|
253
|
+
* Denotation: `map(f, e) = [(t, f(v)) | (t, v) ∈ e]`
|
|
254
|
+
*/ const map$1 = (f, event)=>fusedMap(f, event);
|
|
255
|
+
// --- Internal tag ---
|
|
256
|
+
const BEHAVIOR_KEY = Symbol("pulse/behavior");
|
|
257
|
+
/** Create an opaque Behavior from a BehaviorImpl. Internal use only. */ const _createBehavior = (impl)=>({
|
|
258
|
+
[BEHAVIOR_KEY]: impl
|
|
259
|
+
});
|
|
260
|
+
// --- Stepper subscription ---
|
|
261
|
+
/**
|
|
262
|
+
* Subscribe a stepper behavior to an event stream.
|
|
263
|
+
* Returns a Disposable to unsubscribe.
|
|
264
|
+
*/ const subscribeStepperToEvent = (stepperImpl, event, scheduler)=>{
|
|
265
|
+
const source = _getSource(event);
|
|
266
|
+
const sink = {
|
|
267
|
+
event (time, value) {
|
|
268
|
+
stepperImpl.value = value;
|
|
269
|
+
stepperImpl.time = time;
|
|
270
|
+
stepperImpl.generation++;
|
|
271
|
+
},
|
|
272
|
+
error () {},
|
|
273
|
+
end () {}
|
|
274
|
+
};
|
|
275
|
+
return source.run(sink, scheduler);
|
|
276
|
+
};
|
|
277
|
+
// --- Event ↔ Behavior Bridge ---
|
|
278
|
+
/**
|
|
279
|
+
* Create a Behavior that holds the latest value from an Event.
|
|
280
|
+
*
|
|
281
|
+
* Denotation: `stepper(init, e) = t => latestValue(e, t) ?? init`
|
|
282
|
+
*
|
|
283
|
+
* This is the primary push→pull bridge. The returned behavior is
|
|
284
|
+
* push-updated when the event fires, and pull-sampled when read.
|
|
285
|
+
*
|
|
286
|
+
* IMPORTANT: The caller must provide a scheduler to subscribe to the event.
|
|
287
|
+
* Returns [Behavior, Disposable] — the disposable unsubscribes from the event.
|
|
288
|
+
*/ const stepper = (initial, event, scheduler)=>{
|
|
289
|
+
const impl = {
|
|
290
|
+
tag: "stepper",
|
|
291
|
+
initial,
|
|
292
|
+
value: initial,
|
|
293
|
+
time: scheduler.currentTime(),
|
|
294
|
+
generation: 0
|
|
295
|
+
};
|
|
296
|
+
const disposable = subscribeStepperToEvent(impl, event, scheduler);
|
|
297
|
+
return [
|
|
298
|
+
_createBehavior(impl),
|
|
299
|
+
disposable
|
|
300
|
+
];
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* A Behavior holding the current mouse position.
|
|
305
|
+
*
|
|
306
|
+
* Denotation: `t => { x: mouseX(t), y: mouseY(t) }`
|
|
307
|
+
*
|
|
308
|
+
* Push-updated from mousemove events on the given target (defaults to document).
|
|
309
|
+
* Returns [Behavior, Disposable] — dispose to stop listening.
|
|
310
|
+
*/ const mousePosition = (scheduler, target = document)=>{
|
|
311
|
+
const moves = map$1((e)=>({
|
|
312
|
+
x: e.clientX,
|
|
313
|
+
y: e.clientY
|
|
314
|
+
}), fromDOMEvent("mousemove", target));
|
|
315
|
+
return stepper({
|
|
316
|
+
x: 0,
|
|
317
|
+
y: 0
|
|
318
|
+
}, moves, scheduler);
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* A Behavior holding the current window dimensions.
|
|
322
|
+
*
|
|
323
|
+
* Denotation: `t => { width: innerWidth(t), height: innerHeight(t) }`
|
|
324
|
+
*
|
|
325
|
+
* Push-updated from resize events on window.
|
|
326
|
+
* Returns [Behavior, Disposable] — dispose to stop listening.
|
|
327
|
+
*/ const windowSize = (scheduler)=>{
|
|
328
|
+
const initial = typeof window !== "undefined" ? {
|
|
329
|
+
width: window.innerWidth,
|
|
330
|
+
height: window.innerHeight
|
|
331
|
+
} : {
|
|
332
|
+
width: 0,
|
|
333
|
+
height: 0
|
|
334
|
+
};
|
|
335
|
+
const resizes = map$1(()=>({
|
|
336
|
+
width: window.innerWidth,
|
|
337
|
+
height: window.innerHeight
|
|
338
|
+
}), fromDOMEvent("resize", window));
|
|
339
|
+
return stepper(initial, resizes, scheduler);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
exports.animationFrames = animationFrames;
|
|
343
|
+
exports.fromDOMEvent = fromDOMEvent;
|
|
344
|
+
exports.mousePosition = mousePosition;
|
|
345
|
+
exports.windowSize = windowSize;
|
|
346
|
+
//# sourceMappingURL=index.cjs.map
|