foldkit-primitives 0.2.1 → 0.4.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 +514 -43
- package/dist/device/index.d.mts +11 -11
- package/dist/device/index.d.mts.map +1 -1
- package/dist/device/index.mjs +95 -87
- package/dist/device/index.mjs.map +1 -1
- package/dist/dom/index.d.mts +71 -1
- package/dist/dom/index.d.mts.map +1 -1
- package/dist/dom/index.mjs +303 -5
- package/dist/dom/index.mjs.map +1 -1
- package/dist/editable-text-CKkS_mWt.d.mts +235 -0
- package/dist/editable-text-CKkS_mWt.d.mts.map +1 -0
- package/dist/events/index.d.mts +10 -1
- package/dist/events/index.d.mts.map +1 -1
- package/dist/events/index.mjs +34 -18
- package/dist/events/index.mjs.map +1 -1
- package/dist/follow-tab-stop-DpxwLXvU.mjs +800 -0
- package/dist/follow-tab-stop-DpxwLXvU.mjs.map +1 -0
- package/dist/index-Cppf-lBx.d.mts +130 -0
- package/dist/index-Cppf-lBx.d.mts.map +1 -0
- package/dist/index.d.mts +8 -6
- package/dist/index.mjs +8 -6
- package/dist/input-modality-DGOhAknO.d.mts +26 -0
- package/dist/input-modality-DGOhAknO.d.mts.map +1 -0
- package/dist/input-modality-m6v7__Xs.mjs +43 -0
- package/dist/input-modality-m6v7__Xs.mjs.map +1 -0
- package/dist/interaction/index.d.mts +1017 -0
- package/dist/interaction/index.d.mts.map +1 -0
- package/dist/interaction/index.mjs +1647 -0
- package/dist/interaction/index.mjs.map +1 -0
- package/dist/internal-DP-xLKbT.mjs +37 -0
- package/dist/internal-DP-xLKbT.mjs.map +1 -0
- package/dist/media/index.d.mts.map +1 -1
- package/dist/media/index.mjs +3 -3
- package/dist/media/index.mjs.map +1 -1
- package/dist/motion/index.d.mts +2 -112
- package/dist/motion/index.mjs +2 -208
- package/dist/motion-DGGBZ0wt.mjs +240 -0
- package/dist/motion-DGGBZ0wt.mjs.map +1 -0
- package/dist/net/index.d.mts +68 -4
- package/dist/net/index.d.mts.map +1 -1
- package/dist/net/index.mjs +139 -56
- package/dist/net/index.mjs.map +1 -1
- package/dist/observers/index.d.mts +1 -1
- package/dist/observers/index.mjs +3 -0
- package/dist/observers/index.mjs.map +1 -1
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/state/index.d.mts +57 -15
- package/dist/state/index.d.mts.map +1 -1
- package/dist/state/index.mjs +198 -90
- package/dist/state/index.mjs.map +1 -1
- package/dist/time/index.d.mts +21 -1
- package/dist/time/index.d.mts.map +1 -1
- package/dist/time/index.mjs +48 -19
- package/dist/time/index.mjs.map +1 -1
- package/package.json +31 -8
- package/dist/motion/index.d.mts.map +0 -1
- package/dist/motion/index.mjs.map +0 -1
package/dist/motion/index.mjs
CHANGED
|
@@ -1,208 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import * as Subscription from "foldkit/subscription";
|
|
4
|
-
import { Bundle } from "foldkit-bundle";
|
|
5
|
-
//#region src/motion/presence.ts
|
|
6
|
-
/**
|
|
7
|
-
* Presence as a bundle: mount-transition state for exit animations. `Hide`
|
|
8
|
-
* starts a timed `hiding` phase; the `Hidden` fact it yields carries the
|
|
9
|
-
* generation it was scheduled under, so a `Show` in between wins and the
|
|
10
|
-
* late fact is ignored. The clock is Effect's, so tests drive it.
|
|
11
|
-
*/
|
|
12
|
-
const PresenceModel = Schema.Struct({
|
|
13
|
-
phase: Schema.Literals([
|
|
14
|
-
"shown",
|
|
15
|
-
"hiding",
|
|
16
|
-
"hidden"
|
|
17
|
-
]),
|
|
18
|
-
generation: Schema.Number
|
|
19
|
-
});
|
|
20
|
-
const PresenceMessage = defineMessageUnion({
|
|
21
|
-
Show: {},
|
|
22
|
-
Hide: {},
|
|
23
|
-
Hidden: { generation: Schema.Number }
|
|
24
|
-
});
|
|
25
|
-
/** Whether the content renders: shown or mid-exit. */
|
|
26
|
-
const isVisible = (model) => model.phase !== "hidden";
|
|
27
|
-
const Presence = Bundle.make("Presence", {
|
|
28
|
-
Model: PresenceModel,
|
|
29
|
-
Message: PresenceMessage,
|
|
30
|
-
args: Schema.Struct({ durationMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())) }),
|
|
31
|
-
init: () => ({ model: {
|
|
32
|
-
phase: "hidden",
|
|
33
|
-
generation: 0
|
|
34
|
-
} }),
|
|
35
|
-
update: (model, message, args) => PresenceMessage.match(message, {
|
|
36
|
-
Show: () => ({ model: {
|
|
37
|
-
...model,
|
|
38
|
-
phase: "shown",
|
|
39
|
-
generation: model.generation + 1
|
|
40
|
-
} }),
|
|
41
|
-
Hide: () => {
|
|
42
|
-
const generation = model.generation + 1;
|
|
43
|
-
return {
|
|
44
|
-
model: {
|
|
45
|
-
...model,
|
|
46
|
-
phase: "hiding",
|
|
47
|
-
generation
|
|
48
|
-
},
|
|
49
|
-
commands: [{
|
|
50
|
-
name: "Presence.hide",
|
|
51
|
-
args: { generation },
|
|
52
|
-
effect: Effect.as(Effect.sleep(args.durationMs), PresenceMessage.Hidden({ generation }))
|
|
53
|
-
}]
|
|
54
|
-
};
|
|
55
|
-
},
|
|
56
|
-
Hidden: ({ generation }) => generation === model.generation ? { model: {
|
|
57
|
-
...model,
|
|
58
|
-
phase: "hidden"
|
|
59
|
-
} } : { model }
|
|
60
|
-
})
|
|
61
|
-
});
|
|
62
|
-
//#endregion
|
|
63
|
-
//#region src/motion/spring.ts
|
|
64
|
-
/**
|
|
65
|
-
* A spring as a bundle: a number pulled toward a target with stiffness and
|
|
66
|
-
* damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the
|
|
67
|
-
* trajectory is deterministic under TestClock; the stream ends with
|
|
68
|
-
* Finished, and the value rests exactly at `to`. Underdamped springs
|
|
69
|
-
* overshoot — that is the point — but the rest value is still exact.
|
|
70
|
-
*/
|
|
71
|
-
const SpringModel = Schema.Struct({
|
|
72
|
-
value: Schema.Number,
|
|
73
|
-
velocity: Schema.Number,
|
|
74
|
-
running: Schema.Boolean
|
|
75
|
-
});
|
|
76
|
-
const SpringMessage = defineMessageUnion({
|
|
77
|
-
Started: {},
|
|
78
|
-
Stopped: {},
|
|
79
|
-
Ticked: {
|
|
80
|
-
value: Schema.Number,
|
|
81
|
-
velocity: Schema.Number
|
|
82
|
-
},
|
|
83
|
-
/** Carries the exact end value: the integrator stops near it, not on it. */
|
|
84
|
-
Finished: { value: Schema.Number }
|
|
85
|
-
});
|
|
86
|
-
const step = 16;
|
|
87
|
-
const epsilon = .001;
|
|
88
|
-
const settled = (point, to) => Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon;
|
|
89
|
-
const Spring = Bundle.make("Spring", {
|
|
90
|
-
Model: SpringModel,
|
|
91
|
-
Message: SpringMessage,
|
|
92
|
-
args: Schema.Struct({
|
|
93
|
-
from: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
94
|
-
to: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
95
|
-
stiffness: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),
|
|
96
|
-
damping: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite()))
|
|
97
|
-
}),
|
|
98
|
-
init: (args) => ({ model: {
|
|
99
|
-
value: args.from,
|
|
100
|
-
velocity: 0,
|
|
101
|
-
running: false
|
|
102
|
-
} }),
|
|
103
|
-
update: (model, message) => SpringMessage.match(message, {
|
|
104
|
-
Started: () => ({ model: {
|
|
105
|
-
...model,
|
|
106
|
-
running: true
|
|
107
|
-
} }),
|
|
108
|
-
Stopped: () => ({ model: {
|
|
109
|
-
...model,
|
|
110
|
-
running: false
|
|
111
|
-
} }),
|
|
112
|
-
Ticked: ({ value, velocity }) => ({ model: {
|
|
113
|
-
...model,
|
|
114
|
-
value,
|
|
115
|
-
velocity
|
|
116
|
-
} }),
|
|
117
|
-
Finished: ({ value }) => ({ model: {
|
|
118
|
-
...model,
|
|
119
|
-
value,
|
|
120
|
-
velocity: 0,
|
|
121
|
-
running: false
|
|
122
|
-
} })
|
|
123
|
-
}),
|
|
124
|
-
subscriptions: ({ from, to, stiffness, damping }) => Subscription.make()((entry) => ({ ticks: entry({ running: Schema.Boolean }, {
|
|
125
|
-
modelToDependencies: (model) => ({ running: model.running }),
|
|
126
|
-
dependenciesToStream: ({ running }) => {
|
|
127
|
-
if (!running) return Stream.empty;
|
|
128
|
-
const dt = step / 1e3;
|
|
129
|
-
const points = Stream.tick(step).pipe(Stream.scan({
|
|
130
|
-
x: from,
|
|
131
|
-
v: 0
|
|
132
|
-
}, (point) => {
|
|
133
|
-
const force = -stiffness * (point.x - to) - damping * point.v;
|
|
134
|
-
const v = point.v + force * dt;
|
|
135
|
-
return {
|
|
136
|
-
x: point.x + v * dt,
|
|
137
|
-
v
|
|
138
|
-
};
|
|
139
|
-
}), Stream.drop(1));
|
|
140
|
-
return Stream.concat(points.pipe(Stream.takeUntil((point) => settled(point, to)), Stream.map((point) => SpringMessage.Ticked({
|
|
141
|
-
value: point.x,
|
|
142
|
-
velocity: point.v
|
|
143
|
-
}))), Stream.make(SpringMessage.Finished({ value: to })));
|
|
144
|
-
}
|
|
145
|
-
}) }))
|
|
146
|
-
});
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region src/motion/tween.ts
|
|
149
|
-
/**
|
|
150
|
-
* A tween as a bundle: a number animated from one value to another over a
|
|
151
|
-
* duration, in the Model. Progress comes from Effect's clock, so tests drive
|
|
152
|
-
* it with TestClock; the stream ends with Finished, and the value rests at
|
|
153
|
-
* `to`. Linear interpolation only: easing curves stay the application's job.
|
|
154
|
-
*/
|
|
155
|
-
const TweenModel = Schema.Struct({
|
|
156
|
-
value: Schema.Number,
|
|
157
|
-
running: Schema.Boolean
|
|
158
|
-
});
|
|
159
|
-
const TweenMessage = defineMessageUnion({
|
|
160
|
-
Started: {},
|
|
161
|
-
Stopped: {},
|
|
162
|
-
Ticked: { value: Schema.Number },
|
|
163
|
-
/** Carries the exact end value: the stream may stop on either side of it. */
|
|
164
|
-
Finished: { value: Schema.Number }
|
|
165
|
-
});
|
|
166
|
-
const Tween = Bundle.make("Tween", {
|
|
167
|
-
Model: TweenModel,
|
|
168
|
-
Message: TweenMessage,
|
|
169
|
-
args: Schema.Struct({
|
|
170
|
-
from: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
171
|
-
to: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
172
|
-
ms: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite()))
|
|
173
|
-
}),
|
|
174
|
-
init: (args) => ({ model: {
|
|
175
|
-
value: args.from,
|
|
176
|
-
running: false
|
|
177
|
-
} }),
|
|
178
|
-
update: (model, message) => TweenMessage.match(message, {
|
|
179
|
-
Started: () => ({ model: {
|
|
180
|
-
...model,
|
|
181
|
-
running: true
|
|
182
|
-
} }),
|
|
183
|
-
Stopped: () => ({ model: {
|
|
184
|
-
...model,
|
|
185
|
-
running: false
|
|
186
|
-
} }),
|
|
187
|
-
Ticked: ({ value }) => ({ model: {
|
|
188
|
-
...model,
|
|
189
|
-
value
|
|
190
|
-
} }),
|
|
191
|
-
Finished: ({ value }) => ({ model: {
|
|
192
|
-
...model,
|
|
193
|
-
value,
|
|
194
|
-
running: false
|
|
195
|
-
} })
|
|
196
|
-
}),
|
|
197
|
-
subscriptions: ({ from, to, ms }) => Subscription.make()((entry) => ({ ticks: entry({ running: Schema.Boolean }, {
|
|
198
|
-
modelToDependencies: (model) => ({ running: model.running }),
|
|
199
|
-
dependenciesToStream: ({ running }) => running ? Stream.unwrap(Clock.currentTimeMillis.pipe(Effect.map((startedAt) => {
|
|
200
|
-
const progress = Stream.tick(16).pipe(Stream.mapEffect(() => Clock.currentTimeMillis), Stream.map((now) => Math.min(1, (now - startedAt) / ms)));
|
|
201
|
-
return Stream.concat(progress.pipe(Stream.takeUntil((done) => done >= 1), Stream.map((done) => TweenMessage.Ticked({ value: from + (to - from) * done }))), Stream.make(TweenMessage.Finished({ value: to })));
|
|
202
|
-
}))) : Stream.empty
|
|
203
|
-
}) }))
|
|
204
|
-
});
|
|
205
|
-
//#endregion
|
|
206
|
-
export { Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible };
|
|
207
|
-
|
|
208
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import { a as SpringMessage, c as PresenceMessage, d as motion_exports, i as Spring, l as PresenceModel, n as TweenMessage, o as SpringModel, r as TweenModel, s as Presence, t as Tween, u as isVisible } from "../motion-DGGBZ0wt.mjs";
|
|
2
|
+
export { motion_exports as Motion, Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible };
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { Clock, Context, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
3
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
4
|
+
import * as Subscription from "foldkit/subscription";
|
|
5
|
+
import { Bundle } from "foldkit-bundle";
|
|
6
|
+
//#region src/motion/motion.ts
|
|
7
|
+
/**
|
|
8
|
+
* Whether motion should be reduced, as a service read when a transition
|
|
9
|
+
* starts, not sniffed once. Absent, motion is full, so a placement that
|
|
10
|
+
* provides nothing behaves as before; a page provides `live` (the user's
|
|
11
|
+
* `prefers-reduced-motion`) and a test provides `reduced` or `full`. Under
|
|
12
|
+
* reduced motion a presence exits at once and a tween or spring jumps to its
|
|
13
|
+
* end, in the same Messages, so the Model sees the same transitions.
|
|
14
|
+
*/
|
|
15
|
+
var motion_exports = /* @__PURE__ */ __exportAll({
|
|
16
|
+
Motion: () => Motion,
|
|
17
|
+
full: () => full,
|
|
18
|
+
layer: () => layer,
|
|
19
|
+
live: () => live,
|
|
20
|
+
reduced: () => reduced,
|
|
21
|
+
reducedMotion: () => reducedMotion
|
|
22
|
+
});
|
|
23
|
+
var Motion = class extends Context.Service()("foldkit-primitives/Motion") {};
|
|
24
|
+
/** Reduced or full motion, fixed: for a test or a setting the application owns. */
|
|
25
|
+
const layer = (reduced) => Layer.succeed(Motion, { reduced: Effect.succeed(reduced) });
|
|
26
|
+
const reduced = layer(true);
|
|
27
|
+
const full = layer(false);
|
|
28
|
+
/** The user's `prefers-reduced-motion`, read each time a transition starts. */
|
|
29
|
+
const live = Layer.succeed(Motion, { reduced: Effect.sync(() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) });
|
|
30
|
+
/** Whether to reduce motion now: the service's answer, or `false` without one. */
|
|
31
|
+
const reducedMotion = Effect.flatMap(Effect.serviceOption(Motion), Option.match({
|
|
32
|
+
onNone: () => Effect.succeed(false),
|
|
33
|
+
onSome: (motion) => motion.reduced
|
|
34
|
+
}));
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/motion/presence.ts
|
|
37
|
+
/**
|
|
38
|
+
* Presence as a bundle: mount-transition state for exit animations. `Hide`
|
|
39
|
+
* starts a timed `hiding` phase; the `Hidden` fact it yields carries the
|
|
40
|
+
* generation it was scheduled under, so a `Show` in between wins and the
|
|
41
|
+
* late fact is ignored. The clock is Effect's, so tests drive it.
|
|
42
|
+
*/
|
|
43
|
+
const PresenceModel = Schema.Struct({
|
|
44
|
+
phase: Schema.Literals([
|
|
45
|
+
"shown",
|
|
46
|
+
"hiding",
|
|
47
|
+
"hidden"
|
|
48
|
+
]),
|
|
49
|
+
generation: Schema.Number
|
|
50
|
+
});
|
|
51
|
+
const PresenceMessage = defineMessageUnion({
|
|
52
|
+
Show: {},
|
|
53
|
+
Hide: {},
|
|
54
|
+
Hidden: { generation: Schema.Number }
|
|
55
|
+
});
|
|
56
|
+
/** Whether the content renders: shown or mid-exit. */
|
|
57
|
+
const isVisible = (model) => model.phase !== "hidden";
|
|
58
|
+
const Presence = Bundle.make("Presence", {
|
|
59
|
+
Model: PresenceModel,
|
|
60
|
+
Message: PresenceMessage,
|
|
61
|
+
args: Schema.Struct({ durationMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())) }),
|
|
62
|
+
init: () => ({ model: {
|
|
63
|
+
phase: "hidden",
|
|
64
|
+
generation: 0
|
|
65
|
+
} }),
|
|
66
|
+
update: (model, message, args) => PresenceMessage.match(message, {
|
|
67
|
+
Show: () => model.phase === "shown" ? { model } : { model: {
|
|
68
|
+
...model,
|
|
69
|
+
phase: "shown",
|
|
70
|
+
generation: model.generation + 1
|
|
71
|
+
} },
|
|
72
|
+
Hide: () => {
|
|
73
|
+
if (model.phase === "hidden") return { model };
|
|
74
|
+
const generation = model.generation + 1;
|
|
75
|
+
return {
|
|
76
|
+
model: {
|
|
77
|
+
...model,
|
|
78
|
+
phase: "hiding",
|
|
79
|
+
generation
|
|
80
|
+
},
|
|
81
|
+
commands: [{
|
|
82
|
+
name: "Presence.hide",
|
|
83
|
+
args: { generation },
|
|
84
|
+
effect: Effect.flatMap(reducedMotion, (reduced) => reduced ? Effect.succeed(PresenceMessage.Hidden({ generation })) : Effect.as(Effect.sleep(args.durationMs), PresenceMessage.Hidden({ generation })))
|
|
85
|
+
}]
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
Hidden: ({ generation }) => generation === model.generation ? { model: {
|
|
89
|
+
...model,
|
|
90
|
+
phase: "hidden"
|
|
91
|
+
} } : { model }
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/motion/spring.ts
|
|
96
|
+
/**
|
|
97
|
+
* A spring as a bundle: a number pulled toward a target with stiffness and
|
|
98
|
+
* damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the
|
|
99
|
+
* trajectory is deterministic under TestClock; the stream ends with
|
|
100
|
+
* Finished, and the value rests exactly at `to`. Underdamped springs
|
|
101
|
+
* overshoot — that is the point — but the rest value is still exact.
|
|
102
|
+
*/
|
|
103
|
+
const SpringModel = Schema.Struct({
|
|
104
|
+
value: Schema.Number,
|
|
105
|
+
velocity: Schema.Number,
|
|
106
|
+
running: Schema.Boolean
|
|
107
|
+
});
|
|
108
|
+
const SpringMessage = defineMessageUnion({
|
|
109
|
+
Started: {},
|
|
110
|
+
Stopped: {},
|
|
111
|
+
Ticked: {
|
|
112
|
+
value: Schema.Number,
|
|
113
|
+
velocity: Schema.Number
|
|
114
|
+
},
|
|
115
|
+
/** Carries the exact end value: the integrator stops near it, not on it. */
|
|
116
|
+
Finished: { value: Schema.Number }
|
|
117
|
+
});
|
|
118
|
+
const step = 16;
|
|
119
|
+
const epsilon = .001;
|
|
120
|
+
const settled = (point, to) => Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon;
|
|
121
|
+
const Spring = Bundle.make("Spring", {
|
|
122
|
+
Model: SpringModel,
|
|
123
|
+
Message: SpringMessage,
|
|
124
|
+
args: Schema.Struct({
|
|
125
|
+
from: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
126
|
+
to: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
127
|
+
stiffness: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),
|
|
128
|
+
damping: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite()))
|
|
129
|
+
}),
|
|
130
|
+
init: (args) => ({ model: {
|
|
131
|
+
value: args.from,
|
|
132
|
+
velocity: 0,
|
|
133
|
+
running: false
|
|
134
|
+
} }),
|
|
135
|
+
update: (model, message) => SpringMessage.match(message, {
|
|
136
|
+
Started: () => model.running ? { model } : { model: {
|
|
137
|
+
...model,
|
|
138
|
+
running: true
|
|
139
|
+
} },
|
|
140
|
+
Stopped: () => model.running ? { model: {
|
|
141
|
+
...model,
|
|
142
|
+
running: false
|
|
143
|
+
} } : { model },
|
|
144
|
+
Ticked: ({ value, velocity }) => ({ model: {
|
|
145
|
+
...model,
|
|
146
|
+
value,
|
|
147
|
+
velocity
|
|
148
|
+
} }),
|
|
149
|
+
Finished: ({ value }) => ({ model: {
|
|
150
|
+
...model,
|
|
151
|
+
value,
|
|
152
|
+
velocity: 0,
|
|
153
|
+
running: false
|
|
154
|
+
} })
|
|
155
|
+
}),
|
|
156
|
+
subscriptions: ({ from, to, stiffness, damping }) => Subscription.make()((entry) => ({ ticks: entry({ running: Schema.Boolean }, {
|
|
157
|
+
modelToDependencies: (model) => ({ running: model.running }),
|
|
158
|
+
dependenciesToStream: ({ running }) => {
|
|
159
|
+
if (!running) return Stream.empty;
|
|
160
|
+
const dt = step / 1e3;
|
|
161
|
+
const points = Stream.tick(step).pipe(Stream.scan(() => ({
|
|
162
|
+
x: from,
|
|
163
|
+
v: 0
|
|
164
|
+
}), (point) => {
|
|
165
|
+
const force = -stiffness * (point.x - to) - damping * point.v;
|
|
166
|
+
const v = point.v + force * dt;
|
|
167
|
+
return {
|
|
168
|
+
x: point.x + v * dt,
|
|
169
|
+
v
|
|
170
|
+
};
|
|
171
|
+
}), Stream.drop(1));
|
|
172
|
+
return Stream.unwrap(Effect.map(reducedMotion, (reduced) => reduced ? Stream.make(SpringMessage.Finished({ value: to })) : Stream.concat(points.pipe(Stream.takeUntil((point) => settled(point, to)), Stream.map((point) => SpringMessage.Ticked({
|
|
173
|
+
value: point.x,
|
|
174
|
+
velocity: point.v
|
|
175
|
+
}))), Stream.make(SpringMessage.Finished({ value: to })))));
|
|
176
|
+
}
|
|
177
|
+
}) }))
|
|
178
|
+
});
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/motion/tween.ts
|
|
181
|
+
/**
|
|
182
|
+
* A tween as a bundle: a number animated from one value to another over a
|
|
183
|
+
* duration, in the Model. Progress comes from Effect's clock, so tests drive
|
|
184
|
+
* it with TestClock; the stream ends with Finished, and the value rests at
|
|
185
|
+
* `to`. Linear interpolation only: easing curves stay the application's job.
|
|
186
|
+
*/
|
|
187
|
+
const TweenModel = Schema.Struct({
|
|
188
|
+
value: Schema.Number,
|
|
189
|
+
running: Schema.Boolean
|
|
190
|
+
});
|
|
191
|
+
const TweenMessage = defineMessageUnion({
|
|
192
|
+
Started: {},
|
|
193
|
+
Stopped: {},
|
|
194
|
+
Ticked: { value: Schema.Number },
|
|
195
|
+
/** Carries the exact end value: the stream may stop on either side of it. */
|
|
196
|
+
Finished: { value: Schema.Number }
|
|
197
|
+
});
|
|
198
|
+
const Tween = Bundle.make("Tween", {
|
|
199
|
+
Model: TweenModel,
|
|
200
|
+
Message: TweenMessage,
|
|
201
|
+
args: Schema.Struct({
|
|
202
|
+
from: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
203
|
+
to: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
204
|
+
ms: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite()))
|
|
205
|
+
}),
|
|
206
|
+
init: (args) => ({ model: {
|
|
207
|
+
value: args.from,
|
|
208
|
+
running: false
|
|
209
|
+
} }),
|
|
210
|
+
update: (model, message) => TweenMessage.match(message, {
|
|
211
|
+
Started: () => model.running ? { model } : { model: {
|
|
212
|
+
...model,
|
|
213
|
+
running: true
|
|
214
|
+
} },
|
|
215
|
+
Stopped: () => model.running ? { model: {
|
|
216
|
+
...model,
|
|
217
|
+
running: false
|
|
218
|
+
} } : { model },
|
|
219
|
+
Ticked: ({ value }) => value === model.value ? { model } : { model: {
|
|
220
|
+
...model,
|
|
221
|
+
value
|
|
222
|
+
} },
|
|
223
|
+
Finished: ({ value }) => ({ model: {
|
|
224
|
+
...model,
|
|
225
|
+
value,
|
|
226
|
+
running: false
|
|
227
|
+
} })
|
|
228
|
+
}),
|
|
229
|
+
subscriptions: ({ from, to, ms }) => Subscription.make()((entry) => ({ ticks: entry({ running: Schema.Boolean }, {
|
|
230
|
+
modelToDependencies: (model) => ({ running: model.running }),
|
|
231
|
+
dependenciesToStream: ({ running }) => running ? Stream.unwrap(Effect.flatMap(reducedMotion, (reduced) => reduced ? Effect.succeed(Stream.make(TweenMessage.Finished({ value: to }))) : Clock.currentTimeMillis.pipe(Effect.map((startedAt) => {
|
|
232
|
+
const progress = Stream.tick(16).pipe(Stream.mapEffect(() => Clock.currentTimeMillis), Stream.map((now) => Math.min(1, (now - startedAt) / ms)));
|
|
233
|
+
return Stream.concat(progress.pipe(Stream.takeUntil((done) => done >= 1), Stream.map((done) => TweenMessage.Ticked({ value: from + (to - from) * done }))), Stream.make(TweenMessage.Finished({ value: to })));
|
|
234
|
+
})))) : Stream.empty
|
|
235
|
+
}) }))
|
|
236
|
+
});
|
|
237
|
+
//#endregion
|
|
238
|
+
export { SpringMessage as a, PresenceMessage as c, motion_exports as d, Spring as i, PresenceModel as l, TweenMessage as n, SpringModel as o, TweenModel as r, Presence as s, Tween as t, isVisible as u };
|
|
239
|
+
|
|
240
|
+
//# sourceMappingURL=motion-DGGBZ0wt.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion-DGGBZ0wt.mjs","names":[],"sources":["../src/motion/motion.ts","../src/motion/presence.ts","../src/motion/spring.ts","../src/motion/tween.ts"],"sourcesContent":["/**\n * Whether motion should be reduced, as a service read when a transition\n * starts, not sniffed once. Absent, motion is full, so a placement that\n * provides nothing behaves as before; a page provides `live` (the user's\n * `prefers-reduced-motion`) and a test provides `reduced` or `full`. Under\n * reduced motion a presence exits at once and a tween or spring jumps to its\n * end, in the same Messages, so the Model sees the same transitions.\n */\nimport { Context, Effect, Layer, Option } from 'effect'\n\nexport interface MotionShape {\n readonly reduced: Effect.Effect<boolean>\n}\n\nexport class Motion extends Context.Service<Motion, MotionShape>()('foldkit-primitives/Motion') {}\n\n/** Reduced or full motion, fixed: for a test or a setting the application owns. */\nexport const layer = (reduced: boolean): Layer.Layer<Motion> =>\n Layer.succeed(Motion, { reduced: Effect.succeed(reduced) })\n\nexport const reduced: Layer.Layer<Motion> = layer(true)\nexport const full: Layer.Layer<Motion> = layer(false)\n\n/** The user's `prefers-reduced-motion`, read each time a transition starts. */\nexport const live: Layer.Layer<Motion> = Layer.succeed(Motion, {\n reduced: Effect.sync(\n () =>\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches,\n ),\n})\n\n/** Whether to reduce motion now: the service's answer, or `false` without one. */\nexport const reducedMotion: Effect.Effect<boolean> = Effect.flatMap(\n Effect.serviceOption(Motion),\n Option.match({ onNone: () => Effect.succeed(false), onSome: motion => motion.reduced }),\n)\n","/**\n * Presence as a bundle: mount-transition state for exit animations. `Hide`\n * starts a timed `hiding` phase; the `Hidden` fact it yields carries the\n * generation it was scheduled under, so a `Show` in between wins and the\n * late fact is ignored. The clock is Effect's, so tests drive it.\n */\nimport { Effect, Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const PresenceModel = Schema.Struct({\n phase: Schema.Literals(['shown', 'hiding', 'hidden']),\n generation: Schema.Number,\n})\nexport type PresenceModel = typeof PresenceModel.Type\n\nexport const PresenceMessage = defineMessageUnion({\n Show: {},\n Hide: {},\n Hidden: { generation: Schema.Number },\n})\nexport type PresenceMessage = typeof PresenceMessage.Type\n\n/** Whether the content renders: shown or mid-exit. */\nexport const isVisible = (model: PresenceModel): boolean => model.phase !== 'hidden'\n\nexport const Presence = Bundle.make<\n 'Presence',\n PresenceModel,\n PresenceMessage,\n { readonly durationMs: number },\n never,\n never,\n never,\n void,\n {},\n {}\n>('Presence', {\n Model: PresenceModel,\n Message: PresenceMessage,\n args: Schema.Struct({\n durationMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: () => ({ model: { phase: 'hidden', generation: 0 } }),\n update: (model, message, args) =>\n PresenceMessage.match<Update.ReturnWithOutMessage<PresenceModel, PresenceMessage, never>>(\n message,\n {\n // Every new transition invalidates pending timeouts. While shown none is\n // pending, and hiding what is hidden would show it for `durationMs`.\n Show: () =>\n model.phase === 'shown'\n ? { model }\n : { model: { ...model, phase: 'shown' as const, generation: model.generation + 1 } },\n Hide: () => {\n if (model.phase === 'hidden') return { model }\n const generation = model.generation + 1\n return {\n model: { ...model, phase: 'hiding' as const, generation },\n commands: [\n {\n name: 'Presence.hide',\n args: { generation },\n effect: Effect.flatMap(reducedMotion, reduced =>\n reduced\n ? Effect.succeed(PresenceMessage.Hidden({ generation }))\n : Effect.as(\n Effect.sleep(args.durationMs),\n PresenceMessage.Hidden({ generation }),\n ),\n ),\n },\n ],\n }\n },\n Hidden: ({ generation }) =>\n generation === model.generation\n ? { model: { ...model, phase: 'hidden' as const } }\n : { model },\n },\n ),\n})\n","/**\n * A spring as a bundle: a number pulled toward a target with stiffness and\n * damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the\n * trajectory is deterministic under TestClock; the stream ends with\n * Finished, and the value rests exactly at `to`. Underdamped springs\n * overshoot — that is the point — but the rest value is still exact.\n */\nimport { Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const SpringModel = Schema.Struct({\n value: Schema.Number,\n velocity: Schema.Number,\n running: Schema.Boolean,\n})\nexport type SpringModel = typeof SpringModel.Type\n\nexport const SpringMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Ticked: { value: Schema.Number, velocity: Schema.Number },\n /** Carries the exact end value: the integrator stops near it, not on it. */\n Finished: { value: Schema.Number },\n})\nexport type SpringMessage = typeof SpringMessage.Type\n\nconst step = 16\nconst epsilon = 0.001\n\ninterface Point {\n readonly x: number\n readonly v: number\n}\n\nconst settled = (point: Point, to: number): boolean =>\n Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon\n\nexport const Spring = Bundle.make('Spring', {\n Model: SpringModel,\n Message: SpringMessage,\n args: Schema.Struct({\n from: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n to: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n stiffness: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n damping: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: args => ({ model: { value: args.from, velocity: 0, running: false } }),\n update: (model, message) =>\n SpringMessage.match(message, {\n Started: () => (model.running ? { model } : { model: { ...model, running: true } }),\n Stopped: () => (model.running ? { model: { ...model, running: false } } : { model }),\n Ticked: ({ value, velocity }) => ({ model: { ...model, value, velocity } }),\n Finished: ({ value }) => ({ model: { ...model, value, velocity: 0, running: false } }),\n }),\n subscriptions: ({\n from,\n to,\n stiffness,\n damping,\n }): Subscription.Subscriptions<SpringModel, SpringMessage> =>\n Subscription.make<SpringModel, SpringMessage>()(entry => ({\n ticks: entry(\n { running: Schema.Boolean },\n {\n modelToDependencies: model => ({ running: model.running }),\n dependenciesToStream: ({ running }) => {\n if (!running) return Stream.empty\n // One integration step per tick, not per elapsed wall time: the\n // trajectory is identical on the live clock and TestClock, and a\n // stalled event loop slows the animation instead of catching up\n // all at once.\n const dt = step / 1000\n const points = Stream.tick(step).pipe(\n Stream.scan(\n (): Point => ({ x: from, v: 0 }),\n point => {\n const force = -stiffness * (point.x - to) - damping * point.v\n const v = point.v + force * dt\n return { x: point.x + v * dt, v }\n },\n ),\n // scan emits the seed (no movement yet) before the first step.\n Stream.drop(1),\n )\n return Stream.unwrap(\n Effect.map(reducedMotion, reduced =>\n reduced\n ? Stream.make(SpringMessage.Finished({ value: to }))\n : Stream.concat(\n points.pipe(\n Stream.takeUntil(point => settled(point, to)),\n Stream.map(point =>\n SpringMessage.Ticked({ value: point.x, velocity: point.v }),\n ),\n ),\n Stream.make(SpringMessage.Finished({ value: to })),\n ),\n ),\n )\n },\n },\n ),\n })),\n})\n","/**\n * A tween as a bundle: a number animated from one value to another over a\n * duration, in the Model. Progress comes from Effect's clock, so tests drive\n * it with TestClock; the stream ends with Finished, and the value rests at\n * `to`. Linear interpolation only: easing curves stay the application's job.\n */\nimport { Clock, Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const TweenModel = Schema.Struct({ value: Schema.Number, running: Schema.Boolean })\nexport type TweenModel = typeof TweenModel.Type\n\nexport const TweenMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Ticked: { value: Schema.Number },\n /** Carries the exact end value: the stream may stop on either side of it. */\n Finished: { value: Schema.Number },\n})\nexport type TweenMessage = typeof TweenMessage.Type\n\nexport const Tween = Bundle.make('Tween', {\n Model: TweenModel,\n Message: TweenMessage,\n args: Schema.Struct({\n from: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n to: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n ms: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),\n }),\n init: args => ({ model: { value: args.from, running: false } }),\n update: (model, message) =>\n TweenMessage.match(message, {\n Started: () => (model.running ? { model } : { model: { ...model, running: true } }),\n Stopped: () => (model.running ? { model: { ...model, running: false } } : { model }),\n // The first tick lands at the start, on the value the Model already holds.\n Ticked: ({ value }) => (value === model.value ? { model } : { model: { ...model, value } }),\n Finished: ({ value }) => ({ model: { ...model, value, running: false } }),\n }),\n subscriptions: ({ from, to, ms }): Subscription.Subscriptions<TweenModel, TweenMessage> =>\n Subscription.make<TweenModel, TweenMessage>()(entry => ({\n ticks: entry(\n { running: Schema.Boolean },\n {\n modelToDependencies: model => ({ running: model.running }),\n dependenciesToStream: ({ running }) =>\n running\n ? Stream.unwrap(\n Effect.flatMap(reducedMotion, reduced =>\n reduced\n ? Effect.succeed(Stream.make(TweenMessage.Finished({ value: to })))\n : Clock.currentTimeMillis.pipe(\n Effect.map(startedAt => {\n const progress = Stream.tick(16).pipe(\n Stream.mapEffect(() => Clock.currentTimeMillis),\n Stream.map(now => Math.min(1, (now - startedAt) / ms)),\n )\n return Stream.concat(\n progress.pipe(\n Stream.takeUntil(done => done >= 1),\n Stream.map(done =>\n TweenMessage.Ticked({ value: from + (to - from) * done }),\n ),\n ),\n Stream.make(TweenMessage.Finished({ value: to })),\n )\n }),\n ),\n ),\n )\n : Stream.empty,\n },\n ),\n })),\n})\n"],"mappings":"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"}
|
package/dist/net/index.d.mts
CHANGED
|
@@ -86,6 +86,32 @@ export declare const SseMessage: import("foldkit/message").MessageUnion<{
|
|
|
86
86
|
};
|
|
87
87
|
}>;
|
|
88
88
|
export type SseMessage = typeof SseMessage.Type;
|
|
89
|
+
/**
|
|
90
|
+
* What the reader sees: the stream's state, or its error. A page derives
|
|
91
|
+
* this instead of keeping its own connection state machine. `lastError`
|
|
92
|
+
* carries the stored failure text: the runtime error sentence (`event stream
|
|
93
|
+
* error for …`) or the acquire error's own message.
|
|
94
|
+
*/
|
|
95
|
+
export declare const SseView: import("foldkit/schema").TaggedUnion<{
|
|
96
|
+
readonly Disconnected: {};
|
|
97
|
+
readonly Connecting: {};
|
|
98
|
+
readonly Connected: {};
|
|
99
|
+
readonly Error: {
|
|
100
|
+
readonly error: Schema.String;
|
|
101
|
+
};
|
|
102
|
+
}>;
|
|
103
|
+
export type SseView = typeof SseView.Type;
|
|
104
|
+
/**
|
|
105
|
+
* Whether the stream is live: open with no error outstanding. The browser
|
|
106
|
+
* reconnects a dropped stream itself; a `Failed` only records the error.
|
|
107
|
+
*/
|
|
108
|
+
export declare const isLive: (model: SseModel) => boolean;
|
|
109
|
+
/**
|
|
110
|
+
* The view of a stream, and whether its page still wants it. An error stands
|
|
111
|
+
* until the page stops wanting the stream; a retry still waiting for its
|
|
112
|
+
* stream reads as connecting, so it answers at once.
|
|
113
|
+
*/
|
|
114
|
+
export declare const viewOfSse: (model: SseModel, wanted: boolean) => SseView;
|
|
89
115
|
/** The registry value behind the source tag: the live source and its incoming queue. */
|
|
90
116
|
export interface AcquiredSource {
|
|
91
117
|
readonly source: SourceHandle;
|
|
@@ -136,6 +162,8 @@ export declare const WebSocketModel: Schema.Struct<{
|
|
|
136
162
|
readonly url: Schema.String;
|
|
137
163
|
readonly status: Schema.Literals<readonly ["closed", "connecting", "open"]>;
|
|
138
164
|
readonly lastError: Schema.NullOr<Schema.String>;
|
|
165
|
+
/** Whether this connection attempt ever reached open; reset on Connecting. */
|
|
166
|
+
readonly opened: Schema.Boolean;
|
|
139
167
|
}>;
|
|
140
168
|
export type WebSocketModel = typeof WebSocketModel.Type;
|
|
141
169
|
export declare const WebSocketMessage: import("foldkit/message").MessageUnion<{
|
|
@@ -152,8 +180,12 @@ export declare const WebSocketMessage: import("foldkit/message").MessageUnion<{
|
|
|
152
180
|
readonly SendFailed: {
|
|
153
181
|
readonly message: Schema.String;
|
|
154
182
|
};
|
|
155
|
-
/** Acknowledges dispatch
|
|
156
|
-
readonly Sent: {
|
|
183
|
+
/** Acknowledges dispatch of `data`, not its delivery; update ignores it. */
|
|
184
|
+
readonly Sent: {
|
|
185
|
+
readonly data: Schema.String;
|
|
186
|
+
};
|
|
187
|
+
/** The socket was still connecting when `connectTimeoutMs` ran out, and was closed. */
|
|
188
|
+
readonly TimedOut: {};
|
|
157
189
|
}>;
|
|
158
190
|
export type WebSocketMessage = typeof WebSocketMessage.Type;
|
|
159
191
|
/** The registry value behind the socket tag: the live socket and its incoming queue. */
|
|
@@ -161,6 +193,33 @@ export interface Acquired {
|
|
|
161
193
|
readonly socket: SocketHandle;
|
|
162
194
|
readonly events: Queue.Queue<WebSocketMessage>;
|
|
163
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* What the reader sees: the socket's state, or its error. A page derives
|
|
198
|
+
* this instead of keeping its own connection state machine. `lastError` is
|
|
199
|
+
* already worded for the reader (`Failed to connect to WebSocket`, never the
|
|
200
|
+
* socket's own text, which error handling drops in favor of the sentence).
|
|
201
|
+
*/
|
|
202
|
+
export declare const SocketView: import("foldkit/schema").TaggedUnion<{
|
|
203
|
+
readonly Disconnected: {};
|
|
204
|
+
readonly Connecting: {};
|
|
205
|
+
readonly Connected: {};
|
|
206
|
+
readonly Error: {
|
|
207
|
+
readonly error: Schema.String;
|
|
208
|
+
};
|
|
209
|
+
}>;
|
|
210
|
+
export type SocketView = typeof SocketView.Type;
|
|
211
|
+
/**
|
|
212
|
+
* Whether the socket is usable: open with no error outstanding. Gates sends
|
|
213
|
+
* and received frames.
|
|
214
|
+
*/
|
|
215
|
+
export declare const isOpen: (model: WebSocketModel) => boolean;
|
|
216
|
+
/**
|
|
217
|
+
* The view of a socket, and whether its page still wants it. An error stands
|
|
218
|
+
* until the page stops wanting the socket; a retry still waiting for its
|
|
219
|
+
* socket reads as connecting, so it answers at once. An error on an open
|
|
220
|
+
* socket always reads as an error.
|
|
221
|
+
*/
|
|
222
|
+
export declare const viewOf: (model: WebSocketModel, wanted: boolean) => SocketView;
|
|
164
223
|
/** The live connection, for `send` and the incoming stream. One assembly holds one. */
|
|
165
224
|
export declare const Socket: ManagedResource.ManagedResource<Acquired, ManagedResource.ManagedResourceService<"websocket">>;
|
|
166
225
|
/** The socket service a placed WebSocket's streams and send commands require. */
|
|
@@ -171,19 +230,21 @@ export declare const websocket: <const Name extends string>(config: {
|
|
|
171
230
|
readonly createSocket?: ((url: string) => SocketHandle) | undefined;
|
|
172
231
|
}) => Bundle.Bundle<Name, {
|
|
173
232
|
readonly url: string;
|
|
233
|
+
readonly connectTimeoutMs?: number | undefined;
|
|
174
234
|
}, {
|
|
175
235
|
readonly url: string;
|
|
176
236
|
readonly status: "closed" | "connecting" | "open";
|
|
177
237
|
readonly lastError: string | null;
|
|
238
|
+
readonly opened: boolean;
|
|
178
239
|
}, {
|
|
179
240
|
readonly _tag: "Connecting";
|
|
180
241
|
} | {
|
|
181
242
|
readonly _tag: "Opened";
|
|
243
|
+
} | {
|
|
244
|
+
readonly _tag: "Closed";
|
|
182
245
|
} | {
|
|
183
246
|
readonly _tag: "Received";
|
|
184
247
|
readonly data: string;
|
|
185
|
-
} | {
|
|
186
|
-
readonly _tag: "Closed";
|
|
187
248
|
} | {
|
|
188
249
|
readonly _tag: "Failed";
|
|
189
250
|
readonly message: string;
|
|
@@ -192,6 +253,9 @@ export declare const websocket: <const Name extends string>(config: {
|
|
|
192
253
|
readonly message: string;
|
|
193
254
|
} | {
|
|
194
255
|
readonly _tag: "Sent";
|
|
256
|
+
readonly data: string;
|
|
257
|
+
} | {
|
|
258
|
+
readonly _tag: "TimedOut";
|
|
195
259
|
}, never, ManagedResource.ManagedResourceService<"websocket">, ManagedResource.ManagedResourceService<"websocket">, void, Readonly<{
|
|
196
260
|
socket: ManagedResource.Entry<WebSocketModel, WebSocketMessage, Option.Option<string>, Acquired, SocketService, () => WebSocketMessage>;
|
|
197
261
|
}>, Readonly<{
|
package/dist/net/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/net/broadcast.ts","../../src/net/online.ts","../../src/net/sse.ts","../../src/net/websocket.ts"],"mappings":";;;;;;qBAWa,4CAAgB;;;;;;;;;;YAMjB,0BAA0B,iBAAiB;;iBAGtC;EACf,YAAY;EACZ;EACA,aAAa;aAAkB;;;KAG5B,kBAAkB,iBAAiB;;;;;;qBAmB3B,oBACX,cAEA;;AAAA,SAAS,+BACR,OAAO,OAAO;;;;;;;qBAyBJ,gBACX,cACA,eACA,SAAS,+BACR,QAAQ;;;qBCpEE,aAAW,OAAA;;;YACZ,qBAAqB,YAAY;qBAEhC,yCAAa;;qBAAb,OAAA;;;YACD,uBAAuB,cAAc;qBAkBpC,QAAM,OAAA;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/net/broadcast.ts","../../src/net/online.ts","../../src/net/sse.ts","../../src/net/websocket.ts"],"mappings":";;;;;;qBAWa,4CAAgB;;;;;;;;;;YAMjB,0BAA0B,iBAAiB;;iBAGtC;EACf,YAAY;EACZ;EACA,aAAa;aAAkB;;;KAG5B,kBAAkB,iBAAiB;;;;;;qBAmB3B,oBACX,cAEA;;AAAA,SAAS,+BACR,OAAO,OAAO;;;;;;;qBAyBJ,gBACX,cACA,eACA,SAAS,+BACR,QAAQ;;;qBCpEE,aAAW,OAAA;;;YACZ,qBAAqB,YAAY;qBAEhC,yCAAa;;qBAAb,OAAA;;;YACD,uBAAuB,cAAc;qBAkBpC,QAAM,OAAA;;;;;;;;;iBChBF;WACN;EACT;EACA,UAAU;EACV,aAAa;EACb,WAAW;;qBAGA,UAAQ,OAAA;;;;;YAKT,kBAAkB,SAAS;qBAE1B,sCAAU;;;;;;;;;;;;YAQX,oBAAoB,WAAW;;;;;;;qBAQ9B,kCAAO;;;;;oBAAP,OAAA;;;YAMD,iBAAiB,QAAQ;;;;;qBAMxB,SAAU,OAAO;;;;;;qBAQjB,YAAa,OAAO,UAAU,oBAAkB;;iBAW5C;WACN,QAAQ;WACR,QAAQ,MAAM,MAAM;;;qBAIlB,QAAM,gBAAA,gBAAA,gBAAA,gBAAA;;YAGP,gBAAgB,gBAAgB,iBAAiB;qBAgBhD,YAAa,qBAAqB;WACpC,MAAM;;WAEN,iBAAiB,gBAAgB;MAC3C,OAAA,OAAA;WAOmB;;;;;;;;;;;;;;;;;;EAxBV,QAAA,gBAAgB,MACtB,UACA,YACA,OAAO,gBACP,gBACA,qBACM;;;;;iBC9EO;WACN;EACT,KAAK;EACL;EACA,UAAU;EACV,aAAa;EACb,WAAW;EACX,WAAW;;qBAGA,gBAAc,OAAA;;;;;;;YAOf,wBAAwB,eAAe;qBAEtC,4CAAgB;;;;;;;;;;;;;;;;;;;;;YAajB,0BAA0B,iBAAiB;;iBAGtC;WACN,QAAQ;WACR,QAAQ,MAAM,MAAM;;;;;;;;qBASlB,qCAAU;;;;;oBAAV,OAAA;;;YAMD,oBAAoB,WAAW;;;;;qBAM9B,SAAU,OAAO;;;;;;;qBASjB,SAAU,OAAO,gBAAgB,oBAAkB;;qBAWnD,QAAM,gBAAA,gBAAA,UAAA,gBAAA;;YAGP,gBAAgB,gBAAgB,iBAAiB;qBAyChD,kBAAmB,qBAAqB;WAC1C,MAAM;;WAEN,iBAAiB,gBAAgB;MAC3C,OAAA,OAAA;WAOmB;WAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;EAjD9C,QAAA,gBAAgB,MACtB,gBACA,kBACA,OAAO,gBACP,UACA,qBACM;;EAKF,OACJ,OAAO,gBACP,iBACG,OAAO,qBAAqB,gBAAgB,yBAAyB"}
|