lecodes-sdk 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/README.md +5 -2
  2. package/dist/editor.d.ts +12 -0
  3. package/dist/global.d.ts +4 -8
  4. package/dist/host.d.ts +2 -3
  5. package/dist/types/animate/tween/Animation.d.ts +0 -3
  6. package/dist/types/animate/tween/animateValue.d.ts +4 -2
  7. package/dist/types/animate/tween/easing.d.ts +8 -0
  8. package/dist/types/animate/tween/spec.d.ts +15 -7
  9. package/dist/types/audio/audio.d.ts +2 -1
  10. package/dist/types/canvas/Canvas.d.ts +40 -108
  11. package/dist/types/canvas/gen/cssColor.d.ts +17 -0
  12. package/dist/types/canvas/gen/recorder.d.ts +118 -0
  13. package/dist/types/canvas/gen/spec.d.ts +144 -0
  14. package/dist/types/core/color.d.ts +3 -1
  15. package/dist/types/core/pins.d.ts +18 -0
  16. package/dist/types/g2/Node2D.d.ts +5 -8
  17. package/dist/types/g2/Scene2D.d.ts +6 -2
  18. package/dist/types/gl/Foliage.d.ts +30 -7
  19. package/dist/types/gl/Light.d.ts +8 -0
  20. package/dist/types/gl/Lightmap.d.ts +13 -2
  21. package/dist/types/gl/Material.d.ts +17 -3
  22. package/dist/types/gl/Model.d.ts +6 -2
  23. package/dist/types/gl/Node.d.ts +3 -6
  24. package/dist/types/gl/Scene.d.ts +41 -13
  25. package/dist/types/gl/Texture.d.ts +1 -1
  26. package/dist/types/gl/animation/Locomotion.d.ts +8 -1
  27. package/dist/types/inject.d.ts +11 -11
  28. package/dist/types/inject.editor.d.ts +1 -0
  29. package/dist/types/net/core.d.ts +7 -0
  30. package/dist/types/plugin.d.ts +76 -0
  31. package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
  32. package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
  33. package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
  34. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
  35. package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
  36. package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
  37. package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
  38. package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
  39. package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
  40. package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
  41. package/dist/types/runtime/app.d.ts +9 -2
  42. package/dist/types/runtime/fetch.d.ts +2 -0
  43. package/dist/types/runtime/input.d.ts +1 -1
  44. package/dist/types/runtime/media.d.ts +6 -10
  45. package/dist/types/runtime/misc.d.ts +4 -1
  46. package/dist/types/runtime/net.d.ts +3 -2
  47. package/dist/types/runtime/touch.d.ts +32 -0
  48. package/dist/types/scene/defineScene.d.ts +43 -2
  49. package/dist/types/scene/editor.d.ts +52 -0
  50. package/dist/types/scene/gizmos.d.ts +7 -4
  51. package/dist/types/ui/NativeView.d.ts +6 -4
  52. package/dist/types/ui/UI.d.ts +1 -1
  53. package/dist/types/ui/UIBottomSheet.d.ts +6 -12
  54. package/dist/types/ui/UIButton.d.ts +12 -14
  55. package/dist/types/ui/UIContainer.d.ts +0 -6
  56. package/dist/types/ui/UIImage.d.ts +1 -4
  57. package/dist/types/ui/UIInput.d.ts +8 -24
  58. package/dist/types/ui/UIModal.d.ts +0 -2
  59. package/dist/types/ui/UINode.d.ts +70 -56
  60. package/dist/types/ui/UIPager.d.ts +28 -27
  61. package/dist/types/ui/UIPopover.d.ts +0 -2
  62. package/dist/types/ui/UIScreen.d.ts +12 -17
  63. package/dist/types/ui/UIScrollable.d.ts +1 -4
  64. package/dist/types/ui/UIText.d.ts +0 -2
  65. package/dist/types/ui/UIVideo.d.ts +3 -5
  66. package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
  67. package/dist/types/ui/UIWidget.d.ts +10 -9
  68. package/dist/types/ui/colorKeys.gen.d.ts +9 -0
  69. package/dist/types/ui/presentable.d.ts +46 -32
  70. package/dist/types/ui/router.d.ts +18 -7
  71. package/dist/types/ui/styleColor.d.ts +1 -0
  72. package/dist/types/ui/transitions.d.ts +18 -0
  73. package/dist/types/ui/tree.d.ts +75 -0
  74. package/dist/types/version.d.ts +10 -0
  75. package/dist/types.json +1 -1
  76. package/package.json +12 -3
  77. package/prompts/2d.md +2 -6
  78. package/prompts/3d.md +1 -5
  79. package/prompts/README.md +1 -1
  80. package/prompts/canvas.md +9 -8
  81. package/prompts/compose.ts +1 -1
  82. package/prompts/core.md +3 -3
  83. package/prompts/design.md +1 -1
  84. package/prompts/dist/2d-game.md +473 -239
  85. package/prompts/dist/3d-app.md +553 -205
  86. package/prompts/dist/ar-app.md +435 -202
  87. package/prompts/dist/design.md +113 -95
  88. package/prompts/dist/ui-app.md +386 -170
  89. package/prompts/ui-design.md +2 -3
  90. package/prompts/ui.md +45 -37
  91. package/src/animate/tween/Animation.ts +34 -150
  92. package/src/animate/tween/Timeline.ts +175 -175
  93. package/src/animate/tween/animateValue.ts +6 -3
  94. package/src/animate/tween/easing.ts +10 -3
  95. package/src/animate/tween/spec.ts +41 -15
  96. package/src/audio/Sound.ts +3 -3
  97. package/src/audio/audio.ts +2 -1
  98. package/src/bridges/2d.d.ts +317 -0
  99. package/src/bridges/app.d.ts +91 -0
  100. package/src/bridges/audio.d.ts +97 -0
  101. package/src/bridges/canvas.d.ts +79 -0
  102. package/src/bridges/device.d.ts +72 -0
  103. package/src/bridges/fetch.d.ts +80 -0
  104. package/src/bridges/files.d.ts +70 -0
  105. package/src/bridges/gl.d.ts +1133 -0
  106. package/src/bridges/input.d.ts +72 -0
  107. package/src/bridges/media.d.ts +55 -0
  108. package/src/bridges/nav.d.ts +71 -0
  109. package/src/bridges/net.d.ts +52 -0
  110. package/src/bridges/service.d.ts +52 -0
  111. package/src/bridges/socket.d.ts +31 -0
  112. package/src/bridges/storage.d.ts +33 -0
  113. package/src/bridges/tree.d.ts +301 -0
  114. package/src/bridges/types.d.ts +49 -0
  115. package/src/canvas/Canvas.ts +114 -159
  116. package/src/canvas/gen/cssColor.ts +224 -0
  117. package/src/canvas/gen/recorder.ts +212 -0
  118. package/src/canvas/gen/spec.ts +201 -0
  119. package/src/chisel.ts +193 -0
  120. package/src/compile/assetMacro.ts +1 -1
  121. package/src/compile/bundler.ts +11 -2
  122. package/src/compile/compileProject.ts +43 -4
  123. package/src/compile/fontMacro.ts +3 -4
  124. package/src/compile/header.ts +26 -5
  125. package/src/compile/index.ts +3 -1
  126. package/src/compile/liteMaterial.ts +1 -1
  127. package/src/compile/sceneEditor.ts +11 -26
  128. package/src/core/color.ts +73 -30
  129. package/src/core/pins.ts +51 -0
  130. package/src/core/signals.ts +8 -1
  131. package/src/g2/CharacterController2D.ts +3 -3
  132. package/src/g2/Node2D.ts +57 -39
  133. package/src/g2/Physics2D.ts +2 -2
  134. package/src/g2/Scene2D.ts +35 -23
  135. package/src/g2/Texture2D.ts +1 -1
  136. package/src/g2/loop.ts +4 -4
  137. package/src/gl/CameraPlace.ts +52 -52
  138. package/src/gl/Foliage.ts +72 -17
  139. package/src/gl/Geometry.ts +1 -2
  140. package/src/gl/Light.ts +10 -0
  141. package/src/gl/Lightmap.ts +45 -29
  142. package/src/gl/Material.ts +95 -51
  143. package/src/gl/Mesh.ts +120 -120
  144. package/src/gl/Model.ts +21 -16
  145. package/src/gl/Node.ts +91 -24
  146. package/src/gl/Particles.ts +1 -1
  147. package/src/gl/Scene.ts +100 -44
  148. package/src/gl/Texture.ts +8 -7
  149. package/src/gl/animation/AnimationClip.ts +1 -1
  150. package/src/gl/animation/DynamicBone.ts +482 -482
  151. package/src/gl/animation/Locomotion.ts +8 -3
  152. package/src/gl/nav/NavMesh.ts +3 -4
  153. package/src/gl/physics/Physics.ts +2 -2
  154. package/src/gl/physics/physicsEvents.ts +3 -3
  155. package/src/gl/scenarios.ts +291 -291
  156. package/src/gl/terrain/Terrain.ts +4 -5
  157. package/src/gl/touch.ts +14 -15
  158. package/src/host.d.ts +2 -3
  159. package/src/inject.editor.ts +7 -0
  160. package/src/inject.ts +13 -16
  161. package/src/net/core.ts +6 -5
  162. package/src/net/index.ts +1 -1
  163. package/src/net/replication.ts +1 -1
  164. package/src/plugin.ts +191 -0
  165. package/src/plugins/gen/camera/contract.d.ts +27 -0
  166. package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
  167. package/src/plugins/gen/camera/sdk/camera.ts +57 -0
  168. package/src/plugins/gen/geolocation/contract.d.ts +50 -0
  169. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
  170. package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
  171. package/src/plugins/gen/map/contract.d.ts +144 -0
  172. package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
  173. package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
  174. package/src/plugins/gen/push/contract.d.ts +61 -0
  175. package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
  176. package/src/plugins/gen/push/sdk/push.ts +105 -0
  177. package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
  178. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
  179. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
  180. package/src/plugins/permission.ts +5 -4
  181. package/src/runtime/app.ts +20 -9
  182. package/src/runtime/appEvents.ts +5 -4
  183. package/src/runtime/channel.ts +18 -15
  184. package/src/runtime/clipboard.ts +4 -3
  185. package/src/runtime/datetime.ts +2 -1
  186. package/src/runtime/device.ts +17 -15
  187. package/src/runtime/fetch.ts +30 -20
  188. package/src/runtime/files.ts +16 -15
  189. package/src/runtime/input.ts +12 -10
  190. package/src/runtime/media.ts +50 -46
  191. package/src/runtime/misc.ts +7 -3
  192. package/src/runtime/net.ts +8 -7
  193. package/src/runtime/rpc.ts +1 -3
  194. package/src/runtime/service.ts +19 -14
  195. package/src/runtime/share.ts +4 -3
  196. package/src/runtime/storage.ts +6 -4
  197. package/src/runtime/touch.ts +32 -0
  198. package/src/scene/defineScene.ts +61 -365
  199. package/src/scene/editor.ts +408 -0
  200. package/src/scene/editorPlugins.ts +3 -3
  201. package/src/scene/gizmos.ts +15 -9
  202. package/src/server/db/marci/query.ts +1 -1
  203. package/src/server/host.ts +1 -1
  204. package/src/server/runtime.ts +1 -1
  205. package/src/ui/NativeView.ts +72 -25
  206. package/src/ui/UI.ts +3 -3
  207. package/src/ui/UIBottomSheet.ts +16 -17
  208. package/src/ui/UIButton.ts +54 -16
  209. package/src/ui/UIContainer.ts +0 -6
  210. package/src/ui/UIImage.ts +34 -30
  211. package/src/ui/UIInput.ts +29 -37
  212. package/src/ui/UIModal.ts +1 -3
  213. package/src/ui/UINode.ts +347 -297
  214. package/src/ui/UIPager.ts +93 -78
  215. package/src/ui/UIPopover.ts +0 -2
  216. package/src/ui/UIScreen.ts +41 -36
  217. package/src/ui/UIScrollable.ts +19 -13
  218. package/src/ui/UISpacer.ts +1 -1
  219. package/src/ui/UITabs.ts +8 -6
  220. package/src/ui/UIText.ts +7 -17
  221. package/src/ui/UIVideo.ts +30 -27
  222. package/src/ui/UIVirtualizedList.ts +58 -59
  223. package/src/ui/UIWidget.ts +38 -18
  224. package/src/ui/colorKeys.gen.ts +37 -0
  225. package/src/ui/fonts.ts +2 -2
  226. package/src/ui/presentable.ts +59 -42
  227. package/src/ui/router.ts +52 -35
  228. package/src/ui/styleColor.ts +56 -0
  229. package/src/ui/theme.ts +7 -6
  230. package/src/ui/transitions.ts +249 -0
  231. package/src/ui/tree.ts +346 -0
  232. package/src/version.ts +24 -0
  233. package/tests/helpers/engineWorld.ts +11 -0
  234. package/tests/helpers/fakeTree.ts +353 -0
  235. package/tests/helpers/hostStubs.ts +31 -0
  236. package/tests/helpers/index.ts +14 -0
  237. package/tests/helpers/memoryMarci.ts +124 -0
  238. package/tests/helpers/phases.ts +23 -0
  239. package/tests/helpers/preload.ts +18 -0
  240. package/tests/helpers/stubApp.ts +2 -0
  241. package/tests/helpers/stubDevice.ts +2 -0
  242. package/tests/helpers/stubFetch.ts +2 -0
  243. package/tests/helpers/stubInput.ts +2 -0
  244. package/dist/inject.js +0 -4629
  245. package/dist/types/core/registry.d.ts +0 -7
  246. package/dist/types/plugins/camera.d.ts +0 -25
  247. package/dist/types/plugins/push.d.ts +0 -46
  248. package/src/bridges.d.ts +0 -1769
  249. package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
  250. package/src/compile/__tests__/assetMacro.test.ts +0 -100
  251. package/src/compile/__tests__/assetName.test.ts +0 -55
  252. package/src/compile/__tests__/compile.test.ts +0 -310
  253. package/src/compile/__tests__/detectEntry.test.ts +0 -151
  254. package/src/compile/__tests__/fontMacro.test.ts +0 -199
  255. package/src/compile/__tests__/serverSplit.test.ts +0 -27
  256. package/src/core/__tests__/stateMachine.test.ts +0 -132
  257. package/src/core/registry.ts +0 -23
  258. package/src/plugins/camera.ts +0 -81
  259. package/src/plugins/push.ts +0 -132
  260. package/src/plugins/qr.ts +0 -73
