foldkit-primitives 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -114,6 +114,8 @@ corrects it on subscribe. The Solid equivalent this replaces:
114
114
 
115
115
  ## Media: `foldkit-primitives/media`
116
116
 
117
+ > Reference: [`./media/README.md`](./media/README.md)
118
+
117
119
  `MediaQuery` follows one CSS media query. Model `{ matches: boolean }`,
118
120
  one Message `Changed { matches }`, args `{ query: string }`. The stream emits
119
121
  the current value on subscribe, then every change; without `matchMedia` it is
@@ -135,13 +137,16 @@ the largest name at or below the width (ties break alphabetically); SSR
135
137
  starts at width 0 with `null`. Placing both `Breakpoints` and `WindowSize`
136
138
  doubles resize listeners — pick the one the view reads.
137
139
 
138
- `platformFromUA(ua)` reads `mac | windows | linux | android | ios | unknown`
140
+ `platformFromUA(ua, hints?)` reads `mac | windows | linux | android | ios | unknown`
139
141
  from a passed user-agent string (mobile checks first: Android contains
140
- "Linux", iPhones mention "Mac"); `isBrowser()`/`isServer()` split SSR from
142
+ "Linux", iPhones mention "Mac"); Client Hints `platform` wins when
143
+ recognized, and a multi-touch Mac UA reads as iOS. `isBrowser()`/`isServer()` split SSR from
141
144
  client for init defaults.
142
145
 
143
146
  ## Net: `foldkit-primitives/net`
144
147
 
148
+ > Reference: [`./net/README.md`](./net/README.md)
149
+
145
150
  `Online` keeps `online: boolean` in the Model, read from `navigator.onLine`
146
151
  at startup and kept current by the window's `online`/`offline` events. One
147
152
  Message `Changed { online }`, no args, no OutMessage. Without a window the
@@ -175,6 +180,8 @@ the Command fails, instead of throwing.
175
180
 
176
181
  ## Time: `foldkit-primitives/time`
177
182
 
183
+ > Reference: [`./time/README.md`](./time/README.md)
184
+
178
185
  `Timer` counts ticks while running. Model `{ count, running }`, Messages
179
186
  `Started`/`Stopped`/`Ticked`, args `{ intervalMs }` (positive and finite —
180
187
  anything else is rejected at placement). The tick stream runs on
@@ -207,6 +214,8 @@ platform, not a phrase table. There is deliberately no `now` helper:
207
214
 
208
215
  ## Events: `foldkit-primitives/events`
209
216
 
217
+ > Reference: [`./events/README.md`](./events/README.md)
218
+
210
219
  `Visibility` keeps `visible: boolean` in the Model, read from the document
211
220
  at startup (SSR assumes visible) and kept current by `visibilitychange`:
212
221
  one Message `Changed { visible }`, no args. A hidden page is the
@@ -240,6 +249,8 @@ a window each stream is empty instead of throwing.
240
249
 
241
250
  ## Observers: `foldkit-primitives/observers`
242
251
 
252
+ > Reference: [`./observers/README.md`](./observers/README.md)
253
+
243
254
  `Resize` and `Intersection` are Mounts, not bundles: element-scoped
244
255
  observation attaches in views, not Model slots. Attach `Resize()` (or
245
256
  `Intersection()`) with `h.OnMount` on the element. There are no ref objects
@@ -260,6 +271,8 @@ is same-valued and harmless.
260
271
 
261
272
  ## Device: `foldkit-primitives/device`
262
273
 
274
+ > Reference: [`./device/README.md`](./device/README.md)
275
+
263
276
  `Geolocation` watches the device position while placed. Model `{ status,
264
277
  coords, lastError }` with `status` unknown → ready; denial is its own status
265
278
  (actionable UI), transient failures keep the last fix and note the error.
@@ -269,7 +282,7 @@ geolocation API the stream is empty instead of throwing.
269
282
  `mediaDevices({ name, create? })` scans the device list on placement and
270
283
  re-scans on `Scan`, `DevicesChanged` (wired to `devicechange`), and every
271
284
  placement: `Refreshed { devices }` with `{ deviceId, groupId, kind, label }`.
272
- Denial empties with status `denied`; other failures keep the last list and
285
+ Denial lands as `denied` while keeping the last list; other failures keep the last list and
273
286
  note the error; an unknown `kind` fails the scan at the boundary instead of
274
287
  entering the Model.
275
288
 
@@ -296,6 +309,8 @@ owns fullscreen state.
296
309
 
