@moku-labs/game 0.0.1 → 0.0.2
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 +424 -37
- package/dist/assets.d.mts +161 -0
- package/dist/assets.mjs +4729 -0
- package/dist/component-DGg5DqKK.mjs +44 -0
- package/dist/control.d.mts +162 -0
- package/dist/control.mjs +758 -0
- package/dist/define-sFoO3y6X.d.mts +263 -0
- package/dist/headless-CvamCUcR.mjs +186 -0
- package/dist/headless-KaSWcd0s.d.mts +181 -0
- package/dist/index.d.mts +1204 -50
- package/dist/index.mjs +20978 -2010
- package/dist/inspect.d.mts +109 -0
- package/dist/inspect.mjs +519 -0
- package/dist/jsx-dev-runtime.d.mts +2 -0
- package/dist/jsx-dev-runtime.mjs +2 -0
- package/dist/jsx-runtime.d.mts +2 -0
- package/dist/jsx-runtime.mjs +2 -0
- package/dist/memory-CvgdnsQO.mjs +259 -0
- package/dist/registry-DlpRCibU.mjs +384 -0
- package/dist/runtime-DRlwxkIv.mjs +182 -0
- package/dist/runtime-DiOTkDZz.d.mts +77 -0
- package/dist/session-DmAxY6Ll.mjs +145 -0
- package/dist/testing.d.mts +23 -111
- package/dist/testing.mjs +12 -289
- package/dist/types-BxkNNYul.d.mts +2840 -0
- package/dist/types-CTPS9GBu.d.mts +742 -0
- package/dist/types-DD-QrG_z.d.mts +588 -0
- package/dist/types-DWILGrPn.d.mts +622 -0
- package/dist/types-DYLnSgMI.d.mts +570 -0
- package/dist/types-JNc_UQBo.d.mts +2896 -0
- package/dist/types-yg_ywtT-.d.mts +1859 -0
- package/dist/visual-BDUHSRvf.mjs +734 -0
- package/package.json +26 -4
- package/dist/registry-DWV5C0Mf.mjs +0 -666
- package/dist/types-BfsmUzLC.d.mts +0 -1908
|
@@ -0,0 +1,1859 @@
|
|
|
1
|
+
import { D as Hint, L as Json, T as Descriptor, k as Api, lt as Require, t as Api$1 } from "./types-JNc_UQBo.mjs";
|
|
2
|
+
import { E as ViewHandle, M as ComponentValue, N as Entity, O as AnyComponentType, S as ProjectionMotion, b as MotionHandle, c as Point$1, j as ComponentType, l as ViewportSize, m as ChangeHook, t as Api$3, u as Api$2, y as Motion } from "./types-BxkNNYul.mjs";
|
|
3
|
+
import { Log } from "@moku-labs/common/browser";
|
|
4
|
+
import { PluginCtx } from "@moku-labs/core";
|
|
5
|
+
|
|
6
|
+
//#region src/plugins/input/types.d.ts
|
|
7
|
+
declare namespace types_d_exports$1 {
|
|
8
|
+
export { Config$1 as Config, Deps, Direction, GesturePhase, InputApi, InputCtx, KernelSlice, KeyInput, KeyListener, Point, RawSample, State$1 as State, TapListener, Target };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A point in reference units, as `renderer.viewport.toReference` answers it.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const point: Point = { x: 540, y: 960 };
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
type Point = {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The dominant axis of a swipe.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const direction: Direction = "left";
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
type Direction = "up" | "down" | "left" | "right";
|
|
31
|
+
/**
|
|
32
|
+
* What `app.input.*` is pointed at: the projection key of a live view, or an entity. A key never
|
|
33
|
+
* addresses a view in the despawn queue.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const target: Target = { projection: "board.items", key: "i5" };
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
type Target = {
|
|
41
|
+
projection: string;
|
|
42
|
+
key: string;
|
|
43
|
+
} | Entity;
|
|
44
|
+
/**
|
|
45
|
+
* One pointer event as the DOM handlers queue it: no hit test, no reference units, no decision.
|
|
46
|
+
* `cancel` is a `pointercancel`, `lost` a lost pointer capture, `leave` the pointer leaving the
|
|
47
|
+
* canvas. `pointerType` is the device: only a mouse or a pen hovers.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const sample: RawSample = {
|
|
52
|
+
* kind: "down", pointerType: "touch", pointerId: 1, clientX: 320, clientY: 640
|
|
53
|
+
* };
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
type RawSample = {
|
|
57
|
+
kind: "down" | "move" | "up" | "cancel" | "lost" | "leave";
|
|
58
|
+
pointerType: "mouse" | "touch" | "pen";
|
|
59
|
+
pointerId: number;
|
|
60
|
+
clientX: number;
|
|
61
|
+
clientY: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Where the one active pointer stands in the gesture machine.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const phase: GesturePhase = "dragging";
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
type GesturePhase = "idle" | "pressed" | "longPressed" | "dragging";
|
|
72
|
+
/**
|
|
73
|
+
* What `onTap` hands a listener: the view the finger let go on, inside the tap slop.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const tapped: Entity[] = [];
|
|
78
|
+
* const listener: TapListener = entity => tapped.push(entity);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
type TapListener = (entity: Entity) => void;
|
|
82
|
+
/**
|
|
83
|
+
* One key press as `onKey` hands it to a listener: the DOM `KeyboardEvent.key` and whether Shift
|
|
84
|
+
* was held.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const backTab: KeyInput = { key: "Tab", shift: true };
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
type KeyInput = {
|
|
92
|
+
key: string;
|
|
93
|
+
shift: boolean;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* What `onKey` registers. Returning `true` marks the key handled: input then calls
|
|
97
|
+
* `preventDefault()` on the DOM event, and `app.input.pressKey` answers `true`.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* const closeOnEscape: KeyListener = key => key.key === "Escape";
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
type KeyListener = (key: KeyInput) => boolean | void;
|
|
105
|
+
/**
|
|
106
|
+
* input plugin config. Every distance is in reference px, every duration in milliseconds of
|
|
107
|
+
* `time`, so a time scale and a pause apply to the gestures too.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* createApp({ plugins: [...screen], pluginConfigs: { input: { longPressMs: 300, swipeMinPx: 64 } } });
|
|
112
|
+
* // A merge game carries the item in the hand 8% bigger: a board item resting at scale 0.5 on
|
|
113
|
+
* // screen is drawn at 0.54 from the grab to the drop, even after a press squashed it, then
|
|
114
|
+
* // flies home at 0.5.
|
|
115
|
+
* createApp({ plugins: [...screen], pluginConfigs: { input: { heldScale: 1.08 } } });
|
|
116
|
+
* // A game with its own cursors: the config merges shallowly, so both values are given.
|
|
117
|
+
* createApp({ plugins: [...screen], pluginConfigs: { input: { cursor: { control: "grab", idle: "default" } } } });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
type Config$1 = {
|
|
121
|
+
/** Largest move, in reference px, that still counts as a tap or a long press. */tapSlopPx: number; /** Hold time, in ms of `time`, after which a `Pressable` answers. */
|
|
122
|
+
longPressMs: number; /** Move, in reference px, that turns a press on a `Draggable` into a drag. */
|
|
123
|
+
dragStartPx: number; /** Shortest swipe, in reference px. */
|
|
124
|
+
swipeMinPx: number; /** Longest swipe, in ms of `time`, from pointer down to pointer up. */
|
|
125
|
+
swipeMaxMs: number;
|
|
126
|
+
/**
|
|
127
|
+
* How much bigger the view in the hand is drawn: its rest scale in root space times this, from
|
|
128
|
+
* the grab to the release, so a squash a press left on it does not shrink the lift. A view with
|
|
129
|
+
* no recorded rest starts from its scale at the grab. The drop, the cancel and the abort write
|
|
130
|
+
* the rest scale back, so the way home starts at the view's own size. `1` changes nothing.
|
|
131
|
+
*/
|
|
132
|
+
heldScale: number;
|
|
133
|
+
/**
|
|
134
|
+
* CSS cursors of the canvas. `control` shows while a mouse or a pen rests on a view that
|
|
135
|
+
* carries `Tappable`, `Draggable`, `Pressable`, `Swipeable` or a component another plugin
|
|
136
|
+
* registered through `controls.add`, such as ui's `LocalWrite`; `idle` shows everywhere else.
|
|
137
|
+
* `""` hands the cursor back to the page's own style.
|
|
138
|
+
*/
|
|
139
|
+
cursor: {
|
|
140
|
+
control: string;
|
|
141
|
+
idle: string;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* input plugin state: the raw queue the DOM handlers write, and the one gesture in progress.
|
|
146
|
+
*/
|
|
147
|
+
type State$1 = {
|
|
148
|
+
/** Written by the DOM handlers, drained by the frame step. */samples: RawSample[];
|
|
149
|
+
phase: GesturePhase; /** The one active pointer. A sample of any other pointer is dropped while this is set. */
|
|
150
|
+
pointerId: number | undefined; /** The pressed or held entity. */
|
|
151
|
+
entity: Entity | undefined; /** The projection key of `entity`, read once at press time. */
|
|
152
|
+
key: {
|
|
153
|
+
projection: string;
|
|
154
|
+
key: string;
|
|
155
|
+
} | undefined; /** Reference px at pointer down. */
|
|
156
|
+
start: Point; /** `Transform` minus the pointer at the moment of the grab. */
|
|
157
|
+
grabOffset: Point; /** Sum of `time.delta` since pointer down. No device clock is ever read. */
|
|
158
|
+
pressedMs: number; /** The drop target that carries `Hovered` during a drag. */
|
|
159
|
+
hovered: Entity | undefined; /** The view that carries `PointerOver`: a press would take it, and a mouse or a pen is over it. */
|
|
160
|
+
pointerOver: Entity | undefined; /** The `Parent` the held view had at the grab. It is hung back under it on the release. */
|
|
161
|
+
parent: Entity | undefined; /** The rest scale of the held view in root space: the base of `heldScale`, set back on drop. */
|
|
162
|
+
restScale: number | undefined; /** The remover `world.projection.mute` returned, while a drag runs. */
|
|
163
|
+
unmute: (() => void) | undefined;
|
|
164
|
+
canvas: HTMLCanvasElement | undefined;
|
|
165
|
+
offFrame: (() => void) | undefined;
|
|
166
|
+
detach: (() => void) | undefined; /** Registered through `onTap`, called in this order on every tap. */
|
|
167
|
+
tapListeners: TapListener[]; /** Registered through `onKey`, called in this order on every key. */
|
|
168
|
+
keyListeners: KeyListener[]; /** Takes the one `keydown` listener off `window`; set while a canvas is attached. */
|
|
169
|
+
detachKeys: (() => void) | undefined; /** `time.wake`, bound in `onInit`: every pointer sample leaves the idle frame rate. */
|
|
170
|
+
wake: (() => void) | undefined; /** The cursor last written on the attached canvas; `undefined` while nothing was written. */
|
|
171
|
+
cursor: string | undefined; /** Component types other plugins registered through `controls.add`, once per registration. */
|
|
172
|
+
controls: AnyComponentType[];
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* input plugin API, `app.input`: the same door for the finger, for a test and for an agent. Each
|
|
176
|
+
* method builds the Answer the gesture would build and hands it to `flow.gate.answer`. Nothing
|
|
177
|
+
* moves: no coordinates, no frames, no `Held`, no `settle`.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* // A headless test plays the board without a screen.
|
|
182
|
+
* app.input.tap({ projection: "board.generators", key: "g1" }); // true: the generator spat an item
|
|
183
|
+
* app.input.drag({ projection: "board.items", key: "i5" }, { projection: "board.items", key: "i7" });
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
type InputApi = {
|
|
187
|
+
/**
|
|
188
|
+
* Reads the `Tappable` of a view and answers its intent.
|
|
189
|
+
*
|
|
190
|
+
* @param target - The projection key of a live view, or an entity.
|
|
191
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* // A hidden-object test finds one object without touching a pixel.
|
|
195
|
+
* app.input.tap({ projection: "scene.objects", key: "lamp" });
|
|
196
|
+
* // true: answers { intent: "found", payload: { id: "lamp" } }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
tap(target: Target): boolean;
|
|
200
|
+
/**
|
|
201
|
+
* Reads the `Pressable` of a view and answers its intent, the way a long press would.
|
|
202
|
+
*
|
|
203
|
+
* @param target - The projection key of a live view, or an entity.
|
|
204
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* // An agent opens the info card of an item without holding a finger for 450 ms.
|
|
208
|
+
* app.input.press({ projection: "board.items", key: "i5" });
|
|
209
|
+
* // true: answers { intent: "info", payload: { id: "i5" } }
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
press(target: Target): boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Reads the `Draggable` of the source and the `DropTarget` of the destination and answers the
|
|
215
|
+
* destination's intent with both payloads merged. The destination wins a key both carry.
|
|
216
|
+
*
|
|
217
|
+
* @param from - The view that is carried.
|
|
218
|
+
* @param to - The view it is dropped on; this one names the intent.
|
|
219
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* // The merge test of the fixture game: two items of level 1 become one of level 2.
|
|
223
|
+
* app.input.drag({ projection: "board.items", key: "i5" }, { projection: "board.items", key: "i7" });
|
|
224
|
+
* // true: answers { intent: "merge", payload: { from: "c2", to: "c3" } }
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
drag(from: Target, to: Target): boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Reads the `Swipeable` of a view and answers its intent with the direction in the payload.
|
|
230
|
+
*
|
|
231
|
+
* @param target - The projection key of a live view, or an entity.
|
|
232
|
+
* @param direction - Which way the finger would have gone.
|
|
233
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* // A match-3 test swaps the cell c2 with its right neighbour.
|
|
237
|
+
* app.input.swipe({ projection: "board.cells", key: "c2" }, "right");
|
|
238
|
+
* // true: answers { intent: "swap", payload: { cell: "c2", direction: "right" } }
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
swipe(target: Target, direction: Direction): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Registers a listener called on every tap — a press and a release inside `tapSlopPx` — on the
|
|
244
|
+
* topmost view the hit test accepted, before the `Tappable` answer. A view that carries only
|
|
245
|
+
* `Touchable` reaches the listeners and answers nothing. A listener that throws is logged with
|
|
246
|
+
* its entity, and the listeners after it still run.
|
|
247
|
+
*
|
|
248
|
+
* @param fn - What to run with the tapped entity.
|
|
249
|
+
* @returns The remover; call it to stop listening.
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* // ui applies the local patch of the button the finger tapped.
|
|
253
|
+
* const off = ctx.require(inputPlugin).onTap(entity => {
|
|
254
|
+
* const write = ctx.require(worldPlugin).ecs.get(entity, LocalWrite);
|
|
255
|
+
*
|
|
256
|
+
* if (write !== undefined) applyLocal(entity, write.patch);
|
|
257
|
+
* });
|
|
258
|
+
* off(); // in onStop
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
onTap(fn: TapListener): () => void;
|
|
262
|
+
/**
|
|
263
|
+
* Registers a listener called on every key pressed while the canvas is attached, and on every
|
|
264
|
+
* `key` call. Listeners run in registration order, all of them, each time. A listener that
|
|
265
|
+
* returns `true` marks the key handled, and input calls `preventDefault()` on the DOM event. A
|
|
266
|
+
* listener that throws is logged with its key, and the listeners after it still run.
|
|
267
|
+
*
|
|
268
|
+
* @param fn - What to run with the key; return `true` when it handled it.
|
|
269
|
+
* @returns The remover; call it to stop listening.
|
|
270
|
+
* @example
|
|
271
|
+
* ```ts
|
|
272
|
+
* // ui moves the keyboard focus on Tab and keeps the browser from leaving the canvas.
|
|
273
|
+
* const off = ctx.require(inputPlugin).onKey(key => {
|
|
274
|
+
* if (key.key !== "Tab") return false;
|
|
275
|
+
* moveFocus(key.shift ? -1 : 1);
|
|
276
|
+
*
|
|
277
|
+
* return true;
|
|
278
|
+
* });
|
|
279
|
+
* off(); // in onStop
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
onKey(fn: KeyListener): () => void;
|
|
283
|
+
/**
|
|
284
|
+
* Presses a key without a keyboard: runs the `onKey` listeners exactly as a DOM `keydown` does.
|
|
285
|
+
*
|
|
286
|
+
* @param key - The DOM `KeyboardEvent.key`, such as `"Tab"`, `"Enter"`, `" "` or `"Escape"`.
|
|
287
|
+
* @param options - How the key is pressed.
|
|
288
|
+
* @param options.shift - True presses it with Shift held; the default is `false`.
|
|
289
|
+
* @returns True when a listener handled the key.
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* // A headless test walks the settings popup backwards and closes it.
|
|
293
|
+
* app.input.pressKey("Tab", { shift: true }); // true: ui moved the focus to the previous control
|
|
294
|
+
* app.input.pressKey("Escape"); // true: ui tapped the popup's close button
|
|
295
|
+
* app.input.pressKey("q"); // false: no listener handles it
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
pressKey(key: string, options?: {
|
|
299
|
+
shift?: boolean;
|
|
300
|
+
}): boolean;
|
|
301
|
+
/**
|
|
302
|
+
* The CSS cursor input set on the canvas: `config.cursor.control` while a mouse or a pen rests
|
|
303
|
+
* on a control, `config.cursor.idle` otherwise and whenever nothing is attached.
|
|
304
|
+
*
|
|
305
|
+
* @returns The cursor value.
|
|
306
|
+
* @example
|
|
307
|
+
* ```ts
|
|
308
|
+
* // An e2e check: the mouse rests on the Play button of the home screen.
|
|
309
|
+
* app.input.cursor(); // "pointer"
|
|
310
|
+
* // The mouse moves to the empty sky above it.
|
|
311
|
+
* app.input.cursor(); // ""
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
cursor(): string;
|
|
315
|
+
/**
|
|
316
|
+
* The components that make a view a control besides input's own: the cursor shows
|
|
317
|
+
* `config.cursor.control` over a view that carries one of them.
|
|
318
|
+
*/
|
|
319
|
+
controls: {
|
|
320
|
+
/**
|
|
321
|
+
* Registers a component type of another plugin as a control. A type registered twice stays
|
|
322
|
+
* a control until both removers ran.
|
|
323
|
+
*
|
|
324
|
+
* @param component - The component or tag type that makes a view answer a press.
|
|
325
|
+
* @returns The remover; call it to take the registration back.
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* // ui counts its local-state buttons as controls, so the mouse over a tab shows a hand.
|
|
329
|
+
* const off = ctx.require(inputPlugin).controls.add(LocalWrite);
|
|
330
|
+
* app.input.cursor(); // "pointer" while the mouse rests on the Audio tab
|
|
331
|
+
* off(); // in onStop
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
add(component: AnyComponentType): () => void;
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Resolved dependency APIs.
|
|
339
|
+
*/
|
|
340
|
+
type Deps = {
|
|
341
|
+
time: Api;
|
|
342
|
+
flow: Api$1;
|
|
343
|
+
world: Api$2;
|
|
344
|
+
renderer: Api$3;
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* What the kernel context offers before the deps are attached.
|
|
348
|
+
*
|
|
349
|
+
* `input` owns no event, so `emit` is the kernel's and never called here.
|
|
350
|
+
*/
|
|
351
|
+
type KernelSlice = PluginCtx<Config$1, State$1> & {
|
|
352
|
+
readonly global: object;
|
|
353
|
+
readonly log: Log.LogApi;
|
|
354
|
+
readonly require: Require;
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Domain context of the input plugin: the kernel slice plus the four resolved dependencies.
|
|
358
|
+
*/
|
|
359
|
+
type InputCtx = KernelSlice & {
|
|
360
|
+
readonly deps: Deps;
|
|
361
|
+
};
|
|
362
|
+
declare namespace types_d_exports {
|
|
363
|
+
export { Argument, CompiledMessage, CompiledMessages, Config, ElementNode, Events, I18nApi, I18nCtx, I18nKit, IntlKit, KeysWithoutParameters, Message, Part, RegisteredModule, ResolvedModule, State, StringTable, StringsLoader, TypedTr };
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* One interface element handed to a message as a parameter. Structural on purpose: it mirrors
|
|
367
|
+
* the `DescriptionNode` of `world/projection`, which sits beside `i18n`, so neither plugin
|
|
368
|
+
* imports the other (seam resolution 1).
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* const icon: ElementNode = { type: "icon", props: { name: "hud.coin" }, children: [] };
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
type ElementNode = {
|
|
376
|
+
type: string;
|
|
377
|
+
key?: string;
|
|
378
|
+
props: object;
|
|
379
|
+
children: readonly unknown[];
|
|
380
|
+
};
|
|
381
|
+
/**
|
|
382
|
+
* What a message parameter may be: a word, a number, a list of words, or an element the
|
|
383
|
+
* interface draws in the middle of the sentence.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* const reward: Argument = 25;
|
|
388
|
+
* const players: Argument = ["Ann", "Bo"];
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
type Argument = string | number | readonly string[] | ElementNode;
|
|
392
|
+
/**
|
|
393
|
+
* What `tr` returns: the key and the parameters, frozen. No locale and no string is read at the
|
|
394
|
+
* call site, so the same value survives a locale change and can be compared and logged.
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```ts
|
|
398
|
+
* const orders: Message<"hud.orders", { n: number }> = { key: "hud.orders", params: { n: 3 } };
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
type Message<Key extends string = string, Values = Record<string, Argument>> = {
|
|
402
|
+
key: Key;
|
|
403
|
+
params?: Values;
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* One piece of a formatted message: a run of text, or an element to draw in place. The consumer
|
|
407
|
+
* never joins the pieces itself, which is what keeps an icon inside a sentence possible.
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* ```ts
|
|
411
|
+
* const parts: Part[] = [
|
|
412
|
+
* { kind: "element", node: { type: "icon", props: { name: "hud.coin" }, children: [] } },
|
|
413
|
+
* { kind: "text", text: " 25" }
|
|
414
|
+
* ];
|
|
415
|
+
* ```
|
|
416
|
+
*/
|
|
417
|
+
type Part = {
|
|
418
|
+
kind: "text";
|
|
419
|
+
text: string;
|
|
420
|
+
} | {
|
|
421
|
+
kind: "element";
|
|
422
|
+
node: ElementNode;
|
|
423
|
+
};
|
|
424
|
+
/**
|
|
425
|
+
* The `Intl.*` formatters of one locale, each memoised by the JSON of its options. Compiled
|
|
426
|
+
* messages are handed one and never construct a formatter themselves.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```ts
|
|
430
|
+
* const intl = createIntlKit("ru");
|
|
431
|
+
* intl.plural().select(3); // "few"
|
|
432
|
+
* ```
|
|
433
|
+
*/
|
|
434
|
+
type IntlKit = {
|
|
435
|
+
/** The locale every formatter of this kit is built for. */locale: string; /** The plural rules of the locale; `{ type: "ordinal" }` for `selectordinal`. */
|
|
436
|
+
plural(options?: Intl.PluralRulesOptions): Intl.PluralRules; /** The number format of the locale. */
|
|
437
|
+
number(options?: Intl.NumberFormatOptions): Intl.NumberFormat; /** The list format of the locale, which ICU MessageFormat 1 has no syntax for. */
|
|
438
|
+
list(options?: Intl.ListFormatOptions): Intl.ListFormat; /** The date and time format of the locale. */
|
|
439
|
+
date(options?: Intl.DateTimeFormatOptions): Intl.DateTimeFormat;
|
|
440
|
+
};
|
|
441
|
+
/**
|
|
442
|
+
* One message as the build wrote it: a plain function of the parameters and the locale's kit.
|
|
443
|
+
* There is no ICU parser at run time.
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```ts
|
|
447
|
+
* const hello: CompiledMessage = p => [{ kind: "text", text: `Hi ${String(p.name)}` }];
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
450
|
+
type CompiledMessage = (params: Record<string, unknown>, intl: IntlKit) => Part[];
|
|
451
|
+
/**
|
|
452
|
+
* One compiled module: every message of one locale by its key.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* const en: CompiledMessages = { "home.play": () => [{ kind: "text", text: "Play" }] };
|
|
457
|
+
* ```
|
|
458
|
+
*/
|
|
459
|
+
type CompiledMessages = Record<string, CompiledMessage>;
|
|
460
|
+
/**
|
|
461
|
+
* A compiled module that is imported on first use. A module namespace is unwrapped to its
|
|
462
|
+
* `default` member (seam resolution 2).
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```ts
|
|
466
|
+
* const loadEnglish: StringsLoader = () => import("./generated/strings.en");
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
type StringsLoader = () => Promise<CompiledMessages | {
|
|
470
|
+
default: CompiledMessages;
|
|
471
|
+
}>;
|
|
472
|
+
/**
|
|
473
|
+
* i18n plugin config.
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```ts
|
|
477
|
+
* createApp({ pluginConfigs: { i18n: { locale: "ru", fallback: "en", locales: {} } } });
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
type Config = {
|
|
481
|
+
/** The locale at start. */locale: string; /** The locale a missing key is read from before it is reported missing. */
|
|
482
|
+
fallback: string;
|
|
483
|
+
/**
|
|
484
|
+
* Compiled modules outside features, per locale; a loader is imported on first use and its
|
|
485
|
+
* `default` is unwrapped.
|
|
486
|
+
*/
|
|
487
|
+
locales: Record<string, CompiledMessages | StringsLoader>;
|
|
488
|
+
};
|
|
489
|
+
/**
|
|
490
|
+
* One module registered for one locale, with the name it can be blamed under when two of them
|
|
491
|
+
* carry the same key.
|
|
492
|
+
*/
|
|
493
|
+
type RegisteredModule = {
|
|
494
|
+
/** The feature that brought the module, or `"pluginConfigs.i18n"` for a configured one. */from: string; /** The module itself, or the loader that fetches it on first use. */
|
|
495
|
+
messages: CompiledMessages | StringsLoader;
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* One registered module whose loader already ran, ready to be merged into a locale.
|
|
499
|
+
*/
|
|
500
|
+
type ResolvedModule = {
|
|
501
|
+
/** The feature that brought the module, or `"pluginConfigs.i18n"` for a configured one. */from: string; /** The module itself. */
|
|
502
|
+
messages: CompiledMessages;
|
|
503
|
+
};
|
|
504
|
+
/**
|
|
505
|
+
* i18n plugin state.
|
|
506
|
+
*/
|
|
507
|
+
type State = {
|
|
508
|
+
/** The current locale; `config.locale` until `setLocale`. */locale: string; /** Per locale, one entry per feature and one for the config, filled in `onStart`. */
|
|
509
|
+
registered: Map<string, RegisteredModule[]>; /** Merged and resolved modules per locale. */
|
|
510
|
+
loaded: Map<string, CompiledMessages>; /** One kit per locale, built on first use. */
|
|
511
|
+
intl: Map<string, IntlKit>; /** Keys already reported missing, so each warns once. */
|
|
512
|
+
warned: Set<string>; /** The `setLocale` in flight; a later call waits for it. */
|
|
513
|
+
loading: Promise<void> | undefined;
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* i18n plugin events.
|
|
517
|
+
*/
|
|
518
|
+
type Events = {
|
|
519
|
+
/** The current locale changed and its messages are loaded. */"i18n:locale-changed": {
|
|
520
|
+
locale: string;
|
|
521
|
+
};
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* i18n plugin API, `app.i18n`. Messages are data everywhere else in the game; this is the one
|
|
525
|
+
* place a locale is read and a sentence becomes parts.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* ```ts
|
|
529
|
+
* // A settings screen shows the languages the game was built with and switches to one.
|
|
530
|
+
* app.i18n.locales(); // ["en", "ru"]
|
|
531
|
+
* await app.i18n.setLocale("en");
|
|
532
|
+
* app.i18n.plain(tr("hud.orders", { n: 3 })); // "3 orders"
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
535
|
+
type I18nApi = {
|
|
536
|
+
/**
|
|
537
|
+
* The locale every `format` without a second argument reads.
|
|
538
|
+
*
|
|
539
|
+
* @returns The current locale.
|
|
540
|
+
* @example
|
|
541
|
+
* ```ts
|
|
542
|
+
* app.i18n.locale(); // "ru", the pluginConfigs.i18n.locale the game started with
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
locale(): string;
|
|
546
|
+
/**
|
|
547
|
+
* Switches the current locale. A lazy module is imported once, then `i18n:locale-changed` is
|
|
548
|
+
* emitted; `text` re-resolves every message on it. A call for the locale already current
|
|
549
|
+
* resolves at once and emits nothing.
|
|
550
|
+
*
|
|
551
|
+
* @param locale - The locale to switch to.
|
|
552
|
+
* @returns A promise that settles when the messages of that locale are loaded.
|
|
553
|
+
* @throws {Error} When no feature and no config brought a module for the locale.
|
|
554
|
+
* @example
|
|
555
|
+
* ```ts
|
|
556
|
+
* // features/settings/nodes.ts: the language button of the settings screen.
|
|
557
|
+
* await app.i18n.setLocale("en"); // loads the compiled module, then emits i18n:locale-changed
|
|
558
|
+
* app.i18n.locale(); // "en"
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
setLocale(locale: string): Promise<void>;
|
|
562
|
+
/**
|
|
563
|
+
* Formats a message in the current locale. Resolution order is the current locale, then
|
|
564
|
+
* `config.fallback`, then missing: a missing key gives one text part `⟦key⟧` and one warning.
|
|
565
|
+
*
|
|
566
|
+
* @param message - What `tr` returned.
|
|
567
|
+
* @returns The parts of the sentence, adjacent text merged.
|
|
568
|
+
* @example
|
|
569
|
+
* ```ts
|
|
570
|
+
* // text resolves the `content` of a Text component every time the locale changes.
|
|
571
|
+
* app.i18n.format(tr("hud.orders", { n: 3 })); // [{ kind: "text", text: "3 заказа" }]
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
format(message: Message): readonly Part[];
|
|
575
|
+
/**
|
|
576
|
+
* Formats a message in another registered locale, without switching. `ui.lint` measures a
|
|
577
|
+
* label in every locale this way.
|
|
578
|
+
*
|
|
579
|
+
* @param message - What `tr` returned.
|
|
580
|
+
* @param locale - The registered locale to read.
|
|
581
|
+
* @returns The parts of the sentence in that locale.
|
|
582
|
+
* @throws {Error} When the locale is not registered, or is registered but not loaded yet.
|
|
583
|
+
* @example
|
|
584
|
+
* ```ts
|
|
585
|
+
* // ui.lint does this for every entry of app.i18n.locales() and keeps the widest result.
|
|
586
|
+
* app.i18n.format(tr("hud.orders", { n: 3 }), "en"); // [{ kind: "text", text: "3 orders" }]
|
|
587
|
+
* app.i18n.locale(); // "ru": reading another locale never switches
|
|
588
|
+
* ```
|
|
589
|
+
*/
|
|
590
|
+
format(message: Message, locale: string): readonly Part[];
|
|
591
|
+
/**
|
|
592
|
+
* The parts joined into one string, elements dropped. For logs, tests and `flow.describe()`.
|
|
593
|
+
*
|
|
594
|
+
* @param message - What `tr` returned.
|
|
595
|
+
* @returns The sentence as text.
|
|
596
|
+
* @example
|
|
597
|
+
* ```ts
|
|
598
|
+
* // A test asserts what the HUD says without touching the renderer.
|
|
599
|
+
* app.i18n.plain(tr("hud.coins", { n: 25, icon: coinIcon })); // "25": the icon is dropped
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
plain(message: Message): string;
|
|
603
|
+
/**
|
|
604
|
+
* Tells whether a key exists in the current locale or in the fallback.
|
|
605
|
+
*
|
|
606
|
+
* @param key - The message key.
|
|
607
|
+
* @returns True when `format` would find a message for it.
|
|
608
|
+
* @example
|
|
609
|
+
* ```ts
|
|
610
|
+
* // A projection hides the label of an order that has no description yet.
|
|
611
|
+
* app.i18n.has("orders.hint"); // false: no locale brought that key
|
|
612
|
+
* ```
|
|
613
|
+
*/
|
|
614
|
+
has(key: string): boolean;
|
|
615
|
+
/**
|
|
616
|
+
* Every locale at least one feature or the config brought a module for, sorted.
|
|
617
|
+
*
|
|
618
|
+
* @returns The registered locales.
|
|
619
|
+
* @example
|
|
620
|
+
* ```ts
|
|
621
|
+
* // The language menu is built from this list, and ui.lint measures the widest locale.
|
|
622
|
+
* app.i18n.locales(); // ["en", "ru"]
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
locales(): readonly string[];
|
|
626
|
+
};
|
|
627
|
+
/**
|
|
628
|
+
* What the kernel context offers this plugin.
|
|
629
|
+
*
|
|
630
|
+
* `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
|
|
631
|
+
* row 8), so the own event reaches the context the kernel hands the factories and `emit` is the
|
|
632
|
+
* kernel's own, i18n-typed one. No member of the context is cast.
|
|
633
|
+
*/
|
|
634
|
+
type I18nCtx = PluginCtx<Config, State, Events> & {
|
|
635
|
+
readonly global: object;
|
|
636
|
+
readonly log: Log.LogApi;
|
|
637
|
+
readonly require: Require;
|
|
638
|
+
};
|
|
639
|
+
/**
|
|
640
|
+
* The key-to-parameters table a game generates: `generated/strings.ts` exports it as `Strings`.
|
|
641
|
+
* A key with no parameters carries `Record<never, never>`.
|
|
642
|
+
*
|
|
643
|
+
* @example
|
|
644
|
+
* ```ts
|
|
645
|
+
* const table: StringTable = { "hud.orders": { n: 3 }, "orders.complete": {} };
|
|
646
|
+
* ```
|
|
647
|
+
*/
|
|
648
|
+
type StringTable = Record<string, unknown>;
|
|
649
|
+
/**
|
|
650
|
+
* The keys of a table whose message takes no parameters, so `tr` may be called with the key
|
|
651
|
+
* alone.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* ```ts
|
|
655
|
+
* type Keys = KeysWithoutParameters<{ "home.play": Record<never, never>; "hud.orders": { n: number } }>;
|
|
656
|
+
* // "home.play"
|
|
657
|
+
* ```
|
|
658
|
+
*/
|
|
659
|
+
type KeysWithoutParameters<Table extends StringTable> = { [Key in keyof Table]: Record<never, never> extends Table[Key] ? Key : never }[keyof Table] & string;
|
|
660
|
+
/**
|
|
661
|
+
* `tr` as one game reads it: the key is checked against the generated table, and the parameters
|
|
662
|
+
* are the ones that key declares. A key with no parameters takes no second argument.
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```ts
|
|
666
|
+
* const { tr } = i18nFor<{ "hud.orders": { n: number } }>();
|
|
667
|
+
* tr("hud.orders", { n: 3 }); // { key: "hud.orders", params: { n: 3 } }
|
|
668
|
+
* ```
|
|
669
|
+
*/
|
|
670
|
+
type TypedTr<Table extends StringTable> = {
|
|
671
|
+
<Key extends KeysWithoutParameters<Table>>(key: Key): Message<Key, Table[Key]>;
|
|
672
|
+
<Key extends keyof Table & string>(key: Key, params: Table[Key]): Message<Key, Table[Key]>;
|
|
673
|
+
};
|
|
674
|
+
/**
|
|
675
|
+
* What `defineGame` spreads from this plugin.
|
|
676
|
+
*
|
|
677
|
+
* @example
|
|
678
|
+
* ```ts
|
|
679
|
+
* const kit: I18nKit<{ "home.play": Record<never, never> }> = i18nFor();
|
|
680
|
+
* kit.tr("home.play"); // { key: "home.play" }
|
|
681
|
+
* ```
|
|
682
|
+
*/
|
|
683
|
+
type I18nKit<Table extends StringTable> = {
|
|
684
|
+
tr: TypedTr<Table>;
|
|
685
|
+
};
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/plugins/renderer/components.d.ts
|
|
688
|
+
/**
|
|
689
|
+
* Where a view sits, in reference units and radians. Relative to the `Parent` when there is one.
|
|
690
|
+
* `pivot` is the local point the view turns and scales around, and `x`, `y` is where that point
|
|
691
|
+
* lands; the default pivot `{ x: 0, y: 0 }` is the view's own origin.
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```ts
|
|
695
|
+
* // A 200 x 80 button that grows around its centre.
|
|
696
|
+
* const value: TransformValue = {
|
|
697
|
+
* x: 640, y: 340, rotation: 0, scale: 1.1, pivot: { x: 100, y: 40 }
|
|
698
|
+
* };
|
|
699
|
+
* ```
|
|
700
|
+
*/
|
|
701
|
+
type TransformValue = {
|
|
702
|
+
x: number;
|
|
703
|
+
y: number;
|
|
704
|
+
rotation: number;
|
|
705
|
+
scale: number;
|
|
706
|
+
pivot: Point$1;
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
709
|
+
* How a sized sprite fills its box: `"fill"` stretches, `"contain"` scales uniformly inside the
|
|
710
|
+
* box and centres, `"cover"` scales uniformly over the box and crops the overflow.
|
|
711
|
+
*
|
|
712
|
+
* @example
|
|
713
|
+
* ```ts
|
|
714
|
+
* const fit: SpriteFit = "cover";
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
type SpriteFit = "fill" | "contain" | "cover";
|
|
718
|
+
/**
|
|
719
|
+
* One textured quad. `texture` is an asset key; `defineGame` narrows it to the game's keys.
|
|
720
|
+
* `width` and `height` are the box in reference units; 0 on an axis keeps the texture's own size
|
|
721
|
+
* there. `anchor` is the point of the box that sits on the `Transform`.
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* ```ts
|
|
725
|
+
* const value: SpriteValue = {
|
|
726
|
+
* texture: "board.bg-forest-meadow", tint: 0xffffff, alpha: 1, anchor: { x: 0, y: 0 },
|
|
727
|
+
* width: 1080, height: 1920, fit: "cover"
|
|
728
|
+
* };
|
|
729
|
+
* ```
|
|
730
|
+
*/
|
|
731
|
+
type SpriteValue = {
|
|
732
|
+
texture: string;
|
|
733
|
+
tint: number;
|
|
734
|
+
alpha: number;
|
|
735
|
+
anchor: Point$1;
|
|
736
|
+
width: number;
|
|
737
|
+
height: number;
|
|
738
|
+
fit: SpriteFit;
|
|
739
|
+
};
|
|
740
|
+
/**
|
|
741
|
+
* A stretchable panel. The slice borders come with the texture, not with the component. `debug`
|
|
742
|
+
* strokes the bounds and the four cut lines over the panel: cyan, red when the corners overlap or
|
|
743
|
+
* the texture is missing.
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```ts
|
|
747
|
+
* const value: NineSliceValue = {
|
|
748
|
+
* texture: "ui.panel", width: 600, height: 320, alpha: 1, tint: 0xffffff, debug: false
|
|
749
|
+
* };
|
|
750
|
+
* // The board tray, outlined while its insets are checked.
|
|
751
|
+
* const tray: NineSliceValue = {
|
|
752
|
+
* texture: "board.board-tray", width: 1000, height: 1040, alpha: 1, tint: 0xffffff, debug: true
|
|
753
|
+
* };
|
|
754
|
+
* ```
|
|
755
|
+
*/
|
|
756
|
+
type NineSliceValue = {
|
|
757
|
+
texture: string;
|
|
758
|
+
width: number;
|
|
759
|
+
height: number;
|
|
760
|
+
alpha: number;
|
|
761
|
+
tint: number;
|
|
762
|
+
debug: boolean;
|
|
763
|
+
};
|
|
764
|
+
/**
|
|
765
|
+
* The escape hatch: the game owns a Pixi object and `sync` only attaches and detaches it.
|
|
766
|
+
*
|
|
767
|
+
* @example
|
|
768
|
+
* ```ts
|
|
769
|
+
* const value: DisplayValue = { object: spineAnimation };
|
|
770
|
+
* ```
|
|
771
|
+
*/
|
|
772
|
+
type DisplayValue = {
|
|
773
|
+
object: unknown;
|
|
774
|
+
};
|
|
775
|
+
/**
|
|
776
|
+
* A filled rounded rectangle or a triangle, anchored at the top left of the `Transform`. `kind`
|
|
777
|
+
* picks the outline: `"rect"` (the default) or `"triangle"`, which fills its `w × h` box pointing
|
|
778
|
+
* right (rotate the element for another direction) and ignores `radius`. `fillAlpha` is the alpha
|
|
779
|
+
* of the fill alone: 0 draws only the stroke, a ring. `alpha` fades the whole shape. `dash` is the
|
|
780
|
+
* dash length of the stroke in reference units, with gaps of half a dash; 0 strokes a solid line.
|
|
781
|
+
* `clip` masks the children of the entity to the shape, always solid, which is how `ui` draws a
|
|
782
|
+
* scroll and an overflow.
|
|
783
|
+
*
|
|
784
|
+
* @example
|
|
785
|
+
* ```ts
|
|
786
|
+
* const value: ShapeValue = {
|
|
787
|
+
* kind: "rect", w: 320, h: 96, fill: 0x101018, fillAlpha: 1, alpha: 1, radius: 16,
|
|
788
|
+
* stroke: 0x000000, strokeWidth: 0, dash: 0, clip: false
|
|
789
|
+
* };
|
|
790
|
+
* // The honey ring on a selected cell: a stroke and no fill.
|
|
791
|
+
* const ring: ShapeValue = {
|
|
792
|
+
* kind: "rect", w: 140, h: 140, fill: 0xffffff, fillAlpha: 0, alpha: 1, radius: 24,
|
|
793
|
+
* stroke: 0xffc233, strokeWidth: 6, dash: 0, clip: false
|
|
794
|
+
* };
|
|
795
|
+
* // The play glyph of a watch button, and a dashed focus ring: 10 u dashes, 5 u gaps.
|
|
796
|
+
* const play: ShapeValue = { ...ring, kind: "triangle", w: 36, h: 40, fillAlpha: 1, strokeWidth: 0 };
|
|
797
|
+
* const focus: ShapeValue = { ...ring, w: 200, h: 80, stroke: 0x3a2212, strokeWidth: 4, dash: 10 };
|
|
798
|
+
* ```
|
|
799
|
+
*/
|
|
800
|
+
type ShapeValue = {
|
|
801
|
+
kind: "rect" | "triangle";
|
|
802
|
+
w: number;
|
|
803
|
+
h: number;
|
|
804
|
+
fill: number;
|
|
805
|
+
fillAlpha: number;
|
|
806
|
+
alpha: number;
|
|
807
|
+
radius: number;
|
|
808
|
+
stroke: number;
|
|
809
|
+
strokeWidth: number;
|
|
810
|
+
dash: number;
|
|
811
|
+
clip: boolean;
|
|
812
|
+
};
|
|
813
|
+
/**
|
|
814
|
+
* What `sprite()` takes: the two things every visual needs, and the four that have defaults.
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* ```ts
|
|
818
|
+
* const options: SpriteOptions = { texture: "board.cell", at: { x: 540, y: 300 } };
|
|
819
|
+
* ```
|
|
820
|
+
*/
|
|
821
|
+
type SpriteOptions = {
|
|
822
|
+
texture: string;
|
|
823
|
+
at: Point$1;
|
|
824
|
+
tint?: number;
|
|
825
|
+
alpha?: number;
|
|
826
|
+
anchor?: Point$1;
|
|
827
|
+
scale?: number;
|
|
828
|
+
};
|
|
829
|
+
/**
|
|
830
|
+
* Where a view sits: reference units, radians, uniform scale, and the local point it turns
|
|
831
|
+
* around.
|
|
832
|
+
*/
|
|
833
|
+
declare const Transform: ComponentType<TransformValue>;
|
|
834
|
+
/**
|
|
835
|
+
* One textured quad. `texture` is an asset key, resolved through the texture providers; a size
|
|
836
|
+
* and a `fit` draw it into a box.
|
|
837
|
+
*/
|
|
838
|
+
declare const Sprite: ComponentType<SpriteValue>;
|
|
839
|
+
/**
|
|
840
|
+
* A stretchable panel, sized in reference units, with its own alpha and tint. `debug: true` draws
|
|
841
|
+
* the slice outline over it.
|
|
842
|
+
*/
|
|
843
|
+
declare const NineSlice: ComponentType<{
|
|
844
|
+
texture: string;
|
|
845
|
+
width: number;
|
|
846
|
+
height: number;
|
|
847
|
+
alpha: number;
|
|
848
|
+
tint: number;
|
|
849
|
+
debug: boolean;
|
|
850
|
+
}>;
|
|
851
|
+
/**
|
|
852
|
+
* The entity this view moves with. `0` means no parent.
|
|
853
|
+
*/
|
|
854
|
+
declare const Parent: ComponentType<{
|
|
855
|
+
entity: number;
|
|
856
|
+
}>;
|
|
857
|
+
/**
|
|
858
|
+
* A display object the game owns. Never pooled, never destroyed by `sync`.
|
|
859
|
+
*/
|
|
860
|
+
declare const Display: ComponentType<DisplayValue>;
|
|
861
|
+
/**
|
|
862
|
+
* A filled rounded rectangle or a right-pointing triangle, drawn with Pixi `Graphics` and redrawn
|
|
863
|
+
* only when a field changed. `fillAlpha: 0` draws only the stroke, `dash` above 0 dashes it.
|
|
864
|
+
* `clip: true` masks the children of the entity to the shape.
|
|
865
|
+
*/
|
|
866
|
+
declare const Shape: ComponentType<ShapeValue>;
|
|
867
|
+
/**
|
|
868
|
+
* Bundles the two components every visual needs, so a projection `view` reads as one line.
|
|
869
|
+
*
|
|
870
|
+
* @param options - Texture key, position, and the four values that have defaults.
|
|
871
|
+
* @returns The `Sprite` and the `Transform` value, in that order.
|
|
872
|
+
* @example
|
|
873
|
+
* ```ts
|
|
874
|
+
* sprite({ texture: "board.cell", at: { x: 540, y: 300 } });
|
|
875
|
+
* // [Sprite({ texture: "board.cell", tint: 0xffffff, alpha: 1, anchor: { x: 0.5, y: 0.5 },
|
|
876
|
+
* // width: 0, height: 0, fit: "fill" }),
|
|
877
|
+
* // Transform({ x: 540, y: 300, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } })]
|
|
878
|
+
* ```
|
|
879
|
+
*/
|
|
880
|
+
declare function sprite(options: SpriteOptions): [ComponentValue<SpriteValue>, ComponentValue<TransformValue>];
|
|
881
|
+
//#endregion
|
|
882
|
+
//#region src/plugins/ui/jsx/component.d.ts
|
|
883
|
+
/**
|
|
884
|
+
* Declares a component: a piece of interface with local state, outcomes, or both. The view runs
|
|
885
|
+
* at every reconcile with the instance's own local state, never at build time.
|
|
886
|
+
*
|
|
887
|
+
* @param name - Unique name across every feature of the game.
|
|
888
|
+
* @param spec - The initial local state, the outcomes a popup resolves with, and the view.
|
|
889
|
+
* @returns The component, ready to stand in a tag and in a feature's `ui` key.
|
|
890
|
+
* @example
|
|
891
|
+
* ```ts
|
|
892
|
+
* const Panel = defineComponent("Panel", {
|
|
893
|
+
* local: { tab: "audio" },
|
|
894
|
+
* view: (props: { volume: number }, local) => ({ type: "column", props: {}, children: [] })
|
|
895
|
+
* });
|
|
896
|
+
* Panel.name; // "Panel"
|
|
897
|
+
* ```
|
|
898
|
+
*/
|
|
899
|
+
declare function defineComponent<Properties extends object, Local extends object, Outcomes extends Record<string, unknown>>(name: string, spec: ComponentSpec<Properties, Local, Outcomes> & {
|
|
900
|
+
readonly outcomes: Outcomes;
|
|
901
|
+
}): ComponentDefinition<Properties, Local, Outcomes>;
|
|
902
|
+
/**
|
|
903
|
+
* Declares a component without outcomes: it may stand in a tree, never in a `popup`.
|
|
904
|
+
*
|
|
905
|
+
* @param name - Unique name across every feature of the game.
|
|
906
|
+
* @param spec - The initial local state and the view.
|
|
907
|
+
* @returns The component, ready to stand in a tag.
|
|
908
|
+
* @example
|
|
909
|
+
* ```ts
|
|
910
|
+
* const Row = defineComponent("Row", { view: () => ({ type: "row", props: {}, children: [] }) });
|
|
911
|
+
* Row.outcomes; // undefined
|
|
912
|
+
* ```
|
|
913
|
+
*/
|
|
914
|
+
declare function defineComponent<Properties extends object, Local extends object>(name: string, spec: ComponentSpec<Properties, Local, never>): ComponentDefinition<Properties, Local, undefined>;
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region src/plugins/ui/styles/types.d.ts
|
|
917
|
+
/**
|
|
918
|
+
* A length that may also come from the notch. The four tokens are replaced by `resolve` with the
|
|
919
|
+
* matching `viewport.safeArea` value.
|
|
920
|
+
*
|
|
921
|
+
* @example
|
|
922
|
+
* ```ts
|
|
923
|
+
* const top: SafeAreaToken = "safeArea.top";
|
|
924
|
+
* ```
|
|
925
|
+
*/
|
|
926
|
+
type SafeAreaToken = "safeArea.top" | "safeArea.right" | "safeArea.bottom" | "safeArea.left";
|
|
927
|
+
/**
|
|
928
|
+
* A length in reference units, or one of the safe-area tokens.
|
|
929
|
+
*
|
|
930
|
+
* @example
|
|
931
|
+
* ```ts
|
|
932
|
+
* const length: Length = 24;
|
|
933
|
+
* ```
|
|
934
|
+
*/
|
|
935
|
+
type Length = number | SafeAreaToken;
|
|
936
|
+
/**
|
|
937
|
+
* Padding or margin: one number for every edge, or a value per edge.
|
|
938
|
+
*
|
|
939
|
+
* @example
|
|
940
|
+
* ```ts
|
|
941
|
+
* const padding: Edges = { top: "safeArea.top", left: 16 };
|
|
942
|
+
* ```
|
|
943
|
+
*/
|
|
944
|
+
type Edges = Length | {
|
|
945
|
+
top?: Length;
|
|
946
|
+
right?: Length;
|
|
947
|
+
bottom?: Length;
|
|
948
|
+
left?: Length;
|
|
949
|
+
};
|
|
950
|
+
/**
|
|
951
|
+
* A main-axis or cross-axis size: reference units, a percent of the parent, or `"auto"`.
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* ```ts
|
|
955
|
+
* const width: Extent = "50%";
|
|
956
|
+
* ```
|
|
957
|
+
*/
|
|
958
|
+
type Extent = number | `${number}%` | "auto";
|
|
959
|
+
/**
|
|
960
|
+
* The flow group of the vocabulary: what Yoga's flexbox calls are set from.
|
|
961
|
+
*
|
|
962
|
+
* @example
|
|
963
|
+
* ```ts
|
|
964
|
+
* const flow: FlowStyle = { direction: "row", gap: 12, justify: "between" };
|
|
965
|
+
* ```
|
|
966
|
+
*/
|
|
967
|
+
type FlowStyle = {
|
|
968
|
+
direction?: "row" | "column";
|
|
969
|
+
wrap?: boolean;
|
|
970
|
+
justify?: "start" | "center" | "end" | "between" | "around" | "evenly";
|
|
971
|
+
align?: "start" | "center" | "end" | "stretch";
|
|
972
|
+
alignSelf?: "auto" | "start" | "center" | "end" | "stretch";
|
|
973
|
+
gap?: number;
|
|
974
|
+
grow?: number;
|
|
975
|
+
shrink?: number;
|
|
976
|
+
};
|
|
977
|
+
/**
|
|
978
|
+
* The box group: what the element takes up before its children are placed.
|
|
979
|
+
*
|
|
980
|
+
* @example
|
|
981
|
+
* ```ts
|
|
982
|
+
* // The board slot: 970 u of cells, scaled down on a short phone.
|
|
983
|
+
* const box: BoxStyle = { width: 970, height: 970, fit: "contain" };
|
|
984
|
+
* ```
|
|
985
|
+
*/
|
|
986
|
+
type BoxStyle = {
|
|
987
|
+
padding?: Edges;
|
|
988
|
+
margin?: Edges;
|
|
989
|
+
width?: Extent;
|
|
990
|
+
height?: Extent;
|
|
991
|
+
minWidth?: Extent;
|
|
992
|
+
minHeight?: Extent;
|
|
993
|
+
maxWidth?: Extent;
|
|
994
|
+
maxHeight?: Extent;
|
|
995
|
+
aspect?: number; /** `"hidden"` clips the children to the rect, as a scroll container does. */
|
|
996
|
+
overflow?: "visible" | "hidden" | "scroll";
|
|
997
|
+
/**
|
|
998
|
+
* `"contain"`: the element keeps its own size and leaves the flow, then is scaled down to fit
|
|
999
|
+
* the content box of its parent and centred in it. Its children keep their natural rects.
|
|
1000
|
+
*/
|
|
1001
|
+
fit?: "contain";
|
|
1002
|
+
};
|
|
1003
|
+
/**
|
|
1004
|
+
* The position group. `reason` is the comment `lint()` asks for next to an absolute element.
|
|
1005
|
+
*
|
|
1006
|
+
* @example
|
|
1007
|
+
* ```ts
|
|
1008
|
+
* const pinned: PositionStyle = { position: "absolute", top: 0, right: 0, reason: "badge" };
|
|
1009
|
+
* // The order strip of the board drawn over the tray that follows it.
|
|
1010
|
+
* const strip: PositionStyle = { zIndex: 1 };
|
|
1011
|
+
* ```
|
|
1012
|
+
*/
|
|
1013
|
+
type PositionStyle = {
|
|
1014
|
+
position?: "relative" | "absolute";
|
|
1015
|
+
left?: Length;
|
|
1016
|
+
top?: Length;
|
|
1017
|
+
right?: Length;
|
|
1018
|
+
bottom?: Length;
|
|
1019
|
+
reason?: string;
|
|
1020
|
+
/**
|
|
1021
|
+
* The draw order among the siblings, an integer: a higher one draws over a lower one, and a
|
|
1022
|
+
* sibling without it draws at 0 in markup order. Ignored on a root element, which `lint()`
|
|
1023
|
+
* reports; a root draws at the order of its layer.
|
|
1024
|
+
*/
|
|
1025
|
+
zIndex?: number;
|
|
1026
|
+
};
|
|
1027
|
+
/**
|
|
1028
|
+
* The visual group: what is written to `Shape`, `Sprite` or `NineSlice`. There is no font size
|
|
1029
|
+
* here — the size of a text comes from its text style key.
|
|
1030
|
+
*
|
|
1031
|
+
* @example
|
|
1032
|
+
* ```ts
|
|
1033
|
+
* // A wooden button: the nine-slice of the style, greyed while disabled.
|
|
1034
|
+
* const visual: VisualStyle = { nineSlice: "ui.button-wood", alpha: 1, tint: 0xffffff };
|
|
1035
|
+
* // The popup board while its insets are checked: the slice lines drawn over it.
|
|
1036
|
+
* const checked: VisualStyle = { nineSlice: "ui.panel-signboard", debug: true };
|
|
1037
|
+
* // The play glyph of the Watch button, and a dashed ring around a slot.
|
|
1038
|
+
* const play: VisualStyle = { shape: "triangle", fill: 0xfffbe8 };
|
|
1039
|
+
* const slot: VisualStyle = { stroke: 0x3a2212, strokeWidth: 4, dash: 10, radius: 24 };
|
|
1040
|
+
* ```
|
|
1041
|
+
*/
|
|
1042
|
+
type VisualStyle<Asset extends string = string> = {
|
|
1043
|
+
fill?: number;
|
|
1044
|
+
stroke?: number;
|
|
1045
|
+
strokeWidth?: number;
|
|
1046
|
+
radius?: number;
|
|
1047
|
+
alpha?: number;
|
|
1048
|
+
/**
|
|
1049
|
+
* The shape of the rectangle an element without a nine-slice is drawn with: `"rect"` (the
|
|
1050
|
+
* default) or `"triangle"`, which fills the box pointing right and ignores `radius`. Turn the
|
|
1051
|
+
* element with `rotation` for another direction.
|
|
1052
|
+
*/
|
|
1053
|
+
shape?: "rect" | "triangle"; /** The dash length of the stroke in reference units, with gaps of half a dash; 0 is solid. */
|
|
1054
|
+
dash?: number;
|
|
1055
|
+
/**
|
|
1056
|
+
* The asset key of a nine-slice drawn at the rect instead of the rounded rectangle. Any tag
|
|
1057
|
+
* but `image`, `icon` and `text` takes it; a clipping element (`scroll`, `overflow: "hidden"`)
|
|
1058
|
+
* keeps its rectangle, which carries the clip.
|
|
1059
|
+
*/
|
|
1060
|
+
nineSlice?: Asset; /** Multiplies the colour of a nine-slice or an image. */
|
|
1061
|
+
tint?: number;
|
|
1062
|
+
/**
|
|
1063
|
+
* Outlines the nine-slice of the style: its bounds and the four slice lines, cyan, red when
|
|
1064
|
+
* the corners overlap or the texture is missing. A look for checking art, never for a player.
|
|
1065
|
+
*/
|
|
1066
|
+
debug?: boolean;
|
|
1067
|
+
};
|
|
1068
|
+
/**
|
|
1069
|
+
* Where an element turns and scales: its centre, the middle of its top edge, its top-left
|
|
1070
|
+
* corner, or a point given in fractions of its box.
|
|
1071
|
+
*
|
|
1072
|
+
* @example
|
|
1073
|
+
* ```ts
|
|
1074
|
+
* // A sign that hangs from its ropes swings around the middle of its top edge.
|
|
1075
|
+
* const origin: Origin = "top";
|
|
1076
|
+
* ```
|
|
1077
|
+
*/
|
|
1078
|
+
type Origin = "center" | "top" | "topLeft" | {
|
|
1079
|
+
x: number;
|
|
1080
|
+
y: number;
|
|
1081
|
+
};
|
|
1082
|
+
/**
|
|
1083
|
+
* The transform group: how an element is drawn, never where it is laid out. Written into the
|
|
1084
|
+
* rest `Transform`, so a hover lift or a pressed sink never moves a sibling.
|
|
1085
|
+
*
|
|
1086
|
+
* @example
|
|
1087
|
+
* ```ts
|
|
1088
|
+
* // A button that lifts under the mouse.
|
|
1089
|
+
* const lift: TransformStyle = { offsetY: -6, scale: 1.05, origin: "center" };
|
|
1090
|
+
* // The title plaque of a popup, tilted by 1.5 degrees around the middle of its top edge.
|
|
1091
|
+
* const plaque: TransformStyle = { rotation: -0.026, origin: "top" };
|
|
1092
|
+
* ```
|
|
1093
|
+
*/
|
|
1094
|
+
type TransformStyle = {
|
|
1095
|
+
/** Moves the drawn element right, in reference units. */offsetX?: number; /** Moves the drawn element down, in reference units. */
|
|
1096
|
+
offsetY?: number; /** Uniform scale around the origin. */
|
|
1097
|
+
scale?: number; /** Turns the drawn element around the origin, in radians, clockwise; 0 by default. */
|
|
1098
|
+
rotation?: number; /** The point the element scales and turns around; the centre by default. */
|
|
1099
|
+
origin?: Origin;
|
|
1100
|
+
};
|
|
1101
|
+
/**
|
|
1102
|
+
* Everything but the variants: the five groups of the vocabulary.
|
|
1103
|
+
*
|
|
1104
|
+
* @example
|
|
1105
|
+
* ```ts
|
|
1106
|
+
* const base: BaseStyle = { direction: "row", gap: 8, fill: 0x101018 };
|
|
1107
|
+
* ```
|
|
1108
|
+
*/
|
|
1109
|
+
type BaseStyle<Asset extends string = string> = FlowStyle & BoxStyle & PositionStyle & VisualStyle<Asset> & TransformStyle;
|
|
1110
|
+
/**
|
|
1111
|
+
* The seven state variants of a style, applied in the order disabled, active, selected, hover,
|
|
1112
|
+
* focus, pressed, covered. While `disabled` is true, `hover` and `pressed` are not applied.
|
|
1113
|
+
*
|
|
1114
|
+
* @example
|
|
1115
|
+
* ```ts
|
|
1116
|
+
* // One rule set for every control: lift on hover, sink when pressed, swap the texture when off.
|
|
1117
|
+
* const variants: IsVariants = {
|
|
1118
|
+
* hover: { offsetY: -6, scale: 1.05 },
|
|
1119
|
+
* focus: { scale: 1.05 },
|
|
1120
|
+
* pressed: { offsetY: 4, scale: 0.95 },
|
|
1121
|
+
* disabled: { nineSlice: "ui.button-disabled" }
|
|
1122
|
+
* };
|
|
1123
|
+
* ```
|
|
1124
|
+
*/
|
|
1125
|
+
type IsVariants<Asset extends string = string> = {
|
|
1126
|
+
pressed?: BaseStyle<Asset>;
|
|
1127
|
+
disabled?: BaseStyle<Asset>;
|
|
1128
|
+
active?: BaseStyle<Asset>;
|
|
1129
|
+
selected?: BaseStyle<Asset>; /** The mouse or pen is over the element. Touch never hovers. */
|
|
1130
|
+
hover?: BaseStyle<Asset>; /** The keyboard focus is on the element: Tab moved it there, and no pointer tapped since. */
|
|
1131
|
+
focus?: BaseStyle<Asset>; /** The popup the element belongs to is kept under another one. */
|
|
1132
|
+
covered?: BaseStyle<Asset>;
|
|
1133
|
+
};
|
|
1134
|
+
/**
|
|
1135
|
+
* The four viewport variants of a style, applied in the order portrait, landscape, tall, wide.
|
|
1136
|
+
*
|
|
1137
|
+
* @example
|
|
1138
|
+
* ```ts
|
|
1139
|
+
* const variants: WhenVariants = { landscape: { direction: "row" } };
|
|
1140
|
+
* ```
|
|
1141
|
+
*/
|
|
1142
|
+
type WhenVariants<Asset extends string = string> = {
|
|
1143
|
+
portrait?: BaseStyle<Asset>;
|
|
1144
|
+
landscape?: BaseStyle<Asset>;
|
|
1145
|
+
tall?: BaseStyle<Asset>;
|
|
1146
|
+
wide?: BaseStyle<Asset>;
|
|
1147
|
+
};
|
|
1148
|
+
/**
|
|
1149
|
+
* One style as a game writes it: the base vocabulary plus the two variant tables. `Asset` is the
|
|
1150
|
+
* game's asset key union: `uiFor` narrows `nineSlice` to it, the way it narrows `texture`.
|
|
1151
|
+
*
|
|
1152
|
+
* @example
|
|
1153
|
+
* ```ts
|
|
1154
|
+
* const topBar: Style = { direction: "row", gap: 12, when: { landscape: { justify: "end" } } };
|
|
1155
|
+
* ```
|
|
1156
|
+
*/
|
|
1157
|
+
type Style<Asset extends string = string> = BaseStyle<Asset> & {
|
|
1158
|
+
is?: IsVariants<Asset>;
|
|
1159
|
+
when?: WhenVariants<Asset>;
|
|
1160
|
+
};
|
|
1161
|
+
/**
|
|
1162
|
+
* What `resolve` answers with: the base vocabulary with every safe-area token replaced by a
|
|
1163
|
+
* number and every variant already merged in. Frozen.
|
|
1164
|
+
*
|
|
1165
|
+
* @example
|
|
1166
|
+
* ```ts
|
|
1167
|
+
* const resolved: ResolvedStyle = { direction: "row", gap: 12, padding: 24 };
|
|
1168
|
+
* ```
|
|
1169
|
+
*/
|
|
1170
|
+
type ResolvedStyle = Omit<BaseStyle, "padding" | "margin" | "left" | "top" | "right" | "bottom"> & {
|
|
1171
|
+
padding?: number | {
|
|
1172
|
+
top?: number;
|
|
1173
|
+
right?: number;
|
|
1174
|
+
bottom?: number;
|
|
1175
|
+
left?: number;
|
|
1176
|
+
};
|
|
1177
|
+
margin?: number | {
|
|
1178
|
+
top?: number;
|
|
1179
|
+
right?: number;
|
|
1180
|
+
bottom?: number;
|
|
1181
|
+
left?: number;
|
|
1182
|
+
};
|
|
1183
|
+
left?: number;
|
|
1184
|
+
top?: number;
|
|
1185
|
+
right?: number;
|
|
1186
|
+
bottom?: number;
|
|
1187
|
+
};
|
|
1188
|
+
/**
|
|
1189
|
+
* The viewport flags of a style: which `when` variants apply this frame.
|
|
1190
|
+
*
|
|
1191
|
+
* @example
|
|
1192
|
+
* ```ts
|
|
1193
|
+
* const flags: WhenFlags = { portrait: true, landscape: false, tall: true, wide: false };
|
|
1194
|
+
* ```
|
|
1195
|
+
*/
|
|
1196
|
+
type WhenFlags = {
|
|
1197
|
+
portrait: boolean;
|
|
1198
|
+
landscape: boolean;
|
|
1199
|
+
tall: boolean;
|
|
1200
|
+
wide: boolean;
|
|
1201
|
+
};
|
|
1202
|
+
/**
|
|
1203
|
+
* The state flags of an element: what the markup declared (`disabled`, `active`, `selected`),
|
|
1204
|
+
* `pressed` and `hover` from the pointer, `focus` from the keyboard, and `covered` from the root
|
|
1205
|
+
* it belongs to.
|
|
1206
|
+
*
|
|
1207
|
+
* @example
|
|
1208
|
+
* ```ts
|
|
1209
|
+
* const flags: IsFlags = {
|
|
1210
|
+
* pressed: false, hover: true, focus: false, disabled: false, active: true, selected: false,
|
|
1211
|
+
* covered: false
|
|
1212
|
+
* };
|
|
1213
|
+
* ```
|
|
1214
|
+
*/
|
|
1215
|
+
type IsFlags = {
|
|
1216
|
+
pressed: boolean;
|
|
1217
|
+
hover: boolean;
|
|
1218
|
+
focus: boolean;
|
|
1219
|
+
disabled: boolean;
|
|
1220
|
+
active: boolean;
|
|
1221
|
+
selected: boolean;
|
|
1222
|
+
covered: boolean;
|
|
1223
|
+
};
|
|
1224
|
+
/**
|
|
1225
|
+
* A flat table of design tokens a game reads in its styles.
|
|
1226
|
+
*
|
|
1227
|
+
* @example
|
|
1228
|
+
* ```ts
|
|
1229
|
+
* const tokens: Tokens = { space: { md: 16 }, color: { panel: 0x101018 } };
|
|
1230
|
+
* ```
|
|
1231
|
+
*/
|
|
1232
|
+
type Tokens = {
|
|
1233
|
+
readonly space?: Readonly<Record<string, number>>;
|
|
1234
|
+
readonly color?: Readonly<Record<string, number>>;
|
|
1235
|
+
readonly radius?: Readonly<Record<string, number>>;
|
|
1236
|
+
readonly font?: Readonly<Record<string, string>>;
|
|
1237
|
+
};
|
|
1238
|
+
/**
|
|
1239
|
+
* styles module state: the flags of the frame and the viewport they were read from.
|
|
1240
|
+
*/
|
|
1241
|
+
type StylesState = {
|
|
1242
|
+
flags: WhenFlags;
|
|
1243
|
+
viewport: ViewportSize | undefined;
|
|
1244
|
+
};
|
|
1245
|
+
//#endregion
|
|
1246
|
+
//#region src/plugins/ui/jsx/intrinsics.d.ts
|
|
1247
|
+
/**
|
|
1248
|
+
* The optional brand that makes a wrong value print the name of the prop it was written on.
|
|
1249
|
+
*
|
|
1250
|
+
* @example
|
|
1251
|
+
* ```ts
|
|
1252
|
+
* type Named = string & Brand<"ButtonIntent">;
|
|
1253
|
+
* ```
|
|
1254
|
+
*/
|
|
1255
|
+
type Brand<Name extends string> = {
|
|
1256
|
+
readonly _ui?: Name;
|
|
1257
|
+
};
|
|
1258
|
+
/** The intent a button answers the gate with. */
|
|
1259
|
+
type ButtonIntent = string & Brand<"ButtonIntent">;
|
|
1260
|
+
/** The payload that travels with a button's intent. */
|
|
1261
|
+
type ButtonPayload = Json & Brand<"ButtonPayload">;
|
|
1262
|
+
/** The patch a button writes into the local state of its nearest component. */
|
|
1263
|
+
type ButtonLocal = Record<string, unknown> & Brand<"ButtonLocal">;
|
|
1264
|
+
/** Makes a button the control the Escape key taps while its root is the top one. */
|
|
1265
|
+
type ButtonEscape = boolean & Brand<"ButtonEscape">;
|
|
1266
|
+
/** What a text draws: a plain string, or a message of the game's string table. */
|
|
1267
|
+
type TextContent = (string | Message) & Brand<"TextContent">;
|
|
1268
|
+
/** The component field a text follows instead of a content. */
|
|
1269
|
+
type TextBind = {
|
|
1270
|
+
component: string;
|
|
1271
|
+
field: string;
|
|
1272
|
+
} & Brand<"TextBind">;
|
|
1273
|
+
/** The text style key, or a layout style when the built-in `body` is meant. */
|
|
1274
|
+
type TextStyleProp = (string | Style) & Brand<"TextStyleProp">;
|
|
1275
|
+
/** The asset key of an image. */
|
|
1276
|
+
type ImageTexture = string & Brand<"ImageTexture">;
|
|
1277
|
+
/** The asset key of an icon. */
|
|
1278
|
+
type IconName = string & Brand<"IconName">;
|
|
1279
|
+
/** How an image or an icon fills its rect: inside it, over it (cropped), or stretched. */
|
|
1280
|
+
type ImageFit = ("contain" | "cover" | "fill") & Brand<"ImageFit">;
|
|
1281
|
+
/** The axis a scroll container moves on. `"x"` throws until V4. */
|
|
1282
|
+
type ScrollAxis = ("x" | "y") & Brand<"ScrollAxis">;
|
|
1283
|
+
/** The world projections whose live views a container draws inside itself. */
|
|
1284
|
+
type HostedProjections = readonly string[] & Brand<"HostedProjections">;
|
|
1285
|
+
/**
|
|
1286
|
+
* The props every tag takes, with the key kept out: JSX passes it as the third argument.
|
|
1287
|
+
*
|
|
1288
|
+
* @example
|
|
1289
|
+
* ```ts
|
|
1290
|
+
* const props: BoxProps = { style: { gap: 8 } };
|
|
1291
|
+
* ```
|
|
1292
|
+
*/
|
|
1293
|
+
type BoxProps = CommonProps;
|
|
1294
|
+
/**
|
|
1295
|
+
* What a container takes: the common props and the projections it hosts. Every live view of a
|
|
1296
|
+
* hosted projection that has no parent, and is not held by a drag, is drawn inside the element,
|
|
1297
|
+
* in its local space; it falls back to its layer when the element leaves.
|
|
1298
|
+
*
|
|
1299
|
+
* @example
|
|
1300
|
+
* ```tsx
|
|
1301
|
+
* // The board of a merge game sits in a slot that shrinks to fit a short phone.
|
|
1302
|
+
* <stack
|
|
1303
|
+
* key="boardSlot"
|
|
1304
|
+
* hosts={["board.cells", "board.generators", "board.items"]}
|
|
1305
|
+
* style={{ width: 970, height: 970, fit: "contain" }}
|
|
1306
|
+
* />;
|
|
1307
|
+
* // one frame after the slot is laid out, every live cell, generator and item view without a
|
|
1308
|
+
* // parent carries Parent({ entity: slot }) and draws in the slot's 0..970 space
|
|
1309
|
+
* ```
|
|
1310
|
+
*/
|
|
1311
|
+
type HostProps = CommonProps & {
|
|
1312
|
+
hosts?: HostedProjections;
|
|
1313
|
+
};
|
|
1314
|
+
/**
|
|
1315
|
+
* What a button takes. `intent` and `local` exclude each other: a button either answers the gate
|
|
1316
|
+
* or writes local state. `escape` marks the control the Escape key taps: the close button or the
|
|
1317
|
+
* backdrop of a dismissable popup.
|
|
1318
|
+
*
|
|
1319
|
+
* @example
|
|
1320
|
+
* ```ts
|
|
1321
|
+
* const props: ButtonTagProps = { intent: "claim", payload: { orderId: "o1" } };
|
|
1322
|
+
* // The close button of the settings popup: Escape taps it while the popup is on top.
|
|
1323
|
+
* const close: ButtonTagProps = { intent: "close", escape: true };
|
|
1324
|
+
* ```
|
|
1325
|
+
*/
|
|
1326
|
+
type ButtonTagProps = CommonProps & {
|
|
1327
|
+
escape?: ButtonEscape;
|
|
1328
|
+
} & ({
|
|
1329
|
+
intent?: ButtonIntent;
|
|
1330
|
+
payload?: ButtonPayload;
|
|
1331
|
+
local?: never;
|
|
1332
|
+
} | {
|
|
1333
|
+
intent?: never;
|
|
1334
|
+
payload?: never;
|
|
1335
|
+
local?: ButtonLocal;
|
|
1336
|
+
});
|
|
1337
|
+
/**
|
|
1338
|
+
* What a text takes: a content or a bind, and the style key that carries its size.
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* ```ts
|
|
1342
|
+
* const props: TextTagProps = { content: "120", style: "digits" };
|
|
1343
|
+
* ```
|
|
1344
|
+
*/
|
|
1345
|
+
type TextTagProps = Omit<CommonProps, "style"> & {
|
|
1346
|
+
content?: TextContent;
|
|
1347
|
+
bind?: TextBind;
|
|
1348
|
+
style?: TextStyleProp;
|
|
1349
|
+
};
|
|
1350
|
+
/**
|
|
1351
|
+
* What an image takes: the texture key is required; `fit` is `"contain"` unless it says so.
|
|
1352
|
+
*
|
|
1353
|
+
* @example
|
|
1354
|
+
* ```ts
|
|
1355
|
+
* // A full-bleed background.
|
|
1356
|
+
* const props: ImageProps = {
|
|
1357
|
+
* texture: "board.bg-forest-meadow",
|
|
1358
|
+
* fit: "cover",
|
|
1359
|
+
* style: { position: "absolute", left: 0, top: 0, width: "100%", height: "100%" }
|
|
1360
|
+
* };
|
|
1361
|
+
* ```
|
|
1362
|
+
*/
|
|
1363
|
+
type ImageProps = CommonProps & {
|
|
1364
|
+
texture: ImageTexture;
|
|
1365
|
+
fit?: ImageFit;
|
|
1366
|
+
};
|
|
1367
|
+
/**
|
|
1368
|
+
* What an icon takes: the asset key is required; without a size it follows the line height.
|
|
1369
|
+
*
|
|
1370
|
+
* @example
|
|
1371
|
+
* ```ts
|
|
1372
|
+
* const props: IconProps = { name: "hud.gear", fit: "contain" };
|
|
1373
|
+
* ```
|
|
1374
|
+
*/
|
|
1375
|
+
type IconProps = CommonProps & {
|
|
1376
|
+
name: IconName;
|
|
1377
|
+
fit?: ImageFit;
|
|
1378
|
+
};
|
|
1379
|
+
/**
|
|
1380
|
+
* What a panel takes: the common props. Its nine-slice is a style field, and it swallows every
|
|
1381
|
+
* tap that lands on it.
|
|
1382
|
+
*
|
|
1383
|
+
* @example
|
|
1384
|
+
* ```ts
|
|
1385
|
+
* const props: PanelProps = { style: { nineSlice: "ui.panel", padding: 32 } };
|
|
1386
|
+
* ```
|
|
1387
|
+
*/
|
|
1388
|
+
type PanelProps = CommonProps;
|
|
1389
|
+
/**
|
|
1390
|
+
* What a scroll container takes.
|
|
1391
|
+
*
|
|
1392
|
+
* @example
|
|
1393
|
+
* ```ts
|
|
1394
|
+
* const props: ScrollProps = { axis: "y" };
|
|
1395
|
+
* ```
|
|
1396
|
+
*/
|
|
1397
|
+
type ScrollProps = CommonProps & {
|
|
1398
|
+
axis?: ScrollAxis;
|
|
1399
|
+
};
|
|
1400
|
+
/**
|
|
1401
|
+
* The thirteen tags a screen is written with.
|
|
1402
|
+
*
|
|
1403
|
+
* @example
|
|
1404
|
+
* ```ts
|
|
1405
|
+
* const tags: (keyof UiIntrinsicElements)[] = ["row", "column", "button"];
|
|
1406
|
+
* ```
|
|
1407
|
+
*/
|
|
1408
|
+
type UiIntrinsicElements = {
|
|
1409
|
+
screen: HostProps;
|
|
1410
|
+
layer: BoxProps;
|
|
1411
|
+
row: HostProps;
|
|
1412
|
+
column: HostProps;
|
|
1413
|
+
stack: HostProps;
|
|
1414
|
+
spacer: BoxProps;
|
|
1415
|
+
panel: PanelProps;
|
|
1416
|
+
image: ImageProps;
|
|
1417
|
+
icon: IconProps;
|
|
1418
|
+
text: TextTagProps;
|
|
1419
|
+
button: ButtonTagProps;
|
|
1420
|
+
scroll: ScrollProps;
|
|
1421
|
+
input: BoxProps;
|
|
1422
|
+
};
|
|
1423
|
+
/**
|
|
1424
|
+
* The props of one tag with its `style` narrowed to the asset keys of one game, so a nine-slice
|
|
1425
|
+
* key outside them is an error where the tag is written. Distributes over a union, so the two
|
|
1426
|
+
* shapes of a button stay apart.
|
|
1427
|
+
*
|
|
1428
|
+
* @example
|
|
1429
|
+
* ```ts
|
|
1430
|
+
* type Panel = Restyled<PanelProps, "ui.panel">; // style?: Style<"ui.panel">
|
|
1431
|
+
* ```
|
|
1432
|
+
*/
|
|
1433
|
+
type Restyled<Properties, Asset extends string> = Properties extends unknown ? Omit<Properties, "style"> & {
|
|
1434
|
+
style?: Style<Asset>;
|
|
1435
|
+
} : never;
|
|
1436
|
+
/**
|
|
1437
|
+
* The same tags with the asset keys, text style keys and message keys of one game. `uiFor`
|
|
1438
|
+
* carries it, so a game can name the type of its own intrinsics. The asset keys narrow `texture`,
|
|
1439
|
+
* `name` and the `nineSlice` of every style.
|
|
1440
|
+
*
|
|
1441
|
+
* @example
|
|
1442
|
+
* ```ts
|
|
1443
|
+
* type Tags = IntrinsicElementsFor<"ui.coin", "digits", "hud.coins">;
|
|
1444
|
+
* ```
|
|
1445
|
+
*/
|
|
1446
|
+
type IntrinsicElementsFor<Asset extends string, TextStyleKey extends string, StringKey extends string> = { [Tag in Exclude<keyof UiIntrinsicElements, "image" | "icon" | "text">]: Restyled<UiIntrinsicElements[Tag], Asset> } & {
|
|
1447
|
+
image: Restyled<CommonProps, Asset> & {
|
|
1448
|
+
texture: Asset;
|
|
1449
|
+
fit?: "contain" | "cover" | "fill";
|
|
1450
|
+
};
|
|
1451
|
+
icon: Restyled<CommonProps, Asset> & {
|
|
1452
|
+
name: Asset;
|
|
1453
|
+
fit?: "contain" | "cover" | "fill";
|
|
1454
|
+
};
|
|
1455
|
+
text: Omit<CommonProps, "style"> & {
|
|
1456
|
+
content?: string | Message<StringKey>;
|
|
1457
|
+
bind?: TextBind;
|
|
1458
|
+
style?: TextStyleKey | Style<Asset>;
|
|
1459
|
+
};
|
|
1460
|
+
};
|
|
1461
|
+
//#endregion
|
|
1462
|
+
//#region src/plugins/ui/styles/tokens.d.ts
|
|
1463
|
+
/**
|
|
1464
|
+
* Declares the design tokens of a game: spaces, colours, radii and font keys, flat and plain.
|
|
1465
|
+
*
|
|
1466
|
+
* @param tokens - The four groups; every key inside them is the game's own.
|
|
1467
|
+
* @returns The same object, frozen.
|
|
1468
|
+
* @example
|
|
1469
|
+
* ```ts
|
|
1470
|
+
* defineTokens({ space: { md: 16 } }).space.md; // 16
|
|
1471
|
+
* ```
|
|
1472
|
+
*/
|
|
1473
|
+
declare function defineTokens<const Given extends Tokens>(tokens: Given): Readonly<Given>;
|
|
1474
|
+
//#endregion
|
|
1475
|
+
//#region src/plugins/ui/components.d.ts
|
|
1476
|
+
/**
|
|
1477
|
+
* The rect of a ui element in root coordinates, in reference units. Its `x` and `y` are the rest
|
|
1478
|
+
* pose of the `Transform` of that entity, so a motion never fights the layout.
|
|
1479
|
+
*
|
|
1480
|
+
* @example
|
|
1481
|
+
* ```ts
|
|
1482
|
+
* const value: BoxValue = { x: 0, y: 0, w: 1080, h: 96 };
|
|
1483
|
+
* ```
|
|
1484
|
+
*/
|
|
1485
|
+
type BoxValue = {
|
|
1486
|
+
x: number;
|
|
1487
|
+
y: number;
|
|
1488
|
+
w: number;
|
|
1489
|
+
h: number;
|
|
1490
|
+
};
|
|
1491
|
+
/**
|
|
1492
|
+
* What a button with `local` writes into the local state of its nearest component instance.
|
|
1493
|
+
*
|
|
1494
|
+
* @example
|
|
1495
|
+
* ```ts
|
|
1496
|
+
* const value: LocalWriteValue = { patch: { tab: "audio" } };
|
|
1497
|
+
* ```
|
|
1498
|
+
*/
|
|
1499
|
+
type LocalWriteValue = {
|
|
1500
|
+
patch: Record<string, unknown>;
|
|
1501
|
+
};
|
|
1502
|
+
/**
|
|
1503
|
+
* The rect of a ui element in root coordinates. Written by the solve, read by `tree()`, `lint()`
|
|
1504
|
+
* and the hit test of `guide`.
|
|
1505
|
+
*/
|
|
1506
|
+
declare const Box: ComponentType<{
|
|
1507
|
+
x: number;
|
|
1508
|
+
y: number;
|
|
1509
|
+
w: number;
|
|
1510
|
+
h: number;
|
|
1511
|
+
}>;
|
|
1512
|
+
/**
|
|
1513
|
+
* On a button that writes local state instead of naming an intent. `input.onTap` reports the tap
|
|
1514
|
+
* and `ui` merges the patch into the nearest component instance.
|
|
1515
|
+
*/
|
|
1516
|
+
declare const LocalWrite: ComponentType<LocalWriteValue>;
|
|
1517
|
+
/**
|
|
1518
|
+
* Builds the effect a node awaits to show a popup. The gate opens for the outcomes of the
|
|
1519
|
+
* component, so the node resolves with the intent one of its buttons answered. `over` names the
|
|
1520
|
+
* component of a popup that stays mounted beneath this one, drawn covered, until this one is gone.
|
|
1521
|
+
*
|
|
1522
|
+
* @param component - A component declared with `outcomes`.
|
|
1523
|
+
* @param props - What its view is called with.
|
|
1524
|
+
* @param options - How the popup stands to the others.
|
|
1525
|
+
* @param options.over - The component name of the popup kept beneath this one.
|
|
1526
|
+
* @returns The descriptor `fx()` takes.
|
|
1527
|
+
* @example
|
|
1528
|
+
* ```ts
|
|
1529
|
+
* const Reward = defineComponent("Reward", {
|
|
1530
|
+
* outcomes: { claim: {} },
|
|
1531
|
+
* view: () => ({ type: "panel", props: {}, children: [] })
|
|
1532
|
+
* });
|
|
1533
|
+
* popup(Reward, { gold: 5 });
|
|
1534
|
+
* // { kind: "popup", payload: { component: "Reward", props: { gold: 5 } }, answers: ["claim"] }
|
|
1535
|
+
* ```
|
|
1536
|
+
* @example
|
|
1537
|
+
* ```ts
|
|
1538
|
+
* // The settings node asks before a reset; the settings stay beneath, covered.
|
|
1539
|
+
* const Confirm = defineComponent("Confirm", {
|
|
1540
|
+
* outcomes: { reset: {}, cancel: {} },
|
|
1541
|
+
* view: () => ({ type: "panel", props: {}, children: [] })
|
|
1542
|
+
* });
|
|
1543
|
+
* popup(Confirm, {}, { over: "Settings" });
|
|
1544
|
+
* // { kind: "popup", payload: { component: "Confirm", props: {}, over: "Settings" },
|
|
1545
|
+
* // answers: ["reset", "cancel"] }
|
|
1546
|
+
* ```
|
|
1547
|
+
*/
|
|
1548
|
+
declare function popup<Properties extends object, Local extends object>(component: PopupComponent<Properties, Local>, props: Properties, options?: {
|
|
1549
|
+
over?: string;
|
|
1550
|
+
}): Descriptor;
|
|
1551
|
+
//#endregion
|
|
1552
|
+
//#region src/plugins/ui/jsx/types.d.ts
|
|
1553
|
+
/**
|
|
1554
|
+
* One node of an element description, as the JSX runtime builds it. `key` is the third argument
|
|
1555
|
+
* of `jsx`, never a member of `props`.
|
|
1556
|
+
*
|
|
1557
|
+
* @example
|
|
1558
|
+
* ```ts
|
|
1559
|
+
* const node: DescriptionNode = { type: "row", key: "tabs", props: {}, children: [] };
|
|
1560
|
+
* ```
|
|
1561
|
+
*/
|
|
1562
|
+
type DescriptionNode = {
|
|
1563
|
+
type: string;
|
|
1564
|
+
key?: string;
|
|
1565
|
+
props: Record<string, unknown>;
|
|
1566
|
+
children: DescriptionNode[];
|
|
1567
|
+
};
|
|
1568
|
+
/**
|
|
1569
|
+
* What may sit between the tags of a node before `flatten` is done with it: nodes, arrays,
|
|
1570
|
+
* strings, numbers and the three values that drop.
|
|
1571
|
+
*
|
|
1572
|
+
* @example
|
|
1573
|
+
* ```ts
|
|
1574
|
+
* const child: JsxChild = "5 coins";
|
|
1575
|
+
* ```
|
|
1576
|
+
*/
|
|
1577
|
+
type JsxChild = DescriptionNode | string | number | boolean | null | undefined | readonly JsxChild[];
|
|
1578
|
+
/**
|
|
1579
|
+
* One `change` hook of a ui element: its handle, the value before and the value after.
|
|
1580
|
+
*
|
|
1581
|
+
* @example
|
|
1582
|
+
* ```ts
|
|
1583
|
+
* const moved: ElementChange<BoxValue> = (view, previous, next) =>
|
|
1584
|
+
* next.y > previous.y ? view.toRest(Transform, { ms: 200 }) : undefined;
|
|
1585
|
+
* ```
|
|
1586
|
+
*/
|
|
1587
|
+
type ElementChange<Value> = (view: ViewHandle<unknown>, previous: Value, next: Value, hint?: Hint) => Motion;
|
|
1588
|
+
/**
|
|
1589
|
+
* The motion hooks of one element: the hook triple of `world.projection`, which `defineMotion`
|
|
1590
|
+
* of `anim` returns. `ui` plays two `change` hooks itself and hands them typed values: `Box` gets
|
|
1591
|
+
* the rects, `Transform` the rest poses. A hook for any other component name is accepted, so every
|
|
1592
|
+
* `defineMotion` result fits, and `ui` never plays it.
|
|
1593
|
+
*
|
|
1594
|
+
* @example
|
|
1595
|
+
* ```ts
|
|
1596
|
+
* // An order card that grows into its ready pose sways once on the way.
|
|
1597
|
+
* const cardMotion: ElementMotion = {
|
|
1598
|
+
* change: {
|
|
1599
|
+
* Transform: (view, previous, next) =>
|
|
1600
|
+
* next.scale > previous.scale
|
|
1601
|
+
* ? view.all([
|
|
1602
|
+
* view.toRest(Transform, { ms: 240 }),
|
|
1603
|
+
* view.tween(Transform, { rotation: 0.12 }, { ms: 520, additive: true })
|
|
1604
|
+
* ])
|
|
1605
|
+
* : view.toRest(Transform, { ms: 240 })
|
|
1606
|
+
* }
|
|
1607
|
+
* };
|
|
1608
|
+
* ```
|
|
1609
|
+
*/
|
|
1610
|
+
type ElementMotion = Omit<ProjectionMotion<unknown>, "change"> & {
|
|
1611
|
+
readonly change?: {
|
|
1612
|
+
readonly [component: string]: ChangeHook<never>;
|
|
1613
|
+
Box?(view: ViewHandle<unknown>, previous: BoxValue, next: BoxValue, hint?: Hint): Motion;
|
|
1614
|
+
Transform?(view: ViewHandle<unknown>, previous: TransformValue, next: TransformValue, hint?: Hint): Motion;
|
|
1615
|
+
};
|
|
1616
|
+
};
|
|
1617
|
+
/**
|
|
1618
|
+
* What a game hands `defineComponent`: the local state one instance starts with, the outcomes a
|
|
1619
|
+
* popup of it resolves with, and the view that runs at every reconcile.
|
|
1620
|
+
*
|
|
1621
|
+
* @example
|
|
1622
|
+
* ```ts
|
|
1623
|
+
* const spec: ComponentSpec<{ volume: number }, { tab: string }, never> = {
|
|
1624
|
+
* local: { tab: "audio" },
|
|
1625
|
+
* view: (props, local) => ({ type: "column", props: {}, children: [] })
|
|
1626
|
+
* };
|
|
1627
|
+
* ```
|
|
1628
|
+
*/
|
|
1629
|
+
type ComponentSpec<Properties extends object, Local extends object, Outcomes> = {
|
|
1630
|
+
readonly local?: Local;
|
|
1631
|
+
readonly outcomes?: Outcomes;
|
|
1632
|
+
view(props: Properties, local: Local): DescriptionNode;
|
|
1633
|
+
};
|
|
1634
|
+
/**
|
|
1635
|
+
* What `defineComponent` returns: a function JSX may call, carrying the name, the outcomes and
|
|
1636
|
+
* the local state a fresh instance is cloned from.
|
|
1637
|
+
*
|
|
1638
|
+
* @example
|
|
1639
|
+
* ```ts
|
|
1640
|
+
* const Settings = defineComponent("Settings", { view: () => ({ type: "column", props: {}, children: [] }) });
|
|
1641
|
+
* Settings.name; // "Settings"
|
|
1642
|
+
* ```
|
|
1643
|
+
*/
|
|
1644
|
+
type ComponentDefinition<Properties extends object = object, Local extends object = object, Outcomes = undefined> = {
|
|
1645
|
+
(props: Properties): DescriptionNode;
|
|
1646
|
+
readonly name: string;
|
|
1647
|
+
readonly outcomes: Outcomes;
|
|
1648
|
+
readonly local: Local;
|
|
1649
|
+
readonly view: (props: Properties, local: Local) => DescriptionNode;
|
|
1650
|
+
readonly isUiComponent: true;
|
|
1651
|
+
};
|
|
1652
|
+
/**
|
|
1653
|
+
* A component that names outcomes, which is what `popup` takes. `Local` stays a parameter
|
|
1654
|
+
* because the `local` argument of `view` is contravariant: pinned to `object` it would refuse
|
|
1655
|
+
* every component that keeps local state.
|
|
1656
|
+
*
|
|
1657
|
+
* @example
|
|
1658
|
+
* ```ts
|
|
1659
|
+
* const settings: PopupComponent<{ volume: number }, { tab: string }> = Settings;
|
|
1660
|
+
* ```
|
|
1661
|
+
*/
|
|
1662
|
+
type PopupComponent<Properties extends object, Local extends object = object> = ComponentDefinition<Properties, Local, Record<string, unknown>>;
|
|
1663
|
+
/**
|
|
1664
|
+
* A component definition with its type arguments erased, as the registry stores it.
|
|
1665
|
+
*/
|
|
1666
|
+
type AnyComponentDefinition = ComponentDefinition<never, never, unknown>;
|
|
1667
|
+
/**
|
|
1668
|
+
* One live instance of a component: the local state kept by identity across renders.
|
|
1669
|
+
*/
|
|
1670
|
+
type Instance = {
|
|
1671
|
+
identity: string;
|
|
1672
|
+
component: string;
|
|
1673
|
+
local: Record<string, unknown>;
|
|
1674
|
+
props: object;
|
|
1675
|
+
dirty: boolean;
|
|
1676
|
+
};
|
|
1677
|
+
/**
|
|
1678
|
+
* One live element: the entity, the Yoga node, the resolved style and the place in the tree.
|
|
1679
|
+
* `rect` is natural: under a `fit` ancestor it is the rect before that ancestor's scale. `fit`
|
|
1680
|
+
* is the element's own fit scale (1 without `fit: "contain"`), and `rest` the rest `Transform`
|
|
1681
|
+
* last written for it. `loop` is the motion of the running `loop` hook, kept apart from
|
|
1682
|
+
* `handles`: it never ends, so the exit sweep must not wait for it.
|
|
1683
|
+
*/
|
|
1684
|
+
type Element = {
|
|
1685
|
+
entity: Entity;
|
|
1686
|
+
identity: string;
|
|
1687
|
+
type: string;
|
|
1688
|
+
key: string | undefined;
|
|
1689
|
+
parentType: string | undefined;
|
|
1690
|
+
root: Entity;
|
|
1691
|
+
node: DescriptionNode;
|
|
1692
|
+
style: ResolvedStyle;
|
|
1693
|
+
is: IsFlags;
|
|
1694
|
+
rect: {
|
|
1695
|
+
x: number;
|
|
1696
|
+
y: number;
|
|
1697
|
+
w: number;
|
|
1698
|
+
h: number;
|
|
1699
|
+
};
|
|
1700
|
+
previous: {
|
|
1701
|
+
x: number;
|
|
1702
|
+
y: number;
|
|
1703
|
+
w: number;
|
|
1704
|
+
h: number;
|
|
1705
|
+
};
|
|
1706
|
+
moved: boolean;
|
|
1707
|
+
fit: number;
|
|
1708
|
+
rest: TransformValue;
|
|
1709
|
+
handles: MotionHandle[];
|
|
1710
|
+
loop: MotionHandle | undefined;
|
|
1711
|
+
motion: ElementMotion | undefined;
|
|
1712
|
+
parent: Entity | undefined;
|
|
1713
|
+
children: Entity[];
|
|
1714
|
+
instance: string | undefined;
|
|
1715
|
+
live: boolean;
|
|
1716
|
+
entered: boolean;
|
|
1717
|
+
dropKey: (() => void) | undefined;
|
|
1718
|
+
};
|
|
1719
|
+
/**
|
|
1720
|
+
* The popup side of a root: the handler that shows it now and the root it was opened over.
|
|
1721
|
+
* `released` turns true when that handler's effect ended (answered, or its node aborted): the
|
|
1722
|
+
* root then stays until the flow rests on a node that shows no popup of its component.
|
|
1723
|
+
*/
|
|
1724
|
+
type PopupLink = {
|
|
1725
|
+
close: () => void;
|
|
1726
|
+
released?: boolean;
|
|
1727
|
+
over?: Entity;
|
|
1728
|
+
};
|
|
1729
|
+
/**
|
|
1730
|
+
* One reconciled tree: a projection view or a popup, with the flags the frame step reads.
|
|
1731
|
+
* `covered` is true while the root is kept under another popup; every element of the root then
|
|
1732
|
+
* resolves `is.covered`.
|
|
1733
|
+
*/
|
|
1734
|
+
type Root = {
|
|
1735
|
+
entity: Entity;
|
|
1736
|
+
name: string;
|
|
1737
|
+
tree: DescriptionNode;
|
|
1738
|
+
layer: string;
|
|
1739
|
+
order: number;
|
|
1740
|
+
dirty: boolean;
|
|
1741
|
+
needsSolve: boolean;
|
|
1742
|
+
element: Entity | undefined;
|
|
1743
|
+
popup: PopupLink | undefined;
|
|
1744
|
+
covered: boolean;
|
|
1745
|
+
};
|
|
1746
|
+
/**
|
|
1747
|
+
* Where the focus ring was last drawn: the layer and order of its root, the rect grown by the
|
|
1748
|
+
* ring offset, and the corner radius.
|
|
1749
|
+
*/
|
|
1750
|
+
type DrawnRing = {
|
|
1751
|
+
layer: string;
|
|
1752
|
+
order: number;
|
|
1753
|
+
x: number;
|
|
1754
|
+
y: number;
|
|
1755
|
+
w: number;
|
|
1756
|
+
h: number;
|
|
1757
|
+
radius: number;
|
|
1758
|
+
};
|
|
1759
|
+
/**
|
|
1760
|
+
* The keyboard focus: the focused element, the two entities of the ring drawn around it, where
|
|
1761
|
+
* the ring was last drawn, and whether ui itself is tapping, so its own Enter tap does not read
|
|
1762
|
+
* as a pointer tap that clears the focus.
|
|
1763
|
+
*/
|
|
1764
|
+
type FocusState = {
|
|
1765
|
+
entity: Entity | undefined;
|
|
1766
|
+
ring: {
|
|
1767
|
+
halo: Entity;
|
|
1768
|
+
ring: Entity;
|
|
1769
|
+
} | undefined;
|
|
1770
|
+
drawn: DrawnRing | undefined;
|
|
1771
|
+
tapping: boolean;
|
|
1772
|
+
};
|
|
1773
|
+
/**
|
|
1774
|
+
* jsx module state. `hosts` holds the elements with a `hosts` prop; `hosted` maps a world view to
|
|
1775
|
+
* the element that hosts it.
|
|
1776
|
+
*/
|
|
1777
|
+
type JsxState = {
|
|
1778
|
+
components: Map<string, AnyComponentDefinition>;
|
|
1779
|
+
roots: Map<Entity, Root>;
|
|
1780
|
+
elements: Map<Entity, Element>;
|
|
1781
|
+
byIdentity: Map<string, Entity>;
|
|
1782
|
+
byKey: Map<Entity, Map<string, Entity>>;
|
|
1783
|
+
instances: Map<string, Instance>;
|
|
1784
|
+
exiting: Set<Entity>;
|
|
1785
|
+
removing: Set<Entity>;
|
|
1786
|
+
hosts: Set<Entity>;
|
|
1787
|
+
hosted: Map<Entity, Entity>;
|
|
1788
|
+
reconciles: number;
|
|
1789
|
+
focus: FocusState;
|
|
1790
|
+
};
|
|
1791
|
+
/**
|
|
1792
|
+
* One node of the snapshot `tree()` answers with. `rect` is natural: under a `fit` ancestor it
|
|
1793
|
+
* is the rect before that ancestor's scale. An element with `fit: "contain"` adds `fitScale`,
|
|
1794
|
+
* the scale it is drawn at.
|
|
1795
|
+
*
|
|
1796
|
+
* @example
|
|
1797
|
+
* ```ts
|
|
1798
|
+
* // The board slot of a merge game on an iPhone SE.
|
|
1799
|
+
* const node: UiNode = {
|
|
1800
|
+
* key: "boardSlot", type: "stack", rect: { x: 55, y: 223, w: 970, h: 970 },
|
|
1801
|
+
* style: { width: 970, height: 970, fit: "contain" },
|
|
1802
|
+
* state: {
|
|
1803
|
+
* pressed: false, hover: false, focus: false, disabled: false, active: false, selected: false,
|
|
1804
|
+
* covered: false
|
|
1805
|
+
* },
|
|
1806
|
+
* fitScale: 0.8,
|
|
1807
|
+
* children: []
|
|
1808
|
+
* };
|
|
1809
|
+
* ```
|
|
1810
|
+
*/
|
|
1811
|
+
type UiNode = {
|
|
1812
|
+
key: string | undefined;
|
|
1813
|
+
type: string;
|
|
1814
|
+
rect: {
|
|
1815
|
+
x: number;
|
|
1816
|
+
y: number;
|
|
1817
|
+
w: number;
|
|
1818
|
+
h: number;
|
|
1819
|
+
};
|
|
1820
|
+
style: ResolvedStyle;
|
|
1821
|
+
state: IsFlags;
|
|
1822
|
+
local?: Record<string, unknown>;
|
|
1823
|
+
fitScale?: number;
|
|
1824
|
+
children: UiNode[];
|
|
1825
|
+
};
|
|
1826
|
+
/**
|
|
1827
|
+
* One thing `lint()` found on the live screen.
|
|
1828
|
+
*
|
|
1829
|
+
* @example
|
|
1830
|
+
* ```ts
|
|
1831
|
+
* const finding: Finding = { rule: "tap-target", key: "claim", detail: "32x32 pt" };
|
|
1832
|
+
* ```
|
|
1833
|
+
*/
|
|
1834
|
+
type Finding = {
|
|
1835
|
+
rule: "tap-target" | "text-overflow" | "absolute-without-reason" | "nine-slice-clipped" | "z-index-on-root";
|
|
1836
|
+
key: string;
|
|
1837
|
+
detail: string;
|
|
1838
|
+
};
|
|
1839
|
+
/**
|
|
1840
|
+
* What the common props of every intrinsic tag accept. A tag's own props are added to it.
|
|
1841
|
+
*
|
|
1842
|
+
* @example
|
|
1843
|
+
* ```ts
|
|
1844
|
+
* const props: CommonProps = { style: { gap: 8 }, state: { active: true } };
|
|
1845
|
+
* ```
|
|
1846
|
+
*/
|
|
1847
|
+
type CommonProps = {
|
|
1848
|
+
key?: string;
|
|
1849
|
+
style?: Style;
|
|
1850
|
+
state?: {
|
|
1851
|
+
active?: boolean;
|
|
1852
|
+
disabled?: boolean;
|
|
1853
|
+
selected?: boolean;
|
|
1854
|
+
}; /** The motion hooks; `undefined` written out stops a running loop, as leaving it out does. */
|
|
1855
|
+
motion?: ElementMotion | undefined;
|
|
1856
|
+
children?: JsxChild;
|
|
1857
|
+
};
|
|
1858
|
+
//#endregion
|
|
1859
|
+
export { Transform as A, types_d_exports as B, NineSliceValue as C, Sprite as D, ShapeValue as E, Events as F, InputApi as H, I18nApi as I, Message as L, sprite as M, Argument as N, SpriteOptions as O, Config as P, State as R, NineSlice as S, Shape as T, State$1 as U, Config$1 as V, types_d_exports$1 as W, Style as _, JsxChild as a, defineComponent as b, UiNode as c, popup as d, defineTokens as f, ResolvedStyle as g, IsFlags as h, Finding as i, TransformValue as j, SpriteValue as k, Box as l, UiIntrinsicElements as m, ElementChange as n, JsxState as o, IntrinsicElementsFor as p, ElementMotion as r, PopupComponent as s, DescriptionNode as t, LocalWrite as u, StylesState as v, Parent as w, Display as x, WhenFlags as y, TypedTr as z };
|