lecodes-sdk 2.0.7 → 2.0.9
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/dist/global.d.ts +6 -0
- package/dist/types/g2/Scene2D.d.ts +4 -0
- package/dist/types/gl/Light.d.ts +36 -1
- package/dist/types/gl/Scene.d.ts +4 -0
- package/dist/types/inject.d.ts +2 -1
- package/dist/types/runtime/rpc.d.ts +2 -0
- package/dist/types/server/context.d.ts +4 -0
- package/dist/types/server/db/defineDb.d.ts +1 -1
- package/dist/types/server/db/fields.d.ts +25 -1
- package/dist/types/server/db/marci/query.d.ts +39 -2
- package/dist/types/server/db/types.d.ts +13 -7
- package/dist/types/server/files/db.d.ts +39 -0
- package/dist/types/server/files/models.d.ts +131 -0
- package/dist/types/server/inject.d.ts +1 -0
- package/dist/types/ui/NativeView.d.ts +3 -0
- package/dist/types/ui/UI.d.ts +1 -0
- package/dist/types/ui/UILayer.d.ts +25 -0
- package/dist/types/ui/UIModal.d.ts +20 -16
- package/dist/types/ui/UIPopover.d.ts +5 -3
- package/dist/types/ui/UIVideo.d.ts +3 -0
- package/dist/types/ui/UIWidget.d.ts +25 -24
- package/dist/types/ui/presentable.d.ts +10 -0
- package/dist/types/ui/transitions.d.ts +2 -1
- package/dist/types/ui/tree.d.ts +1 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +3 -2
- package/prompts/3d-scene-files.md +3 -3
- package/prompts/3d-scene.md +1 -1
- package/prompts/dist/3d-app.md +4 -4
- package/src/bridges/gl.d.ts +5 -0
- package/src/bridges/tree.d.ts +24 -12
- package/src/chisel.ts +1 -1
- package/src/compile/libraryImports.ts +9 -0
- package/src/compile/serverTypes.ts +6 -0
- package/src/g2/Scene2D.ts +10 -0
- package/src/gl/Light.ts +78 -2
- package/src/gl/Material.ts +3 -0
- package/src/gl/Scene.ts +11 -1
- package/src/inject.ts +2 -0
- package/src/runtime/rpc.ts +15 -3
- package/src/server/context.ts +4 -0
- package/src/server/db/defineDb.ts +62 -9
- package/src/server/db/fields.ts +21 -1
- package/src/server/db/httpTransport.ts +9 -1
- package/src/server/db/marci/query.ts +71 -4
- package/src/server/db/types.ts +16 -5
- package/src/server/files/db.ts +182 -0
- package/src/server/files/host.ts +496 -0
- package/src/server/files/models.ts +116 -0
- package/src/server/host.ts +17 -4
- package/src/server/inject.ts +1 -0
- package/src/server/runtime.ts +41 -0
- package/src/server/validate.ts +5 -0
- package/src/ui/NativeView.ts +11 -0
- package/src/ui/UI.ts +1 -0
- package/src/ui/UIBottomSheet.ts +8 -7
- package/src/ui/UILayer.ts +88 -0
- package/src/ui/UIModal.ts +59 -43
- package/src/ui/UINode.ts +9 -1
- package/src/ui/UIPopover.ts +23 -9
- package/src/ui/UIVideo.ts +11 -0
- package/src/ui/UIWidget.ts +70 -44
- package/src/ui/presentable.ts +11 -1
- package/src/ui/transitions.ts +36 -7
- package/src/ui/tree.ts +4 -0
- package/src/version.ts +1 -1
- package/tests/helpers/fakeTree.ts +7 -3
package/src/ui/UIWidget.ts
CHANGED
|
@@ -1,42 +1,45 @@
|
|
|
1
1
|
import { TouchStartEvent } from "../runtime/touch"
|
|
2
|
-
import { Presentable } from "./presentable"
|
|
3
2
|
import { ContainerElement, buildUI, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type PaddingStyle, type PositionStyle, type UIChildArg, type UIContainerBase, type UINodeChild, type ChildrenFn } from "./UINode"
|
|
4
|
-
import {
|
|
3
|
+
import { UIOverlay } from "./UILayer"
|
|
4
|
+
import { TREE_FLAG_BACK, TREE_FLAG_INTERACTIVE, TREE_FLAG_OVERLAY_TAP, tree } from "./tree"
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle & PositionStyle & {
|
|
8
|
+
/** Shown (`"flex"`, the default) or not: what `show()` / `hide()` write, what a state block may
|
|
9
|
+
* flip (`$open: { display: "flex" }`). */
|
|
10
|
+
display?: "none" | "flex",
|
|
8
11
|
/** A layer behind the widget that intercepts clicks — `"transparent"` still intercepts, `null`
|
|
9
12
|
* removes the layer. A tap on it fires `onOverlayTap`. */
|
|
10
13
|
overlayColor?: Color | null
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
|
|
14
|
-
/** A floating overlay
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
+
/** A floating overlay that belongs to a WINDOW, not to a box (docs/plans/widgets-plan.md): a dialog,
|
|
18
|
+
* a sheet, a HUD, a mini-player. A widget is a CHILD of the place it belongs to — a `UIScreen`
|
|
19
|
+
* (among its children), a surface's content (`scene.setContent([hud])`) or the app layer
|
|
20
|
+
* (`UIOverlay`) — and is laid out and painted in the box of the outermost presented destination
|
|
21
|
+
* that contains it: absolutely positioned in that box, above everything else in it. A widget on a
|
|
22
|
+
* tab page covers the tab bar and rides the shell's push and pop. A widget inside a `UIColumn` or
|
|
23
|
+
* any other box is an error: a floating element of a box is an absolute child, not a widget.
|
|
24
|
+
*
|
|
25
|
+
* It shows with its place unless its style says `display: "none"`; `show()` / `hide()` flip that,
|
|
26
|
+
* and so does a state block (`$class: { display: "flex" }`). `UIModal` / `UIPopover` /
|
|
27
|
+
* `UIBottomSheet` build on it. */
|
|
17
28
|
export interface UIWidget extends UIContainerBase<UIWidgetStyle, DrawableStyle & BaseStyle & { overlayColor?: Color | null }> {
|
|
18
29
|
readonly type: "widget",
|
|
19
30
|
|
|
20
|
-
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* (= back to a global overlay). Set it while the widget is hidden. The widget is always its own
|
|
25
|
-
* overlay root mounted into the owner's page view — it never becomes a child of the owner, so it
|
|
26
|
-
* stays topmost and its updates never trigger the owner's relayout. Works with a `UIScreen` that
|
|
27
|
-
* is a page inside a `UIPager`: the widget mounts into that tab and rides its swipe.
|
|
28
|
-
*/
|
|
29
|
-
attachTo(owner: Presentable | null): this,
|
|
30
|
-
|
|
31
|
-
/** Show the widget. By default it is a global overlay above every destination — it stays
|
|
32
|
-
* visible across navigation until `hide()` (dialogs, mini-player, global loader). If the
|
|
33
|
-
* widget was attached via `attachTo(owner)`, it mounts on that page's layer instead and is
|
|
34
|
-
* only visible while the owner is presented. */
|
|
31
|
+
/** Show the widget: `display: "flex"`, on top of its root — it moves to the end of its parent's
|
|
32
|
+
* children, so a dialog opened later covers an earlier one and a HUD declared after it. A widget
|
|
33
|
+
* with no parent is appended to the app layer first (`UIOverlay`) — a global overlay above every
|
|
34
|
+
* destination, up until `hide()`. */
|
|
35
35
|
show(): void,
|
|
36
|
-
/**
|
|
36
|
+
/** Hide the widget: `display: "none"`. It stays where it is (its place, the app layer), ready
|
|
37
|
+
* for the next `show()`. */
|
|
37
38
|
hide(): void,
|
|
38
39
|
|
|
39
|
-
/**
|
|
40
|
+
/** Shown right now by its own display (`show()` ran, `hide()` hasn't, no state block hides
|
|
41
|
+
* it). True for a widget hidden WITH its page or its covered screen: that is its place's
|
|
42
|
+
* visibility, not the widget's. */
|
|
40
43
|
readonly isShown: boolean,
|
|
41
44
|
|
|
42
45
|
/** Touch began on the widget; `ev.track(...)` takes over the rest of the gesture. */
|
|
@@ -53,41 +56,63 @@ export interface UIWidget extends UIContainerBase<UIWidgetStyle, DrawableStyle &
|
|
|
53
56
|
|
|
54
57
|
export class WidgetElement extends ContainerElement<"widget"> {
|
|
55
58
|
scrollable = false as const
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
/** @internal The widget's own display as last known, while it has a place: what the app wrote,
|
|
60
|
+
* corrected by the runtime's report when a state block flipped it (TREE_EVENT_WIDGET_VISIBLE).
|
|
61
|
+
* False while the widget is nowhere (made, not placed; removed). */
|
|
62
|
+
_shown = false
|
|
58
63
|
constructor(type: "widget", style: UIWidgetStyle, children: UINodeChild[] | ChildrenFn) {
|
|
59
|
-
// Widgets are absolutely positioned in
|
|
64
|
+
// Widgets are absolutely positioned in their root's box — in the constructor (not the
|
|
60
65
|
// factory) so subclasses like ModalElement inherit it.
|
|
61
66
|
super(type, { position: "absolute", ...style }, children)
|
|
62
67
|
}
|
|
63
|
-
attachTo(owner: Presentable | null): this {
|
|
64
|
-
this._owner = owner ?? undefined
|
|
65
|
-
return this
|
|
66
|
-
}
|
|
67
68
|
show(): void {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
tree().showWidget(this._h.id, kind, id)
|
|
69
|
+
if (this._h.id === 0) return
|
|
70
|
+
const parentId = tree().parent(this._h.id)
|
|
71
|
+
if (parentId !== 0 && this._shown) return
|
|
72
|
+
if (parentId === 0) {
|
|
73
|
+
// No place of its own: the app layer takes it HIDDEN, and the flip below shows it — a show()
|
|
74
|
+
// is a display flip wherever the widget is, so its pose plays (a modal's fade) exactly as on
|
|
75
|
+
// a placed one. Appended shown, it would merely appear: a widget shows with its place, no pose.
|
|
76
|
+
this.style({ display: "none" } as any)
|
|
77
|
+
UIOverlay.append(this as any) // pins it: _pinned reads the display
|
|
78
78
|
} else {
|
|
79
|
-
|
|
79
|
+
// Shown = on top of its root: a show() moves the widget to the TAIL of its parent's children
|
|
80
|
+
// (child order is the one ordering rule), so a modal opened later covers an earlier one and
|
|
81
|
+
// the HUD declared after it — while still hidden, so the move is silent (no onClose / onOpen).
|
|
82
|
+
// A state block flipping the display moves nothing: the order is what the tree says.
|
|
83
|
+
const siblings = tree().children(parentId)
|
|
84
|
+
if (siblings[siblings.length - 1] !== this._h.id) {
|
|
85
|
+
tree().remove(parentId, this._h.id)
|
|
86
|
+
tree().insert(parentId, this._h.id, siblings.length - 1)
|
|
87
|
+
}
|
|
80
88
|
}
|
|
89
|
+
this.style({ display: "flex" } as any)
|
|
90
|
+
this._setShown(true)
|
|
81
91
|
}
|
|
82
92
|
hide(): void {
|
|
83
93
|
if (this._h.id === 0 || !this._shown) return
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
unpin(this._h.id)
|
|
94
|
+
this._setShown(false)
|
|
95
|
+
this.style({ display: "none" } as any)
|
|
87
96
|
}
|
|
88
97
|
get isShown(): boolean {
|
|
89
98
|
return this._shown
|
|
90
99
|
}
|
|
100
|
+
/** @internal The intent changed (the app's call, or the runtime's report of a cascade flip):
|
|
101
|
+
* subclasses hook the change (UIModal's onOpen / onClose). */
|
|
102
|
+
_setShown(shown: boolean): void {
|
|
103
|
+
if (shown === this._shown) return
|
|
104
|
+
this._shown = shown
|
|
105
|
+
this._shownChanged(shown)
|
|
106
|
+
}
|
|
107
|
+
/** @internal */
|
|
108
|
+
_shownChanged(_shown: boolean): void {}
|
|
109
|
+
/** @internal The runtime: the widget's resolved display flipped — or, at its place, the cascade
|
|
110
|
+
* says otherwise than the style it read in `_pinned` (the runtime reports nothing else there). */
|
|
111
|
+
_emitVisible(shown: boolean): void { this._setShown(shown) }
|
|
112
|
+
/** @internal Placed (inserted under a screen, a layer): shown by its own display from here on. */
|
|
113
|
+
_pinned(): void { this._setShown((this._style as { display?: string }).display !== "none") }
|
|
114
|
+
/** @internal Taken out of its place: shown nowhere. */
|
|
115
|
+
_unpinned(): void { this._setShown(false) }
|
|
91
116
|
|
|
92
117
|
readonly touchStartListeners: any[] = []
|
|
93
118
|
onTouchStart (callback: any): this {
|
|
@@ -138,7 +163,8 @@ const makeWidget = (style: any, children: any) => new WidgetElement("widget", st
|
|
|
138
163
|
/** @internal Compiler fast path (chisel `flatten_ui` raw lowering) — see UIContainer. */
|
|
139
164
|
export const __UIWidget = (children: UINodeChild[] | ChildrenFn): UIWidget => makeWidget({}, children)
|
|
140
165
|
|
|
141
|
-
/** Create a widget (same argument forms as `UIColumn`).
|
|
166
|
+
/** Create a widget (same argument forms as `UIColumn`). Put it where it belongs — among a screen's
|
|
167
|
+
* children, in `scene.setContent([...])`, or `UIOverlay` — position it with absolute-style props
|
|
142
168
|
* (`top`/`left`/`bottom`/`right`), then `show()`/`hide()`. */
|
|
143
169
|
export function UIWidget(...children: UIChildArg[]): UIWidget;
|
|
144
170
|
export function UIWidget(children: UINodeChild[] | ChildrenFn): UIWidget;
|
package/src/ui/presentable.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Anything that can occupy the app's main slot implements it: UIScreen, Scene (+ARScene),
|
|
3
3
|
// Scene2D, NativeView, UIVideo. Exactly one Presentable is visible at a time — a screen no
|
|
4
4
|
// longer overlays a scene, it replaces it (with a transition); UI over a scene goes through
|
|
5
|
-
// `
|
|
5
|
+
// `scene.setContent([hud])` — a widget is the destination's content (docs/plans/widgets-plan.md).
|
|
6
6
|
//
|
|
7
7
|
// A destination is addressed by (kind, id): a screen / video by its node id, a scene by its
|
|
8
8
|
// scene id, a native view by its viewId (`_dest()`). The runtime reports the lifecycle back through
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// element code into engine-only bundles.
|
|
13
13
|
|
|
14
14
|
import type { EasingInput } from "../animate/tween/easing"
|
|
15
|
+
import type { UIWidget } from "./UIWidget"
|
|
15
16
|
|
|
16
17
|
/** The built-in transitions (ui/transitions.ts holds what each one is). `push` / `pop` are the
|
|
17
18
|
* stacked-navigation pair — the incoming screen slides over the other one, which drifts and dims;
|
|
@@ -32,6 +33,9 @@ export type TransitionPose = {
|
|
|
32
33
|
opacity?: number | number[]
|
|
33
34
|
/** Black over this screen, 0..1: how dark the screen UNDER the other one gets. */
|
|
34
35
|
dim?: number | number[]
|
|
36
|
+
/** A widget's overlay layer (its scrim) in the pose — a widget's enter / exit pose only
|
|
37
|
+
* (`UIModal.transition`): `"transparent"` fades the scrim in with the dialog. */
|
|
38
|
+
overlayColor?: string | string[]
|
|
35
39
|
/** ms (default 300). */
|
|
36
40
|
duration?: number
|
|
37
41
|
/** ms to wait; the screen holds its first pose through it. */
|
|
@@ -97,6 +101,12 @@ export interface Presentable {
|
|
|
97
101
|
onOpen(callback: () => void): this
|
|
98
102
|
onClose(callback: () => void): this
|
|
99
103
|
onBack(callback: () => void): this
|
|
104
|
+
/** The destination's widgets (docs/plans/widgets-plan.md): a dialog, a HUD, a sheet declared as
|
|
105
|
+
* CONTENT of the place they belong to. A `UIScreen` takes them among its children; a surface — a
|
|
106
|
+
* `Scene`, a `Scene2D`, a `NativeView`, a `UIVideo` — takes widgets alone, laid out over it while
|
|
107
|
+
* it is presented (touches outside a widget's box reach the surface). A widget shows with its
|
|
108
|
+
* destination unless its style says `display: "none"`; `show()` / `hide()` flip that. */
|
|
109
|
+
setContent(content: (UIWidget | null | undefined | false)[] | (() => (UIWidget | null | undefined | false)[])): this
|
|
100
110
|
/** @internal Async work that must finish BEFORE this destination replaces the current one
|
|
101
111
|
* (camera permission, warm render, session launch — ARScene). The previous destination —
|
|
102
112
|
* typically a loading screen — stays visible while it runs; `open()` and `Router.push/replace`
|
package/src/ui/transitions.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Easing } from "../animate/easings"
|
|
11
11
|
import { DEFAULT_EASING, parseEasing, type EasingSpec } from "../animate/tween/easing"
|
|
12
|
-
import { CLOCK_UI, DOM_UI, KIND_FLOAT, KIND_TRANSFORM, buildBlob, parseTransformList, transformLanes, warnOnce, type Keyframe, type Track, type TweenTarget } from "../animate/tween/spec"
|
|
12
|
+
import { CLOCK_UI, DOM_UI, KIND_FLOAT, KIND_STRING, KIND_TRANSFORM, buildBlob, parseTransformList, transformLanes, warnOnce, type Keyframe, type Track, type TweenTarget } from "../animate/tween/spec"
|
|
13
13
|
import type { Transition, TransitionName, TransitionPose, TransitionSpec } from "./presentable"
|
|
14
14
|
|
|
15
15
|
/** Wire ids: a registered transition is > 0. */
|
|
@@ -120,7 +120,9 @@ export const mirrored = (s: TransitionSpec): TransitionSpec => ({
|
|
|
120
120
|
|
|
121
121
|
/** One prop of a side: its keys in the order the side plays them (an incoming side ends at rest
|
|
122
122
|
* after them, an outgoing one starts at rest before them), and what rest is. */
|
|
123
|
-
|
|
123
|
+
// A string lane (a widget's `overlayColor`): its keys are strings, its rest is CAPTURED at play —
|
|
124
|
+
// the widget's own scrim color, which the transition cannot know.
|
|
125
|
+
type Lane = { prop: string, kind: number, sig?: string, keys: number[][], rest: number[], strKeys?: string[] }
|
|
124
126
|
type Side = { lanes: Lane[], atMs: number, durMs: number, easing: EasingSpec }
|
|
125
127
|
|
|
126
128
|
const list = <T>(v: T | T[]): T[] => (Array.isArray(v) ? v : [v])
|
|
@@ -141,12 +143,16 @@ const transformLane = (value: string | string[]): Lane | null => {
|
|
|
141
143
|
const floatLane = (prop: string, value: number | number[], rest: number): Lane =>
|
|
142
144
|
({ prop, kind: KIND_FLOAT, keys: list(value).map((v) => [v]), rest: [rest] })
|
|
143
145
|
|
|
146
|
+
const stringLane = (prop: string, value: string | string[]): Lane =>
|
|
147
|
+
({ prop, kind: KIND_STRING, keys: list(value).map(() => [0]), rest: [0], strKeys: list(value) })
|
|
148
|
+
|
|
144
149
|
const sideOf = (pose: TransitionPose | undefined): Side | null => {
|
|
145
150
|
if (!pose) return null
|
|
146
151
|
const lanes: Lane[] = []
|
|
147
152
|
if (pose.transform !== undefined) { const l = transformLane(pose.transform); if (l) lanes.push(l) }
|
|
148
153
|
if (pose.opacity !== undefined) lanes.push(floatLane("opacity", pose.opacity, 1))
|
|
149
154
|
if (pose.dim !== undefined) lanes.push(floatLane("dim", pose.dim, 0))
|
|
155
|
+
if (pose.overlayColor !== undefined) lanes.push(stringLane("overlayColor", pose.overlayColor))
|
|
150
156
|
if (lanes.length === 0) return null
|
|
151
157
|
return {
|
|
152
158
|
lanes,
|
|
@@ -162,7 +168,7 @@ const sideOf = (pose: TransitionPose | undefined): Side | null => {
|
|
|
162
168
|
* the direction being the opposite one. No dim: nothing is over it. */
|
|
163
169
|
const borrowed = (side: Side | null): Side | null => {
|
|
164
170
|
if (!side) return null
|
|
165
|
-
const lanes = side.lanes.filter((l) => l.prop !== "dim").map((l) => {
|
|
171
|
+
const lanes = side.lanes.filter((l) => l.prop !== "dim" && l.strKeys === undefined).map((l) => {
|
|
166
172
|
if (l.kind !== KIND_TRANSFORM) return { ...l, keys: l.keys.slice().reverse() }
|
|
167
173
|
const sign: number[] = []
|
|
168
174
|
for (const fn of l.sig!.split(",")) for (let i = transformLanes(fn); i > 0; i--) sign.push(fn.startsWith("translate") ? -1 : 1)
|
|
@@ -178,6 +184,12 @@ const tracksOf = (side: Side | null, role: number, incoming: boolean): Track[] =
|
|
|
178
184
|
if (!side) return []
|
|
179
185
|
const channel = { domain: DOM_UI, id: () => role + 1, value: () => null }
|
|
180
186
|
return side.lanes.map((l) => {
|
|
187
|
+
if (l.strKeys !== undefined) {
|
|
188
|
+
// a string lane: the keys as strings, the rest captured at play (the node's own value)
|
|
189
|
+
const values: (string | null)[] = incoming ? [...l.strKeys, null] : [null, ...l.strKeys]
|
|
190
|
+
const keys: Keyframe[] = values.map((str, i) => ({ t: i / (values.length - 1), easing: side.easing, value: str === null ? undefined : { str } }))
|
|
191
|
+
return { target: roleTarget, prop: l.prop, channel, kind: l.kind, lanes: 1, atMs: side.atMs, durMs: side.durMs, commit: false, keys }
|
|
192
|
+
}
|
|
181
193
|
// explicit keys at both ends: a captured one that fails to read would leave a screen in its pose
|
|
182
194
|
const values = incoming ? [...l.keys, l.rest] : [l.rest, ...l.keys]
|
|
183
195
|
const keys: Keyframe[] = values.map((lanes, i) => ({ t: i / (values.length - 1), easing: side.easing, value: { lanes } }))
|
|
@@ -186,15 +198,16 @@ const tracksOf = (side: Side | null, role: number, incoming: boolean): Track[] =
|
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
/** @internal The blob of a spec: `roles[i]` is the role of the blob's target `i`. Null = the spec
|
|
189
|
-
* moves nothing.
|
|
190
|
-
|
|
201
|
+
* moves nothing. `ownAlone`: the ALONE roles are the spec's own sides as written (a widget's pose:
|
|
202
|
+
* it enters from `enter`, leaves to `exit`, nothing is borrowed from the other side). */
|
|
203
|
+
export const _transitionBlob = (spec: TransitionSpec, ownAlone = false): { data: Float32Array, strings: string[], roles: number[], flags: number } | null => {
|
|
191
204
|
const enter = sideOf(spec.enter), exit = sideOf(spec.exit)
|
|
192
205
|
const enterOnTop = (spec.onTop ?? "enter") === "enter"
|
|
193
206
|
const tracks = [
|
|
194
207
|
...tracksOf(enter, ROLE_ENTER, true),
|
|
195
208
|
...tracksOf(exit, ROLE_EXIT, false),
|
|
196
|
-
...tracksOf(enterOnTop ? enter : borrowed(exit), ROLE_ENTER_ALONE, true),
|
|
197
|
-
...tracksOf(enterOnTop ? borrowed(enter) : exit, ROLE_EXIT_ALONE, false),
|
|
209
|
+
...tracksOf(ownAlone ? enter : enterOnTop ? enter : borrowed(exit), ROLE_ENTER_ALONE, true),
|
|
210
|
+
...tracksOf(ownAlone ? exit : enterOnTop ? borrowed(enter) : exit, ROLE_EXIT_ALONE, false),
|
|
198
211
|
]
|
|
199
212
|
if (tracks.length === 0) return null
|
|
200
213
|
let durationMs = 0
|
|
@@ -242,8 +255,24 @@ export const _transitionBack = (t: Transition): number => {
|
|
|
242
255
|
return e.back
|
|
243
256
|
}
|
|
244
257
|
|
|
258
|
+
/** @internal The wire id of a WIDGET's pose (docs/plans/widgets-plan.md decision 11): the pose it
|
|
259
|
+
* enters from and leaves to, as one transition whose ALONE roles the runtime plays on the widget's
|
|
260
|
+
* display flips. Registered once per distinct pose object; `null` = no pose (TRANSITION_NONE). */
|
|
261
|
+
let poseIds = new WeakMap<TransitionPose, number>()
|
|
262
|
+
export const _widgetPoseId = (pose: TransitionPose | null): number => {
|
|
263
|
+
if (!pose) return TRANSITION_NONE
|
|
264
|
+
let id = poseIds.get(pose)
|
|
265
|
+
if (id === undefined) {
|
|
266
|
+
const blob = _transitionBlob({ enter: pose, exit: pose }, true)
|
|
267
|
+
id = blob ? _creatorTree.defineTransition(blob.data, blob.strings, blob.roles, blob.flags) : TRANSITION_NONE
|
|
268
|
+
poseIds.set(pose, id)
|
|
269
|
+
}
|
|
270
|
+
return id
|
|
271
|
+
}
|
|
272
|
+
|
|
245
273
|
/** @internal Tests: a new fake runtime has no transition of the previous one. */
|
|
246
274
|
export const _resetTransitions = (): void => {
|
|
247
275
|
byName = new Map()
|
|
248
276
|
bySpec = new WeakMap()
|
|
277
|
+
poseIds = new WeakMap()
|
|
249
278
|
}
|
package/src/ui/tree.ts
CHANGED
|
@@ -45,6 +45,7 @@ export const TREE_EVENT_HOVER_ENTER = 32
|
|
|
45
45
|
export const TREE_EVENT_HOVER_MOVE = 33
|
|
46
46
|
export const TREE_EVENT_HOVER_END = 34
|
|
47
47
|
export const TREE_EVENT_HOVER_CANCEL = 35
|
|
48
|
+
export const TREE_EVENT_WIDGET_VISIBLE = 36
|
|
48
49
|
|
|
49
50
|
// ---- setFlags(id, mask) ---------------------------------------------------------------------------
|
|
50
51
|
export const TREE_FLAG_INTERACTIVE = 1 << 0
|
|
@@ -132,6 +133,8 @@ export interface TreeElement {
|
|
|
132
133
|
_pinned?(): void
|
|
133
134
|
_unpinned?(): void
|
|
134
135
|
_emitBack?(): boolean
|
|
136
|
+
/** A widget: its resolved display flipped (a style write or the cascade) — `isShown` follows. */
|
|
137
|
+
_emitVisible?(shown: boolean): void
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
/** @internal A track handler (runtime/touch.ts TrackHandler), as the emitter reads it. */
|
|
@@ -285,6 +288,7 @@ const emit = (id: number, kind: number, a?: any, b?: any, c?: any, d?: any, e?:
|
|
|
285
288
|
t?.onEnd?.({ clientX: a, clientY: b, deltaX: c, deltaY: d })
|
|
286
289
|
return
|
|
287
290
|
}
|
|
291
|
+
case TREE_EVENT_WIDGET_VISIBLE: pins.get(id)?._emitVisible?.(!!a); return
|
|
288
292
|
case TREE_EVENT_HOVER_CANCEL: {
|
|
289
293
|
const t = hoverTracker
|
|
290
294
|
hoverTracker = null
|
package/src/version.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// same manifest). Its MAJOR is the bundle ↔ runtime contract: a bundle's `// sdk:` header line
|
|
5
5
|
// and a host's embedded version must agree on the major for the host to run the bundle; minor
|
|
6
6
|
// and patch never gate anything (features are detected, never versioned).
|
|
7
|
-
export const SDK_VERSION = "2.0.
|
|
7
|
+
export const SDK_VERSION = "2.0.9"
|
|
8
8
|
|
|
9
9
|
/** The major of a semver string, or null when it is not one. */
|
|
10
10
|
export const sdkMajor = (version: string | null | undefined): number | null => {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
TREE_EVENT_CLOSE, TREE_EVENT_DETACHED, TREE_EVENT_FREED, TREE_EVENT_OPEN, TREE_EVENT_ROUTER_CHANGE, TREE_EVENT_VLIST_SYNC,
|
|
10
10
|
} from "../../src/ui/tree"
|
|
11
11
|
import { _resetTransitions } from "../../src/ui/transitions"
|
|
12
|
+
import { _resetOverlay } from "../../src/ui/UILayer"
|
|
12
13
|
|
|
13
14
|
export type FakeNode = {
|
|
14
15
|
id: number, type: string, parent: number, children: number[],
|
|
@@ -28,7 +29,9 @@ export class FakeTree {
|
|
|
28
29
|
displaySize: [number, number] = [0, 0]
|
|
29
30
|
current: Dest | null = null
|
|
30
31
|
stack: Dest[] = []
|
|
31
|
-
|
|
32
|
+
/** The layers by destination key (`kind:id`; `255:0` = the app layer) and the widgets' poses. */
|
|
33
|
+
layers = new Map<string, number>()
|
|
34
|
+
poses = new Map<number, number>()
|
|
32
35
|
freed: number[] = []
|
|
33
36
|
tweens: { id: number, data: Float32Array, strings: string[], targets: number[] }[] = []
|
|
34
37
|
tweenEvent: ((id: number, kind: number, index: number, ...lanes: number[]) => void) | null = null
|
|
@@ -207,8 +210,8 @@ export class FakeTree {
|
|
|
207
210
|
else onReject(`no view ${viewId}`)
|
|
208
211
|
}
|
|
209
212
|
viewCallHandler: ((viewId: number, method: string, args: any[], res: (r?: unknown) => void, rej: (m: string) => void) => void) | null = null
|
|
210
|
-
|
|
211
|
-
|
|
213
|
+
setLayer = (kind: number, id: number, layer: number): void => { this.rec("setLayer", kind, id, layer); if (layer) this.layers.set(`${kind}:${id}`, layer); else this.layers.delete(`${kind}:${id}`) }
|
|
214
|
+
setWidgetPose = (id: number, transition: number): void => { this.rec("setWidgetPose", id, transition); this.poses.set(id, transition) }
|
|
212
215
|
|
|
213
216
|
// ---- pager -----------------------------------------------------------------------------------------
|
|
214
217
|
private pagerTop(p: FakeNode): number | null {
|
|
@@ -341,6 +344,7 @@ export const installFakeTree = (): FakeTree => {
|
|
|
341
344
|
fake.emitter = FakeTree.primary ?? prev?.emitter ?? null
|
|
342
345
|
if (prev?.tweenEvent) fake.tweenEvent = prev.tweenEvent // registered once per world too
|
|
343
346
|
_resetTransitions() // …while a transition is registered per RUNTIME: the new fake has none
|
|
347
|
+
_resetOverlay() // …and the app layer is a node of the previous fake
|
|
344
348
|
;(globalThis as any)._creatorTree = fake
|
|
345
349
|
return fake
|
|
346
350
|
}
|