@wyndlass/app-sdk 0.0.0-stage → 1.0.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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -2
  3. package/cli/app-package.mjs +111 -0
  4. package/cli/commands.mjs +114 -0
  5. package/cli/main.mjs +66 -0
  6. package/cli/package.mjs +119 -0
  7. package/cli/preview.mjs +142 -0
  8. package/cli/zip.mjs +63 -0
  9. package/dist/contract/app-contract.d.ts +221 -0
  10. package/dist/contract/app-contract.js +6 -0
  11. package/dist/contract/app-definition.d.ts +155 -0
  12. package/dist/contract/app-definition.js +3 -0
  13. package/dist/contract/data-answer.d.ts +40 -0
  14. package/dist/contract/data-answer.js +3 -0
  15. package/dist/contract/data-calendar.d.ts +57 -0
  16. package/dist/contract/data-calendar.js +3 -0
  17. package/dist/contract/data-news.d.ts +29 -0
  18. package/dist/contract/data-news.js +3 -0
  19. package/dist/contract/data-quotes.d.ts +75 -0
  20. package/dist/contract/data-quotes.js +3 -0
  21. package/dist/contract/data-series.d.ts +51 -0
  22. package/dist/contract/data-series.js +3 -0
  23. package/dist/contract/data-snapshot.d.ts +30 -0
  24. package/dist/contract/data-snapshot.js +3 -0
  25. package/dist/contract/data-weather.d.ts +421 -0
  26. package/dist/contract/data-weather.js +3 -0
  27. package/dist/contract/player-api-event.d.ts +253 -0
  28. package/dist/contract/player-api-event.js +3 -0
  29. package/dist/contract/player-api-status.d.ts +178 -0
  30. package/dist/contract/player-api-status.js +3 -0
  31. package/dist/easing.d.ts +16 -0
  32. package/dist/easing.js +71 -0
  33. package/dist/index.d.ts +94 -0
  34. package/dist/index.js +218 -0
  35. package/dist/preview/frame/frame.js +2 -0
  36. package/dist/preview/frame/index.html +12 -0
  37. package/dist/preview/index.html +40 -0
  38. package/dist/preview/page.js +7 -0
  39. package/dist/qr.d.ts +5 -0
  40. package/dist/qr.js +14 -0
  41. package/dist/react.d.ts +52 -0
  42. package/dist/react.js +149 -0
  43. package/package.json +83 -3
  44. package/protocol/app-definition.schema.json +435 -0
  45. package/protocol/app-packages.json +172 -0
  46. package/protocol/common.schema.json +197 -0
  47. package/protocol/samples/calendar.json +63 -0
  48. package/protocol/samples/events/app.command.json +14 -0
  49. package/protocol/samples/events/item.end.json +13 -0
  50. package/protocol/samples/events/item.start.json +18 -0
  51. package/protocol/samples/events/state.changed.json +11 -0
  52. package/protocol/samples/news.json +37 -0
  53. package/protocol/samples/player-status.json +58 -0
  54. package/protocol/samples/weather.json +59 -0
  55. package/templates/react/AGENTS.md +13 -0
  56. package/templates/react/README.md +22 -0
  57. package/templates/react/app.json +14 -0
  58. package/templates/react/gitignore +2 -0
  59. package/templates/react/package.json +22 -0
  60. package/templates/react/src/app.css +7 -0
  61. package/templates/react/src/app.tsx +39 -0
  62. package/templates/react/src/env.d.ts +5 -0
  63. package/templates/react/tsconfig.json +13 -0
  64. package/templates/vanilla/AGENTS.md +13 -0
  65. package/templates/vanilla/README.md +22 -0
  66. package/templates/vanilla/app.json +14 -0
  67. package/templates/vanilla/gitignore +2 -0
  68. package/templates/vanilla/package.json +18 -0
  69. package/templates/vanilla/src/app.css +7 -0
  70. package/templates/vanilla/src/app.ts +59 -0
  71. package/templates/vanilla/src/env.d.ts +5 -0
  72. package/templates/vanilla/tsconfig.json +12 -0
