lecodes-sdk 2.0.8 → 2.0.10
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 +3 -0
- package/dist/host.d.ts +6 -2
- package/dist/types/g2/Scene2D.d.ts +4 -0
- package/dist/types/gl/Light.d.ts +31 -7
- package/dist/types/gl/Scene.d.ts +4 -0
- package/dist/types/inject.d.ts +1 -1
- package/dist/types/runtime/misc.d.ts +4 -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 +1 -1
- package/prompts/3d-scene-files.md +3 -3
- package/prompts/3d-scene.md +1 -1
- package/prompts/core.md +1 -1
- package/prompts/dist/3d-app.md +4 -4
- package/prompts/ui.md +3 -7
- 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/assetMacro.ts +69 -4
- package/src/compile/bundler.ts +10 -3
- package/src/g2/Scene2D.ts +10 -0
- package/src/gl/Light.ts +257 -194
- package/src/gl/Material.ts +3 -0
- package/src/gl/Scene.ts +11 -1
- package/src/host.d.ts +6 -2
- package/src/inject.ts +1 -0
- package/src/runtime/misc.ts +4 -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/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.10"
|
|
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
|
}
|