@@ -10,8 +10,10 @@
10
10
  // wrapper convention.
11
11
 
12
12
  import { _channelCall, _channelEmit, _channelOff, _channelOn, type ChannelListeners } from "../runtime/channel"
13
- import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "./presentable"
13
+ import { _bumpNavEpoch, _setCurrent, Presentable, type DestTuple, type DismissOptions, type PresentOptions } from "./presentable"
14
+ import { _transitionId, TRANSITION_NONE } from "./transitions"
14
15
  import { Element, type BaseStyle, type DrawableStyle, type ElementStyle, type UIElementBase } from "./UINode"
16
+ import { TREE_DEST_NATIVE, registerDest, registerNativeView, tree, unregisterNativeView } from "./tree"
15
17
 
16
18
  export type UINativeViewStyle = ElementStyle & DrawableStyle
17
19
 
@@ -28,25 +30,30 @@ export interface NativeView extends UIElementBase<UINativeViewStyle, DrawableSty
28
30
  /** Show fullscreen as the current destination (see Presentable). */
29
31
  open(options?: PresentOptions): void,
30
32
  /** Dismiss if this is the visible destination. */
31
- close(): void,
33
+ close(options?: DismissOptions): void,
32
34
 
33
35
  /** Became the visible destination. */
34
36
  onOpen(callback: () => void): this
