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/src/ui/UIPager.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ContainerElement, type BaseStyle, type ChildrenFn, type DrawableStyle, type ElementStyle, type UIElementBase, type UINodeChild } from "./UINode"
|
|
2
2
|
import { disposeBinding } from "../core/signals"
|
|
3
3
|
import type { UIScreen } from "./UIScreen"
|
|
4
|
+
import type { NavigateOptions, PopOptions } from "./router"
|
|
5
|
+
import { _transitionBack, _transitionId, TRANSITION_UNSET } from "./transitions"
|
|
6
|
+
import { pin, tree } from "./tree"
|
|
4
7
|
|
|
5
8
|
// UIPager — the system's ONE screen-navigation element (docs/pager-component-plan.md): sibling
|
|
6
9
|
// tabs that swipe natively side to side, with a native navigation STACK per tab. `push` opens a
|
|
@@ -11,12 +14,15 @@ import type { UIScreen } from "./UIScreen"
|
|
|
11
14
|
// the current screen (UIPager.current) — no reference threading into child screens. Router stays
|
|
12
15
|
// distinct: `Router.push` opens ON TOP of the pager (bars and all), the pager navigates within.
|
|
13
16
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// The SDK owns the logical state (`_pages` flat in TAB-MAJOR order, `_lens` = per-tab stack
|
|
18
|
+
// lengths, `_index` = the selected tab) and sends it to the runtime whole (`pagerSetContent`) or
|
|
19
|
+
// as a stack command with the page id; the runtime mirrors it, parents the pages' views under the
|
|
20
|
+
// pager and reports host-driven changes back (TREE_EVENT_PAGER_SELECT / PAGER_POP) — both
|
|
21
|
+
// idempotent no-ops here when the logical state already matches.
|
|
22
|
+
//
|
|
23
|
+
// A change of a tab's stack plays a transition exactly as the Router's does (transitions.ts): the
|
|
24
|
+
// same names and specs, the same way back remembered by the entry — the runtime plays it between
|
|
25
|
+
// the two pages, inside the pager's box.
|
|
20
26
|
|
|
21
27
|
/** Styles a pager accepts — layout box + drawable (background, borders, …). Pages style
|
|
22
28
|
* themselves; the pager only owns its own slot. */
|
|
@@ -59,17 +65,21 @@ export interface UIPager extends UIElementBase<UIPagerStyle, DrawableStyle & Bas
|
|
|
59
65
|
/** Fires when the selected tab changes and has settled — a user swipe or `select()`. */
|
|
60
66
|
onSelect(callback: (index: number) => void): this,
|
|
61
67
|
|
|
62
|
-
/** Push `screen` on top of the **current tab
|
|
63
|
-
*
|
|
64
|
-
|
|
68
|
+
/** Push `screen` on top of the **current tab**; the edge back-swipe (or `pop()`) removes it.
|
|
69
|
+
* Other tabs are unaffected. Transitions as in `Router.push`: `"push"` unless one is named, and
|
|
70
|
+
* the screen remembers its way back (`popTransition`, or the mirror of the way it came). */
|
|
71
|
+
push(screen: UIScreen, opts?: NavigateOptions): this,
|
|
65
72
|
/** Pop the current tab's top screen (no-op at the tab root — the root never pops). The
|
|
66
|
-
* programmatic twin of the native edge back-swipe.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
* programmatic twin of the native edge back-swipe. Plays the way back the screen remembers
|
|
74
|
+
* unless a transition is named. */
|
|
75
|
+
pop(opts?: PopOptions): this,
|
|
76
|
+
/** Unwind the current tab back to its root screen in one step: the top screen leaves with its
|
|
77
|
+
* way back (or the named transition), the ones between are never shown. */
|
|
78
|
+
popToRoot(opts?: PopOptions): this,
|
|
70
79
|
/** Swap the current tab's top screen for `screen` without growing the stack (at depth 1 this
|
|
71
|
-
* swaps the tab's root).
|
|
72
|
-
|
|
80
|
+
* swaps the tab's root). `"fade"` unless a transition is named; the way back stays the one of
|
|
81
|
+
* the place in the stack unless `popTransition` names another. */
|
|
82
|
+
replace(screen: UIScreen, opts?: NavigateOptions): this,
|
|
73
83
|
/** The current tab's screens, root → top (read-only snapshot). */
|
|
74
84
|
readonly stack: UIScreen[],
|
|
75
85
|
/** The current tab's stack depth — 1 means only the root. While `depth > 1`, swiping between
|
|
@@ -77,13 +87,16 @@ export interface UIPager extends UIElementBase<UIPagerStyle, DrawableStyle & Bas
|
|
|
77
87
|
readonly depth: number,
|
|
78
88
|
/** Fires when the current tab's depth changes — push, pop, replace, or a back-swipe. */
|
|
79
89
|
onChange(callback: (depth: number) => void): this,
|
|
90
|
+
/** The pages, tab-major (every tab's stack root → top). */
|
|
91
|
+
readonly children: UIScreen[],
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
export class PagerElement extends ContainerElement<"pager"> {
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
/** @internal The pages, flat in tab-major order. */
|
|
96
|
+
_pages: any[] = []
|
|
97
|
+
/** @internal Per-tab stack lengths — partitions `_pages`. */
|
|
98
|
+
_lens: number[] = []
|
|
99
|
+
/** @internal Selected tab. */
|
|
87
100
|
_index = 0
|
|
88
101
|
// >0 while a screen owned by this pager is the visible one (its onOpen fired, onClose hasn't).
|
|
89
102
|
// Only one owned screen is ever open, so this is effectively 0/1 — the count only makes the
|
|
@@ -96,24 +109,33 @@ export class PagerElement extends ContainerElement<"pager"> {
|
|
|
96
109
|
// flexShrink: 1 default (SDK-owned, like UIScrollable's): pages are separate layout roots
|
|
97
110
|
// sized to the pager's box, so yoga sees no shrinkable content — without this a pager in a
|
|
98
111
|
// flex column overflows the screen instead of leaving room for the tab bar.
|
|
99
|
-
super("pager", { flexShrink: 1, ...style },
|
|
100
|
-
this.
|
|
101
|
-
for (const s of this.children) this._track(s)
|
|
112
|
+
super("pager", { flexShrink: 1, ...style }, [])
|
|
113
|
+
this._rebuild(tabs as any[])
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
/** Start offset of tab `i`'s segment in the flat `
|
|
116
|
+
/** Start offset of tab `i`'s segment in the flat `_pages`. */
|
|
105
117
|
private _offset(i: number): number {
|
|
106
118
|
let off = 0
|
|
107
119
|
for (let t = 0; t < i; t++) off += this._lens[t]
|
|
108
120
|
return off
|
|
109
121
|
}
|
|
110
122
|
|
|
123
|
+
/** Push the whole picture to the runtime. */
|
|
124
|
+
private _sync(): void {
|
|
125
|
+
if (this._h.id === 0) return
|
|
126
|
+
for (const p of this._pages) pin(p)
|
|
127
|
+
tree().pagerSetContent(this._h.id, this._pages.map((p) => p._h.id), this._lens.slice(), this._index)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get children(): any[] {
|
|
131
|
+
return this._pages.slice()
|
|
132
|
+
}
|
|
111
133
|
get index(): number {
|
|
112
134
|
return this._index
|
|
113
135
|
}
|
|
114
136
|
get stack(): UIScreen[] {
|
|
115
137
|
const off = this._offset(this._index)
|
|
116
|
-
return this.
|
|
138
|
+
return this._pages.slice(off, off + (this._lens[this._index] ?? 0))
|
|
117
139
|
}
|
|
118
140
|
get depth(): number {
|
|
119
141
|
return this._lens[this._index] ?? 0
|
|
@@ -131,74 +153,78 @@ export class PagerElement extends ContainerElement<"pager"> {
|
|
|
131
153
|
select(index: number, animated = false): this {
|
|
132
154
|
if (index < 0 || index >= this._lens.length || index === this._index) return this
|
|
133
155
|
this._index = index
|
|
134
|
-
if (this.
|
|
156
|
+
if (this._h.id !== 0) tree().pagerSelect(this._h.id, index, animated)
|
|
135
157
|
this._emitSelect()
|
|
136
158
|
return this
|
|
137
159
|
}
|
|
138
160
|
|
|
139
|
-
push(screen: UIScreen): this {
|
|
161
|
+
push(screen: UIScreen, opts?: NavigateOptions): this {
|
|
140
162
|
if (this._lens.length === 0) {
|
|
141
163
|
console.warn("UIPager.push: pager has no tabs")
|
|
142
164
|
return this
|
|
143
165
|
}
|
|
144
|
-
this.
|
|
166
|
+
this._pages.splice(this._offset(this._index) + this._lens[this._index], 0, screen)
|
|
145
167
|
this._lens[this._index]++
|
|
146
168
|
this._track(screen)
|
|
147
|
-
|
|
169
|
+
pin(screen as any)
|
|
170
|
+
if (this._h.id !== 0) {
|
|
171
|
+
const transition = opts?.transition ?? "push"
|
|
172
|
+
const back = opts?.popTransition !== undefined ? _transitionId(opts.popTransition) : _transitionBack(transition)
|
|
173
|
+
tree().pagerPush(this._h.id, (screen as any)._h.id, _transitionId(transition), back)
|
|
174
|
+
}
|
|
148
175
|
this._emitChange()
|
|
149
176
|
return this
|
|
150
177
|
}
|
|
151
178
|
|
|
152
|
-
pop(): this {
|
|
179
|
+
pop(opts?: PopOptions): this {
|
|
153
180
|
if ((this._lens[this._index] ?? 0) <= 1) return this
|
|
154
|
-
this.
|
|
181
|
+
this._pages.splice(this._offset(this._index) + this._lens[this._index] - 1, 1)
|
|
155
182
|
this._lens[this._index]--
|
|
156
|
-
|
|
183
|
+
// the runtime detaches the page when its transition lands (its close fires first)
|
|
184
|
+
if (this._h.id !== 0) tree().pagerPop(this._h.id, _transitionId(opts?.transition, TRANSITION_UNSET))
|
|
157
185
|
this._emitChange()
|
|
158
186
|
return this
|
|
159
187
|
}
|
|
160
188
|
|
|
161
|
-
popToRoot(): this {
|
|
189
|
+
popToRoot(opts?: PopOptions): this {
|
|
162
190
|
const len = this._lens[this._index] ?? 0
|
|
163
191
|
if (len <= 1) return this
|
|
164
|
-
this.
|
|
192
|
+
this._pages.splice(this._offset(this._index) + 1, len - 1)
|
|
165
193
|
this._lens[this._index] = 1
|
|
166
|
-
if (this.
|
|
194
|
+
if (this._h.id !== 0) tree().pagerPopToRoot(this._h.id, _transitionId(opts?.transition, TRANSITION_UNSET))
|
|
167
195
|
this._emitChange()
|
|
168
196
|
return this
|
|
169
197
|
}
|
|
170
198
|
|
|
171
|
-
replace(screen: UIScreen): this {
|
|
199
|
+
replace(screen: UIScreen, opts?: NavigateOptions): this {
|
|
172
200
|
if (this._lens.length === 0) {
|
|
173
201
|
console.warn("UIPager.replace: pager has no tabs")
|
|
174
202
|
return this
|
|
175
203
|
}
|
|
176
|
-
this.
|
|
204
|
+
const at = this._offset(this._index) + this._lens[this._index] - 1
|
|
205
|
+
const old = this._pages[at]
|
|
206
|
+
if (old === screen) return this
|
|
207
|
+
this._pages[at] = screen
|
|
177
208
|
this._track(screen)
|
|
178
|
-
|
|
209
|
+
pin(screen as any)
|
|
210
|
+
if (this._h.id !== 0) tree().pagerReplace(this._h.id, (screen as any)._h.id, _transitionId(opts?.transition ?? "fade"), _transitionId(opts?.popTransition))
|
|
179
211
|
this._emitChange()
|
|
180
212
|
return this
|
|
181
213
|
}
|
|
182
214
|
|
|
183
215
|
// ── Children ARE the tabs ──────────────────────────────────────────────────────────────────
|
|
184
|
-
// Every inherited container mutation
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
// overridden to mutate the element first (children, `_lens`, `_index`) and then notify over the
|
|
189
|
-
// same command channel every other pager op uses, where the host reconciles against the node.
|
|
190
|
-
//
|
|
191
|
-
// Content set this way is always one screen per tab: a per-tab STACK only ever grows through
|
|
192
|
-
// push/replace, so `_lens` resets to all-ones here.
|
|
216
|
+
// Every inherited container mutation is overridden to mutate the logical state first and then
|
|
217
|
+
// hand the runtime the whole picture (pagerSetContent), where it reconciles the pages. Content set
|
|
218
|
+
// this way is always one screen per tab: a per-tab STACK only ever grows through push/replace,
|
|
219
|
+
// so `_lens` resets to all-ones here.
|
|
193
220
|
private _rebuild(children: any[]): this {
|
|
194
221
|
const tabs = children.filter(Boolean)
|
|
195
222
|
const prevIndex = this._index
|
|
196
|
-
this.
|
|
197
|
-
this.children.push(...tabs)
|
|
223
|
+
this._pages = tabs
|
|
198
224
|
this._lens = tabs.map(() => 1)
|
|
199
225
|
if (this._index >= tabs.length) this._index = Math.max(0, tabs.length - 1)
|
|
200
226
|
for (const s of tabs) this._track(s)
|
|
201
|
-
|
|
227
|
+
this._sync()
|
|
202
228
|
if (this._index !== prevIndex) this._emitSelect()
|
|
203
229
|
this._emitChange()
|
|
204
230
|
return this
|
|
@@ -213,36 +239,36 @@ export class PagerElement extends ContainerElement<"pager"> {
|
|
|
213
239
|
return this._rebuild(children as any[])
|
|
214
240
|
}
|
|
215
241
|
append(...nodes: UINodeChild[]): this {
|
|
216
|
-
return this._rebuild([...this.
|
|
242
|
+
return this._rebuild([...this._pages, ...nodes])
|
|
217
243
|
}
|
|
218
244
|
insert(index: number, ...nodes: UINodeChild[]): this {
|
|
219
|
-
const next = this.
|
|
245
|
+
const next = this._pages.slice()
|
|
220
246
|
next.splice(index, 0, ...(nodes as any[]))
|
|
221
247
|
return this._rebuild(next)
|
|
222
248
|
}
|
|
223
|
-
remove(...nodesToDelete:
|
|
249
|
+
remove(...nodesToDelete: UINodeChild[]): this {
|
|
224
250
|
const drop = new Set<any>(nodesToDelete)
|
|
225
|
-
return this._rebuild(this.
|
|
251
|
+
return this._rebuild(this._pages.filter((c: any) => !drop.has(c)))
|
|
226
252
|
}
|
|
227
253
|
_reconcile(next: UINodeChild[]): void {
|
|
228
254
|
this._rebuild(next as any[])
|
|
229
255
|
}
|
|
230
256
|
|
|
231
|
-
|
|
232
|
-
|
|
257
|
+
/** @internal Host-driven tab change (committed swipe): the native pager already moved — sync
|
|
258
|
+
* the logical index + fire onSelect. */
|
|
233
259
|
_hostDidSelect(index: number): void {
|
|
234
260
|
if (index < 0 || index >= this._lens.length || index === this._index) return
|
|
235
261
|
this._index = index
|
|
236
262
|
this._emitSelect()
|
|
237
263
|
}
|
|
238
264
|
|
|
239
|
-
|
|
240
|
-
|
|
265
|
+
/** @internal Host-driven pop on the current tab (interactive edge back-swipe / back button): the
|
|
266
|
+
* native stack already popped — sync the logical stack + fire onChange. Idempotent. */
|
|
241
267
|
_hostDidPop(depth: number): void {
|
|
242
268
|
if (depth < 1) depth = 1
|
|
243
269
|
const len = this._lens[this._index] ?? 0
|
|
244
270
|
if (depth >= len) return
|
|
245
|
-
this.
|
|
271
|
+
this._pages.splice(this._offset(this._index) + depth, len - depth)
|
|
246
272
|
this._lens[this._index] = depth
|
|
247
273
|
this._emitChange()
|
|
248
274
|
}
|
|
@@ -281,8 +307,6 @@ export class PagerElement extends ContainerElement<"pager"> {
|
|
|
281
307
|
}
|
|
282
308
|
}
|
|
283
309
|
|
|
284
|
-
const isScreenArg = (v: any): boolean => v != null && typeof v === "object" && v.type === "screen"
|
|
285
|
-
|
|
286
310
|
/** Callable factory + ambient statics. The name `UIPager` is a value (this factory) and a type
|
|
287
311
|
* (the instance interface above) — a standard value/type merge. */
|
|
288
312
|
interface UIPagerConstructor {
|
|
@@ -291,32 +315,23 @@ interface UIPagerConstructor {
|
|
|
291
315
|
* swiping); several → swipeable sibling tabs, fixed at construction, each starting as its own
|
|
292
316
|
* one-screen stack. */
|
|
293
317
|
(...tabs: (UIScreen | UIScreen[] | false | null | undefined)[]): UIPager
|
|
294
|
-
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
295
|
-
(style: UIPagerStyle, content?: UIScreen | UIScreen[]): UIPager
|
|
296
318
|
/** The pager that owns the currently visible screen, or `null` when the visible screen isn't
|
|
297
319
|
* hosted by one. With pagers nested inside pages, resolves to the **innermost** — so ambient
|
|
298
320
|
* calls act on the pager the user is actually looking at. */
|
|
299
321
|
readonly current: UIPager | null
|
|
300
322
|
/** `UIPager.current.push(screen)` — push onto the pager owning the visible screen, no
|
|
301
323
|
* reference threading. No-op with a console warning when there is no current pager. */
|
|
302
|
-
push(screen: UIScreen): void
|
|
324
|
+
push(screen: UIScreen, opts?: NavigateOptions): void
|
|
303
325
|
/** `UIPager.current.pop()` — pop the visible pager's current tab (no-op at the tab root, or
|
|
304
326
|
* with a warning when there is no current pager). */
|
|
305
|
-
pop(): void
|
|
327
|
+
pop(opts?: PopOptions): void
|
|
306
328
|
/** `UIPager.current.popToRoot()` — unwind the visible pager's current tab to its root. */
|
|
307
|
-
popToRoot(): void
|
|
329
|
+
popToRoot(opts?: PopOptions): void
|
|
308
330
|
}
|
|
309
331
|
|
|
310
332
|
function pagerFactory(...args: any[]): UIPager {
|
|
311
|
-
let style: any = null
|
|
312
|
-
let start = 0
|
|
313
|
-
const a0 = args.length > 0 ? args[0] : undefined
|
|
314
|
-
if (a0 != null && typeof a0 === "object" && !Array.isArray(a0) && !isScreenArg(a0)) {
|
|
315
|
-
style = a0
|
|
316
|
-
start = 1
|
|
317
|
-
}
|
|
318
333
|
const tabs: any[] = []
|
|
319
|
-
for (let i =
|
|
334
|
+
for (let i = 0; i < args.length; i++) {
|
|
320
335
|
const a = args[i]
|
|
321
336
|
if (Array.isArray(a)) {
|
|
322
337
|
for (let j = 0; j < a.length; j++) if (a[j]) tabs.push(a[j])
|
|
@@ -324,7 +339,7 @@ function pagerFactory(...args: any[]): UIPager {
|
|
|
324
339
|
tabs.push(a)
|
|
325
340
|
}
|
|
326
341
|
}
|
|
327
|
-
return new PagerElement(
|
|
342
|
+
return new PagerElement(null, tabs) as unknown as UIPager
|
|
328
343
|
}
|
|
329
344
|
|
|
330
345
|
/**
|
|
@@ -351,12 +366,12 @@ const _resolveCurrent = (): UIPager | null => {
|
|
|
351
366
|
// as part of that binding's declaration (`ItemKind::AttachedTo`, chisel >= 0.4.2) — kept iff
|
|
352
367
|
// `UIPager` is reached, shaken away with it otherwise. Under chisel <= 0.4.1 they were dropped
|
|
353
368
|
// outright and `UIPager.push` arrived as `undefined` at runtime, in bundled builds only; don't lower
|
|
354
|
-
// the pinned `@letary/chisel` back past 0.4.2. Covered by sdk/tests/compile.test.ts.
|
|
369
|
+
// the pinned `@letary/chisel` back past 0.4.2. Covered by sdk/tests/compile/inject.test.ts.
|
|
355
370
|
// `Object.defineProperty` for `current`, not `Object.assign({ get current() {} })` — Object.assign
|
|
356
371
|
// *invokes* the getter and copies the resulting value, freezing it at null forever.
|
|
357
372
|
Object.defineProperty(UIPager, "current", {
|
|
358
373
|
get: () => (_activeStack[_activeStack.length - 1] ?? null) as unknown as UIPager | null,
|
|
359
374
|
})
|
|
360
|
-
;(UIPager as any).push = (screen: UIScreen) => { _resolveCurrent()?.push(screen) }
|
|
361
|
-
;(UIPager as any).pop = () => { _resolveCurrent()?.pop() }
|
|
362
|
-
;(UIPager as any).popToRoot = () => { _resolveCurrent()?.popToRoot() }
|
|
375
|
+
;(UIPager as any).push = (screen: UIScreen, opts?: NavigateOptions) => { _resolveCurrent()?.push(screen, opts) }
|
|
376
|
+
;(UIPager as any).pop = (opts?: PopOptions) => { _resolveCurrent()?.pop(opts) }
|
|
377
|
+
;(UIPager as any).popToRoot = (opts?: PopOptions) => { _resolveCurrent()?.popToRoot(opts) }
|
package/src/ui/UIPopover.ts
CHANGED
|
@@ -93,8 +93,6 @@ const makePopover = (style: any, children: any) => new PopoverElement(style, chi
|
|
|
93
93
|
* `show(anchor)` next to an element or `{x, y}` point. Same argument forms as `UIColumn`. */
|
|
94
94
|
export function UIPopover(...children: UIChildArg[]): UIPopover;
|
|
95
95
|
export function UIPopover(children: UINodeChild[] | ChildrenFn): UIPopover;
|
|
96
|
-
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
97
|
-
export function UIPopover(style: UIPopoverStyle, children?: UINodeChild[] | ChildrenFn): UIPopover;
|
|
98
96
|
export function UIPopover(...args: any[]): UIPopover {
|
|
99
97
|
return buildUI(args, null, makePopover)
|
|
100
98
|
}
|
package/src/ui/UIScreen.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { TouchStartEvent } from "../runtime/touch"
|
|
2
|
-
import { _bumpNavEpoch,
|
|
2
|
+
import { _bumpNavEpoch, _setCurrent, Presentable, type DestTuple, type DismissOptions, type PresentOptions } from "./presentable"
|
|
3
|
+
import { _transitionId, TRANSITION_NONE } from "./transitions"
|
|
3
4
|
import { ContainerElement, buildUI, type BaseStyle, type ContainerStyle, type DrawableStyle, type PaddingStyle, type UIChildArg, type UIContainerBase, type UINodeChild, type ChildrenFn, } from "./UINode"
|
|
5
|
+
import { TREE_DEST_SCREEN, TREE_FLAG_BACK, TREE_FLAG_INTERACTIVE, TREE_FLAG_KEEP_ALIVE, pin, tree } from "./tree"
|
|
4
6
|
|
|
5
7
|
export type UIScreenStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle
|
|
6
8
|
|
|
@@ -15,9 +17,12 @@ export interface UIScreen extends UIContainerBase<UIScreenStyle, DrawableStyle &
|
|
|
15
17
|
* scene, a native view — suspending an active Router until `Router.restore()`). */
|
|
16
18
|
open(options?: PresentOptions): void,
|
|
17
19
|
/** Dismiss if this screen is the visible destination. */
|
|
18
|
-
close(): void
|
|
20
|
+
close(options?: DismissOptions): void
|
|
19
21
|
|
|
20
22
|
/** Hardware/system back while this screen is current. */
|
|
23
|
+
onBack(callback: () => void): this
|
|
24
|
+
/** @deprecated Renamed `onBack` (2026-09-26). An alias for the projects compiled before the
|
|
25
|
+
* rename; removed with the release that recompiles them. */
|
|
21
26
|
onBackPressed(callback: () => void): this
|
|
22
27
|
|
|
23
28
|
/** The screen became the visible destination (first open, or revealed by a pop). */
|
|
@@ -26,38 +31,32 @@ export interface UIScreen extends UIContainerBase<UIScreenStyle, DrawableStyle &
|
|
|
26
31
|
onClose(callback: () => void): this
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
|
-
* Keep this screen's
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* detach/re-attach. You own the lifetime — a kept tree is freed only by `dispose()` or when the
|
|
33
|
-
* project unloads. (Native-only today; web rebuilds.)
|
|
34
|
+
* Keep this screen's native state when a pager releases it: instead of dropping its view, the
|
|
35
|
+
* host keeps it (scroll position, input text), so re-pushing the SAME screen object restores it
|
|
36
|
+
* in place. `onClose`/`onOpen` still fire on detach/re-attach. Free it with `destroy()`.
|
|
34
37
|
*/
|
|
35
38
|
keepAlive(enabled?: boolean): this
|
|
36
|
-
/** Free a `keepAlive()` screen's retained tree now. No-op unless it's currently detached-and-kept. */
|
|
37
|
-
dispose(): this
|
|
38
39
|
|
|
39
40
|
/** Touch began on the screen; `ev.track(...)` takes over the rest of the gesture. */
|
|
40
41
|
onTouchStart(callback: (ev: TouchStartEvent<UIScreen>) => void): this,
|
|
42
|
+
/** @internal The destination tuple (Presentable). */
|
|
43
|
+
_dest(): DestTuple
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
export class ScreenElement extends ContainerElement<"screen"> {
|
|
47
|
+
/** @internal */
|
|
48
|
+
_dest(): DestTuple { return [TREE_DEST_SCREEN, this._h.id, "", ""] }
|
|
49
|
+
|
|
44
50
|
open(options?: PresentOptions): void {
|
|
51
|
+
if (this._h.id === 0) return
|
|
45
52
|
_bumpNavEpoch()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} else {
|
|
49
|
-
// Legacy host: no destination navigation — transition is dropped.
|
|
50
|
-
_creatorUI.openScreen(this)
|
|
51
|
-
}
|
|
53
|
+
pin(this) // presented: the runtime holds it until it retires it (TREE_EVENT_DETACHED)
|
|
54
|
+
tree().open(TREE_DEST_SCREEN, this._h.id, "", "", _transitionId(options?.transition, TRANSITION_NONE))
|
|
52
55
|
_setCurrent(this as any)
|
|
53
56
|
}
|
|
54
|
-
close(): void {
|
|
57
|
+
close(options?: DismissOptions): void {
|
|
55
58
|
_bumpNavEpoch()
|
|
56
|
-
|
|
57
|
-
_creatorUI.closeView!()
|
|
58
|
-
} else {
|
|
59
|
-
_creatorUI.closeScreen()
|
|
60
|
-
}
|
|
59
|
+
tree().close(TREE_DEST_SCREEN, this._h.id, _transitionId(options?.transition, TRANSITION_NONE))
|
|
61
60
|
if (Presentable.current === (this as any)) _setCurrent(null)
|
|
62
61
|
}
|
|
63
62
|
protected ol: (() => void)[] = []
|
|
@@ -70,25 +69,24 @@ export class ScreenElement extends ContainerElement<"screen"> {
|
|
|
70
69
|
this.cl.push(callback)
|
|
71
70
|
return this
|
|
72
71
|
}
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
/** @internal presentation lifecycle from the runtime (also a pager's page open / close). */
|
|
73
|
+
_emitOpen(): void { for (const cb of this.ol.slice()) cb() }
|
|
74
|
+
/** @internal */
|
|
75
|
+
_emitClose(): void { for (const cb of this.cl.slice()) cb() }
|
|
76
|
+
|
|
75
77
|
keepAlive(enabled = true): this {
|
|
76
|
-
this.
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
dispose(): this {
|
|
80
|
-
// Rides the generic command channel (no dedicated bridge). No-op while _id is 0 (never mounted)
|
|
81
|
-
// or when the tree isn't currently detached-and-kept — the host ignores it then.
|
|
82
|
-
if (this._id !== 0) _creatorUI.command(this, "disposeKeepAlive")
|
|
78
|
+
if (enabled) this._addFlags(TREE_FLAG_KEEP_ALIVE)
|
|
79
|
+
else this._removeFlags(TREE_FLAG_KEEP_ALIVE)
|
|
83
80
|
return this
|
|
84
81
|
}
|
|
82
|
+
|
|
85
83
|
readonly touchStartListeners: any[] = []
|
|
86
|
-
_interactive = false
|
|
87
84
|
onTouchStart (callback: any): this {
|
|
88
|
-
this._interactive = true
|
|
89
85
|
this.touchStartListeners.push(callback)
|
|
86
|
+
this._addFlags(TREE_FLAG_INTERACTIVE)
|
|
90
87
|
return this
|
|
91
88
|
}
|
|
89
|
+
/** @internal */
|
|
92
90
|
_emitTouchStart(pointerId: number, clientX: number, clientY: number): any {
|
|
93
91
|
const touchEvent = new TouchStartEvent(clientX, clientY, pointerId)
|
|
94
92
|
touchEvent.target = this
|
|
@@ -100,11 +98,20 @@ export class ScreenElement extends ContainerElement<"screen"> {
|
|
|
100
98
|
}
|
|
101
99
|
return null
|
|
102
100
|
}
|
|
103
|
-
_backButtonCallback?: () => void
|
|
104
|
-
|
|
101
|
+
private _backButtonCallback?: () => void
|
|
102
|
+
onBack(callback: any): this {
|
|
105
103
|
this._backButtonCallback = callback
|
|
104
|
+
this._addFlags(TREE_FLAG_BACK)
|
|
106
105
|
return this
|
|
107
106
|
}
|
|
107
|
+
/** @deprecated see the interface */
|
|
108
|
+
onBackPressed(callback: any): this { return this.onBack(callback) }
|
|
109
|
+
/** @internal the runtime's back chain: true = handled. */
|
|
110
|
+
_emitBack(): boolean {
|
|
111
|
+
if (!this._backButtonCallback) return false
|
|
112
|
+
this._backButtonCallback()
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
const makeScreen = (style: any, children: any) => new ScreenElement("screen", style, children)
|
|
@@ -116,8 +123,6 @@ export const __UIScreen = (children: UINodeChild[] | ChildrenFn): UIScreen => ma
|
|
|
116
123
|
* as `UIColumn`. */
|
|
117
124
|
export function UIScreen(...children: UIChildArg[]): UIScreen;
|
|
118
125
|
export function UIScreen(children: UINodeChild[] | ChildrenFn): UIScreen;
|
|
119
|
-
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
120
|
-
export function UIScreen(style: UIScreenStyle, children?: UINodeChild[] | ChildrenFn): UIScreen;
|
|
121
126
|
export function UIScreen(...args: any[]): UIScreen {
|
|
122
127
|
return buildUI(args, null, makeScreen)
|
|
123
128
|
}
|
package/src/ui/UIScrollable.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { UIContainerStyle } from "./UIContainer"
|
|
2
2
|
import { type UIChildArg, type UIContainerBase, type UINodeChild, type ChildrenFn, type Color, ContainerElement, buildUI } from "./UINode"
|
|
3
|
+
import { TREE_FLAG_OVERSCROLL, TREE_FLAG_REFRESH, TREE_FLAG_SCROLL, TREE_FLAG_SCROLL_RELEASE } from "./tree"
|
|
3
4
|
|
|
4
5
|
export type ScrollStyle = {
|
|
5
6
|
showScrollbar?: boolean,
|
|
@@ -30,42 +31,49 @@ export interface UIScrollable extends UIContainerBase<UIScrollableStyle> {
|
|
|
30
31
|
onScrollRelease(callback: () => void): UIScrollable,
|
|
31
32
|
/** Pulled past the content edge by `delta` px. */
|
|
32
33
|
onOverscroll(callback: (delta: number) => void): UIScrollable
|
|
33
|
-
/** Pull-to-refresh: the spinner stays until the returned promise settles.
|
|
34
|
-
* node mounts (hosts read it at creation). Vertical only; native hosts — no-op on web. */
|
|
34
|
+
/** Pull-to-refresh: the spinner stays until the returned promise settles. Vertical only. */
|
|
35
35
|
onRefresh(callback: () => void | Promise<void>): UIScrollable
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
class ScrollableElement extends ContainerElement<"scrollable"> {
|
|
39
39
|
|
|
40
|
-
readonly osl: any[] = []
|
|
41
|
-
readonly sl: any[] = []
|
|
42
|
-
readonly srl: any[] = []
|
|
43
|
-
|
|
44
|
-
get scrollListeners() {
|
|
45
|
-
return this.sl
|
|
46
|
-
}
|
|
40
|
+
private readonly osl: any[] = []
|
|
41
|
+
private readonly sl: any[] = []
|
|
42
|
+
private readonly srl: any[] = []
|
|
43
|
+
private _refreshCallback?: () => void | Promise<void>
|
|
47
44
|
|
|
48
45
|
onScroll (callback: any): this {
|
|
49
46
|
this.sl.push(callback)
|
|
47
|
+
this._addFlags(TREE_FLAG_SCROLL)
|
|
50
48
|
return this
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
onScrollRelease (callback: any): this {
|
|
54
52
|
this.srl.push(callback)
|
|
53
|
+
this._addFlags(TREE_FLAG_SCROLL_RELEASE)
|
|
55
54
|
return this
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
onOverscroll (callback: any): this {
|
|
59
58
|
this.osl.push(callback)
|
|
59
|
+
this._addFlags(TREE_FLAG_OVERSCROLL)
|
|
60
60
|
return this
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// Read by the host at mount (same contract the scrollable screen had before 2026-07-22).
|
|
64
|
-
declare _refreshCallback: any
|
|
65
63
|
onRefresh (callback: any): this {
|
|
66
64
|
this._refreshCallback = callback
|
|
65
|
+
this._addFlags(TREE_FLAG_REFRESH)
|
|
67
66
|
return this
|
|
68
67
|
}
|
|
68
|
+
|
|
69
|
+
/** @internal */
|
|
70
|
+
_emitScroll(position: number): void { for (const cb of this.sl.slice()) cb(position) }
|
|
71
|
+
/** @internal */
|
|
72
|
+
_emitScrollRelease(): void { for (const cb of this.srl.slice()) cb() }
|
|
73
|
+
/** @internal */
|
|
74
|
+
_emitOverscroll(delta: number): void { for (const cb of this.osl.slice()) cb(delta) }
|
|
75
|
+
/** @internal the host awaits the returned promise (its spinner) */
|
|
76
|
+
_emitRefresh(): unknown { return this._refreshCallback?.() }
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
// flexShrink: 1 default (SDK-owned, like UIRow's flexDirection): without it a scrollable in a
|
|
@@ -80,8 +88,6 @@ export const __UIScrollable = (children: UINodeChild[] | ChildrenFn): UIScrollab
|
|
|
80
88
|
* `flexGrow: 1`. */
|
|
81
89
|
export function UIScrollable(...children: UIChildArg[]): UIScrollable
|
|
82
90
|
export function UIScrollable(children: UINodeChild[] | ChildrenFn): UIScrollable
|
|
83
|
-
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
84
|
-
export function UIScrollable(style: UIScrollableStyle, children?: UINodeChild[] | ChildrenFn): UIScrollable
|
|
85
91
|
export function UIScrollable(...args: any[]): UIScrollable {
|
|
86
92
|
return buildUI(args, SCROLLABLE_DEFAULTS, makeScrollable)
|
|
87
93
|
}
|
package/src/ui/UISpacer.ts
CHANGED
package/src/ui/UITabs.ts
CHANGED
|
@@ -73,6 +73,7 @@ export class TabsElement extends ScreenElement {
|
|
|
73
73
|
|
|
74
74
|
constructor(defs: Record<string, UITabDef>) {
|
|
75
75
|
super("screen", { bgColor: SCREEN_BG }, [])
|
|
76
|
+
this.style({ bgColor: SCREEN_BG })
|
|
76
77
|
this.ids = Object.keys(defs)
|
|
77
78
|
if (this.ids.length === 0) console.warn("UITabs: no tabs declared")
|
|
78
79
|
|
|
@@ -94,16 +95,17 @@ export class TabsElement extends ScreenElement {
|
|
|
94
95
|
this._badges.push(badge)
|
|
95
96
|
this._badgeTexts.push(badgeText)
|
|
96
97
|
const button = UIButton(anchor, icon ? label : null)
|
|
97
|
-
.style({
|
|
98
|
+
.style({ flexDirection: "column", gap: 3, flex: 1, pt: 8, pb: 6 })
|
|
99
|
+
.named(`tab-${id}`)
|
|
98
100
|
.onClick(() => this.select(id))
|
|
99
101
|
this._buttons.push(button)
|
|
100
102
|
return button
|
|
101
103
|
})
|
|
102
104
|
|
|
103
105
|
this._bar = UIRow(buttons).style({
|
|
104
|
-
|
|
106
|
+
bgColor: BAR_BG, pb: "comfort-bottom",
|
|
105
107
|
borderTopWidth: 1, borderTopColor: BAR_BORDER,
|
|
106
|
-
})
|
|
108
|
+
}).named("tabbar")
|
|
107
109
|
|
|
108
110
|
this._pagerEl.onSelect((i) => {
|
|
109
111
|
this._applyActive(i)
|
|
@@ -220,11 +222,11 @@ export function defineTabs<T extends Record<string, TabDef>>(tabs: T): TabsHandl
|
|
|
220
222
|
return UIButton(
|
|
221
223
|
def.icon ? UIImage(def.icon).style({ width: 22, height: 22, tintColor: color }) : null,
|
|
222
224
|
UIText(def.label).style({ fontSize: 10, color }),
|
|
223
|
-
).style({
|
|
225
|
+
).style({ flexDirection: "column", gap: 3, flex: 1, pt: 8, pb: 6 }).named(`tab-${id}`)
|
|
224
226
|
})).style({
|
|
225
|
-
|
|
227
|
+
bgColor: BAR_BG, pb: "comfort-bottom",
|
|
226
228
|
borderTopWidth: 1, borderTopColor: BAR_BORDER,
|
|
227
|
-
})
|
|
229
|
+
}).named("tabbar")
|
|
228
230
|
|
|
229
231
|
const screen = (active: keyof T & string, ...children: UIChildArg[]) =>
|
|
230
232
|
UIScreen(
|