foldkit-primitives 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -1
- package/dist/device/index.d.mts +69 -36
- package/dist/device/index.d.mts.map +1 -1
- package/dist/device/index.mjs +34 -34
- package/dist/device/index.mjs.map +1 -1
- package/dist/dom/index.d.mts +68 -19
- package/dist/dom/index.d.mts.map +1 -1
- package/dist/dom/index.mjs +32 -25
- package/dist/dom/index.mjs.map +1 -1
- package/dist/{editable-text-BbsPOATY.d.mts → editable-text-Dvm-Fuqs.d.mts} +65 -3
- package/dist/editable-text-Dvm-Fuqs.d.mts.map +1 -0
- package/dist/events/index.d.mts +52 -31
- package/dist/events/index.d.mts.map +1 -1
- package/dist/events/index.mjs +40 -28
- package/dist/events/index.mjs.map +1 -1
- package/dist/{follow-tab-stop-BDxyoUPQ.mjs → follow-tab-stop-Ci1tqAIW.mjs} +63 -61
- package/dist/follow-tab-stop-Ci1tqAIW.mjs.map +1 -0
- package/dist/{index-Cppf-lBx.d.mts → index-CmmxqSEi.d.mts} +34 -15
- package/dist/index-CmmxqSEi.d.mts.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -3
- package/dist/{input-modality-m6v7__Xs.mjs → input-modality-BXD383jg.mjs} +9 -9
- package/dist/input-modality-BXD383jg.mjs.map +1 -0
- package/dist/{input-modality-DGOhAknO.d.mts → input-modality-BY-bSrEc.d.mts} +9 -2
- package/dist/input-modality-BY-bSrEc.d.mts.map +1 -0
- package/dist/interaction/index.d.mts +118 -118
- package/dist/interaction/index.d.mts.map +1 -1
- package/dist/interaction/index.mjs +3 -3
- package/dist/interaction/index.mjs.map +1 -1
- package/dist/media/index.d.mts +24 -3
- package/dist/media/index.d.mts.map +1 -1
- package/dist/media/index.mjs +23 -13
- package/dist/media/index.mjs.map +1 -1
- package/dist/motion/index.d.mts +1 -1
- package/dist/motion/index.mjs +1 -2
- package/dist/{motion-DGGBZ0wt.mjs → motion-DmFDppRn.mjs} +20 -20
- package/dist/{motion-DGGBZ0wt.mjs.map → motion-DmFDppRn.mjs.map} +1 -1
- package/dist/net/index.d.mts +63 -25
- package/dist/net/index.d.mts.map +1 -1
- package/dist/net/index.mjs +31 -31
- package/dist/net/index.mjs.map +1 -1
- package/dist/observers/index.d.mts +25 -0
- package/dist/observers/index.d.mts.map +1 -1
- package/dist/observers/index.mjs +22 -22
- package/dist/observers/index.mjs.map +1 -1
- package/dist/state/index.d.mts +98 -44
- package/dist/state/index.d.mts.map +1 -1
- package/dist/state/index.mjs +39 -39
- package/dist/state/index.mjs.map +1 -1
- package/dist/time/index.d.mts +35 -5
- package/dist/time/index.d.mts.map +1 -1
- package/dist/time/index.mjs +20 -20
- package/dist/time/index.mjs.map +1 -1
- package/package.json +14 -14
- package/dist/editable-text-BbsPOATY.d.mts.map +0 -1
- package/dist/follow-tab-stop-BDxyoUPQ.mjs.map +0 -1
- package/dist/index-Cppf-lBx.d.mts.map +0 -1
- package/dist/input-modality-DGOhAknO.d.mts.map +0 -1
- package/dist/input-modality-m6v7__Xs.mjs.map +0 -1
package/dist/media/index.d.mts
CHANGED
|
@@ -8,11 +8,22 @@ export declare const BreakpointsModel: Schema.Struct<{
|
|
|
8
8
|
export type BreakpointsModel = typeof BreakpointsModel.Type;
|
|
9
9
|
export declare const BreakpointsMessage: import("foldkit/message").MessageUnion<{
|
|
10
10
|
readonly Changed: {
|
|
11
|
-
|
|
11
|
+
width: Schema.Number;
|
|
12
12
|
};
|
|
13
13
|
}>;
|
|
14
14
|
export type BreakpointsMessage = typeof BreakpointsMessage.Type;
|
|
15
|
+
/**
|
|
16
|
+
* The breakpoint a width falls in: the name with the largest threshold at or
|
|
17
|
+
* below it, or `null` below every threshold. `Breakpoints` uses it; a view that
|
|
18
|
+
* has a width of its own can too.
|
|
19
|
+
*/
|
|
15
20
|
export declare const breakpointFor: (width: number, breakpoints: Readonly<Record<string, number>>) => string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Named breakpoints over the viewport width: one `resize` listener, derived
|
|
23
|
+
* names in `update`. Thresholds are mobile-first min-widths: the breakpoint
|
|
24
|
+
* is the largest name whose threshold is <= width. Ties break
|
|
25
|
+
* alphabetically so duplicates stay deterministic.
|
|
26
|
+
*/
|
|
16
27
|
export declare const Breakpoints: Bundle.Bundle<"Breakpoints", {
|
|
17
28
|
readonly breakpoints: {
|
|
18
29
|
readonly [x: string]: number;
|
|
@@ -32,10 +43,15 @@ export declare const MediaQueryModel: Schema.Struct<{
|
|
|
32
43
|
export type MediaQueryModel = typeof MediaQueryModel.Type;
|
|
33
44
|
export declare const MediaQueryMessage: import("foldkit/message").MessageUnion<{
|
|
34
45
|
readonly Changed: {
|
|
35
|
-
|
|
46
|
+
matches: Schema.Boolean;
|
|
36
47
|
};
|
|
37
48
|
}>;
|
|
38
49
|
export type MediaQueryMessage = typeof MediaQueryMessage.Type;
|
|
50
|
+
/**
|
|
51
|
+
* A media query as a bundle: `matches` in the Model, kept current by a
|
|
52
|
+
* `matchMedia` change stream. State stays in the parent Model, so it replays
|
|
53
|
+
* and time-travels like any other state; the browser only reports facts.
|
|
54
|
+
*/
|
|
39
55
|
export declare const MediaQuery: Bundle.Bundle<"MediaQuery", {
|
|
40
56
|
readonly query: string;
|
|
41
57
|
}, {
|
|
@@ -67,7 +83,7 @@ export declare const PrefersReducedMotion: Bundle.Bundle<"MediaQuery", void, {
|
|
|
67
83
|
* checks come first. `isBrowser`/`isServer` split SSR from client for init
|
|
68
84
|
* defaults.
|
|
69
85
|
*/
|
|
70
|
-
export declare const Platform: readonly [
|
|
86
|
+
export declare const Platform: readonly ['mac', 'windows', 'linux', 'android', 'ios', 'unknown'];
|
|
71
87
|
export type Platform = (typeof Platform)[number];
|
|
72
88
|
/**
|
|
73
89
|
* Client Hints (`navigator.userAgentData`) plus touch points
|
|
@@ -79,6 +95,11 @@ export interface PlatformHints {
|
|
|
79
95
|
readonly platform?: string | undefined;
|
|
80
96
|
readonly touchPoints?: number | undefined;
|
|
81
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The platform a user-agent string names, preferring Client Hints when given
|
|
100
|
+
* (`navigator.userAgentData.platform`), with touch points telling a desktop-mode
|
|
101
|
+
* iPad from a Mac. `unknown` when nothing matches.
|
|
102
|
+
*/
|
|
82
103
|
export declare const platformFromUA: (userAgent: string, hints?: PlatformHints) => Platform;
|
|
83
104
|
/** Whether a window exists: the client side of an SSR split. */
|
|
84
105
|
export declare const isBrowser: () => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/media/breakpoints.ts","../../src/media/mediaquery.ts","../../src/media/platform.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/media/breakpoints.ts","../../src/media/mediaquery.ts","../../src/media/platform.ts"],"mappings":";;;qBAKa,kBAAgB,OAAA;;;;YAIjB,0BAA0B,iBAAiB;qBAE1C,8CAAkB;;IAAmC,OAAK,OAAA;;;YAC3D,4BAA4B,mBAAmB;;;;;;qBAO9C,gBAAa,eACX,aACA,SAAS;;;;;;;qBA+CX,aAAW,OAAA;;;;;;;;;;;;;qBC/DX,iBAAe,OAAA;oBAAf,OAAA;;YACD,yBAAyB,gBAAgB;qBAExC,6CAAiB;;IAAmC,SAAO,OAAA;;;YAC5D,2BAA2B,kBAAkB;;;;;;qBAiC5C,YAAU,OAAA;;;;;;;;;qBAcV,aAAW,OAAA;;;;;;;qBAGX,sBAAoB,OAAA;;;;;;;;;;;;;;;qBCpDpB;YACD,mBAAmB;;;;;;;iBAQd;WACN;WACA;;;;;;;qBAyBE,iBAAc,mBAAqB,QAAS,kBAAqB;;qBAkBjE;;qBAGA"}
|
package/dist/media/index.mjs
CHANGED
|
@@ -3,17 +3,16 @@ import { defineMessageUnion } from "foldkit/message";
|
|
|
3
3
|
import * as Subscription from "foldkit/subscription";
|
|
4
4
|
import { Bundle } from "foldkit-bundle";
|
|
5
5
|
//#region src/media/breakpoints.ts
|
|
6
|
-
/**
|
|
7
|
-
* Named breakpoints over the viewport width: one `resize` listener, derived
|
|
8
|
-
* names in `update`. Thresholds are mobile-first min-widths: the breakpoint
|
|
9
|
-
* is the largest name whose threshold is <= width. Ties break
|
|
10
|
-
* alphabetically so duplicates stay deterministic.
|
|
11
|
-
*/
|
|
12
6
|
const BreakpointsModel = Schema.Struct({
|
|
13
7
|
width: Schema.Number,
|
|
14
8
|
breakpoint: Schema.NullOr(Schema.String)
|
|
15
9
|
});
|
|
16
10
|
const BreakpointsMessage = defineMessageUnion({ Changed: { width: Schema.Number } });
|
|
11
|
+
/**
|
|
12
|
+
* The breakpoint a width falls in: the name with the largest threshold at or
|
|
13
|
+
* below it, or `null` below every threshold. `Breakpoints` uses it; a view that
|
|
14
|
+
* has a width of its own can too.
|
|
15
|
+
*/
|
|
17
16
|
const breakpointFor = (width, breakpoints) => {
|
|
18
17
|
let best = null;
|
|
19
18
|
let bestThreshold = -Infinity;
|
|
@@ -40,6 +39,12 @@ const widthStream = () => {
|
|
|
40
39
|
return Stream.concat(Stream.make(BreakpointsMessage.Changed({ width: window.innerWidth })), Stream.fromQueue(queue));
|
|
41
40
|
}));
|
|
42
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Named breakpoints over the viewport width: one `resize` listener, derived
|
|
44
|
+
* names in `update`. Thresholds are mobile-first min-widths: the breakpoint
|
|
45
|
+
* is the largest name whose threshold is <= width. Ties break
|
|
46
|
+
* alphabetically so duplicates stay deterministic.
|
|
47
|
+
*/
|
|
43
48
|
const Breakpoints = Bundle.make("Breakpoints", {
|
|
44
49
|
Model: BreakpointsModel,
|
|
45
50
|
Message: BreakpointsMessage,
|
|
@@ -52,15 +57,10 @@ const Breakpoints = Bundle.make("Breakpoints", {
|
|
|
52
57
|
width,
|
|
53
58
|
breakpoint: breakpointFor(width, args.breakpoints)
|
|
54
59
|
} } }),
|
|
55
|
-
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.
|
|
60
|
+
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistentEntry(widthStream()) }))
|
|
56
61
|
});
|
|
57
62
|
//#endregion
|
|
58
63
|
//#region src/media/mediaquery.ts
|
|
59
|
-
/**
|
|
60
|
-
* A media query as a bundle: `matches` in the Model, kept current by a
|
|
61
|
-
* `matchMedia` change stream. State stays in the parent Model, so it replays
|
|
62
|
-
* and time-travels like any other state; the browser only reports facts.
|
|
63
|
-
*/
|
|
64
64
|
const MediaQueryModel = Schema.Struct({ matches: Schema.Boolean });
|
|
65
65
|
const MediaQueryMessage = defineMessageUnion({ Changed: { matches: Schema.Boolean } });
|
|
66
66
|
const matchMediaStream = (query) => {
|
|
@@ -78,13 +78,18 @@ const matchMediaStream = (query) => {
|
|
|
78
78
|
return Stream.concat(Stream.make(MediaQueryMessage.Changed({ matches: list.matches })), Stream.fromQueue(queue));
|
|
79
79
|
}));
|
|
80
80
|
};
|
|
81
|
+
/**
|
|
82
|
+
* A media query as a bundle: `matches` in the Model, kept current by a
|
|
83
|
+
* `matchMedia` change stream. State stays in the parent Model, so it replays
|
|
84
|
+
* and time-travels like any other state; the browser only reports facts.
|
|
85
|
+
*/
|
|
81
86
|
const MediaQuery = Bundle.make("MediaQuery", {
|
|
82
87
|
Model: MediaQueryModel,
|
|
83
88
|
Message: MediaQueryMessage,
|
|
84
89
|
args: Schema.Struct({ query: Schema.String }),
|
|
85
90
|
init: () => ({ model: { matches: false } }),
|
|
86
91
|
update: (model, message) => message.matches === model.matches ? { model } : { model: { matches: message.matches } },
|
|
87
|
-
subscriptions: ({ query }) => Subscription.make()(() => ({ changes: Subscription.
|
|
92
|
+
subscriptions: ({ query }) => Subscription.make()(() => ({ changes: Subscription.persistentEntry(matchMediaStream(query)) }))
|
|
88
93
|
});
|
|
89
94
|
/** `(prefers-color-scheme: dark)`, bound so placements give no args. */
|
|
90
95
|
const PrefersDark = MediaQuery.with({ query: "(prefers-color-scheme: dark)" });
|
|
@@ -117,6 +122,11 @@ const hintedPlatform = (platform) => {
|
|
|
117
122
|
default: return null;
|
|
118
123
|
}
|
|
119
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* The platform a user-agent string names, preferring Client Hints when given
|
|
127
|
+
* (`navigator.userAgentData.platform`), with touch points telling a desktop-mode
|
|
128
|
+
* iPad from a Mac. `unknown` when nothing matches.
|
|
129
|
+
*/
|
|
120
130
|
const platformFromUA = (userAgent, hints = {}) => {
|
|
121
131
|
const hinted = hints.platform === void 0 ? null : hintedPlatform(hints.platform);
|
|
122
132
|
const ua = userAgent.toLowerCase();
|
package/dist/media/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/media/breakpoints.ts","../../src/media/mediaquery.ts","../../src/media/platform.ts"],"sourcesContent":["/**\n * Named breakpoints over the viewport width: one `resize` listener, derived\n * names in `update`. Thresholds are mobile-first min-widths: the breakpoint\n * is the largest name whose threshold is <= width. Ties break\n * alphabetically so duplicates stay deterministic.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const BreakpointsModel = Schema.Struct({\n width: Schema.Number,\n breakpoint: Schema.NullOr(Schema.String),\n})\nexport type BreakpointsModel = typeof BreakpointsModel.Type\n\nexport const BreakpointsMessage = defineMessageUnion({ Changed: { width: Schema.Number } })\nexport type BreakpointsMessage = typeof BreakpointsMessage.Type\n\nexport const breakpointFor = (\n width: number,\n breakpoints: Readonly<Record<string, number>>,\n): string | null => {\n let best: string | null = null\n let bestThreshold = -Infinity\n for (const name of Object.keys(breakpoints)) {\n const threshold = breakpoints[name]!\n if (\n threshold <= width &&\n (threshold > bestThreshold || (threshold === bestThreshold && (best === null || name > best)))\n ) {\n best = name\n bestThreshold = threshold\n }\n }\n return best\n}\n\nconst widthStream = (): Stream.Stream<BreakpointsMessage> => {\n if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {\n return Stream.empty\n }\n return Stream.unwrap(\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<BreakpointsMessage>()\n const onResize = () => {\n Effect.runFork(Queue.offer(queue, BreakpointsMessage.Changed({ width: window.innerWidth })))\n }\n window.addEventListener('resize', onResize)\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n window.removeEventListener('resize', onResize)\n }),\n )\n return Stream.concat(\n Stream.make(BreakpointsMessage.Changed({ width: window.innerWidth })),\n Stream.fromQueue(queue),\n )\n }),\n )\n}\n\nexport const Breakpoints = Bundle.make('Breakpoints', {\n Model: BreakpointsModel,\n Message: BreakpointsMessage,\n args: Schema.Struct({\n breakpoints: Schema.Record(Schema.String, Schema.Number.pipe(Schema.check(Schema.isFinite()))),\n }),\n init: args => ({\n model: { width: 0, breakpoint: breakpointFor(0, args.breakpoints) },\n }),\n update: (model, message, args) =>\n BreakpointsMessage.match(message, {\n // A height-only resize reports the same width.\n Changed: ({ width }) =>\n width === model.width\n ? { model }\n : { model: { width, breakpoint: breakpointFor(width, args.breakpoints) } },\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) =>\n message.matches === model.matches ? { model } : { 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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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/media/breakpoints.ts","../../src/media/mediaquery.ts","../../src/media/platform.ts"],"sourcesContent":["import { 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\n/**\n * The breakpoint a width falls in: the name with the largest threshold at or\n * below it, or `null` below every threshold. `Breakpoints` uses it; a view that\n * has a width of its own can too.\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\n/**\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 */\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 // A height-only resize reports the same width.\n Changed: ({ width }) =>\n width === model.width\n ? { model }\n : { model: { width, breakpoint: breakpointFor(width, args.breakpoints) } },\n }),\n subscriptions: (): Subscription.Subscriptions<BreakpointsModel, BreakpointsMessage> =>\n Subscription.make<BreakpointsModel, BreakpointsMessage>()(() => ({\n changes: Subscription.persistentEntry(widthStream()),\n })),\n})\n","import { 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\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 */\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) =>\n message.matches === model.matches ? { model } : { model: { matches: message.matches } },\n subscriptions: ({ query }): Subscription.Subscriptions<MediaQueryModel, MediaQueryMessage> =>\n Subscription.make<MediaQueryModel, MediaQueryMessage>()(() => ({\n changes: Subscription.persistentEntry(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\n/**\n * The platform a user-agent string names, preferring Client Hints when given\n * (`navigator.userAgentData.platform`), with touch points telling a desktop-mode\n * iPad from a Mac. `unknown` when nothing matches.\n */\nexport const platformFromUA = (userAgent: string, hints: PlatformHints = {}): Platform => {\n const hinted = hints.platform === undefined ? null : hintedPlatform(hints.platform)\n const ua = userAgent.toLowerCase()\n let sniffed: Platform = 'unknown'\n if (ua.includes('android')) sniffed = 'android'\n else if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) sniffed = 'ios'\n else if (ua.includes('windows') || ua.includes('win32') || ua.includes('win64')) {\n sniffed = 'windows'\n } else if (ua.includes('macintosh') || ua.includes('mac os')) sniffed = 'mac'\n else if (ua.includes('linux')) sniffed = 'linux'\n const candidate = hinted ?? sniffed\n // Desktop-mode iPads report \"macOS\" with a tablet touchscreen: only a\n // multi-touch Mac UA is really an iPad. Touchscreen Windows and Linux\n // machines keep their platform.\n return candidate === 'mac' && (hints.touchPoints ?? 0) > 1 ? 'ios' : candidate\n}\n\n/** Whether a window exists: the client side of an SSR split. */\nexport const isBrowser = (): boolean => typeof window !== 'undefined'\n\n/** Whether no window exists: the server side of an SSR split. */\nexport const isServer = (): boolean => !isBrowser()\n"],"mappings":"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"}
|
package/dist/motion/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SpringMessage, c as PresenceMessage, d as motion_d_exports, i as Spring, l as PresenceModel, n as TweenMessage, o as SpringModel, r as TweenModel, s as Presence, t as Tween, u as isVisible } from "../index-
|
|
1
|
+
import { a as SpringMessage, c as PresenceMessage, d as motion_d_exports, i as Spring, l as PresenceModel, n as TweenMessage, o as SpringModel, r as TweenModel, s as Presence, t as Tween, u as isVisible } from "../index-CmmxqSEi.mjs";
|
|
2
2
|
export { motion_d_exports as Motion, Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible };
|
package/dist/motion/index.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { motion_exports as Motion, Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible };
|
|
1
|
+
export { d as Motion, s as Presence, c as PresenceMessage, l as PresenceModel, i as Spring, a as SpringMessage, o as SpringModel, t as Tween, n as TweenMessage, r as TweenModel, u as isVisible } from "../motion-DmFDppRn.mjs";
|
|
@@ -34,12 +34,6 @@ const reducedMotion = Effect.flatMap(Effect.serviceOption(Motion), Option.match(
|
|
|
34
34
|
}));
|
|
35
35
|
//#endregion
|
|
36
36
|
//#region src/motion/presence.ts
|
|
37
|
-
/**
|
|
38
|
-
* Presence as a bundle: mount-transition state for exit animations. `Hide`
|
|
39
|
-
* starts a timed `hiding` phase; the `Hidden` fact it yields carries the
|
|
40
|
-
* generation it was scheduled under, so a `Show` in between wins and the
|
|
41
|
-
* late fact is ignored. The clock is Effect's, so tests drive it.
|
|
42
|
-
*/
|
|
43
37
|
const PresenceModel = Schema.Struct({
|
|
44
38
|
phase: Schema.Literals([
|
|
45
39
|
"shown",
|
|
@@ -55,6 +49,12 @@ const PresenceMessage = defineMessageUnion({
|
|
|
55
49
|
});
|
|
56
50
|
/** Whether the content renders: shown or mid-exit. */
|
|
57
51
|
const isVisible = (model) => model.phase !== "hidden";
|
|
52
|
+
/**
|
|
53
|
+
* Presence as a bundle: mount-transition state for exit animations. `Hide`
|
|
54
|
+
* starts a timed `hiding` phase; the `Hidden` fact it yields carries the
|
|
55
|
+
* generation it was scheduled under, so a `Show` in between wins and the
|
|
56
|
+
* late fact is ignored. The clock is Effect's, so tests drive it.
|
|
57
|
+
*/
|
|
58
58
|
const Presence = Bundle.make("Presence", {
|
|
59
59
|
Model: PresenceModel,
|
|
60
60
|
Message: PresenceMessage,
|
|
@@ -93,13 +93,6 @@ const Presence = Bundle.make("Presence", {
|
|
|
93
93
|
});
|
|
94
94
|
//#endregion
|
|
95
95
|
//#region src/motion/spring.ts
|
|
96
|
-
/**
|
|
97
|
-
* A spring as a bundle: a number pulled toward a target with stiffness and
|
|
98
|
-
* damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the
|
|
99
|
-
* trajectory is deterministic under TestClock; the stream ends with
|
|
100
|
-
* Finished, and the value rests exactly at `to`. Underdamped springs
|
|
101
|
-
* overshoot — that is the point — but the rest value is still exact.
|
|
102
|
-
*/
|
|
103
96
|
const SpringModel = Schema.Struct({
|
|
104
97
|
value: Schema.Number,
|
|
105
98
|
velocity: Schema.Number,
|
|
@@ -118,6 +111,13 @@ const SpringMessage = defineMessageUnion({
|
|
|
118
111
|
const step = 16;
|
|
119
112
|
const epsilon = .001;
|
|
120
113
|
const settled = (point, to) => Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon;
|
|
114
|
+
/**
|
|
115
|
+
* A spring as a bundle: a number pulled toward a target with stiffness and
|
|
116
|
+
* damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the
|
|
117
|
+
* trajectory is deterministic under TestClock; the stream ends with
|
|
118
|
+
* Finished, and the value rests exactly at `to`. Underdamped springs
|
|
119
|
+
* overshoot — that is the point — but the rest value is still exact.
|
|
120
|
+
*/
|
|
121
121
|
const Spring = Bundle.make("Spring", {
|
|
122
122
|
Model: SpringModel,
|
|
123
123
|
Message: SpringMessage,
|
|
@@ -178,12 +178,6 @@ const Spring = Bundle.make("Spring", {
|
|
|
178
178
|
});
|
|
179
179
|
//#endregion
|
|
180
180
|
//#region src/motion/tween.ts
|
|
181
|
-
/**
|
|
182
|
-
* A tween as a bundle: a number animated from one value to another over a
|
|
183
|
-
* duration, in the Model. Progress comes from Effect's clock, so tests drive
|
|
184
|
-
* it with TestClock; the stream ends with Finished, and the value rests at
|
|
185
|
-
* `to`. Linear interpolation only: easing curves stay the application's job.
|
|
186
|
-
*/
|
|
187
181
|
const TweenModel = Schema.Struct({
|
|
188
182
|
value: Schema.Number,
|
|
189
183
|
running: Schema.Boolean
|
|
@@ -195,6 +189,12 @@ const TweenMessage = defineMessageUnion({
|
|
|
195
189
|
/** Carries the exact end value: the stream may stop on either side of it. */
|
|
196
190
|
Finished: { value: Schema.Number }
|
|
197
191
|
});
|
|
192
|
+
/**
|
|
193
|
+
* A tween as a bundle: a number animated from one value to another over a
|
|
194
|
+
* duration, in the Model. Progress comes from Effect's clock, so tests drive
|
|
195
|
+
* it with TestClock; the stream ends with Finished, and the value rests at
|
|
196
|
+
* `to`. Linear interpolation only: easing curves stay the application's job.
|
|
197
|
+
*/
|
|
198
198
|
const Tween = Bundle.make("Tween", {
|
|
199
199
|
Model: TweenModel,
|
|
200
200
|
Message: TweenMessage,
|
|
@@ -237,4 +237,4 @@ const Tween = Bundle.make("Tween", {
|
|
|
237
237
|
//#endregion
|
|
238
238
|
export { SpringMessage as a, PresenceMessage as c, motion_exports as d, Spring as i, PresenceModel as l, TweenMessage as n, SpringModel as o, TweenModel as r, Presence as s, Tween as t, isVisible as u };
|
|
239
239
|
|
|
240
|
-
//# sourceMappingURL=motion-
|
|
240
|
+
//# sourceMappingURL=motion-DmFDppRn.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion-DGGBZ0wt.mjs","names":[],"sources":["../src/motion/motion.ts","../src/motion/presence.ts","../src/motion/spring.ts","../src/motion/tween.ts"],"sourcesContent":["/**\n * Whether motion should be reduced, as a service read when a transition\n * starts, not sniffed once. Absent, motion is full, so a placement that\n * provides nothing behaves as before; a page provides `live` (the user's\n * `prefers-reduced-motion`) and a test provides `reduced` or `full`. Under\n * reduced motion a presence exits at once and a tween or spring jumps to its\n * end, in the same Messages, so the Model sees the same transitions.\n */\nimport { Context, Effect, Layer, Option } from 'effect'\n\nexport interface MotionShape {\n readonly reduced: Effect.Effect<boolean>\n}\n\nexport class Motion extends Context.Service<Motion, MotionShape>()('foldkit-primitives/Motion') {}\n\n/** Reduced or full motion, fixed: for a test or a setting the application owns. */\nexport const layer = (reduced: boolean): Layer.Layer<Motion> =>\n Layer.succeed(Motion, { reduced: Effect.succeed(reduced) })\n\nexport const reduced: Layer.Layer<Motion> = layer(true)\nexport const full: Layer.Layer<Motion> = layer(false)\n\n/** The user's `prefers-reduced-motion`, read each time a transition starts. */\nexport const live: Layer.Layer<Motion> = Layer.succeed(Motion, {\n reduced: Effect.sync(\n () =>\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches,\n ),\n})\n\n/** Whether to reduce motion now: the service's answer, or `false` without one. */\nexport const reducedMotion: Effect.Effect<boolean> = Effect.flatMap(\n Effect.serviceOption(Motion),\n Option.match({ onNone: () => Effect.succeed(false), onSome: motion => motion.reduced }),\n)\n","/**\n * Presence as a bundle: mount-transition state for exit animations. `Hide`\n * starts a timed `hiding` phase; the `Hidden` fact it yields carries the\n * generation it was scheduled under, so a `Show` in between wins and the\n * late fact is ignored. The clock is Effect's, so tests drive it.\n */\nimport { Effect, Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const PresenceModel = Schema.Struct({\n phase: Schema.Literals(['shown', 'hiding', 'hidden']),\n generation: Schema.Number,\n})\nexport type PresenceModel = typeof PresenceModel.Type\n\nexport const PresenceMessage = defineMessageUnion({\n Show: {},\n Hide: {},\n Hidden: { generation: Schema.Number },\n})\nexport type PresenceMessage = typeof PresenceMessage.Type\n\n/** Whether the content renders: shown or mid-exit. */\nexport const isVisible = (model: PresenceModel): boolean => model.phase !== 'hidden'\n\nexport const Presence = Bundle.make<\n 'Presence',\n PresenceModel,\n PresenceMessage,\n { readonly durationMs: number },\n never,\n never,\n never,\n void,\n {},\n {}\n>('Presence', {\n Model: PresenceModel,\n Message: PresenceMessage,\n args: Schema.Struct({\n durationMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: () => ({ model: { phase: 'hidden', generation: 0 } }),\n update: (model, message, args) =>\n PresenceMessage.match<Update.ReturnWithOutMessage<PresenceModel, PresenceMessage, never>>(\n message,\n {\n // Every new transition invalidates pending timeouts. While shown none is\n // pending, and hiding what is hidden would show it for `durationMs`.\n Show: () =>\n model.phase === 'shown'\n ? { model }\n : { model: { ...model, phase: 'shown' as const, generation: model.generation + 1 } },\n Hide: () => {\n if (model.phase === 'hidden') return { model }\n const generation = model.generation + 1\n return {\n model: { ...model, phase: 'hiding' as const, generation },\n commands: [\n {\n name: 'Presence.hide',\n args: { generation },\n effect: Effect.flatMap(reducedMotion, reduced =>\n reduced\n ? Effect.succeed(PresenceMessage.Hidden({ generation }))\n : Effect.as(\n Effect.sleep(args.durationMs),\n PresenceMessage.Hidden({ generation }),\n ),\n ),\n },\n ],\n }\n },\n Hidden: ({ generation }) =>\n generation === model.generation\n ? { model: { ...model, phase: 'hidden' as const } }\n : { model },\n },\n ),\n})\n","/**\n * A spring as a bundle: a number pulled toward a target with stiffness and\n * damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the\n * trajectory is deterministic under TestClock; the stream ends with\n * Finished, and the value rests exactly at `to`. Underdamped springs\n * overshoot — that is the point — but the rest value is still exact.\n */\nimport { Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const SpringModel = Schema.Struct({\n value: Schema.Number,\n velocity: Schema.Number,\n running: Schema.Boolean,\n})\nexport type SpringModel = typeof SpringModel.Type\n\nexport const SpringMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Ticked: { value: Schema.Number, velocity: Schema.Number },\n /** Carries the exact end value: the integrator stops near it, not on it. */\n Finished: { value: Schema.Number },\n})\nexport type SpringMessage = typeof SpringMessage.Type\n\nconst step = 16\nconst epsilon = 0.001\n\ninterface Point {\n readonly x: number\n readonly v: number\n}\n\nconst settled = (point: Point, to: number): boolean =>\n Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon\n\nexport const Spring = Bundle.make('Spring', {\n Model: SpringModel,\n Message: SpringMessage,\n args: Schema.Struct({\n from: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n to: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n stiffness: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n damping: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: args => ({ model: { value: args.from, velocity: 0, running: false } }),\n update: (model, message) =>\n SpringMessage.match(message, {\n Started: () => (model.running ? { model } : { model: { ...model, running: true } }),\n Stopped: () => (model.running ? { model: { ...model, running: false } } : { model }),\n Ticked: ({ value, velocity }) => ({ model: { ...model, value, velocity } }),\n Finished: ({ value }) => ({ model: { ...model, value, velocity: 0, running: false } }),\n }),\n subscriptions: ({\n from,\n to,\n stiffness,\n damping,\n }): Subscription.Subscriptions<SpringModel, SpringMessage> =>\n Subscription.make<SpringModel, SpringMessage>()(entry => ({\n ticks: entry(\n { running: Schema.Boolean },\n {\n modelToDependencies: model => ({ running: model.running }),\n dependenciesToStream: ({ running }) => {\n if (!running) return Stream.empty\n // One integration step per tick, not per elapsed wall time: the\n // trajectory is identical on the live clock and TestClock, and a\n // stalled event loop slows the animation instead of catching up\n // all at once.\n const dt = step / 1000\n const points = Stream.tick(step).pipe(\n Stream.scan(\n (): Point => ({ x: from, v: 0 }),\n point => {\n const force = -stiffness * (point.x - to) - damping * point.v\n const v = point.v + force * dt\n return { x: point.x + v * dt, v }\n },\n ),\n // scan emits the seed (no movement yet) before the first step.\n Stream.drop(1),\n )\n return Stream.unwrap(\n Effect.map(reducedMotion, reduced =>\n reduced\n ? Stream.make(SpringMessage.Finished({ value: to }))\n : Stream.concat(\n points.pipe(\n Stream.takeUntil(point => settled(point, to)),\n Stream.map(point =>\n SpringMessage.Ticked({ value: point.x, velocity: point.v }),\n ),\n ),\n Stream.make(SpringMessage.Finished({ value: to })),\n ),\n ),\n )\n },\n },\n ),\n })),\n})\n","/**\n * A tween as a bundle: a number animated from one value to another over a\n * duration, in the Model. Progress comes from Effect's clock, so tests drive\n * it with TestClock; the stream ends with Finished, and the value rests at\n * `to`. Linear interpolation only: easing curves stay the application's job.\n */\nimport { Clock, Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const TweenModel = Schema.Struct({ value: Schema.Number, running: Schema.Boolean })\nexport type TweenModel = typeof TweenModel.Type\n\nexport const TweenMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Ticked: { value: Schema.Number },\n /** Carries the exact end value: the stream may stop on either side of it. */\n Finished: { value: Schema.Number },\n})\nexport type TweenMessage = typeof TweenMessage.Type\n\nexport const Tween = Bundle.make('Tween', {\n Model: TweenModel,\n Message: TweenMessage,\n args: Schema.Struct({\n from: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n to: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n ms: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),\n }),\n init: args => ({ model: { value: args.from, running: false } }),\n update: (model, message) =>\n TweenMessage.match(message, {\n Started: () => (model.running ? { model } : { model: { ...model, running: true } }),\n Stopped: () => (model.running ? { model: { ...model, running: false } } : { model }),\n // The first tick lands at the start, on the value the Model already holds.\n Ticked: ({ value }) => (value === model.value ? { model } : { model: { ...model, value } }),\n Finished: ({ value }) => ({ model: { ...model, value, running: false } }),\n }),\n subscriptions: ({ from, to, ms }): Subscription.Subscriptions<TweenModel, TweenMessage> =>\n Subscription.make<TweenModel, TweenMessage>()(entry => ({\n ticks: entry(\n { running: Schema.Boolean },\n {\n modelToDependencies: model => ({ running: model.running }),\n dependenciesToStream: ({ running }) =>\n running\n ? Stream.unwrap(\n Effect.flatMap(reducedMotion, reduced =>\n reduced\n ? Effect.succeed(Stream.make(TweenMessage.Finished({ value: to })))\n : Clock.currentTimeMillis.pipe(\n Effect.map(startedAt => {\n const progress = Stream.tick(16).pipe(\n Stream.mapEffect(() => Clock.currentTimeMillis),\n Stream.map(now => Math.min(1, (now - startedAt) / ms)),\n )\n return Stream.concat(\n progress.pipe(\n Stream.takeUntil(done => done >= 1),\n Stream.map(done =>\n TweenMessage.Ticked({ value: from + (to - from) * done }),\n ),\n ),\n Stream.make(TweenMessage.Finished({ value: to })),\n )\n }),\n ),\n ),\n )\n : Stream.empty,\n },\n ),\n })),\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"motion-DmFDppRn.mjs","names":[],"sources":["../src/motion/motion.ts","../src/motion/presence.ts","../src/motion/spring.ts","../src/motion/tween.ts"],"sourcesContent":["/**\n * Whether motion should be reduced, as a service read when a transition\n * starts, not sniffed once. Absent, motion is full, so a placement that\n * provides nothing behaves as before; a page provides `live` (the user's\n * `prefers-reduced-motion`) and a test provides `reduced` or `full`. Under\n * reduced motion a presence exits at once and a tween or spring jumps to its\n * end, in the same Messages, so the Model sees the same transitions.\n */\nimport { Context, Effect, Layer, Option } from 'effect'\n\nexport interface MotionShape {\n readonly reduced: Effect.Effect<boolean>\n}\n\nexport class Motion extends Context.Service<Motion, MotionShape>()('foldkit-primitives/Motion') {}\n\n/** Reduced or full motion, fixed: for a test or a setting the application owns. */\nexport const layer = (reduced: boolean): Layer.Layer<Motion> =>\n Layer.succeed(Motion, { reduced: Effect.succeed(reduced) })\n\nexport const reduced: Layer.Layer<Motion> = layer(true)\nexport const full: Layer.Layer<Motion> = layer(false)\n\n/** The user's `prefers-reduced-motion`, read each time a transition starts. */\nexport const live: Layer.Layer<Motion> = Layer.succeed(Motion, {\n reduced: Effect.sync(\n () =>\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches,\n ),\n})\n\n/** Whether to reduce motion now: the service's answer, or `false` without one. */\nexport const reducedMotion: Effect.Effect<boolean> = Effect.flatMap(\n Effect.serviceOption(Motion),\n Option.match({ onNone: () => Effect.succeed(false), onSome: motion => motion.reduced }),\n)\n","import { Effect, Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const PresenceModel = Schema.Struct({\n phase: Schema.Literals(['shown', 'hiding', 'hidden']),\n generation: Schema.Number,\n})\nexport type PresenceModel = typeof PresenceModel.Type\n\nexport const PresenceMessage = defineMessageUnion({\n Show: {},\n Hide: {},\n Hidden: { generation: Schema.Number },\n})\nexport type PresenceMessage = typeof PresenceMessage.Type\n\n/** Whether the content renders: shown or mid-exit. */\nexport const isVisible = (model: PresenceModel): boolean => model.phase !== 'hidden'\n\n/**\n * Presence as a bundle: mount-transition state for exit animations. `Hide`\n * starts a timed `hiding` phase; the `Hidden` fact it yields carries the\n * generation it was scheduled under, so a `Show` in between wins and the\n * late fact is ignored. The clock is Effect's, so tests drive it.\n */\nexport const Presence = Bundle.make<\n 'Presence',\n PresenceModel,\n PresenceMessage,\n { readonly durationMs: number },\n never,\n never,\n never,\n void,\n {},\n {}\n>('Presence', {\n Model: PresenceModel,\n Message: PresenceMessage,\n args: Schema.Struct({\n durationMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: () => ({ model: { phase: 'hidden', generation: 0 } }),\n update: (model, message, args) =>\n PresenceMessage.match<Update.ReturnWithOutMessage<PresenceModel, PresenceMessage, never>>(\n message,\n {\n // Every new transition invalidates pending timeouts. While shown none is\n // pending, and hiding what is hidden would show it for `durationMs`.\n Show: () =>\n model.phase === 'shown'\n ? { model }\n : { model: { ...model, phase: 'shown' as const, generation: model.generation + 1 } },\n Hide: () => {\n if (model.phase === 'hidden') return { model }\n const generation = model.generation + 1\n return {\n model: { ...model, phase: 'hiding' as const, generation },\n commands: [\n {\n name: 'Presence.hide',\n args: { generation },\n effect: Effect.flatMap(reducedMotion, reduced =>\n reduced\n ? Effect.succeed(PresenceMessage.Hidden({ generation }))\n : Effect.as(\n Effect.sleep(args.durationMs),\n PresenceMessage.Hidden({ generation }),\n ),\n ),\n },\n ],\n }\n },\n Hidden: ({ generation }) =>\n generation === model.generation\n ? { model: { ...model, phase: 'hidden' as const } }\n : { model },\n },\n ),\n})\n","import { Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const SpringModel = Schema.Struct({\n value: Schema.Number,\n velocity: Schema.Number,\n running: Schema.Boolean,\n})\nexport type SpringModel = typeof SpringModel.Type\n\nexport const SpringMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Ticked: { value: Schema.Number, velocity: Schema.Number },\n /** Carries the exact end value: the integrator stops near it, not on it. */\n Finished: { value: Schema.Number },\n})\nexport type SpringMessage = typeof SpringMessage.Type\n\nconst step = 16\nconst epsilon = 0.001\n\ninterface Point {\n readonly x: number\n readonly v: number\n}\n\nconst settled = (point: Point, to: number): boolean =>\n Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon\n\n/**\n * A spring as a bundle: a number pulled toward a target with stiffness and\n * damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the\n * trajectory is deterministic under TestClock; the stream ends with\n * Finished, and the value rests exactly at `to`. Underdamped springs\n * overshoot — that is the point — but the rest value is still exact.\n */\nexport const Spring = Bundle.make('Spring', {\n Model: SpringModel,\n Message: SpringMessage,\n args: Schema.Struct({\n from: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n to: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n stiffness: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n damping: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: args => ({ model: { value: args.from, velocity: 0, running: false } }),\n update: (model, message) =>\n SpringMessage.match(message, {\n Started: () => (model.running ? { model } : { model: { ...model, running: true } }),\n Stopped: () => (model.running ? { model: { ...model, running: false } } : { model }),\n Ticked: ({ value, velocity }) => ({ model: { ...model, value, velocity } }),\n Finished: ({ value }) => ({ model: { ...model, value, velocity: 0, running: false } }),\n }),\n subscriptions: ({\n from,\n to,\n stiffness,\n damping,\n }): Subscription.Subscriptions<SpringModel, SpringMessage> =>\n Subscription.make<SpringModel, SpringMessage>()(entry => ({\n ticks: entry(\n { running: Schema.Boolean },\n {\n modelToDependencies: model => ({ running: model.running }),\n dependenciesToStream: ({ running }) => {\n if (!running) return Stream.empty\n // One integration step per tick, not per elapsed wall time: the\n // trajectory is identical on the live clock and TestClock, and a\n // stalled event loop slows the animation instead of catching up\n // all at once.\n const dt = step / 1000\n const points = Stream.tick(step).pipe(\n Stream.scan(\n (): Point => ({ x: from, v: 0 }),\n point => {\n const force = -stiffness * (point.x - to) - damping * point.v\n const v = point.v + force * dt\n return { x: point.x + v * dt, v }\n },\n ),\n // scan emits the seed (no movement yet) before the first step.\n Stream.drop(1),\n )\n return Stream.unwrap(\n Effect.map(reducedMotion, reduced =>\n reduced\n ? Stream.make(SpringMessage.Finished({ value: to }))\n : Stream.concat(\n points.pipe(\n Stream.takeUntil(point => settled(point, to)),\n Stream.map(point =>\n SpringMessage.Ticked({ value: point.x, velocity: point.v }),\n ),\n ),\n Stream.make(SpringMessage.Finished({ value: to })),\n ),\n ),\n )\n },\n },\n ),\n })),\n})\n","import { Clock, Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const TweenModel = Schema.Struct({ value: Schema.Number, running: Schema.Boolean })\nexport type TweenModel = typeof TweenModel.Type\n\nexport const TweenMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Ticked: { value: Schema.Number },\n /** Carries the exact end value: the stream may stop on either side of it. */\n Finished: { value: Schema.Number },\n})\nexport type TweenMessage = typeof TweenMessage.Type\n\n/**\n * A tween as a bundle: a number animated from one value to another over a\n * duration, in the Model. Progress comes from Effect's clock, so tests drive\n * it with TestClock; the stream ends with Finished, and the value rests at\n * `to`. Linear interpolation only: easing curves stay the application's job.\n */\nexport const Tween = Bundle.make('Tween', {\n Model: TweenModel,\n Message: TweenMessage,\n args: Schema.Struct({\n from: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n to: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n ms: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),\n }),\n init: args => ({ model: { value: args.from, running: false } }),\n update: (model, message) =>\n TweenMessage.match(message, {\n Started: () => (model.running ? { model } : { model: { ...model, running: true } }),\n Stopped: () => (model.running ? { model: { ...model, running: false } } : { model }),\n // The first tick lands at the start, on the value the Model already holds.\n Ticked: ({ value }) => (value === model.value ? { model } : { model: { ...model, value } }),\n Finished: ({ value }) => ({ model: { ...model, value, running: false } }),\n }),\n subscriptions: ({ from, to, ms }): Subscription.Subscriptions<TweenModel, TweenMessage> =>\n Subscription.make<TweenModel, TweenMessage>()(entry => ({\n ticks: entry(\n { running: Schema.Boolean },\n {\n modelToDependencies: model => ({ running: model.running }),\n dependenciesToStream: ({ running }) =>\n running\n ? Stream.unwrap(\n Effect.flatMap(reducedMotion, reduced =>\n reduced\n ? Effect.succeed(Stream.make(TweenMessage.Finished({ value: to })))\n : Clock.currentTimeMillis.pipe(\n Effect.map(startedAt => {\n const progress = Stream.tick(16).pipe(\n Stream.mapEffect(() => Clock.currentTimeMillis),\n Stream.map(now => Math.min(1, (now - startedAt) / ms)),\n )\n return Stream.concat(\n progress.pipe(\n Stream.takeUntil(done => done >= 1),\n Stream.map(done =>\n TweenMessage.Ticked({ value: from + (to - from) * done }),\n ),\n ),\n Stream.make(TweenMessage.Finished({ value: to })),\n )\n }),\n ),\n ),\n )\n : Stream.empty,\n },\n ),\n })),\n})\n"],"mappings":"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"}
|
package/dist/net/index.d.mts
CHANGED
|
@@ -7,11 +7,11 @@ import * as Update from "foldkit/update";
|
|
|
7
7
|
export declare const BroadcastMessage: import("foldkit/message").MessageUnion<{
|
|
8
8
|
/** Notifies without storing: project the payload into your own field to keep it. */
|
|
9
9
|
readonly Received: {
|
|
10
|
-
|
|
10
|
+
data: Schema.Unknown;
|
|
11
11
|
};
|
|
12
12
|
readonly Posted: {};
|
|
13
13
|
readonly BroadcastFailed: {
|
|
14
|
-
|
|
14
|
+
message: Schema.String;
|
|
15
15
|
};
|
|
16
16
|
}>;
|
|
17
17
|
export type BroadcastMessage = typeof BroadcastMessage.Type;
|
|
@@ -47,10 +47,15 @@ export declare const OnlineModel: Schema.Struct<{
|
|
|
47
47
|
export type OnlineModel = typeof OnlineModel.Type;
|
|
48
48
|
export declare const OnlineMessage: import("foldkit/message").MessageUnion<{
|
|
49
49
|
readonly Changed: {
|
|
50
|
-
|
|
50
|
+
online: Schema.Boolean;
|
|
51
51
|
};
|
|
52
52
|
}>;
|
|
53
53
|
export type OnlineMessage = typeof OnlineMessage.Type;
|
|
54
|
+
/**
|
|
55
|
+
* Online presence as a bundle: `online` in the Model, kept current by the
|
|
56
|
+
* window's online/offline events. The browser reports facts; the Model owns
|
|
57
|
+
* the state, so presence replays like any other transition.
|
|
58
|
+
*/
|
|
54
59
|
export declare const Online: Bundle.Bundle<"Online", void, {
|
|
55
60
|
readonly online: boolean;
|
|
56
61
|
}, {
|
|
@@ -68,6 +73,7 @@ export interface Conversation {
|
|
|
68
73
|
export type ConversationHandler<Opening> = (opening: Opening, conversation: Conversation) => void;
|
|
69
74
|
/** Starts the host, once, the first time a conversation needs it. */
|
|
70
75
|
export type StartHost<Opening> = () => ConversationHandler<Opening> | Promise<ConversationHandler<Opening>>;
|
|
76
|
+
/** Where `SharedHost` runs the host: in a SharedWorker, or in the page when there is none. */
|
|
71
77
|
export interface SharedHostOptions<Opening> {
|
|
72
78
|
/**
|
|
73
79
|
* The SharedWorker whose entry calls `serve`. Constructed by the
|
|
@@ -96,11 +102,28 @@ export interface SharedHost<Opening> {
|
|
|
96
102
|
*/
|
|
97
103
|
readonly connect: (options: SharedHostOptions<Opening>) => SharedHostConnection<Opening>;
|
|
98
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* A shared host: its `name`, which keeps two on one origin apart, and
|
|
107
|
+
* the schema of what opens a conversation, which the host decodes before
|
|
108
|
+
* anything else, ignoring a message that does not decode.
|
|
109
|
+
*/
|
|
110
|
+
declare const define: <Opening, Encoded>(options: {
|
|
111
|
+
readonly name: string;
|
|
112
|
+
readonly opening: Schema.Codec<Opening, Encoded, never, never>;
|
|
113
|
+
}) => SharedHost<Opening>;
|
|
114
|
+
/**
|
|
115
|
+
* One server in the browser for many documents: every tab and frame of an
|
|
116
|
+
* application meets the same host. A page opens conversations, each a
|
|
117
|
+
* `MessagePort` with a decoded opening that says what it is for; the host, in
|
|
118
|
+
* a SharedWorker shared by every tab, or in the top document where there is no
|
|
119
|
+
* SharedWorker, is handed each one with a signal that aborts when the page
|
|
120
|
+
* that opened it is gone.
|
|
121
|
+
*
|
|
122
|
+
* It owns the host's lifetime and the routing of conversations, nothing
|
|
123
|
+
* spoken over them: Sync's `portSocket` and Remote's `port` do that.
|
|
124
|
+
*/
|
|
99
125
|
export declare const SharedHost: {
|
|
100
|
-
define:
|
|
101
|
-
readonly name: string;
|
|
102
|
-
readonly opening: Schema.Codec<Opening, Encoded, never, never>;
|
|
103
|
-
}) => SharedHost<Opening>;
|
|
126
|
+
define: typeof define;
|
|
104
127
|
};
|
|
105
128
|
//#endregion
|
|
106
129
|
//#region src/net/sse.d.ts
|
|
@@ -123,11 +146,11 @@ export declare const SseMessage: import("foldkit/message").MessageUnion<{
|
|
|
123
146
|
readonly Opened: {};
|
|
124
147
|
/** Notifies without storing: project the payload into your own field to keep it. */
|
|
125
148
|
readonly Received: {
|
|
126
|
-
|
|
149
|
+
data: Schema.String;
|
|
127
150
|
};
|
|
128
151
|
readonly Closed: {};
|
|
129
152
|
readonly Failed: {
|
|
130
|
-
|
|
153
|
+
message: Schema.String;
|
|
131
154
|
};
|
|
132
155
|
}>;
|
|
133
156
|
export type SseMessage = typeof SseMessage.Type;
|
|
@@ -142,7 +165,7 @@ export declare const SseView: import("foldkit/schema").TaggedUnion<{
|
|
|
142
165
|
readonly Connecting: {};
|
|
143
166
|
readonly Connected: {};
|
|
144
167
|
readonly Error: {
|
|
145
|
-
|
|
168
|
+
error: Schema.String;
|
|
146
169
|
};
|
|
147
170
|
}>;
|
|
148
171
|
export type SseView = typeof SseView.Type;
|
|
@@ -166,6 +189,12 @@ export interface AcquiredSource {
|
|
|
166
189
|
export declare const Source: ManagedResource.ManagedResource<AcquiredSource, ManagedResource.ManagedResourceService<"sse">>;
|
|
167
190
|
/** The source service a placed SSE stream requires. */
|
|
168
191
|
export type SourceService = ManagedResource.ServiceOf<typeof Source>;
|
|
192
|
+
/**
|
|
193
|
+
* Server-sent events as a bundle: connection lifecycle in the Model, the
|
|
194
|
+
* event stream through one Managed Resource. One-directional, so there is no
|
|
195
|
+
* send: the server speaks, the Model listens. The browser reconnects dropped
|
|
196
|
+
* streams itself; a Failed only records the error.
|
|
197
|
+
*/
|
|
169
198
|
export declare const sse: <const Name extends string>(config: {
|
|
170
199
|
readonly name: Name;
|
|
171
200
|
/** Defaults to the platform EventSource, read lazily so tests can substitute a double. */
|
|
@@ -178,16 +207,16 @@ export declare const sse: <const Name extends string>(config: {
|
|
|
178
207
|
readonly lastError: string | null;
|
|
179
208
|
}, {
|
|
180
209
|
readonly _tag: "Connecting";
|
|
181
|
-
} | {
|
|
182
|
-
readonly _tag: "Opened";
|
|
183
210
|
} | {
|
|
184
211
|
readonly _tag: "Closed";
|
|
185
|
-
} | {
|
|
186
|
-
readonly _tag: "Received";
|
|
187
|
-
readonly data: string;
|
|
188
212
|
} | {
|
|
189
213
|
readonly _tag: "Failed";
|
|
190
214
|
readonly message: string;
|
|
215
|
+
} | {
|
|
216
|
+
readonly _tag: "Opened";
|
|
217
|
+
} | {
|
|
218
|
+
readonly _tag: "Received";
|
|
219
|
+
readonly data: string;
|
|
191
220
|
}, never, ManagedResource.ManagedResourceService<"sse">, ManagedResource.ManagedResourceService<"sse">, void, Readonly<{
|
|
192
221
|
source: ManagedResource.Entry<SseModel, SseMessage, Option.Option<string>, AcquiredSource, SourceService, () => SseMessage>;
|
|
193
222
|
}>, {}>;
|
|
@@ -216,18 +245,18 @@ export declare const WebSocketMessage: import("foldkit/message").MessageUnion<{
|
|
|
216
245
|
readonly Opened: {};
|
|
217
246
|
/** Notifies without storing: project the payload into your own field to keep it. */
|
|
218
247
|
readonly Received: {
|
|
219
|
-
|
|
248
|
+
data: Schema.String;
|
|
220
249
|
};
|
|
221
250
|
readonly Closed: {};
|
|
222
251
|
readonly Failed: {
|
|
223
|
-
|
|
252
|
+
message: Schema.String;
|
|
224
253
|
};
|
|
225
254
|
readonly SendFailed: {
|
|
226
|
-
|
|
255
|
+
message: Schema.String;
|
|
227
256
|
};
|
|
228
257
|
/** Acknowledges dispatch of `data`, not its delivery; update ignores it. */
|
|
229
258
|
readonly Sent: {
|
|
230
|
-
|
|
259
|
+
data: Schema.String;
|
|
231
260
|
};
|
|
232
261
|
/** The socket was still connecting when `connectTimeoutMs` ran out, and was closed. */
|
|
233
262
|
readonly TimedOut: {};
|
|
@@ -249,7 +278,7 @@ export declare const SocketView: import("foldkit/schema").TaggedUnion<{
|
|
|
249
278
|
readonly Connecting: {};
|
|
250
279
|
readonly Connected: {};
|
|
251
280
|
readonly Error: {
|
|
252
|
-
|
|
281
|
+
error: Schema.String;
|
|
253
282
|
};
|
|
254
283
|
}>;
|
|
255
284
|
export type SocketView = typeof SocketView.Type;
|
|
@@ -269,6 +298,14 @@ export declare const viewOf: (model: WebSocketModel, wanted: boolean) => SocketV
|
|
|
269
298
|
export declare const Socket: ManagedResource.ManagedResource<Acquired, ManagedResource.ManagedResourceService<"websocket">>;
|
|
270
299
|
/** The socket service a placed WebSocket's streams and send commands require. */
|
|
271
300
|
export type SocketService = ManagedResource.ServiceOf<typeof Socket>;
|
|
301
|
+
/**
|
|
302
|
+
* A WebSocket as a bundle: connection lifecycle in the Model, full duplex
|
|
303
|
+
* through one Managed Resource. The resource owns the socket; a `send`
|
|
304
|
+
* helper writes through its tag service (lifted to the parent like every
|
|
305
|
+
* helper); a subscription stream reads incoming messages through the same
|
|
306
|
+
* tag. No hidden hub: everything the bundle touches is the Model, the
|
|
307
|
+
* resource registry, or a Message.
|
|
308
|
+
*/
|
|
272
309
|
export declare const websocket: <const Name extends string>(config: {
|
|
273
310
|
readonly name: Name;
|
|
274
311
|
/** Defaults to the platform WebSocket, read lazily so tests can substitute a double. */
|
|
@@ -283,16 +320,16 @@ export declare const websocket: <const Name extends string>(config: {
|
|
|
283
320
|
readonly opened: boolean;
|
|
284
321
|
}, {
|
|
285
322
|
readonly _tag: "Connecting";
|
|
286
|
-
} | {
|
|
287
|
-
readonly _tag: "Opened";
|
|
288
323
|
} | {
|
|
289
324
|
readonly _tag: "Closed";
|
|
290
|
-
} | {
|
|
291
|
-
readonly _tag: "Received";
|
|
292
|
-
readonly data: string;
|
|
293
325
|
} | {
|
|
294
326
|
readonly _tag: "Failed";
|
|
295
327
|
readonly message: string;
|
|
328
|
+
} | {
|
|
329
|
+
readonly _tag: "Opened";
|
|
330
|
+
} | {
|
|
331
|
+
readonly _tag: "Received";
|
|
332
|
+
readonly data: string;
|
|
296
333
|
} | {
|
|
297
334
|
readonly _tag: "SendFailed";
|
|
298
335
|
readonly message: string;
|
|
@@ -307,4 +344,5 @@ export declare const websocket: <const Name extends string>(config: {
|
|
|
307
344
|
send: (model: WebSocketModel, data: string) => Update.ReturnWithOutMessage<WebSocketModel, WebSocketMessage, never, SocketService>;
|
|
308
345
|
}>>;
|
|
309
346
|
//#endregion
|
|
347
|
+
export {};
|
|
310
348
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/net/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/net/broadcast.ts","../../src/net/online.ts","../../src/net/sharedHost.ts","../../src/net/sse.ts","../../src/net/websocket.ts"],"mappings":";;;;;;qBAWa,4CAAgB
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/net/broadcast.ts","../../src/net/online.ts","../../src/net/sharedHost.ts","../../src/net/sse.ts","../../src/net/websocket.ts"],"mappings":";;;;;;qBAWa,4CAAgB;;;IAEf,MAAI,OAAA;;;;IAEG,SAAO,OAAA;;;YAEhB,0BAA0B,iBAAiB;;iBAGtC;EACf,YAAY;EACZ;EACA,aAAa;aAAkB;;;KAG5B,kBAAkB,iBAAiB;;;;;;qBAmB3B,oBAAiB;;AAGnB,SAAA,+BACR,OAAO,OAAO;;;;;;;qBAyBJ,gBAAa,cACZ,eACC,SACJ,+BACR,QAAQ;;;qBCzEE,aAAW,OAAA;mBAAX,OAAA;;YACD,qBAAqB,YAAY;qBAEhC,yCAAa;;IAAmC,QAAM,OAAA;;;YACvD,uBAAuB,cAAc;;;;;;qBAuBpC,QAAM,OAAA;;;;;;;;;iBC7BF;WACN,MAAM;WACN,QAAQ;;;YAIP,oBAAoB,YAAY,SAAS,SAAS,cAAc;;YAGhE,UAAU,iBACpB,oBAAoB,WAAW,QAAQ,oBAAoB;;iBAG5C,kBAAkB;;;;;;WAMxB;aAAyB,MAAM;;;WAE/B,QAAQ,UAAU;;;iBAIZ,qBAAqB;;WAE3B,OAAO,SAAS,YAAY;;iBAGtB,WAAW;WACjB;;WAEA,QAAQ,OAAO,UAAU;;;;;;WAMzB,UAAU,SAAS,kBAAkB,aAAa,qBAAqB;;;;;;;cAQ5E,SAAU,SAAS,SAAO;WACrB;WACA,SAAS,OAAO,MAAM,SAAS;MACtC,WAAW;;;;;;;;;;;;qBA4IF;iBAAA;;;;;iBCvLI;WACN;EACT;EACA,UAAU;EACV,aAAa;EACb,WAAW;;qBAGA,UAAQ,OAAA;;;;;YAKT,kBAAkB,SAAS;qBAE1B,sCAAU;;;;;IAIT,MAAI,OAAA;;;;IAEN,SAAO,OAAA;;;YAEP,oBAAoB,WAAW;;;;;;;qBAQ9B,kCAAO;;;;;IAIT,OAAK,OAAA;;;YAEJ,iBAAiB,QAAQ;;;;;qBAMxB,SAAM,OAAW;;;;;;qBAQjB,YAAS,OAAW,UAAQ,oBAAoB;;iBAW5C;WACN,QAAQ;WACR,QAAQ,MAAM,MAAM;;;qBAIlB,QAAM,gBAAA,gBAAA,gBAAA,gBAAA;;YAGP,gBAAgB,gBAAgB,iBAAiB;;;;;;;qBAsBhD,YAAa,qBAAmB;WAClC,MAAM;;WAEN,iBAAiB,gBAAgB;MAC3C,OAAA,OAAA;WAOmB;;;;;;;;;;;;;;;;;;EA9BV,QAAA,gBAAgB,MACtB,UACA,YACA,OAAO,gBACP,gBACA,qBACM;;;;;iBChFO;WACN;EACT,KAAK;EACL;EACA,UAAU;EACV,aAAa;EACb,WAAW;EACX,WAAW;;qBAGA,gBAAc,OAAA;;;;;;;YAOf,wBAAwB,eAAe;qBAEtC,4CAAgB;;;;;IAIf,MAAI,OAAA;;;;IAEN,SAAO,OAAA;;;IACH,SAAO,OAAA;;;;IAEb,MAAI,OAAA;;;;;YAIF,0BAA0B,iBAAiB;;iBAGtC;WACN,QAAQ;WACR,QAAQ,MAAM,MAAM;;;;;;;;qBASlB,qCAAU;;;;;IAIZ,OAAK,OAAA;;;YAEJ,oBAAoB,WAAW;;;;;qBAM9B,SAAM,OAAW;;;;;;;qBASjB,SAAM,OAAW,gBAAc,oBAAoB;;qBAWnD,QAAM,gBAAA,gBAAA,UAAA,gBAAA;;YAGP,gBAAgB,gBAAgB,iBAAiB;;;;;;;;;qBAiDhD,kBAAmB,qBAAmB;WACxC,MAAM;;WAEN,iBAAiB,gBAAgB;MAC3C,OAAA,OAAA;WAOmB;WAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;EAzD9C,QAAA,gBAAgB,MACtB,gBACA,kBACA,OAAO,gBACP,UACA,qBACM;;EAKF,OACJ,OAAO,gBACP,iBACG,OAAO,qBAAqB,gBAAgB,yBAAyB"}
|