297
310
  ## DOM: `foldkit-primitives/dom`
298
311
 
312
+ > Reference: [`./dom/README.md`](./dom/README.md)
313
+
299
314
  `copyText` copies text as a Command: use it in `update` beside any bundle.
300
315
  It yields `Copied` on success and `CopyFailed` otherwise — denial, insecure
301
316
  context, or no clipboard API (SSR) all become the failure Message instead of
@@ -317,6 +332,8 @@ state, like any controlled input.
317
332
 
318
333
  ## State: `foldkit-primitives/state`
319
334
 
335
+ > Reference: [`./state/README.md`](./state/README.md)
336
+
320
337
  `Pagination` keeps `{ page, perPage, total }` in the Model, with `total: null`
321
338
  while unknown. Every transition clamps into range: past the last page lands on
322
339
  it, below one lands on one, and a smaller total pulls the page back. New sizes
@@ -362,7 +379,8 @@ live ones) and a `settleMs` silence (default 150). Every scroll marks
362
379
  `Viewport` reports the container's own scrolls and `MeasureRow({ key })` reports row heights, both as Mounts;
363
380
  `windowFor(model, keys, viewportHeight)` answers which rows to render plus
364
381
  the spacer height, `isAtEnd(model, keys, viewportHeight, threshold)` is the
365
- infinite-scroll check (an empty list counts as ended), and `offsetFor`
382
+ infinite-scroll check (an empty list counts as ended), `distanceToEnd`
383
+ answers the pixels remaining for prefetch thresholds, and `offsetFor`
366
384
  computes programmatic scroll targets the application actuates itself.
367
385
  `Prune` drops heights for departed keys — the bundle never sees key order.
368
386
  Poisoned positions and heights are ignored, never stored. For window-
@@ -396,6 +414,8 @@ application uses.
396
414
 
397
415
  ## Motion: `foldkit-primitives/motion`
398
416
 
417
+ > Reference: [`./motion/README.md`](./motion/README.md)
418
+
399
419
  `Tween` animates one number from `from` to `to` over `ms` milliseconds.
400
420
  Model `{ value, running }`, Messages `Started`/`Ticked`/`Finished`, args
