lecodes-sdk 0.19.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 (367) hide show
  1. package/README.md +114 -0
  2. package/dist/global.d.ts +503 -0
  3. package/dist/host.d.ts +61 -0
  4. package/dist/inject.js +4730 -0
  5. package/dist/types/animate/animate.d.ts +20 -0
  6. package/dist/types/animate/bezier.d.ts +2 -0
  7. package/dist/types/animate/easings.d.ts +21 -0
  8. package/dist/types/canvas/Canvas.d.ts +168 -0
  9. package/dist/types/core/Aspect.d.ts +171 -0
  10. package/dist/types/core/InspectorUI.d.ts +88 -0
  11. package/dist/types/core/StateMachine.d.ts +81 -0
  12. package/dist/types/core/color.d.ts +13 -0
  13. package/dist/types/core/compWrite.d.ts +11 -0
  14. package/dist/types/core/events.d.ts +11 -0
  15. package/dist/types/core/fields.d.ts +58 -0
  16. package/dist/types/core/registry.d.ts +7 -0
  17. package/dist/types/core/signals.d.ts +29 -0
  18. package/dist/types/core/time.d.ts +31 -0
  19. package/dist/types/g2/Camera2D.d.ts +19 -0
  20. package/dist/types/g2/CharacterController2D.d.ts +134 -0
  21. package/dist/types/g2/Node2D.d.ts +91 -0
  22. package/dist/types/g2/OneWay2D.d.ts +24 -0
  23. package/dist/types/g2/Physics2D.d.ts +147 -0
  24. package/dist/types/g2/Scene2D.d.ts +72 -0
  25. package/dist/types/g2/Shape2D.d.ts +58 -0
  26. package/dist/types/g2/Sprite.d.ts +51 -0
  27. package/dist/types/g2/SpriteAnimation.d.ts +50 -0
  28. package/dist/types/g2/SpriteSheet.d.ts +69 -0
  29. package/dist/types/g2/Texture2D.d.ts +16 -0
  30. package/dist/types/g2/Tilemap.d.ts +20 -0
  31. package/dist/types/g2/Tileset.d.ts +32 -0
  32. package/dist/types/g2/Trigger2D.d.ts +20 -0
  33. package/dist/types/g2/autotile.d.ts +133 -0
  34. package/dist/types/g2/cells.d.ts +14 -0
  35. package/dist/types/g2/defineScene2d.d.ts +141 -0
  36. package/dist/types/g2/groups2d.d.ts +16 -0
  37. package/dist/types/g2/loop.d.ts +4 -0
  38. package/dist/types/g2/scenarios2d.d.ts +30 -0
  39. package/dist/types/g2/touch.d.ts +2 -0
  40. package/dist/types/gl/Camera.d.ts +79 -0
  41. package/dist/types/gl/CameraPlace.d.ts +26 -0
  42. package/dist/types/gl/CharacterController.d.ts +98 -0
  43. package/dist/types/gl/Gearbox.d.ts +86 -0
  44. package/dist/types/gl/Geometry.d.ts +39 -0
  45. package/dist/types/gl/IK.d.ts +53 -0
  46. package/dist/types/gl/InstancedMesh.d.ts +46 -0
  47. package/dist/types/gl/Light.d.ts +73 -0
  48. package/dist/types/gl/Lightmap.d.ts +85 -0
  49. package/dist/types/gl/Locomotion.d.ts +279 -0
  50. package/dist/types/gl/Material.d.ts +100 -0
  51. package/dist/types/gl/Mesh.d.ts +33 -0
  52. package/dist/types/gl/Model.d.ts +53 -0
  53. package/dist/types/gl/NavAgent.d.ts +99 -0
  54. package/dist/types/gl/NavMesh.d.ts +169 -0
  55. package/dist/types/gl/Node.d.ts +99 -0
  56. package/dist/types/gl/Noise.d.ts +20 -0
  57. package/dist/types/gl/Particles.d.ts +277 -0
  58. package/dist/types/gl/Physics.d.ts +91 -0
  59. package/dist/types/gl/Plane.d.ts +19 -0
  60. package/dist/types/gl/Ragdoll.d.ts +86 -0
  61. package/dist/types/gl/Ray.d.ts +8 -0
  62. package/dist/types/gl/Scene.d.ts +221 -0
  63. package/dist/types/gl/Shape.d.ts +122 -0
  64. package/dist/types/gl/Terrain.d.ts +280 -0
  65. package/dist/types/gl/Texture.d.ts +27 -0
  66. package/dist/types/gl/Trigger.d.ts +10 -0
  67. package/dist/types/gl/Vehicle.d.ts +191 -0
  68. package/dist/types/gl/Wheel.d.ts +95 -0
  69. package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
  70. package/dist/types/gl/animation/Animator.d.ts +219 -0
  71. package/dist/types/gl/animation/Layer.d.ts +31 -0
  72. package/dist/types/gl/animation/Loop.d.ts +17 -0
  73. package/dist/types/gl/animation/Playback.d.ts +36 -0
  74. package/dist/types/gl/animation/core.d.ts +367 -0
  75. package/dist/types/gl/controls.d.ts +19 -0
  76. package/dist/types/gl/physicsEvents.d.ts +1 -0
  77. package/dist/types/gl/scenarios.d.ts +98 -0
  78. package/dist/types/gl/state.d.ts +5 -0
  79. package/dist/types/gl/terrainMesh.d.ts +46 -0
  80. package/dist/types/gl/touch.d.ts +2 -0
  81. package/dist/types/inject.d.ts +123 -0
  82. package/dist/types/math/Mathf.d.ts +39 -0
  83. package/dist/types/math/mat4.d.ts +66 -0
  84. package/dist/types/math/quat.d.ts +53 -0
  85. package/dist/types/math/vec.d.ts +129 -0
  86. package/dist/types/net/codec.d.ts +25 -0
  87. package/dist/types/net/core.d.ts +106 -0
  88. package/dist/types/net/index.d.ts +80 -0
  89. package/dist/types/net/replication.d.ts +101 -0
  90. package/dist/types/plugins/camera.d.ts +25 -0
  91. package/dist/types/plugins/geolocation.d.ts +39 -0
  92. package/dist/types/plugins/oauth.d.ts +25 -0
  93. package/dist/types/plugins/permission.d.ts +1 -0
  94. package/dist/types/plugins/push.d.ts +46 -0
  95. package/dist/types/plugins/qr.d.ts +21 -0
  96. package/dist/types/plugins/service.d.ts +33 -0
  97. package/dist/types/runtime/app.d.ts +67 -0
  98. package/dist/types/runtime/appEvents.d.ts +7 -0
  99. package/dist/types/runtime/channel.d.ts +1 -0
  100. package/dist/types/runtime/clipboard.d.ts +7 -0
  101. package/dist/types/runtime/datetime.d.ts +80 -0
  102. package/dist/types/runtime/device.d.ts +126 -0
  103. package/dist/types/runtime/fetch.d.ts +31 -0
  104. package/dist/types/runtime/files.d.ts +33 -0
  105. package/dist/types/runtime/input.d.ts +121 -0
  106. package/dist/types/runtime/media.d.ts +46 -0
  107. package/dist/types/runtime/misc.d.ts +11 -0
  108. package/dist/types/runtime/net.d.ts +15 -0
  109. package/dist/types/runtime/rpc.d.ts +44 -0
  110. package/dist/types/runtime/service.d.ts +1 -0
  111. package/dist/types/runtime/share.d.ts +2 -0
  112. package/dist/types/runtime/storage.d.ts +5 -0
  113. package/dist/types/runtime/touch.d.ts +56 -0
  114. package/dist/types/scene/defineScene.d.ts +296 -0
  115. package/dist/types/scene/editorPlugins.d.ts +82 -0
  116. package/dist/types/scene/gizmos.d.ts +56 -0
  117. package/dist/types/scene/grammar.d.ts +56 -0
  118. package/dist/types/scene/level.d.ts +98 -0
  119. package/dist/types/scene/material.d.ts +76 -0
  120. package/dist/types/server/auth/appConfig.d.ts +16 -0
  121. package/dist/types/server/auth/global.d.ts +56 -0
  122. package/dist/types/server/auth/models.d.ts +164 -0
  123. package/dist/types/server/auth/types.d.ts +56 -0
  124. package/dist/types/server/channel.d.ts +28 -0
  125. package/dist/types/server/context.d.ts +27 -0
  126. package/dist/types/server/db/defineDb.d.ts +36 -0
  127. package/dist/types/server/db/fields.d.ts +146 -0
  128. package/dist/types/server/db/index.d.ts +6 -0
  129. package/dist/types/server/db/marci/query.d.ts +326 -0
  130. package/dist/types/server/db/types.d.ts +163 -0
  131. package/dist/types/server/errors.d.ts +8 -0
  132. package/dist/types/server/inject.d.ts +5 -0
  133. package/dist/types/ui/NativeView.d.ts +38 -0
  134. package/dist/types/ui/UI.d.ts +27 -0
  135. package/dist/types/ui/UIBottomSheet.d.ts +71 -0
  136. package/dist/types/ui/UIButton.d.ts +30 -0
  137. package/dist/types/ui/UIContainer.d.ts +33 -0
  138. package/dist/types/ui/UIImage.d.ts +35 -0
  139. package/dist/types/ui/UIInput.d.ts +110 -0
  140. package/dist/types/ui/UIModal.d.ts +70 -0
  141. package/dist/types/ui/UINode.d.ts +363 -0
  142. package/dist/types/ui/UIPager.d.ts +123 -0
  143. package/dist/types/ui/UIPopover.d.ts +39 -0
  144. package/dist/types/ui/UIScreen.d.ts +57 -0
  145. package/dist/types/ui/UIScrollable.d.ts +40 -0
  146. package/dist/types/ui/UISpacer.d.ts +9 -0
  147. package/dist/types/ui/UITabs.d.ts +96 -0
  148. package/dist/types/ui/UIText.d.ts +19 -0
  149. package/dist/types/ui/UIVideo.d.ts +28 -0
  150. package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
  151. package/dist/types/ui/UIWidget.d.ts +65 -0
  152. package/dist/types/ui/fonts.d.ts +9 -0
  153. package/dist/types/ui/presentable.d.ts +68 -0
  154. package/dist/types/ui/router.d.ts +40 -0
  155. package/dist/types/ui/theme.d.ts +26 -0
  156. package/dist/types.json +1 -0
  157. package/package.json +46 -0
  158. package/prompts/2d.md +395 -0
  159. package/prompts/3d-scene-files.md +113 -0
  160. package/prompts/3d-scene.md +119 -0
  161. package/prompts/3d.md +303 -0
  162. package/prompts/README.md +142 -0
  163. package/prompts/ar.md +109 -0
  164. package/prompts/canvas.md +27 -0
  165. package/prompts/compose.ts +46 -0
  166. package/prompts/concept.md +49 -0
  167. package/prompts/core-design.md +75 -0
  168. package/prompts/core.md +264 -0
  169. package/prompts/design.md +218 -0
  170. package/prompts/dist/2d-game.md +1530 -0
  171. package/prompts/dist/3d-app.md +1672 -0
  172. package/prompts/dist/ar-app.md +1548 -0
  173. package/prompts/dist/concept.md +49 -0
  174. package/prompts/dist/design.md +498 -0
  175. package/prompts/dist/ui-app.md +1134 -0
  176. package/prompts/index.md +37 -0
  177. package/prompts/intake-prompt.md +33 -0
  178. package/prompts/namer-prompt.md +12 -0
  179. package/prompts/router-prompt.md +50 -0
  180. package/prompts/select.ts +111 -0
  181. package/prompts/ui-design.md +203 -0
  182. package/prompts/ui.md +803 -0
  183. package/src/animate/animate.ts +238 -0
  184. package/src/animate/bezier.ts +138 -0
  185. package/src/animate/easings.ts +126 -0
  186. package/src/bridges.d.ts +1345 -0
  187. package/src/canvas/Canvas.ts +305 -0
  188. package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
  189. package/src/compile/__tests__/assetMacro.test.ts +74 -0
  190. package/src/compile/__tests__/assetName.test.ts +55 -0
  191. package/src/compile/__tests__/compile.test.ts +287 -0
  192. package/src/compile/__tests__/detectEntry.test.ts +132 -0
  193. package/src/compile/__tests__/fontMacro.test.ts +199 -0
  194. package/src/compile/aspectMacro.ts +86 -0
  195. package/src/compile/assetIconMacro.ts +384 -0
  196. package/src/compile/assetMacro.ts +146 -0
  197. package/src/compile/assetName.ts +50 -0
  198. package/src/compile/bundler.ts +287 -0
  199. package/src/compile/compileProject.ts +127 -0
  200. package/src/compile/detectEntry.ts +128 -0
  201. package/src/compile/fontMacro.ts +459 -0
  202. package/src/compile/fontRegistry.ts +78 -0
  203. package/src/compile/header.ts +67 -0
  204. package/src/compile/index.ts +100 -0
  205. package/src/compile/libraryImports.ts +52 -0
  206. package/src/compile/liteMaterial.ts +247 -0
  207. package/src/compile/sceneEditor.ts +88 -0
  208. package/src/compile/serverSplit.ts +233 -0
  209. package/src/compile/serverTypes.ts +227 -0
  210. package/src/compile/sfnt.ts +98 -0
  211. package/src/compile/shaderSchema.ts +202 -0
  212. package/src/compile/shaderTargets.ts +77 -0
  213. package/src/compile/sourcemap.ts +25 -0
  214. package/src/core/Aspect.ts +568 -0
  215. package/src/core/InspectorUI.ts +212 -0
  216. package/src/core/StateMachine.ts +308 -0
  217. package/src/core/__tests__/stateMachine.test.ts +132 -0
  218. package/src/core/color.ts +66 -0
  219. package/src/core/compWrite.ts +42 -0
  220. package/src/core/events.ts +38 -0
  221. package/src/core/fields.ts +120 -0
  222. package/src/core/registry.ts +23 -0
  223. package/src/core/signals.ts +277 -0
  224. package/src/core/time.ts +84 -0
  225. package/src/g2/Camera2D.ts +40 -0
  226. package/src/g2/CharacterController2D.ts +276 -0
  227. package/src/g2/Node2D.ts +267 -0
  228. package/src/g2/OneWay2D.ts +66 -0
  229. package/src/g2/Physics2D.ts +346 -0
  230. package/src/g2/Scene2D.ts +209 -0
  231. package/src/g2/Shape2D.ts +259 -0
  232. package/src/g2/Sprite.ts +89 -0
  233. package/src/g2/SpriteAnimation.ts +171 -0
  234. package/src/g2/SpriteSheet.ts +166 -0
  235. package/src/g2/Texture2D.ts +47 -0
  236. package/src/g2/Tilemap.ts +41 -0
  237. package/src/g2/Tileset.ts +71 -0
  238. package/src/g2/Trigger2D.ts +77 -0
  239. package/src/g2/autotile.ts +433 -0
  240. package/src/g2/cells.ts +91 -0
  241. package/src/g2/defineScene2d.ts +381 -0
  242. package/src/g2/groups2d.ts +106 -0
  243. package/src/g2/loop.ts +50 -0
  244. package/src/g2/scenarios2d.ts +69 -0
  245. package/src/g2/touch.ts +83 -0
  246. package/src/gl/Camera.ts +160 -0
  247. package/src/gl/CameraPlace.ts +52 -0
  248. package/src/gl/CharacterController.ts +238 -0
  249. package/src/gl/Gearbox.ts +212 -0
  250. package/src/gl/Geometry.ts +279 -0
  251. package/src/gl/IK.ts +193 -0
  252. package/src/gl/InstancedMesh.ts +132 -0
  253. package/src/gl/Light.ts +135 -0
  254. package/src/gl/Lightmap.ts +402 -0
  255. package/src/gl/Locomotion.ts +498 -0
  256. package/src/gl/Material.ts +279 -0
  257. package/src/gl/Mesh.ts +83 -0
  258. package/src/gl/Model.ts +124 -0
  259. package/src/gl/NavAgent.ts +337 -0
  260. package/src/gl/NavMesh.ts +397 -0
  261. package/src/gl/Node.ts +350 -0
  262. package/src/gl/Noise.ts +30 -0
  263. package/src/gl/Particles.ts +676 -0
  264. package/src/gl/Physics.ts +222 -0
  265. package/src/gl/Plane.ts +53 -0
  266. package/src/gl/Ragdoll.ts +270 -0
  267. package/src/gl/Ray.ts +16 -0
  268. package/src/gl/Scene.ts +505 -0
  269. package/src/gl/Shape.ts +409 -0
  270. package/src/gl/Terrain.ts +1092 -0
  271. package/src/gl/Texture.ts +63 -0
  272. package/src/gl/Trigger.ts +45 -0
  273. package/src/gl/Vehicle.ts +473 -0
  274. package/src/gl/Wheel.ts +240 -0
  275. package/src/gl/animation/AnimationClip.ts +204 -0
  276. package/src/gl/animation/Animator.ts +329 -0
  277. package/src/gl/animation/Layer.ts +39 -0
  278. package/src/gl/animation/Loop.ts +31 -0
  279. package/src/gl/animation/Playback.ts +52 -0
  280. package/src/gl/animation/core.ts +670 -0
  281. package/src/gl/controls.ts +95 -0
  282. package/src/gl/physicsEvents.ts +20 -0
  283. package/src/gl/scenarios.ts +291 -0
  284. package/src/gl/state.ts +6 -0
  285. package/src/gl/terrainMesh.ts +219 -0
  286. package/src/gl/touch.ts +68 -0
  287. package/src/host.d.ts +61 -0
  288. package/src/inject.ts +203 -0
  289. package/src/math/Mathf.ts +118 -0
  290. package/src/math/mat4.ts +278 -0
  291. package/src/math/quat.ts +232 -0
  292. package/src/math/vec.ts +255 -0
  293. package/src/net/codec.ts +119 -0
  294. package/src/net/core.ts +384 -0
  295. package/src/net/index.ts +181 -0
  296. package/src/net/replication.ts +622 -0
  297. package/src/plugins/camera.ts +81 -0
  298. package/src/plugins/geolocation.ts +123 -0
  299. package/src/plugins/oauth.ts +61 -0
  300. package/src/plugins/permission.ts +7 -0
  301. package/src/plugins/push.ts +132 -0
  302. package/src/plugins/qr.ts +73 -0
  303. package/src/plugins/service.ts +47 -0
  304. package/src/runtime/app.ts +101 -0
  305. package/src/runtime/appEvents.ts +54 -0
  306. package/src/runtime/channel.ts +61 -0
  307. package/src/runtime/clipboard.ts +20 -0
  308. package/src/runtime/datetime.ts +329 -0
  309. package/src/runtime/device.ts +293 -0
  310. package/src/runtime/fetch.ts +77 -0
  311. package/src/runtime/files.ts +108 -0
  312. package/src/runtime/input.ts +175 -0
  313. package/src/runtime/media.ts +111 -0
  314. package/src/runtime/misc.ts +16 -0
  315. package/src/runtime/net.ts +36 -0
  316. package/src/runtime/rpc.ts +218 -0
  317. package/src/runtime/service.ts +83 -0
  318. package/src/runtime/share.ts +9 -0
  319. package/src/runtime/storage.ts +13 -0
  320. package/src/runtime/touch.ts +76 -0
  321. package/src/scene/defineScene.ts +1396 -0
  322. package/src/scene/editorPlugins.ts +110 -0
  323. package/src/scene/gizmos.ts +148 -0
  324. package/src/scene/grammar.ts +120 -0
  325. package/src/scene/level.ts +296 -0
  326. package/src/scene/material.ts +188 -0
  327. package/src/server/auth/appConfig.ts +12 -0
  328. package/src/server/auth/global.ts +80 -0
  329. package/src/server/auth/host.ts +318 -0
  330. package/src/server/auth/models.ts +83 -0
  331. package/src/server/auth/types.ts +50 -0
  332. package/src/server/channel.ts +56 -0
  333. package/src/server/context.ts +36 -0
  334. package/src/server/db/defineDb.ts +237 -0
  335. package/src/server/db/fields.ts +132 -0
  336. package/src/server/db/httpTransport.ts +93 -0
  337. package/src/server/db/index.ts +7 -0
  338. package/src/server/db/marci/query.ts +412 -0
  339. package/src/server/db/types.ts +202 -0
  340. package/src/server/errors.ts +12 -0
  341. package/src/server/host.ts +74 -0
  342. package/src/server/inject.ts +13 -0
  343. package/src/server/runtime.ts +133 -0
  344. package/src/server/validate.ts +87 -0
  345. package/src/ui/NativeView.ts +142 -0
  346. package/src/ui/UI.ts +39 -0
  347. package/src/ui/UIBottomSheet.ts +139 -0
  348. package/src/ui/UIButton.ts +101 -0
  349. package/src/ui/UIContainer.ts +60 -0
  350. package/src/ui/UIImage.ts +83 -0
  351. package/src/ui/UIInput.ts +185 -0
  352. package/src/ui/UIModal.ts +139 -0
  353. package/src/ui/UINode.ts +830 -0
  354. package/src/ui/UIPager.ts +362 -0
  355. package/src/ui/UIPopover.ts +100 -0
  356. package/src/ui/UIScreen.ts +123 -0
  357. package/src/ui/UIScrollable.ts +87 -0
  358. package/src/ui/UISpacer.ts +14 -0
  359. package/src/ui/UITabs.ts +236 -0
  360. package/src/ui/UIText.ts +51 -0
  361. package/src/ui/UIVideo.ts +88 -0
  362. package/src/ui/UIVirtualizedList.ts +241 -0
  363. package/src/ui/UIWidget.ts +127 -0
  364. package/src/ui/fonts.ts +13 -0
  365. package/src/ui/presentable.ts +117 -0
  366. package/src/ui/router.ts +132 -0
  367. package/src/ui/theme.ts +84 -0