35
37
  /** Stopped being the visible destination. */
36
38
  onClose(callback: () => void): this
37
39
  /** Hardware/system back while current. */
38
- onBackPressed(callback: () => void): this
40
+ onBack(callback: () => void): this
39
41
 
40
42
  /** Invoke a method on the native view (`map.call("setCenter", [lng, lat])`). Args and the
41
- * result are JSON-serialized. Rejects if the host has no such view/method. */
43
+ * result are plain data (and Uint8Array). Rejects if the host has no such view/method. */
42
44
  call(method: string, ...args: any[]): Promise<any>
43
45
  /** Subscribe to an event the native view emits (`map.on("markerTap", cb)`). */
44
46
  on(event: string, callback: (data?: any) => void): this
45
47
  off(event: string, callback: (data?: any) => void): this
48
+ /** @internal The destination tuple (Presentable). */
49
+ _dest(): DestTuple
46
50
  }
47
51
 
48
52
  let nextViewId = 1
49
53
 
54
+ const PRESENTED = 1
55
+ const MOUNTED = 2
56
+
50
57
  /** @internal Base for typed plugin wrappers (QRScanner, CameraView — src/plugins/): subclass it,
51
58
  * pin the viewName/params, and layer typed methods over the raw `call`/`on` channel. Not a
52
59
  * public global — apps use the `NativeView(name)` factory below. */
@@ -55,37 +62,41 @@ export class NativeViewElement extends Element<"native"> implements Presentable
55
62
  readonly params: any