401
421
  `{ from, to, ms }` (a non-positive or non-finite duration is rejected at
@@ -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,kBAAe,SAAa,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;;;;;;;;;;;;;;;;;;;;;qBCzEX,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,qBAAmB;WAC3C,MAAM;;WAEN,WAAW,WAAW,cAAc;MAC9C,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBC/CY,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,qBAAmB;WAC1C,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,qBAAmB;WAC1C,MAAM;;WAEN,WAAW,WAAW,cAAc;MAC9C,OAAA,OAAA;WAYqB,OAAA;;;;;;;;;;;;;;;;;;;;;EApCb,OAAA,gBAAgB,MACrB,kBACA,oBACA,OAAO,OAAO,wBACd,UACA,eACC,UAAU,aAAa"}
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"}
@@ -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` with an empty list (actionable UI); any other
156
- * failure keeps the last list and notes the error. No media-devices API
157
- * yields an empty stream and a `Failed` scan instead of throwing.
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":";;;;;;;;;;;;;AAWA,MAAa,oBAAoB,mBAAmB;CAClD,SAAS,EAAE,QAAQ,OAAO,QAAQ;CAClC,SAAS,CAAC;CACV,QAAQ,CAAC;CACT,QAAQ,EAAE,SAAS,OAAO,OAAO;AACnC,CAAC;AAGD,MAAMA,iBAAe,UACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;;;;;;;AAQvD,MAAa,mBAAmB,aAAgE;CAC9F,MAAM;CACN,MAAM,CAAC;CACP,QAAQ,OAAO,YACb,OAAO,WAAW;EAChB,WAAW;GACT,MAAM,cAAc;GAIpB,MAAM,UAAU,YAAY,qBAAqB,YAAY;GAC7D,IAAI,OAAO,YAAY,YACrB,MAAM,IAAI,MAAM,2BAA2B;GAE7C,OAAQ,QAAgC,KAAK,OAAO;EACtD;EACA,QAAQ,UAAmB;CAC7B,CAAC,GACD;EACE,YAAW,UAAS,OAAO,QAAQ,kBAAkB,OAAO,EAAE,SAASA,cAAY,KAAK,EAAE,CAAC,CAAC;EAC5F,iBAAiB,OAAO,QAAQ,kBAAkB,QAAQ,CAAC;CAC7D,CACF;AACF;;AAGA,MAAa,wBAAkE;CAC7E,MAAM;CACN,MAAM,CAAC;CACP,QAAQ,OAAO,YACb,OAAO,WAAW;EAChB,WAAW;GACT,IAAI,OAAO,aAAa,aACtB,MAAM,IAAI,MAAM,2BAA2B;GAE7C,MAAM,MAAM;GAIZ,MAAM,OAAO,IAAI,kBAAkB,IAAI;GACvC,IAAI,OAAO,SAAS,YAClB,MAAM,IAAI,MAAM,2BAA2B;GAE7C,OAAQ,KAA6B,KAAK,QAAQ;EACpD;EACA,QAAQ,UAAmB;CAC7B,CAAC,GACD;EACE,YAAW,UAAS,OAAO,QAAQ,kBAAkB,OAAO,EAAE,SAASA,cAAY,KAAK,EAAE,CAAC,CAAC;EAC5F,iBAAiB,OAAO,QAAQ,kBAAkB,OAAO,CAAC;CAC5D,CACF;AACF;;;;;AAMA,MAAa,0BAA4D;CACvE,IAAI,OAAO,aAAa,eAAe,OAAO,SAAS,qBAAqB,YAC1E,OAAO,OAAO;CAGhB,MAAM,iBACJ,SAAS,sBAAsB,QAAQ,SAAS,sBAAsB,KAAA;CACxE,OAAO,OAAO,OACZ,OAAO,KAAK,kBAAkB,QAAQ,EAAE,QAAQ,SAAS,EAAE,CAAC,CAAC,GAC7D,OAAO,kBAAkB,UAAU,kBAAkB,CAAC,CAAC,KACrD,OAAO,UAAU,kBAAkB,QAAQ,EAAE,QAAQ,SAAS,EAAE,CAAC,CAAC,CACpE,CACF;AACF;;;;;;;;;ACvFA,MAAa,cAAc,OAAO,OAAO;CACvC,UAAU,OAAO;CACjB,WAAW,OAAO;CAClB,UAAU,OAAO;AACnB,CAAC;AAGD,MAAa,mBAAmB,OAAO,OAAO;CAC5C,QAAQ,OAAO,SAAS;EAAC;EAAW;EAAS;CAAQ,CAAC;CACtD,QAAQ,OAAO,OAAO,WAAW;CACjC,WAAW,OAAO,OAAO,OAAO,MAAM;AACxC,CAAC;AAGD,MAAa,qBAAqB,mBAAmB;CACnD,SAAS;EACP,UAAU,OAAO;EACjB,WAAW,OAAO;EAClB,UAAU,OAAO;CACnB;CACA,QAAQ,CAAC;CACT,QAAQ,EAAE,SAAS,OAAO,OAAO;AACnC,CAAC;AAYD,MAAM,oBAAuD;CAC3D,IACE,OAAO,cAAc,eACrB,OAAO,UAAU,aAAa,kBAAkB,YAEhD,OAAO,OAAO;CAEhB,MAAM,cAAc,UAAU;CAC9B,OAAO,OAAO,OACZ,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,aAA8B,aAAY;GAC9C,OAAO,QACL,MAAM,MACJ,OACA,mBAAmB,QAAQ;IACzB,UAAU,SAAS,OAAO;IAC1B,WAAW,SAAS,OAAO;IAC3B,UAAU,SAAS,OAAO;GAC5B,CAAC,CACH,CACF;EACF;EACA,MAAM,WAAyB,UAAS;GACtC,OAAO,QACL,MAAM,MACJ,OACA,MAAM,SAAS,IACX,mBAAmB,OAAO,IAC1B,mBAAmB,OAAO,EAAE,SAAS,MAAM,QAAQ,CAAC,CAC1D,CACF;EACF;EACA,MAAM,UAAU,YAAY,cAAc,WAAW,OAAO;EAC5D,OAAO,OAAO,mBAAmB,OAAO,WAAW,YAAY,WAAW,OAAO,CAAC,CAAC;EACnF,OAAO,OAAO,UAAU,KAAK;CAC/B,CAAC,CACH;AACF;AAEA,MAAa,cAAc,OAAO,KAWhC,eAAe;CACf,OAAO;CACP,SAAS;CACT,aAAa,EAAE,OAAO;EAAE,QAAQ;EAAW,QAAQ;EAAM,WAAW;CAAK,EAAE;CAC3E,SAAS,OAAO,YACd,mBAAmB,MAEjB,SAAS;EACT,UAAU,EAAE,UAAU,WAAW,gBAAgB,EAC/C,OAAO;GAAE,GAAG;GAAO,QAAQ;GAAkB,QAAQ;IAAE;IAAU;IAAW;GAAS;EAAE,EACzF;EACA,eAAe,EAAE,OAAO;GAAE,GAAG;GAAO,QAAQ;EAAkB,EAAE;EAChE,SAAS,EAAE,eAAe,EAAE,OAAO;GAAE,GAAG;GAAO,WAAW;EAAQ,EAAE;CACtE,CAAC;CACH,qBACE,aAAa,KAA2C,CAAC,QAAQ,EAC/D,OAAO,aAAa,WAAW,YAAY,CAAC,EAC9C,EAAE;AACN,CAAC;;;;;;;;;;;ACrGD,MAAa,cAAc,OAAO,OAAO;CACvC,UAAU,OAAO;CACjB,SAAS,OAAO;CAChB,MAAM,OAAO,SAAS;EAAC;EAAc;EAAe;CAAY,CAAC;CACjE,OAAO,OAAO;AAChB,CAAC;AAGD,MAAa,oBAAoB,OAAO,OAAO;CAC7C,QAAQ,OAAO,SAAS;EAAC;EAAW;EAAS;CAAQ,CAAC;CACtD,SAAS,OAAO,MAAM,WAAW;CACjC,WAAW,OAAO,OAAO,OAAO,MAAM;AACxC,CAAC;AAGD,MAAa,sBAAsB,mBAAmB;CACpD,MAAM,CAAC;CACP,gBAAgB,CAAC;CACjB,WAAW,EAAE,SAAS,OAAO,MAAM,WAAW,EAAE;CAChD,QAAQ,CAAC;CACT,QAAQ,EAAE,SAAS,OAAO,OAAO;AACnC,CAAC;AAiBD,MAAMC,iBAAe,UACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvD,MAAMC,cAAY,UAChB,OAAQ,OAA8C,SAAS,YAC/D,CAAC,mBAAmB,eAAe,CAAC,CAAC,SAAU,MAAoC,IAAI;AAEzF,MAAa,gBAA2C,WAIlD;CACJ,MAAM,eAA0C;EAC9C,IAAI,OAAO,cAAc,aAAa,OAAO;EAC7C,IAAI,OAAO,WAAW,KAAA,GAAW,OAAO,OAAO,OAAO,SAAS;EAE/D,OADgB,UAAU,gBACR;CACpB;CAEA,MAAM,aAAa,OAAO,YACxB,OAAO,WAAW;EAChB,KAAK,YAAY;GACf,MAAM,UAAU,OAAO;GACvB,IAAI,YAAY,MAAM,MAAM,IAAI,MAAM,+BAA+B;GAIrE,QAAO,MAHc,QAAQ,iBAAiB,EAAA,CAGhC,KAAI,SAAQ;IACxB,IACE,KAAK,SAAS,gBACd,KAAK,SAAS,iBACd,KAAK,SAAS,cAEd,MAAM,IAAI,MAAM,wBAAwB,KAAK,MAAM;IAGrD,OAAO;KACL,UAAU,KAAK;KACf,SAAS,KAAK;KACd,MAAM,KAAK;KACX,OAAO,KAAK;IACd;GACF,CAAC;EACH;EACA,QAAQ,UAAmB;CAC7B,CAAC,GACD;EACE,YAAW,UACT,OAAO,QACLA,WAAS,KAAK,IACV,oBAAoB,OAAO,IAC3B,oBAAoB,OAAO,EAAE,SAASD,cAAY,KAAK,EAAE,CAAC,CAChE;EACF,YAAW,YACT,OAAO,QAAQ,oBAAoB,UAAU,EAAE,SAAS,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC;CAC3E,CACF;CAEA,MAAM,cAAc;EAAE,MAAM,GAAG,OAAO,KAAK;EAAQ,QAAQ;CAAW;CAEtE,MAAM,sBAA0D;EAC9D,MAAM,UAAU,OAAO;EACvB,IAAI,YAAY,MAAM,OAAO,OAAO;EAGpC,OAAO,OAAO,kBAAkB,SAAmC,cAAc,CAAC,CAAC,KACjF,OAAO,UAAU,oBAAoB,eAAe,CAAC,CACvD;CACF;CAEA,OAAO,OAAO,KAAmD,OAAO,MAAM;EAC5E,OAAO;EACP,SAAS;EACT,aAAa;GACX,OAAO;IAAE,QAAQ;IAAW,SAAS,CAAC;IAAG,WAAW;GAAK;GACzD,UAAU,CAAC,KAAK,CAAC;EACnB;EACA,SAAS,OAAO,YACd,oBAAoB,MAElB,SAAS;GACT,aAAa;IAAE;IAAO,UAAU,CAAC,KAAK,CAAC;GAAE;GACzC,uBAAuB;IAAE;IAAO,UAAU,CAAC,KAAK,CAAC;GAAE;GACnD,YAAY,EAAE,eAAe,EAC3B,OAAO;IAAE,GAAG;IAAO,QAAQ;IAAkB,SAAS,CAAC,GAAG,OAAO;GAAE,EACrE;GACA,eAAe,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAkB,EAAE;GAChE,SAAS,EAAE,eAAe,EAAE,OAAO;IAAE,GAAG;IAAO,WAAW;GAAQ,EAAE;EACtE,CAAC;EACH,qBACE,aAAa,KAA6C,CAAC,QAAQ,EACjE,SAAS,aAAa,WAAW,cAAc,CAAC,EAClD,EAAE;CACN,CAAC;AACH;;;;;;;;;;;;ACnIA,MAAa,mBAAmB,OAAO,OAAO;CAC5C,QAAQ,OAAO,SAAS;EAAC;EAAQ;EAAc;EAAQ;CAAQ,CAAC;CAChE,WAAW,OAAO,OAAO,OAAO,MAAM;AACxC,CAAC;AAGD,MAAa,qBAAqB,mBAAmB;CACnD,SAAS,CAAC;CACV,SAAS,CAAC;CACV,MAAM,CAAC;CACP,OAAO,CAAC;CACR,QAAQ,CAAC;CACT,QAAQ,EAAE,SAAS,OAAO,OAAO;AACnC,CAAC;;AAmBD,MAAa,aAAa,gBAAgB,IAAuB,CAAC,CAAC,cAAc;AAgBjF,MAAME,iBAAe,UACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvD,MAAM,YAAY,UAChB,OAAQ,OAA8C,SAAS,YAC/D,CAAC,mBAAmB,eAAe,CAAC,CAAC,SAAU,MAAoC,IAAI;AAEzF,MAAa,eAA0C,WAKjD;CACJ,MAAM,WAAW,gBAAoE;EACnF,IAAI,OAAO,YAAY,KAAA,GAAW,OAAO,OAAO,QAAQ,WAAW;EACnE,IACE,OAAO,cAAc,eACrB,OAAO,UAAU,cAAc,iBAAiB,YAEhD,OAAO,QAAQ,uBAAO,IAAI,MAAM,+BAA+B,CAAC;EAElE,OAAO,UAAU,aAAa,aAAa,WAAW;CACxD;CAEA,OAAO,OAAO,KAAK,OAAO,MAAM;EAC9B,OAAO;EACP,SAAS;EACT,MAAM,OAAO,OAAO;GAAE,OAAO,OAAO;GAAS,OAAO,OAAO;EAAQ,CAAC;EACpE,aAAa,EAAE,OAAO;GAAE,QAAQ;GAAiB,WAAW;EAAK,EAAE;EACnE,SAAS,OAAO,YACd,mBAAmB,MAEjB,SAAS;GACT,gBAAgB,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAsB,EAAE;GACrE,gBAAgB,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAgB,EAAE;GAC/D,aAAa,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ;IAAiB,WAAW;GAAK,EAAE;GAC7E,cAAc,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAgB,EAAE;GAC7D,eAAe,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAkB,EAAE;GAChE,SAAS,EAAE,eAAe,EACxB,OAAO;IAAE,GAAG;IAAO,QAAQ;IAAiB,WAAW;GAAQ,EACjE;EACF,CAAC;EACH,YAAW,SACT,gBAAgB,KAA2C,CAAC,EAAC,WAAU,EACrE,QAAQ,MACN,OAAO,OAAO,OAAO,OAAO;GAAE,OAAO,OAAO;GAAS,OAAO,OAAO;EAAQ,CAAC,CAAC,GAC7E;GACE,UAAU;GACV,2BAA0B,UACxB,MAAM,WAAW,gBAAgB,MAAM,WAAW,SAC9C,OAAO,KAAK;IAAE,OAAO,KAAK;IAAO,OAAO,KAAK;GAAM,CAAC,IACpD,OAAO,KAAK;GAClB,UAAU,gBACR,OAAO,WAAW;IAChB,WAAW,QAAQ,WAAW;IAC9B,QAAQ,UAAmB;GAC7B,CAAC;GACH,kBAAkB,mBAAmB,KAAK;GAC1C,kBAAkB,mBAAmB,MAAM;GAC3C,iBAAgB,UACd,SAAS,KAAK,IACV,mBAAmB,OAAO,IAC1B,mBAAmB,OAAO,EAAE,SAASA,cAAY,KAAK,EAAE,CAAC;GAC/D,UAAS,WACP,OAAO,WAAW;IAChB,KAAK,MAAM,SAAS,OAAO,UAAU,GAAG,MAAM,KAAK;GACrD,CAAC;EACL,CACF,EACF,EAAE;CACN,CAAC;AACH;;;;;;;;;;;ACvHA,MAAa,kBAAkB,OAAO,SAAS;CAAC;CAAW;CAAU;AAAQ,CAAC;AAG9E,MAAa,mBAAmB,OAAO,OAAO;CAC5C,QAAQ,OAAO,OAAO,OAAO,QAAQ,eAAe;CACpD,WAAW,OAAO,OAAO,OAAO,MAAM;AACxC,CAAC;AAGD,MAAa,qBAAqB,mBAAmB;CACnD,UAAU,EAAE,QAAQ,OAAO,OAAO,OAAO,QAAQ,eAAe,EAAE;CAClE,SAAS;EAAE,MAAM,OAAO;EAAQ,OAAO;CAAgB;CACvD,SAAS,CAAC;CACV,QAAQ,EAAE,SAAS,OAAO,OAAO;AACnC,CAAC;;AAqBD,MAAa,QAAQ,gBAAgB,IAAc,CAAC,CAAC,aAAa;AAgBlE,MAAM,eAAe,UACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvD,MAAM,aAAa,OAAe,SAAkC;CAClE,IAAI,UAAU,aAAa,UAAU,YAAY,UAAU,UACzD,MAAM,IAAI,MAAM,gCAAgC,KAAK,IAAI,OAAO;CAElE,OAAO;AACT;AAEA,MAAa,eAA0C,WAIjD;CACJ,MAAM,eAAyC;EAC7C,IAAI,OAAO,cAAc,aAAa,OAAO;EAC7C,IAAI,OAAO,WAAW,KAAA,GAAW,OAAO,OAAO,OAAO,SAAS;EAE/D,OADY,UAAU,eACR;CAChB;CAEA,OAAO,OAAO,KAWZ,OAAO,MAAM;EACb,OAAO;EACP,SAAS;EACT,MAAM,OAAO,OAAO,EAAE,OAAO,OAAO,MAAM,OAAO,MAAM,EAAE,CAAC;EAC1D,aAAa,EAAE,OAAO;GAAE,QAAQ,CAAC;GAAG,WAAW;EAAK,EAAE;EACtD,SAAS,OAAO,YACd,mBAAmB,MAEjB,SAAS;GACT,WAAW,EAAE,cAAc,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ,EAAE,GAAG,OAAO;GAAE,EAAE;GACxE,UAAU,EAAE,MAAM,aAAa,EAC7B,OAAO;IAAE,GAAG;IAAO,QAAQ;KAAE,GAAG,MAAM;MAAS,OAAO;IAAM;GAAE,EAChE;GACA,gBAAgB,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ,CAAC;GAAE,EAAE;GAClD,SAAS,EAAE,eAAe,EAAE,OAAO;IAAE,GAAG;IAAO,WAAW;GAAQ,EAAE;EACtE,CAAC;EACH,YAAW,SACT,gBAAgB,KAA2C,CAAC,EAAC,WAAU,EACrE,OAAO,MAAM,OAAO,OAAO,OAAO,MAAM,OAAO,MAAM,CAAC,GAAG;GACvD,UAAU;GACV,gCAAgC,OAAO,KAAK,CAAC,GAAG,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC;GACpE,UAAU,UACR,OAAO,IAAI,aAAa;IACtB,MAAM,MAAM,OAAO;IACnB,IAAI,QAAQ,MAAM,MAAM,IAAI,MAAM,6BAA6B;IAC/D,MAAM,SAAS,OAAO,MAAM,UAA8B;IAC1D,MAAM,2BAAW,IAAI,IAAoC;IAEzD,IAAI,OAAsB,QAAQ,QAAQ;IAC1C,MAAM,SAAS,YAAgC;KAC7C,MAAM,YACJ,OAAO,WAAW,MAAM,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,WACxC,KAAA,SACA,KAAA,CACR;KACF,OAAO,KAAK,KAAK,KAAK,GAAG;IAC3B;IAGA,MAAM,UAA6E,CAAC;IACpF,KAAK,MAAM,QAAQ,OAAO;KACxB,MAAM,SAAS,OAAO,OAAO,WAAW;MACtC,WAAW,IAAI,MAAM,EAAE,KAAK,CAAC;MAC7B,QAAQ,UAAmB;KAC7B,CAAC;KACD,QAAQ,KAAK;MAAC;MAAM;MAAQ,UAAU,OAAO,OAAO,IAAI;KAAC,CAAU;IACrE;IACA,MAAM,WAA4C,CAAC;IACnD,KAAK,MAAM,CAAC,MAAM,QAAQ,UAAU,SAAS;KAC3C,OAAO,iBAAiB;MACtB,IAAI;OACF,MACE,mBAAmB,QAAQ;QAAE;QAAM,OAAO,UAAU,OAAO,OAAO,IAAI;OAAE,CAAC,CAC3E;MACF,QAAQ;OAGN,MACE,mBAAmB,OAAO,EACxB,SAAS,gCAAgC,OAC3C,CAAC,CACH;MACF;KACF;KACA,SAAS,IAAI,MAAM,MAAM;KACzB,SAAS,QAAQ;IACnB;IACA,OAAO;KAAE;KAAU;KAAU;IAAO;GACtC,CAAC;GAGH,aAAa,EAAE,eAAe,mBAAmB,SAAS,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,CAAC;GACrF,kBAAkB,mBAAmB,QAAQ;GAC7C,iBAAgB,UAAS,mBAAmB,OAAO,EAAE,SAAS,YAAY,KAAK,EAAE,CAAC;GAClF,UAAU,EAAE,UAAU,aACpB,OAAO,OACL,OAAO,QACL,OAAO,WAAW;IAChB,KAAK,MAAM,UAAU,SAAS,OAAO,GAAG,OAAO,WAAW;GAC5D,CAAC,GACD,MAAM,SAAS,MAAM,CACvB,CACF;EACJ,CAAC,EACH,EAAE;EACJ,qBAKE,aAAa,KAAyD,CAAC,QAAQ,EAG7E,SAAS,aAAa,WACpB,OAAO,OACL,OAAO,YAAY,MAAM,KAAK;GAE5B,iBAAiB,OAAO,QAAQ,OAAO,KAAK;GAC5C,YAAY,EAAE,aAAa,OAAO,QAAQ,OAAO,UAAU,MAAM,CAAC;EACpE,CAAC,CACH,CACF,EACF,EAAE;CACN,CAAC;AACH"}
@@ -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,WAAQ,iBAAmB,QAAQ;;;qBCXnC,OAAK,OAAA;;;;YAIN,eAAe,MAAM;qBA2BpB,YAAS,eACP;WAED;WAAyB;;qBAoC1B,WAAS,MAAA;;;;;;;;;qBCvET,yCAAa;;;sBAAb,OAAA;;;YAID,uBAAuB,cAAc;;qBASpC,aAAU,gBAAkB,QAAQ;;;qBCbpC,WAAS,OAAA;;;;;YAKV,mBAAmB,UAAU;qBAE5B,wCAAY;;;;sBAAZ,OAAA;;;YAKD,sBAAsB,aAAa;;qBAUlC,QAAK,MAAU,cAAY,QAAQ"}
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"}
@@ -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,cAAW,iBAAmB,OAAS;;;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"}
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 };
@@ -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. Desktop-mode iPads report `mac` — their UA hides the tablet;
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
- export declare const platformFromUA: (userAgent: string) => Platform;
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,gBAAa,eACX,aACA,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;;;;;;;;;;;;;;;;qBClDpB;YACD,mBAAmB;qBAElB,iBAAc,sBAAwB;;qBAWtC;;qBAGA"}
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"}
@@ -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. Desktop-mode iPads report `mac` — their UA hides the tablet;
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 platformFromUA = (userAgent) => {
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
- if (ua.includes("android")) return "android";
114
- if (ua.includes("iphone") || ua.includes("ipad") || ua.includes("ipod")) return "ios";
115
- if (ua.includes("windows") || ua.includes("win32") || ua.includes("win64")) return "windows";
116
- if (ua.includes("macintosh") || ua.includes("mac os")) return "mac";
117
- if (ua.includes("linux")) return "linux";
118
- return "unknown";
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";
@@ -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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"}
@@ -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,YAAS,OAAW;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"}
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"}
@@ -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, create?: ChannelFactory | undefined) => Stream.Stream<BroadcastMessage>;
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;
@@ -104,14 +106,14 @@ export declare const sse: <const Name extends string>(config: {
104
106
  readonly status: "closed" | "connecting" | "open";
105
107
  readonly lastError: string | null;
106
108
  }, {
109
+ readonly _tag: "Received";
110
+ readonly data: string;
111
+ } | {
107
112
  readonly _tag: "Connecting";
108
113
  } | {
109
114
  readonly _tag: "Opened";
110
115
  } | {
111
116
  readonly _tag: "Closed";
112
- } | {
113
- readonly _tag: "Received";
114
- readonly data: string;
115
117
  } | {
116
118
  readonly _tag: "Failed";
117
119
  readonly message: string;
@@ -174,14 +176,14 @@ 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";
183
182
  } | {
184
183
  readonly _tag: "Closed";
184
+ } | {
185
+ readonly _tag: "Received";
186
+ readonly data: string;
185
187
  } | {
186
188
  readonly _tag: "Failed";
187
189
  readonly message: string;
@@ -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,oBAAiB,cAChB,SAEH,+BACR,OAAO,OAAO;;;;;;;qBAyBJ,gBAAa,cACZ,eACC,SACJ,+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,qBAAmB;WAClC,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,qBAAmB;WACxC,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"}
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"}
@@ -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
- total: number | null;
99
- page: number;
100
- perPage: number;
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,OAAK,OAAS,OAAO,MAAM,oBAAe,OAAA;;;;;YAM3D,aAAa;WACd,MAAM,cAAc;WACpB,SAAS;WACT,QAAQ,cAAc;;qBAGpB,gBAAiB,qBAAqB,OAAK;WAC7C,MAAM;WACN,OAAO,OAAO,MAAM;WACpB;MACV,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;qBA+DY,UAAW,OAAK,OAAS,aAAa;;qBAGtC,UAAW,OAAK,OAAS,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,YAAS,OAAW;;qBAIpB,SAAM,OAAW;qBAEjB,YAAU,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBChCV,QAAK,eAAiB,aAAa,kBAAa;;;qBCEhD,mBAAiB,OAAA;;;YAClB,2BAA2B,kBAAkB;qBAE5C,+CAAmB;;;;;;;;;;;;;;;YAOpB,6BAA6B,oBAAoB;;qBAMhD,aAAU,OAAW,mBAAiB;qBAGtC,cAAY,OAAA;;;;;;;;;;;;;;;;;;;iBCRR;;WAEN;;WAEA;;;iBAIM;WACN;WACA;WACA;WACA;;;qBAkBE,cAAW,MAChB,uBAAqB,SAClB,SAAS,yBAAuB,QACjC;;qBAWG,YAAS,eACP,MACP,uBAAqB,SAClB,SAAS,yBAAuB,QACjC;;;;;;;;qBAiBG,eAAY,MACjB,uBAAqB,SAClB,SAAS,yBAAuB,QACjC,eAAa,mBACJ,wBACK,qBAErB;qBA4BU,cAAY,OAAA;;;;;;;;;;;YAWb,sBAAsB,aAAa;qBAElC,0CAAc;;;;;;;;;;;;;;;;;;;YAUf,wBAAwB,eAAe;;;;;;;qBAetC,YAAS,OACb,cAAY,MACb,uBAAqB,2BAE1B;WAA2B;;;;;;;;qBAmBjB,UAAO,OACX,cAAY,MACb,uBAAqB,wBACL;qBAaX,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,eAAY,UACb;WAAyB;WAAsB;IAAgB;iBAc1D;WACN;WACA;WACA;WACA;WACA;WACA;;;;;;;;;qBAUE,UAAO,MACZ,uBAAqB,SAClB,SAAS,yBAAuB;WAE9B;WACA;WACA;WACA;;WAEC,YAAY,cAAc;WAA4B"}
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"}
@@ -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
- const total = totalHeight(keys, model.heights, layoutOf(model));
327
- if (total <= 0) return true;
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
@@ -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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"}
@@ -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,OAAK;WAC9C,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,qBAAkB,MAAU,MAAI,IAAM,MAAI;;;qBCM1C,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"}
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.0",
3
+ "version": "0.2.0",
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.1.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": "^5.7.2",
89
+ "typescript": "5.9.3",
90
90
  "ws": "^8.18.0"
91
91
  },
92
92
  "scripts": {