@@ -0,0 +1,830 @@
1
+ import type { FetchResponse } from "../runtime/fetch"
2
+ import { createBinding, disposeBinding } from "../core/signals"
3
+
4
+ export interface UINode {
5
+ readonly type: string
6
+ /** Author-given semantic name (a stable selector for tests + AI review feedback). */
7
+ readonly name?: string
8
+ style: Style<this, any>
9
+ /** Style-class proxy — read/set/toggle/bind the `$`-classes declared in `.style()`. See {@link Classes}. */
10
+ readonly class: Classes<this>
11
+ }
12
+
13
+ /** A child slot value: a node, or falsy (conditional rendering — falsy entries are skipped). */
14
+ export type UINodeChild = UINode | null | undefined | false
15
+
16
+ /** A factory child argument: a node (or falsy for conditionals), or an array of them — arrays are
17
+ * flattened one level at the argument position, so `UIColumn(header, items.map(row), footer)`
18
+ * needs no spread. */
19
+ export type UIChildArg = UINodeChild | UINodeChild[]
20
+
21
+ /** A color: CSS-style string (`"#1c1c1e"`, `"rgba(0,0,0,0.5)"`, `"var(--primaryColor)"`) or
22
+ * packed number; `null` clears. */
23
+ export type Color = number | string | null
24
+
25
+ /** Style props every element accepts. */
26
+ export type BaseStyle = {
27
+ opacity?: number | `${number}`,
28
+ transform?: string | null,
29
+ /** Pivot of `transform` (CSS `transform-origin` subset): one or two values, each a keyword
30
+ * (`left`/`center`/`right`, `top`/`center`/`bottom`), a percentage of the box, or a px length
31
+ * (bare numbers are px). One value sets x, y stays `50%`. Default `"50% 50%"` = the centre. */
32
+ transformOrigin?: string | null,
33
+ backgroundColor?: Color | null,
34
+ bgColor?: Color | null,
35
+ overflow?: "visible" | "hidden",
36
+ boxSizing?: "border-box" | "content-box",
37
+ /**
38
+ * Author-given semantic name — a stable selector for tests and for marking elements in the AI
39
+ * code-review feedback loop. NOT a style: it's extracted at construction (never sent to the layout
40
+ * engine) and surfaced on the node + in the renderer's serialized output. Available on every element.
41
+ */
42
+ name?: string,
43
+ }
44
+
45
+ /** Painted-box props: background, gradient, border, radius. */
46
+ export type DrawableStyle = {
47
+ backgroundImage?: FetchResponse | File | string | null,
48
+ bgGradient?: string,
49
+ backgroundGradient?: string,
50
+ bgImage?: FetchResponse | File | string | null,
51
+ backgroundSize?: "cover" | "contain" | "tile",
52
+ bgSize?: "cover" | "contain" | "tile",
53
+ border?: string | number | null,
54
+ borderWidth?: number,
55
+ borderColor?: Color,
56
+
57
+ borderLeft?: string | number | null,
58
+ borderLeftWidth?: number,
59
+ borderLeftColor?: Color,
60
+ borderTop?: string | number | null,
61
+ borderTopWidth?: number,
62
+ borderTopColor?: Color,
63
+ borderRight?: string | number | null,
64
+ borderRightWidth?: number,
65
+ borderRightColor?: Color,
66
+ borderBottom?: string | number | null,
67
+ borderBottomWidth?: number,
68
+ borderBottomColor?: Color,
69
+
70
+ borderRadius?: UIValue | string,
71
+ borderTopLeftRadius?: UIValue,
72
+ borderTopRightRadius?: UIValue,
73
+ borderBottomLeftRadius?: UIValue,
74
+ borderBottomRightRadius?: UIValue,
75
+
76
+ pointerEvents?: "all" | "none",
77
+ }
78
+
79
+ /** Flex-layout props of a container (how it lays out its children). */
80
+ export type ContainerStyle = {
81
+ flexDirection?: "row" | "column",
82
+ justifyContent?: "flex-start" | "center" | "flex-end" | "space-between" | "space-evenly",
83
+ alignItems?: "flex-start" | "center" | "flex-end" | "stretch",
84
+ gap?: UIValue,
85
+ flexWrap?: "nowrap" | "wrap" | "wrap-reverse"
86
+ }
87
+
88
+ /** A dimension: a number (logical px) or a CSS-style unit / function string. */
89
+ type UIValue = number |
90
+ `${number}px` |
91
+ `${number}vw` |
92
+ `${number}vh` |
93
+ `${number}vmin` |
94
+ `${number}vmax` |
95
+ `${number}em` |
96
+ `calc(${string})` |
97
+ `min(${string})` |
98
+ `max(${string})` |
99
+ `${string}%` |
100
+ `clamp(${string})` |
101
+ `var(--${string})`
102
+
103
+ /** Padding, with `p`/`px`/`pl`… shorthands. `safe-*` insets by the device safe area,
104
+ * `comfort-*` by the theme's comfort knobs (max of both). */
105
+ export type PaddingStyle = {
106
+ paddingLeft?: UIValue | "safe-left" | "comfort-left",
107
+ paddingTop?: UIValue | "safe-top" | "comfort-top",
108
+ paddingRight?: UIValue | "safe-right" | "comfort-right",
109
+ paddingBottom?: UIValue | "safe-bottom" | "comfort-bottom",
110
+ padding?: UIValue | "safe-all" | "comfort-all",
111
+ paddingHorizontal?: UIValue | "comfort-x",
112
+ paddingVertical?: UIValue | "comfort-y",
113
+
114
+ pl?: UIValue | "safe-left" | "comfort-left",
115
+ pt?: UIValue | "safe-top" | "comfort-top",
116
+ pr?: UIValue | "safe-right" | "comfort-right",
117
+ pb?: UIValue | "safe-bottom" | "comfort-bottom",
118
+ p?: UIValue | "safe-all" | "comfort-all",
119
+ px?: UIValue | "comfort-x",
120
+ py?: UIValue | "comfort-y",
121
+ }
122
+
123
+ /** Margin, with `m`/`mx`/`ml`… shorthands; `"auto"` centers/pushes like CSS. */
124
+ export type MarginStyle = {
125
+ marginLeft?: UIValue | "auto" | "safe-left" | "comfort-left",
126
+ marginTop?: UIValue | "auto" | "safe-top" | "comfort-top",
127
+ marginRight?: UIValue | "auto" | "safe-right" | "comfort-right",
128
+ marginBottom?: UIValue | "auto" | "safe-bottom" | "comfort-bottom",
129
+ margin?: UIValue | "auto" | "safe-all" | "comfort-all",
130
+ marginHorizontal?: UIValue | "auto" | "comfort-x",
131
+ marginVertical?: UIValue | "auto" | "comfort-y",
132
+
133
+ ml?: UIValue | "auto" | "safe-left" | "comfort-left",
134
+ mt?: UIValue | "auto" | "safe-top" | "comfort-top",
135
+ mr?: UIValue | "auto" | "safe-right" | "comfort-right",
136
+ mb?: UIValue | "auto" | "safe-bottom" | "comfort-bottom",
137
+ m?: UIValue | "auto" | "safe-all" | "comfort-all",
138
+ mx?: UIValue | "auto" | "comfort-x",
139
+ my?: UIValue | "auto" | "comfort-y",
140
+ }
141
+
142
+ /** Box position (for `position: "absolute"`) and size constraints. */
143
+ export type PositionStyle = {
144
+ top?: UIValue | `${number}%` | 'safe-top' | 'comfort-top',
145
+ left?: UIValue | `${number}%` | 'safe-left' | 'comfort-left',
146
+ bottom?: UIValue | `${number}%` | 'safe-bottom' | 'comfort-bottom',
147
+ right?: UIValue | `${number}%` | 'safe-right' | 'comfort-right',
148
+ /** Shorthand for all four edges at one value. A longhand (`top`, `left`, …) overrides it. */
149
+ inset?: UIValue | `${number}%` | "auto",
150
+ width?: UIValue | `${number}%` | "auto",
151
+ height?: UIValue | `${number}%` | "auto",
152
+ maxWidth?: UIValue | `${number}%` | "auto",
153
+ maxHeight?: UIValue | `${number}%` | "auto",
154
+ minWidth?: UIValue | `${number}%` | "auto",
155
+ minHeight?: UIValue | `${number}%` | "auto",
156
+ }
157
+
158
+ /** The base style of every element: box + flex-child props. */
159
+ export type ElementStyle = PaddingStyle & MarginStyle & BaseStyle & PositionStyle & {
160
+ position?: "absolute" | "static" | "relative",
161
+ aspectRatio?: number,
162
+ flex?: number | string,
163
+ flexGrow?: number,
164
+ flexShrink?: number,
165
+ flexBase?: UIValue | `${number}%` | "auto",
166
+ alignSelf?: "flex-start" | "center" | "flex-end" | "stretch",
167
+ display?: "none" | "flex"
168
+ }
169
+
170
+ /** Typography props. */
171
+ export type TextStyle = {
172
+ textAlign?: "start" | "center" | "end" | "left" | "right"
173
+ fontFamily?: string,
174
+ fontSize?: UIValue,
175
+ lineHeight?: UIValue | "normal",
176
+ fontWeight?: number | `${number}` | "normal" | "bold",
177
+ fontStyle?: "normal" | "italic",
178
+ color?: Color,
179
+ textDecoration?: "underline" | "line-through" | "none",
180
+ letterSpacing?: UIValue,
181
+ /** Cap the text at N lines; the box is measured to the clamped height. 0 / omitted = unbounded. */
182
+ lineClamp?: number,
183
+ /** How a clamped line ends. Only meaningful together with `lineClamp`. Default "ellipsis". */
184
+ textOverflow?: "ellipsis" | "clip"
185
+ }
186
+
187
+ // Pulls function-valued props out of `style` (mutating it — they must never reach the host bridge)
188
+ // and returns them. A static value under the same key overrides (disposes) an existing binding.
189
+ function extractBindings(el: any, style: any): [string, () => any][] | null {
190
+ let fns: [string, () => any][] | null = null
191
+ for (const key of Object.keys(style)) {
192
+ const value = style[key]
193
+ if (typeof value === "function") {
194
+ (fns ??= []).push([key, value])
195
+ delete style[key]
196
+ } else if (el._fx !== undefined) {
197
+ disposeBinding(el, key)
198
+ }
199
+ }
200
+ return fns
201
+ }
202
+
203
+ // One reactive binding per style key: re-runs when a signal the function read changes, writing
204
+ // through the style proxy (so a mounted node hits _creatorUI.updateStyle, an unmounted one just
205
+ // keeps _style current for the next mount).
206
+ function applyBindings(el: any, fns: [string, () => any][]) {
207
+ for (const [ key, fn ] of fns) {
208
+ createBinding(el, key, () => { el.style[key] = fn() })
209
+ }
210
+ }
211
+
212
+ // NB: returns `this` — `.style()` must chain. Don't rewrite.
213
+ function styleFunction(this: any, newStyle: any) {
214
+ // `name` is a JS-node selector (read by hosts off el.name), not a layout property — extract it
215
+ // exactly like the constructor so `.style({ name })` is equivalent to the first-arg style. Left
216
+ // in the style it would never become el.name (breaking name-based lookups) and would leak into
217
+ // the layout engine.
218
+ if (newStyle && typeof newStyle.name === "string" && newStyle.name.length > 0) {
219
+ this.name = newStyle.name
220
+ delete newStyle.name
221
+ }
222
+ if (newStyle) this._mapStyle?.(newStyle)
223
+ const fns = extractBindings(this, newStyle)
224
+ Object.assign(this._style, newStyle)
225
+ if (this._id !== 0) {
226
+ _creatorUI.mergeStyle(this._id, newStyle)
227
+ }
228
+ if (fns) applyBindings(this, fns)
229
+ return this
230
+ }
231
+
232
+ const handler: ProxyHandler<any> = {
233
+ get(target, key, receiver) {
234
+ if (key === "_raw") {
235
+ return target._el._style
236
+ }
237
+ return target._el._style[key]
238
+ },
239
+ set(target, key: string, value: string | number) {
240
+ if (typeof value === "function") {
241
+ // Reactive binding: `el.style.prop = () => sig.value`. Note a later plain write does NOT
242
+ // dispose the binding (this is the hot path) — override statically via `.style({ prop })`.
243
+ const el = target._el
244
+ const fn = value as unknown as () => any
245
+ createBinding(el, key, () => { el.style[key] = fn() })
246
+ return true
247
+ }
248
+ const el = target._el
249
+ if (el._mapStyle !== undefined) {
250
+ const mapped = { [key]: value }
251
+ el._mapStyle(mapped)
252
+ value = mapped[key]
253
+ }
254
+ if (el._id !== 0) {
255
+ _creatorUI.updateStyle(el._id, key, value)
256
+ }
257
+ el._style[key] = value
258
+ return true
259
+ },
260
+ getOwnPropertyDescriptor(target, p) {
261
+ return Object.getOwnPropertyDescriptor(target._el._style, p)
262
+ },
263
+ ownKeys(target) {
264
+ return Object.keys(target._el._style)
265
+ }
266
+ }
267
+
268
+ // ---- the el.class proxy --------------------------------------------------------------------------
269
+
270
+ // `$checked` and `checked` name the same class — `$` is only the declaration-site prefix in .style().
271
+ const classKey = (name: string) => name[0] === "$" ? name.slice(1) : name
272
+
273
+ // Single-class state write, shared by the set trap, the batch form and binding re-runs. No-op when
274
+ // the state already matches, so an unchanged binding re-run doesn't resend over the bridge.
275
+ function writeClass(el: any, name: string, enabled: boolean) {
276
+ if ((el._classes?.has(name) ?? false) === enabled) return
277
+ if (enabled) {
278
+ (el._classes ??= new Set()).add(name)
279
+ } else {
280
+ el._classes.delete(name)
281
+ }
282
+ if (el._id !== 0) {
283
+ _creatorUI.setClass(el._id, name, enabled)
284
+ }
285
+ }
286
+
287
+ // One class write: a function value installs/replaces a reactive binding, a plain value replaces
288
+ // the binding (last write wins — unlike the style proxy's hot-path caveat, a stale class binding
289
+ // silently reverting a later plain write would be a footgun, and class flips are never per-frame).
290
+ function writeClassValue(el: any, name: string, value: ClassValue) {
291
+ name = classKey(name)
292
+ if (typeof value === "function") {
293
+ createBinding(el, "#class:" + name, () => { writeClass(el, name, !!value()) })
294
+ } else {
295
+ if (el._fx !== undefined) disposeBinding(el, "#class:" + name)
296
+ writeClass(el, name, !!value)
297
+ }
298
+ }
299
+
300
+ // The callable target of the el.class proxy (batch form), bound to the element like styleFunction.
301
+ function classFunction(this: any, classes: Record<string, ClassValue>) {
302
+ for (const key of Object.keys(classes)) {
303
+ writeClassValue(this, key, classes[key])
304
+ }
305
+ return this
306
+ }
307
+
308
+ const classHandler: ProxyHandler<any> = {
309
+ get(target, key) {
310
+ if (typeof key !== "string") return undefined
311
+ return target._el._classes?.has(classKey(key)) ?? false
312
+ },
313
+ set(target, key, value) {
314
+ if (typeof key === "string") writeClassValue(target._el, key, value)
315
+ return true
316
+ },
317
+ // Object.keys(el.class) / spread list the ACTIVE class names (inspection + tests).
318
+ getOwnPropertyDescriptor(target, p) {
319
+ if (typeof p !== "string" || !(target._el._classes?.has(classKey(p)) ?? false)) return undefined
320
+ return { value: true, enumerable: true, configurable: true, writable: true }
321
+ },
322
+ ownKeys(target) {
323
+ const classes = target._el._classes
324
+ return classes !== undefined ? [ ...classes ] : []
325
+ }
326
+ }
327
+
328
+ /** Reactive style input: every primitive-valued prop also accepts a `() => value` binding that
329
+ * re-applies when a signal it read changes. Nested state blocks (onPressed, onLandscape, …) stay
330
+ * static — bindings are extracted at the top level only. */
331
+ export type Reactive<T> = { [K in keyof T]: NonNullable<T[K]> extends object ? T[K] : T[K] | (() => T[K]) }
332
+
333
+ /** User-defined style classes: any `$`-prefixed key in `.style()` declares a state block toggled
334
+ * from code via the `el.class` proxy; `duration`/`delay` animate the swap. Class state INHERITS
335
+ * down the tree (CSS-`.dark`-on-body style): a class set on a node also activates same-name `$`
336
+ * blocks on all descendants within the same root (docs/style-class-cascade-plan.md).
337
+ * `$pressed`/`$focused` are reserved — hosts toggle them on press/focus (they beat other
338
+ * classes, lose to onPressed/onFocused), so children can react to an ancestor's press.
339
+ *
340
+ * Instantiated with the node's FULL style `T` — a class block accepts everything the node's
341
+ * `.style()` does, like `onLandscape`. Narrowing it wrongly rejects valid props such as
342
+ * `$active: { color }`; guarded by tests/ui-types.test.ts. */
343
+ export type ClassStyles<T> = { [key: `$${string}`]: T & { duration?: number, delay?: number } }
344
+
345
+ /** Value accepted by a `el.class` write: a boolean sets the class, a `() => boolean` binds it —
346
+ * the class then tracks the signals the function reads (re-evaluated on change). Reads through
347
+ * the proxy always return a plain boolean, never the bound function. */
348
+ export type ClassValue = boolean | (() => boolean)
349
+
350
+ /** The `el.class` proxy — the runtime switch for `$`-class blocks declared in `.style()`
351
+ * (see {@link ClassStyles}). One surface, mirroring `el.style`:
352
+ *
353
+ * ```ts
354
+ * el.class.checked // read: is it active on THIS element? → boolean
355
+ * el.class.checked = true // activate (false deactivates)
356
+ * el.class.open = !el.class.open // toggle
357
+ * el.class.done = () => todo.done.value // reactive binding (signals)
358
+ * el.class({ checked: true, done: () => … }) // batch form — returns the element, chainable
359
+ * ```
360
+ *
361
+ * Class names are accepted with or without the declaration-site `$` prefix. `R` is the concrete
362
+ * node type, so the batch form chains like `.style()`.
363
+ *
364
+ * Typing note: property access is `any` because an index signature can't give reads (`boolean`)
365
+ * and writes ({@link ClassValue}) different types — the batch form is the fully typed path;
366
+ * single-key writes are runtime-coerced with `!!`. */
367
+ export type Classes<R> = ((classes: Record<string, ClassValue>) => R) & { [key: string]: any }
368
+
369
+ /** Members every UI element shares — element interfaces extend this so docs and types live in one
370
+ * place. `S` is the element's style object; `A` is the subset `animateTo` accepts (defaults to `S`). */
371
+ export interface UIElementBase<S extends object, A extends object = S> {
372
+ // NB: no `name` member here. Declaring it would give every element a property in common with
373
+ // the style types (BaseStyle.name), letting TS's weak-type check match a NODE against a legacy
374
+ // style-first overload — `UIPager(UIText("x"))` must stay a type error (tests/ui-types.test.ts).
375
+ /** Style: `.style({...})` merges (chainable); `el.style.key = v` writes one prop, a `() => v`
376
+ * value binds it to signals — see {@link Style}. */
377
+ style: Style<this, S>
378
+ /** Tween to the target style — meta keys `duration`/`delay`/`loop`/…, see {@link AnimateStyle}. */
379
+ animateTo: AnimateStyle<this, A>
380
+ /** Tween from the given style to the current one (entrance animations). */
381
+ animateFrom: AnimateStyle<this, A>
382
+ /** Fires after every layout pass with the parent-relative box. */
383
+ onLayout(onLayout: OnLayoutCallback): this
384
+ /** Absolute rect in device space, read live (includes scroll); `null` before mount. */
385
+ getBoundingClientRect(): BoundingClientRect | null
386
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
387
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
388
+ * Classes cascade to descendants. See {@link Classes}. */
389
+ readonly class: Classes<this>
390
+ }
391
+
392
+ /** {@link UIElementBase} plus the child-management surface every container shares. */
393
+ export interface UIContainerBase<S extends object, A extends object = S> extends UIElementBase<S, A> {
394
+ /** Append children. */
395
+ append(...nodes: UINodeChild[]): this
396
+ /** Insert children at `index`. */
397
+ insert(index: number, ...nodes: UINodeChild[]): this
398
+ /** Remove (unmount) the given children. */
399
+ remove(...nodes: UINodeChild[]): this
400
+ /** Replace all children — an array, or a function for reactive children. */
401
+ setContent(nodes: UINodeChild[] | ChildrenFn): this
402
+ /** The children array — mutate via `append`/`insert`/`remove`/`setContent`. */
403
+ readonly children: UINodeChild[]
404
+ }
405
+
406
+ /** Orientation state blocks — override props applied only in landscape / portrait (like a
407
+ * `$`-class, keyed by device orientation instead of `el.class`). Same full-`T` rule as class
408
+ * blocks. */
409
+ export type OrientationStyles<T> = {
410
+ onLandscape?: T,
411
+ onPortrait?: T,
412
+ /** @deprecated Misspelling of `onPortrait` (no second "r"). Kept so existing projects and published
413
+ * bundles keep working — hosts still honour it — but new code should use `onPortrait`. */
414
+ onPortait?: T,
415
+ }
416
+
417
+ // NB: do NOT annotate `this: R` on StyleFn. As a parameter it makes R contravariant, turning every
418
+ // node type INVARIANT (UIScreen<false> stopped being assignable to UIScreen<boolean>). Runtime
419
+ // `this` is already fixed by `styleFunction.bind(this)`; R stays covariant in the return, so
420
+ // `.style()` still chains. Guarded by tests/ui-types.test-d.ts.
421
+ export type StyleFn <R, T extends object> = ((style: Reactive<T> & OrientationStyles<T> & ClassStyles<T>) => R)
422
+
423
+ /** The `el.style` surface: callable — `.style({...})` merges and returns the element for
424
+ * chaining — and per-key readable/writable (`el.style.opacity = 0.5`; a `() => value` write
425
+ * installs a reactive binding). */
426
+ export type Style <R, T extends object> = StyleFn<R,T> & T & OrientationStyles<T> & ClassStyles<T>
427
+
428
+ /** The `animateTo`/`animateFrom` surface: target style props plus the flat meta keys below
429
+ * (stripped before the commit into the element's style). */
430
+ export type AnimateStyle<R, T extends object> = ((style: T & {
431
+ /** Tween length in **milliseconds** (default 225). */
432
+ duration?: number,
433
+ /** Wait before starting, in **milliseconds**. A looping animation delays once, not per cycle. */
434
+ delay?: number,
435
+ /** Escape hatch: target an internal style layer (the onPressed/orientation mechanism). Not part of the supported surface. */
436
+ layer?: number,
437
+ /** `false` = animate without writing the targets into the element's style (e.g. a fade-out right before `.hide()`). */
438
+ commit?: boolean,
439
+ /** Repeat the animation: `true` = forever, a number = that many cycles. A looping animation is an
440
+ * effect, not a state change — it never commits, and the element ends back at its base style.
441
+ * The loop stops when a later `animateTo`/`animateFrom` runs on the element or it leaves the screen. */
442
+ loop?: boolean | number,
443
+ /** `"ping-pong"` (default) animates there and back each cycle — no jump. `"restart"` snaps back and
444
+ * replays forward — for full-turn spinners (`transform: "rotate(360deg)"`) and shimmers. Only meaningful with `loop`. */
445
+ loopMode?: "restart" | "ping-pong",
446
+ }) => R)
447
+
448
+ /** Meta keys of the animateTo/animateFrom options bag — everything that is not a style target. */
449
+ const ANIMATE_META = new Set(["duration", "delay", "layer", "commit", "loop", "loopMode"])
450
+
451
+ export type AppearStyle <R, T> = (arg: { from: T, duration?: number, delay?: number }) => R
452
+ export type DisappearStyle <R, T> = (arg: { to: T, duration?: number, delay?: number }) => R
453
+
454
+ /** `onLayout` payload — the node's box in PARENT-relative coordinates. */
455
+ export type OnLayoutCallback = (layout: { left: number, top: number, width: number, height: number }) => void
456
+
457
+ /** Web-DOMRect shape on purpose (getBoundingClientRect priors must hold): all eight fields, with
458
+ * right/bottom/x/y derived SDK-side — hosts only report [left, top, width, height]. */
459
+ export type BoundingClientRect = {
460
+ x: number, y: number,
461
+ left: number, top: number, right: number, bottom: number,
462
+ width: number, height: number,
463
+ }
464
+
465
+ export class Element<T extends string> {
466
+ readonly type: T
467
+ /** Author-given semantic name (LeCodes `name`) — see BaseStyle.name. Extracted from the options
468
+ * object so it never reaches the layout engine; the renderer reads it off the node. */
469
+ readonly name?: string
470
+ protected _style: any
471
+
472
+ protected _id: number = 0
473
+
474
+ protected _styleProxy: any
475
+ protected _appear: any
476
+ protected _disappear: any
477
+ /** @internal Reactive bindings owned by this element (style key / "#text" → effect). The element
478
+ * is the effect's only strong owner, so discarding a subtree lets its bindings collect with it. */
479
+ _fx?: Map<string, any>
480
+
481
+ /** @internal Per-subclass style normalization (SDK-name → wire-name, e.g. UIInput's
482
+ * `type: "phone"` → `"tel"`), mutating the style in place before it is stored or sent. Runs on
483
+ * every entry point: the first-arg style, `.style()`, and single-key `el.style.k =` writes. */
484
+ _mapStyle?(style: any): void
485
+
486
+ // Per-type default styles are inlined at each component's construction site (spread UNDER the
487
+ // user's style — e.g. UIRow's `flexDirection: "row"`), so every node reaches the host with
488
+ // explicit values and hosts keep no per-type style opinions of their own.
489
+ constructor(type: T, style: any) {
490
+ this.type = type
491
+ if (style && typeof style.name === "string" && style.name.length > 0) {
492
+ this.name = style.name
493
+ delete style.name
494
+ }
495
+ if (style) this._mapStyle?.(style)
496
+ this._style = style
497
+ if (style) {
498
+ const fns = extractBindings(this, style)
499
+ if (fns) applyBindings(this, fns) // sync first run — _style holds concrete values pre-mount
500
+ }
501
+ // Creation-site capture for the editor preview's element inspector: the host sets the flag
502
+ // before running the bundle, and the stack (mapped through the bundle's source map) points at
503
+ // the user code that created this element. Off everywhere else — zero cost when unset.
504
+ if ((globalThis as any).__lecodesCaptureSites) {
505
+ (this as any)._site = new Error("lecodes-site")
506
+ }
507
+ }
508
+
509
+ get style(): any {
510
+ if (this._styleProxy) return this._styleProxy
511
+ const obj = styleFunction.bind(this)
512
+ // @ts-ignore
513
+ obj._el = this
514
+
515
+ const proxy = new Proxy(obj, handler)
516
+ this._styleProxy = proxy
517
+ return proxy
518
+ }
519
+
520
+ /** Tween to the target style and commit it (unless `commit: false` or looping — a loop is an
521
+ * effect, not a state change). Meta keys: `duration`/`delay`/`loop`/… — see {@link AnimateStyle}. */
522
+ animateTo(style: any): this{
523
+ _creatorUI.animateTo(this._id, style)
524
+ if (style.commit !== false && !style.loop) {
525
+ for (const key in style) {
526
+ if (!ANIMATE_META.has(key)) this._style[key] = style[key]
527
+ }
528
+ }
529
+ return this
530
+ }
531
+ /** Tween FROM the given style to the element's current one (entrance animations). */
532
+ animateFrom(style: any): this{
533
+ _creatorUI.animateFrom(this._id, style)
534
+ return this
535
+ }
536
+
537
+ /** @internal Active style-class names (stored without the `$`). Kept on the element — not just in
538
+ * the layout engine — so hosts re-apply them when a detached node is re-mounted (screens
539
+ * re-assign _id on every open). */
540
+ protected _classes?: Set<string>
541
+
542
+ protected _classProxy?: any
543
+
544
+ /** Style-class control — flips the `$`-class blocks declared in `.style()`. One proxy, same
545
+ * contract as `el.style`:
546
+ *
547
+ * - `el.class.checked` — read: is the class active on THIS element? → `boolean`
548
+ * - `el.class.checked = true` / `= false` — activate / deactivate
549
+ * - `el.class.open = !el.class.open` — toggle
550
+ * - `el.class.done = () => todo.done.value` — reactive: the class tracks the signals the
551
+ * function reads; a later write to the same class (plain or function) replaces the binding
552
+ * - `el.class({ checked: true, done: () => … })` — batch form, returns the element (chainable)
553
+ *
554
+ * Names are accepted with or without the leading `$`. A class set on an element CASCADES: it is
555
+ * also active on every descendant (their same-name `$` blocks light up), until the subtree's
556
+ * root — hosted screens/widgets don't inherit. Reads reflect only this element's own classes;
557
+ * there is no opt-out below an active ancestor. `Object.keys(el.class)` lists the active names. */
558
+ get class(): any {
559
+ if (this._classProxy) return this._classProxy
560
+ const obj = classFunction.bind(this)
561
+ // @ts-ignore
562
+ obj._el = this
563
+
564
+ const proxy = new Proxy(obj, classHandler)
565
+ this._classProxy = proxy
566
+ return proxy
567
+ }
568
+
569
+ protected ll?: OnLayoutCallback[]
570
+ /** Observe layout: fires after every host layout pass with the parent-relative box. */
571
+ onLayout(onLayout: OnLayoutCallback): this {
572
+ if (this.ll) {
573
+ this.ll.push(onLayout)
574
+ } else {
575
+ this.ll = [ onLayout ]
576
+ }
577
+ return this
578
+ }
579
+
580
+ /** Absolute rect in device space — the space `UIWidget` positions in and touch events report
581
+ * `clientX`/`clientY` in — including scroll offsets, read live at call time (unlike `onLayout`,
582
+ * whose coordinates are parent-relative and go stale when an ancestor scrolls). `null` before
583
+ * the element is mounted and on hosts without the bridge read. Anchor popovers position-once
584
+ * at open; don't poll per frame. */
585
+ getBoundingClientRect(): BoundingClientRect | null {
586
+ if (this._id === 0) return null
587
+ const rect = _creatorUI.getBoundingClientRect?.(this._id)
588
+ if (!rect) return null
589
+ const [left, top, width, height] = rect
590
+ return { x: left, y: top, left, top, right: left + width, bottom: top + height, width, height }
591
+ }
592
+ }
593
+
594
+ // ---- reactive children --------------------------------------------------------------------------
595
+
596
+ /** Reactive children: re-runs when a signal it read changes; the result is reconciled against
597
+ * the mounted children (kept nodes stay mounted, state intact). */
598
+ export type ChildrenFn = () => UINodeChild[]
599
+
600
+ // The container whose children binding is currently executing. __uiMap uses it as the cache owner;
601
+ // nested bindings save/restore it, so each level caches on its own element.
602
+ let currentMapOwner: any = null
603
+
604
+ /**
605
+ * Compiler-emitted memoized map for reactive children. The chisel `reactiveUi` pass rewrites
606
+ * `list.map(fn)` inside a children binding to `__uiMap(list, fn, "<file>:<offset>")`, so unchanged
607
+ * items keep their element identity across re-runs (the reconciler then leaves them mounted).
608
+ * Cache: per (binding owner, call-site slot), keyed by item reference; duplicate items consume
609
+ * cached nodes in order. Outside a children binding, for non-array inputs, or for non-element
610
+ * results it behaves exactly like Array.prototype.map — a wrong rewrite can't change behavior.
611
+ */
612
+ export function __uiMap(list: any, render: (item: any, index: number) => any, slot: string): any[] {
613
+ if (currentMapOwner === null || !Array.isArray(list)) {
614
+ return list?.map(render) ?? []
615
+ }
616
+ const slots: Map<string, Map<any, any[]>> = currentMapOwner._mapCache ??= new Map()
617
+ const prev = slots.get(slot)
618
+ const next = new Map<any, any[]>()
619
+ const out: any[] = []
620
+ for (let i = 0; i < list.length; i++) {
621
+ const item = list[i]
622
+ const stack = prev?.get(item)
623
+ let node = stack !== undefined && stack.length > 0 ? stack.shift() : undefined
624
+ if (node === undefined) {
625
+ node = render(item, i)
626
+ if (!(node instanceof Element)) {
627
+ out.push(node) // not a UI element — plain map semantics, never cached
628
+ continue
629
+ }
630
+ }
631
+ let arr = next.get(item)
632
+ if (arr === undefined) next.set(item, arr = [])
633
+ arr.push(node)
634
+ out.push(node)
635
+ }
636
+ // entries left in `prev` (items gone from the list) are dropped here; their nodes and bindings
637
+ // become unreachable and collect
638
+ slots.set(slot, next)
639
+ return out
640
+ }
641
+
642
+ // ---- factory argument dispatch ------------------------------------------------------------------
643
+
644
+ /** @internal Shared argument dispatch for the UI factories (`UIRow(...)`, `UIButton(...)`, …).
645
+ * Children are variadic; an array argument is flattened one level, so both the legacy
646
+ * `UIColumn([a, b])` and `UIColumn(header, items.map(row), footer)` work. A lone function
647
+ * argument is a reactive {@link ChildrenFn}. A plain non-node object in first position is the
648
+ * legacy style bag: it is spread over `defaults`, or — when `defaults` is null — stored as-is
649
+ * (the Element constructor mutates it in place, matching the old path). The rest-args array is
650
+ * taken over as the children array when nothing needs flattening — callers must hand it off. */
651
+ export function buildUI<R>(args: any[], defaults: any, make: (style: any, children: any[] | ChildrenFn) => R): R {
652
+ const n = args.length
653
+ const a0 = n > 0 ? args[0] : undefined
654
+ let style: any
655
+ let start = 0
656
+ if (a0 !== null && typeof a0 === "object" && !Array.isArray(a0) && !(a0 instanceof Element)) {
657
+ style = defaults ? { ...defaults, ...a0 } : a0
658
+ start = 1
659
+ } else {
660
+ style = defaults ? { ...defaults } : {}
661
+ }
662
+ if (n === start + 1) {
663
+ const a = args[start]
664
+ // lone function → reactive children; lone array → used as the children array directly
665
+ // (the legacy zero-copy path — nested arrays inside it are NOT flattened)
666
+ if (typeof a === "function" || Array.isArray(a)) return make(style, a)
667
+ }
668
+ // Single pass: reuse the rest-args array as the children array until the first array argument
669
+ // shows up, then copy the prefix once and flatten from there. Falsy entries stay — they are
670
+ // compacted at mount like always.
671
+ let flat: any[] | undefined
672
+ for (let i = start; i < n; i++) {
673
+ const a = args[i]
674
+ if (flat === undefined) {
675
+ if (Array.isArray(a)) {
676
+ flat = args.slice(start, i)
677
+ for (let j = 0; j < a.length; j++) flat.push(a[j])
678
+ }
679
+ } else if (Array.isArray(a)) {
680
+ for (let j = 0; j < a.length; j++) flat.push(a[j])
681
+ } else {
682
+ flat.push(a)
683
+ }
684
+ }
685
+ if (flat !== undefined) return make(style, flat)
686
+ return make(style, start === 0 ? args : args.slice(1))
687
+ }
688
+
689
+ export class ContainerElement<T extends string> extends Element<T> {
690
+ children: any[] = []
691
+
692
+ constructor(type: T, style: any, children: any[] | ChildrenFn) {
693
+ super(type, style)
694
+ if (typeof children === "function") {
695
+ this._bindChildren(children)
696
+ } else {
697
+ this.children = children
698
+ }
699
+ }
700
+
701
+ /** @internal Reactive children: re-runs when a signal read inside `fn` changes and reconciles
702
+ * the result against the mounted children with minimal insert/remove ops. */
703
+ _bindChildren(fn: ChildrenFn) {
704
+ createBinding(this, "#children", () => {
705
+ const prevOwner = currentMapOwner
706
+ currentMapOwner = this
707
+ let next: UINodeChild[]
708
+ try {
709
+ next = fn()
710
+ } finally {
711
+ currentMapOwner = prevOwner
712
+ }
713
+ this._reconcile(Array.isArray(next) ? next : [])
714
+ })
715
+ }
716
+
717
+ /** @internal Identity diff of the current children against `next`: departed nodes are removed,
718
+ * new ones inserted at their position, kept nodes stay mounted (so their host state survives).
719
+ * A moved node is remove+insert — it remounts. Falsy entries are compacted away. */
720
+ _reconcile(next: UINodeChild[]) {
721
+ const target: any[] = []
722
+ for (const c of next) if (c) target.push(c)
723
+
724
+ if (this._id === 0) {
725
+ this.children.length = 0
726
+ this.children.push(...target)
727
+ return
728
+ }
729
+
730
+ // compact holes left by a previous static children array (falsy entries were never mounted)
731
+ for (let i = this.children.length - 1; i >= 0; i--) {
732
+ if (!this.children[i]) this.children.splice(i, 1)
733
+ }
734
+
735
+ const targetSet = new Set(target)
736
+ const departed = this.children.filter(c => !targetSet.has(c))
737
+ if (departed.length > 0) this.remove(...departed)
738
+
739
+ // this.children now mirrors the mounted order; walk the target and fix mismatches in place
740
+ for (let i = 0; i < target.length; i++) {
741
+ if (this.children[i] === target[i]) continue
742
+ const node = target[i]
743
+ if (this.children.includes(node, i + 1)) {
744
+ this.remove(node) // moved forward — remount at its new position
745
+ }
746
+ this.insert(i, node)
747
+ }
748
+ }
749
+
750
+ /** Append children (falsy entries are kept in the array but never mounted). */
751
+ append(...nodes: UINodeChild[]): this {
752
+ if (this._id === 0) {
753
+ this.children.push(...nodes)
754
+ return this
755
+ }
756
+ let index = 0
757
+ for (let child of this.children) {
758
+ if (child) {
759
+ index++
760
+ }
761
+ }
762
+ for (let node of nodes) {
763
+ if (!node) continue
764
+ _creatorUI.insertNode(index, this._id, node)
765
+ index++
766
+ }
767
+ this.children.push(...nodes)
768
+ return this
769
+ }
770
+ /** Insert children at `index` of the children array. */
771
+ insert(index: number, ...nodes: UINodeChild[]): this {
772
+ if (this._id === 0) {
773
+ this.children.splice(index, 0, ...nodes)
774
+ return this
775
+ }
776
+
777
+ let _index = 0
778
+ for (let i = 0; i < index; i++) {
779
+ if (this.children[i]) {
780
+ _index++
781
+ }
782
+ }
783
+ for (let node of nodes) {
784
+ if (!node) continue
785
+ _creatorUI.insertNode(_index, this._id, node)
786
+ _index++
787
+ }
788
+ this.children.splice(index, 0, ...nodes)
789
+ return this
790
+ }
791
+ /** Remove (unmount) the given children. */
792
+ remove(...nodesToDelete: UINode[]): this {
793
+ const set = new Set(nodesToDelete)
794
+
795
+ for (let i = 0; i < this.children.length; i++) {
796
+ if (set.has(this.children[i])) {
797
+ set.delete(this.children[i])
798
+ this.children.splice(i, 1)
799
+ i--
800
+
801
+ if (set.size === 0) {
802
+ break
803
+ }
804
+ }
805
+ }
806
+
807
+ if (this._id === 0) {
808
+ return this
809
+ }
810
+ for (let node of nodesToDelete) {
811
+ _creatorUI.removeNode(this._id, node)
812
+ }
813
+ return this
814
+ }
815
+
816
+ /** Replace all children — a plain array, or a function for reactive children. */
817
+ setContent(children: UINodeChild[] | ChildrenFn): this {
818
+ if (typeof children === "function") {
819
+ this._bindChildren(children) // createBinding replaces a previous "#children" binding
820
+ return this
821
+ }
822
+ disposeBinding(this, "#children") // static content overrides a reactive binding
823
+ if (this._id !== 0) {
824
+ _creatorUI.setContent(this._id, this.children, children)
825
+ }
826
+ this.children.length = 0
827
+ this.children.push(...children)
828
+ return this
829
+ }
830
+ }