56
63
  /** @internal stable per-instance handle for the viewCall channel + promotion identity. */
57
64
  readonly _viewId: number
58
- /** @internal event listeners by event name — the host invokes these (vlist pcl/psl convention)
59
- * or calls _emitViewEvent. */
65
+ /** @internal event listeners by event name (host → view events come through the tree emitter). */
60
66
  readonly nvl: ChannelListeners = {}
61
67
  readonly ol: (() => void)[] = []
62
68
  readonly cl: (() => void)[] = []
63
- _backButtonCallback?: () => void
64
- private _vd?: object
69
+ private _backButtonCallback?: () => void
70
+ private readonly _paramsJson: string
71
+ /** @internal Where the host has a view of this instance: PRESENTED | MOUNTED. */
72
+ private _shown = 0
65
73
 
66
74
  constructor(viewName: string, params: any) {
67
75
  super("native", {})
68
76
  this.viewName = viewName
69
77
  this.params = params
70
78
  this._viewId = nextViewId++
79
+ this._paramsJson = JSON.stringify(params ?? {})
80
+ tree().setString(this._h.id, "viewName", viewName)
81
+ tree().setString(this._h.id, "viewParams", this._paramsJson)
82
+ tree().setInt(this._h.id, "viewId", this._viewId)
83
+ registerDest(TREE_DEST_NATIVE, this._viewId, this)
71
84
  }
72
85
 
73
- /** @internal stable wire descriptor; hosts hand it back via router onChange (`_p` = this). */
74
- _viewDesc(): object {
75
- return this._vd ??= { type: "native", viewName: this.viewName, params: this.params, viewId: this._viewId, _p: this }
76
- }
86
+ /** @internal */
87
+ _dest(): DestTuple { return [TREE_DEST_NATIVE, this._viewId, this.viewName, this._paramsJson] }
77
88
 
