foldkit-primitives 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -15
- package/dist/device/index.d.mts +9 -9
- package/dist/device/index.d.mts.map +1 -1
- package/dist/device/index.mjs +5 -5
- package/dist/device/index.mjs.map +1 -1
- package/dist/dom/index.d.mts.map +1 -1
- package/dist/events/index.d.mts +4 -4
- package/dist/events/index.d.mts.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/media/index.d.mts +17 -8
- package/dist/media/index.d.mts.map +1 -1
- package/dist/media/index.mjs +21 -9
- package/dist/media/index.mjs.map +1 -1
- package/dist/motion/index.d.mts +5 -5
- package/dist/motion/index.d.mts.map +1 -1
- package/dist/net/index.d.mts +7 -5
- package/dist/net/index.d.mts.map +1 -1
- package/dist/state/index.d.mts +10 -4
- package/dist/state/index.d.mts.map +1 -1
- package/dist/state/index.mjs +11 -4
- package/dist/state/index.mjs.map +1 -1
- package/dist/time/index.d.mts +2 -2
- package/dist/time/index.d.mts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `foldkit-primitives`
|
|
2
2
|
|
|
3
|
-
Ready-made [`foldkit-bundle`](
|
|
3
|
+
Ready-made [`foldkit-bundle`](../bundle) primitives: media queries, breakpoints,
|
|
4
4
|
presence, timers, intervals, debounce, tweens, springs, pagination, history,
|
|
5
5
|
locales, selections, ranges, geolocation, cameras, permissions, sockets,
|
|
6
6
|
broadcasts, observers, and clipboard. Most are ordinary bundles — Model,
|
|
@@ -33,6 +33,15 @@ package depends on `foldkit-bundle` and nothing else (besides peers).
|
|
|
33
33
|
| One-shot actions | Command, not a bundle | clipboard copy, share, script load |
|
|
34
34
|
| Derived data | pure function, not a bundle | range, relative time, platform |
|
|
35
35
|
|
|
36
|
+
Choose by what you need to own. State that outlives the moment — a match,
|
|
37
|
+
a count, a page, a position — wants a bundle: the Model keeps it, replay
|
|
38
|
+
sees it. A stream you only react to wants an entry: map it into the
|
|
39
|
+
parent's Message and keep nothing. Work bound to one element wants a
|
|
40
|
+
Mount. Work that runs once and reports back wants a Command. A value
|
|
41
|
+
computed from data you already have wants a pure function. When in doubt,
|
|
42
|
+
start with the lighter form; promote to a bundle the day the state needs
|
|
43
|
+
a name in the Model.
|
|
44
|
+
|
|
36
45
|
A bundle holds no state and performs no I/O by itself. The parent Model owns
|
|
37
46
|
the placed slice; the browser (or server, or clock) only reports facts as
|
|
38
47
|
Messages. The same rule as everywhere else: observation is not ownership.
|
|
@@ -69,6 +78,20 @@ pnpm add foldkit-primitives foldkit-bundle effect foldkit
|
|
|
69
78
|
`foldkit-primitives/events`, `foldkit-primitives/observers`, `foldkit-primitives/dom` —
|
|
70
79
|
so bundlers drop the primitives you never import.
|
|
71
80
|
|
|
81
|
+
## Map of the package
|
|
82
|
+
|
|
83
|
+
Each subpath is one concern, one import:
|
|
84
|
+
|
|
85
|
+
- `media` — environment facts: MediaQuery (+presets), Breakpoints, platform
|
|
86
|
+
- `net` — remote facts: Online, WebSocket, SSE, BroadcastChannel
|
|
87
|
+
- `time` — clock facts: Timer, Interval, Debounce, Throttle, relative time
|
|
88
|
+
- `state` — owned UI state: Pagination, History, Locale, SelectionSet, Virtual, range
|
|
89
|
+
- `motion` — animation state: Tween, Spring, Presence
|
|
90
|
+
- `device` — hardware: Geolocation, MediaDevices, MediaStream, Permissions, Fullscreen
|
|
91
|
+
- `events` — raw browser events: Visibility, WindowSize, Idle, keyboard, pointer, scroll, focus
|
|
92
|
+
- `observers` — element Mounts: Resize, Intersection, Mutation, Bounds
|
|
93
|
+
- `dom` — element Mounts and one-shot Commands: Autofocus, InputMask, clipboard, share, script loading
|
|
94
|
+
|
|
72
95
|
## Sixty seconds: follow the color scheme
|
|
73
96
|
|
|
74
97
|
**Declare where it lives.** A declaration names the Model field and the
|
|
@@ -105,15 +128,33 @@ const config = placements.complete({
|
|
|
105
128
|
|
|
106
129
|
`update` routes `GotDarkMessage` to the bundle; `subscriptions` runs the
|
|
107
130
|
`matchMedia` stream; `initial` starts `matches` at `false` and the stream
|
|
108
|
-
corrects it on subscribe.
|
|
131
|
+
corrects it on subscribe. None of the placement calls perform I/O:
|
|
132
|
+
`declare` names a slot, `at` binds config to it, `assemble` derives the
|
|
133
|
+
folding and the streams — the browser is touched only when the runtime
|
|
134
|
+
subscribes. The Solid equivalent this replaces:
|
|
109
135
|
|
|
110
136
|
```ts
|
|
111
137
|
// solid-primitives: const dark = createMediaQuery('(prefers-color-scheme: dark)')
|
|
112
138
|
// Here the fact lives in the Model: replay, DevTools, and time travel see it.
|
|
113
139
|
```
|
|
114
140
|
|
|
141
|
+
## Composing placements
|
|
142
|
+
|
|
143
|
+
One assembly holds every placement: spread its update, init,
|
|
144
|
+
subscriptions, and resources once per application, as above. Two
|
|
145
|
+
placements of one bundle observe twice — share the field instead, so one
|
|
146
|
+
stream feeds every reader. One assembly holds one socket, stream, or
|
|
147
|
+
watch: resource tags are per module, and `assemble` refuses the second.
|
|
148
|
+
Bundles whose settled value is the whole point (`Debounce`, `Throttle`)
|
|
149
|
+
surface it as an OutMessage the placement handles with `onOut`, so the
|
|
150
|
+
signal can never be dropped by omission. Entries lift with
|
|
151
|
+
`Subscription.persistent`, mapping into the parent's Message; Mounts
|
|
152
|
+
attach in views with `h.OnMount`.
|
|
153
|
+
|
|
115
154
|
## Media: `foldkit-primitives/media`
|
|
116
155
|
|
|
156
|
+
> Reference: [`./media/README.md`](./media/README.md)
|
|
157
|
+
|
|
117
158
|
`MediaQuery` follows one CSS media query. Model `{ matches: boolean }`,
|
|
118
159
|
one Message `Changed { matches }`, args `{ query: string }`. The stream emits
|
|
119
160
|
the current value on subscribe, then every change; without `matchMedia` it is
|
|
@@ -135,13 +176,16 @@ the largest name at or below the width (ties break alphabetically); SSR
|
|
|
135
176
|
starts at width 0 with `null`. Placing both `Breakpoints` and `WindowSize`
|
|
136
177
|
doubles resize listeners — pick the one the view reads.
|
|
137
178
|
|
|
138
|
-
`platformFromUA(ua)` reads `mac | windows | linux | android | ios | unknown`
|
|
179
|
+
`platformFromUA(ua, hints?)` reads `mac | windows | linux | android | ios | unknown`
|
|
139
180
|
from a passed user-agent string (mobile checks first: Android contains
|
|
140
|
-
"Linux", iPhones mention "Mac"); `
|
|
181
|
+
"Linux", iPhones mention "Mac"); Client Hints `platform` wins when
|
|
182
|
+
recognized, and a multi-touch Mac UA reads as iOS. `isBrowser()`/`isServer()` split SSR from
|
|
141
183
|
client for init defaults.
|
|
142
184
|
|
|
143
185
|
## Net: `foldkit-primitives/net`
|
|
144
186
|
|
|
187
|
+
> Reference: [`./net/README.md`](./net/README.md)
|
|
188
|
+
|
|
145
189
|
`Online` keeps `online: boolean` in the Model, read from `navigator.onLine`
|
|
146
190
|
at startup and kept current by the window's `online`/`offline` events. One
|
|
147
191
|
Message `Changed { online }`, no args, no OutMessage. Without a window the
|
|
@@ -175,6 +219,8 @@ the Command fails, instead of throwing.
|
|
|
175
219
|
|
|
176
220
|
## Time: `foldkit-primitives/time`
|
|
177
221
|
|
|
222
|
+
> Reference: [`./time/README.md`](./time/README.md)
|
|
223
|
+
|
|
178
224
|
`Timer` counts ticks while running. Model `{ count, running }`, Messages
|
|
179
225
|
`Started`/`Stopped`/`Ticked`, args `{ intervalMs }` (positive and finite —
|
|
180
226
|
anything else is rejected at placement). The tick stream runs on
|
|
@@ -207,6 +253,8 @@ platform, not a phrase table. There is deliberately no `now` helper:
|
|
|
207
253
|
|
|
208
254
|
## Events: `foldkit-primitives/events`
|
|
209
255
|
|
|
256
|
+
> Reference: [`./events/README.md`](./events/README.md)
|
|
257
|
+
|
|
210
258
|
`Visibility` keeps `visible: boolean` in the Model, read from the document
|
|
211
259
|
at startup (SSR assumes visible) and kept current by `visibilitychange`:
|
|
212
260
|
one Message `Changed { visible }`, no args. A hidden page is the
|
|
@@ -240,6 +288,8 @@ a window each stream is empty instead of throwing.
|
|
|
240
288
|
|
|
241
289
|
## Observers: `foldkit-primitives/observers`
|
|
242
290
|
|
|
291
|
+
> Reference: [`./observers/README.md`](./observers/README.md)
|
|
292
|
+
|
|
243
293
|
`Resize` and `Intersection` are Mounts, not bundles: element-scoped
|
|
244
294
|
observation attaches in views, not Model slots. Attach `Resize()` (or
|
|
245
295
|
`Intersection()`) with `h.OnMount` on the element. There are no ref objects
|
|
@@ -260,6 +310,8 @@ is same-valued and harmless.
|
|
|
260
310
|
|
|
261
311
|
## Device: `foldkit-primitives/device`
|
|
262
312
|
|
|
313
|
+
> Reference: [`./device/README.md`](./device/README.md)
|
|
314
|
+
|
|
263
315
|
`Geolocation` watches the device position while placed. Model `{ status,
|
|
264
316
|
coords, lastError }` with `status` unknown → ready; denial is its own status
|
|
265
317
|
(actionable UI), transient failures keep the last fix and note the error.
|
|
@@ -269,7 +321,7 @@ geolocation API the stream is empty instead of throwing.
|
|
|
269
321
|
`mediaDevices({ name, create? })` scans the device list on placement and
|
|
270
322
|
re-scans on `Scan`, `DevicesChanged` (wired to `devicechange`), and every
|
|
271
323
|
placement: `Refreshed { devices }` with `{ deviceId, groupId, kind, label }`.
|
|
272
|
-
Denial
|
|
324
|
+
Denial lands as `denied` while keeping the last list; other failures keep the last list and
|
|
273
325
|
note the error; an unknown `kind` fails the scan at the boundary instead of
|
|
274
326
|
entering the Model.
|
|
275
327
|
|
|
@@ -296,6 +348,8 @@ owns fullscreen state.
|
|
|
296
348
|
|
|
297
349
|
## DOM: `foldkit-primitives/dom`
|
|
298
350
|
|
|
351
|
+
> Reference: [`./dom/README.md`](./dom/README.md)
|
|
352
|
+
|
|
299
353
|
`copyText` copies text as a Command: use it in `update` beside any bundle.
|
|
300
354
|
It yields `Copied` on success and `CopyFailed` otherwise — denial, insecure
|
|
301
355
|
context, or no clipboard API (SSR) all become the failure Message instead of
|
|
@@ -317,6 +371,8 @@ state, like any controlled input.
|
|
|
317
371
|
|
|
318
372
|
## State: `foldkit-primitives/state`
|
|
319
373
|
|
|
374
|
+
> Reference: [`./state/README.md`](./state/README.md)
|
|
375
|
+
|
|
320
376
|
`Pagination` keeps `{ page, perPage, total }` in the Model, with `total: null`
|
|
321
377
|
while unknown. Every transition clamps into range: past the last page lands on
|
|
322
378
|
it, below one lands on one, and a smaller total pulls the page back. New sizes
|
|
@@ -362,7 +418,8 @@ live ones) and a `settleMs` silence (default 150). Every scroll marks
|
|
|
362
418
|
`Viewport` reports the container's own scrolls and `MeasureRow({ key })` reports row heights, both as Mounts;
|
|
363
419
|
`windowFor(model, keys, viewportHeight)` answers which rows to render plus
|
|
364
420
|
the spacer height, `isAtEnd(model, keys, viewportHeight, threshold)` is the
|
|
365
|
-
infinite-scroll check (an empty list counts as ended),
|
|
421
|
+
infinite-scroll check (an empty list counts as ended), `distanceToEnd`
|
|
422
|
+
answers the pixels remaining for prefetch thresholds, and `offsetFor`
|
|
366
423
|
computes programmatic scroll targets the application actuates itself.
|
|
367
424
|
`Prune` drops heights for departed keys — the bundle never sees key order.
|
|
368
425
|
Poisoned positions and heights are ignored, never stored. For window-
|
|
@@ -385,17 +442,10 @@ browser), restored through a `MirrorRestored` Message the application
|
|
|
385
442
|
reduces. Nothing here duplicates it — reach for the mirror when a slice
|
|
386
443
|
should survive reload, and keep this package's bundles for live facts.
|
|
387
444
|
|
|
388
|
-
## With Surface and Mirror
|
|
389
|
-
|
|
390
|
-
Placed state is ordinary Model, so the surrounding tools apply unchanged —
|
|
391
|
-
no bundle-specific Surface or Mirror API exists, by design. Declare a Surface
|
|
392
|
-
over the placed fields to render them or expose them to an agent; point
|
|
393
|
-
`Mirror.url` at them to link them; spread the bundle's cases into the same
|
|
394
|
-
unions. The field refs and wrapper Messages are the same ones the rest of the
|
|
395
|
-
application uses.
|
|
396
|
-
|
|
397
445
|
## Motion: `foldkit-primitives/motion`
|
|
398
446
|
|
|
447
|
+
> Reference: [`./motion/README.md`](./motion/README.md)
|
|
448
|
+
|
|
399
449
|
`Tween` animates one number from `from` to `to` over `ms` milliseconds.
|
|
400
450
|
Model `{ value, running }`, Messages `Started`/`Ticked`/`Finished`, args
|
|
401
451
|
`{ from, to, ms }` (a non-positive or non-finite duration is rejected at
|
|
@@ -435,6 +485,15 @@ forking before the first `TestClock.adjust`, or dispatched events hit
|
|
|
435
485
|
unregistered listeners. Each ingredient is demonstrated in this package's
|
|
436
486
|
`test/` directory, named after its primitive.
|
|
437
487
|
|
|
488
|
+
## With Surface and Mirror
|
|
489
|
+
|
|
490
|
+
Placed state is ordinary Model, so the surrounding tools apply unchanged —
|
|
491
|
+
no bundle-specific Surface or Mirror API exists, by design. Declare a Surface
|
|
492
|
+
over the placed fields to render them or expose them to an agent; point
|
|
493
|
+
`Mirror.url` at them to link them; spread the bundle's cases into the same
|
|
494
|
+
unions. The field refs and wrapper Messages are the same ones the rest of the
|
|
495
|
+
application uses.
|
|
496
|
+
|
|
438
497
|
## Failure and recovery
|
|
439
498
|
|
|
440
499
|
| Failure | Behaviour |
|
package/dist/device/index.d.mts
CHANGED
|
@@ -59,7 +59,7 @@ export declare const GeolocationMessage: import("foldkit/message").MessageUnion<
|
|
|
59
59
|
}>;
|
|
60
60
|
export type GeolocationMessage = typeof GeolocationMessage.Type;
|
|
61
61
|
export declare const Geolocation: Bundle.Bundle<"Geolocation", void, {
|
|
62
|
-
readonly status: "unknown" | "
|
|
62
|
+
readonly status: "unknown" | "ready" | "denied";
|
|
63
63
|
readonly coords: {
|
|
64
64
|
readonly latitude: number;
|
|
65
65
|
readonly longitude: number;
|
|
@@ -67,6 +67,8 @@ export declare const Geolocation: Bundle.Bundle<"Geolocation", void, {
|
|
|
67
67
|
} | null;
|
|
68
68
|
readonly lastError: string | null;
|
|
69
69
|
}, {
|
|
70
|
+
readonly _tag: "Denied";
|
|
71
|
+
} | {
|
|
70
72
|
readonly _tag: "Failed";
|
|
71
73
|
readonly message: string;
|
|
72
74
|
} | {
|
|
@@ -74,8 +76,6 @@ export declare const Geolocation: Bundle.Bundle<"Geolocation", void, {
|
|
|
74
76
|
readonly latitude: number;
|
|
75
77
|
readonly longitude: number;
|
|
76
78
|
readonly accuracy: number;
|
|
77
|
-
} | {
|
|
78
|
-
readonly _tag: "Denied";
|
|
79
79
|
}, never, never, never, void, {}, {}>;
|
|
80
80
|
//#endregion
|
|
81
81
|
//#region src/device/media-devices.d.ts
|
|
@@ -130,7 +130,7 @@ export declare const mediaDevices: <const Name extends string>(config: {
|
|
|
130
130
|
/** Read lazily so tests can substitute a double, like the SSE bundle. */
|
|
131
131
|
readonly create?: ((navigator: Navigator) => MediaDevicesHandle | null) | undefined;
|
|
132
132
|
}) => Bundle.Bundle<Name, void, {
|
|
133
|
-
readonly status: "unknown" | "
|
|
133
|
+
readonly status: "unknown" | "ready" | "denied";
|
|
134
134
|
readonly devices: readonly {
|
|
135
135
|
readonly deviceId: string;
|
|
136
136
|
readonly groupId: string;
|
|
@@ -139,8 +139,6 @@ export declare const mediaDevices: <const Name extends string>(config: {
|
|
|
139
139
|
}[];
|
|
140
140
|
readonly lastError: string | null;
|
|
141
141
|
}, {
|
|
142
|
-
readonly _tag: "Denied";
|
|
143
|
-
} | {
|
|
144
142
|
readonly _tag: "Failed";
|
|
145
143
|
readonly message: string;
|
|
146
144
|
} | {
|
|
@@ -155,6 +153,8 @@ export declare const mediaDevices: <const Name extends string>(config: {
|
|
|
155
153
|
readonly kind: "audioinput" | "audiooutput" | "videoinput";
|
|
156
154
|
readonly label: string;
|
|
157
155
|
}[];
|
|
156
|
+
} | {
|
|
157
|
+
readonly _tag: "Denied";
|
|
158
158
|
}, never, never, never, void, {}, {}>;
|
|
159
159
|
//#endregion
|
|
160
160
|
//#region src/device/media-stream.d.ts
|
|
@@ -288,20 +288,20 @@ export declare const permissions: <const Name extends string>(config: {
|
|
|
288
288
|
readonly names: ReadonlyArray<string>;
|
|
289
289
|
}, {
|
|
290
290
|
readonly states: {
|
|
291
|
-
readonly [x: string]: "
|
|
291
|
+
readonly [x: string]: "denied" | "granted" | "prompt";
|
|
292
292
|
};
|
|
293
293
|
readonly lastError: string | null;
|
|
294
294
|
}, {
|
|
295
295
|
readonly _tag: "Changed";
|
|
296
296
|
readonly name: string;
|
|
297
|
-
readonly state: "
|
|
297
|
+
readonly state: "denied" | "granted" | "prompt";
|
|
298
298
|
} | {
|
|
299
299
|
readonly _tag: "Failed";
|
|
300
300
|
readonly message: string;
|
|
301
301
|
} | {
|
|
302
302
|
readonly _tag: "Snapshot";
|
|
303
303
|
readonly states: {
|
|
304
|
-
readonly [x: string]: "
|
|
304
|
+
readonly [x: string]: "denied" | "granted" | "prompt";
|
|
305
305
|
};
|
|
306
306
|
} | {
|
|
307
307
|
readonly _tag: "Cleared";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/device/fullscreen.ts","../../src/device/geolocation.ts","../../src/device/media-devices.ts","../../src/device/media-stream.ts","../../src/device/permissions.ts"],"mappings":";;;;;qBAWa,6CAAiB;;;;;;;;;;YAMlB,2BAA2B,kBAAkB;;;;;;;qBAW5C,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/device/fullscreen.ts","../../src/device/geolocation.ts","../../src/device/media-devices.ts","../../src/device/media-stream.ts","../../src/device/permissions.ts"],"mappings":";;;;;qBAWa,6CAAiB;;;;;;;;;;YAMlB,2BAA2B,kBAAkB;;;;;;;qBAW5C,kBAAmB,SAAS,YAAU,QAAQ;;qBA0B9C,sBAAqB,QAAQ;;;;;qBAgC7B,yBAAwB,OAAO,OAAO;;;qBC1EtC,aAAW,OAAA;;;;;YAKZ,qBAAqB,YAAY;qBAEhC,kBAAgB,OAAA;;;;;;;;;YAKjB,0BAA0B,iBAAiB;qBAE1C,8CAAkB;;;;;;;;;;;YASnB,4BAA4B,mBAAmB;qBAmD9C,aAAW,OAAA;;;;;;;;;;;;;;;;;;;;;qBCxEX,aAAW,OAAA;;;;;;YAMZ,qBAAqB,YAAY;qBAEhC,mBAAiB,OAAA;;;;;;;;;;YAKlB,2BAA2B,kBAAkB;qBAE5C,+CAAmB;;;;;;;;;;;;;;;;YAOpB,6BAA6B,oBAAoB;;iBAG5C;EACf,oBAAoB,QAClB;aACW;aACA;aACA;aACA;;EAGb,iBAAiB,cAAc;EAC/B,oBAAoB,cAAc;;qBAUvB,qBAAsB,qBAAqB;WAC7C,MAAM;;WAEN,WAAW,WAAW,cAAc;MAC9C,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBChDY,kBAAgB,OAAA;;;;YAIjB,0BAA0B,iBAAiB;qBAE1C,8CAAkB;;;;;;;sBAAlB,OAAA;;;YAQD,4BAA4B,mBAAmB;;iBAG1C;EACf;;;iBAIe;EACf,aAAa,cAAc;;iBAGZ;WACN;WACA;;;qBAIE,YAAU,gBAAA,gBAAA,mBAAA,gBAAA;;YAGX,oBAAoB,gBAAgB,iBAAiB;qBAoBpD,oBAAqB,qBAAqB;WAC5C,MAAM;;WAEN,YACL,aAAa,2BAA2B,QAAQ;MACrD,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBC5DY,iBAAe,OAAA;YAChB,yBAAyB,gBAAgB;qBAExC,kBAAgB,OAAA;;;;YAIjB,0BAA0B,iBAAiB;qBAE1C,8CAAkB;;;;;;;;;;;;;YAMnB,4BAA4B,mBAAmB;;iBAG1C;WACN;EACT;;;iBAIe;EACf,MAAM;aAAuB;MAAiB,QAAQ;;UAG9C;WACC,UAAU,oBAAoB;WAC9B,UAAU,SAAS,eAAe;WAClC,QAAQ,MAAM,MAAM;;;qBAIlB,OAAK,gBAAA,gBAAA,UAAA,gBAAA;;YAGN,eAAe,gBAAgB,iBAAiB;qBAuB/C,oBAAqB,qBAAqB;WAC5C,MAAM;;WAEN,WAAW,WAAW,cAAc;MAC9C,OAAA,OAAA;WAYqB,OAAA;;;;;;;;;;;;;;;;;;;;;EApCb,OAAA,gBAAgB,MACrB,kBACA,oBACA,OAAO,OAAO,wBACd,UACA,eACC,UAAU,aAAa"}
|
package/dist/device/index.mjs
CHANGED
|
@@ -152,9 +152,10 @@ const Geolocation = Bundle.make("Geolocation", {
|
|
|
152
152
|
/**
|
|
153
153
|
* Media devices as a bundle factory: the enumerated device list in the
|
|
154
154
|
* Model, scanned on placement and re-scanned on every `devicechange`.
|
|
155
|
-
* Denial lands as `denied`
|
|
156
|
-
*
|
|
157
|
-
*
|
|
155
|
+
* Denial lands as `denied` while keeping the last list (actionable UI over
|
|
156
|
+
* stale data, like Geolocation's last fix); any other failure keeps the
|
|
157
|
+
* list and notes the error. No media-devices API yields an empty stream
|
|
158
|
+
* and a `Failed` scan instead of throwing.
|
|
158
159
|
*/
|
|
159
160
|
const MediaDevice = Schema.Struct({
|
|
160
161
|
deviceId: Schema.String,
|
|
@@ -245,8 +246,7 @@ const mediaDevices = (config) => {
|
|
|
245
246
|
} }),
|
|
246
247
|
Denied: () => ({ model: {
|
|
247
248
|
...model,
|
|
248
|
-
status: "denied"
|
|
249
|
-
devices: []
|
|
249
|
+
status: "denied"
|
|
250
250
|
} }),
|
|
251
251
|
Failed: ({ message }) => ({ model: {
|
|
252
252
|
...model,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["failMessage","failMessage","isDenied","failMessage"],"sources":["../../src/device/fullscreen.ts","../../src/device/geolocation.ts","../../src/device/media-devices.ts","../../src/device/media-stream.ts","../../src/device/permissions.ts"],"sourcesContent":["/**\n * Fullscreen as Commands plus an entry — not a bundle, because the document\n * owns the state: `document.fullscreenElement` is the fact, and the entry\n * reports it as `Changed`. Commands yield `Entered`/`Exited` on success and\n * `Failed` otherwise (rejected request, missing API), so `update` handles\n * both outcomes as Messages.\n */\nimport { Effect, Schema, Stream } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const FullscreenMessage = defineMessageUnion({\n Changed: { active: Schema.Boolean },\n Entered: {},\n Exited: {},\n Failed: { message: Schema.String },\n})\nexport type FullscreenMessage = typeof FullscreenMessage.Type\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\n/**\n * Requests fullscreen for an element. The capability is feature-checked on\n * the element itself (SVG and HTML elements both qualify), falling back to\n * the legacy `webkit` prefix; a platform with neither yields `Failed`\n * instead of throwing.\n */\nexport const enterFullscreen = (element: Element): Command<FullscreenMessage, never, never> => ({\n name: 'Fullscreen.enter',\n args: {},\n effect: Effect.matchEffect(\n Effect.tryPromise({\n try: () => {\n const requestable = element as unknown as {\n readonly requestFullscreen?: unknown\n readonly webkitRequestFullscreen?: unknown\n }\n const request = requestable.requestFullscreen ?? requestable.webkitRequestFullscreen\n if (typeof request !== 'function') {\n throw new Error('fullscreen is unavailable')\n }\n return (request as () => Promise<void>).call(element)\n },\n catch: (error: unknown) => error,\n }),\n {\n onFailure: error => Effect.succeed(FullscreenMessage.Failed({ message: failMessage(error) })),\n onSuccess: () => Effect.succeed(FullscreenMessage.Entered()),\n },\n ),\n})\n\n/** Leaves fullscreen, with the same legacy fallback. Without a document API it yields `Failed`, not a throw. */\nexport const exitFullscreen = (): Command<FullscreenMessage, never, never> => ({\n name: 'Fullscreen.exit',\n args: {},\n effect: Effect.matchEffect(\n Effect.tryPromise({\n try: () => {\n if (typeof document === 'undefined') {\n throw new Error('fullscreen is unavailable')\n }\n const api = document as unknown as {\n readonly exitFullscreen?: unknown\n readonly webkitExitFullscreen?: unknown\n }\n const exit = api.exitFullscreen ?? api.webkitExitFullscreen\n if (typeof exit !== 'function') {\n throw new Error('fullscreen is unavailable')\n }\n return (exit as () => Promise<void>).call(document)\n },\n catch: (error: unknown) => error,\n }),\n {\n onFailure: error => Effect.succeed(FullscreenMessage.Failed({ message: failMessage(error) })),\n onSuccess: () => Effect.succeed(FullscreenMessage.Exited()),\n },\n ),\n})\n\n/**\n * `Changed` whenever fullscreen state flips, starting with the current\n * answer. Without a document the stream is empty instead of throwing.\n */\nexport const fullscreenChanges = (): Stream.Stream<FullscreenMessage> => {\n if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') {\n return Stream.empty\n }\n // Incomplete DOMs leave fullscreenElement undefined instead of null.\n const isActive = (): boolean =>\n document.fullscreenElement !== null && document.fullscreenElement !== undefined\n return Stream.concat(\n Stream.make(FullscreenMessage.Changed({ active: isActive() })),\n Stream.fromEventListener(document, 'fullscreenchange').pipe(\n Stream.map(() => FullscreenMessage.Changed({ active: isActive() })),\n ),\n )\n}\n","/**\n * Geolocation as a bundle: the device position in the Model, watched while\n * placed. Permission denial is its own status (actionable UI); transient\n * failures keep the last fix and note the error. No geolocation API yields\n * an empty stream instead of throwing.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const Coordinates = Schema.Struct({\n latitude: Schema.Number,\n longitude: Schema.Number,\n accuracy: Schema.Number,\n})\nexport type Coordinates = typeof Coordinates.Type\n\nexport const GeolocationModel = Schema.Struct({\n status: Schema.Literals(['unknown', 'ready', 'denied']),\n coords: Schema.NullOr(Coordinates),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type GeolocationModel = typeof GeolocationModel.Type\n\nexport const GeolocationMessage = defineMessageUnion({\n Located: {\n latitude: Schema.Number,\n longitude: Schema.Number,\n accuracy: Schema.Number,\n },\n Denied: {},\n Failed: { message: Schema.String },\n})\nexport type GeolocationMessage = typeof GeolocationMessage.Type\n\ntype PositionCallback = (position: {\n readonly coords: {\n readonly latitude: number\n readonly longitude: number\n readonly accuracy: number\n }\n}) => void\ntype ErrorCallback = (error: { readonly code: number; readonly message: string }) => void\n\nconst watchStream = (): Stream.Stream<GeolocationMessage> => {\n if (\n typeof navigator === 'undefined' ||\n typeof navigator.geolocation?.watchPosition !== 'function'\n ) {\n return Stream.empty\n }\n const geolocation = navigator.geolocation\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<GeolocationMessage>()\n const onLocated: PositionCallback = position => {\n Effect.runFork(\n Queue.offer(\n queue,\n GeolocationMessage.Located({\n latitude: position.coords.latitude,\n longitude: position.coords.longitude,\n accuracy: position.coords.accuracy,\n }),\n ),\n )\n }\n const onError: ErrorCallback = error => {\n Effect.runFork(\n Queue.offer(\n queue,\n error.code === 1\n ? GeolocationMessage.Denied()\n : GeolocationMessage.Failed({ message: error.message }),\n ),\n )\n }\n const watchId = geolocation.watchPosition(onLocated, onError)\n yield* Effect.addFinalizer(() => Effect.sync(() => geolocation.clearWatch(watchId)))\n return Stream.fromQueue(queue)\n }),\n )\n}\n\nexport const Geolocation = Bundle.make<\n 'Geolocation',\n GeolocationModel,\n GeolocationMessage,\n void,\n never,\n never,\n never,\n void,\n {},\n {}\n>('Geolocation', {\n Model: GeolocationModel,\n Message: GeolocationMessage,\n init: () => ({ model: { status: 'unknown', coords: null, lastError: null } }),\n update: (model, message) =>\n GeolocationMessage.match<\n Update.ReturnWithOutMessage<GeolocationModel, GeolocationMessage, never>\n >(message, {\n Located: ({ latitude, longitude, accuracy }) => ({\n model: { ...model, status: 'ready' as const, coords: { latitude, longitude, accuracy } },\n }),\n Denied: () => ({ model: { ...model, status: 'denied' as const } }),\n Failed: ({ message }) => ({ model: { ...model, lastError: message } }),\n }),\n subscriptions: (): Subscription.Subscriptions<GeolocationModel, GeolocationMessage> =>\n Subscription.make<GeolocationModel, GeolocationMessage>()(() => ({\n watch: Subscription.persistent(watchStream()),\n })),\n})\n","/**\n * Media devices as a bundle factory: the enumerated device list in the\n * Model, scanned on placement and re-scanned on every `devicechange`.\n * Denial lands as `denied` with an empty list (actionable UI); any other\n * failure keeps the last list and notes the error. No media-devices API\n * yields an empty stream and a `Failed` scan instead of throwing.\n */\nimport { Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const MediaDevice = Schema.Struct({\n deviceId: Schema.String,\n groupId: Schema.String,\n kind: Schema.Literals(['audioinput', 'audiooutput', 'videoinput']),\n label: Schema.String,\n})\nexport type MediaDevice = typeof MediaDevice.Type\n\nexport const MediaDevicesModel = Schema.Struct({\n status: Schema.Literals(['unknown', 'ready', 'denied']),\n devices: Schema.Array(MediaDevice),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type MediaDevicesModel = typeof MediaDevicesModel.Type\n\nexport const MediaDevicesMessage = defineMessageUnion({\n Scan: {},\n DevicesChanged: {},\n Refreshed: { devices: Schema.Array(MediaDevice) },\n Denied: {},\n Failed: { message: Schema.String },\n})\nexport type MediaDevicesMessage = typeof MediaDevicesMessage.Type\n\n/** The slice of MediaDevices the bundle needs; satisfied by the platform object. */\nexport interface MediaDevicesHandle {\n enumerateDevices(): Promise<\n ReadonlyArray<{\n readonly deviceId: string\n readonly groupId: string\n readonly kind: string\n readonly label: string\n }>\n >\n addEventListener(type: string, listener: () => void): void\n removeEventListener(type: string, listener: () => void): void\n}\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst isDenied = (error: unknown): boolean =>\n typeof (error as { readonly name?: unknown } | null)?.name === 'string' &&\n ['NotAllowedError', 'SecurityError'].includes((error as { readonly name: string }).name)\n\nexport const mediaDevices = <const Name extends string>(config: {\n readonly name: Name\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n readonly create?: ((navigator: Navigator) => MediaDevicesHandle | null) | undefined\n}) => {\n const handle = (): MediaDevicesHandle | null => {\n if (typeof navigator === 'undefined') return null\n if (config.create !== undefined) return config.create(navigator)\n const devices = navigator.mediaDevices as unknown as MediaDevicesHandle | undefined\n return devices ?? null\n }\n\n const scanEffect = Effect.matchEffect(\n Effect.tryPromise({\n try: async () => {\n const devices = handle()\n if (devices === null) throw new Error('media devices are unavailable')\n const listed = await devices.enumerateDevices()\n // The platform constrains kind to three values; anything else is a\n // boundary violation, so it fails the scan instead of entering the Model.\n return listed.map(item => {\n if (\n item.kind !== 'audioinput' &&\n item.kind !== 'audiooutput' &&\n item.kind !== 'videoinput'\n ) {\n throw new Error(`unknown device kind: ${item.kind}`)\n }\n // Guarded by the check above: only the three platform kinds reach here.\n return {\n deviceId: item.deviceId,\n groupId: item.groupId,\n kind: item.kind as MediaDevice['kind'],\n label: item.label,\n }\n })\n },\n catch: (error: unknown) => error,\n }),\n {\n onFailure: error =>\n Effect.succeed(\n isDenied(error)\n ? MediaDevicesMessage.Denied()\n : MediaDevicesMessage.Failed({ message: failMessage(error) }),\n ),\n onSuccess: devices =>\n Effect.succeed(MediaDevicesMessage.Refreshed({ devices: [...devices] })),\n },\n )\n\n const scan = () => ({ name: `${config.name}.scan`, effect: scanEffect })\n\n const changedStream = (): Stream.Stream<MediaDevicesMessage> => {\n const devices = handle()\n if (devices === null) return Stream.empty\n // The handle carries only the slice the bundle needs, not the full\n // EventTarget surface, so the listener attaches through a cast.\n return Stream.fromEventListener(devices as unknown as EventTarget, 'devicechange').pipe(\n Stream.map(() => MediaDevicesMessage.DevicesChanged()),\n )\n }\n\n return Bundle.make<Name, MediaDevicesModel, MediaDevicesMessage>(config.name, {\n Model: MediaDevicesModel,\n Message: MediaDevicesMessage,\n init: () => ({\n model: { status: 'unknown', devices: [], lastError: null },\n commands: [scan()],\n }),\n update: (model, message) =>\n MediaDevicesMessage.match<\n Update.ReturnWithOutMessage<MediaDevicesModel, MediaDevicesMessage, never>\n >(message, {\n Scan: () => ({ model, commands: [scan()] }),\n DevicesChanged: () => ({ model, commands: [scan()] }),\n Refreshed: ({ devices }) => ({\n model: { ...model, status: 'ready' as const, devices: [...devices] },\n }),\n Denied: () => ({ model: { ...model, status: 'denied' as const, devices: [] } }),\n Failed: ({ message }) => ({ model: { ...model, lastError: message } }),\n }),\n subscriptions: (): Subscription.Subscriptions<MediaDevicesModel, MediaDevicesMessage> =>\n Subscription.make<MediaDevicesModel, MediaDevicesMessage>()(() => ({\n changes: Subscription.persistent(changedStream()),\n })),\n })\n}\n","/**\n * A camera/mic stream as a bundle factory: one live MediaStream held in a\n * Managed Resource while the Model asks for it. `Started` requests,\n * `Stopped` releases; denial parks at `denied` (actionable UI), any other\n * acquire failure parks at `idle` with the error noted — both clear\n * requirements, so a failing device never spins an acquire loop. Release\n * stops every track. No media-devices API fails the acquire instead of\n * throwing.\n */\nimport { Effect, Option, Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const MediaStreamModel = Schema.Struct({\n status: Schema.Literals(['idle', 'requesting', 'live', 'denied']),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type MediaStreamModel = typeof MediaStreamModel.Type\n\nexport const MediaStreamMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Live: {},\n Ended: {},\n Denied: {},\n Failed: { message: Schema.String },\n})\nexport type MediaStreamMessage = typeof MediaStreamMessage.Type\n\n/** A single media track: stopped on release. */\nexport interface MediaTrackHandle {\n stop(): void\n}\n\n/** The live stream: its tracks stop on release. */\nexport interface MediaStreamHandle {\n getTracks(): ReadonlyArray<MediaTrackHandle>\n}\n\nexport interface MediaStreamConstraints {\n readonly audio: boolean\n readonly video: boolean\n}\n\n/** The live stream, for commands that attach it to an element. One assembly holds one. */\nexport const LiveStream = ManagedResource.tag<MediaStreamHandle>()('media-stream')\n\n/** The stream service a placed camera requires. */\nexport type LiveStreamService = ManagedResource.ServiceOf<typeof LiveStream>\n\ntype StreamResources = Readonly<{\n stream: ManagedResource.Entry<\n MediaStreamModel,\n MediaStreamMessage,\n Option.Option<MediaStreamConstraints>,\n MediaStreamHandle,\n LiveStreamService,\n () => MediaStreamMessage\n >\n}>\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst isDenied = (error: unknown): boolean =>\n typeof (error as { readonly name?: unknown } | null)?.name === 'string' &&\n ['NotAllowedError', 'SecurityError'].includes((error as { readonly name: string }).name)\n\nexport const mediaStream = <const Name extends string>(config: {\n readonly name: Name\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n readonly request?:\n ((constraints: MediaStreamConstraints) => Promise<MediaStreamHandle>) | undefined\n}) => {\n const request = (constraints: MediaStreamConstraints): Promise<MediaStreamHandle> => {\n if (config.request !== undefined) return config.request(constraints)\n if (\n typeof navigator === 'undefined' ||\n typeof navigator.mediaDevices?.getUserMedia !== 'function'\n ) {\n return Promise.reject(new Error('media devices are unavailable'))\n }\n return navigator.mediaDevices.getUserMedia(constraints)\n }\n\n return Bundle.make(config.name, {\n Model: MediaStreamModel,\n Message: MediaStreamMessage,\n args: Schema.Struct({ audio: Schema.Boolean, video: Schema.Boolean }),\n init: () => ({ model: { status: 'idle' as const, lastError: null } }),\n update: (model, message) =>\n MediaStreamMessage.match<\n Update.ReturnWithOutMessage<MediaStreamModel, MediaStreamMessage, never>\n >(message, {\n Started: () => ({ model: { ...model, status: 'requesting' as const } }),\n Stopped: () => ({ model: { ...model, status: 'idle' as const } }),\n Live: () => ({ model: { ...model, status: 'live' as const, lastError: null } }),\n Ended: () => ({ model: { ...model, status: 'idle' as const } }),\n Denied: () => ({ model: { ...model, status: 'denied' as const } }),\n Failed: ({ message }) => ({\n model: { ...model, status: 'idle' as const, lastError: message },\n }),\n }),\n resources: args =>\n ManagedResource.make<MediaStreamModel, MediaStreamMessage>()(entry => ({\n stream: entry(\n Schema.Option(Schema.Struct({ audio: Schema.Boolean, video: Schema.Boolean })),\n {\n resource: LiveStream,\n modelToMaybeRequirements: model =>\n model.status === 'requesting' || model.status === 'live'\n ? Option.some({ audio: args.audio, video: args.video })\n : Option.none(),\n acquire: (constraints: MediaStreamConstraints) =>\n Effect.tryPromise({\n try: () => request(constraints),\n catch: (error: unknown) => error,\n }),\n onAcquired: () => MediaStreamMessage.Live(),\n onReleased: () => MediaStreamMessage.Ended(),\n onAcquireError: error =>\n isDenied(error)\n ? MediaStreamMessage.Denied()\n : MediaStreamMessage.Failed({ message: failMessage(error) }),\n release: stream =>\n Effect.sync(() => {\n for (const track of stream.getTracks()) track.stop()\n }),\n },\n ),\n })),\n })\n}\n","/**\n * Permission states as a bundle factory: queried on placement, kept current\n * by each status object's `onchange`. A resource holds the status objects\n * (they cannot live in the Model); its queue feeds the subscription, and\n * release detaches every handler. An unknown permission name fails the\n * acquire; an unknown state string fails it too — both become `Failed`,\n * never Model facts.\n */\nimport { Effect, Option, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const PermissionState = Schema.Literals(['granted', 'denied', 'prompt'])\nexport type PermissionState = typeof PermissionState.Type\n\nexport const PermissionsModel = Schema.Struct({\n states: Schema.Record(Schema.String, PermissionState),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type PermissionsModel = typeof PermissionsModel.Type\n\nexport const PermissionsMessage = defineMessageUnion({\n Snapshot: { states: Schema.Record(Schema.String, PermissionState) },\n Changed: { name: Schema.String, state: PermissionState },\n Cleared: {},\n Failed: { message: Schema.String },\n})\nexport type PermissionsMessage = typeof PermissionsMessage.Type\n\n/** A queried permission status; `onchange` fires while watched. */\nexport interface PermissionStatusHandle {\n readonly state: string\n onchange: (() => void) | null\n}\n\n/** The slice of the Permissions API the bundle needs. */\nexport interface PermissionsHandle {\n query(descriptor: { readonly name: string }): Promise<PermissionStatusHandle>\n}\n\ninterface Acquired {\n readonly statuses: ReadonlyMap<string, PermissionStatusHandle>\n readonly snapshot: Readonly<Record<string, PermissionState>>\n readonly events: Queue.Queue<PermissionsMessage>\n}\n\n/** The watched statuses, for the changes subscription. One assembly holds one. */\nexport const Watch = ManagedResource.tag<Acquired>()('permissions')\n\n/** The watch service a placed Permissions bundle requires. */\nexport type WatchService = ManagedResource.ServiceOf<typeof Watch>\n\ntype WatchResources = Readonly<{\n watch: ManagedResource.Entry<\n PermissionsModel,\n PermissionsMessage,\n Option.Option<ReadonlyArray<string>>,\n Acquired,\n WatchService,\n (acquired: Acquired) => PermissionsMessage\n >\n}>\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst readState = (state: string, name: string): PermissionState => {\n if (state !== 'granted' && state !== 'denied' && state !== 'prompt') {\n throw new Error(`unknown permission state for ${name}: ${state}`)\n }\n return state\n}\n\nexport const permissions = <const Name extends string>(config: {\n readonly name: Name\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n readonly create?: ((navigator: Navigator) => PermissionsHandle | null) | undefined\n}) => {\n const handle = (): PermissionsHandle | null => {\n if (typeof navigator === 'undefined') return null\n if (config.create !== undefined) return config.create(navigator)\n const api = navigator.permissions as unknown as PermissionsHandle | undefined\n return api ?? null\n }\n\n return Bundle.make<\n Name,\n PermissionsModel,\n PermissionsMessage,\n { readonly names: ReadonlyArray<string> },\n never,\n never,\n WatchService,\n void,\n WatchResources,\n {}\n >(config.name, {\n Model: PermissionsModel,\n Message: PermissionsMessage,\n args: Schema.Struct({ names: Schema.Array(Schema.String) }),\n init: () => ({ model: { states: {}, lastError: null } }),\n update: (model, message) =>\n PermissionsMessage.match<\n Update.ReturnWithOutMessage<PermissionsModel, PermissionsMessage, never>\n >(message, {\n Snapshot: ({ states }) => ({ model: { ...model, states: { ...states } } }),\n Changed: ({ name, state }) => ({\n model: { ...model, states: { ...model.states, [name]: state } },\n }),\n Cleared: () => ({ model: { ...model, states: {} } }),\n Failed: ({ message }) => ({ model: { ...model, lastError: message } }),\n }),\n resources: args =>\n ManagedResource.make<PermissionsModel, PermissionsMessage>()(entry => ({\n watch: entry(Schema.Option(Schema.Array(Schema.String)), {\n resource: Watch,\n modelToMaybeRequirements: () => Option.some([...new Set(args.names)]),\n acquire: (names: ReadonlyArray<string>) =>\n Effect.gen(function* () {\n const api = handle()\n if (api === null) throw new Error('permissions are unavailable')\n const events = yield* Queue.unbounded<PermissionsMessage>()\n const statuses = new Map<string, PermissionStatusHandle>()\n // Offers run in arrival order; see the WebSocket bundle.\n let tail: Promise<void> = Promise.resolve()\n const offer = (message: PermissionsMessage) => {\n const run = () =>\n Effect.runPromise(Queue.offer(events, message)).then(\n () => undefined,\n () => undefined,\n )\n tail = tail.then(run, run)\n }\n // Two phases: query and validate everything first, so a later\n // name failing cannot abandon handlers attached to earlier ones.\n const queried: Array<readonly [string, PermissionStatusHandle, PermissionState]> = []\n for (const name of names) {\n const status = yield* Effect.tryPromise({\n try: () => api.query({ name }),\n catch: (error: unknown) => error,\n })\n queried.push([name, status, readState(status.state, name)] as const)\n }\n const snapshot: Record<string, PermissionState> = {}\n for (const [name, status, state] of queried) {\n status.onchange = () => {\n try {\n offer(\n PermissionsMessage.Changed({ name, state: readState(status.state, name) }),\n )\n } catch {\n // readState evaluates before offer runs, so a state the\n // schema rejects lands here instead of crashing the handler.\n offer(\n PermissionsMessage.Failed({\n message: `unknown permission state for ${name}`,\n }),\n )\n }\n }\n statuses.set(name, status)\n snapshot[name] = state\n }\n return { statuses, snapshot, events }\n }),\n // The snapshot is captured during acquire, so onAcquired cannot\n // throw re-reading a state that changed mid-acquire.\n onAcquired: ({ snapshot }) => PermissionsMessage.Snapshot({ states: { ...snapshot } }),\n onReleased: () => PermissionsMessage.Cleared(),\n onAcquireError: error => PermissionsMessage.Failed({ message: failMessage(error) }),\n release: ({ statuses, events }) =>\n Effect.asVoid(\n Effect.andThen(\n Effect.sync(() => {\n for (const status of statuses.values()) status.onchange = null\n }),\n Queue.shutdown(events),\n ),\n ),\n }),\n })),\n subscriptions: (): Subscription.Subscriptions<\n PermissionsModel,\n PermissionsMessage,\n WatchService\n > =>\n Subscription.make<PermissionsModel, PermissionsMessage, WatchService>()(() => ({\n // No model gate: the queue outlives every state change, and release\n // ends the stream through teardown, not through dependencies.\n changes: Subscription.persistent(\n Stream.unwrap(\n Effect.matchEffect(Watch.get, {\n // Released between placement and attach: end quietly.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ events }) => Effect.succeed(Stream.fromQueue(events)),\n }),\n ),\n ),\n })),\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["failMessage","failMessage","isDenied","failMessage"],"sources":["../../src/device/fullscreen.ts","../../src/device/geolocation.ts","../../src/device/media-devices.ts","../../src/device/media-stream.ts","../../src/device/permissions.ts"],"sourcesContent":["/**\n * Fullscreen as Commands plus an entry — not a bundle, because the document\n * owns the state: `document.fullscreenElement` is the fact, and the entry\n * reports it as `Changed`. Commands yield `Entered`/`Exited` on success and\n * `Failed` otherwise (rejected request, missing API), so `update` handles\n * both outcomes as Messages.\n */\nimport { Effect, Schema, Stream } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const FullscreenMessage = defineMessageUnion({\n Changed: { active: Schema.Boolean },\n Entered: {},\n Exited: {},\n Failed: { message: Schema.String },\n})\nexport type FullscreenMessage = typeof FullscreenMessage.Type\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\n/**\n * Requests fullscreen for an element. The capability is feature-checked on\n * the element itself (SVG and HTML elements both qualify), falling back to\n * the legacy `webkit` prefix; a platform with neither yields `Failed`\n * instead of throwing.\n */\nexport const enterFullscreen = (element: Element): Command<FullscreenMessage, never, never> => ({\n name: 'Fullscreen.enter',\n args: {},\n effect: Effect.matchEffect(\n Effect.tryPromise({\n try: () => {\n const requestable = element as unknown as {\n readonly requestFullscreen?: unknown\n readonly webkitRequestFullscreen?: unknown\n }\n const request = requestable.requestFullscreen ?? requestable.webkitRequestFullscreen\n if (typeof request !== 'function') {\n throw new Error('fullscreen is unavailable')\n }\n return (request as () => Promise<void>).call(element)\n },\n catch: (error: unknown) => error,\n }),\n {\n onFailure: error => Effect.succeed(FullscreenMessage.Failed({ message: failMessage(error) })),\n onSuccess: () => Effect.succeed(FullscreenMessage.Entered()),\n },\n ),\n})\n\n/** Leaves fullscreen, with the same legacy fallback. Without a document API it yields `Failed`, not a throw. */\nexport const exitFullscreen = (): Command<FullscreenMessage, never, never> => ({\n name: 'Fullscreen.exit',\n args: {},\n effect: Effect.matchEffect(\n Effect.tryPromise({\n try: () => {\n if (typeof document === 'undefined') {\n throw new Error('fullscreen is unavailable')\n }\n const api = document as unknown as {\n readonly exitFullscreen?: unknown\n readonly webkitExitFullscreen?: unknown\n }\n const exit = api.exitFullscreen ?? api.webkitExitFullscreen\n if (typeof exit !== 'function') {\n throw new Error('fullscreen is unavailable')\n }\n return (exit as () => Promise<void>).call(document)\n },\n catch: (error: unknown) => error,\n }),\n {\n onFailure: error => Effect.succeed(FullscreenMessage.Failed({ message: failMessage(error) })),\n onSuccess: () => Effect.succeed(FullscreenMessage.Exited()),\n },\n ),\n})\n\n/**\n * `Changed` whenever fullscreen state flips, starting with the current\n * answer. Without a document the stream is empty instead of throwing.\n */\nexport const fullscreenChanges = (): Stream.Stream<FullscreenMessage> => {\n if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') {\n return Stream.empty\n }\n // Incomplete DOMs leave fullscreenElement undefined instead of null.\n const isActive = (): boolean =>\n document.fullscreenElement !== null && document.fullscreenElement !== undefined\n return Stream.concat(\n Stream.make(FullscreenMessage.Changed({ active: isActive() })),\n Stream.fromEventListener(document, 'fullscreenchange').pipe(\n Stream.map(() => FullscreenMessage.Changed({ active: isActive() })),\n ),\n )\n}\n","/**\n * Geolocation as a bundle: the device position in the Model, watched while\n * placed. Permission denial is its own status (actionable UI); transient\n * failures keep the last fix and note the error. No geolocation API yields\n * an empty stream instead of throwing.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const Coordinates = Schema.Struct({\n latitude: Schema.Number,\n longitude: Schema.Number,\n accuracy: Schema.Number,\n})\nexport type Coordinates = typeof Coordinates.Type\n\nexport const GeolocationModel = Schema.Struct({\n status: Schema.Literals(['unknown', 'ready', 'denied']),\n coords: Schema.NullOr(Coordinates),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type GeolocationModel = typeof GeolocationModel.Type\n\nexport const GeolocationMessage = defineMessageUnion({\n Located: {\n latitude: Schema.Number,\n longitude: Schema.Number,\n accuracy: Schema.Number,\n },\n Denied: {},\n Failed: { message: Schema.String },\n})\nexport type GeolocationMessage = typeof GeolocationMessage.Type\n\ntype PositionCallback = (position: {\n readonly coords: {\n readonly latitude: number\n readonly longitude: number\n readonly accuracy: number\n }\n}) => void\ntype ErrorCallback = (error: { readonly code: number; readonly message: string }) => void\n\nconst watchStream = (): Stream.Stream<GeolocationMessage> => {\n if (\n typeof navigator === 'undefined' ||\n typeof navigator.geolocation?.watchPosition !== 'function'\n ) {\n return Stream.empty\n }\n const geolocation = navigator.geolocation\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<GeolocationMessage>()\n const onLocated: PositionCallback = position => {\n Effect.runFork(\n Queue.offer(\n queue,\n GeolocationMessage.Located({\n latitude: position.coords.latitude,\n longitude: position.coords.longitude,\n accuracy: position.coords.accuracy,\n }),\n ),\n )\n }\n const onError: ErrorCallback = error => {\n Effect.runFork(\n Queue.offer(\n queue,\n error.code === 1\n ? GeolocationMessage.Denied()\n : GeolocationMessage.Failed({ message: error.message }),\n ),\n )\n }\n const watchId = geolocation.watchPosition(onLocated, onError)\n yield* Effect.addFinalizer(() => Effect.sync(() => geolocation.clearWatch(watchId)))\n return Stream.fromQueue(queue)\n }),\n )\n}\n\nexport const Geolocation = Bundle.make<\n 'Geolocation',\n GeolocationModel,\n GeolocationMessage,\n void,\n never,\n never,\n never,\n void,\n {},\n {}\n>('Geolocation', {\n Model: GeolocationModel,\n Message: GeolocationMessage,\n init: () => ({ model: { status: 'unknown', coords: null, lastError: null } }),\n update: (model, message) =>\n GeolocationMessage.match<\n Update.ReturnWithOutMessage<GeolocationModel, GeolocationMessage, never>\n >(message, {\n Located: ({ latitude, longitude, accuracy }) => ({\n model: { ...model, status: 'ready' as const, coords: { latitude, longitude, accuracy } },\n }),\n Denied: () => ({ model: { ...model, status: 'denied' as const } }),\n Failed: ({ message }) => ({ model: { ...model, lastError: message } }),\n }),\n subscriptions: (): Subscription.Subscriptions<GeolocationModel, GeolocationMessage> =>\n Subscription.make<GeolocationModel, GeolocationMessage>()(() => ({\n watch: Subscription.persistent(watchStream()),\n })),\n})\n","/**\n * Media devices as a bundle factory: the enumerated device list in the\n * Model, scanned on placement and re-scanned on every `devicechange`.\n * Denial lands as `denied` while keeping the last list (actionable UI over\n * stale data, like Geolocation's last fix); any other failure keeps the\n * list and notes the error. No media-devices API yields an empty stream\n * and a `Failed` scan instead of throwing.\n */\nimport { Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const MediaDevice = Schema.Struct({\n deviceId: Schema.String,\n groupId: Schema.String,\n kind: Schema.Literals(['audioinput', 'audiooutput', 'videoinput']),\n label: Schema.String,\n})\nexport type MediaDevice = typeof MediaDevice.Type\n\nexport const MediaDevicesModel = Schema.Struct({\n status: Schema.Literals(['unknown', 'ready', 'denied']),\n devices: Schema.Array(MediaDevice),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type MediaDevicesModel = typeof MediaDevicesModel.Type\n\nexport const MediaDevicesMessage = defineMessageUnion({\n Scan: {},\n DevicesChanged: {},\n Refreshed: { devices: Schema.Array(MediaDevice) },\n Denied: {},\n Failed: { message: Schema.String },\n})\nexport type MediaDevicesMessage = typeof MediaDevicesMessage.Type\n\n/** The slice of MediaDevices the bundle needs; satisfied by the platform object. */\nexport interface MediaDevicesHandle {\n enumerateDevices(): Promise<\n ReadonlyArray<{\n readonly deviceId: string\n readonly groupId: string\n readonly kind: string\n readonly label: string\n }>\n >\n addEventListener(type: string, listener: () => void): void\n removeEventListener(type: string, listener: () => void): void\n}\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst isDenied = (error: unknown): boolean =>\n typeof (error as { readonly name?: unknown } | null)?.name === 'string' &&\n ['NotAllowedError', 'SecurityError'].includes((error as { readonly name: string }).name)\n\nexport const mediaDevices = <const Name extends string>(config: {\n readonly name: Name\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n readonly create?: ((navigator: Navigator) => MediaDevicesHandle | null) | undefined\n}) => {\n const handle = (): MediaDevicesHandle | null => {\n if (typeof navigator === 'undefined') return null\n if (config.create !== undefined) return config.create(navigator)\n const devices = navigator.mediaDevices as unknown as MediaDevicesHandle | undefined\n return devices ?? null\n }\n\n const scanEffect = Effect.matchEffect(\n Effect.tryPromise({\n try: async () => {\n const devices = handle()\n if (devices === null) throw new Error('media devices are unavailable')\n const listed = await devices.enumerateDevices()\n // The platform constrains kind to three values; anything else is a\n // boundary violation, so it fails the scan instead of entering the Model.\n return listed.map(item => {\n if (\n item.kind !== 'audioinput' &&\n item.kind !== 'audiooutput' &&\n item.kind !== 'videoinput'\n ) {\n throw new Error(`unknown device kind: ${item.kind}`)\n }\n // Guarded by the check above: only the three platform kinds reach here.\n return {\n deviceId: item.deviceId,\n groupId: item.groupId,\n kind: item.kind as MediaDevice['kind'],\n label: item.label,\n }\n })\n },\n catch: (error: unknown) => error,\n }),\n {\n onFailure: error =>\n Effect.succeed(\n isDenied(error)\n ? MediaDevicesMessage.Denied()\n : MediaDevicesMessage.Failed({ message: failMessage(error) }),\n ),\n onSuccess: devices =>\n Effect.succeed(MediaDevicesMessage.Refreshed({ devices: [...devices] })),\n },\n )\n\n const scan = () => ({ name: `${config.name}.scan`, effect: scanEffect })\n\n const changedStream = (): Stream.Stream<MediaDevicesMessage> => {\n const devices = handle()\n if (devices === null) return Stream.empty\n // The handle carries only the slice the bundle needs, not the full\n // EventTarget surface, so the listener attaches through a cast.\n return Stream.fromEventListener(devices as unknown as EventTarget, 'devicechange').pipe(\n Stream.map(() => MediaDevicesMessage.DevicesChanged()),\n )\n }\n\n return Bundle.make<Name, MediaDevicesModel, MediaDevicesMessage>(config.name, {\n Model: MediaDevicesModel,\n Message: MediaDevicesMessage,\n init: () => ({\n model: { status: 'unknown', devices: [], lastError: null },\n commands: [scan()],\n }),\n update: (model, message) =>\n MediaDevicesMessage.match<\n Update.ReturnWithOutMessage<MediaDevicesModel, MediaDevicesMessage, never>\n >(message, {\n Scan: () => ({ model, commands: [scan()] }),\n DevicesChanged: () => ({ model, commands: [scan()] }),\n Refreshed: ({ devices }) => ({\n model: { ...model, status: 'ready' as const, devices: [...devices] },\n }),\n Denied: () => ({ model: { ...model, status: 'denied' as const } }),\n Failed: ({ message }) => ({ model: { ...model, lastError: message } }),\n }),\n subscriptions: (): Subscription.Subscriptions<MediaDevicesModel, MediaDevicesMessage> =>\n Subscription.make<MediaDevicesModel, MediaDevicesMessage>()(() => ({\n changes: Subscription.persistent(changedStream()),\n })),\n })\n}\n","/**\n * A camera/mic stream as a bundle factory: one live MediaStream held in a\n * Managed Resource while the Model asks for it. `Started` requests,\n * `Stopped` releases; denial parks at `denied` (actionable UI), any other\n * acquire failure parks at `idle` with the error noted — both clear\n * requirements, so a failing device never spins an acquire loop. Release\n * stops every track. No media-devices API fails the acquire instead of\n * throwing.\n */\nimport { Effect, Option, Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const MediaStreamModel = Schema.Struct({\n status: Schema.Literals(['idle', 'requesting', 'live', 'denied']),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type MediaStreamModel = typeof MediaStreamModel.Type\n\nexport const MediaStreamMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Live: {},\n Ended: {},\n Denied: {},\n Failed: { message: Schema.String },\n})\nexport type MediaStreamMessage = typeof MediaStreamMessage.Type\n\n/** A single media track: stopped on release. */\nexport interface MediaTrackHandle {\n stop(): void\n}\n\n/** The live stream: its tracks stop on release. */\nexport interface MediaStreamHandle {\n getTracks(): ReadonlyArray<MediaTrackHandle>\n}\n\nexport interface MediaStreamConstraints {\n readonly audio: boolean\n readonly video: boolean\n}\n\n/** The live stream, for commands that attach it to an element. One assembly holds one. */\nexport const LiveStream = ManagedResource.tag<MediaStreamHandle>()('media-stream')\n\n/** The stream service a placed camera requires. */\nexport type LiveStreamService = ManagedResource.ServiceOf<typeof LiveStream>\n\ntype StreamResources = Readonly<{\n stream: ManagedResource.Entry<\n MediaStreamModel,\n MediaStreamMessage,\n Option.Option<MediaStreamConstraints>,\n MediaStreamHandle,\n LiveStreamService,\n () => MediaStreamMessage\n >\n}>\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst isDenied = (error: unknown): boolean =>\n typeof (error as { readonly name?: unknown } | null)?.name === 'string' &&\n ['NotAllowedError', 'SecurityError'].includes((error as { readonly name: string }).name)\n\nexport const mediaStream = <const Name extends string>(config: {\n readonly name: Name\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n readonly request?:\n ((constraints: MediaStreamConstraints) => Promise<MediaStreamHandle>) | undefined\n}) => {\n const request = (constraints: MediaStreamConstraints): Promise<MediaStreamHandle> => {\n if (config.request !== undefined) return config.request(constraints)\n if (\n typeof navigator === 'undefined' ||\n typeof navigator.mediaDevices?.getUserMedia !== 'function'\n ) {\n return Promise.reject(new Error('media devices are unavailable'))\n }\n return navigator.mediaDevices.getUserMedia(constraints)\n }\n\n return Bundle.make(config.name, {\n Model: MediaStreamModel,\n Message: MediaStreamMessage,\n args: Schema.Struct({ audio: Schema.Boolean, video: Schema.Boolean }),\n init: () => ({ model: { status: 'idle' as const, lastError: null } }),\n update: (model, message) =>\n MediaStreamMessage.match<\n Update.ReturnWithOutMessage<MediaStreamModel, MediaStreamMessage, never>\n >(message, {\n Started: () => ({ model: { ...model, status: 'requesting' as const } }),\n Stopped: () => ({ model: { ...model, status: 'idle' as const } }),\n Live: () => ({ model: { ...model, status: 'live' as const, lastError: null } }),\n Ended: () => ({ model: { ...model, status: 'idle' as const } }),\n Denied: () => ({ model: { ...model, status: 'denied' as const } }),\n Failed: ({ message }) => ({\n model: { ...model, status: 'idle' as const, lastError: message },\n }),\n }),\n resources: args =>\n ManagedResource.make<MediaStreamModel, MediaStreamMessage>()(entry => ({\n stream: entry(\n Schema.Option(Schema.Struct({ audio: Schema.Boolean, video: Schema.Boolean })),\n {\n resource: LiveStream,\n modelToMaybeRequirements: model =>\n model.status === 'requesting' || model.status === 'live'\n ? Option.some({ audio: args.audio, video: args.video })\n : Option.none(),\n acquire: (constraints: MediaStreamConstraints) =>\n Effect.tryPromise({\n try: () => request(constraints),\n catch: (error: unknown) => error,\n }),\n onAcquired: () => MediaStreamMessage.Live(),\n onReleased: () => MediaStreamMessage.Ended(),\n onAcquireError: error =>\n isDenied(error)\n ? MediaStreamMessage.Denied()\n : MediaStreamMessage.Failed({ message: failMessage(error) }),\n release: stream =>\n Effect.sync(() => {\n for (const track of stream.getTracks()) track.stop()\n }),\n },\n ),\n })),\n })\n}\n","/**\n * Permission states as a bundle factory: queried on placement, kept current\n * by each status object's `onchange`. A resource holds the status objects\n * (they cannot live in the Model); its queue feeds the subscription, and\n * release detaches every handler. An unknown permission name fails the\n * acquire; an unknown state string fails it too — both become `Failed`,\n * never Model facts.\n */\nimport { Effect, Option, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const PermissionState = Schema.Literals(['granted', 'denied', 'prompt'])\nexport type PermissionState = typeof PermissionState.Type\n\nexport const PermissionsModel = Schema.Struct({\n states: Schema.Record(Schema.String, PermissionState),\n lastError: Schema.NullOr(Schema.String),\n})\nexport type PermissionsModel = typeof PermissionsModel.Type\n\nexport const PermissionsMessage = defineMessageUnion({\n Snapshot: { states: Schema.Record(Schema.String, PermissionState) },\n Changed: { name: Schema.String, state: PermissionState },\n Cleared: {},\n Failed: { message: Schema.String },\n})\nexport type PermissionsMessage = typeof PermissionsMessage.Type\n\n/** A queried permission status; `onchange` fires while watched. */\nexport interface PermissionStatusHandle {\n readonly state: string\n onchange: (() => void) | null\n}\n\n/** The slice of the Permissions API the bundle needs. */\nexport interface PermissionsHandle {\n query(descriptor: { readonly name: string }): Promise<PermissionStatusHandle>\n}\n\ninterface Acquired {\n readonly statuses: ReadonlyMap<string, PermissionStatusHandle>\n readonly snapshot: Readonly<Record<string, PermissionState>>\n readonly events: Queue.Queue<PermissionsMessage>\n}\n\n/** The watched statuses, for the changes subscription. One assembly holds one. */\nexport const Watch = ManagedResource.tag<Acquired>()('permissions')\n\n/** The watch service a placed Permissions bundle requires. */\nexport type WatchService = ManagedResource.ServiceOf<typeof Watch>\n\ntype WatchResources = Readonly<{\n watch: ManagedResource.Entry<\n PermissionsModel,\n PermissionsMessage,\n Option.Option<ReadonlyArray<string>>,\n Acquired,\n WatchService,\n (acquired: Acquired) => PermissionsMessage\n >\n}>\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst readState = (state: string, name: string): PermissionState => {\n if (state !== 'granted' && state !== 'denied' && state !== 'prompt') {\n throw new Error(`unknown permission state for ${name}: ${state}`)\n }\n return state\n}\n\nexport const permissions = <const Name extends string>(config: {\n readonly name: Name\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n readonly create?: ((navigator: Navigator) => PermissionsHandle | null) | undefined\n}) => {\n const handle = (): PermissionsHandle | null => {\n if (typeof navigator === 'undefined') return null\n if (config.create !== undefined) return config.create(navigator)\n const api = navigator.permissions as unknown as PermissionsHandle | undefined\n return api ?? null\n }\n\n return Bundle.make<\n Name,\n PermissionsModel,\n PermissionsMessage,\n { readonly names: ReadonlyArray<string> },\n never,\n never,\n WatchService,\n void,\n WatchResources,\n {}\n >(config.name, {\n Model: PermissionsModel,\n Message: PermissionsMessage,\n args: Schema.Struct({ names: Schema.Array(Schema.String) }),\n init: () => ({ model: { states: {}, lastError: null } }),\n update: (model, message) =>\n PermissionsMessage.match<\n Update.ReturnWithOutMessage<PermissionsModel, PermissionsMessage, never>\n >(message, {\n Snapshot: ({ states }) => ({ model: { ...model, states: { ...states } } }),\n Changed: ({ name, state }) => ({\n model: { ...model, states: { ...model.states, [name]: state } },\n }),\n Cleared: () => ({ model: { ...model, states: {} } }),\n Failed: ({ message }) => ({ model: { ...model, lastError: message } }),\n }),\n resources: args =>\n ManagedResource.make<PermissionsModel, PermissionsMessage>()(entry => ({\n watch: entry(Schema.Option(Schema.Array(Schema.String)), {\n resource: Watch,\n modelToMaybeRequirements: () => Option.some([...new Set(args.names)]),\n acquire: (names: ReadonlyArray<string>) =>\n Effect.gen(function* () {\n const api = handle()\n if (api === null) throw new Error('permissions are unavailable')\n const events = yield* Queue.unbounded<PermissionsMessage>()\n const statuses = new Map<string, PermissionStatusHandle>()\n // Offers run in arrival order; see the WebSocket bundle.\n let tail: Promise<void> = Promise.resolve()\n const offer = (message: PermissionsMessage) => {\n const run = () =>\n Effect.runPromise(Queue.offer(events, message)).then(\n () => undefined,\n () => undefined,\n )\n tail = tail.then(run, run)\n }\n // Two phases: query and validate everything first, so a later\n // name failing cannot abandon handlers attached to earlier ones.\n const queried: Array<readonly [string, PermissionStatusHandle, PermissionState]> = []\n for (const name of names) {\n const status = yield* Effect.tryPromise({\n try: () => api.query({ name }),\n catch: (error: unknown) => error,\n })\n queried.push([name, status, readState(status.state, name)] as const)\n }\n const snapshot: Record<string, PermissionState> = {}\n for (const [name, status, state] of queried) {\n status.onchange = () => {\n try {\n offer(\n PermissionsMessage.Changed({ name, state: readState(status.state, name) }),\n )\n } catch {\n // readState evaluates before offer runs, so a state the\n // schema rejects lands here instead of crashing the handler.\n offer(\n PermissionsMessage.Failed({\n message: `unknown permission state for ${name}`,\n }),\n )\n }\n }\n statuses.set(name, status)\n snapshot[name] = state\n }\n return { statuses, snapshot, events }\n }),\n // The snapshot is captured during acquire, so onAcquired cannot\n // throw re-reading a state that changed mid-acquire.\n onAcquired: ({ snapshot }) => PermissionsMessage.Snapshot({ states: { ...snapshot } }),\n onReleased: () => PermissionsMessage.Cleared(),\n onAcquireError: error => PermissionsMessage.Failed({ message: failMessage(error) }),\n release: ({ statuses, events }) =>\n Effect.asVoid(\n Effect.andThen(\n Effect.sync(() => {\n for (const status of statuses.values()) status.onchange = null\n }),\n Queue.shutdown(events),\n ),\n ),\n }),\n })),\n subscriptions: (): Subscription.Subscriptions<\n PermissionsModel,\n PermissionsMessage,\n WatchService\n > =>\n Subscription.make<PermissionsModel, PermissionsMessage, WatchService>()(() => ({\n // No model gate: the queue outlives every state change, and release\n // ends the stream through teardown, not through dependencies.\n changes: Subscription.persistent(\n Stream.unwrap(\n Effect.matchEffect(Watch.get, {\n // Released between placement and attach: end quietly.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ events }) => Effect.succeed(Stream.fromQueue(events)),\n }),\n ),\n ),\n })),\n })\n}\n"],"mappings":"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"}
|
package/dist/dom/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/dom/autofocus.ts","../../src/dom/clipboard.ts","../../src/dom/input-mask.ts","../../src/dom/script-loader.ts","../../src/dom/share.ts"],"mappings":";;;;qBAUa,SAAO,OAAA;YACR,iBAAiB,QAAQ;qBAExB,WAAS,MAAA;;;;;qBCJT,4CAAgB;;;sBAAhB,OAAA;;;YAID,0BAA0B,iBAAiB;;;;;;qBAU1C,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/dom/autofocus.ts","../../src/dom/clipboard.ts","../../src/dom/input-mask.ts","../../src/dom/script-loader.ts","../../src/dom/share.ts"],"mappings":";;;;qBAUa,SAAO,OAAA;YACR,iBAAiB,QAAQ;qBAExB,WAAS,MAAA;;;;;qBCJT,4CAAgB;;;sBAAhB,OAAA;;;YAID,0BAA0B,iBAAiB;;;;;;qBAU1C,WAAY,iBAAe,QAAQ;;;qBCXnC,OAAK,OAAA;;;;YAIN,eAAe,MAAM;qBA2BpB,YACX,eACA;WACY;WAAyB;;qBAoC1B,WAAS,MAAA;;;;;;;;;qBCvET,yCAAa;;;sBAAb,OAAA;;;YAID,uBAAuB,cAAc;;qBASpC,aAAc,gBAAc,QAAQ;;;qBCbpC,WAAS,OAAA;;;;;YAKV,mBAAmB,UAAU;qBAE5B,wCAAY;;;;sBAAZ,OAAA;;;YAKD,sBAAsB,aAAa;;qBAUlC,QAAS,MAAM,cAAY,QAAQ"}
|
package/dist/events/index.d.mts
CHANGED
|
@@ -51,7 +51,7 @@ export type IdleMessage = typeof IdleMessage.Type;
|
|
|
51
51
|
export declare const Idle: Bundle.Bundle<"Idle", {
|
|
52
52
|
readonly timeoutMs: number;
|
|
53
53
|
}, {
|
|
54
|
-
idle: boolean;
|
|
54
|
+
readonly idle: boolean;
|
|
55
55
|
}, {
|
|
56
56
|
readonly _tag: "BecameIdle";
|
|
57
57
|
} | {
|
|
@@ -123,7 +123,7 @@ export declare const VisibilityMessage: import("foldkit/message").MessageUnion<{
|
|
|
123
123
|
}>;
|
|
124
124
|
export type VisibilityMessage = typeof VisibilityMessage.Type;
|
|
125
125
|
export declare const Visibility: Bundle.Bundle<"Visibility", void, {
|
|
126
|
-
visible: boolean;
|
|
126
|
+
readonly visible: boolean;
|
|
127
127
|
}, {
|
|
128
128
|
readonly _tag: "Changed";
|
|
129
129
|
readonly visible: boolean;
|
|
@@ -143,8 +143,8 @@ export declare const WindowSizeMessage: import("foldkit/message").MessageUnion<{
|
|
|
143
143
|
}>;
|
|
144
144
|
export type WindowSizeMessage = typeof WindowSizeMessage.Type;
|
|
145
145
|
export declare const WindowSize: Bundle.Bundle<"WindowSize", void, {
|
|
146
|
-
width: number;
|
|
147
|
-
height: number;
|
|
146
|
+
readonly width: number;
|
|
147
|
+
readonly height: number;
|
|
148
148
|
}, {
|
|
149
149
|
readonly _tag: "Changed";
|
|
150
150
|
readonly width: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/events/active-element.ts","../../src/events/hotkeys.ts","../../src/events/idle.ts","../../src/events/keyboard.ts","../../src/events/pointer.ts","../../src/events/scroll.ts","../../src/events/visibility.ts","../../src/events/window-size.ts"],"mappings":";;;qBASa,gDAAoB;;;;;;YAGrB,8BAA8B,qBAAqB;;;;;;qBAOlD,2BAA0B,OAAO,OAAO;;;;;;;;;;;;;;;iBCNpC;WACN;WACA;WACA;WACA;WACA;WACA;;qBAkCE,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/events/active-element.ts","../../src/events/hotkeys.ts","../../src/events/idle.ts","../../src/events/keyboard.ts","../../src/events/pointer.ts","../../src/events/scroll.ts","../../src/events/visibility.ts","../../src/events/window-size.ts"],"mappings":";;;qBASa,gDAAoB;;;;;;YAGrB,8BAA8B,qBAAqB;;;;;;qBAOlD,2BAA0B,OAAO,OAAO;;;;;;;;;;;;;;;iBCNpC;WACN;WACA;WACA;WACA;WACA;WACA;;qBAkCE,cAAe,iBAAiB,OAAO;;;qBCvCvC,WAAS,OAAA;;;YACV,mBAAmB,UAAU;qBAE5B,uCAAW;;;;YACZ,qBAAqB,YAAY;qBAchC,MAAI,OAAA;;;;;;;;;;;qBCvBJ,2CAAe;;;;;;;;;;;;;YAWhB,yBAAyB,gBAAgB;;;;;;qBAOxC,sBAAqB,OAAO,OAAO;;;qBClBnC,0CAAc;;;;;;YAGf,wBAAwB,eAAe;;;;;;qBAOtC,qBAAoB,OAAO,OAAO;;;qBCVlC,yCAAa;;;;;;YAGd,uBAAuB,cAAc;;;;;;;qBAQpC,oBAAmB,OAAO,OAAO;;;qBCTjC,iBAAe,OAAA;;;YAChB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;sBAAjB,OAAA;;;YACD,2BAA2B,kBAAkB;qBAiB5C,YAAU,OAAA;;;;;;;;qBCpBV,iBAAe,OAAA;;;;YAChB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;;;;;YAGlB,2BAA2B,kBAAkB;qBAmC5C,YAAU,OAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Coordinates, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, LiveStream, LiveStreamService, MediaDevice, MediaDevicesHandle, MediaDevicesMessage, MediaDevicesModel, MediaStreamConstraints, MediaStreamHandle, MediaStreamMessage, MediaStreamModel, MediaTrackHandle, PermissionState, PermissionStatusHandle, PermissionsHandle, PermissionsMessage, PermissionsModel, Watch, WatchService, enterFullscreen, exitFullscreen, fullscreenChanges, mediaDevices, mediaStream, permissions } from "./device/index.mjs";
|
|
2
2
|
import { Autofocus, ClipboardMessage, Focused, Input, InputMask, ScriptMessage, ShareData, ShareMessage, applyMask, copyText, loadScript, share } from "./dom/index.mjs";
|
|
3
3
|
import { ActiveElementMessage, Idle, IdleMessage, IdleModel, KeyPress, KeyboardMessage, PointerMessage, ScrollMessage, Visibility, VisibilityMessage, VisibilityModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, keyboardEvents, matchHotkey, pointerEvents, scrollEvents } from "./events/index.mjs";
|
|
4
|
-
import { Breakpoints, BreakpointsMessage, BreakpointsModel, MediaQuery, MediaQueryMessage, MediaQueryModel, Platform, PrefersDark, PrefersReducedMotion, breakpointFor, isBrowser, isServer, platformFromUA } from "./media/index.mjs";
|
|
4
|
+
import { Breakpoints, BreakpointsMessage, BreakpointsModel, MediaQuery, MediaQueryMessage, MediaQueryModel, Platform, PlatformHints, PrefersDark, PrefersReducedMotion, breakpointFor, isBrowser, isServer, platformFromUA } from "./media/index.mjs";
|
|
5
5
|
import { Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible } from "./motion/index.mjs";
|
|
6
6
|
import { Acquired, AcquiredSource, BroadcastChannelHandle, BroadcastMessage, Online, OnlineMessage, OnlineModel, Socket, SocketHandle, SocketService, Source, SourceHandle, SourceService, SseMessage, SseModel, WebSocketMessage, WebSocketModel, broadcastMessages, postBroadcast, sse, websocket } from "./net/index.mjs";
|
|
7
7
|
import { Bounds, Intersection, IntersectionChanged, Measured, Mutated, Mutation, Resize, Resized } from "./observers/index.mjs";
|
|
8
|
-
import { HistoryModel, Locale, LocaleMessage, LocaleModel, MasonryPlacement, MeasureRow, Pagination, PaginationMessage, PaginationModel, RowMeasured, SelectionSet, SelectionSetMessage, SelectionSetModel, Viewport, ViewportScrolled, Virtual, VirtualLayout, VirtualMessage, VirtualModel, VirtualWindow, canRedo, canUndo, history, isAtEnd, isSelected, masonry, offset, offsetFor, pageCount, range, stickyHeader, totalHeight, visibleRange, windowFor } from "./state/index.mjs";
|
|
8
|
+
import { HistoryModel, Locale, LocaleMessage, LocaleModel, MasonryPlacement, MeasureRow, Pagination, PaginationMessage, PaginationModel, RowMeasured, SelectionSet, SelectionSetMessage, SelectionSetModel, Viewport, ViewportScrolled, Virtual, VirtualLayout, VirtualMessage, VirtualModel, VirtualWindow, canRedo, canUndo, distanceToEnd, history, isAtEnd, isSelected, masonry, offset, offsetFor, pageCount, range, stickyHeader, totalHeight, visibleRange, windowFor } from "./state/index.mjs";
|
|
9
9
|
import { Interval, IntervalMessage, IntervalModel, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, debounce, formatRelativeTime } from "./time/index.mjs";
|
|
10
|
-
export { Acquired, AcquiredSource, ActiveElementMessage, Autofocus, Bounds, Breakpoints, BreakpointsMessage, BreakpointsModel, BroadcastChannelHandle, BroadcastMessage, ClipboardMessage, Coordinates, Focused, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, HistoryModel, Idle, IdleMessage, IdleModel, Input, InputMask, Intersection, IntersectionChanged, Interval, IntervalMessage, IntervalModel, KeyPress, KeyboardMessage, LiveStream, LiveStreamService, Locale, LocaleMessage, LocaleModel, MasonryPlacement, MeasureRow, Measured, MediaDevice, MediaDevicesHandle, MediaDevicesMessage, MediaDevicesModel, MediaQuery, MediaQueryMessage, MediaQueryModel, MediaStreamConstraints, MediaStreamHandle, MediaStreamMessage, MediaStreamModel, MediaTrackHandle, Mutated, Mutation, Online, OnlineMessage, OnlineModel, Pagination, PaginationMessage, PaginationModel, PermissionState, PermissionStatusHandle, PermissionsHandle, PermissionsMessage, PermissionsModel, Platform, PointerMessage, PrefersDark, PrefersReducedMotion, Presence, PresenceMessage, PresenceModel, Resize, Resized, RowMeasured, ScriptMessage, ScrollMessage, SelectionSet, SelectionSetMessage, SelectionSetModel, ShareData, ShareMessage, Socket, SocketHandle, SocketService, Source, SourceHandle, SourceService, Spring, SpringMessage, SpringModel, SseMessage, SseModel, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, Tween, TweenMessage, TweenModel, Viewport, ViewportScrolled, Virtual, VirtualLayout, VirtualMessage, VirtualModel, VirtualWindow, Visibility, VisibilityMessage, VisibilityModel, Watch, WatchService, WebSocketMessage, WebSocketModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, applyMask, breakpointFor, broadcastMessages, canRedo, canUndo, copyText, debounce, enterFullscreen, exitFullscreen, formatRelativeTime, fullscreenChanges, history, isAtEnd, isBrowser, isSelected, isServer, isVisible, keyboardEvents, loadScript, masonry, matchHotkey, mediaDevices, mediaStream, offset, offsetFor, pageCount, permissions, platformFromUA, pointerEvents, postBroadcast, range, scrollEvents, share, sse, stickyHeader, totalHeight, visibleRange, websocket, windowFor };
|
|
10
|
+
export { Acquired, AcquiredSource, ActiveElementMessage, Autofocus, Bounds, Breakpoints, BreakpointsMessage, BreakpointsModel, BroadcastChannelHandle, BroadcastMessage, ClipboardMessage, Coordinates, Focused, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, HistoryModel, Idle, IdleMessage, IdleModel, Input, InputMask, Intersection, IntersectionChanged, Interval, IntervalMessage, IntervalModel, KeyPress, KeyboardMessage, LiveStream, LiveStreamService, Locale, LocaleMessage, LocaleModel, MasonryPlacement, MeasureRow, Measured, MediaDevice, MediaDevicesHandle, MediaDevicesMessage, MediaDevicesModel, MediaQuery, MediaQueryMessage, MediaQueryModel, MediaStreamConstraints, MediaStreamHandle, MediaStreamMessage, MediaStreamModel, MediaTrackHandle, Mutated, Mutation, Online, OnlineMessage, OnlineModel, Pagination, PaginationMessage, PaginationModel, PermissionState, PermissionStatusHandle, PermissionsHandle, PermissionsMessage, PermissionsModel, Platform, PlatformHints, PointerMessage, PrefersDark, PrefersReducedMotion, Presence, PresenceMessage, PresenceModel, Resize, Resized, RowMeasured, ScriptMessage, ScrollMessage, SelectionSet, SelectionSetMessage, SelectionSetModel, ShareData, ShareMessage, Socket, SocketHandle, SocketService, Source, SourceHandle, SourceService, Spring, SpringMessage, SpringModel, SseMessage, SseModel, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, Tween, TweenMessage, TweenModel, Viewport, ViewportScrolled, Virtual, VirtualLayout, VirtualMessage, VirtualModel, VirtualWindow, Visibility, VisibilityMessage, VisibilityModel, Watch, WatchService, WebSocketMessage, WebSocketModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, applyMask, breakpointFor, broadcastMessages, canRedo, canUndo, copyText, debounce, distanceToEnd, enterFullscreen, exitFullscreen, formatRelativeTime, fullscreenChanges, history, isAtEnd, isBrowser, isSelected, isServer, isVisible, keyboardEvents, loadScript, masonry, matchHotkey, mediaDevices, mediaStream, offset, offsetFor, pageCount, permissions, platformFromUA, pointerEvents, postBroadcast, range, scrollEvents, share, sse, stickyHeader, totalHeight, visibleRange, websocket, windowFor };
|
package/dist/index.mjs
CHANGED
|
@@ -5,6 +5,6 @@ import { Breakpoints, BreakpointsMessage, BreakpointsModel, MediaQuery, MediaQue
|
|
|
5
5
|
import { Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible } from "./motion/index.mjs";
|
|
6
6
|
import { BroadcastMessage, Online, OnlineMessage, OnlineModel, Socket, Source, SseMessage, SseModel, WebSocketMessage, WebSocketModel, broadcastMessages, postBroadcast, sse, websocket } from "./net/index.mjs";
|
|
7
7
|
import { Bounds, Intersection, IntersectionChanged, Measured, Mutated, Mutation, Resize, Resized } from "./observers/index.mjs";
|
|
8
|
-
import { HistoryModel, Locale, LocaleMessage, LocaleModel, MeasureRow, Pagination, PaginationMessage, PaginationModel, RowMeasured, SelectionSet, SelectionSetMessage, SelectionSetModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, canRedo, canUndo, history, isAtEnd, isSelected, masonry, offset, offsetFor, pageCount, range, stickyHeader, totalHeight, visibleRange, windowFor } from "./state/index.mjs";
|
|
8
|
+
import { HistoryModel, Locale, LocaleMessage, LocaleModel, MeasureRow, Pagination, PaginationMessage, PaginationModel, RowMeasured, SelectionSet, SelectionSetMessage, SelectionSetModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, canRedo, canUndo, distanceToEnd, history, isAtEnd, isSelected, masonry, offset, offsetFor, pageCount, range, stickyHeader, totalHeight, visibleRange, windowFor } from "./state/index.mjs";
|
|
9
9
|
import { Interval, IntervalMessage, IntervalModel, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, debounce, formatRelativeTime } from "./time/index.mjs";
|
|
10
|
-
export { ActiveElementMessage, Autofocus, Bounds, Breakpoints, BreakpointsMessage, BreakpointsModel, BroadcastMessage, ClipboardMessage, Coordinates, Focused, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, HistoryModel, Idle, IdleMessage, IdleModel, Input, InputMask, Intersection, IntersectionChanged, Interval, IntervalMessage, IntervalModel, KeyboardMessage, LiveStream, Locale, LocaleMessage, LocaleModel, MeasureRow, Measured, MediaDevice, MediaDevicesMessage, MediaDevicesModel, MediaQuery, MediaQueryMessage, MediaQueryModel, MediaStreamMessage, MediaStreamModel, Mutated, Mutation, Online, OnlineMessage, OnlineModel, Pagination, PaginationMessage, PaginationModel, PermissionState, PermissionsMessage, PermissionsModel, Platform, PointerMessage, PrefersDark, PrefersReducedMotion, Presence, PresenceMessage, PresenceModel, Resize, Resized, RowMeasured, ScriptMessage, ScrollMessage, SelectionSet, SelectionSetMessage, SelectionSetModel, ShareData, ShareMessage, Socket, Source, Spring, SpringMessage, SpringModel, SseMessage, SseModel, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, Tween, TweenMessage, TweenModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, Visibility, VisibilityMessage, VisibilityModel, Watch, WebSocketMessage, WebSocketModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, applyMask, breakpointFor, broadcastMessages, canRedo, canUndo, copyText, debounce, enterFullscreen, exitFullscreen, formatRelativeTime, fullscreenChanges, history, isAtEnd, isBrowser, isSelected, isServer, isVisible, keyboardEvents, loadScript, masonry, matchHotkey, mediaDevices, mediaStream, offset, offsetFor, pageCount, permissions, platformFromUA, pointerEvents, postBroadcast, range, scrollEvents, share, sse, stickyHeader, totalHeight, visibleRange, websocket, windowFor };
|
|
10
|
+
export { ActiveElementMessage, Autofocus, Bounds, Breakpoints, BreakpointsMessage, BreakpointsModel, BroadcastMessage, ClipboardMessage, Coordinates, Focused, FullscreenMessage, Geolocation, GeolocationMessage, GeolocationModel, HistoryModel, Idle, IdleMessage, IdleModel, Input, InputMask, Intersection, IntersectionChanged, Interval, IntervalMessage, IntervalModel, KeyboardMessage, LiveStream, Locale, LocaleMessage, LocaleModel, MeasureRow, Measured, MediaDevice, MediaDevicesMessage, MediaDevicesModel, MediaQuery, MediaQueryMessage, MediaQueryModel, MediaStreamMessage, MediaStreamModel, Mutated, Mutation, Online, OnlineMessage, OnlineModel, Pagination, PaginationMessage, PaginationModel, PermissionState, PermissionsMessage, PermissionsModel, Platform, PointerMessage, PrefersDark, PrefersReducedMotion, Presence, PresenceMessage, PresenceModel, Resize, Resized, RowMeasured, ScriptMessage, ScrollMessage, SelectionSet, SelectionSetMessage, SelectionSetModel, ShareData, ShareMessage, Socket, Source, Spring, SpringMessage, SpringModel, SseMessage, SseModel, Throttle, ThrottleMessage, ThrottleModel, Throttled, Timer, TimerMessage, TimerModel, Tween, TweenMessage, TweenModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, Visibility, VisibilityMessage, VisibilityModel, Watch, WebSocketMessage, WebSocketModel, WindowSize, WindowSizeMessage, WindowSizeModel, activeElementEvents, applyMask, breakpointFor, broadcastMessages, canRedo, canUndo, copyText, debounce, distanceToEnd, enterFullscreen, exitFullscreen, formatRelativeTime, fullscreenChanges, history, isAtEnd, isBrowser, isSelected, isServer, isVisible, keyboardEvents, loadScript, masonry, matchHotkey, mediaDevices, mediaStream, offset, offsetFor, pageCount, permissions, platformFromUA, pointerEvents, postBroadcast, range, scrollEvents, share, sse, stickyHeader, totalHeight, visibleRange, websocket, windowFor };
|
package/dist/media/index.d.mts
CHANGED
|
@@ -18,8 +18,8 @@ export declare const Breakpoints: Bundle.Bundle<"Breakpoints", {
|
|
|
18
18
|
readonly [x: string]: number;
|
|
19
19
|
};
|
|
20
20
|
}, {
|
|
21
|
-
width: number;
|
|
22
|
-
breakpoint: string | null;
|
|
21
|
+
readonly width: number;
|
|
22
|
+
readonly breakpoint: string | null;
|
|
23
23
|
}, {
|
|
24
24
|
readonly _tag: "Changed";
|
|
25
25
|
readonly width: number;
|
|
@@ -39,21 +39,21 @@ export type MediaQueryMessage = typeof MediaQueryMessage.Type;
|
|
|
39
39
|
export declare const MediaQuery: Bundle.Bundle<"MediaQuery", {
|
|
40
40
|
readonly query: string;
|
|
41
41
|
}, {
|
|
42
|
-
matches: boolean;
|
|
42
|
+
readonly matches: boolean;
|
|
43
43
|
}, {
|
|
44
44
|
readonly _tag: "Changed";
|
|
45
45
|
readonly matches: boolean;
|
|
46
46
|
}, never, never, never, void, {}, {}>;
|
|
47
47
|
/** `(prefers-color-scheme: dark)`, bound so placements give no args. */
|
|
48
48
|
export declare const PrefersDark: Bundle.Bundle<"MediaQuery", void, {
|
|
49
|
-
matches: boolean;
|
|
49
|
+
readonly matches: boolean;
|
|
50
50
|
}, {
|
|
51
51
|
readonly _tag: "Changed";
|
|
52
52
|
readonly matches: boolean;
|
|
53
53
|
}, never, never, never, void, {}, {}>;
|
|
54
54
|
/** `(prefers-reduced-motion: reduce)`, bound so placements give no args. */
|
|
55
55
|
export declare const PrefersReducedMotion: Bundle.Bundle<"MediaQuery", void, {
|
|
56
|
-
matches: boolean;
|
|
56
|
+
readonly matches: boolean;
|
|
57
57
|
}, {
|
|
58
58
|
readonly _tag: "Changed";
|
|
59
59
|
readonly matches: boolean;
|
|
@@ -65,12 +65,21 @@ export declare const PrefersReducedMotion: Bundle.Bundle<"MediaQuery", void, {
|
|
|
65
65
|
* string (passed in, so tests pin it without stubbing globals); order
|
|
66
66
|
* matters — Android contains "Linux", iPhones mention "Mac" — so mobile
|
|
67
67
|
* checks come first. `isBrowser`/`isServer` split SSR from client for init
|
|
68
|
-
* defaults.
|
|
69
|
-
* touch detection stays the application's job.
|
|
68
|
+
* defaults.
|
|
70
69
|
*/
|
|
71
70
|
export declare const Platform: readonly ["mac", "windows", "linux", "android", "ios", "unknown"];
|
|
72
71
|
export type Platform = (typeof Platform)[number];
|
|
73
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Client Hints (`navigator.userAgentData`) plus touch points
|
|
74
|
+
* (`navigator.maxTouchPoints`), passed in so detection stays pure and
|
|
75
|
+
* pinned. Only `platform` and `touchPoints` can change the answer — the
|
|
76
|
+
* `mobile` flag never disambiguates on its own, so it is not accepted.
|
|
77
|
+
*/
|
|
78
|
+
export interface PlatformHints {
|
|
79
|
+
readonly platform?: string | undefined;
|
|
80
|
+
readonly touchPoints?: number | undefined;
|
|
81
|
+
}
|
|
82
|
+
export declare const platformFromUA: (userAgent: string, hints?: PlatformHints) => Platform;
|
|
74
83
|
/** Whether a window exists: the client side of an SSR split. */
|
|
75
84
|
export declare const isBrowser: () => boolean;
|
|
76
85
|
/** Whether no window exists: the server side of an SSR split. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/media/breakpoints.ts","../../src/media/mediaquery.ts","../../src/media/platform.ts"],"mappings":";;;qBAWa,kBAAgB,OAAA;;;;YAIjB,0BAA0B,iBAAiB;qBAE1C,8CAAkB;;oBAAlB,OAAA;;;YACD,4BAA4B,mBAAmB;qBAE9C,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/media/breakpoints.ts","../../src/media/mediaquery.ts","../../src/media/platform.ts"],"mappings":";;;qBAWa,kBAAgB,OAAA;;;;YAIjB,0BAA0B,iBAAiB;qBAE1C,8CAAkB;;oBAAlB,OAAA;;;YACD,4BAA4B,mBAAmB;qBAE9C,gBACX,eACA,aAAa,SAAS;qBAyCX,aAAW,OAAA;;;;;;;;;;;;;qBCrDX,iBAAe,OAAA;;;YAChB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;sBAAjB,OAAA;;;YACD,2BAA2B,kBAAkB;qBA4B5C,YAAU,OAAA;;;;;;;;;qBAaV,aAAW,OAAA;;;;;;;qBAGX,sBAAoB,OAAA;;;;;;;;;;;;;;;qBCnDpB;YACD,mBAAmB;;;;;;;iBAQd;WACN;WACA;;qBAoBE,iBAAkB,mBAAmB,QAAO,kBAAqB;;qBAkBjE;;qBAGA"}
|
package/dist/media/index.mjs
CHANGED
|
@@ -97,8 +97,7 @@ const PrefersReducedMotion = MediaQuery.with({ query: "(prefers-reduced-motion:
|
|
|
97
97
|
* string (passed in, so tests pin it without stubbing globals); order
|
|
98
98
|
* matters — Android contains "Linux", iPhones mention "Mac" — so mobile
|
|
99
99
|
* checks come first. `isBrowser`/`isServer` split SSR from client for init
|
|
100
|
-
* defaults.
|
|
101
|
-
* touch detection stays the application's job.
|
|
100
|
+
* defaults.
|
|
102
101
|
*/
|
|
103
102
|
const Platform = [
|
|
104
103
|
"mac",
|
|
@@ -108,14 +107,27 @@ const Platform = [
|
|
|
108
107
|
"ios",
|
|
109
108
|
"unknown"
|
|
110
109
|
];
|
|
111
|
-
const
|
|
110
|
+
const hintedPlatform = (platform) => {
|
|
111
|
+
switch (platform.toLowerCase()) {
|
|
112
|
+
case "macos": return "mac";
|
|
113
|
+
case "windows": return "windows";
|
|
114
|
+
case "linux": return "linux";
|
|
115
|
+
case "android": return "android";
|
|
116
|
+
case "ios": return "ios";
|
|
117
|
+
default: return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const platformFromUA = (userAgent, hints = {}) => {
|
|
121
|
+
const hinted = hints.platform === void 0 ? null : hintedPlatform(hints.platform);
|
|
112
122
|
const ua = userAgent.toLowerCase();
|
|
113
|
-
|
|
114
|
-
if (ua.includes("
|
|
115
|
-
if (ua.includes("
|
|
116
|
-
if (ua.includes("
|
|
117
|
-
if (ua.includes("
|
|
118
|
-
|
|
123
|
+
let sniffed = "unknown";
|
|
124
|
+
if (ua.includes("android")) sniffed = "android";
|
|
125
|
+
else if (ua.includes("iphone") || ua.includes("ipad") || ua.includes("ipod")) sniffed = "ios";
|
|
126
|
+
else if (ua.includes("windows") || ua.includes("win32") || ua.includes("win64")) sniffed = "windows";
|
|
127
|
+
else if (ua.includes("macintosh") || ua.includes("mac os")) sniffed = "mac";
|
|
128
|
+
else if (ua.includes("linux")) sniffed = "linux";
|
|
129
|
+
const candidate = hinted ?? sniffed;
|
|
130
|
+
return candidate === "mac" && (hints.touchPoints ?? 0) > 1 ? "ios" : candidate;
|
|
119
131
|
};
|
|
120
132
|
/** Whether a window exists: the client side of an SSR split. */
|
|
121
133
|
const isBrowser = () => typeof window !== "undefined";
|
package/dist/media/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/media/breakpoints.ts","../../src/media/mediaquery.ts","../../src/media/platform.ts"],"sourcesContent":["/**\n * Named breakpoints over the viewport width: one `resize` listener, derived\n * names in `update`. Thresholds are mobile-first min-widths: the breakpoint\n * is the largest name whose threshold is <= width. Ties break\n * alphabetically so duplicates stay deterministic.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const BreakpointsModel = Schema.Struct({\n width: Schema.Number,\n breakpoint: Schema.NullOr(Schema.String),\n})\nexport type BreakpointsModel = typeof BreakpointsModel.Type\n\nexport const BreakpointsMessage = defineMessageUnion({ Changed: { width: Schema.Number } })\nexport type BreakpointsMessage = typeof BreakpointsMessage.Type\n\nexport const breakpointFor = (\n width: number,\n breakpoints: Readonly<Record<string, number>>,\n): string | null => {\n let best: string | null = null\n let bestThreshold = -Infinity\n for (const name of Object.keys(breakpoints)) {\n const threshold = breakpoints[name]!\n if (\n threshold <= width &&\n (threshold > bestThreshold || (threshold === bestThreshold && (best === null || name > best)))\n ) {\n best = name\n bestThreshold = threshold\n }\n }\n return best\n}\n\nconst widthStream = (): Stream.Stream<BreakpointsMessage> => {\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {\n return Stream.empty\n }\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<BreakpointsMessage>()\n const onResize = () => {\n Effect.runFork(Queue.offer(queue, BreakpointsMessage.Changed({ width: window.innerWidth })))\n }\n window.addEventListener('resize', onResize)\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n window.removeEventListener('resize', onResize)\n }),\n )\n return Stream.concat(\n Stream.make(BreakpointsMessage.Changed({ width: window.innerWidth })),\n Stream.fromQueue(queue),\n )\n }),\n )\n}\n\nexport const Breakpoints = Bundle.make('Breakpoints', {\n Model: BreakpointsModel,\n Message: BreakpointsMessage,\n args: Schema.Struct({\n breakpoints: Schema.Record(Schema.String, Schema.Number.pipe(Schema.check(Schema.isFinite()))),\n }),\n init: args => ({\n model: { width: 0, breakpoint: breakpointFor(0, args.breakpoints) },\n }),\n update: (model, message, args) =>\n BreakpointsMessage.match(message, {\n Changed: ({ width }) => ({\n model: { width, breakpoint: breakpointFor(width, args.breakpoints) },\n }),\n }),\n subscriptions: (): Subscription.Subscriptions<BreakpointsModel, BreakpointsMessage> =>\n Subscription.make<BreakpointsModel, BreakpointsMessage>()(() => ({\n changes: Subscription.persistent(widthStream()),\n })),\n})\n","/**\n * A media query as a bundle: `matches` in the Model, kept current by a\n * `matchMedia` change stream. State stays in the parent Model, so it replays\n * and time-travels like any other state; the browser only reports facts.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const MediaQueryModel = Schema.Struct({ matches: Schema.Boolean })\nexport type MediaQueryModel = typeof MediaQueryModel.Type\n\nexport const MediaQueryMessage = defineMessageUnion({ Changed: { matches: Schema.Boolean } })\nexport type MediaQueryMessage = typeof MediaQueryMessage.Type\n\nconst matchMediaStream = (query: string): Stream.Stream<MediaQueryMessage> => {\n // No window (SSR) or no matchMedia (old browser, minimal DOM): nothing to\n // listen to, so the slice keeps its initial value instead of throwing.\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return Stream.empty\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<MediaQueryMessage>()\n const list = window.matchMedia(query)\n const onChange = (event: MediaQueryListEvent) => {\n Effect.runFork(Queue.offer(queue, MediaQueryMessage.Changed({ matches: event.matches })))\n }\n list.addEventListener('change', onChange)\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n list.removeEventListener('change', onChange)\n }),\n )\n // The current value first, so the Model is right before the first change.\n return Stream.concat(\n Stream.make(MediaQueryMessage.Changed({ matches: list.matches })),\n Stream.fromQueue(queue),\n )\n }),\n )\n}\n\nexport const MediaQuery = Bundle.make('MediaQuery', {\n Model: MediaQueryModel,\n Message: MediaQueryMessage,\n args: Schema.Struct({ query: Schema.String }),\n init: () => ({ model: { matches: false } }),\n update: (model, message) => ({ model: { matches: message.matches } }),\n subscriptions: ({ query }): Subscription.Subscriptions<MediaQueryModel, MediaQueryMessage> =>\n Subscription.make<MediaQueryModel, MediaQueryMessage>()(() => ({\n changes: Subscription.persistent(matchMediaStream(query)),\n })),\n})\n\n/** `(prefers-color-scheme: dark)`, bound so placements give no args. */\nexport const PrefersDark = MediaQuery.with({ query: '(prefers-color-scheme: dark)' })\n\n/** `(prefers-reduced-motion: reduce)`, bound so placements give no args. */\nexport const PrefersReducedMotion = MediaQuery.with({ query: '(prefers-reduced-motion: reduce)' })\n","/**\n * Platform detection as pure functions. `platformFromUA` reads a user-agent\n * string (passed in, so tests pin it without stubbing globals); order\n * matters — Android contains \"Linux\", iPhones mention \"Mac\" — so mobile\n * checks come first. `isBrowser`/`isServer` split SSR from client for init\n * defaults. Desktop-mode iPads report `mac` — their UA hides the tablet;\n * touch detection stays the application's job.\n */\nexport const Platform = ['mac', 'windows', 'linux', 'android', 'ios', 'unknown'] as const\nexport type Platform = (typeof Platform)[number]\n\nexport const platformFromUA = (userAgent: string): Platform => {\n const ua = userAgent.toLowerCase()\n if (ua.includes('android')) return 'android'\n if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) return 'ios'\n if (ua.includes('windows') || ua.includes('win32') || ua.includes('win64')) return 'windows'\n if (ua.includes('macintosh') || ua.includes('mac os')) return 'mac'\n if (ua.includes('linux')) return 'linux'\n return 'unknown'\n}\n\n/** Whether a window exists: the client side of an SSR split. */\nexport const isBrowser = (): boolean => typeof window !== 'undefined'\n\n/** Whether no window exists: the server side of an SSR split. */\nexport const isServer = (): boolean => !isBrowser()\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/media/breakpoints.ts","../../src/media/mediaquery.ts","../../src/media/platform.ts"],"sourcesContent":["/**\n * Named breakpoints over the viewport width: one `resize` listener, derived\n * names in `update`. Thresholds are mobile-first min-widths: the breakpoint\n * is the largest name whose threshold is <= width. Ties break\n * alphabetically so duplicates stay deterministic.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const BreakpointsModel = Schema.Struct({\n width: Schema.Number,\n breakpoint: Schema.NullOr(Schema.String),\n})\nexport type BreakpointsModel = typeof BreakpointsModel.Type\n\nexport const BreakpointsMessage = defineMessageUnion({ Changed: { width: Schema.Number } })\nexport type BreakpointsMessage = typeof BreakpointsMessage.Type\n\nexport const breakpointFor = (\n width: number,\n breakpoints: Readonly<Record<string, number>>,\n): string | null => {\n let best: string | null = null\n let bestThreshold = -Infinity\n for (const name of Object.keys(breakpoints)) {\n const threshold = breakpoints[name]!\n if (\n threshold <= width &&\n (threshold > bestThreshold || (threshold === bestThreshold && (best === null || name > best)))\n ) {\n best = name\n bestThreshold = threshold\n }\n }\n return best\n}\n\nconst widthStream = (): Stream.Stream<BreakpointsMessage> => {\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {\n return Stream.empty\n }\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<BreakpointsMessage>()\n const onResize = () => {\n Effect.runFork(Queue.offer(queue, BreakpointsMessage.Changed({ width: window.innerWidth })))\n }\n window.addEventListener('resize', onResize)\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n window.removeEventListener('resize', onResize)\n }),\n )\n return Stream.concat(\n Stream.make(BreakpointsMessage.Changed({ width: window.innerWidth })),\n Stream.fromQueue(queue),\n )\n }),\n )\n}\n\nexport const Breakpoints = Bundle.make('Breakpoints', {\n Model: BreakpointsModel,\n Message: BreakpointsMessage,\n args: Schema.Struct({\n breakpoints: Schema.Record(Schema.String, Schema.Number.pipe(Schema.check(Schema.isFinite()))),\n }),\n init: args => ({\n model: { width: 0, breakpoint: breakpointFor(0, args.breakpoints) },\n }),\n update: (model, message, args) =>\n BreakpointsMessage.match(message, {\n Changed: ({ width }) => ({\n model: { width, breakpoint: breakpointFor(width, args.breakpoints) },\n }),\n }),\n subscriptions: (): Subscription.Subscriptions<BreakpointsModel, BreakpointsMessage> =>\n Subscription.make<BreakpointsModel, BreakpointsMessage>()(() => ({\n changes: Subscription.persistent(widthStream()),\n })),\n})\n","/**\n * A media query as a bundle: `matches` in the Model, kept current by a\n * `matchMedia` change stream. State stays in the parent Model, so it replays\n * and time-travels like any other state; the browser only reports facts.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const MediaQueryModel = Schema.Struct({ matches: Schema.Boolean })\nexport type MediaQueryModel = typeof MediaQueryModel.Type\n\nexport const MediaQueryMessage = defineMessageUnion({ Changed: { matches: Schema.Boolean } })\nexport type MediaQueryMessage = typeof MediaQueryMessage.Type\n\nconst matchMediaStream = (query: string): Stream.Stream<MediaQueryMessage> => {\n // No window (SSR) or no matchMedia (old browser, minimal DOM): nothing to\n // listen to, so the slice keeps its initial value instead of throwing.\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return Stream.empty\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<MediaQueryMessage>()\n const list = window.matchMedia(query)\n const onChange = (event: MediaQueryListEvent) => {\n Effect.runFork(Queue.offer(queue, MediaQueryMessage.Changed({ matches: event.matches })))\n }\n list.addEventListener('change', onChange)\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n list.removeEventListener('change', onChange)\n }),\n )\n // The current value first, so the Model is right before the first change.\n return Stream.concat(\n Stream.make(MediaQueryMessage.Changed({ matches: list.matches })),\n Stream.fromQueue(queue),\n )\n }),\n )\n}\n\nexport const MediaQuery = Bundle.make('MediaQuery', {\n Model: MediaQueryModel,\n Message: MediaQueryMessage,\n args: Schema.Struct({ query: Schema.String }),\n init: () => ({ model: { matches: false } }),\n update: (model, message) => ({ model: { matches: message.matches } }),\n subscriptions: ({ query }): Subscription.Subscriptions<MediaQueryModel, MediaQueryMessage> =>\n Subscription.make<MediaQueryModel, MediaQueryMessage>()(() => ({\n changes: Subscription.persistent(matchMediaStream(query)),\n })),\n})\n\n/** `(prefers-color-scheme: dark)`, bound so placements give no args. */\nexport const PrefersDark = MediaQuery.with({ query: '(prefers-color-scheme: dark)' })\n\n/** `(prefers-reduced-motion: reduce)`, bound so placements give no args. */\nexport const PrefersReducedMotion = MediaQuery.with({ query: '(prefers-reduced-motion: reduce)' })\n","/**\n * Platform detection as pure functions. `platformFromUA` reads a user-agent\n * string (passed in, so tests pin it without stubbing globals); order\n * matters — Android contains \"Linux\", iPhones mention \"Mac\" — so mobile\n * checks come first. `isBrowser`/`isServer` split SSR from client for init\n * defaults.\n */\nexport const Platform = ['mac', 'windows', 'linux', 'android', 'ios', 'unknown'] as const\nexport type Platform = (typeof Platform)[number]\n\n/**\n * Client Hints (`navigator.userAgentData`) plus touch points\n * (`navigator.maxTouchPoints`), passed in so detection stays pure and\n * pinned. Only `platform` and `touchPoints` can change the answer — the\n * `mobile` flag never disambiguates on its own, so it is not accepted.\n */\nexport interface PlatformHints {\n readonly platform?: string | undefined\n readonly touchPoints?: number | undefined\n}\n\nconst hintedPlatform = (platform: string): Platform | null => {\n switch (platform.toLowerCase()) {\n case 'macos':\n return 'mac'\n case 'windows':\n return 'windows'\n case 'linux':\n return 'linux'\n case 'android':\n return 'android'\n case 'ios':\n return 'ios'\n default:\n return null\n }\n}\n\nexport const platformFromUA = (userAgent: string, hints: PlatformHints = {}): Platform => {\n const hinted = hints.platform === undefined ? null : hintedPlatform(hints.platform)\n const ua = userAgent.toLowerCase()\n let sniffed: Platform = 'unknown'\n if (ua.includes('android')) sniffed = 'android'\n else if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) sniffed = 'ios'\n else if (ua.includes('windows') || ua.includes('win32') || ua.includes('win64')) {\n sniffed = 'windows'\n } else if (ua.includes('macintosh') || ua.includes('mac os')) sniffed = 'mac'\n else if (ua.includes('linux')) sniffed = 'linux'\n const candidate = hinted ?? sniffed\n // Desktop-mode iPads report \"macOS\" with a tablet touchscreen: only a\n // multi-touch Mac UA is really an iPad. Touchscreen Windows and Linux\n // machines keep their platform.\n return candidate === 'mac' && (hints.touchPoints ?? 0) > 1 ? 'ios' : candidate\n}\n\n/** Whether a window exists: the client side of an SSR split. */\nexport const isBrowser = (): boolean => typeof window !== 'undefined'\n\n/** Whether no window exists: the server side of an SSR split. */\nexport const isServer = (): boolean => !isBrowser()\n"],"mappings":"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"}
|
package/dist/motion/index.d.mts
CHANGED
|
@@ -56,9 +56,9 @@ export declare const Spring: Bundle.Bundle<"Spring", {
|
|
|
56
56
|
readonly stiffness: number;
|
|
57
57
|
readonly damping: number;
|
|
58
58
|
}, {
|
|
59
|
-
value: number;
|
|
60
|
-
velocity: number;
|
|
61
|
-
running: boolean;
|
|
59
|
+
readonly value: number;
|
|
60
|
+
readonly velocity: number;
|
|
61
|
+
readonly running: boolean;
|
|
62
62
|
}, {
|
|
63
63
|
readonly _tag: "Started";
|
|
64
64
|
} | {
|
|
@@ -95,8 +95,8 @@ export declare const Tween: Bundle.Bundle<"Tween", {
|
|
|
95
95
|
readonly to: number;
|
|
96
96
|
readonly ms: number;
|
|
97
97
|
}, {
|
|
98
|
-
value: number;
|
|
99
|
-
running: boolean;
|
|
98
|
+
readonly value: number;
|
|
99
|
+
readonly running: boolean;
|
|
100
100
|
}, {
|
|
101
101
|
readonly _tag: "Started";
|
|
102
102
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/motion/presence.ts","../../src/motion/spring.ts","../../src/motion/tween.ts"],"mappings":";;;qBAWa,eAAa,OAAA;;;;YAId,uBAAuB,cAAc;qBAEpC,2CAAe;;;;yBAAf,OAAA;;;YAKD,yBAAyB,gBAAgB;;qBAGxC,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/motion/presence.ts","../../src/motion/spring.ts","../../src/motion/tween.ts"],"mappings":";;;qBAWa,eAAa,OAAA;;;;YAId,uBAAuB,cAAc;qBAEpC,2CAAe;;;;yBAAf,OAAA;;;YAKD,yBAAyB,gBAAgB;;qBAGxC,YAAa,OAAO;qBAEpB,UAAQ,OAAA;WAII;;;;;;;;;;;;;;qBCnBZ,aAAW,OAAA;;;;;YAKZ,qBAAqB,YAAY;qBAEhC,yCAAa;;;;;;;;;;;;YAOd,uBAAuB,cAAc;qBAapC,QAAM,OAAA;;;;;;;;;;;;;;;;;;;;;;;qBC5BN,YAAU,OAAA;;;;YACX,oBAAoB,WAAW;qBAE9B,wCAAY;;;;;;;;;;;YAOb,sBAAsB,aAAa;qBAElC,OAAK,OAAA"}
|
package/dist/net/index.d.mts
CHANGED
|
@@ -29,7 +29,9 @@ type ChannelFactory = (name: string) => BroadcastChannelHandle;
|
|
|
29
29
|
* BroadcastChannel API the stream is empty instead of throwing; closing the
|
|
30
30
|
* stream closes the channel.
|
|
31
31
|
*/
|
|
32
|
-
export declare const broadcastMessages: (name: string,
|
|
32
|
+
export declare const broadcastMessages: (name: string,
|
|
33
|
+
/** Read lazily so tests can substitute a double, like the SSE bundle. */
|
|
34
|
+
create?: ChannelFactory | undefined) => Stream.Stream<BroadcastMessage>;
|
|
33
35
|
/**
|
|
34
36
|
* Posts one message to `name`. Opens a channel, posts, and closes it: the
|
|
35
37
|
* send is one-shot, so it is a Command, not a resource. Without a
|
|
@@ -50,7 +52,7 @@ export declare const OnlineMessage: import("foldkit/message").MessageUnion<{
|
|
|
50
52
|
}>;
|
|
51
53
|
export type OnlineMessage = typeof OnlineMessage.Type;
|
|
52
54
|
export declare const Online: Bundle.Bundle<"Online", void, {
|
|
53
|
-
online: boolean;
|
|
55
|
+
readonly online: boolean;
|
|
54
56
|
}, {
|
|
55
57
|
readonly _tag: "Changed";
|
|
56
58
|
readonly online: boolean;
|
|
@@ -174,12 +176,12 @@ export declare const websocket: <const Name extends string>(config: {
|
|
|
174
176
|
readonly status: "closed" | "connecting" | "open";
|
|
175
177
|
readonly lastError: string | null;
|
|
176
178
|
}, {
|
|
177
|
-
readonly _tag: "Received";
|
|
178
|
-
readonly data: string;
|
|
179
|
-
} | {
|
|
180
179
|
readonly _tag: "Connecting";
|
|
181
180
|
} | {
|
|
182
181
|
readonly _tag: "Opened";
|
|
182
|
+
} | {
|
|
183
|
+
readonly _tag: "Received";
|
|
184
|
+
readonly data: string;
|
|
183
185
|
} | {
|
|
184
186
|
readonly _tag: "Closed";
|
|
185
187
|
} | {
|
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,
|
|
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;;;;;;;;;iBClBF;WACN;EACT;EACA,UAAU;EACV,aAAa;EACb,WAAW;;qBAGA,UAAQ,OAAA;;;;;YAKT,kBAAkB,SAAS;qBAE1B,sCAAU;;;;;;;;;;;;YAQX,oBAAoB,WAAW;;iBAG1B;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;;;;;iBC1CO;WACN;EACT,KAAK;EACL;EACA,UAAU;EACV,aAAa;EACb,WAAW;EACX,WAAW;;qBAGA,gBAAc,OAAA;;;;;YAKf,wBAAwB,eAAe;qBAEtC,4CAAgB;;;;;;;;;;;;;;;;;YAWjB,0BAA0B,iBAAiB;;iBAGtC;WACN,QAAQ;WACR,QAAQ,MAAM,MAAM;;;qBAIlB,QAAM,gBAAA,gBAAA,UAAA,gBAAA;;YAGP,gBAAgB,gBAAgB,iBAAiB;qBAgChD,kBAAmB,qBAAqB;WAC1C,MAAM;;WAEN,iBAAiB,gBAAgB;MAC3C,OAAA,OAAA;WAOmB;;;;;;;;;;;;;;;;;;;;;;;EAxCV,QAAA,gBAAgB,MACtB,gBACA,kBACA,OAAO,gBACP,UACA,qBACM;;EAKF,OACJ,OAAO,gBACP,iBACG,OAAO,qBAAqB,gBAAgB,yBAAyB"}
|
package/dist/state/index.d.mts
CHANGED
|
@@ -60,7 +60,7 @@ export type LocaleMessage = typeof LocaleMessage.Type;
|
|
|
60
60
|
export declare const Locale: Bundle.Bundle<"Locale", {
|
|
61
61
|
readonly default: string;
|
|
62
62
|
}, {
|
|
63
|
-
locale: string;
|
|
63
|
+
readonly locale: string;
|
|
64
64
|
}, {
|
|
65
65
|
readonly _tag: "SetLocale";
|
|
66
66
|
readonly locale: string;
|
|
@@ -95,9 +95,9 @@ export declare const Pagination: Bundle.Bundle<"Pagination", {
|
|
|
95
95
|
readonly perPage: number;
|
|
96
96
|
readonly total?: number | null | undefined;
|
|
97
97
|
}, {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
readonly page: number;
|
|
99
|
+
readonly perPage: number;
|
|
100
|
+
readonly total: number | null;
|
|
101
101
|
}, {
|
|
102
102
|
readonly _tag: "GoToPage";
|
|
103
103
|
readonly page: number;
|
|
@@ -231,6 +231,12 @@ export type VirtualMessage = typeof VirtualMessage.Type;
|
|
|
231
231
|
export declare const windowFor: (model: VirtualModel, keys: ReadonlyArray<string>, viewportHeight: number) => VirtualWindow & {
|
|
232
232
|
readonly totalHeight: number;
|
|
233
233
|
};
|
|
234
|
+
/**
|
|
235
|
+
* Pixels from the viewport's bottom edge to the content's end: negative
|
|
236
|
+
* past it, zero exactly there. Prefetch thresholds want this number ("load
|
|
237
|
+
* when within 500px"), not just the boolean below.
|
|
238
|
+
*/
|
|
239
|
+
export declare const distanceToEnd: (model: VirtualModel, keys: ReadonlyArray<string>, viewportHeight: number) => number;
|
|
234
240
|
/**
|
|
235
241
|
* Whether the viewport rests at (or past) the end, within `threshold`
|
|
236
242
|
* pixels: the infinite-scroll check. An empty list counts as ended — there
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/state/history.ts","../../src/state/locale.ts","../../src/state/pagination.ts","../../src/state/range.ts","../../src/state/selection.ts","../../src/state/virtual.ts"],"mappings":";;;;qBASa,eAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/state/history.ts","../../src/state/locale.ts","../../src/state/pagination.ts","../../src/state/range.ts","../../src/state/selection.ts","../../src/state/virtual.ts"],"mappings":";;;;qBASa,eAAgB,OAAO,OAAO,OAAO,MAAM,oBAAe,OAAA;;;;;YAM3D,aAAa;WACd,MAAM,cAAc;WACpB,SAAS;WACT,QAAQ,cAAc;;qBAGpB,gBAAiB,qBAAqB,OAAO;WAC/C,MAAM;WACN,OAAO,OAAO,MAAM;WACpB;MACV,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;qBA+DY,UAAW,OAAO,OAAO,aAAa;;qBAGtC,UAAW,OAAO,OAAO,aAAa;;;qBCjFtC,aAAW,OAAA;;;YACZ,qBAAqB,YAAY;qBAEhC,yCAAa;;qBAAb,OAAA;;;YACD,uBAAuB,cAAc;qBAEpC,QAAM,OAAA;;;;;;;;;;qBCPN,iBAAe,OAAA;;;;;YAKhB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;;;;;;;;;;;;YAOlB,2BAA2B,kBAAkB;;qBAU5C,YAAa,OAAO;;qBAIpB,SAAU,OAAO;qBAEjB,YAAU,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBChCV,QAAS,eAAe,aAAa,kBAAW;;;qBCEhD,mBAAiB,OAAA;;;YAClB,2BAA2B,kBAAkB;qBAE5C,+CAAmB;;;;;;;;;;;;;;;YAOpB,6BAA6B,oBAAoB;;qBAMhD,aAAc,OAAO,mBAAmB;qBAGxC,cAAY,OAAA;;;;;;;;;;;;;;;;;;;iBCRR;;WAEN;;WAEA;;;iBAIM;WACN;WACA;WACA;WACA;;;qBAkBE,cACX,MAAM,uBACN,SAAS,SAAS,yBAClB,QAAQ;;qBAWG,YACX,eACA,MAAM,uBACN,SAAS,SAAS,yBAClB,QAAQ;;;;;;;;qBAiBG,eACX,MAAM,uBACN,SAAS,SAAS,yBAClB,QAAQ,eACR,mBACA,wBACA,qBACC;qBA4BU,cAAY,OAAA;;;;;;;;;;;YAWb,sBAAsB,aAAa;qBAElC,0CAAc;;;;;;;;;;;;;;;;;;;YAUf,wBAAwB,eAAe;;;;;;;qBAetC,YACX,OAAO,cACP,MAAM,uBACN,2BACC;WAA2B;;;;;;;qBAkBjB,gBACX,OAAO,cACP,MAAM,uBACN;;;;;;;qBAYW,UACX,OAAO,cACP,MAAM,uBACN,wBACA;qBAYW,SAAO,OAAA;WAKU;WACP;WACL;WACS;WACF;WACD;WACQ;WACF,iBAAA,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAmG1B,kBAAgB,OAAA;;;YACjB,0BAA0B,iBAAiB;;;;;;qBAO1C,UAAQ,MAAA;;;;qBAeR,aAAW,OAAA;;;;YAIZ,qBAAqB,YAAY;;;;;;qBAShC,YAAU,MAAA;;;;;;;;;;;;;;qBAgCV,eACX,UAAU;WAAyB;WAAsB;IACzD;iBAae;WACN;WACA;WACA;WACA;WACA;WACA;;;;;;;;;qBAUE,UACX,MAAM,uBACN,SAAS,SAAS,yBAClB;WACW;WACA;WACA;WACA;;WAEC,YAAY,cAAc;WAA4B"}
|
package/dist/state/index.mjs
CHANGED
|
@@ -317,15 +317,22 @@ const windowFor = (model, keys, viewportHeight) => {
|
|
|
317
317
|
};
|
|
318
318
|
};
|
|
319
319
|
/**
|
|
320
|
+
* Pixels from the viewport's bottom edge to the content's end: negative
|
|
321
|
+
* past it, zero exactly there. Prefetch thresholds want this number ("load
|
|
322
|
+
* when within 500px"), not just the boolean below.
|
|
323
|
+
*/
|
|
324
|
+
const distanceToEnd = (model, keys, viewportHeight) => {
|
|
325
|
+
return totalHeight(keys, model.heights, layoutOf(model)) - (model.scrollTop + viewportHeight);
|
|
326
|
+
};
|
|
327
|
+
/**
|
|
320
328
|
* Whether the viewport rests at (or past) the end, within `threshold`
|
|
321
329
|
* pixels: the infinite-scroll check. An empty list counts as ended — there
|
|
322
330
|
* is nothing to scroll, so more should load. Thresholds below zero clamp
|
|
323
331
|
* to zero.
|
|
324
332
|
*/
|
|
325
333
|
const isAtEnd = (model, keys, viewportHeight, threshold = 0) => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return model.scrollTop + viewportHeight >= total - Math.max(0, threshold);
|
|
334
|
+
if (totalHeight(keys, model.heights, layoutOf(model)) <= 0) return true;
|
|
335
|
+
return distanceToEnd(model, keys, viewportHeight) <= Math.max(0, threshold);
|
|
329
336
|
};
|
|
330
337
|
const saneTop = (top) => Number.isFinite(top) ? Math.max(0, top) : null;
|
|
331
338
|
const saneMeasured = (height) => Number.isFinite(height) && height >= 0 ? height : null;
|
|
@@ -499,6 +506,6 @@ const masonry = (keys, heights, options) => {
|
|
|
499
506
|
};
|
|
500
507
|
};
|
|
501
508
|
//#endregion
|
|
502
|
-
export { HistoryModel, Locale, LocaleMessage, LocaleModel, MeasureRow, Pagination, PaginationMessage, PaginationModel, RowMeasured, SelectionSet, SelectionSetMessage, SelectionSetModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, canRedo, canUndo, history, isAtEnd, isSelected, masonry, offset, offsetFor, pageCount, range, stickyHeader, totalHeight, visibleRange, windowFor };
|
|
509
|
+
export { HistoryModel, Locale, LocaleMessage, LocaleModel, MeasureRow, Pagination, PaginationMessage, PaginationModel, RowMeasured, SelectionSet, SelectionSetMessage, SelectionSetModel, Viewport, ViewportScrolled, Virtual, VirtualMessage, VirtualModel, canRedo, canUndo, distanceToEnd, history, isAtEnd, isSelected, masonry, offset, offsetFor, pageCount, range, stickyHeader, totalHeight, visibleRange, windowFor };
|
|
503
510
|
|
|
504
511
|
//# sourceMappingURL=index.mjs.map
|
package/dist/state/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/state/history.ts","../../src/state/locale.ts","../../src/state/pagination.ts","../../src/state/range.ts","../../src/state/selection.ts","../../src/state/virtual.ts"],"sourcesContent":["/**\n * Undo/redo as a bundle factory: the past, present, and future of one value\n * live in the Model, so every step replays. Pushing records, even an unchanged\n * value; skipping duplicates is the application's policy, not the stack's.\n */\nimport { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const HistoryModel = <Value>(value: Schema.Codec<Value, unknown>) =>\n Schema.Struct({\n past: Schema.Array(value),\n present: value,\n future: Schema.Array(value),\n })\nexport type HistoryModel<Value> = {\n readonly past: ReadonlyArray<Value>\n readonly present: Value\n readonly future: ReadonlyArray<Value>\n}\n\nexport const history = <const Name extends string, Value>(config: {\n readonly name: Name\n readonly value: Schema.Codec<Value, unknown>\n readonly capacity?: number | undefined\n}) => {\n const capacity = config.capacity ?? 100\n // A negative or fractional capacity would silently keep a wrong-sized past;\n // fail at the factory, naming the caller, instead.\n if (!Number.isInteger(capacity) || capacity < 0) {\n throw new Error(`History \"${config.name}\": capacity must be a non-negative integer`)\n }\n const Model = HistoryModel(config.value)\n const Message = defineMessageUnion({\n Push: { value: config.value },\n Undo: {},\n Redo: {},\n Clear: {},\n })\n return Object.assign(\n Bundle.make(config.name, {\n Model,\n Message,\n args: Schema.Struct({ initial: config.value }),\n init: args => ({ model: { past: [], present: args.initial, future: [] } }),\n update: (model, message) =>\n Message.match(message, {\n Push: ({ value }) => ({\n model: {\n // slice(-0) is slice(0): a zero capacity keeps nothing, explicitly.\n past: capacity === 0 ? [] : [...model.past, model.present].slice(-capacity),\n present: value,\n future: [],\n },\n }),\n Undo: () => {\n const previous = model.past[model.past.length - 1]\n if (previous === undefined) return { model }\n return {\n model: {\n past: model.past.slice(0, -1),\n present: previous,\n future: [model.present, ...model.future],\n },\n }\n },\n Redo: () => {\n const next = model.future[0]\n if (next === undefined) return { model }\n return {\n model: {\n past: [...model.past, model.present],\n present: next,\n future: model.future.slice(1),\n },\n }\n },\n Clear: () => ({ model: { past: [], present: model.present, future: [] } }),\n }),\n }),\n // The union object is both the Message codec and its constructors, so a\n // placement dispatches `EditHistory.Message.Push(...)` like any bundle's\n // exported Message.\n { Message },\n )\n}\n\n/** Whether Undo would move: the past is non-empty. */\nexport const canUndo = <Value>(model: HistoryModel<Value>): boolean => model.past.length > 0\n\n/** Whether Redo would move: the future is non-empty. */\nexport const canRedo = <Value>(model: HistoryModel<Value>): boolean => model.future.length > 0\n","/**\n * Locale as a bundle: the current locale string in the Model. Starts from\n * `navigator.language` where available, falling back to the configured\n * default; changes arrive as Messages. No subscriptions: language changes\n * take effect on reload.\n */\nimport { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const LocaleModel = Schema.Struct({ locale: Schema.String })\nexport type LocaleModel = typeof LocaleModel.Type\n\nexport const LocaleMessage = defineMessageUnion({ SetLocale: { locale: Schema.String } })\nexport type LocaleMessage = typeof LocaleMessage.Type\n\nexport const Locale = Bundle.make('Locale', {\n Model: LocaleModel,\n Message: LocaleMessage,\n args: Schema.Struct({ default: Schema.String }),\n init: args => ({\n model: {\n locale:\n typeof navigator !== 'undefined' && typeof navigator.language === 'string'\n ? navigator.language\n : args.default,\n },\n }),\n update: (model, message) =>\n LocaleMessage.match(message, {\n SetLocale: ({ locale }) => ({ model: { ...model, locale } }),\n }),\n})\n","/**\n * Pagination as a bundle: the page, page size, and total live in the Model,\n * so page changes replay and every transition clamps into range. Pure logic,\n * no streams: data loading stays the application's job.\n */\nimport { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const PaginationModel = Schema.Struct({\n page: Schema.Number,\n perPage: Schema.Number,\n total: Schema.NullOr(Schema.Number),\n})\nexport type PaginationModel = typeof PaginationModel.Type\n\nexport const PaginationMessage = defineMessageUnion({\n GoToPage: { page: Schema.Number },\n NextPage: {},\n PrevPage: {},\n SetPerPage: { perPage: Schema.Number },\n SetTotal: { total: Schema.NullOr(Schema.Number) },\n})\nexport type PaginationMessage = typeof PaginationMessage.Type\n\nconst clampPage = (page: number, perPage: number, total: number | null): number => {\n if (!Number.isInteger(page)) return 1\n if (page < 1) return 1\n if (total === null) return page\n return Math.min(page, Math.max(1, Math.ceil(total / perPage)))\n}\n\n/** The number of pages, or null while the total is unknown. */\nexport const pageCount = (model: PaginationModel): number | null =>\n model.total === null ? null : Math.max(1, Math.ceil(model.total / model.perPage))\n\n/** The offset of the first item on the page, for a slice or a query. */\nexport const offset = (model: PaginationModel): number => (model.page - 1) * model.perPage\n\nexport const Pagination = Bundle.make('Pagination', {\n Model: PaginationModel,\n Message: PaginationMessage,\n args: Schema.Struct({\n perPage: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n total: Schema.optional(Schema.NullOr(Schema.Number.pipe(Schema.check(Schema.isFinite())))),\n }),\n init: args => ({\n model: { page: 1, perPage: args.perPage, total: args.total ?? null },\n }),\n update: (model, message) =>\n PaginationMessage.match(message, {\n GoToPage: ({ page }) => ({\n model: { ...model, page: clampPage(page, model.perPage, model.total) },\n }),\n NextPage: () => ({\n model: { ...model, page: clampPage(model.page + 1, model.perPage, model.total) },\n }),\n PrevPage: () => ({\n model: { ...model, page: clampPage(model.page - 1, model.perPage, model.total) },\n }),\n SetPerPage: ({ perPage }) =>\n perPage > 0\n ? { model: { ...model, perPage, page: clampPage(model.page, perPage, model.total) } }\n : { model },\n SetTotal: ({ total }) =>\n total === null || total >= 0\n ? {\n model: { ...model, total, page: clampPage(model.page, model.perPage, total) },\n }\n : { model },\n }),\n})\n","/**\n * A numeric range as an array: `range(1, 4)` is `[1, 2, 3]`, inclusive of\n * the start and exclusive of the end, like `Array.from` length math. The\n * pagination companion: `range(1, (pageCount(model) ?? 0) + 1)` is the page\n * list. A zero or non-finite step would loop forever, so it throws, naming\n * the caller — like History's capacity check.\n */\nexport const range = (start: number, end: number, step = 1): ReadonlyArray<number> => {\n if (!Number.isFinite(step) || step === 0) {\n throw new Error(`range: step must be a non-zero finite number, got ${step}`)\n }\n const values: Array<number> = []\n if (step > 0) {\n for (let value = start; value < end; value += step) {\n values.push(value)\n }\n } else {\n for (let value = start; value > end; value += step) {\n values.push(value)\n }\n }\n return values\n}\n","/**\n * A selection set as a bundle: the selected ids in the Model, changed one at\n * a time or replaced whole. Order follows first selection; toggling moves a\n * reselected id to the end. Pure logic, no streams.\n */\nimport { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const SelectionSetModel = Schema.Struct({ selected: Schema.Array(Schema.String) })\nexport type SelectionSetModel = typeof SelectionSetModel.Type\n\nexport const SelectionSetMessage = defineMessageUnion({\n Select: { id: Schema.String },\n Deselect: { id: Schema.String },\n Toggle: { id: Schema.String },\n ReplaceAll: { ids: Schema.Array(Schema.String) },\n Clear: {},\n})\nexport type SelectionSetMessage = typeof SelectionSetMessage.Type\n\nconst without = (selected: ReadonlyArray<string>, id: string): ReadonlyArray<string> =>\n selected.filter(other => other !== id)\n\n/** Whether the id is currently selected. */\nexport const isSelected = (model: SelectionSetModel, id: string): boolean =>\n model.selected.includes(id)\n\nexport const SelectionSet = Bundle.make('SelectionSet', {\n Model: SelectionSetModel,\n Message: SelectionSetMessage,\n init: () => ({ model: { selected: [] } }),\n update: (model, message) =>\n SelectionSetMessage.match(message, {\n Select: ({ id }) =>\n isSelected(model, id) ? { model } : { model: { selected: [...model.selected, id] } },\n Deselect: ({ id }) => ({ model: { selected: without(model.selected, id) } }),\n Toggle: ({ id }) =>\n isSelected(model, id)\n ? { model: { selected: without(model.selected, id) } }\n : { model: { selected: [...model.selected, id] } },\n ReplaceAll: ({ ids }) => ({ model: { selected: [...new Set(ids)] } }),\n Clear: () => ({ model: { selected: [] } }),\n }),\n})\n","/**\n * Windowing math for virtualized lists as pure functions: which rows to\n * render, how tall the scrollable area is, and where each row starts. Row\n * heights come from measured values with a per-row estimate fallback, so\n * unmeasured rows still occupy space and measuring later only corrects.\n * Keys are strings; heights are non-negative, and non-finite measurements\n * fall back to the estimate instead of poisoning the sums.\n *\n * The `Virtual` bundle below owns the scroll position and the measured\n * heights; `Viewport` reports container scrolls and `MeasureRow` reports\n * row heights, both as Mounts on the elements they observe. The application\n * renders `windowFor(...)` rows with its own views (keyed, so `each`\n * placements keep identity) inside a spacer of `totalHeight(...)`.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Mount from 'foldkit/mount'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport interface VirtualWindow {\n /** First visible row index, inclusive. */\n readonly start: number\n /** End row index, exclusive. */\n readonly end: number\n}\n\n/** Layout constants: per-row estimate, inter-row gap, and end padding. */\nexport interface VirtualLayout {\n readonly estimatedHeight: number\n readonly gap: number\n readonly paddingStart: number\n readonly paddingEnd: number\n}\n\nconst saneHeight = (height: number, estimatedHeight: number): number =>\n Number.isFinite(height) && height >= 0 ? height : estimatedHeight\n\nconst heightAt = (\n index: number,\n heights: Readonly<Record<string, number>>,\n keys: ReadonlyArray<string>,\n layout: VirtualLayout,\n): number => {\n const key = keys[index]\n if (key === undefined) return layout.estimatedHeight\n return saneHeight(heights[key] ?? layout.estimatedHeight, layout.estimatedHeight)\n}\n\n/** Total scrollable height: padding, measured rows, estimates, and gaps. */\nexport const totalHeight = (\n keys: ReadonlyArray<string>,\n heights: Readonly<Record<string, number>>,\n layout: VirtualLayout,\n): number => {\n let total = layout.paddingStart + layout.paddingEnd\n for (let index = 0; index < keys.length; index++) {\n total += heightAt(index, heights, keys, layout)\n if (index < keys.length - 1) total += layout.gap\n }\n return total\n}\n\n/** Pixel offset of a row's top edge: padding plus the prefix before it. */\nexport const offsetFor = (\n index: number,\n keys: ReadonlyArray<string>,\n heights: Readonly<Record<string, number>>,\n layout: VirtualLayout,\n): number => {\n const clamped = Math.max(0, Math.min(index, keys.length))\n let offset = layout.paddingStart\n for (let i = 0; i < clamped; i++) {\n offset += heightAt(i, heights, keys, layout) + layout.gap\n }\n return offset\n}\n\n/**\n * Which rows to render for a scroll position: the rows intersecting\n * `[scrollTop, scrollTop + viewportHeight)`, widened by `overscan` on both\n * sides and clamped to `[0, count]`. Gaps and padding are dead space — no\n * row — so boxes are tested, not cumulative coverage. An overscan of zero\n * renders exactly the visible rows.\n */\nexport const visibleRange = (\n keys: ReadonlyArray<string>,\n heights: Readonly<Record<string, number>>,\n layout: VirtualLayout,\n scrollTop: number,\n viewportHeight: number,\n overscan: number,\n): VirtualWindow => {\n const count = keys.length\n if (count === 0 || viewportHeight <= 0) return { start: 0, end: 0 }\n const top = Math.max(0, scrollTop)\n const bottom = top + viewportHeight\n let start = 0\n let offset = layout.paddingStart\n for (let index = 0; index < count; index++) {\n const height = heightAt(index, heights, keys, layout)\n if (offset + height <= top) {\n start = index + 1\n offset += height + layout.gap\n } else {\n break\n }\n }\n let end = start\n let cursor = offset\n while (end < count && cursor < bottom) {\n cursor += heightAt(end, heights, keys, layout) + layout.gap\n end += 1\n }\n return {\n start: Math.max(0, start - Math.max(0, overscan)),\n end: Math.min(count, end + Math.max(0, overscan)),\n }\n}\n\nexport const VirtualModel = Schema.Struct({\n scrollTop: Schema.Number,\n heights: Schema.Record(Schema.String, Schema.Number),\n scrolling: Schema.Boolean,\n generation: Schema.Number,\n estimatedHeight: Schema.Number,\n overscan: Schema.Number,\n gap: Schema.Number,\n paddingStart: Schema.Number,\n paddingEnd: Schema.Number,\n})\nexport type VirtualModel = typeof VirtualModel.Type\n\nexport const VirtualMessage = defineMessageUnion({\n Scrolled: { top: Schema.Number },\n Measured: { key: Schema.String, height: Schema.Number },\n /** Drops heights for keys no longer listed: the bundle never sees the key\n order, so the application tells it what left. */\n Prune: { keys: Schema.Array(Schema.String) },\n /** Fires when scrolling has been silent for `settleMs`: only the latest\n generation counts, so a fling's intermediate timers emit nothing. */\n Settled: { generation: Schema.Number },\n})\nexport type VirtualMessage = typeof VirtualMessage.Type\n\nconst layoutOf = (model: VirtualModel): VirtualLayout => ({\n estimatedHeight: model.estimatedHeight,\n gap: model.gap,\n paddingStart: model.paddingStart,\n paddingEnd: model.paddingEnd,\n})\n\n/**\n * The rows to render plus the spacer height, from the Model, the key order,\n * and the viewport height. Layout and overscan come from the Model, so the\n * call cannot disagree with the placement. Keys stay the application's:\n * render each row keyed, and per-row placements keep identity.\n */\nexport const windowFor = (\n model: VirtualModel,\n keys: ReadonlyArray<string>,\n viewportHeight: number,\n): VirtualWindow & { readonly totalHeight: number } => {\n const layout = layoutOf(model)\n const window = visibleRange(\n keys,\n model.heights,\n layout,\n model.scrollTop,\n viewportHeight,\n model.overscan,\n )\n return { ...window, totalHeight: totalHeight(keys, model.heights, layout) }\n}\n\n/**\n * Whether the viewport rests at (or past) the end, within `threshold`\n * pixels: the infinite-scroll check. An empty list counts as ended — there\n * is nothing to scroll, so more should load. Thresholds below zero clamp\n * to zero.\n */\nexport const isAtEnd = (\n model: VirtualModel,\n keys: ReadonlyArray<string>,\n viewportHeight: number,\n threshold = 0,\n): boolean => {\n const total = totalHeight(keys, model.heights, layoutOf(model))\n if (total <= 0) return true\n return model.scrollTop + viewportHeight >= total - Math.max(0, threshold)\n}\n\nconst saneTop = (top: number): number | null => (Number.isFinite(top) ? Math.max(0, top) : null)\n\nconst saneMeasured = (height: number): number | null =>\n Number.isFinite(height) && height >= 0 ? height : null\n\nexport const Virtual = Bundle.make<\n 'Virtual',\n VirtualModel,\n VirtualMessage,\n {\n readonly estimatedHeight: number\n readonly overscan: number\n readonly gap: number\n readonly paddingStart: number\n readonly paddingEnd: number\n readonly settleMs?: number | undefined\n readonly initialScrollTop?: number | undefined\n readonly initialHeights?: Readonly<Record<string, number>> | undefined\n }\n>('Virtual', {\n Model: VirtualModel,\n Message: VirtualMessage,\n args: Schema.Struct({\n estimatedHeight: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n overscan: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),\n gap: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n paddingStart: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n paddingEnd: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n settleMs: Schema.optional(\n Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),\n ),\n initialScrollTop: Schema.optional(\n Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n ),\n initialHeights: Schema.optional(Schema.Record(Schema.String, Schema.Number)),\n }),\n init: args => {\n // Restored measurements pass through the same sanitation as live ones,\n // so a poisoned cache cannot wedge the sums.\n const heights: Record<string, number> = {}\n for (const [key, height] of Object.entries(args.initialHeights ?? {})) {\n const sane = saneMeasured(height)\n if (sane !== null) heights[key] = sane\n }\n return {\n model: {\n scrollTop: args.initialScrollTop ?? 0,\n heights,\n scrolling: false,\n generation: 0,\n estimatedHeight: args.estimatedHeight,\n overscan: args.overscan,\n gap: args.gap,\n paddingStart: args.paddingStart,\n paddingEnd: args.paddingEnd,\n },\n }\n },\n update: (model, message, args) =>\n VirtualMessage.match<Update.ReturnWithOutMessage<VirtualModel, VirtualMessage, never>>(\n message,\n {\n Scrolled: ({ top }) => {\n const sane = saneTop(top)\n if (sane === null) return { model }\n const generation = model.generation + 1\n return {\n model: { ...model, scrollTop: sane, scrolling: true, generation },\n commands: [\n {\n name: 'Virtual.settle',\n args: { generation },\n effect: Effect.as(\n Effect.sleep(args.settleMs ?? 150),\n VirtualMessage.Settled({ generation }),\n ),\n },\n ],\n }\n },\n Measured: ({ key, height }) => {\n const sane = saneMeasured(height)\n return sane === null\n ? { model }\n : { model: { ...model, heights: { ...model.heights, [key]: sane } } }\n },\n Prune: ({ keys }) => {\n const kept = new Set(keys)\n const heights: Record<string, number> = {}\n for (const [key, height] of Object.entries(model.heights)) {\n if (kept.has(key)) heights[key] = height\n }\n return { model: { ...model, heights } }\n },\n // A superseded silence timer carries an old generation: ignore it.\n Settled: ({ generation }) =>\n generation === model.generation ? { model: { ...model, scrolling: false } } : { model },\n },\n ),\n})\n\nexport const ViewportScrolled = Schema.TaggedStruct('ViewportScrolled', { top: Schema.Number })\nexport type ViewportScrolled = typeof ViewportScrolled.Type\n\n/**\n * The scroll container's position, reported on its own scrolls starting\n * with the current one. Attach to the scrolling element — viewport scrolls\n * arrive through the window-level scroll entry instead.\n */\nexport const Viewport = Mount.defineStream('Viewport', {\n messages: [ViewportScrolled],\n execute: ({ element }) => {\n // The mount needs only a scroll position, not a full Element.\n const read = (): ViewportScrolled => {\n const scroller = element as unknown as { readonly scrollTop: number }\n return ViewportScrolled.make({ top: scroller.scrollTop })\n }\n return Stream.concat(\n Stream.make(read()),\n Stream.fromEventListener(element, 'scroll').pipe(Stream.map(read)),\n )\n },\n})\n\nexport const RowMeasured = Schema.TaggedStruct('RowMeasured', {\n key: Schema.String,\n height: Schema.Number,\n})\nexport type RowMeasured = typeof RowMeasured.Type\n\ntype RowObserverCtor = new (callback: ResizeObserverCallback) => ResizeObserver\n\n/**\n * One row's height, reported on every resize starting with the current\n * one. The key is the application's row identity, echoed back so the\n * parent files the height without tracking which Mount sent what.\n */\nexport const MeasureRow = Mount.defineStream('MeasureRow', {\n messages: [RowMeasured],\n args: { key: Schema.String },\n execute: ({ element, key }) =>\n Stream.callback<typeof RowMeasured.Type>(queue =>\n Effect.gen(function* () {\n const Observed = (globalThis as { ResizeObserver?: RowObserverCtor }).ResizeObserver\n if (Observed === undefined) return\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n const observer = new Observed(entries => {\n const rect = entries[0]?.contentRect\n if (rect !== undefined) {\n Queue.offerUnsafe(queue, RowMeasured.make({ key, height: rect.height }))\n }\n })\n observer.observe(element)\n return observer\n }),\n observer => Effect.sync(() => observer.disconnect()),\n )\n }),\n ),\n})\n\n/**\n * Which section header sticks for a window: the last section starting at\n * or before the first rendered row, or null above the first section. Order\n * independent — the scan takes the maximum, so unsorted input still\n * answers. CSS `position: sticky` does the sticking; this answers *what*\n * sticks, for styling and callbacks.\n */\nexport const stickyHeader = (\n sections: ReadonlyArray<{ readonly key: string; readonly index: number }>,\n startIndex: number,\n): string | null => {\n let current: string | null = null\n let currentIndex = -1\n for (const section of sections) {\n if (section.index <= startIndex && section.index > currentIndex) {\n current = section.key\n currentIndex = section.index\n }\n }\n return current\n}\n\nexport interface MasonryPlacement {\n readonly key: string\n readonly column: number\n readonly x: number\n readonly y: number\n readonly width: number\n readonly height: number\n}\n\n/**\n * Masonry layout as a pure function: fixed column count and width, each\n * item packed into the currently shortest column. Unmeasured items use the\n * estimate; poisoned heights fall back the same way. This is layout only,\n * not windowing — every placed item renders, so it fits hundreds of images,\n * not hundred-thousands. Needing a windowed grid is a different algorithm.\n */\nexport const masonry = (\n keys: ReadonlyArray<string>,\n heights: Readonly<Record<string, number>>,\n options: {\n readonly columns: number\n readonly columnWidth: number\n readonly gap: number\n readonly estimatedHeight: number\n },\n): { readonly placements: ReadonlyArray<MasonryPlacement>; readonly totalHeight: number } => {\n if (!Number.isInteger(options.columns) || options.columns < 1) {\n throw new Error(`masonry: columns must be a positive integer, got ${options.columns}`)\n }\n if (!(options.columnWidth > 0) || !Number.isFinite(options.columnWidth)) {\n throw new Error(\n `masonry: columnWidth must be a positive finite number, got ${options.columnWidth}`,\n )\n }\n if (options.gap < 0 || !Number.isFinite(options.gap)) {\n throw new Error(`masonry: gap must be a non-negative finite number, got ${options.gap}`)\n }\n if (options.estimatedHeight < 0 || !Number.isFinite(options.estimatedHeight)) {\n throw new Error(\n `masonry: estimatedHeight must be a non-negative finite number, got ${options.estimatedHeight}`,\n )\n }\n const columnHeights: Array<number> = new Array(options.columns).fill(0)\n const placements: Array<MasonryPlacement> = []\n for (const key of keys) {\n const raw = heights[key] ?? options.estimatedHeight\n const height = Number.isFinite(raw) && raw >= 0 ? raw : options.estimatedHeight\n let column = 0\n for (let c = 1; c < columnHeights.length; c++) {\n if (columnHeights[c]! < columnHeights[column]!) column = c\n }\n placements.push({\n key,\n column,\n x: column * (options.columnWidth + options.gap),\n y: columnHeights[column]!,\n width: options.columnWidth,\n height,\n })\n columnHeights[column] = columnHeights[column]! + height + options.gap\n }\n return {\n placements,\n totalHeight: placements.length === 0 ? 0 : Math.max(...columnHeights) - options.gap,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/state/history.ts","../../src/state/locale.ts","../../src/state/pagination.ts","../../src/state/range.ts","../../src/state/selection.ts","../../src/state/virtual.ts"],"sourcesContent":["/**\n * Undo/redo as a bundle factory: the past, present, and future of one value\n * live in the Model, so every step replays. Pushing records, even an unchanged\n * value; skipping duplicates is the application's policy, not the stack's.\n */\nimport { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const HistoryModel = <Value>(value: Schema.Codec<Value, unknown>) =>\n Schema.Struct({\n past: Schema.Array(value),\n present: value,\n future: Schema.Array(value),\n })\nexport type HistoryModel<Value> = {\n readonly past: ReadonlyArray<Value>\n readonly present: Value\n readonly future: ReadonlyArray<Value>\n}\n\nexport const history = <const Name extends string, Value>(config: {\n readonly name: Name\n readonly value: Schema.Codec<Value, unknown>\n readonly capacity?: number | undefined\n}) => {\n const capacity = config.capacity ?? 100\n // A negative or fractional capacity would silently keep a wrong-sized past;\n // fail at the factory, naming the caller, instead.\n if (!Number.isInteger(capacity) || capacity < 0) {\n throw new Error(`History \"${config.name}\": capacity must be a non-negative integer`)\n }\n const Model = HistoryModel(config.value)\n const Message = defineMessageUnion({\n Push: { value: config.value },\n Undo: {},\n Redo: {},\n Clear: {},\n })\n return Object.assign(\n Bundle.make(config.name, {\n Model,\n Message,\n args: Schema.Struct({ initial: config.value }),\n init: args => ({ model: { past: [], present: args.initial, future: [] } }),\n update: (model, message) =>\n Message.match(message, {\n Push: ({ value }) => ({\n model: {\n // slice(-0) is slice(0): a zero capacity keeps nothing, explicitly.\n past: capacity === 0 ? [] : [...model.past, model.present].slice(-capacity),\n present: value,\n future: [],\n },\n }),\n Undo: () => {\n const previous = model.past[model.past.length - 1]\n if (previous === undefined) return { model }\n return {\n model: {\n past: model.past.slice(0, -1),\n present: previous,\n future: [model.present, ...model.future],\n },\n }\n },\n Redo: () => {\n const next = model.future[0]\n if (next === undefined) return { model }\n return {\n model: {\n past: [...model.past, model.present],\n present: next,\n future: model.future.slice(1),\n },\n }\n },\n Clear: () => ({ model: { past: [], present: model.present, future: [] } }),\n }),\n }),\n // The union object is both the Message codec and its constructors, so a\n // placement dispatches `EditHistory.Message.Push(...)` like any bundle's\n // exported Message.\n { Message },\n )\n}\n\n/** Whether Undo would move: the past is non-empty. */\nexport const canUndo = <Value>(model: HistoryModel<Value>): boolean => model.past.length > 0\n\n/** Whether Redo would move: the future is non-empty. */\nexport const canRedo = <Value>(model: HistoryModel<Value>): boolean => model.future.length > 0\n","/**\n * Locale as a bundle: the current locale string in the Model. Starts from\n * `navigator.language` where available, falling back to the configured\n * default; changes arrive as Messages. No subscriptions: language changes\n * take effect on reload.\n */\nimport { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const LocaleModel = Schema.Struct({ locale: Schema.String })\nexport type LocaleModel = typeof LocaleModel.Type\n\nexport const LocaleMessage = defineMessageUnion({ SetLocale: { locale: Schema.String } })\nexport type LocaleMessage = typeof LocaleMessage.Type\n\nexport const Locale = Bundle.make('Locale', {\n Model: LocaleModel,\n Message: LocaleMessage,\n args: Schema.Struct({ default: Schema.String }),\n init: args => ({\n model: {\n locale:\n typeof navigator !== 'undefined' && typeof navigator.language === 'string'\n ? navigator.language\n : args.default,\n },\n }),\n update: (model, message) =>\n LocaleMessage.match(message, {\n SetLocale: ({ locale }) => ({ model: { ...model, locale } }),\n }),\n})\n","/**\n * Pagination as a bundle: the page, page size, and total live in the Model,\n * so page changes replay and every transition clamps into range. Pure logic,\n * no streams: data loading stays the application's job.\n */\nimport { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const PaginationModel = Schema.Struct({\n page: Schema.Number,\n perPage: Schema.Number,\n total: Schema.NullOr(Schema.Number),\n})\nexport type PaginationModel = typeof PaginationModel.Type\n\nexport const PaginationMessage = defineMessageUnion({\n GoToPage: { page: Schema.Number },\n NextPage: {},\n PrevPage: {},\n SetPerPage: { perPage: Schema.Number },\n SetTotal: { total: Schema.NullOr(Schema.Number) },\n})\nexport type PaginationMessage = typeof PaginationMessage.Type\n\nconst clampPage = (page: number, perPage: number, total: number | null): number => {\n if (!Number.isInteger(page)) return 1\n if (page < 1) return 1\n if (total === null) return page\n return Math.min(page, Math.max(1, Math.ceil(total / perPage)))\n}\n\n/** The number of pages, or null while the total is unknown. */\nexport const pageCount = (model: PaginationModel): number | null =>\n model.total === null ? null : Math.max(1, Math.ceil(model.total / model.perPage))\n\n/** The offset of the first item on the page, for a slice or a query. */\nexport const offset = (model: PaginationModel): number => (model.page - 1) * model.perPage\n\nexport const Pagination = Bundle.make('Pagination', {\n Model: PaginationModel,\n Message: PaginationMessage,\n args: Schema.Struct({\n perPage: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n total: Schema.optional(Schema.NullOr(Schema.Number.pipe(Schema.check(Schema.isFinite())))),\n }),\n init: args => ({\n model: { page: 1, perPage: args.perPage, total: args.total ?? null },\n }),\n update: (model, message) =>\n PaginationMessage.match(message, {\n GoToPage: ({ page }) => ({\n model: { ...model, page: clampPage(page, model.perPage, model.total) },\n }),\n NextPage: () => ({\n model: { ...model, page: clampPage(model.page + 1, model.perPage, model.total) },\n }),\n PrevPage: () => ({\n model: { ...model, page: clampPage(model.page - 1, model.perPage, model.total) },\n }),\n SetPerPage: ({ perPage }) =>\n perPage > 0\n ? { model: { ...model, perPage, page: clampPage(model.page, perPage, model.total) } }\n : { model },\n SetTotal: ({ total }) =>\n total === null || total >= 0\n ? {\n model: { ...model, total, page: clampPage(model.page, model.perPage, total) },\n }\n : { model },\n }),\n})\n","/**\n * A numeric range as an array: `range(1, 4)` is `[1, 2, 3]`, inclusive of\n * the start and exclusive of the end, like `Array.from` length math. The\n * pagination companion: `range(1, (pageCount(model) ?? 0) + 1)` is the page\n * list. A zero or non-finite step would loop forever, so it throws, naming\n * the caller — like History's capacity check.\n */\nexport const range = (start: number, end: number, step = 1): ReadonlyArray<number> => {\n if (!Number.isFinite(step) || step === 0) {\n throw new Error(`range: step must be a non-zero finite number, got ${step}`)\n }\n const values: Array<number> = []\n if (step > 0) {\n for (let value = start; value < end; value += step) {\n values.push(value)\n }\n } else {\n for (let value = start; value > end; value += step) {\n values.push(value)\n }\n }\n return values\n}\n","/**\n * A selection set as a bundle: the selected ids in the Model, changed one at\n * a time or replaced whole. Order follows first selection; toggling moves a\n * reselected id to the end. Pure logic, no streams.\n */\nimport { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const SelectionSetModel = Schema.Struct({ selected: Schema.Array(Schema.String) })\nexport type SelectionSetModel = typeof SelectionSetModel.Type\n\nexport const SelectionSetMessage = defineMessageUnion({\n Select: { id: Schema.String },\n Deselect: { id: Schema.String },\n Toggle: { id: Schema.String },\n ReplaceAll: { ids: Schema.Array(Schema.String) },\n Clear: {},\n})\nexport type SelectionSetMessage = typeof SelectionSetMessage.Type\n\nconst without = (selected: ReadonlyArray<string>, id: string): ReadonlyArray<string> =>\n selected.filter(other => other !== id)\n\n/** Whether the id is currently selected. */\nexport const isSelected = (model: SelectionSetModel, id: string): boolean =>\n model.selected.includes(id)\n\nexport const SelectionSet = Bundle.make('SelectionSet', {\n Model: SelectionSetModel,\n Message: SelectionSetMessage,\n init: () => ({ model: { selected: [] } }),\n update: (model, message) =>\n SelectionSetMessage.match(message, {\n Select: ({ id }) =>\n isSelected(model, id) ? { model } : { model: { selected: [...model.selected, id] } },\n Deselect: ({ id }) => ({ model: { selected: without(model.selected, id) } }),\n Toggle: ({ id }) =>\n isSelected(model, id)\n ? { model: { selected: without(model.selected, id) } }\n : { model: { selected: [...model.selected, id] } },\n ReplaceAll: ({ ids }) => ({ model: { selected: [...new Set(ids)] } }),\n Clear: () => ({ model: { selected: [] } }),\n }),\n})\n","/**\n * Windowing math for virtualized lists as pure functions: which rows to\n * render, how tall the scrollable area is, and where each row starts. Row\n * heights come from measured values with a per-row estimate fallback, so\n * unmeasured rows still occupy space and measuring later only corrects.\n * Keys are strings; heights are non-negative, and non-finite measurements\n * fall back to the estimate instead of poisoning the sums.\n *\n * The `Virtual` bundle below owns the scroll position and the measured\n * heights; `Viewport` reports container scrolls and `MeasureRow` reports\n * row heights, both as Mounts on the elements they observe. The application\n * renders `windowFor(...)` rows with its own views (keyed, so `each`\n * placements keep identity) inside a spacer of `totalHeight(...)`.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Mount from 'foldkit/mount'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport interface VirtualWindow {\n /** First visible row index, inclusive. */\n readonly start: number\n /** End row index, exclusive. */\n readonly end: number\n}\n\n/** Layout constants: per-row estimate, inter-row gap, and end padding. */\nexport interface VirtualLayout {\n readonly estimatedHeight: number\n readonly gap: number\n readonly paddingStart: number\n readonly paddingEnd: number\n}\n\nconst saneHeight = (height: number, estimatedHeight: number): number =>\n Number.isFinite(height) && height >= 0 ? height : estimatedHeight\n\nconst heightAt = (\n index: number,\n heights: Readonly<Record<string, number>>,\n keys: ReadonlyArray<string>,\n layout: VirtualLayout,\n): number => {\n const key = keys[index]\n if (key === undefined) return layout.estimatedHeight\n return saneHeight(heights[key] ?? layout.estimatedHeight, layout.estimatedHeight)\n}\n\n/** Total scrollable height: padding, measured rows, estimates, and gaps. */\nexport const totalHeight = (\n keys: ReadonlyArray<string>,\n heights: Readonly<Record<string, number>>,\n layout: VirtualLayout,\n): number => {\n let total = layout.paddingStart + layout.paddingEnd\n for (let index = 0; index < keys.length; index++) {\n total += heightAt(index, heights, keys, layout)\n if (index < keys.length - 1) total += layout.gap\n }\n return total\n}\n\n/** Pixel offset of a row's top edge: padding plus the prefix before it. */\nexport const offsetFor = (\n index: number,\n keys: ReadonlyArray<string>,\n heights: Readonly<Record<string, number>>,\n layout: VirtualLayout,\n): number => {\n const clamped = Math.max(0, Math.min(index, keys.length))\n let offset = layout.paddingStart\n for (let i = 0; i < clamped; i++) {\n offset += heightAt(i, heights, keys, layout) + layout.gap\n }\n return offset\n}\n\n/**\n * Which rows to render for a scroll position: the rows intersecting\n * `[scrollTop, scrollTop + viewportHeight)`, widened by `overscan` on both\n * sides and clamped to `[0, count]`. Gaps and padding are dead space — no\n * row — so boxes are tested, not cumulative coverage. An overscan of zero\n * renders exactly the visible rows.\n */\nexport const visibleRange = (\n keys: ReadonlyArray<string>,\n heights: Readonly<Record<string, number>>,\n layout: VirtualLayout,\n scrollTop: number,\n viewportHeight: number,\n overscan: number,\n): VirtualWindow => {\n const count = keys.length\n if (count === 0 || viewportHeight <= 0) return { start: 0, end: 0 }\n const top = Math.max(0, scrollTop)\n const bottom = top + viewportHeight\n let start = 0\n let offset = layout.paddingStart\n for (let index = 0; index < count; index++) {\n const height = heightAt(index, heights, keys, layout)\n if (offset + height <= top) {\n start = index + 1\n offset += height + layout.gap\n } else {\n break\n }\n }\n let end = start\n let cursor = offset\n while (end < count && cursor < bottom) {\n cursor += heightAt(end, heights, keys, layout) + layout.gap\n end += 1\n }\n return {\n start: Math.max(0, start - Math.max(0, overscan)),\n end: Math.min(count, end + Math.max(0, overscan)),\n }\n}\n\nexport const VirtualModel = Schema.Struct({\n scrollTop: Schema.Number,\n heights: Schema.Record(Schema.String, Schema.Number),\n scrolling: Schema.Boolean,\n generation: Schema.Number,\n estimatedHeight: Schema.Number,\n overscan: Schema.Number,\n gap: Schema.Number,\n paddingStart: Schema.Number,\n paddingEnd: Schema.Number,\n})\nexport type VirtualModel = typeof VirtualModel.Type\n\nexport const VirtualMessage = defineMessageUnion({\n Scrolled: { top: Schema.Number },\n Measured: { key: Schema.String, height: Schema.Number },\n /** Drops heights for keys no longer listed: the bundle never sees the key\n order, so the application tells it what left. */\n Prune: { keys: Schema.Array(Schema.String) },\n /** Fires when scrolling has been silent for `settleMs`: only the latest\n generation counts, so a fling's intermediate timers emit nothing. */\n Settled: { generation: Schema.Number },\n})\nexport type VirtualMessage = typeof VirtualMessage.Type\n\nconst layoutOf = (model: VirtualModel): VirtualLayout => ({\n estimatedHeight: model.estimatedHeight,\n gap: model.gap,\n paddingStart: model.paddingStart,\n paddingEnd: model.paddingEnd,\n})\n\n/**\n * The rows to render plus the spacer height, from the Model, the key order,\n * and the viewport height. Layout and overscan come from the Model, so the\n * call cannot disagree with the placement. Keys stay the application's:\n * render each row keyed, and per-row placements keep identity.\n */\nexport const windowFor = (\n model: VirtualModel,\n keys: ReadonlyArray<string>,\n viewportHeight: number,\n): VirtualWindow & { readonly totalHeight: number } => {\n const layout = layoutOf(model)\n const window = visibleRange(\n keys,\n model.heights,\n layout,\n model.scrollTop,\n viewportHeight,\n model.overscan,\n )\n return { ...window, totalHeight: totalHeight(keys, model.heights, layout) }\n}\n\n/**\n * Pixels from the viewport's bottom edge to the content's end: negative\n * past it, zero exactly there. Prefetch thresholds want this number (\"load\n * when within 500px\"), not just the boolean below.\n */\nexport const distanceToEnd = (\n model: VirtualModel,\n keys: ReadonlyArray<string>,\n viewportHeight: number,\n): number => {\n const total = totalHeight(keys, model.heights, layoutOf(model))\n return total - (model.scrollTop + viewportHeight)\n}\n\n/**\n * Whether the viewport rests at (or past) the end, within `threshold`\n * pixels: the infinite-scroll check. An empty list counts as ended — there\n * is nothing to scroll, so more should load. Thresholds below zero clamp\n * to zero.\n */\nexport const isAtEnd = (\n model: VirtualModel,\n keys: ReadonlyArray<string>,\n viewportHeight: number,\n threshold = 0,\n): boolean => {\n const total = totalHeight(keys, model.heights, layoutOf(model))\n if (total <= 0) return true\n return distanceToEnd(model, keys, viewportHeight) <= Math.max(0, threshold)\n}\n\nconst saneTop = (top: number): number | null => (Number.isFinite(top) ? Math.max(0, top) : null)\n\nconst saneMeasured = (height: number): number | null =>\n Number.isFinite(height) && height >= 0 ? height : null\n\nexport const Virtual = Bundle.make<\n 'Virtual',\n VirtualModel,\n VirtualMessage,\n {\n readonly estimatedHeight: number\n readonly overscan: number\n readonly gap: number\n readonly paddingStart: number\n readonly paddingEnd: number\n readonly settleMs?: number | undefined\n readonly initialScrollTop?: number | undefined\n readonly initialHeights?: Readonly<Record<string, number>> | undefined\n }\n>('Virtual', {\n Model: VirtualModel,\n Message: VirtualMessage,\n args: Schema.Struct({\n estimatedHeight: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n overscan: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),\n gap: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n paddingStart: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n paddingEnd: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n settleMs: Schema.optional(\n Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),\n ),\n initialScrollTop: Schema.optional(\n Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n ),\n initialHeights: Schema.optional(Schema.Record(Schema.String, Schema.Number)),\n }),\n init: args => {\n // Restored measurements pass through the same sanitation as live ones,\n // so a poisoned cache cannot wedge the sums.\n const heights: Record<string, number> = {}\n for (const [key, height] of Object.entries(args.initialHeights ?? {})) {\n const sane = saneMeasured(height)\n if (sane !== null) heights[key] = sane\n }\n return {\n model: {\n scrollTop: args.initialScrollTop ?? 0,\n heights,\n scrolling: false,\n generation: 0,\n estimatedHeight: args.estimatedHeight,\n overscan: args.overscan,\n gap: args.gap,\n paddingStart: args.paddingStart,\n paddingEnd: args.paddingEnd,\n },\n }\n },\n update: (model, message, args) =>\n VirtualMessage.match<Update.ReturnWithOutMessage<VirtualModel, VirtualMessage, never>>(\n message,\n {\n Scrolled: ({ top }) => {\n const sane = saneTop(top)\n if (sane === null) return { model }\n const generation = model.generation + 1\n return {\n model: { ...model, scrollTop: sane, scrolling: true, generation },\n commands: [\n {\n name: 'Virtual.settle',\n args: { generation },\n effect: Effect.as(\n Effect.sleep(args.settleMs ?? 150),\n VirtualMessage.Settled({ generation }),\n ),\n },\n ],\n }\n },\n Measured: ({ key, height }) => {\n const sane = saneMeasured(height)\n return sane === null\n ? { model }\n : { model: { ...model, heights: { ...model.heights, [key]: sane } } }\n },\n Prune: ({ keys }) => {\n const kept = new Set(keys)\n const heights: Record<string, number> = {}\n for (const [key, height] of Object.entries(model.heights)) {\n if (kept.has(key)) heights[key] = height\n }\n return { model: { ...model, heights } }\n },\n // A superseded silence timer carries an old generation: ignore it.\n Settled: ({ generation }) =>\n generation === model.generation ? { model: { ...model, scrolling: false } } : { model },\n },\n ),\n})\n\nexport const ViewportScrolled = Schema.TaggedStruct('ViewportScrolled', { top: Schema.Number })\nexport type ViewportScrolled = typeof ViewportScrolled.Type\n\n/**\n * The scroll container's position, reported on its own scrolls starting\n * with the current one. Attach to the scrolling element — viewport scrolls\n * arrive through the window-level scroll entry instead.\n */\nexport const Viewport = Mount.defineStream('Viewport', {\n messages: [ViewportScrolled],\n execute: ({ element }) => {\n // The mount needs only a scroll position, not a full Element.\n const read = (): ViewportScrolled => {\n const scroller = element as unknown as { readonly scrollTop: number }\n return ViewportScrolled.make({ top: scroller.scrollTop })\n }\n return Stream.concat(\n Stream.make(read()),\n Stream.fromEventListener(element, 'scroll').pipe(Stream.map(read)),\n )\n },\n})\n\nexport const RowMeasured = Schema.TaggedStruct('RowMeasured', {\n key: Schema.String,\n height: Schema.Number,\n})\nexport type RowMeasured = typeof RowMeasured.Type\n\ntype RowObserverCtor = new (callback: ResizeObserverCallback) => ResizeObserver\n\n/**\n * One row's height, reported on every resize starting with the current\n * one. The key is the application's row identity, echoed back so the\n * parent files the height without tracking which Mount sent what.\n */\nexport const MeasureRow = Mount.defineStream('MeasureRow', {\n messages: [RowMeasured],\n args: { key: Schema.String },\n execute: ({ element, key }) =>\n Stream.callback<typeof RowMeasured.Type>(queue =>\n Effect.gen(function* () {\n const Observed = (globalThis as { ResizeObserver?: RowObserverCtor }).ResizeObserver\n if (Observed === undefined) return\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n const observer = new Observed(entries => {\n const rect = entries[0]?.contentRect\n if (rect !== undefined) {\n Queue.offerUnsafe(queue, RowMeasured.make({ key, height: rect.height }))\n }\n })\n observer.observe(element)\n return observer\n }),\n observer => Effect.sync(() => observer.disconnect()),\n )\n }),\n ),\n})\n\n/**\n * Which section header sticks for a window: the last section starting at\n * or before the first rendered row, or null above the first section. Order\n * independent — the scan takes the maximum, so unsorted input still\n * answers. CSS `position: sticky` does the sticking; this answers *what*\n * sticks, for styling and callbacks.\n */\nexport const stickyHeader = (\n sections: ReadonlyArray<{ readonly key: string; readonly index: number }>,\n startIndex: number,\n): string | null => {\n let current: string | null = null\n let currentIndex = -1\n for (const section of sections) {\n if (section.index <= startIndex && section.index > currentIndex) {\n current = section.key\n currentIndex = section.index\n }\n }\n return current\n}\n\nexport interface MasonryPlacement {\n readonly key: string\n readonly column: number\n readonly x: number\n readonly y: number\n readonly width: number\n readonly height: number\n}\n\n/**\n * Masonry layout as a pure function: fixed column count and width, each\n * item packed into the currently shortest column. Unmeasured items use the\n * estimate; poisoned heights fall back the same way. This is layout only,\n * not windowing — every placed item renders, so it fits hundreds of images,\n * not hundred-thousands. Needing a windowed grid is a different algorithm.\n */\nexport const masonry = (\n keys: ReadonlyArray<string>,\n heights: Readonly<Record<string, number>>,\n options: {\n readonly columns: number\n readonly columnWidth: number\n readonly gap: number\n readonly estimatedHeight: number\n },\n): { readonly placements: ReadonlyArray<MasonryPlacement>; readonly totalHeight: number } => {\n if (!Number.isInteger(options.columns) || options.columns < 1) {\n throw new Error(`masonry: columns must be a positive integer, got ${options.columns}`)\n }\n if (!(options.columnWidth > 0) || !Number.isFinite(options.columnWidth)) {\n throw new Error(\n `masonry: columnWidth must be a positive finite number, got ${options.columnWidth}`,\n )\n }\n if (options.gap < 0 || !Number.isFinite(options.gap)) {\n throw new Error(`masonry: gap must be a non-negative finite number, got ${options.gap}`)\n }\n if (options.estimatedHeight < 0 || !Number.isFinite(options.estimatedHeight)) {\n throw new Error(\n `masonry: estimatedHeight must be a non-negative finite number, got ${options.estimatedHeight}`,\n )\n }\n const columnHeights: Array<number> = new Array(options.columns).fill(0)\n const placements: Array<MasonryPlacement> = []\n for (const key of keys) {\n const raw = heights[key] ?? options.estimatedHeight\n const height = Number.isFinite(raw) && raw >= 0 ? raw : options.estimatedHeight\n let column = 0\n for (let c = 1; c < columnHeights.length; c++) {\n if (columnHeights[c]! < columnHeights[column]!) column = c\n }\n placements.push({\n key,\n column,\n x: column * (options.columnWidth + options.gap),\n y: columnHeights[column]!,\n width: options.columnWidth,\n height,\n })\n columnHeights[column] = columnHeights[column]! + height + options.gap\n }\n return {\n placements,\n totalHeight: placements.length === 0 ? 0 : Math.max(...columnHeights) - options.gap,\n }\n}\n"],"mappings":"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"}
|
package/dist/time/index.d.mts
CHANGED
|
@@ -116,8 +116,8 @@ export type TimerMessage = typeof TimerMessage.Type;
|
|
|
116
116
|
export declare const Timer: Bundle.Bundle<"Timer", {
|
|
117
117
|
readonly intervalMs: number;
|
|
118
118
|
}, {
|
|
119
|
-
count: number;
|
|
120
|
-
running: boolean;
|
|
119
|
+
readonly count: number;
|
|
120
|
+
readonly running: boolean;
|
|
121
121
|
}, {
|
|
122
122
|
readonly _tag: "Started";
|
|
123
123
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/time/debounce.ts","../../src/time/interval.ts","../../src/time/relative.ts","../../src/time/throttle.ts","../../src/time/timer.ts"],"mappings":";;;qBAaa,iBAAkB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/time/debounce.ts","../../src/time/interval.ts","../../src/time/relative.ts","../../src/time/throttle.ts","../../src/time/timer.ts"],"mappings":";;;qBAaa,iBAAkB,qBAAqB,OAAO;WAChD,MAAM;WACN,OAAO,OAAO,MAAM;MAC9B,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCJY,eAAa,OAAA;;;;YAId,uBAAuB,cAAc;qBAEpC,2CAAe;;;;iBAAf,OAAA;;;YAKD,yBAAyB,gBAAgB;qBAExC,UAAQ,OAAA;;;;;;;;;;;;;;;;;;;;;qBCnBR,qBAAsB,MAAM,MAAM,IAAI,MAAM;;;qBCM5C,eAAa,OAAA;;;YACd,uBAAuB,cAAc;qBAEpC,2CAAe;;;kBAAf,OAAA;;;YAID,yBAAyB,gBAAgB;qBAExC,WAAS,OAAA;;;YACV,mBAAmB,UAAU;qBAE5B,UAAQ,OAAA;;;;;;;;;;;;;;;qBCdR,YAAU,OAAA;;;;YACX,oBAAoB,WAAW;qBAE9B,wCAAY;;;;;YAKb,sBAAsB,aAAa;qBAElC,OAAK,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foldkit-primitives",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Ready-made primitives for the Elm architecture, packaged with foldkit-bundle: media queries, presence, timers, pagination, history, sockets, observers, and clipboard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"foldkit",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"LICENSE"
|
|
75
75
|
],
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"foldkit-bundle": "0.
|
|
77
|
+
"foldkit-bundle": "0.2.0"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
80
|
"effect": "^4.0.0-rc.112",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"foldkit": "0.158.2",
|
|
87
87
|
"jsdom": "26.1.0",
|
|
88
88
|
"tsdown": "^0.23.0",
|
|
89
|
-
"typescript": "
|
|
89
|
+
"typescript": "5.9.3",
|
|
90
90
|
"ws": "^8.18.0"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|