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.
Files changed (51) hide show
  1. package/dist/global.d.ts +3 -0
  2. package/dist/host.d.ts +6 -2
  3. package/dist/types/g2/Scene2D.d.ts +4 -0
  4. package/dist/types/gl/Light.d.ts +31 -7
  5. package/dist/types/gl/Scene.d.ts +4 -0
  6. package/dist/types/inject.d.ts +1 -1
  7. package/dist/types/runtime/misc.d.ts +4 -0
  8. package/dist/types/ui/NativeView.d.ts +3 -0
  9. package/dist/types/ui/UI.d.ts +1 -0
  10. package/dist/types/ui/UILayer.d.ts +25 -0
  11. package/dist/types/ui/UIModal.d.ts +20 -16
  12. package/dist/types/ui/UIPopover.d.ts +5 -3
  13. package/dist/types/ui/UIVideo.d.ts +3 -0
  14. package/dist/types/ui/UIWidget.d.ts +25 -24
  15. package/dist/types/ui/presentable.d.ts +10 -0
  16. package/dist/types/ui/transitions.d.ts +2 -1
  17. package/dist/types/ui/tree.d.ts +1 -0
  18. package/dist/types/version.d.ts +1 -1
  19. package/dist/types.json +1 -1
  20. package/package.json +1 -1
  21. package/prompts/3d-scene-files.md +3 -3
  22. package/prompts/3d-scene.md +1 -1
  23. package/prompts/core.md +1 -1
  24. package/prompts/dist/3d-app.md +4 -4
  25. package/prompts/ui.md +3 -7
  26. package/src/bridges/gl.d.ts +5 -0
  27. package/src/bridges/tree.d.ts +24 -12
  28. package/src/chisel.ts +1 -1
  29. package/src/compile/assetMacro.ts +69 -4
  30. package/src/compile/bundler.ts +10 -3
  31. package/src/g2/Scene2D.ts +10 -0
  32. package/src/gl/Light.ts +257 -194
  33. package/src/gl/Material.ts +3 -0
  34. package/src/gl/Scene.ts +11 -1
  35. package/src/host.d.ts +6 -2
  36. package/src/inject.ts +1 -0
  37. package/src/runtime/misc.ts +4 -0
  38. package/src/ui/NativeView.ts +11 -0
  39. package/src/ui/UI.ts +1 -0
  40. package/src/ui/UIBottomSheet.ts +8 -7
  41. package/src/ui/UILayer.ts +88 -0
  42. package/src/ui/UIModal.ts +59 -43
  43. package/src/ui/UINode.ts +9 -1
  44. package/src/ui/UIPopover.ts +23 -9
  45. package/src/ui/UIVideo.ts +11 -0
  46. package/src/ui/UIWidget.ts +70 -44
  47. package/src/ui/presentable.ts +11 -1
  48. package/src/ui/transitions.ts +36 -7
  49. package/src/ui/tree.ts +4 -0
  50. package/src/version.ts +1 -1
  51. package/tests/helpers/fakeTree.ts +7 -3
@@ -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
- type Lane = { prop: string, kind: number, sig?: string, keys: number[][], rest: number[] }
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
- export const _transitionBlob = (spec: TransitionSpec): { data: Float32Array, strings: string[], roles: number[], flags: number } | null => {
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.8"
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
- widgets: number[] = []
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
- showWidget = (id: number, ownerKind: number, ownerId: number): void => { this.rec("showWidget", id, ownerKind, ownerId); if (!this.widgets.includes(id)) this.widgets.push(id) }
211
- hideWidget = (id: number): void => { this.rec("hideWidget", id); this.widgets = this.widgets.filter((w) => w !== id) }
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
  }