@@ -0,0 +1,253 @@
1
+ /**
2
+ * An event the controller emits (protocol/player-api.json, ADR 0050): to apps that subscribed on the screen, and in the ring player.events reads. Told apart by type.
3
+ */
4
+ export type PlayerAPIEvent = StateChanged | ContentUpdated | ItemStart | ItemEnd | ItemError | ConnectionChanged | AudioChanged | DisplayChanged | TimeChanged | AppCommand | AppFailed | AppHeld;
5
+ /**
6
+ * RFC 3339 timestamp with offset.
7
+ */
8
+ export type CommonTimestamp = string;
9
+ /**
10
+ * idle: nothing to play; stopped: the standby screen.
11
+ */
12
+ export type PlayerApiStatusState = "starting" | "playing" | "paused" | "stopped" | "idle" | "error";
13
+ /**
14
+ * Who caused a change: the player itself, an app on the screen, or a remote caller whose principal core relayed.
15
+ */
16
+ export type PlayerApiCommonBy = {
17
+ kind: "player";
18
+ } | {
19
+ kind: "app";
20
+ /**
21
+ * The app's id (app-definition).
22
+ */
23
+ app: string;
24
+ instance: string;
25
+ } | {
26
+ kind: "remote";
27
+ principal: CommonPrincipalRef;
28
+ };
29
+ /**
30
+ * A principal, e.g. user:u_123, agent:fleet-guardian, device:dev_...
31
+ */
32
+ export type CommonPrincipalRef = string;
33
+ /**
34
+ * Lowercase hex SHA-256 digest.
35
+ */
36
+ export type CommonSha256 = string;
37
+ /**
38
+ * A zone of the screen; main until compositions have zones.
39
+ */
40
+ export type PlayerApiCommonZone = string;
41
+ /**
42
+ * Media item id (asset, app, composition, playlist or live source).
43
+ */
44
+ export type CommonMediaId = string;
45
+ /**
46
+ * Whether the player is connected to Wyndlass (NATS).
47
+ */
48
+ export type PlayerApiCommonConnection = "online" | "offline";
49
+ /**
50
+ * The app's id (app-definition), where the layout carries it (appInfo).
51
+ */
52
+ export type AppId = string;
53
+ /**
54
+ * The app's element, as a path of element ids from the outermost layout (ADR 0053, decision 13).
55
+ */
56
+ export type ElementPath = string;
57
+ export interface StateChanged {
58
+ type: "state.changed";
59
+ /**
60
+ * The event's place in this player's sequence since it started; player.events pages by it.
61
+ */
62
+ seq: number;
63
+ at: CommonTimestamp;
64
+ state: PlayerApiStatusState;
65
+ previous: PlayerApiStatusState;
66
+ by: PlayerApiCommonBy;
67
+ }
68
+ export interface ContentUpdated {
69
+ type: "content.updated";
70
+ /**
71
+ * The event's place in this player's sequence since it started; player.events pages by it.
72
+ */
73
+ seq: number;
74
+ at: CommonTimestamp;
75
+ manifest: CommonSha256;
76
+ previous?: CommonSha256;
77
+ }
78
+ export interface ItemStart {
79
+ type: "item.start";
80
+ /**
81
+ * The event's place in this player's sequence since it started; player.events pages by it.
82
+ */
83
+ seq: number;
84
+ at: CommonTimestamp;
85
+ zone: PlayerApiCommonZone;
86
+ item: CommonItemRef;
87
+ blob: CommonSha256;
88
+ contentType: string;
89
+ startedAt: CommonTimestamp;
90
+ durationMs?: number;
91
+ /**
92
+ * The transition the item's entry asked for and the one the player played (WP-0056): a crossfade it could not play, between two native videos on a host that holds one, plays as a cut. Absent when the entry asked for none.
93
+ */
94
+ transition?: {
95
+ asked: "cut" | "crossfade";
96
+ played: "cut" | "crossfade";
97
+ };
98
+ /**
99
+ * The layout element the item plays in, as a path of element ids from the outermost layout (ADR 0053, decision 13; WP-0056); `loop` for a playlist playing on its own. Absent for the screen's own loop.
100
+ */
101
+ element?: string;
102
+ }
103
+ /**
104
+ * A pinned reference to a media item version.
105
+ */
106
+ export interface CommonItemRef {
107
+ id: CommonMediaId;
108
+ version: number;
109
+ }
110
+ export interface ItemEnd {
111
+ type: "item.end";
112
+ /**
113
+ * The event's place in this player's sequence since it started; player.events pages by it.
114
+ */
115
+ seq: number;
116
+ at: CommonTimestamp;
117
+ zone: PlayerApiCommonZone;
118
+ item: CommonItemRef;
119
+ blob: CommonSha256;
120
+ endedAt: CommonTimestamp;
121
+ /**
122
+ * replaced: a new manifest; finished: the app ended its slot.
123
+ */
124
+ reason: "completed" | "skipped" | "stopped" | "error" | "replaced" | "finished";
125
+ /**
126
+ * The layout element the item plays in, as a path of element ids from the outermost layout (ADR 0053, decision 13; WP-0056); `loop` for a playlist playing on its own. Absent for the screen's own loop.
127
+ */
128
+ element?: string;
129
+ }
130
+ export interface ItemError {
131
+ type: "item.error";
132
+ /**
133
+ * The event's place in this player's sequence since it started; player.events pages by it.
134
+ */
135
+ seq: number;
136
+ at: CommonTimestamp;
137
+ zone: PlayerApiCommonZone;
138
+ item: CommonItemRef;
139
+ blob: CommonSha256;
140
+ code: string;
141
+ message: string;
142
+ }
143
+ export interface ConnectionChanged {
144
+ type: "connection.changed";
145
+ /**
146
+ * The event's place in this player's sequence since it started; player.events pages by it.
147
+ */
148
+ seq: number;
149
+ at: CommonTimestamp;
150
+ connection: PlayerApiCommonConnection;
151
+ }
152
+ export interface AudioChanged {
153
+ type: "audio.changed";
154
+ /**
155
+ * The event's place in this player's sequence since it started; player.events pages by it.
156
+ */
157
+ seq: number;
158
+ at: CommonTimestamp;
159
+ audio: PlayerApiCommonAudio;
160
+ by: PlayerApiCommonBy;
161
+ }
162
+ /**
163
+ * The player's own level, not the operating system's.
164
+ */
165
+ export interface PlayerApiCommonAudio {
166
+ volume: number;
167
+ muted: boolean;
168
+ }
169
+ export interface DisplayChanged {
170
+ type: "display.changed";
171
+ /**
172
+ * The event's place in this player's sequence since it started; player.events pages by it.
173
+ */
174
+ seq: number;
175
+ at: CommonTimestamp;
176
+ display: PlayerApiCommonDisplay;
177
+ by: PlayerApiCommonBy;
178
+ }
179
+ export interface PlayerApiCommonDisplay {
180
+ power: "on" | "standby" | "unknown";
181
+ brightness?: number;
182
+ }
183
+ export interface TimeChanged {
184
+ type: "time.changed";
185
+ /**
186
+ * The event's place in this player's sequence since it started; player.events pages by it.
187
+ */
188
+ seq: number;
189
+ at: CommonTimestamp;
190
+ reason: "clock-adjusted" | "time-zone-changed";
191
+ }
192
+ export interface AppCommand {
193
+ type: "app.command";
194
+ /**
195
+ * The event's place in this player's sequence since it started; player.events pages by it.
196
+ */
197
+ seq: number;
198
+ at: CommonTimestamp;
199
+ name: string;
200
+ /**
201
+ * Any JSON the sender gave.
202
+ */
203
+ argument?: {
204
+ [k: string]: unknown | undefined;
205
+ };
206
+ by: PlayerApiCommonBy;
207
+ }
208
+ /**
209
+ * An app on the screen could not run, or misbehaved (WP-0100). The rest of its layout plays on.
210
+ */
211
+ export interface AppFailed {
212
+ type: "app.failed";
213
+ /**
214
+ * The event's place in this player's sequence since it started; player.events pages by it.
215
+ */
216
+ seq: number;
217
+ at: CommonTimestamp;
218
+ item: CommonItemRef1;
219
+ app?: AppId;
220
+ element?: ElementPath;
221
+ /**
222
+ * load: its bundle did not import. shape: it exports no element class the host can define, or (a customer's app) no way to take its context. unsupported: it needs a newer Player API or WebView than this screen has, or it is a customer's app and this screen's page was never handed its port (WP-0100 part b). error: it threw three times in a minute, and was stopped. late: it was not ready by its deadline, and shows as it is. overran: an enter or exit ran past its clip.
223
+ */
224
+ reason: "load" | "shape" | "unsupported" | "error" | "late" | "overran";
225
+ /**
226
+ * The name of what it threw (TypeError), for error, load and shape. Never its message, which could carry anything the app holds.
227
+ */
228
+ error?: string;
229
+ }
230
+ /**
231
+ * A pinned reference to a media item version.
232
+ */
233
+ export interface CommonItemRef1 {
234
+ id: CommonMediaId;
235
+ version: number;
236
+ }
237
+ /**
238
+ * An app version held back on this screen until the next manifest, after the page stopped answering twice within an hour while it was on screen (WP-0100). Its element is drawn empty meanwhile.
239
+ */
240
+ export interface AppHeld {
241
+ type: "app.held";
242
+ /**
243
+ * The event's place in this player's sequence since it started; player.events pages by it.
244
+ */
245
+ seq: number;
246
+ at: CommonTimestamp;
247
+ item: CommonItemRef;
248
+ app?: AppId;
249
+ /**
250
+ * How many times the page stopped answering in the last hour with this app on screen.
251
+ */
252
+ hangs: number;
253
+ }
@@ -0,0 +1,3 @@
1
+ // Copied from protocol/generated/ts/player-api-event.d.ts by app-sdk/scripts/copy-protocol.mjs: edit the source, then run `pnpm --filter @wyndlass/app-sdk copy-protocol`.
2
+ /* Generated from protocol/schemas by scripts/generate.mjs. Do not edit. */
3
+ export {};
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Device id.
3
+ */
4
+ export type CommonDeviceId = string;
5
+ export type CommonSemver = string;
6
+ /**
7
+ * IANA time zone name.
8
+ */
9
+ export type CommonTimeZone = string;
10
+ /**
11
+ * BCP 47 language tag.
12
+ */
13
+ export type CommonLocale = string;
14
+ /**
15
+ * Whether the player is connected to Wyndlass (NATS).
16
+ */
17
+ export type PlayerApiCommonConnection = "online" | "offline";
18
+ /**
19
+ * idle: nothing to play; stopped: the standby screen.
20
+ */
21
+ export type State = "starting" | "playing" | "paused" | "stopped" | "idle" | "error";
22
+ /**
23
+ * Who caused a change: the player itself, an app on the screen, or a remote caller whose principal core relayed.
24
+ */
25
+ export type PlayerApiCommonBy = {
26
+ kind: "player";
27
+ } | {
28
+ kind: "app";
29
+ /**
30
+ * The app's id (app-definition).
31
+ */
32
+ app: string;
33
+ instance: string;
34
+ } | {
35
+ kind: "remote";
36
+ principal: CommonPrincipalRef;
37
+ };
38
+ /**
39
+ * A principal, e.g. user:u_123, agent:fleet-guardian, device:dev_...
40
+ */
41
+ export type CommonPrincipalRef = string;
42
+ /**
43
+ * RFC 3339 timestamp with offset.
44
+ */
45
+ export type CommonTimestamp = string;
46
+ /**
47
+ * Lowercase hex SHA-256 digest.
48
+ */
49
+ export type CommonSha256 = string;
50
+ /**
51
+ * RFC 3339 timestamp with offset.
52
+ */
53
+ export type CommonTimestamp1 = string;
54
+ /**
55
+ * A zone of the screen; main until compositions have zones.
56
+ */
57
+ export type PlayerApiCommonZone = string;
58
+ /**
59
+ * Media item id (asset, app, composition, playlist or live source).
60
+ */
61
+ export type CommonMediaId = string;
62
+ /**
63
+ * Everything the controller knows about the player at once: player.status's result, and what apps read through their controller (protocol/player-api.json, ADR 0050).
64
+ */
65
+ export interface PlayerAPIStatus {
66
+ apiVersion: number;
67
+ device: PlayerApiCommonDeviceFacts;
68
+ /**
69
+ * Where player-core runs; in preview and the designer, commands change nothing.
70
+ */
71
+ mode: "player" | "preview" | "designer";
72
+ timeZone: CommonTimeZone;
73
+ locale: CommonLocale;
74
+ connection: PlayerApiCommonConnection;
75
+ state: State;
76
+ held?: Held;
77
+ content?: Content;
78
+ /**
79
+ * What each zone shows.
80
+ *
81
+ * @maxItems 64
82
+ */
83
+ now: NowItem[];
84
+ audio?: PlayerApiCommonAudio;
85
+ display?: PlayerApiCommonDisplay;
86
+ capabilities: PlayerApiCommonHostCapabilities;
87
+ }
88
+ /**
89
+ * What the host says the device is; fixed while the player runs.
90
+ */
91
+ export interface PlayerApiCommonDeviceFacts {
92
+ id: CommonDeviceId;
93
+ /**
94
+ * The device type (protocol/device-types).
95
+ */
96
+ type: string;
97
+ /**
98
+ * Which shell hosts player-core.
99
+ */
100
+ shell: "android" | "universal-electron" | "universal-brightsign" | "wpf" | "webos" | "simulator";
101
+ shellVersion: CommonSemver;
102
+ coreVersion: CommonSemver;
103
+ model?: string;
104
+ resolution: PlayerApiCommonResolution;
105
+ orientation: "landscape" | "portrait";
106
+ }
107
+ export interface PlayerApiCommonResolution {
108
+ width: number;
109
+ height: number;
110
+ }
111
+ /**
112
+ * A remote pause or stop in effect: apps' playback commands are refused with HELD until a remote play or a restart (ADR 0050).
113
+ */
114
+ export interface Held {
115
+ by: PlayerApiCommonBy;
116
+ since: CommonTimestamp;
117
+ }
118
+ /**
119
+ * The manifest playing; absent when there is none.
120
+ */
121
+ export interface Content {
122
+ manifest: CommonSha256;
123
+ issuedAt: CommonTimestamp;
124
+ switchedAt: CommonTimestamp;
125
+ windowTo?: CommonTimestamp1;
126
+ /**
127
+ * The window has ended: the player plays this content until a newer manifest arrives (WP-0053).
128
+ */
129
+ stale?: boolean;
130
+ }
131
+ export interface NowItem {
132
+ zone: PlayerApiCommonZone;
133
+ item: CommonItemRef;
134
+ blob: CommonSha256;
135
+ contentType: string;
136
+ startedAt: CommonTimestamp;
137
+ durationMs?: number;
138
+ positionMs?: number;
139
+ }
140
+ /**
141
+ * A pinned reference to a media item version.
142
+ */
143
+ export interface CommonItemRef {
144
+ id: CommonMediaId;
145
+ version: number;
146
+ }
147
+ /**
148
+ * The player's own level, not the operating system's.
149
+ */
150
+ export interface PlayerApiCommonAudio {
151
+ volume: number;
152
+ muted: boolean;
153
+ }
154
+ export interface PlayerApiCommonDisplay {
155
+ power: "on" | "standby" | "unknown";
156
+ brightness?: number;
157
+ }
158
+ /**
159
+ * What the host can do beyond the controller's own; tools that need what it lacks are not listed.
160
+ */
161
+ export interface PlayerApiCommonHostCapabilities {
162
+ /**
163
+ * The player's volume and mute can be set.
164
+ */
165
+ audio: boolean;
166
+ /**
167
+ * With audio, whether the volume can be set to a level (audio.setVolume). False where only mute can: the Amazon Signage Stick, whose API sets no level on any TV (WP-0096 part g). Absent means it can.
168
+ */
169
+ volume?: boolean;
170
+ display: {
171
+ power: boolean;
172
+ brightness: boolean;
173
+ /**
174
+ * The host turns its whole output, native surfaces included, by the system (ADR 0052, decision 4): host.setDisplay's rotation. Without it, the page turns itself (WP-0056 part d).
175
+ */
176
+ rotation?: boolean;
177
+ };
178
+ }
@@ -0,0 +1,3 @@
1
+ // Copied from protocol/generated/ts/player-api-status.d.ts by app-sdk/scripts/copy-protocol.mjs: edit the source, then run `pnpm --filter @wyndlass/app-sdk copy-protocol`.
2
+ /* Generated from protocol/schemas by scripts/generate.mjs. Do not edit. */
3
+ export {};
@@ -0,0 +1,16 @@
1
+ /** A CSS easing name or cubic-bezier(a, b, c, d) as a cubic Bézier; `ease` when absent or unreadable. */
2
+ export declare function easingOf(name: string | undefined): readonly number[];
3
+ /**
4
+ * The cubic Bézier `easing` at x, as browsers solve it: x(t) inverted by Newton's method, then bisection, to 1e-7; then
5
+ * y at that t. 0 before 0 and 1 after 1.
6
+ */
7
+ export declare function cubicBezier(easing: readonly number[], x: number): number;
8
+ /**
9
+ * When each of n items animates in a staggered enter or exit (ADR 0071, decision 4): item i starts i × stagger after
10
+ * the start, and every item runs for what is left, so the last ends with the whole. The stagger is shortened, to whole
11
+ * milliseconds, where it would leave an item less than 16 ms (one frame).
12
+ */
13
+ export declare function staggerTimes(startMs: number, durationMs: number, staggerMs: number | undefined, n: number): {
14
+ startMs: number;
15
+ endMs: number;
16
+ }[];
package/dist/easing.js ADDED
@@ -0,0 +1,71 @@
1
+ // The presets' curves (WP-0101 decision 2): the SDK's own, so it imports nothing of player-core's. Both give every case
2
+ // of protocol/test-vectors/easing.json, so an app's enter and exit move as the layout's clips around it do.
3
+ const NAMED = {
4
+ linear: [0, 0, 1, 1], ease: [0.25, 0.1, 0.25, 1], "ease-in": [0.42, 0, 1, 1], "ease-out": [0, 0, 0.58, 1], "ease-in-out": [0.42, 0, 0.58, 1],
5
+ };
6
+ /** A CSS easing name or cubic-bezier(a, b, c, d) as a cubic Bézier; `ease` when absent or unreadable. */
7
+ export function easingOf(name) {
8
+ if (!name)
9
+ return NAMED.ease;
10
+ if (NAMED[name])
11
+ return NAMED[name];
12
+ const m = /^cubic-bezier\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\)$/.exec(name);
13
+ const v = m?.slice(1).map(Number);
14
+ return v && v.every(Number.isFinite) ? v : NAMED.ease;
15
+ }
16
+ /**
17
+ * The cubic Bézier `easing` at x, as browsers solve it: x(t) inverted by Newton's method, then bisection, to 1e-7; then
18
+ * y at that t. 0 before 0 and 1 after 1.
19
+ */
20
+ export function cubicBezier(easing, x) {
21
+ if (x <= 0)
22
+ return 0;
23
+ if (x >= 1)
24
+ return 1;
25
+ const [x1, y1, x2, y2] = easing;
26
+ if (x1 === y1 && x2 === y2)
27
+ return x;
28
+ const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
29
+ const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
30
+ const sampleX = (t) => ((ax * t + bx) * t + cx) * t;
31
+ const sampleY = (t) => ((ay * t + by) * t + cy) * t;
32
+ const slopeX = (t) => (3 * ax * t + 2 * bx) * t + cx;
33
+ let t = x;
34
+ for (let i = 0; i < 8; i++) {
35
+ const err = sampleX(t) - x;
36
+ if (Math.abs(err) < 1e-7)
37
+ return sampleY(t);
38
+ const d = slopeX(t);
39
+ if (Math.abs(d) < 1e-6)
40
+ break;
41
+ t -= err / d;
42
+ }
43
+ let lo = 0, hi = 1;
44
+ t = x;
45
+ while (lo < hi) {
46
+ const v = sampleX(t);
47
+ if (Math.abs(v - x) < 1e-7)
48
+ break;
49
+ if (x > v)
50
+ lo = t;
51
+ else
52
+ hi = t;
53
+ t = (hi - lo) / 2 + lo;
54
+ if (hi - lo < 1e-7)
55
+ break;
56
+ }
57
+ return sampleY(t);
58
+ }
59
+ /**
60
+ * When each of n items animates in a staggered enter or exit (ADR 0071, decision 4): item i starts i × stagger after
61
+ * the start, and every item runs for what is left, so the last ends with the whole. The stagger is shortened, to whole
62
+ * milliseconds, where it would leave an item less than 16 ms (one frame).
63
+ */
64
+ export function staggerTimes(startMs, durationMs, staggerMs, n) {
65
+ if (n <= 0)
66
+ return [];
67
+ const most = n > 1 ? Math.floor(Math.max(0, durationMs - 16) / (n - 1)) : 0;
68
+ const s = Math.min(staggerMs ?? 0, most);
69
+ const each = durationMs - (n - 1) * s;
70
+ return Array.from({ length: n }, (_, i) => ({ startMs: startMs + i * s, endMs: startMs + i * s + each }));
71
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The app runtime (WP-0101, ADR 0101): what every app on Wyndlass's screens shares, Wyndlass's own and an
3
+ * organization's. Its context from the host, its props with their defaults, drawing from the layout's clock (so seeking
4
+ * and pausing draw the right moment), saying it is ready once, and its enter and exit presets on that clock.
5
+ *
6
+ * Both shapes of the app contract (WP-0100, ADR 0100): a host of Player API 2 defines the bundle's default export under
7
+ * a tag of its own and hands it the context once, through ATTACH, leaving nothing on the element; an older one reads the
8
+ * tag the bundle defined (`define`) and sets the context as `element.wyndlass`.
9
+ *
10
+ * Outside a host (a unit test, the SDK's preview before it gives one) an app still runs: the time is now, each prop is
11
+ * its default, data and images are refused as unavailable, and ready() does nothing.
12
+ */
13
+ import { ATTACH, type AppContext, type AppController, type DataAnswer, type HookOptions, type ImpressionsFeed } from "./contract/app-contract.js";
14
+ export { ATTACH };
15
+ export type { AppContext, AppController, AppElement, AppModule, AppCapability, DataAnswer, HookOptions, ImpressionsFeed, ImpressionsNow, ImpressionEvent, ScreenFacts, TableBinding, TableRule, TimeNow, NotApplied, } from "./contract/app-contract.js";
16
+ export type { AppDefinition } from "./contract/app-definition.js";
17
+ /** The data service's answers, one namespace for each source (data-*.schema.json). */
18
+ export type * as calendar from "./contract/data-calendar.js";
19
+ export type * as news from "./contract/data-news.js";
20
+ export type * as quotes from "./contract/data-quotes.js";
21
+ export type * as series from "./contract/data-series.js";
22
+ export type * as weather from "./contract/data-weather.js";
23
+ export type * as tables from "./contract/data-snapshot.js";
24
+ export { cubicBezier, easingOf, staggerTimes } from "./easing.js";
25
+ /** A default's type, widened: a prop that defaults to "digital" may be any string. */
26
+ type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
27
+ type Params = Record<string, string | number | boolean>;
28
+ /** An app's context, however the host gave it; none outside a host. */
29
+ export declare function contextOf(app: WyndlassApp): AppContext | undefined;
30
+ /** The data service's answer for a source the definition declares (data-answer.schema.json); refused outside a host. */
31
+ export declare function data<T = unknown>(app: WyndlassApp, source: string, params: Params): Promise<DataAnswer<T>>;
32
+ /** The answer the screen last kept for the same request, however old, at once (WP-0111); none before the first, or here. */
33
+ export declare function kept<T = unknown>(app: WyndlassApp, source: string, params: Params): DataAnswer<T> | undefined;
34
+ /** An image a data answer names, as a blob URL the app can draw; refused outside a host. */
35
+ export declare function image(app: WyndlassApp, path: string): Promise<string>;
36
+ /** A data table's snapshot as the manifest carries it, the bound table's with only the rows the binding chooses; null without one. */
37
+ export declare function snapshot(app: WyndlassApp, table: string): unknown | null;
38
+ /** The app's view of the player's controller (ADR 0050), on screens, as its definition's `capabilities.player` allows. */
39
+ export declare function controllerOf(app: WyndlassApp): AppController | undefined;
40
+ /** The people at the screen (WP-0105), where the definition declares the impressions sensor and the site measures. */
41
+ export declare function impressionsOf(app: WyndlassApp): ImpressionsFeed | undefined;
42
+ export declare abstract class WyndlassApp extends HTMLElement {
43
+ #private;
44
+ /** The context as a host older than Player API 2 sets it. Declared only, so nothing is left on a newer host's element. */
45
+ wyndlass?: AppContext;
46
+ protected root: ShadowRoot;
47
+ /** Player API 2: the host hands the context over once, before the element joins the document; a second is ignored. */
48
+ [ATTACH](context: AppContext): void;
49
+ connectedCallback(): void;
50
+ disconnectedCallback(): void;
51
+ /** A prop, or its default when it is absent or empty. */
52
+ protected prop<T>(name: string, fallback: T): Widen<T>;
53
+ /** The font prop, a typography value (layout.schema.json#/$defs/typography, WP-0068 part e), as CSS for the app's box. */
54
+ protected font(): string;
55
+ /**
56
+ * Styles for the app's shadow root, as a constructable stylesheet (adoptedStyleSheets), not a <style>: a page whose
57
+ * style policy uses nonces still draws it (WP-0124 decision 9). Each call adds a sheet after the others. Call it from
58
+ * mount(), once the root is there.
59
+ */
60
+ protected css(text: string): CSSStyleSheet;
61
+ /** The layout's clock: now, or the moment it is held at when seeked or paused. */
62
+ protected now(): number;
63
+ /** When the layout started, on the same clock. */
64
+ protected startedAt(): number;
65
+ protected get timeZone(): string;
66
+ protected get locale(): string;
67
+ /** Says the app may be shown; once. Within 5 s of its start, or the screen reports it late. */
68
+ protected ready(): void;
69
+ /** Builds the app's shadow DOM, once. */
70
+ protected abstract mount(): void;
71
+ /** Draws the moment `epochMs` on the layout's clock. */
72
+ protected abstract draw(epochMs: number): void;
73
+ update(epochMs: number): void;
74
+ enter(o: HookOptions): void;
75
+ exit(o: HookOptions): void;
76
+ /** Forgets the enters and exits given so far (the designer gives them again when the clips change). */
77
+ reset(): void;
78
+ /** A paged app's items, for a staggered enter or exit (ADR 0071, decision 4); none animates the app as one box. */
79
+ protected staggered(): HTMLElement[];
80
+ }
81
+ /** A size that fits the app's box: the smaller of a share of its height and a share of its width (--h and --w). */
82
+ export declare function fit(ofHeight: number, ofWidth: number): string;
83
+ /** A typography value as CSS: its face, decoration, letter case, spacing, line height, shadow, and its scale as --s. */
84
+ export declare function typographyCss(value: unknown): string;
85
+ /** The colour props as CSS, with a fallback. */
86
+ export declare function color(v: unknown, fallback: string): string;
87
+ /**
88
+ * Defines an element once, under its fixed tag, and returns the tag (the bundle's `element` export): the old shape, for
89
+ * players older than Player API 2, which only Wyndlass's own apps need. A newer host defines the default export under a
90
+ * tag of its own instead.
91
+ */
92
+ export declare function define(tag: string, element: CustomElementConstructor): string;
93
+ /** Text safe to put in markup. */
94
+ export declare function escape(s: string): string;