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.
- package/README.md +114 -0
- package/dist/global.d.ts +503 -0
- package/dist/host.d.ts +61 -0
- package/dist/inject.js +4730 -0
- package/dist/types/animate/animate.d.ts +20 -0
- package/dist/types/animate/bezier.d.ts +2 -0
- package/dist/types/animate/easings.d.ts +21 -0
- package/dist/types/canvas/Canvas.d.ts +168 -0
- package/dist/types/core/Aspect.d.ts +171 -0
- package/dist/types/core/InspectorUI.d.ts +88 -0
- package/dist/types/core/StateMachine.d.ts +81 -0
- package/dist/types/core/color.d.ts +13 -0
- package/dist/types/core/compWrite.d.ts +11 -0
- package/dist/types/core/events.d.ts +11 -0
- package/dist/types/core/fields.d.ts +58 -0
- package/dist/types/core/registry.d.ts +7 -0
- package/dist/types/core/signals.d.ts +29 -0
- package/dist/types/core/time.d.ts +31 -0
- package/dist/types/g2/Camera2D.d.ts +19 -0
- package/dist/types/g2/CharacterController2D.d.ts +134 -0
- package/dist/types/g2/Node2D.d.ts +91 -0
- package/dist/types/g2/OneWay2D.d.ts +24 -0
- package/dist/types/g2/Physics2D.d.ts +147 -0
- package/dist/types/g2/Scene2D.d.ts +72 -0
- package/dist/types/g2/Shape2D.d.ts +58 -0
- package/dist/types/g2/Sprite.d.ts +51 -0
- package/dist/types/g2/SpriteAnimation.d.ts +50 -0
- package/dist/types/g2/SpriteSheet.d.ts +69 -0
- package/dist/types/g2/Texture2D.d.ts +16 -0
- package/dist/types/g2/Tilemap.d.ts +20 -0
- package/dist/types/g2/Tileset.d.ts +32 -0
- package/dist/types/g2/Trigger2D.d.ts +20 -0
- package/dist/types/g2/autotile.d.ts +133 -0
- package/dist/types/g2/cells.d.ts +14 -0
- package/dist/types/g2/defineScene2d.d.ts +141 -0
- package/dist/types/g2/groups2d.d.ts +16 -0
- package/dist/types/g2/loop.d.ts +4 -0
- package/dist/types/g2/scenarios2d.d.ts +30 -0
- package/dist/types/g2/touch.d.ts +2 -0
- package/dist/types/gl/Camera.d.ts +79 -0
- package/dist/types/gl/CameraPlace.d.ts +26 -0
- package/dist/types/gl/CharacterController.d.ts +98 -0
- package/dist/types/gl/Gearbox.d.ts +86 -0
- package/dist/types/gl/Geometry.d.ts +39 -0
- package/dist/types/gl/IK.d.ts +53 -0
- package/dist/types/gl/InstancedMesh.d.ts +46 -0
- package/dist/types/gl/Light.d.ts +73 -0
- package/dist/types/gl/Lightmap.d.ts +85 -0
- package/dist/types/gl/Locomotion.d.ts +279 -0
- package/dist/types/gl/Material.d.ts +100 -0
- package/dist/types/gl/Mesh.d.ts +33 -0
- package/dist/types/gl/Model.d.ts +53 -0
- package/dist/types/gl/NavAgent.d.ts +99 -0
- package/dist/types/gl/NavMesh.d.ts +169 -0
- package/dist/types/gl/Node.d.ts +99 -0
- package/dist/types/gl/Noise.d.ts +20 -0
- package/dist/types/gl/Particles.d.ts +277 -0
- package/dist/types/gl/Physics.d.ts +91 -0
- package/dist/types/gl/Plane.d.ts +19 -0
- package/dist/types/gl/Ragdoll.d.ts +86 -0
- package/dist/types/gl/Ray.d.ts +8 -0
- package/dist/types/gl/Scene.d.ts +221 -0
- package/dist/types/gl/Shape.d.ts +122 -0
- package/dist/types/gl/Terrain.d.ts +280 -0
- package/dist/types/gl/Texture.d.ts +27 -0
- package/dist/types/gl/Trigger.d.ts +10 -0
- package/dist/types/gl/Vehicle.d.ts +191 -0
- package/dist/types/gl/Wheel.d.ts +95 -0
- package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
- package/dist/types/gl/animation/Animator.d.ts +219 -0
- package/dist/types/gl/animation/Layer.d.ts +31 -0
- package/dist/types/gl/animation/Loop.d.ts +17 -0
- package/dist/types/gl/animation/Playback.d.ts +36 -0
- package/dist/types/gl/animation/core.d.ts +367 -0
- package/dist/types/gl/controls.d.ts +19 -0
- package/dist/types/gl/physicsEvents.d.ts +1 -0
- package/dist/types/gl/scenarios.d.ts +98 -0
- package/dist/types/gl/state.d.ts +5 -0
- package/dist/types/gl/terrainMesh.d.ts +46 -0
- package/dist/types/gl/touch.d.ts +2 -0
- package/dist/types/inject.d.ts +123 -0
- package/dist/types/math/Mathf.d.ts +39 -0
- package/dist/types/math/mat4.d.ts +66 -0
- package/dist/types/math/quat.d.ts +53 -0
- package/dist/types/math/vec.d.ts +129 -0
- package/dist/types/net/codec.d.ts +25 -0
- package/dist/types/net/core.d.ts +106 -0
- package/dist/types/net/index.d.ts +80 -0
- package/dist/types/net/replication.d.ts +101 -0
- package/dist/types/plugins/camera.d.ts +25 -0
- package/dist/types/plugins/geolocation.d.ts +39 -0
- package/dist/types/plugins/oauth.d.ts +25 -0
- package/dist/types/plugins/permission.d.ts +1 -0
- package/dist/types/plugins/push.d.ts +46 -0
- package/dist/types/plugins/qr.d.ts +21 -0
- package/dist/types/plugins/service.d.ts +33 -0
- package/dist/types/runtime/app.d.ts +67 -0
- package/dist/types/runtime/appEvents.d.ts +7 -0
- package/dist/types/runtime/channel.d.ts +1 -0
- package/dist/types/runtime/clipboard.d.ts +7 -0
- package/dist/types/runtime/datetime.d.ts +80 -0
- package/dist/types/runtime/device.d.ts +126 -0
- package/dist/types/runtime/fetch.d.ts +31 -0
- package/dist/types/runtime/files.d.ts +33 -0
- package/dist/types/runtime/input.d.ts +121 -0
- package/dist/types/runtime/media.d.ts +46 -0
- package/dist/types/runtime/misc.d.ts +11 -0
- package/dist/types/runtime/net.d.ts +15 -0
- package/dist/types/runtime/rpc.d.ts +44 -0
- package/dist/types/runtime/service.d.ts +1 -0
- package/dist/types/runtime/share.d.ts +2 -0
- package/dist/types/runtime/storage.d.ts +5 -0
- package/dist/types/runtime/touch.d.ts +56 -0
- package/dist/types/scene/defineScene.d.ts +296 -0
- package/dist/types/scene/editorPlugins.d.ts +82 -0
- package/dist/types/scene/gizmos.d.ts +56 -0
- package/dist/types/scene/grammar.d.ts +56 -0
- package/dist/types/scene/level.d.ts +98 -0
- package/dist/types/scene/material.d.ts +76 -0
- package/dist/types/server/auth/appConfig.d.ts +16 -0
- package/dist/types/server/auth/global.d.ts +56 -0
- package/dist/types/server/auth/models.d.ts +164 -0
- package/dist/types/server/auth/types.d.ts +56 -0
- package/dist/types/server/channel.d.ts +28 -0
- package/dist/types/server/context.d.ts +27 -0
- package/dist/types/server/db/defineDb.d.ts +36 -0
- package/dist/types/server/db/fields.d.ts +146 -0
- package/dist/types/server/db/index.d.ts +6 -0
- package/dist/types/server/db/marci/query.d.ts +326 -0
- package/dist/types/server/db/types.d.ts +163 -0
- package/dist/types/server/errors.d.ts +8 -0
- package/dist/types/server/inject.d.ts +5 -0
- package/dist/types/ui/NativeView.d.ts +38 -0
- package/dist/types/ui/UI.d.ts +27 -0
- package/dist/types/ui/UIBottomSheet.d.ts +71 -0
- package/dist/types/ui/UIButton.d.ts +30 -0
- package/dist/types/ui/UIContainer.d.ts +33 -0
- package/dist/types/ui/UIImage.d.ts +35 -0
- package/dist/types/ui/UIInput.d.ts +110 -0
- package/dist/types/ui/UIModal.d.ts +70 -0
- package/dist/types/ui/UINode.d.ts +363 -0
- package/dist/types/ui/UIPager.d.ts +123 -0
- package/dist/types/ui/UIPopover.d.ts +39 -0
- package/dist/types/ui/UIScreen.d.ts +57 -0
- package/dist/types/ui/UIScrollable.d.ts +40 -0
- package/dist/types/ui/UISpacer.d.ts +9 -0
- package/dist/types/ui/UITabs.d.ts +96 -0
- package/dist/types/ui/UIText.d.ts +19 -0
- package/dist/types/ui/UIVideo.d.ts +28 -0
- package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
- package/dist/types/ui/UIWidget.d.ts +65 -0
- package/dist/types/ui/fonts.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +68 -0
- package/dist/types/ui/router.d.ts +40 -0
- package/dist/types/ui/theme.d.ts +26 -0
- package/dist/types.json +1 -0
- package/package.json +46 -0
- package/prompts/2d.md +395 -0
- package/prompts/3d-scene-files.md +113 -0
- package/prompts/3d-scene.md +119 -0
- package/prompts/3d.md +303 -0
- package/prompts/README.md +142 -0
- package/prompts/ar.md +109 -0
- package/prompts/canvas.md +27 -0
- package/prompts/compose.ts +46 -0
- package/prompts/concept.md +49 -0
- package/prompts/core-design.md +75 -0
- package/prompts/core.md +264 -0
- package/prompts/design.md +218 -0
- package/prompts/dist/2d-game.md +1530 -0
- package/prompts/dist/3d-app.md +1672 -0
- package/prompts/dist/ar-app.md +1548 -0
- package/prompts/dist/concept.md +49 -0
- package/prompts/dist/design.md +498 -0
- package/prompts/dist/ui-app.md +1134 -0
- package/prompts/index.md +37 -0
- package/prompts/intake-prompt.md +33 -0
- package/prompts/namer-prompt.md +12 -0
- package/prompts/router-prompt.md +50 -0
- package/prompts/select.ts +111 -0
- package/prompts/ui-design.md +203 -0
- package/prompts/ui.md +803 -0
- package/src/animate/animate.ts +238 -0
- package/src/animate/bezier.ts +138 -0
- package/src/animate/easings.ts +126 -0
- package/src/bridges.d.ts +1345 -0
- package/src/canvas/Canvas.ts +305 -0
- package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
- package/src/compile/__tests__/assetMacro.test.ts +74 -0
- package/src/compile/__tests__/assetName.test.ts +55 -0
- package/src/compile/__tests__/compile.test.ts +287 -0
- package/src/compile/__tests__/detectEntry.test.ts +132 -0
- package/src/compile/__tests__/fontMacro.test.ts +199 -0
- package/src/compile/aspectMacro.ts +86 -0
- package/src/compile/assetIconMacro.ts +384 -0
- package/src/compile/assetMacro.ts +146 -0
- package/src/compile/assetName.ts +50 -0
- package/src/compile/bundler.ts +287 -0
- package/src/compile/compileProject.ts +127 -0
- package/src/compile/detectEntry.ts +128 -0
- package/src/compile/fontMacro.ts +459 -0
- package/src/compile/fontRegistry.ts +78 -0
- package/src/compile/header.ts +67 -0
- package/src/compile/index.ts +100 -0
- package/src/compile/libraryImports.ts +52 -0
- package/src/compile/liteMaterial.ts +247 -0
- package/src/compile/sceneEditor.ts +88 -0
- package/src/compile/serverSplit.ts +233 -0
- package/src/compile/serverTypes.ts +227 -0
- package/src/compile/sfnt.ts +98 -0
- package/src/compile/shaderSchema.ts +202 -0
- package/src/compile/shaderTargets.ts +77 -0
- package/src/compile/sourcemap.ts +25 -0
- package/src/core/Aspect.ts +568 -0
- package/src/core/InspectorUI.ts +212 -0
- package/src/core/StateMachine.ts +308 -0
- package/src/core/__tests__/stateMachine.test.ts +132 -0
- package/src/core/color.ts +66 -0
- package/src/core/compWrite.ts +42 -0
- package/src/core/events.ts +38 -0
- package/src/core/fields.ts +120 -0
- package/src/core/registry.ts +23 -0
- package/src/core/signals.ts +277 -0
- package/src/core/time.ts +84 -0
- package/src/g2/Camera2D.ts +40 -0
- package/src/g2/CharacterController2D.ts +276 -0
- package/src/g2/Node2D.ts +267 -0
- package/src/g2/OneWay2D.ts +66 -0
- package/src/g2/Physics2D.ts +346 -0
- package/src/g2/Scene2D.ts +209 -0
- package/src/g2/Shape2D.ts +259 -0
- package/src/g2/Sprite.ts +89 -0
- package/src/g2/SpriteAnimation.ts +171 -0
- package/src/g2/SpriteSheet.ts +166 -0
- package/src/g2/Texture2D.ts +47 -0
- package/src/g2/Tilemap.ts +41 -0
- package/src/g2/Tileset.ts +71 -0
- package/src/g2/Trigger2D.ts +77 -0
- package/src/g2/autotile.ts +433 -0
- package/src/g2/cells.ts +91 -0
- package/src/g2/defineScene2d.ts +381 -0
- package/src/g2/groups2d.ts +106 -0
- package/src/g2/loop.ts +50 -0
- package/src/g2/scenarios2d.ts +69 -0
- package/src/g2/touch.ts +83 -0
- package/src/gl/Camera.ts +160 -0
- package/src/gl/CameraPlace.ts +52 -0
- package/src/gl/CharacterController.ts +238 -0
- package/src/gl/Gearbox.ts +212 -0
- package/src/gl/Geometry.ts +279 -0
- package/src/gl/IK.ts +193 -0
- package/src/gl/InstancedMesh.ts +132 -0
- package/src/gl/Light.ts +135 -0
- package/src/gl/Lightmap.ts +402 -0
- package/src/gl/Locomotion.ts +498 -0
- package/src/gl/Material.ts +279 -0
- package/src/gl/Mesh.ts +83 -0
- package/src/gl/Model.ts +124 -0
- package/src/gl/NavAgent.ts +337 -0
- package/src/gl/NavMesh.ts +397 -0
- package/src/gl/Node.ts +350 -0
- package/src/gl/Noise.ts +30 -0
- package/src/gl/Particles.ts +676 -0
- package/src/gl/Physics.ts +222 -0
- package/src/gl/Plane.ts +53 -0
- package/src/gl/Ragdoll.ts +270 -0
- package/src/gl/Ray.ts +16 -0
- package/src/gl/Scene.ts +505 -0
- package/src/gl/Shape.ts +409 -0
- package/src/gl/Terrain.ts +1092 -0
- package/src/gl/Texture.ts +63 -0
- package/src/gl/Trigger.ts +45 -0
- package/src/gl/Vehicle.ts +473 -0
- package/src/gl/Wheel.ts +240 -0
- package/src/gl/animation/AnimationClip.ts +204 -0
- package/src/gl/animation/Animator.ts +329 -0
- package/src/gl/animation/Layer.ts +39 -0
- package/src/gl/animation/Loop.ts +31 -0
- package/src/gl/animation/Playback.ts +52 -0
- package/src/gl/animation/core.ts +670 -0
- package/src/gl/controls.ts +95 -0
- package/src/gl/physicsEvents.ts +20 -0
- package/src/gl/scenarios.ts +291 -0
- package/src/gl/state.ts +6 -0
- package/src/gl/terrainMesh.ts +219 -0
- package/src/gl/touch.ts +68 -0
- package/src/host.d.ts +61 -0
- package/src/inject.ts +203 -0
- package/src/math/Mathf.ts +118 -0
- package/src/math/mat4.ts +278 -0
- package/src/math/quat.ts +232 -0
- package/src/math/vec.ts +255 -0
- package/src/net/codec.ts +119 -0
- package/src/net/core.ts +384 -0
- package/src/net/index.ts +181 -0
- package/src/net/replication.ts +622 -0
- package/src/plugins/camera.ts +81 -0
- package/src/plugins/geolocation.ts +123 -0
- package/src/plugins/oauth.ts +61 -0
- package/src/plugins/permission.ts +7 -0
- package/src/plugins/push.ts +132 -0
- package/src/plugins/qr.ts +73 -0
- package/src/plugins/service.ts +47 -0
- package/src/runtime/app.ts +101 -0
- package/src/runtime/appEvents.ts +54 -0
- package/src/runtime/channel.ts +61 -0
- package/src/runtime/clipboard.ts +20 -0
- package/src/runtime/datetime.ts +329 -0
- package/src/runtime/device.ts +293 -0
- package/src/runtime/fetch.ts +77 -0
- package/src/runtime/files.ts +108 -0
- package/src/runtime/input.ts +175 -0
- package/src/runtime/media.ts +111 -0
- package/src/runtime/misc.ts +16 -0
- package/src/runtime/net.ts +36 -0
- package/src/runtime/rpc.ts +218 -0
- package/src/runtime/service.ts +83 -0
- package/src/runtime/share.ts +9 -0
- package/src/runtime/storage.ts +13 -0
- package/src/runtime/touch.ts +76 -0
- package/src/scene/defineScene.ts +1396 -0
- package/src/scene/editorPlugins.ts +110 -0
- package/src/scene/gizmos.ts +148 -0
- package/src/scene/grammar.ts +120 -0
- package/src/scene/level.ts +296 -0
- package/src/scene/material.ts +188 -0
- package/src/server/auth/appConfig.ts +12 -0
- package/src/server/auth/global.ts +80 -0
- package/src/server/auth/host.ts +318 -0
- package/src/server/auth/models.ts +83 -0
- package/src/server/auth/types.ts +50 -0
- package/src/server/channel.ts +56 -0
- package/src/server/context.ts +36 -0
- package/src/server/db/defineDb.ts +237 -0
- package/src/server/db/fields.ts +132 -0
- package/src/server/db/httpTransport.ts +93 -0
- package/src/server/db/index.ts +7 -0
- package/src/server/db/marci/query.ts +412 -0
- package/src/server/db/types.ts +202 -0
- package/src/server/errors.ts +12 -0
- package/src/server/host.ts +74 -0
- package/src/server/inject.ts +13 -0
- package/src/server/runtime.ts +133 -0
- package/src/server/validate.ts +87 -0
- package/src/ui/NativeView.ts +142 -0
- package/src/ui/UI.ts +39 -0
- package/src/ui/UIBottomSheet.ts +139 -0
- package/src/ui/UIButton.ts +101 -0
- package/src/ui/UIContainer.ts +60 -0
- package/src/ui/UIImage.ts +83 -0
- package/src/ui/UIInput.ts +185 -0
- package/src/ui/UIModal.ts +139 -0
- package/src/ui/UINode.ts +830 -0
- package/src/ui/UIPager.ts +362 -0
- package/src/ui/UIPopover.ts +100 -0
- package/src/ui/UIScreen.ts +123 -0
- package/src/ui/UIScrollable.ts +87 -0
- package/src/ui/UISpacer.ts +14 -0
- package/src/ui/UITabs.ts +236 -0
- package/src/ui/UIText.ts +51 -0
- package/src/ui/UIVideo.ts +88 -0
- package/src/ui/UIVirtualizedList.ts +241 -0
- package/src/ui/UIWidget.ts +127 -0
- package/src/ui/fonts.ts +13 -0
- package/src/ui/presentable.ts +117 -0
- package/src/ui/router.ts +132 -0
- package/src/ui/theme.ts +84 -0
package/src/ui/UINode.ts
ADDED
|
@@ -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
|
+
}
|