78
89
  open(options?: PresentOptions): void {
79
- if (!_navSupported()) {
80
- throw new Error(`NativeView("${this.viewName}") is not supported on this host (requires _creatorUI.openView)`)
90
+ if (!tree().isViewSupported(this.viewName)) {
91
+ throw new Error(`NativeView("${this.viewName}") is not supported on this host (no registered view)`)
81
92
  }
82
93
  _bumpNavEpoch()
83
- _creatorUI.openView!(this._viewDesc(), options?.transition ?? "none")
94
+ tree().open(TREE_DEST_NATIVE, this._viewId, this.viewName, this._paramsJson, _transitionId(options?.transition, TRANSITION_NONE))
84
95
  _setCurrent(this)
85
96
  }
86
- close(): void {
97
+ close(options?: DismissOptions): void {
87
98
  _bumpNavEpoch()
88
- if (_navSupported()) _creatorUI.closeView!()
99
+ tree().close(TREE_DEST_NATIVE, this._viewId, _transitionId(options?.transition, TRANSITION_NONE))
89
100
  if (Presentable.current === this) _setCurrent(null)
90
101
  }
91
102
 
@@ -97,15 +108,47 @@ export class NativeViewElement extends Element<"native"> implements Presentable
97
108
  this.cl.push(callback)
98
109
  return this
99
110
  }
100
- onBackPressed(callback: () => void): this {
111
+ onBack(callback: () => void): this {
101
112
  this._backButtonCallback = callback
102
113
  return this
103
114
  }
115
+ /** @internal */
116
+ // The host has a view that can send events while the instance is presented OR mounted among a
117
+ // screen's children: findable by viewId while it is either (owned handles) — a promoted view that
118
+ // closes is still the embedded one.
119
+ private _reach(where: number, on: boolean): void {
120
+ const was = this._shown
121
+ this._shown = on ? was | where : was & ~where
122
+ if (was === 0 && this._shown !== 0) registerNativeView(this._viewId, this)
123
+ else if (was !== 0 && this._shown === 0) unregisterNativeView(this._viewId)
124
+ }
125
+ /** @internal */
126
+ _emitOpen(): void { this._reach(PRESENTED, true); for (const cb of this.ol.slice()) cb() }
127
+ /** @internal */
128
+ _emitClose(): void { this._reach(PRESENTED, false); for (const cb of this.cl.slice()) cb() }
129
+ /** @internal */
130
+ _pinned(): void { this._reach(MOUNTED, true) }
131
+ /** @internal */
132
+ _unpinned(): void { this._reach(MOUNTED, false) }
133
+ /** @internal */
134
+ override _freed(): void {
135
+ this._reach(MOUNTED, false)
136
+ super._freed()
137
+ }
138
+ /** @internal */
139
+ _emitBack(): boolean {
140
+ if (!this._backButtonCallback) return false
141
+ this._backButtonCallback()
142
+ return true
143
+ }
144
+
145
+ call(method: string, ...args: any[]): Promise<any> { return this._channelSend(method, args) }
104
146
 
105
- call(method: string, ...args: any[]): Promise<any> {
147
+ /** @internal One call over the channel (runtime/channel.ts). */
148
+ _channelSend(method: string, args: any[]): Promise<any> {
106
149
  return _channelCall(
107
- typeof _creatorUI !== "undefined" ? _creatorUI : undefined, "viewCall",
108
- `NativeView("${this.viewName}") is not supported on this host (requires _creatorUI.viewCall)`,
150
+ typeof _creatorTree !== "undefined" ? _creatorTree : undefined, "viewCall",
151
+ `NativeView("${this.viewName}") is not supported on this host (requires _creatorTree.viewCall)`,
109
152
  this._viewId, method, args)
110
153
  }
111
154
 
@@ -117,9 +160,9 @@ export class NativeViewElement extends Element<"native"> implements Presentable
117
160
  _channelOff(this.nvl, event, callback)
118
161
  return this
119
162
  }
120
- /** @internal host entry: deliver a native event (payload as a JSON string, or omitted). */
121
- _emitViewEvent(event: string, dataJson?: string): void {
122
- _channelEmit(this.nvl, event, dataJson)
163
+ /** @internal host entry: deliver a native event (its payload, or none). */
164
+ _emitViewEvent(event: string, data?: unknown): void {
165
+ _channelEmit(this.nvl, event, data)
123
166
  }
124
167
  }
125
168
 
@@ -133,10 +176,14 @@ export const NativeView: {
133
176
  (name: string, params?: object): NativeView
134
177
  /** Whether this host registered a `registerView` factory under `name`. */
135
178
  isSupported(name: string): boolean
179
+ /** The contract version of the host's view `name`: 0 when it has none, 1 for one written by hand. */
180
+ version(name: string): number
136
181
  } = /*#__PURE__*/ (() => {
137
182
  const factory = (name: string, params: object = {}): NativeView =>
138
183
  new NativeViewElement(name, params) as unknown as NativeView
139
184
  factory.isSupported = (name: string): boolean =>
140
- typeof _creatorUI !== "undefined" && typeof _creatorUI.isViewSupported === "function" && _creatorUI.isViewSupported(name)
185
+ typeof _creatorTree !== "undefined" && _creatorTree.isViewSupported(name)
186
+ factory.version = (name: string): number =>
187
+ typeof _creatorTree !== "undefined" ? _creatorTree.viewVersion(name) : 0
141
188
  return factory
142
189
  })()
package/src/ui/UI.ts CHANGED
@@ -1,5 +1,5 @@
1
- // Renderer-side contract + per-renderer status (DOM / canvas-ui / desktop) for every component
2
- // below is tracked in packages/canvas-ui/COMPONENTS.md.
1
+ // The UI surface: every element the SDK exports. The host side of each is the runtime's tree
2
+ // (docs/tree.md); the renderers implement HostUI over node ids.
3
3
  export { UIButton, type UIButtonStyle } from './UIButton'
4
4
  export { UIScreen, type UIScreenStyle } from './UIScreen'
5
5
  export { UISpacer, type UISpacerStyle } from './UISpacer'
@@ -25,7 +25,7 @@ export { Router } from './router'
25
25
  export { NativeView, type UINativeViewStyle } from './NativeView'
26
26
  // Presentable is both the interface and its runtime companion (Presentable.current).
27
27
  export { Presentable } from './presentable'
28
- export type { PresentOptions, Transition, TransitionName, TransitionSpec, TransitionTransform } from './presentable'
28
+ export type { DismissOptions, PresentOptions, Transition, TransitionName, TransitionPose, TransitionSpec } from './presentable'
29
29
 
30
30
  // Internal: memoized map for reactive children — the chisel `reactiveUi` pass rewrites
31
31
  // `list.map(fn)` inside children bindings to `__uiMap(list, fn, slot)` calls.
@@ -1,6 +1,7 @@
1
1
  import { ModalElement, type UIModalStyle } from "./UIModal"
2
2
  import type { UIWidget } from "./UIWidget"
3
3
  import { buildUI, type ChildrenFn, type UIChildArg, type UINodeChild } from "./UINode"
4
+ import { TREE_FLAG_DETENT } from "./tree"
4
5
 
5
6
  export type UIBottomSheetStyle = UIModalStyle
6
7
 
@@ -18,11 +19,11 @@ const SHEET_ANIM_MS = 250
18
19
  * modal machinery.
19
20
  *
20
21
  * ```ts
21
- * const actions = UIBottomSheet({ bgColor: "#1c1c1e", borderRadius: 16 }, [rows]) // content-sized
22
+ * const actions = UIBottomSheet(rows).style({ bgColor: "#1c1c1e", borderRadius: 16 }) // content-sized
22
23
  *
23
- * const mapSheet = UIBottomSheet({ bgColor: "#1c1c1e", borderRadius: 16 }, [results])
24
+ * const mapSheet = UIBottomSheet(results).style({ bgColor: "#1c1c1e", borderRadius: 16 })
24
25
  * .detents([0.3, 0.6, 1])
25
- * .onDetentChange(i => map.setPadding(...))
26
+ * .onDetent(i => map.setPadding(...))
26
27
  * ```
27
28
  */
28
29
  export interface UIBottomSheet extends UIWidget {
@@ -30,12 +31,12 @@ export interface UIBottomSheet extends UIWidget {
30
31
  * the last (highest) detent; set this before `show()`. Without a `detents()` call the sheet
31
32
  * is CONTENT-SIZED instead — one position, as tall as its children (up to the screen). */
32
33
  detents(list: number[]): this,
33
- /** Snap to a detent (animated). Clamped to the detents range; fires `onDetentChange`. */
34
+ /** Snap to a detent (animated). Clamped to the detents range; fires `onDetent`. */
34
35
  setDetent(index: number): void,
35
36
  /** Current detent index. */
36
37
  readonly detent: number,
37
38
  /** Fires on every settle: a finger snap on native hosts, or a `setDetent()` call. */
38
- onDetentChange(callback: (index: number) => void): this,
39
+ onDetent(callback: (index: number) => void): this,
39
40
 
40
41
  /** Mount and slide in to the current detent. No-op while already open. */
41
42
  show(): void,
@@ -56,10 +57,6 @@ export interface UIBottomSheet extends UIWidget {
56
57
  export class BottomSheetElement extends ModalElement {
57
58
  _detents: number[] = [1]
58
59
  _detent = 0
59
- /** Host-facing (the `sl`/`otl` contract): hosts read `dtl` at mount and call it with the
60
- * settled detent index for GESTURE settles only — programmatic `setDetent` fires JS-side.
61
- * `-1` = dragged below the lowest detent while dismissible. */
62
- readonly dtl: any[] = []
63
60
  readonly _detentListeners: ((index: number) => void)[] = []
64
61
 
65
62
  constructor(style: UIBottomSheetStyle, children: UINodeChild[] | ChildrenFn) {
@@ -70,11 +67,15 @@ export class BottomSheetElement extends ModalElement {
70
67
  // The inherited show/hide pose animates ONLY the scrim: sheet geometry rides the
71
68
  // sheetDetent style key, never the pose system.
72
69
  this._transition = { duration: SHEET_ANIM_MS }
73
- this.dtl.push((index: number) => {
74
- if (index < 0) { this.hide(); return }
75
- this._detent = index
76
- for (const callback of this._detentListeners) callback(index)
77
- })
70
+ this._addFlags(TREE_FLAG_DETENT)
71
+ }
72
+
73
+ /** @internal The host's GESTURE settles only (programmatic `setDetent` fires JS-side); `-1` =
74
+ * dragged below the lowest detent while dismissible. */
75
+ _emitDetent(index: number): void {
76
+ if (index < 0) { this.hide(); return }
77
+ this._detent = index
78
+ for (const callback of this._detentListeners) callback(index)
78
79
  }
79
80
 
80
81
  detents(list: number[]): this {
@@ -101,7 +102,7 @@ export class BottomSheetElement extends ModalElement {
101
102
  for (const callback of this._detentListeners) callback(clamped)
102
103
  }
103
104
 
104
- onDetentChange(callback: (index: number) => void): this {
105
+ onDetent(callback: (index: number) => void): this {
105
106
  this._detentListeners.push(callback)
106
107
  return this
107
108
  }
@@ -132,8 +133,6 @@ const makeBottomSheet = (style: any, children: any) => new BottomSheetElement(st
132
133
  * `.detents([...])` for snap positions. Same argument forms as `UIColumn`. */
133
134
  export function UIBottomSheet(...children: UIChildArg[]): UIBottomSheet;
134
135
  export function UIBottomSheet(children: UINodeChild[] | ChildrenFn): UIBottomSheet;
135
- /** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
136
- export function UIBottomSheet(style: UIBottomSheetStyle, children?: UINodeChild[] | ChildrenFn): UIBottomSheet;
137
136
  export function UIBottomSheet(...args: any[]): UIBottomSheet {
138
137
  return buildUI(args, null, makeBottomSheet)
139
138
  }
@@ -1,12 +1,13 @@
1
- import { ClickEvent, TouchStartEvent, LongPressEvent } from "../runtime/touch"
2
- import type { ClickEvent as ClickEventType, TouchStartEvent as TouchStartEventType, LongPressEvent as LongPressEventType } from "../runtime/touch"
3
- import { type ElementStyle, type ContainerStyle, ContainerElement, buildUI, type DrawableStyle, type UIChildArg, type UIContainerBase, type UINodeChild, type ChildrenFn, type BaseStyle, type Color, type LayerEasing } from "./UINode"
1
+ import { ClickEvent, TouchStartEvent, LongPressEvent, MouseEnterEvent } from "../runtime/touch"
2
+ import type { ClickEvent as ClickEventType, TouchStartEvent as TouchStartEventType, LongPressEvent as LongPressEventType, MouseEnterEvent as MouseEnterEventType } from "../runtime/touch"
3
+ import { type ElementStyle, type ContainerStyle, ContainerElement, buildUI, type DrawableStyle, type UIChildArg, type UIContainerBase, type UINodeChild, type ChildrenFn, type BaseStyle, type Color } from "./UINode"
4
+ import { TREE_FLAG_CLICK, TREE_FLAG_HOVER, TREE_FLAG_INTERACTIVE, TREE_FLAG_LONG_PRESS, tree } from "./tree"
4
5
 
5
6
  export type UIButtonStyle = ElementStyle & DrawableStyle & ContainerStyle & { rippleColor?: Color | "default" }
6
7
 
7
- /** A tappable container — row + centered children by default. Press feedback via the `onPressed`
8
- * style block, the `$pressed` class, or `rippleColor`. */
9
- export interface UIButton extends UIContainerBase<UIButtonStyle & { onPressed?: UIButtonStyle & { duration?: number, delay?: number, easing?: LayerEasing } }, DrawableStyle & BaseStyle> {
8
+ /** A tappable container — row + centered children by default. Press feedback via the `$pressed`
9
+ * class block or `rippleColor`. */
10
+ export interface UIButton extends UIContainerBase<UIButtonStyle, DrawableStyle & BaseStyle> {
10
11
  readonly type: "button",
11
12
 
12
13
  /** Fired on a completed tap/click. */
@@ -17,8 +18,14 @@ export interface UIButton extends UIContainerBase<UIButtonStyle & { onPressed?:
17
18
  * `ev.track(...)` the rest of the gesture (like `onTouchStart`), so a hold can flow straight into
18
19
  * dragging the element. A handled long-press suppresses the click that would follow the release. */
19
20
  onLongPress(callback: (ev: LongPressEventType<UIButton>) => void): this,
21
+ /** Fired when a mouse cursor (no button) enters the button — desktop / web; touch never fires it.
22
+ * There is no leave event: `ev.track({ onMove, onEnd, onCancel })` follows the hover to its
23
+ * end. The visual state is the reserved `$hovered` class (cascades like `$pressed`). */
24
+ onMouseEnter(callback: (ev: MouseEnterEventType<UIButton>) => void): this,
20
25
  /** Is a finger currently down on the button? */
21
- isPressed(): boolean,
26
+ readonly isPressed: boolean,
27
+ /** Is a mouse cursor currently over the button (the topmost interactive node under it)? */
28
+ readonly isHovered: boolean,
22
29
  }
23
30
 
24
31
 
@@ -27,11 +34,19 @@ class ButtonElement extends ContainerElement<"button"> {
27
34
  readonly clickListeners: any[] = []
28
35
  readonly touchStartListeners: any[] = []
29
36
  readonly longPressListeners: any[] = []
37
+ readonly mouseEnterListeners: any[] = []
38
+
39
+ constructor(type: "button", style: any, children: any) {
40
+ super(type, style, children)
41
+ this._addFlags(TREE_FLAG_INTERACTIVE) // a button is a touch target from the start (press feedback)
42
+ }
30
43
 
31
44
  onClick (callback: any): this {
32
45
  this.clickListeners.push(callback)
46
+ this._addFlags(TREE_FLAG_CLICK)
33
47
  return this
34
48
  }
49
+ /** @internal */
35
50
  _emitClick(pointerId: number, clientX: number, clientY: number) {
36
51
  const touchEvent = new ClickEvent(clientX, clientY, pointerId)
37
52
  touchEvent.target = this
@@ -45,6 +60,7 @@ class ButtonElement extends ContainerElement<"button"> {
45
60
  this.touchStartListeners.push(callback)
46
61
  return this
47
62
  }
63
+ /** @internal */
48
64
  _emitTouchStart(pointerId: number, clientX: number, clientY: number): any {
49
65
  const touchEvent = new TouchStartEvent(clientX, clientY, pointerId)
50
66
  touchEvent.target = this
@@ -59,12 +75,13 @@ class ButtonElement extends ContainerElement<"button"> {
59
75
 
60
76
  onLongPress (callback: any): this {
61
77
  this.longPressListeners.push(callback)
78
+ this._addFlags(TREE_FLAG_LONG_PRESS)
62
79
  return this
63
80
  }
64
- // Fired by the host when the finger is held past the long-press threshold. Returns null when
65
- // nothing is listening — the host then lets the trailing click through as usual. Otherwise it
66
- // returns an object whose presence tells the host to swallow that click, carrying the optional
67
- // drag handler a listener registered via `ev.track(...)` for the host to drive the gesture.
81
+ /** @internal Fired by the host when the finger is held past the long-press threshold. Returns
82
+ * null when nothing is listening — the trailing click then goes through as usual. Otherwise the
83
+ * wrapper whose presence swallows that click, carrying the optional drag handler a listener
84
+ * registered via `ev.track(...)`. */
68
85
  _emitLongPress(pointerId: number, clientX: number, clientY: number): any {
69
86
  if (this.longPressListeners.length === 0) return null
70
87
  const longPressEvent = new LongPressEvent(clientX, clientY, pointerId)
@@ -75,9 +92,32 @@ class ButtonElement extends ContainerElement<"button"> {
75
92
  return { track: (longPressEvent as any)._trackHandler ?? null }
76
93
  }
77
94
 
78
- isPressed(): boolean {
79
- if (!this._id) return false
80
- return _creatorUI.isButtonPressed(this._id)
95
+ get isPressed(): boolean {
96
+ if (!this._h.id) return false
97
+ return tree().isButtonPressed(this._h.id)
98
+ }
99
+
100
+ onMouseEnter (callback: any): this {
101
+ this.mouseEnterListeners.push(callback)
102
+ this._addFlags(TREE_FLAG_HOVER) // the runtime crosses into JS for hover only on flagged nodes
103
+ return this
104
+ }
105
+ /** @internal Fired by the runtime when the cursor enters (TREE_EVENT_HOVER_ENTER). Returns the
106
+ * hover track handler a listener registered via `ev.track(...)`, or null — the emitter keeps it
107
+ * in its one hover slot and answers whether moves are wanted. */
108
+ _emitMouseEnter(clientX: number, clientY: number): any {
109
+ const ev = new MouseEnterEvent(clientX, clientY)
110
+ ev.target = this
111
+ for (let callback of this.mouseEnterListeners) {
112
+ callback(ev)
113
+ if ((ev as any)._trackHandler) return (ev as any)._trackHandler
114
+ }
115
+ return null
116
+ }
117
+
118
+ get isHovered(): boolean {
119
+ if (!this._h.id) return false
120
+ return tree().isHovered(this._h.id)
81
121
  }
82
122
  }
83
123
 
@@ -94,8 +134,6 @@ export const __UIButton = (children: UINodeChild[] | ChildrenFn): UIButton => ma
94
134
  * `UIColumn`; attach behavior with `.onClick(...)`. */
95
135
  export function UIButton(...children: UIChildArg[]): UIButton;
96
136
  export function UIButton(children: UINodeChild[] | ChildrenFn): UIButton;
97
- /** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
98
- export function UIButton(style: UIButtonStyle, children?: UINodeChild[] | ChildrenFn): UIButton;
99
137
  export function UIButton(...args: any[]): UIButton {
100
138
  return buildUI(args, BUTTON_DEFAULTS, makeButton)
101
139
  }
@@ -19,8 +19,6 @@ export const __UIBox = (children: UINodeChild[] | ChildrenFn): UIBox => makeBox(
19
19
  * entries are skipped; a lone function makes the children reactive. */
20
20
  export function UIColumn(...children: UIChildArg[]): UIColumn
21
21
  export function UIColumn(children: UINodeChild[] | ChildrenFn): UIColumn
22
- /** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
23
- export function UIColumn(style: UIContainerStyle, children?: UINodeChild[] | ChildrenFn): UIColumn
24
22
  export function UIColumn(...args: any[]): UIColumn {
25
23
  return buildUI(args, null, makeColumn)
26
24
  }
@@ -35,8 +33,6 @@ const makeRow = (style: any, children: any) => new ContainerElement("row", style
35
33
  /** Create a horizontal flex container (same argument forms as `UIColumn`). */
36
34
  export function UIRow(...children: UIChildArg[]): UIRow
37
35
  export function UIRow(children: UINodeChild[] | ChildrenFn): UIRow
38
- /** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
39
- export function UIRow(style: UIContainerStyle, children?: UINodeChild[] | ChildrenFn): UIRow
40
36
  export function UIRow(...args: any[]): UIRow {
41
37
  return buildUI(args, ROW_DEFAULTS, makeRow)
42
38
  }
@@ -53,8 +49,6 @@ const makeBox = (style: any, children: any) => new ContainerElement("box", style
53
49
  * avatars, empty states). Same argument forms as `UIColumn`. */
54
50
  export function UIBox(...children: UIChildArg[]): UIBox
55
51
  export function UIBox(children: UINodeChild[] | ChildrenFn): UIBox
56
- /** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
57
- export function UIBox(style: UIContainerStyle, children?: UINodeChild[] | ChildrenFn): UIBox
58
52
  export function UIBox(...args: any[]): UIBox {
59
53
  return buildUI(args, BOX_DEFAULTS, makeBox)
60
54
  }
package/src/ui/UIImage.ts CHANGED
@@ -4,11 +4,12 @@ import type { File } from "../runtime/fetch"
4
4
  import { Canvas, isCanvas } from "../canvas/Canvas"
5
5
  import type { Scene2D } from "../g2/Scene2D"
6
6
  import { Element, type Color, type ElementStyle, type UIElementBase } from "./UINode"
7
+ import { wireColor } from "./styleColor"
8
+ import { tree } from "./tree"
7
9
 
8
10
  export type UIImageStyle = ElementStyle & { borderRadius?: number, objectFit?: "cover" | "contain" | "fill", tintColor?: Color }
9
11
 
10
- /** A baked Canvas surface as an image source. The UI backend resolves it to a UI texture — see
11
- * docs/canvas-contract.md ("Pending"). */
12
+ /** A baked Canvas surface as an image source. The UI backend resolves it to a UI texture. */
12
13
  export type CanvasImageSource = { canvasSurface: number }
13
14
 
14
15
  /** A live 2D scene as an image source: the host draws the scene into the node's box every frame
@@ -50,48 +51,51 @@ export interface UIImage extends UIElementBase<UIImageStyle> {
50
51
  }
51
52
 
52
53
  class ImageElement extends Element<"image"> {
53
- private _src: ResolvedImageSource
54
- // Current source sub-rect; undefined until the first setSourceRect. Kept on the node (not
55
- // just sent as a command) so a crop set while detached (_id == 0) is reapplied at mount —
56
- // the viewer reads it when it creates the element.
57
- private _sourceRect?: [number, number, number, number]
54
+ private _src!: ResolvedImageSource
58
55
  constructor(style: any, src: ImageSource) {
59
56
  super("image", style)
60
- this._src = toSrc(src)
57
+ this.src = src
61
58
  }
62
59
 
63
60
  get src(): ResolvedImageSource {
64
61
  return this._src
65
62
  }
66
63
  set src(value: ImageSource) {
67
- this._src = toSrc(value)
68
- if (this._id !== 0) {
69
- _creatorUI.updateNode(this._id, "src", this._src)
64
+ const next = toSrc(value)
65
+ // A source tint that is not a colour throws BEFORE anything is applied, like a style colour.
66
+ const n: any = next
67
+ const tint = n && typeof n === "object" && "svg" in n && n.tintColor != null ? wireColor("tintColor", n.tintColor) as string : null
68
+ this._src = next
69
+ if (this._h.id === 0) return
70
+ // The wire spellings of a source (the host resolves each prefix): a url as is, "id:<bufferId>"
71
+ // for a fetch / asset buffer, "svg:<source>" (+ "srcTintColor", '#rrggbbaa' or a var(), AFTER
72
+ // the src: a src write resets the source tint), "canvas:<surfaceId>", "scene2d:<sceneId>".
73
+ const s: any = this._src
74
+ if (typeof s === "string") {
75
+ tree().setString(this._h.id, "src", s)
76
+ } else if (s && typeof s === "object") {
77
+ if ("svg" in s) {
78
+ tree().setString(this._h.id, "src", "svg:" + s.svg)
79
+ if (tint != null) tree().setString(this._h.id, "srcTintColor", tint)
80
+ } else if ("canvasSurface" in s) {
81
+ tree().setString(this._h.id, "src", "canvas:" + s.canvasSurface)
82
+ } else if ("scene2d" in s) {
83
+ tree().setString(this._h.id, "src", "scene2d:" + s.scene2d)
84
+ } else if ("_h" in s) {
85
+ tree().setString(this._h.id, "src", "id:" + s._h.id)
86
+ }
70
87
  }
71
88
  }
72
89
 
73
90
  setSourceRect(x: number, y: number, w: number, h: number): this {
74
- // Stash on the node so a crop set before mount is reapplied when the element is created.
75
- this._sourceRect = [x, y, w, h]
76
- // Live mutation: fast channel (not _style → no PX_VALUE density scaling: these are texture
77
- // pixels, a different coordinate space). On a detached node there is nothing to mutate yet —
78
- // the stash above carries it to mount. The viewer decides relayout vs draw-only by comparing
79
- // w/h against the rect it has stashed on the element.
80
- if (this._id !== 0) {
81
- _creatorUI.setSourceRect(this._id, x, y, w, h)
82
- }
91
+ // Texture pixels (not a style → no density scaling); the host decides relayout vs draw-only by
92
+ // comparing w/h against the rect it has.
93
+ if (this._h.id !== 0) tree().setString(this._h.id, "sourceRect", `${x} ${y} ${w} ${h}`)
83
94
  return this
84
95
  }
85
96
  }
86
97
 
87
98
  /** Create an image element from a source (see {@link ImageSource}). */
88
- export function UIImage(src: ImageSource): UIImage
89
- /** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
90
- export function UIImage(style: UIImageStyle, src: ImageSource): UIImage
91
- export function UIImage(...args: [ImageSource] | [UIImageStyle, ImageSource]): UIImage {
92
- if (args.length === 1) {
93
- return new ImageElement({}, args[0])
94
- } else {
95
- return new ImageElement(args[0], args[1])
96
- }
97
- }
99
+ export function UIImage(src: ImageSource): UIImage {
100
+ return new ImageElement({}, src)
101
+ }