lecodes-sdk 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/editor.d.ts +12 -0
- package/dist/global.d.ts +4 -8
- package/dist/host.d.ts +2 -3
- package/dist/types/animate/tween/Animation.d.ts +0 -3
- package/dist/types/animate/tween/animateValue.d.ts +4 -2
- package/dist/types/animate/tween/easing.d.ts +8 -0
- package/dist/types/animate/tween/spec.d.ts +15 -7
- package/dist/types/audio/audio.d.ts +2 -1
- package/dist/types/canvas/Canvas.d.ts +40 -108
- package/dist/types/canvas/gen/cssColor.d.ts +17 -0
- package/dist/types/canvas/gen/recorder.d.ts +118 -0
- package/dist/types/canvas/gen/spec.d.ts +144 -0
- package/dist/types/core/color.d.ts +3 -1
- package/dist/types/core/pins.d.ts +18 -0
- package/dist/types/g2/Node2D.d.ts +5 -8
- package/dist/types/g2/Scene2D.d.ts +6 -2
- package/dist/types/gl/Foliage.d.ts +30 -7
- package/dist/types/gl/Light.d.ts +8 -0
- package/dist/types/gl/Lightmap.d.ts +13 -2
- package/dist/types/gl/Material.d.ts +17 -3
- package/dist/types/gl/Model.d.ts +6 -2
- package/dist/types/gl/Node.d.ts +3 -6
- package/dist/types/gl/Scene.d.ts +41 -13
- package/dist/types/gl/Texture.d.ts +1 -1
- package/dist/types/gl/animation/Locomotion.d.ts +8 -1
- package/dist/types/inject.d.ts +11 -11
- package/dist/types/inject.editor.d.ts +1 -0
- package/dist/types/net/core.d.ts +7 -0
- package/dist/types/plugin.d.ts +76 -0
- package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
- package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
- package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
- package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
- package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
- package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
- package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
- package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
- package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
- package/dist/types/runtime/app.d.ts +9 -2
- package/dist/types/runtime/fetch.d.ts +2 -0
- package/dist/types/runtime/input.d.ts +1 -1
- package/dist/types/runtime/media.d.ts +6 -10
- package/dist/types/runtime/misc.d.ts +4 -1
- package/dist/types/runtime/net.d.ts +3 -2
- package/dist/types/runtime/touch.d.ts +32 -0
- package/dist/types/scene/defineScene.d.ts +43 -2
- package/dist/types/scene/editor.d.ts +52 -0
- package/dist/types/scene/gizmos.d.ts +7 -4
- package/dist/types/ui/NativeView.d.ts +6 -4
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UIBottomSheet.d.ts +6 -12
- package/dist/types/ui/UIButton.d.ts +12 -14
- package/dist/types/ui/UIContainer.d.ts +0 -6
- package/dist/types/ui/UIImage.d.ts +1 -4
- package/dist/types/ui/UIInput.d.ts +8 -24
- package/dist/types/ui/UIModal.d.ts +0 -2
- package/dist/types/ui/UINode.d.ts +70 -56
- package/dist/types/ui/UIPager.d.ts +28 -27
- package/dist/types/ui/UIPopover.d.ts +0 -2
- package/dist/types/ui/UIScreen.d.ts +12 -17
- package/dist/types/ui/UIScrollable.d.ts +1 -4
- package/dist/types/ui/UIText.d.ts +0 -2
- package/dist/types/ui/UIVideo.d.ts +3 -5
- package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
- package/dist/types/ui/UIWidget.d.ts +10 -9
- package/dist/types/ui/colorKeys.gen.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +46 -32
- package/dist/types/ui/router.d.ts +18 -7
- package/dist/types/ui/styleColor.d.ts +1 -0
- package/dist/types/ui/transitions.d.ts +18 -0
- package/dist/types/ui/tree.d.ts +75 -0
- package/dist/types/version.d.ts +10 -0
- package/dist/types.json +1 -1
- package/package.json +12 -3
- package/prompts/2d.md +2 -6
- package/prompts/3d.md +1 -5
- package/prompts/README.md +1 -1
- package/prompts/canvas.md +9 -8
- package/prompts/compose.ts +1 -1
- package/prompts/core.md +3 -3
- package/prompts/design.md +1 -1
- package/prompts/dist/2d-game.md +473 -239
- package/prompts/dist/3d-app.md +553 -205
- package/prompts/dist/ar-app.md +435 -202
- package/prompts/dist/design.md +113 -95
- package/prompts/dist/ui-app.md +386 -170
- package/prompts/ui-design.md +2 -3
- package/prompts/ui.md +45 -37
- package/src/animate/tween/Animation.ts +34 -150
- package/src/animate/tween/Timeline.ts +175 -175
- package/src/animate/tween/animateValue.ts +6 -3
- package/src/animate/tween/easing.ts +10 -3
- package/src/animate/tween/spec.ts +41 -15
- package/src/audio/Sound.ts +3 -3
- package/src/audio/audio.ts +2 -1
- package/src/bridges/2d.d.ts +317 -0
- package/src/bridges/app.d.ts +91 -0
- package/src/bridges/audio.d.ts +97 -0
- package/src/bridges/canvas.d.ts +79 -0
- package/src/bridges/device.d.ts +72 -0
- package/src/bridges/fetch.d.ts +80 -0
- package/src/bridges/files.d.ts +70 -0
- package/src/bridges/gl.d.ts +1133 -0
- package/src/bridges/input.d.ts +72 -0
- package/src/bridges/media.d.ts +55 -0
- package/src/bridges/nav.d.ts +71 -0
- package/src/bridges/net.d.ts +52 -0
- package/src/bridges/service.d.ts +52 -0
- package/src/bridges/socket.d.ts +31 -0
- package/src/bridges/storage.d.ts +33 -0
- package/src/bridges/tree.d.ts +301 -0
- package/src/bridges/types.d.ts +49 -0
- package/src/canvas/Canvas.ts +114 -159
- package/src/canvas/gen/cssColor.ts +224 -0
- package/src/canvas/gen/recorder.ts +212 -0
- package/src/canvas/gen/spec.ts +201 -0
- package/src/chisel.ts +193 -0
- package/src/compile/assetMacro.ts +1 -1
- package/src/compile/bundler.ts +11 -2
- package/src/compile/compileProject.ts +43 -4
- package/src/compile/fontMacro.ts +3 -4
- package/src/compile/header.ts +26 -5
- package/src/compile/index.ts +3 -1
- package/src/compile/liteMaterial.ts +1 -1
- package/src/compile/sceneEditor.ts +11 -26
- package/src/core/color.ts +73 -30
- package/src/core/pins.ts +51 -0
- package/src/core/signals.ts +8 -1
- package/src/g2/CharacterController2D.ts +3 -3
- package/src/g2/Node2D.ts +57 -39
- package/src/g2/Physics2D.ts +2 -2
- package/src/g2/Scene2D.ts +35 -23
- package/src/g2/Texture2D.ts +1 -1
- package/src/g2/loop.ts +4 -4
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/Foliage.ts +72 -17
- package/src/gl/Geometry.ts +1 -2
- package/src/gl/Light.ts +10 -0
- package/src/gl/Lightmap.ts +45 -29
- package/src/gl/Material.ts +95 -51
- package/src/gl/Mesh.ts +120 -120
- package/src/gl/Model.ts +21 -16
- package/src/gl/Node.ts +91 -24
- package/src/gl/Particles.ts +1 -1
- package/src/gl/Scene.ts +100 -44
- package/src/gl/Texture.ts +8 -7
- package/src/gl/animation/AnimationClip.ts +1 -1
- package/src/gl/animation/DynamicBone.ts +482 -482
- package/src/gl/animation/Locomotion.ts +8 -3
- package/src/gl/nav/NavMesh.ts +3 -4
- package/src/gl/physics/Physics.ts +2 -2
- package/src/gl/physics/physicsEvents.ts +3 -3
- package/src/gl/scenarios.ts +291 -291
- package/src/gl/terrain/Terrain.ts +4 -5
- package/src/gl/touch.ts +14 -15
- package/src/host.d.ts +2 -3
- package/src/inject.editor.ts +7 -0
- package/src/inject.ts +13 -16
- package/src/net/core.ts +6 -5
- package/src/net/index.ts +1 -1
- package/src/net/replication.ts +1 -1
- package/src/plugin.ts +191 -0
- package/src/plugins/gen/camera/contract.d.ts +27 -0
- package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
- package/src/plugins/gen/camera/sdk/camera.ts +57 -0
- package/src/plugins/gen/geolocation/contract.d.ts +50 -0
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
- package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
- package/src/plugins/gen/map/contract.d.ts +144 -0
- package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
- package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
- package/src/plugins/gen/push/contract.d.ts +61 -0
- package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
- package/src/plugins/gen/push/sdk/push.ts +105 -0
- package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
- package/src/plugins/permission.ts +5 -4
- package/src/runtime/app.ts +20 -9
- package/src/runtime/appEvents.ts +5 -4
- package/src/runtime/channel.ts +18 -15
- package/src/runtime/clipboard.ts +4 -3
- package/src/runtime/datetime.ts +2 -1
- package/src/runtime/device.ts +17 -15
- package/src/runtime/fetch.ts +30 -20
- package/src/runtime/files.ts +16 -15
- package/src/runtime/input.ts +12 -10
- package/src/runtime/media.ts +50 -46
- package/src/runtime/misc.ts +7 -3
- package/src/runtime/net.ts +8 -7
- package/src/runtime/rpc.ts +1 -3
- package/src/runtime/service.ts +19 -14
- package/src/runtime/share.ts +4 -3
- package/src/runtime/storage.ts +6 -4
- package/src/runtime/touch.ts +32 -0
- package/src/scene/defineScene.ts +61 -365
- package/src/scene/editor.ts +408 -0
- package/src/scene/editorPlugins.ts +3 -3
- package/src/scene/gizmos.ts +15 -9
- package/src/server/db/marci/query.ts +1 -1
- package/src/server/host.ts +1 -1
- package/src/server/runtime.ts +1 -1
- package/src/ui/NativeView.ts +72 -25
- package/src/ui/UI.ts +3 -3
- package/src/ui/UIBottomSheet.ts +16 -17
- package/src/ui/UIButton.ts +54 -16
- package/src/ui/UIContainer.ts +0 -6
- package/src/ui/UIImage.ts +34 -30
- package/src/ui/UIInput.ts +29 -37
- package/src/ui/UIModal.ts +1 -3
- package/src/ui/UINode.ts +347 -297
- package/src/ui/UIPager.ts +93 -78
- package/src/ui/UIPopover.ts +0 -2
- package/src/ui/UIScreen.ts +41 -36
- package/src/ui/UIScrollable.ts +19 -13
- package/src/ui/UISpacer.ts +1 -1
- package/src/ui/UITabs.ts +8 -6
- package/src/ui/UIText.ts +7 -17
- package/src/ui/UIVideo.ts +30 -27
- package/src/ui/UIVirtualizedList.ts +58 -59
- package/src/ui/UIWidget.ts +38 -18
- package/src/ui/colorKeys.gen.ts +37 -0
- package/src/ui/fonts.ts +2 -2
- package/src/ui/presentable.ts +59 -42
- package/src/ui/router.ts +52 -35
- package/src/ui/styleColor.ts +56 -0
- package/src/ui/theme.ts +7 -6
- package/src/ui/transitions.ts +249 -0
- package/src/ui/tree.ts +346 -0
- package/src/version.ts +24 -0
- package/tests/helpers/engineWorld.ts +11 -0
- package/tests/helpers/fakeTree.ts +353 -0
- package/tests/helpers/hostStubs.ts +31 -0
- package/tests/helpers/index.ts +14 -0
- package/tests/helpers/memoryMarci.ts +124 -0
- package/tests/helpers/phases.ts +23 -0
- package/tests/helpers/preload.ts +18 -0
- package/tests/helpers/stubApp.ts +2 -0
- package/tests/helpers/stubDevice.ts +2 -0
- package/tests/helpers/stubFetch.ts +2 -0
- package/tests/helpers/stubInput.ts +2 -0
- package/dist/inject.js +0 -4629
- package/dist/types/core/registry.d.ts +0 -7
- package/dist/types/plugins/camera.d.ts +0 -25
- package/dist/types/plugins/push.d.ts +0 -46
- package/src/bridges.d.ts +0 -1769
- package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
- package/src/compile/__tests__/assetMacro.test.ts +0 -100
- package/src/compile/__tests__/assetName.test.ts +0 -55
- package/src/compile/__tests__/compile.test.ts +0 -310
- package/src/compile/__tests__/detectEntry.test.ts +0 -151
- package/src/compile/__tests__/fontMacro.test.ts +0 -199
- package/src/compile/__tests__/serverSplit.test.ts +0 -27
- package/src/core/__tests__/stateMachine.test.ts +0 -132
- package/src/core/registry.ts +0 -23
- package/src/plugins/camera.ts +0 -81
- package/src/plugins/push.ts +0 -132
- package/src/plugins/qr.ts +0 -73
package/prompts/ui-design.md
CHANGED
|
@@ -57,7 +57,7 @@ UIImage(src) // src: string url | SvgSource | imported asset
|
|
|
57
57
|
// UIButton — the only TAPPABLE container: a UIRow with children centered on both axes by default.
|
|
58
58
|
// Anything clickable in the prototype (a card, a list row, an icon) — wrap it in a UIButton.
|
|
59
59
|
UIButton(...children)
|
|
60
|
-
// extra styles:
|
|
60
|
+
// extra styles: $pressed: { bgColor, opacity, ... } — style while the finger is down (press feedback is
|
|
61
61
|
// OPT-IN; set it so the live prototype feels real)
|
|
62
62
|
// Note: buttons render no chrome of their own — style bgColor/borderRadius/padding yourself, and give a
|
|
63
63
|
// button an explicit height (on its own it is only as tall as its text). flexDirection: "column" for cards.
|
|
@@ -109,8 +109,7 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
|
|
|
109
109
|
// Note: calc()/min()/max() do NOT support % ("calc(100% - 20px)" is invalid; use "calc(100vw - 20px)")
|
|
110
110
|
|
|
111
111
|
// --- Colors in UI styles ---
|
|
112
|
-
// hex
|
|
113
|
-
// and exactly these names: white black red green blue yellow orange purple gray cyan magenta brown transparent clear
|
|
112
|
+
// any CSS color (hex, rgb()/hsl(), the CSS names — green = #008000, transparent/clear), 0xRRGGBB, [r,g,b(,a)], "var(--x)"
|
|
114
113
|
|
|
115
114
|
// --- Safe areas ---
|
|
116
115
|
// Env keywords resolving to the device insets (notch, home indicator):
|
package/prompts/ui.md
CHANGED
|
@@ -27,7 +27,7 @@ UIScreen(...children)
|
|
|
27
27
|
// popping back fires onOpen again. Anything started in onOpen (loops, intervals, sockets) MUST be stopped
|
|
28
28
|
// in onClose (see TIMERS & FRAME LOOP above).
|
|
29
29
|
// .onTouchStart(cb) — fires for touches anywhere on the screen; use for full-screen gestures (see TOUCH GESTURES above)
|
|
30
|
-
// .
|
|
30
|
+
// .onBack(cb) — Android hardware/gesture back; typically Router.pop()
|
|
31
31
|
// Screens NEVER scroll — the canonical screen is fixed chrome (header, tab bar) + ONE UIScrollable body
|
|
32
32
|
// with flexGrow: 1: UIScreen(Header(), UIScrollable(content).style({ flexGrow: 1 }))
|
|
33
33
|
// Note: a screen always fills the device — sizing styles on it (width, height, flexGrow, flexShrink, position) are no-ops
|
|
@@ -63,7 +63,7 @@ Router.init(UIScreen(pager, tabBar)) // bar buttons: .onClick(() => pager.sele
|
|
|
63
63
|
// UIWidget — floating overlay, independent of screens, always position: fixed in device coordinates
|
|
64
64
|
// Persists across Router navigation — create ONCE at module scope, reuse; hidden by default.
|
|
65
65
|
UIWidget(...children)
|
|
66
|
-
// .show(), .hide(), .
|
|
66
|
+
// .show(), .hide(), .isShown (getter), .onTouchStart(cb), .onBack(cb)
|
|
67
67
|
// extra style: overlayColor — full-screen scrim BEHIND the widget that blocks taps underneath, turning it into
|
|
68
68
|
// a modal; null (default) = no layer, "transparent" = invisible but still blocks. A scrim tap fires
|
|
69
69
|
// .onOverlayTap(cb) — usually () => widget.hide()
|
|
@@ -88,7 +88,7 @@ UIModal(...children)
|
|
|
88
88
|
UIBottomSheet(...children)
|
|
89
89
|
// .detents([0.3, 0.6, 1]) — snap positions, ascending fractions of screen height (the map-app model). Call
|
|
90
90
|
// BEFORE show(); sizes the sheet to the HIGHEST detent — lower detents show the top slice of the content.
|
|
91
|
-
// .setDetent(i) (animated), .detent (getter), .
|
|
91
|
+
// .setDetent(i) (animated), .detent (getter), .onDetent(cb(i)) — every settle: finger snap or setDetent()
|
|
92
92
|
// .show()/.hide() slide in/out — plus the full UIModal surface (scrim, onOpen/onClose, dismissible).
|
|
93
93
|
// Dragging below the lowest detent closes it (fires onClose); .dismissible(false) collapses there instead —
|
|
94
94
|
// the persistent map sheet (pair it with overlayColor: null so the page behind stays interactive).
|
|
@@ -174,11 +174,12 @@ player.play() // stop playback in the screen's onClose; .dispose() when gone f
|
|
|
174
174
|
// UIButton — the only TAPPABLE container: a UIRow with children centered on both axes by default.
|
|
175
175
|
// To make anything clickable (a card, a list row, an icon) — wrap it in a UIButton.
|
|
176
176
|
UIButton(...children)
|
|
177
|
-
// .onClick(cb), .onTouchStart(cb), .isPressed() // gestures: see TOUCH GESTURES above
|
|
178
|
-
//
|
|
177
|
+
// .onClick(cb), .onTouchStart(cb), .isPressed (getter) // gestures: see TOUCH GESTURES above
|
|
178
|
+
// .onMouseEnter(ev => ev.track({ onMove?, onEnd?, onCancel? })), .isHovered (getter) // mouse only; no leave event — onEnd is the leave
|
|
179
|
+
// extra styles: $pressed: { bgColor, opacity, ..., duration? } — style while the finger is down (reserved class);
|
|
179
180
|
// rippleColor ("default" or a Color, Android only)
|
|
180
181
|
// Note: press feedback is OPT-IN — nothing happens visually unless you set it. rippleColor replaces the
|
|
181
|
-
//
|
|
182
|
+
// $pressed visual on Android, so rippleColor + $pressed = ripple on Android, $pressed dim on iOS.
|
|
182
183
|
// Note: buttons render no chrome of their own — style bgColor/borderRadius/padding yourself, and give a
|
|
183
184
|
// button an explicit height (on its own it is only as tall as its text). flexDirection: "column" for cards.
|
|
184
185
|
|
|
@@ -192,7 +193,7 @@ UIInput() / UITextArea()
|
|
|
192
193
|
// maxLength: number; autocapitalize: "none"|"words"|"sentences"|"characters"; autocorrect: boolean
|
|
193
194
|
// keyboardShrink: boolean (default true) — false: keyboard OVERLAYS the UI, no relayout (chat composers)
|
|
194
195
|
// keyboardDismiss: boolean (default true) — false: taps never dismiss the keyboard (chat composers)
|
|
195
|
-
//
|
|
196
|
+
// $focused: { borderColor, ..., duration? } — style while focused (reserved class)
|
|
196
197
|
// Note: type values are KEYBOARD HINTS, not validators — "number" doesn't block pasted letters. The
|
|
197
198
|
// phone-pad value is "phone" (there is NO "tel").
|
|
198
199
|
// Note: "date"/"time" open the native PICKER in the keyboard slot (typing disabled). .value and onChange stay
|
|
@@ -240,7 +241,7 @@ UIText("Hi").style({ color: "white" }).style({ fontSize: 20 }).onClick(...)
|
|
|
240
241
|
// Older code may pass a style object as the FIRST factory argument — legacy; write new styles with .style().
|
|
241
242
|
|
|
242
243
|
// Mutable CONTENT properties are NOT styles — they live on the element:
|
|
243
|
-
// UIText: .text UIInput/UITextArea: .value UIImage: .src UIButton: .isPressed()
|
|
244
|
+
// UIText: .text UIInput/UITextArea: .value UIImage: .src UIButton: .isPressed (read-only)
|
|
244
245
|
myText.text = "Updated" // re-renders immediately
|
|
245
246
|
|
|
246
247
|
// Style<T> is a global helper type for reusable style objects, no import needed:
|
|
@@ -278,20 +279,19 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
|
|
|
278
279
|
// but do NOT support % ("calc(100% - 20px)" is invalid; use "calc(100vw - 20px)")
|
|
279
280
|
|
|
280
281
|
// --- Colors in UI styles ---
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
// (UI styles only — engine APIs are hex/packed-int only, see COLORS above)
|
|
282
|
+
// any CSS color (hex, rgb()/hsl(), the CSS names — green = #008000, transparent/clear), 0xRRGGBB, [r,g,b(,a)],
|
|
283
|
+
// or "var(--x)" — the same colors as every engine API (see COLORS above); a non-color throws
|
|
284
284
|
|
|
285
285
|
// --- Safe areas & comfort ---
|
|
286
286
|
// Env keywords resolving to the device insets (notch, home indicator):
|
|
287
287
|
// "safe-top" | "safe-bottom" | "safe-left" | "safe-right"; "safe-all" — p / m shorthands only
|
|
288
288
|
// Comfort tokens — where content comfortably starts, never collapse to 0: max(safe-edge, knob) on clearance
|
|
289
|
-
// insets (iOS notch/home indicator
|
|
290
|
-
// knob ADDS instead. Knob defaults: 12 top,
|
|
289
|
+
// insets (iOS notch/home indicator); past exact-height system BARS (Android status/nav bar) the
|
|
290
|
+
// knob ADDS instead. Knob defaults: 12 top, 8 bottom, 16 horizontal (the page gutter):
|
|
291
291
|
// "comfort-top" | "comfort-bottom" | "comfort-left" | "comfort-right"
|
|
292
292
|
// "comfort-x" — px/mx | "comfort-y" — py/my | "comfort-all" — p / m
|
|
293
293
|
// Accepted on per-side padding & margin, position offsets (top/left/bottom/right), and inside calc()/min()/max():
|
|
294
|
-
.style({ pb: "comfort-bottom" }) // bottom bar: 34 over a home indicator,
|
|
294
|
+
.style({ pb: "comfort-bottom" }) // bottom bar: 34 over a home indicator, 56 above Android's button bar, 8 on inset-less devices
|
|
295
295
|
.style({ px: "comfort-x" }) // page gutter that also clears the notch in landscape
|
|
296
296
|
.style({ pb: "calc(comfort-bottom + 56px)" }) // scroll content clearing a 56px floating bar
|
|
297
297
|
|
|
@@ -306,14 +306,9 @@ label.style({ color: T.primaryColor, pl: "var(--comfort-left)" })
|
|
|
306
306
|
// Bare comfort-* tokens apply the safe-area formula; "var(--comfort-top)" reads the raw knob.
|
|
307
307
|
// "var(--name, fallback)" applies the fallback while the key is unset. Env names (safe-*, vw/vh) are not theme keys.
|
|
308
308
|
|
|
309
|
-
// ---
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
// Keys: onLandscape, onPortrait.
|
|
313
|
-
|
|
314
|
-
// --- Style classes ($name) — named states that CASCADE ---
|
|
315
|
-
// A $-prefixed key in .style() declares a named style state (like onPressed, but yours: selected,
|
|
316
|
-
// checked, expanded); duration/delay inside the block animate the swap. Drive it via el.class:
|
|
309
|
+
// --- Style classes ($name) — the ONLY state mechanism; they CASCADE ---
|
|
310
|
+
// A $-prefixed key in .style() declares a style state (yours: selected, checked, expanded);
|
|
311
|
+
// duration/delay/easing inside the block animate the swap. Drive yours via el.class:
|
|
317
312
|
const item = UIButton(UIText("Wi-Fi")).style({ bgColor: "#151515", $selected: { bgColor: "#1d2b45", duration: 150 } })
|
|
318
313
|
item.onClick(ev => ev.target.class.selected = !ev.target.class.selected)
|
|
319
314
|
// el.class.selected reads/writes a boolean; el.class({ a: true, b: false }) is the chainable batch
|
|
@@ -324,11 +319,13 @@ UIButton(
|
|
|
324
319
|
UIImage(icon).style({ tintColor: "#888", $selected: { tintColor: "#5b8cff" } }),
|
|
325
320
|
UIText("Label").style({ color: "#888", $selected: { color: "#5b8cff" } }),
|
|
326
321
|
) // .class.selected = true → icon AND label restyle (the cascade stops at hosted screens/widgets)
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
322
|
+
// RESERVED — the system toggles them, el.class.pressed = … throws:
|
|
323
|
+
// $hovered / $pressed / $focused (mouse over / finger down / input focused) cascade to children but STOP at
|
|
324
|
+
// a nested UIButton — a button inside a pressed card is not pressed;
|
|
325
|
+
// $landscape / $portrait — GLOBAL, from the display size (the swap on rotation is instant).
|
|
326
|
+
UIButton(UIText("Buy").style({ $pressed: { color: "#999" } })).style({ $hovered: { opacity: 0.9 }, $pressed: { transform: "scale(0.97)" } })
|
|
327
|
+
screen.style({ flexDirection: "column", p: 16, $landscape: { flexDirection: "row", p: 32 } })
|
|
328
|
+
// Precedence on the same prop: base < $landscape/$portrait < $classes < $hovered < $pressed < $focused.
|
|
332
329
|
|
|
333
330
|
// --- Defaults that surprise ---
|
|
334
331
|
// flexShrink: 0 — elements don't shrink to fit (exception: UIScrollable, UIVirtualizedList and UIPager
|
|
@@ -411,14 +408,25 @@ UIButton().onLayout(({ width }) => { buttonWidth = width })
|
|
|
411
408
|
// ===== ROUTER (multi-page apps) =====
|
|
412
409
|
// Tabs are UITabs' job, in-tab stacks UIPager's (see UI COMPONENTS) — the Router owns what sits ABOVE the shell.
|
|
413
410
|
Router.init(homeScreen, opts?: { showDefaultBackButton?: boolean }) // call once in the entry file (default false)
|
|
414
|
-
Router.push(screen)
|
|
415
|
-
Router.pop(to?: number
|
|
411
|
+
Router.push(screen, opts?: { transition, popTransition }) // push onto the stack, screen becomes active (default "push")
|
|
412
|
+
Router.pop(to?: number, opts?: { transition })
|
|
413
|
+
// default -1 = one back; negative = relative (-2 = back two), 0/positive = absolute
|
|
416
414
|
// stack index (0 = home). Popped screens' native trees are destroyed; the UIScreen
|
|
417
|
-
// object stays reusable (push remounts it).
|
|
418
|
-
Router.replace(screen, opts?: { transition }) // swap the top screen
|
|
419
|
-
// "slide-from-right" | "slide-from-top" | "slide-from-bottom" | "zoom" | "zoom-out" | "zoom-in" | "fade" | "none" (default "fade")
|
|
415
|
+
// object stays reusable (push remounts it). Plays the way back the screen remembers.
|
|
416
|
+
Router.replace(screen, opts?: { transition, popTransition }) // swap the top screen (default "none"; theme({ replaceTransition }))
|
|
420
417
|
Router.current // the active UIScreen (getter)
|
|
421
|
-
Router.hide() / Router.restore() // hide/restore the whole router — stack stays alive; fires the top screen's onClose/onOpen
|
|
418
|
+
Router.hide(opts?) / Router.restore(opts?) // hide/restore the whole router — stack stays alive; fires the top screen's onClose/onOpen
|
|
419
|
+
// TRANSITIONS — every call above, and open() / close() of any destination, takes { transition }:
|
|
420
|
+
// a name: "push" | "pop" | "slide-from-left" | "slide-from-right" | "slide-from-top" | "slide-from-bottom" |
|
|
421
|
+
// "zoom" | "zoom-in" | "zoom-out" | "fade" | "none"
|
|
422
|
+
// or your own: { enter?: pose, exit?: pose, onTop?: "enter" | "exit" } — the incoming screen comes FROM `enter`,
|
|
423
|
+
// the outgoing one goes TO `exit`. pose = { transform?, opacity?, dim?, duration? (ms, 300), delay?, easing? };
|
|
424
|
+
// arrays are keyframes; in a pose a % in translate is a percent of the screen ("translateY(100%)").
|
|
425
|
+
// e.g. Router.push(page, { transition: { enter: { transform: "translateY(100%)", duration: 400 }, exit: { dim: 0.3 } } })
|
|
426
|
+
// popTransition = the way BACK the screen remembers (Router.pop(), the back gesture, the system's back play it).
|
|
427
|
+
// Default: the transition it came with, the other way round (a slide from the bottom leaves downwards;
|
|
428
|
+
// a zoom / a fade go back with a fade). A replace without one keeps the way back of the screen it replaces.
|
|
429
|
+
// A screen's own transform / opacity are not seen while a transition moves it — style a child instead.
|
|
422
430
|
Router.addEventListener("change", (screen: UIScreen) => ...) / .removeEventListener("change", cb) // after every navigation
|
|
423
431
|
// Screens BELOW the top stay mounted — element trees and state survive, they're just not rendered.
|
|
424
432
|
// push fires the outgoing screen's onClose + the incoming one's onOpen; pop fires them in reverse and
|
|
@@ -622,7 +630,7 @@ const detailScreen = (name: string) => UIScreen(
|
|
|
622
630
|
).style({ p: 16, pt: "comfort-top", gap: 16, bgColor: "black" })
|
|
623
631
|
|
|
624
632
|
const Item = (name: string) => UIButton(UIText(name).style({ color: "white" }))
|
|
625
|
-
.style({ height: 52, px: 16, justifyContent: "flex-start", borderRadius: 12, bgColor: "#151515",
|
|
633
|
+
.style({ height: 52, px: 16, justifyContent: "flex-start", borderRadius: 12, bgColor: "#151515", $pressed: { opacity: 0.7 } })
|
|
626
634
|
.onClick(() => UIPager.push(detailScreen(name))) // in-tab push: tab bar stays, back-swipe pops
|
|
627
635
|
|
|
628
636
|
export const homeScreen = UIScreen(
|
|
@@ -658,7 +666,7 @@ Router.init(tabs)
|
|
|
658
666
|
const Field = (label: string, style: Style<UIInput> = {}) => {
|
|
659
667
|
const input = UIInput().style({ height: 50, px: 14, borderRadius: 12, bgColor: "#161A22",
|
|
660
668
|
border: "1px solid #262C3A", color: "white", placeholderColor: "#5A6272",
|
|
661
|
-
|
|
669
|
+
$focused: { borderColor: "#4C8DFF" }, ...style })
|
|
662
670
|
const message = UIText("").style({ fontSize: 13, minHeight: 18, color: "#FF6B6B" })
|
|
663
671
|
input.onChange(() => message.text = "")
|
|
664
672
|
return {
|
|
@@ -709,7 +717,7 @@ const screen = UIScreen(
|
|
|
709
717
|
UIText("Create account").style({ fontSize: 28, fontWeight: 700, mb: 12 }),
|
|
710
718
|
name.node, email.node, password.node,
|
|
711
719
|
UIButton(btnLabel = UIText("Create account").style({ fontSize: 16, fontWeight: 700 }))
|
|
712
|
-
.style({ name: "submit", height: 52, borderRadius: 14, bgColor: "#4C8DFF", mt: 8,
|
|
720
|
+
.style({ name: "submit", height: 52, borderRadius: 14, bgColor: "#4C8DFF", mt: 8, $pressed: { opacity: 0.85 } })
|
|
713
721
|
.onClick(() => submit())
|
|
714
722
|
).style({ flexGrow: 1, px: 20, pt: "comfort-top", pb: 28, gap: 8 })
|
|
715
723
|
).style({ bgColor: "#0C0F14" })
|
|
@@ -739,7 +747,7 @@ const sheet = UIBottomSheet(
|
|
|
739
747
|
.style({ bgColor: "white", borderRadius: 20, overlayColor: null }) // no scrim — the map stays interactive
|
|
740
748
|
.detents([0.25, 0.6, 1]) // collapsed / half / full
|
|
741
749
|
.dismissible(false) // drag below the lowest detent collapses, never closes
|
|
742
|
-
.
|
|
750
|
+
.onDetent(i => console.log("detent", i))
|
|
743
751
|
|
|
744
752
|
const mapScreen = UIScreen(
|
|
745
753
|
// the map / page content behind the sheet
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// The animation HANDLE (docs/timeline-plan.md §2.2): what `animateTo` / `animateFrom` / `Timeline`
|
|
2
2
|
// return. Owns one spec; every `play()` builds the blob (target ids resolve at play time), hands it
|
|
3
|
-
// to the host (`
|
|
3
|
+
// to the host (`_creatorTree.tweenCreate`) and gets events back (`registerTweenEvent`: time-callbacks
|
|
4
4
|
// and finish). On a host without the tween core the UI tracks fall back to the old
|
|
5
5
|
// `animateTo` / `animateFrom` bridge pair (single-value tweens only) and the clock is a timer —
|
|
6
6
|
// see `fallback` below.
|
|
7
7
|
|
|
8
8
|
import { CLOCK_GAME, CLOCK_UI, DOM_UI, DOM_VALUE, buildBlob, evaluateTrack, type Track, type TweenSpec } from "./spec"
|
|
9
|
+
import { DEAD_HANDLE, Pins } from "../../core/pins"
|
|
9
10
|
|
|
10
11
|
const OP_PLAY = 0
|
|
11
12
|
const OP_PAUSE = 1
|
|
@@ -43,17 +44,17 @@ export interface Animation {
|
|
|
43
44
|
onFinish(fn: (done: boolean) => void): this
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
const hasCore = (): boolean => typeof _creatorUI !== "undefined" && typeof (_creatorUI as any).tweenCreate === "function"
|
|
47
47
|
|
|
48
48
|
// ---- host events -------------------------------------------------------------------------------
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
/** The animations native owns right now (playing), by id — what the host's events resolve to. */
|
|
51
|
+
const tweens = new Pins<TweenAnimation>()
|
|
51
52
|
let eventsRegistered = false
|
|
52
53
|
const ensureEvents = (): void => {
|
|
53
54
|
if (eventsRegistered) return
|
|
54
55
|
eventsRegistered = true
|
|
55
|
-
|
|
56
|
-
const a =
|
|
56
|
+
_creatorTree.registerTweenEvent(function (this: unknown, id: number, kind: number, index: number) {
|
|
57
|
+
const a = tweens.get(id)
|
|
57
58
|
if (!a) return
|
|
58
59
|
if (kind === EV_CALL) a._fireCall(index)
|
|
59
60
|
else if (kind === EV_FINISH) a._hostFinished()
|
|
@@ -81,7 +82,8 @@ export class TweenAnimation implements Animation {
|
|
|
81
82
|
/** @internal VALUE-track deliverers by slot (`animate()` / `Timeline.animate`). */
|
|
82
83
|
_values = new Map<number, (lanes: Float32Array) => void>()
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
/** @internal The host animation's owned handle (DEAD_HANDLE while there is none). */
|
|
86
|
+
_h: Handle = DEAD_HANDLE
|
|
85
87
|
private _started = false // play() ran at least once (the first play keeps the constructor's promise)
|
|
86
88
|
private _endTime = 0 // `time` after the host freed a finished animation
|
|
87
89
|
private _playing = false
|
|
@@ -90,7 +92,6 @@ export class TweenAnimation implements Animation {
|
|
|
90
92
|
private _resolve!: (done: boolean) => void
|
|
91
93
|
private _settled = true
|
|
92
94
|
private _listeners: Array<(done: boolean) => void> = []
|
|
93
|
-
private _fb?: Fallback
|
|
94
95
|
|
|
95
96
|
constructor(spec: TweenSpec) {
|
|
96
97
|
this._spec = spec
|
|
@@ -118,13 +119,12 @@ export class TweenAnimation implements Animation {
|
|
|
118
119
|
get rate(): number { return this._rate }
|
|
119
120
|
set rate(r: number) {
|
|
120
121
|
this._rate = r
|
|
121
|
-
if (this.
|
|
122
|
-
this._fb?.setRate(r)
|
|
122
|
+
if (this._h.id) _creatorTree.tweenSetRate(this._h.id, r)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
get time(): number {
|
|
126
|
-
if (this.
|
|
127
|
-
return this.
|
|
126
|
+
if (this._h.id) return _creatorTree.tweenGetTime(this._h.id)
|
|
127
|
+
return this._endTime
|
|
128
128
|
}
|
|
129
129
|
set time(ms: number) { this.seek(ms) }
|
|
130
130
|
|
|
@@ -143,36 +143,29 @@ export class TweenAnimation implements Animation {
|
|
|
143
143
|
return s.durationMs * n * (s.pingPong && s.iterations !== 1 ? 2 : 1)
|
|
144
144
|
}
|
|
145
145
|
private _elapsedMs(): number {
|
|
146
|
-
if (this.
|
|
147
|
-
return this.
|
|
146
|
+
if (this._h.id) return _creatorTree.tweenGetElapsed(this._h.id)
|
|
147
|
+
return this._endTime > 0 ? this._totalMs() : 0
|
|
148
148
|
}
|
|
149
149
|
private _seekTotal(ms: number): void {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
;(_creatorUI as any).tweenSeek(this._id, ms)
|
|
153
|
-
} else {
|
|
154
|
-
this._ensureFallback().seek(ms)
|
|
155
|
-
}
|
|
150
|
+
this._ensure()
|
|
151
|
+
_creatorTree.tweenSeek(this._h.id, ms)
|
|
156
152
|
}
|
|
157
153
|
|
|
158
154
|
/** Create the host animation (paused at t = 0) if it doesn't exist. */
|
|
159
155
|
private _ensure(): void {
|
|
160
|
-
if (this.
|
|
156
|
+
if (this._h.id) return
|
|
161
157
|
ensureEvents()
|
|
162
158
|
const blob = buildBlob(this._spec)
|
|
163
|
-
this.
|
|
164
|
-
if (this.
|
|
165
|
-
if (this._rate !== 1) (_creatorUI as any).tweenSetRate(this._id, this._rate)
|
|
159
|
+
this._h = _creatorTree.tweenCreate(blob.data, blob.strings, blob.targets as number[]) ?? DEAD_HANDLE
|
|
160
|
+
if (this._h.id && this._rate !== 1) _creatorTree.tweenSetRate(this._h.id, this._rate)
|
|
166
161
|
}
|
|
167
162
|
|
|
168
163
|
private _destroy(): void {
|
|
169
|
-
if (this.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
this.
|
|
164
|
+
if (this._h.id) {
|
|
165
|
+
tweens.unpin(this._h.id)
|
|
166
|
+
_creatorTree.tweenControl(this._h.id, OP_CANCEL) // the runtime frees it; drop the handle too
|
|
167
|
+
this._h = DEAD_HANDLE
|
|
173
168
|
}
|
|
174
|
-
this._fb?.cancel()
|
|
175
|
-
this._fb = undefined
|
|
176
169
|
}
|
|
177
170
|
|
|
178
171
|
play(): this {
|
|
@@ -195,27 +188,26 @@ export class TweenAnimation implements Animation {
|
|
|
195
188
|
if (last && last.value !== undefined) t.channel.commit(last.raw)
|
|
196
189
|
}
|
|
197
190
|
this._playing = true
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
} else {
|
|
202
|
-
this._ensureFallback().play()
|
|
203
|
-
}
|
|
191
|
+
this._ensure()
|
|
192
|
+
_creatorTree.tweenControl(this._h.id, OP_PLAY)
|
|
193
|
+
tweens.pin(this) // playing: native owns it, its events must find it
|
|
204
194
|
return this
|
|
205
195
|
}
|
|
206
196
|
|
|
207
197
|
pause(): this {
|
|
208
|
-
|
|
209
|
-
|
|
198
|
+
this._ensure()
|
|
199
|
+
_creatorTree.tweenControl(this._h.id, OP_PAUSE)
|
|
210
200
|
this._playing = false
|
|
201
|
+
tweens.unpin(this._h.id) // paused: only the app's reference keeps it
|
|
211
202
|
return this
|
|
212
203
|
}
|
|
213
204
|
|
|
214
205
|
resume(): this {
|
|
215
206
|
if (this._settled) return this.play()
|
|
216
|
-
|
|
217
|
-
|
|
207
|
+
this._ensure()
|
|
208
|
+
_creatorTree.tweenControl(this._h.id, OP_RESUME)
|
|
218
209
|
this._playing = true
|
|
210
|
+
tweens.pin(this)
|
|
219
211
|
return this
|
|
220
212
|
}
|
|
221
213
|
|
|
@@ -229,12 +221,8 @@ export class TweenAnimation implements Animation {
|
|
|
229
221
|
}
|
|
230
222
|
|
|
231
223
|
finish(): this {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
;(_creatorUI as any).tweenControl(this._id, OP_FINISH) // the host answers with EV_FINISH
|
|
235
|
-
} else {
|
|
236
|
-
this._ensureFallback().finish()
|
|
237
|
-
}
|
|
224
|
+
this._ensure()
|
|
225
|
+
_creatorTree.tweenControl(this._h.id, OP_FINISH) // the host answers with EV_FINISH
|
|
238
226
|
return this
|
|
239
227
|
}
|
|
240
228
|
|
|
@@ -256,115 +244,11 @@ export class TweenAnimation implements Animation {
|
|
|
256
244
|
|
|
257
245
|
/** @internal The host reached the end (naturally or via finish()) and has freed the animation. */
|
|
258
246
|
_hostFinished(): void {
|
|
259
|
-
if (this.
|
|
260
|
-
// the old-host path's timers / value loop must stop with the run, or an interval outlives it
|
|
261
|
-
if (this._fb) { this._fb.cancel(); this._fb = undefined }
|
|
247
|
+
if (this._h.id) { tweens.unpin(this._h.id); this._h = DEAD_HANDLE } // the runtime freed it
|
|
262
248
|
this._endTime = this._spec.durationMs
|
|
263
249
|
this._settle(true)
|
|
264
250
|
}
|
|
265
251
|
|
|
266
|
-
// ---- old hosts ------------------------------------------------------------------------------
|
|
267
|
-
|
|
268
|
-
private _ensureFallback(): Fallback {
|
|
269
|
-
if (!this._fb) this._fb = new Fallback(this)
|
|
270
|
-
return this._fb
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/** Old-host path: UI tracks go through the legacy `animateTo` / `animateFrom` pair (one value per
|
|
275
|
-
* prop — the last explicit key for a to-tween, the first for a from-tween; no easing, no seek), a
|
|
276
|
-
* track on any other domain is dropped with one warning, and the clock is a timer that fires the
|
|
277
|
-
* calls and the finish. Good enough for the simple cases on web / Apple until their core lands. */
|
|
278
|
-
class Fallback {
|
|
279
|
-
time = 0
|
|
280
|
-
elapsed = 0
|
|
281
|
-
private _timers: ReturnType<typeof setTimeout>[] = []
|
|
282
|
-
private _startedAt = 0
|
|
283
|
-
private _rate = 1
|
|
284
|
-
private _warned = false
|
|
285
|
-
private _loop?: ReturnType<typeof setInterval>
|
|
286
|
-
private _valueTracks: Track[] = []
|
|
287
|
-
private _scratch = new Float32Array(16)
|
|
288
|
-
private _lanes: number[] = []
|
|
289
|
-
|
|
290
|
-
private a: TweenAnimation
|
|
291
|
-
constructor(a: TweenAnimation) { this.a = a }
|
|
292
|
-
|
|
293
|
-
setRate(r: number): void { this._rate = r }
|
|
294
|
-
|
|
295
|
-
play(): void {
|
|
296
|
-
const s = this.a._spec
|
|
297
|
-
this.cancel()
|
|
298
|
-
this._startedAt = Date.now()
|
|
299
|
-
const byTarget = new Map<Track["target"], { to: Record<string, unknown>, from: Record<string, unknown>, at: number, dur: number, commit: boolean }>()
|
|
300
|
-
this._valueTracks = []
|
|
301
|
-
for (const t of s.tracks) {
|
|
302
|
-
if (t.channel.domain === DOM_VALUE) { this._valueTracks.push(t); continue }
|
|
303
|
-
if (t.channel.domain !== DOM_UI) {
|
|
304
|
-
if (!this._warned) { this._warned = true; console.warn("[tween] this host animates UI elements and values only (no tween core) — 3D / 2D tracks skipped") }
|
|
305
|
-
continue
|
|
306
|
-
}
|
|
307
|
-
const rec = byTarget.get(t.target) ?? { to: {}, from: {}, at: t.atMs, dur: t.durMs, commit: t.commit }
|
|
308
|
-
byTarget.set(t.target, rec)
|
|
309
|
-
const first = t.keys[0]!, last = t.keys[t.keys.length - 1]!
|
|
310
|
-
if (last.value === undefined) rec.from[t.prop] = first.raw // animateFrom shape
|
|
311
|
-
else rec.to[t.prop] = last.raw
|
|
312
|
-
rec.at = Math.min(rec.at, t.atMs)
|
|
313
|
-
rec.dur = Math.max(rec.dur, t.durMs)
|
|
314
|
-
}
|
|
315
|
-
for (const [target, rec] of byTarget) {
|
|
316
|
-
// Sent even for an unmounted element (id 0), exactly as the direct call used to be — the host
|
|
317
|
-
// ignores it; a stub host in tests records it.
|
|
318
|
-
const id = ((target as any)._id as number) ?? 0
|
|
319
|
-
const delay = rec.at + s.delayMs
|
|
320
|
-
const meta: Record<string, unknown> = { duration: rec.dur }
|
|
321
|
-
if (delay > 0) meta.delay = delay
|
|
322
|
-
if (s.iterations !== 1) { meta.loop = s.iterations < 0 ? true : s.iterations; meta.loopMode = s.pingPong ? "ping-pong" : "restart" }
|
|
323
|
-
// The host merges its animate layer into base on commit (the SDK committed its own style already)
|
|
324
|
-
if (Object.keys(rec.to).length) _creatorUI.animateTo?.(id, { ...rec.to, ...meta, ...(rec.commit ? {} : { commit: false }) })
|
|
325
|
-
if (Object.keys(rec.from).length) _creatorUI.animateFrom?.(id, { ...rec.from, ...meta })
|
|
326
|
-
}
|
|
327
|
-
const scale = this._rate > 0 ? 1 / this._rate : 0
|
|
328
|
-
if (scale === 0) return
|
|
329
|
-
for (let i = 0; i < s.calls.length; i++) {
|
|
330
|
-
this._timers.push(setTimeout(() => this.a._fireCall(i), (s.delayMs + s.calls[i]!) * scale))
|
|
331
|
-
}
|
|
332
|
-
if (s.iterations === 1) {
|
|
333
|
-
this._timers.push(setTimeout(() => { this._applyValues(s.durationMs); this.elapsed = this.time = s.durationMs; this.a._hostFinished() }, (s.delayMs + s.durationMs) * scale))
|
|
334
|
-
}
|
|
335
|
-
// VALUE tracks are evaluated here in JS (the C evaluator's twin), ~60 times a second
|
|
336
|
-
if (this._valueTracks.length) {
|
|
337
|
-
this._applyValues(0)
|
|
338
|
-
this._loop = setInterval(() => {
|
|
339
|
-
const total = (Date.now() - this._startedAt) * this._rate - s.delayMs
|
|
340
|
-
if (total < 0) return
|
|
341
|
-
const period = s.durationMs * (s.pingPong && s.iterations !== 1 ? 2 : 1)
|
|
342
|
-
let local = period > 0 ? total % period : 0
|
|
343
|
-
if (s.iterations !== 1 && s.pingPong && local > s.durationMs) local = 2 * s.durationMs - local
|
|
344
|
-
if (s.iterations === 1) local = Math.min(total, s.durationMs)
|
|
345
|
-
this.elapsed = total
|
|
346
|
-
this.time = local
|
|
347
|
-
this._applyValues(local)
|
|
348
|
-
}, 16)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
private _applyValues(local: number): void {
|
|
352
|
-
for (const t of this._valueTracks) {
|
|
353
|
-
evaluateTrack(t, local, this._lanes)
|
|
354
|
-
for (let i = 0; i < t.lanes; i++) this._scratch[i] = this._lanes[i]!
|
|
355
|
-
this.a._fireValue(t.channel.id() as number, this._scratch)
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
pause(): void { this.cancel() }
|
|
359
|
-
resume(): void { /* no partial resume on the timer clock: the tween already ran on the host */ }
|
|
360
|
-
seek(ms: number): void { this.elapsed = ms; this.time = ms % Math.max(1, this.a._spec.durationMs); this._applyValues(this.time) }
|
|
361
|
-
finish(): void { this.cancel(); this._applyValues(this.a._spec.durationMs); this.a._hostFinished() }
|
|
362
|
-
cancel(): void {
|
|
363
|
-
for (const t of this._timers) clearTimeout(t)
|
|
364
|
-
this._timers = []
|
|
365
|
-
if (this._loop !== undefined) { clearInterval(this._loop); this._loop = undefined }
|
|
366
|
-
if (this._startedAt) this.elapsed = this.time = Math.min(this.a._spec.durationMs, (Date.now() - this._startedAt) * this._rate)
|
|
367
|
-
}
|
|
368
252
|
}
|
|
369
253
|
|
|
370
254
|
/** @internal Build a one-bag animation for a single target (the `animateTo` / `animateFrom` path). */
|