lecodes-cli 0.18.2 → 0.19.2
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/dist/index.js +1866 -301
- package/package.json +14 -12
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/src/api.ts +302 -0
- package/src/browserAuth.ts +87 -0
- package/src/cmgenTool.ts +104 -0
- package/src/commands/app.ts +891 -0
- package/src/commands/appAndroid.ts +603 -0
- package/src/commands/appDesktop.ts +326 -0
- package/src/commands/appDesktopMac.ts +470 -0
- package/src/commands/appIcon.ts +187 -0
- package/src/commands/appShared.ts +448 -0
- package/src/commands/appTemplates.ts +883 -0
- package/src/commands/appTemplatesAndroid.ts +599 -0
- package/src/commands/appTemplatesGradlew.ts +9 -0
- package/src/commands/assets.ts +28 -0
- package/src/commands/clone.ts +60 -0
- package/src/commands/compile.ts +253 -0
- package/src/commands/create.ts +59 -0
- package/src/commands/design.ts +863 -0
- package/src/commands/designTemplates.ts +11 -0
- package/src/commands/desktop.ts +122 -0
- package/src/commands/dev.ts +214 -0
- package/src/commands/diff.ts +59 -0
- package/src/commands/init.ts +191 -0
- package/src/commands/install.ts +198 -0
- package/src/commands/lightmap.ts +290 -0
- package/src/commands/link.ts +147 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/navmesh.ts +225 -0
- package/src/commands/pn.ts +305 -0
- package/src/commands/projectTemplates.ts +129 -0
- package/src/commands/pull.ts +109 -0
- package/src/commands/push.ts +173 -0
- package/src/commands/render.ts +414 -0
- package/src/commands/scene.ts +148 -0
- package/src/commands/shaders.ts +157 -0
- package/src/commands/status.ts +29 -0
- package/src/commands/test.ts +370 -0
- package/src/commands/thumbs.ts +178 -0
- package/src/commands/types.ts +87 -0
- package/src/commands/update.ts +190 -0
- package/src/compile/assetIcons.ts +214 -0
- package/src/compile/collect.ts +46 -0
- package/src/compile/collectLocal.ts +37 -0
- package/src/compile/designCompile.ts +104 -0
- package/src/compile/fonts.ts +156 -0
- package/src/compile/headlessBundle.ts +129 -0
- package/src/compile/nativeStack.ts +21 -0
- package/src/compile/projectCompile.ts +109 -0
- package/src/compile/sceneCompile.ts +113 -0
- package/src/compile/screenEntry.ts +127 -0
- package/src/compile/shaders.ts +245 -0
- package/src/config.ts +42 -0
- package/src/designMeta.ts +35 -0
- package/src/desktopRenderer.ts +532 -0
- package/src/desktopScript.ts +276 -0
- package/src/dev/clientTemplates.ts +160 -0
- package/src/dev/devServer.ts +292 -0
- package/src/dev/wsServer.ts +144 -0
- package/src/distRoot.ts +20 -0
- package/src/ignore.ts +163 -0
- package/src/index.ts +491 -0
- package/src/lecodes-3d-editor.d.ts +41 -0
- package/src/lecodes-assets.d.ts +7 -0
- package/src/lecodes-design.d.ts +191 -0
- package/src/lecodes-renderer.d.ts +131 -0
- package/src/localFiles.ts +144 -0
- package/src/manifest.ts +46 -0
- package/src/matcTool.ts +137 -0
- package/src/peers.ts +40 -0
- package/src/project.ts +61 -0
- package/src/projectEnv.ts +94 -0
- package/src/qrcode-terminal.d.ts +9 -0
- package/src/releases.ts +125 -0
- package/src/serverDiff.ts +78 -0
- package/src/textDiff.ts +103 -0
- package/src/types.ts +0 -0
- package/src/util.ts +146 -0
- package/runtime/sdk/animate/animate.ts +0 -238
- package/runtime/sdk/animate/bezier.ts +0 -138
- package/runtime/sdk/animate/easings.ts +0 -126
- package/runtime/sdk/canvas/Canvas.ts +0 -305
- package/runtime/sdk/core/Aspect.ts +0 -512
- package/runtime/sdk/core/InspectorUI.ts +0 -212
- package/runtime/sdk/core/color.ts +0 -66
- package/runtime/sdk/core/compWrite.ts +0 -42
- package/runtime/sdk/core/events.ts +0 -38
- package/runtime/sdk/core/fields.ts +0 -120
- package/runtime/sdk/core/registry.ts +0 -23
- package/runtime/sdk/core/signals.ts +0 -277
- package/runtime/sdk/core/time.ts +0 -81
- package/runtime/sdk/g2/Camera2D.ts +0 -40
- package/runtime/sdk/g2/CharacterController2D.ts +0 -276
- package/runtime/sdk/g2/Node2D.ts +0 -267
- package/runtime/sdk/g2/OneWay2D.ts +0 -66
- package/runtime/sdk/g2/Physics2D.ts +0 -346
- package/runtime/sdk/g2/Scene2D.ts +0 -209
- package/runtime/sdk/g2/Shape2D.ts +0 -259
- package/runtime/sdk/g2/Sprite.ts +0 -89
- package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
- package/runtime/sdk/g2/SpriteSheet.ts +0 -166
- package/runtime/sdk/g2/Texture2D.ts +0 -47
- package/runtime/sdk/g2/Tilemap.ts +0 -41
- package/runtime/sdk/g2/Tileset.ts +0 -71
- package/runtime/sdk/g2/Trigger2D.ts +0 -77
- package/runtime/sdk/g2/autotile.ts +0 -433
- package/runtime/sdk/g2/cells.ts +0 -91
- package/runtime/sdk/g2/defineScene2d.ts +0 -381
- package/runtime/sdk/g2/groups2d.ts +0 -106
- package/runtime/sdk/g2/loop.ts +0 -50
- package/runtime/sdk/g2/scenarios2d.ts +0 -69
- package/runtime/sdk/g2/touch.ts +0 -83
- package/runtime/sdk/gl/Camera.ts +0 -160
- package/runtime/sdk/gl/CameraPlace.ts +0 -52
- package/runtime/sdk/gl/CharacterController.ts +0 -238
- package/runtime/sdk/gl/Gearbox.ts +0 -212
- package/runtime/sdk/gl/Geometry.ts +0 -279
- package/runtime/sdk/gl/IK.ts +0 -193
- package/runtime/sdk/gl/InstancedMesh.ts +0 -132
- package/runtime/sdk/gl/Light.ts +0 -99
- package/runtime/sdk/gl/Lightmap.ts +0 -179
- package/runtime/sdk/gl/Material.ts +0 -245
- package/runtime/sdk/gl/Mesh.ts +0 -83
- package/runtime/sdk/gl/Model.ts +0 -64
- package/runtime/sdk/gl/Node.ts +0 -350
- package/runtime/sdk/gl/Noise.ts +0 -30
- package/runtime/sdk/gl/Particles.ts +0 -676
- package/runtime/sdk/gl/Physics.ts +0 -222
- package/runtime/sdk/gl/Plane.ts +0 -53
- package/runtime/sdk/gl/Ray.ts +0 -16
- package/runtime/sdk/gl/Scene.ts +0 -479
- package/runtime/sdk/gl/Shape.ts +0 -377
- package/runtime/sdk/gl/Texture.ts +0 -46
- package/runtime/sdk/gl/Trigger.ts +0 -45
- package/runtime/sdk/gl/Vehicle.ts +0 -473
- package/runtime/sdk/gl/Wheel.ts +0 -240
- package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
- package/runtime/sdk/gl/animation/Animator.ts +0 -87
- package/runtime/sdk/gl/animation/Layer.ts +0 -29
- package/runtime/sdk/gl/animation/Loop.ts +0 -25
- package/runtime/sdk/gl/animation/Playback.ts +0 -43
- package/runtime/sdk/gl/animation/core.ts +0 -294
- package/runtime/sdk/gl/controls.ts +0 -95
- package/runtime/sdk/gl/physicsEvents.ts +0 -20
- package/runtime/sdk/gl/scenarios.ts +0 -291
- package/runtime/sdk/gl/state.ts +0 -6
- package/runtime/sdk/gl/touch.ts +0 -68
- package/runtime/sdk/inject.ts +0 -186
- package/runtime/sdk/math/Mathf.ts +0 -118
- package/runtime/sdk/math/mat4.ts +0 -278
- package/runtime/sdk/math/quat.ts +0 -232
- package/runtime/sdk/math/vec.ts +0 -255
- package/runtime/sdk/plugins/camera.ts +0 -81
- package/runtime/sdk/plugins/geolocation.ts +0 -123
- package/runtime/sdk/plugins/oauth.ts +0 -61
- package/runtime/sdk/plugins/permission.ts +0 -7
- package/runtime/sdk/plugins/push.ts +0 -132
- package/runtime/sdk/plugins/qr.ts +0 -73
- package/runtime/sdk/plugins/service.ts +0 -47
- package/runtime/sdk/runtime/app.ts +0 -101
- package/runtime/sdk/runtime/appEvents.ts +0 -54
- package/runtime/sdk/runtime/channel.ts +0 -61
- package/runtime/sdk/runtime/clipboard.ts +0 -20
- package/runtime/sdk/runtime/datetime.ts +0 -329
- package/runtime/sdk/runtime/device.ts +0 -293
- package/runtime/sdk/runtime/fetch.ts +0 -77
- package/runtime/sdk/runtime/files.ts +0 -23
- package/runtime/sdk/runtime/input.ts +0 -175
- package/runtime/sdk/runtime/media.ts +0 -111
- package/runtime/sdk/runtime/misc.ts +0 -16
- package/runtime/sdk/runtime/net.ts +0 -36
- package/runtime/sdk/runtime/rpc.ts +0 -218
- package/runtime/sdk/runtime/service.ts +0 -83
- package/runtime/sdk/runtime/share.ts +0 -9
- package/runtime/sdk/runtime/storage.ts +0 -13
- package/runtime/sdk/runtime/touch.ts +0 -76
- package/runtime/sdk/scene/defineScene.ts +0 -1227
- package/runtime/sdk/scene/editorPlugins.ts +0 -92
- package/runtime/sdk/scene/gizmos.ts +0 -148
- package/runtime/sdk/scene/grammar.ts +0 -120
- package/runtime/sdk/scene/material.ts +0 -188
- package/runtime/sdk/server/auth/appConfig.ts +0 -12
- package/runtime/sdk/server/auth/global.ts +0 -80
- package/runtime/sdk/server/auth/host.ts +0 -318
- package/runtime/sdk/server/auth/models.ts +0 -83
- package/runtime/sdk/server/auth/types.ts +0 -50
- package/runtime/sdk/server/channel.ts +0 -56
- package/runtime/sdk/server/context.ts +0 -36
- package/runtime/sdk/server/db/defineDb.ts +0 -237
- package/runtime/sdk/server/db/fields.ts +0 -132
- package/runtime/sdk/server/db/httpTransport.ts +0 -93
- package/runtime/sdk/server/db/index.ts +0 -7
- package/runtime/sdk/server/db/marci/query.ts +0 -412
- package/runtime/sdk/server/db/types.ts +0 -202
- package/runtime/sdk/server/errors.ts +0 -12
- package/runtime/sdk/server/host.ts +0 -74
- package/runtime/sdk/server/inject.ts +0 -13
- package/runtime/sdk/server/runtime.ts +0 -133
- package/runtime/sdk/server/validate.ts +0 -87
- package/runtime/sdk/ui/NativeView.ts +0 -142
- package/runtime/sdk/ui/UI.ts +0 -39
- package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
- package/runtime/sdk/ui/UIButton.ts +0 -101
- package/runtime/sdk/ui/UIContainer.ts +0 -60
- package/runtime/sdk/ui/UIImage.ts +0 -83
- package/runtime/sdk/ui/UIInput.ts +0 -185
- package/runtime/sdk/ui/UIModal.ts +0 -139
- package/runtime/sdk/ui/UINode.ts +0 -826
- package/runtime/sdk/ui/UIPager.ts +0 -362
- package/runtime/sdk/ui/UIPopover.ts +0 -100
- package/runtime/sdk/ui/UIScreen.ts +0 -123
- package/runtime/sdk/ui/UIScrollable.ts +0 -87
- package/runtime/sdk/ui/UISpacer.ts +0 -14
- package/runtime/sdk/ui/UITabs.ts +0 -236
- package/runtime/sdk/ui/UIText.ts +0 -51
- package/runtime/sdk/ui/UIVideo.ts +0 -88
- package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
- package/runtime/sdk/ui/UIWidget.ts +0 -127
- package/runtime/sdk/ui/fonts.ts +0 -13
- package/runtime/sdk/ui/presentable.ts +0 -117
- package/runtime/sdk/ui/router.ts +0 -132
- package/runtime/sdk/ui/theme.ts +0 -84
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Element, type ElementStyle, type UIElementBase } from "./UINode"
|
|
2
|
-
|
|
3
|
-
export type UISpacerStyle = ElementStyle
|
|
4
|
-
|
|
5
|
-
/** A flexible gap — `flexGrow: 1` by default, pushes its siblings apart. */
|
|
6
|
-
export interface UISpacer extends UIElementBase<UISpacerStyle> {
|
|
7
|
-
readonly type: "spacer",
|
|
8
|
-
}
|
|
9
|
-
/** Create a flexible gap (`flexGrow: 1`) — e.g. between a header's title and its actions. */
|
|
10
|
-
export function UISpacer(): UISpacer
|
|
11
|
-
export function UISpacer(style: UISpacerStyle): UISpacer
|
|
12
|
-
export function UISpacer(style?: UISpacerStyle): UISpacer {
|
|
13
|
-
return new Element("spacer", { flexGrow: 1, ...style })
|
|
14
|
-
}
|
package/runtime/sdk/ui/UITabs.ts
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { PagerElement, type UIPager } from "./UIPager"
|
|
2
|
-
import { ScreenElement, UIScreen } from "./UIScreen"
|
|
3
|
-
import { UIButton } from "./UIButton"
|
|
4
|
-
import { UIRow, UIColumn, type UIRow as UIRowType } from "./UIContainer"
|
|
5
|
-
import { UIText } from "./UIText"
|
|
6
|
-
import { UIImage, type ImageSource } from "./UIImage"
|
|
7
|
-
import type { UIChildArg } from "./UINode"
|
|
8
|
-
|
|
9
|
-
// UITabs — the standard bottom-tab shell, prewired: a UIScreen composing [ UIPager (flexGrow: 1),
|
|
10
|
-
// tab bar ]. Pure SDK composition over the pager — zero new ABI, works wherever UIPager does.
|
|
11
|
-
// `defineTabs` (below) is its design-stage twin (lecodes-design/tabbar-surface.md): the same keyed
|
|
12
|
-
// config without screens, mounted per-file with a literal tab id. They share the constants and bar
|
|
13
|
-
// geometry in this file, so a designed mockup and the built app render identically.
|
|
14
|
-
|
|
15
|
-
/** Active/inactive/bar colors ride the theme — one `theme({ primaryColor, mutedColor, tabbarBg })`
|
|
16
|
-
* call restyles the bar app-wide; the fallbacks keep an unthemed app looking right. */
|
|
17
|
-
const ACTIVE = "var(--primaryColor, #5b8cff)"
|
|
18
|
-
const INACTIVE = "var(--mutedColor, #8a919e)"
|
|
19
|
-
const BAR_BG = "var(--tabbarBg, #15171c)"
|
|
20
|
-
const BAR_BORDER = "var(--tabbarBorder, #23262e)"
|
|
21
|
-
const SCREEN_BG = "var(--screenBg, #101114)"
|
|
22
|
-
const BADGE_BG = "var(--badgeColor, #ff453a)"
|
|
23
|
-
|
|
24
|
-
export type UITabDef = {
|
|
25
|
-
label: string,
|
|
26
|
-
/** Tab icon — typically `assetIcon("lucide:…")`. Tinted active/inactive by the bar. */
|
|
27
|
-
icon?: ImageSource,
|
|
28
|
-
/** The tab's root screen. Tabs are fixed at construction (like UIPager's). */
|
|
29
|
-
screen: UIScreen,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* A bottom-tab app shell: swipeable tabs (a `UIPager`) plus a themed tab bar, as one
|
|
34
|
-
* `UIScreen` — present it directly (`Router.init(tabs)`). Keys are tab ids, in tab order:
|
|
35
|
-
*
|
|
36
|
-
* ```ts
|
|
37
|
-
* const tabs = UITabs({
|
|
38
|
-
* home: { label: "Home", icon: assetIcon("lucide:house"), screen: homeScreen },
|
|
39
|
-
* profile: { label: "Profile", icon: assetIcon("lucide:user"), screen: profileScreen },
|
|
40
|
-
* })
|
|
41
|
-
* Router.init(tabs)
|
|
42
|
-
* ```
|
|
43
|
-
*
|
|
44
|
-
* In-tab navigation stays the pager's: `UIPager.push(detail)` from any screen (bar stays);
|
|
45
|
-
* `Router.push` opens above the shell (bar covered). Restyle via `theme()` (primaryColor,
|
|
46
|
-
* mutedColor, tabbarBg, …) or the `bar` handle; for a fully custom bar build on `UIPager`.
|
|
47
|
-
*/
|
|
48
|
-
export interface UITabs extends UIScreen {
|
|
49
|
-
/** The active tab id. */
|
|
50
|
-
readonly tab: string,
|
|
51
|
-
/** Switch tabs (instant, the tab-bar convention; `animated: true` slides). */
|
|
52
|
-
select(id: string, animated?: boolean): this,
|
|
53
|
-
/** Fires when the active tab changes and has settled — a bar tap, swipe, or `select()`. */
|
|
54
|
-
onSelect(callback: (id: string, index: number) => void): this,
|
|
55
|
-
/** Show a badge on a tab: `true` = dot, a number/string = count pill, `false`/`null`/`0` clears. */
|
|
56
|
-
badge(id: string, value: number | string | boolean | null): this,
|
|
57
|
-
/** The pager under the bar — in-tab stacks (`push`/`pop`/`depth`). */
|
|
58
|
-
readonly pager: UIPager,
|
|
59
|
-
/** The bar row (named "tabbar") — for restyling beyond the theme variables. */
|
|
60
|
-
readonly bar: UIRowType,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class TabsElement extends ScreenElement {
|
|
64
|
-
private readonly ids: string[]
|
|
65
|
-
private readonly _pagerEl: PagerElement
|
|
66
|
-
private readonly _bar: any
|
|
67
|
-
private readonly _icons: (any | null)[] = []
|
|
68
|
-
private readonly _labels: any[] = []
|
|
69
|
-
private readonly _badges: any[] = []
|
|
70
|
-
private readonly _badgeTexts: any[] = []
|
|
71
|
-
private readonly _buttons: any[] = []
|
|
72
|
-
private readonly _selectListeners: ((id: string, index: number) => void)[] = []
|
|
73
|
-
|
|
74
|
-
constructor(defs: Record<string, UITabDef>) {
|
|
75
|
-
super("screen", { bgColor: SCREEN_BG }, [])
|
|
76
|
-
this.ids = Object.keys(defs)
|
|
77
|
-
if (this.ids.length === 0) console.warn("UITabs: no tabs declared")
|
|
78
|
-
|
|
79
|
-
this._pagerEl = new PagerElement({ flexGrow: 1 }, this.ids.map((id) => defs[id].screen))
|
|
80
|
-
|
|
81
|
-
const buttons = this.ids.map((id, i) => {
|
|
82
|
-
const def = defs[id]
|
|
83
|
-
const icon = def.icon ? UIImage(def.icon).style({ width: 22, height: 22, tintColor: INACTIVE }) : null
|
|
84
|
-
const label = UIText(def.label).style({ fontSize: 10, color: INACTIVE })
|
|
85
|
-
const badgeText = UIText("").style({ fontSize: 9, fontWeight: 700, color: "white" })
|
|
86
|
-
const badge = UIRow(badgeText).style({
|
|
87
|
-
position: "absolute", top: -3, right: -6, display: "none",
|
|
88
|
-
bgColor: BADGE_BG, borderRadius: 99, justifyContent: "center", alignItems: "center",
|
|
89
|
-
})
|
|
90
|
-
// The badge anchors to the icon (or the label when there is none) — its wrap must not clip.
|
|
91
|
-
const anchor = UIColumn(icon ?? label, badge).style({ overflow: "visible", alignItems: "center" })
|
|
92
|
-
this._icons.push(icon)
|
|
93
|
-
this._labels.push(label)
|
|
94
|
-
this._badges.push(badge)
|
|
95
|
-
this._badgeTexts.push(badgeText)
|
|
96
|
-
const button = UIButton(anchor, icon ? label : null)
|
|
97
|
-
.style({ name: `tab-${id}`, flexDirection: "column", gap: 3, flex: 1, pt: 8, pb: 6 })
|
|
98
|
-
.onClick(() => this.select(id))
|
|
99
|
-
this._buttons.push(button)
|
|
100
|
-
return button
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
this._bar = UIRow(buttons).style({
|
|
104
|
-
name: "tabbar", bgColor: BAR_BG, pb: "comfort-bottom",
|
|
105
|
-
borderTopWidth: 1, borderTopColor: BAR_BORDER,
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
this._pagerEl.onSelect((i) => {
|
|
109
|
-
this._applyActive(i)
|
|
110
|
-
for (const cb of this._selectListeners) cb(this.ids[i], i)
|
|
111
|
-
})
|
|
112
|
-
this._applyActive(0)
|
|
113
|
-
this.append(this._pagerEl as any, this._bar)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private _applyActive(index: number): void {
|
|
117
|
-
for (let i = 0; i < this.ids.length; i++) {
|
|
118
|
-
const on = i === index
|
|
119
|
-
const color = on ? ACTIVE : INACTIVE
|
|
120
|
-
// Imperative writes carry the visual on every host; the class is the styling hook — custom
|
|
121
|
-
// content inside a tab button can declare `$active` blocks (class state cascades down).
|
|
122
|
-
this._buttons[i].class.active = on
|
|
123
|
-
if (this._icons[i]) this._icons[i].style.tintColor = color
|
|
124
|
-
this._labels[i].style.color = color
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
get tab(): string {
|
|
129
|
-
return this.ids[this._pagerEl.index]
|
|
130
|
-
}
|
|
131
|
-
get pager(): any {
|
|
132
|
-
return this._pagerEl
|
|
133
|
-
}
|
|
134
|
-
get bar(): any {
|
|
135
|
-
return this._bar
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
select(id: string, animated = false): this {
|
|
139
|
-
const index = this.ids.indexOf(id)
|
|
140
|
-
if (index < 0) {
|
|
141
|
-
console.warn(`UITabs.select: unknown tab "${id}"`)
|
|
142
|
-
return this
|
|
143
|
-
}
|
|
144
|
-
this._pagerEl.select(index, animated)
|
|
145
|
-
return this
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
onSelect(callback: (id: string, index: number) => void): this {
|
|
149
|
-
this._selectListeners.push(callback)
|
|
150
|
-
return this
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
badge(id: string, value: number | string | boolean | null | undefined): this {
|
|
154
|
-
const i = this.ids.indexOf(id)
|
|
155
|
-
if (i < 0) {
|
|
156
|
-
console.warn(`UITabs.badge: unknown tab "${id}"`)
|
|
157
|
-
return this
|
|
158
|
-
}
|
|
159
|
-
if (value === null || value === undefined || value === false || value === 0 || value === "") {
|
|
160
|
-
this._badges[i].style.display = "none"
|
|
161
|
-
} else if (value === true) {
|
|
162
|
-
this._badgeTexts[i].text = ""
|
|
163
|
-
this._badges[i].style({ display: "flex", width: 9, height: 9, minWidth: 0, px: 0 })
|
|
164
|
-
} else {
|
|
165
|
-
this._badgeTexts[i].text = String(value)
|
|
166
|
-
this._badges[i].style({ display: "flex", width: "auto", height: 14, minWidth: 14, px: 4 })
|
|
167
|
-
}
|
|
168
|
-
return this
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** Create the standard bottom-tab shell — see {@link UITabs}. Keys are tab ids in tab order;
|
|
173
|
-
* each tab needs a `label` and its root `screen` (plus an optional `icon`). */
|
|
174
|
-
export function UITabs(tabs: Record<string, UITabDef>): UITabs {
|
|
175
|
-
return new TabsElement(tabs) as unknown as UITabs
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ---- defineTabs — the design-stage twin ---------------------------------------------------------
|
|
179
|
-
|
|
180
|
-
/** A `defineTabs` tab: `label` + optional `icon` (an `assetIcon("lucide:…")` call — never a
|
|
181
|
-
* hand-written source object). `UITabDef` is this plus the tab's root `screen`. */
|
|
182
|
-
export type TabDef = Omit<UITabDef, "screen">
|
|
183
|
-
|
|
184
|
-
/** What `defineTabs` returns: the bar (mount it yourself when the screen owns its whole layout)
|
|
185
|
-
* and the standard screen shell around it. `active` must be a declared tab id, as a literal. */
|
|
186
|
-
export type TabsHandle<T extends Record<string, TabDef>> = {
|
|
187
|
-
/** Tab ids in declaration order. */
|
|
188
|
-
ids: (keyof T & string)[]
|
|
189
|
-
/** The bar alone — one button per tab, the `active` one highlighted. */
|
|
190
|
-
bar(active: keyof T & string): UIRowType
|
|
191
|
-
/** A screen sitting ON the bar: `[ padded content column, bar ]`. */
|
|
192
|
-
screen(active: keyof T & string, ...children: UIChildArg[]): UIScreen
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Declare a tab bar WITHOUT screens — the design-stage twin of {@link UITabs} (same keys, same
|
|
197
|
-
* `tab-<id>` button names, same theme variables), for mockups where every screen is its own file
|
|
198
|
-
* and mounts the bar with a literal tab id:
|
|
199
|
-
*
|
|
200
|
-
* ```ts
|
|
201
|
-
* export const mainTabs = defineTabs({
|
|
202
|
-
* home: { label: "Home", icon: assetIcon("lucide:house") },
|
|
203
|
-
* profile: { label: "Profile", icon: assetIcon("lucide:user") },
|
|
204
|
-
* })
|
|
205
|
-
* // in a screen file:
|
|
206
|
-
* export default () => mainTabs.screen("home", [ ...content ])
|
|
207
|
-
* ```
|
|
208
|
-
*
|
|
209
|
-
* Restyle through the theme — `theme({ primaryColor, mutedColor, tabbarBg, screenBg })` recolors
|
|
210
|
-
* every bar at once. The built app passes the same keys (plus each tab's root screen) to `UITabs`,
|
|
211
|
-
* which renders the identical bar interactively.
|
|
212
|
-
*/
|
|
213
|
-
export function defineTabs<T extends Record<string, TabDef>>(tabs: T): TabsHandle<T> {
|
|
214
|
-
const ids = Object.keys(tabs) as (keyof T & string)[]
|
|
215
|
-
|
|
216
|
-
const bar = (active: keyof T & string) =>
|
|
217
|
-
UIRow(ids.map((id) => {
|
|
218
|
-
const def = tabs[id]
|
|
219
|
-
const color = id === active ? ACTIVE : INACTIVE
|
|
220
|
-
return UIButton(
|
|
221
|
-
def.icon ? UIImage(def.icon).style({ width: 22, height: 22, tintColor: color }) : null,
|
|
222
|
-
UIText(def.label).style({ fontSize: 10, color }),
|
|
223
|
-
).style({ name: `tab-${id}`, flexDirection: "column", gap: 3, flex: 1, pt: 8, pb: 6 })
|
|
224
|
-
})).style({
|
|
225
|
-
name: "tabbar", bgColor: BAR_BG, pb: "comfort-bottom",
|
|
226
|
-
borderTopWidth: 1, borderTopColor: BAR_BORDER,
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
const screen = (active: keyof T & string, ...children: UIChildArg[]) =>
|
|
230
|
-
UIScreen(
|
|
231
|
-
UIColumn(...children).style({ flexGrow: 1, p: 16, pt: "safe-top" }),
|
|
232
|
-
bar(active),
|
|
233
|
-
).style({ bgColor: SCREEN_BG })
|
|
234
|
-
|
|
235
|
-
return { ids, bar, screen }
|
|
236
|
-
}
|
package/runtime/sdk/ui/UIText.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { createBinding } from "../core/signals"
|
|
2
|
-
import { Element, type BaseStyle, type DrawableStyle, type ElementStyle, type TextStyle, type UIElementBase } from "./UINode"
|
|
3
|
-
|
|
4
|
-
export type UITextStyle = ElementStyle & TextStyle & DrawableStyle
|
|
5
|
-
|
|
6
|
-
/** A text label. */
|
|
7
|
-
export interface UIText extends UIElementBase<UITextStyle, DrawableStyle & BaseStyle & TextStyle> {
|
|
8
|
-
readonly type: "text",
|
|
9
|
-
/** The displayed string — writable at any time. */
|
|
10
|
-
text: string,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class TextElement extends Element<"text"> {
|
|
14
|
-
private _text: string
|
|
15
|
-
constructor(style: any, text: string | (() => string)) {
|
|
16
|
-
super("text", style)
|
|
17
|
-
if (typeof text === "function") {
|
|
18
|
-
// Reactive content: re-runs when a signal `text()` read changes; the setter routes to
|
|
19
|
-
// _creatorUI.updateNode when mounted and just keeps _text current when not.
|
|
20
|
-
this._text = ""
|
|
21
|
-
createBinding(this, "#text", () => {
|
|
22
|
-
const value = text()
|
|
23
|
-
if (value !== this._text) this.text = value
|
|
24
|
-
})
|
|
25
|
-
} else {
|
|
26
|
-
this._text = text
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
get text(): string {
|
|
30
|
-
return this._text
|
|
31
|
-
}
|
|
32
|
-
set text(value) {
|
|
33
|
-
this._text = value
|
|
34
|
-
if (this._id !== 0) {
|
|
35
|
-
_creatorUI.updateNode(this._id, "text", value)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Create a text label — a string, or `() => string` for reactive text that re-renders when a
|
|
41
|
-
* signal it read changes. */
|
|
42
|
-
export function UIText(text: string | (() => string)): UIText;
|
|
43
|
-
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
44
|
-
export function UIText(style: UITextStyle, text: string | (() => string)): UIText;
|
|
45
|
-
export function UIText(...args: [string | (() => string)] | [UITextStyle, string | (() => string)]): UIText {
|
|
46
|
-
if (args.length === 1) {
|
|
47
|
-
return new TextElement({}, args[0])
|
|
48
|
-
} else {
|
|
49
|
-
return new TextElement(args[0], args[1])
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type { VideoPlayer } from "../runtime/media"
|
|
2
|
-
import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "./presentable"
|
|
3
|
-
import { Element, type BaseStyle, type DrawableStyle, type ElementStyle, type UIElementBase } from "./UINode"
|
|
4
|
-
|
|
5
|
-
export type UIVideoStyle = ElementStyle & DrawableStyle & { objectFit?: "cover" | "contain" | "fill" }
|
|
6
|
-
|
|
7
|
-
/** A video surface showing a `VideoPlayer`'s output — embeddable in a screen AND
|
|
8
|
-
* fullscreen-presentable (`open()`), with seamless promotion between the two. */
|
|
9
|
-
export interface UIVideo extends UIElementBase<UIVideoStyle, DrawableStyle & BaseStyle> {
|
|
10
|
-
readonly type: "video",
|
|
11
|
-
/** The player driving this surface (playback control lives on it). */
|
|
12
|
-
player: VideoPlayer
|
|
13
|
-
|
|
14
|
-
/** Show fullscreen as the current destination. Promotion: if this video is mounted in a screen,
|
|
15
|
-
* the SAME native player moves fullscreen — playback uninterrupted; a pop moves it back. */
|
|
16
|
-
open(options?: PresentOptions): void
|
|
17
|
-
/** Dismiss if this is the visible destination. */
|
|
18
|
-
close(): void
|
|
19
|
-
/** Became the visible destination. */
|
|
20
|
-
onOpen(callback: () => void): this
|
|
21
|
-
/** Stopped being the visible destination. */
|
|
22
|
-
onClose(callback: () => void): this
|
|
23
|
-
/** Hardware/system back while current. */
|
|
24
|
-
onBackPressed(callback: () => void): this
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class VideoElement extends Element<"video"> implements Presentable {
|
|
28
|
-
private _playerId: number
|
|
29
|
-
private _player: VideoPlayer
|
|
30
|
-
constructor(style: any, player: VideoPlayer) {
|
|
31
|
-
super("video", style)
|
|
32
|
-
this._player = player
|
|
33
|
-
this._playerId = (player as any)._id
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
get player() {
|
|
37
|
-
return this._player
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ---- Presentable (docs/navigation-presentable-plan.md) ----
|
|
41
|
-
readonly ol: (() => void)[] = []
|
|
42
|
-
readonly cl: (() => void)[] = []
|
|
43
|
-
_backButtonCallback?: () => void
|
|
44
|
-
private _vd?: object
|
|
45
|
-
|
|
46
|
-
/** @internal wire descriptor — type "videoView" (distinct from the "video" node type). */
|
|
47
|
-
_viewDesc(): object {
|
|
48
|
-
return this._vd ??= { type: "videoView", node: this, _p: this }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
open(options?: PresentOptions): void {
|
|
52
|
-
if (!_navSupported()) {
|
|
53
|
-
throw new Error("Fullscreen video is not supported on this host (requires _creatorUI.openView)")
|
|
54
|
-
}
|
|
55
|
-
_bumpNavEpoch()
|
|
56
|
-
_creatorUI.openView!(this._viewDesc(), options?.transition ?? "none")
|
|
57
|
-
_setCurrent(this)
|
|
58
|
-
}
|
|
59
|
-
close(): void {
|
|
60
|
-
_bumpNavEpoch()
|
|
61
|
-
if (_navSupported()) _creatorUI.closeView!()
|
|
62
|
-
if (Presentable.current === this) _setCurrent(null)
|
|
63
|
-
}
|
|
64
|
-
onOpen(callback: () => void): this {
|
|
65
|
-
this.ol.push(callback)
|
|
66
|
-
return this
|
|
67
|
-
}
|
|
68
|
-
onClose(callback: () => void): this {
|
|
69
|
-
this.cl.push(callback)
|
|
70
|
-
return this
|
|
71
|
-
}
|
|
72
|
-
onBackPressed(callback: () => void): this {
|
|
73
|
-
this._backButtonCallback = callback
|
|
74
|
-
return this
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Create a video node showing `player`'s output. */
|
|
79
|
-
export function UIVideo(player: VideoPlayer): UIVideo
|
|
80
|
-
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
81
|
-
export function UIVideo(style: UIVideoStyle, player: VideoPlayer): UIVideo
|
|
82
|
-
export function UIVideo(...args: [VideoPlayer] | [UIVideoStyle, VideoPlayer]): UIVideo {
|
|
83
|
-
if (args.length === 1) {
|
|
84
|
-
return new VideoElement({}, args[0])
|
|
85
|
-
} else {
|
|
86
|
-
return new VideoElement(args[0], args[1])
|
|
87
|
-
}
|
|
88
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { Element, type Color, type DrawableStyle, type ElementStyle, type UIElementBase, type UINodeChild } from "./UINode"
|
|
2
|
-
|
|
3
|
-
export type UIVirtualizedListStyle = DrawableStyle & ElementStyle & {
|
|
4
|
-
/** Tint of the native pull-to-refresh spinner (pairs with `onRefresh`). */
|
|
5
|
-
refreshControlColor?: Color,
|
|
6
|
-
/** Same key and semantics as UIScrollable's; `"none"` keeps a chat composer's keyboard up no
|
|
7
|
-
* matter how the history is scrolled. */
|
|
8
|
-
keyboardDismissMode?: "interactive" | "scroll" | "none",
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface VListConfig<T> {
|
|
12
|
-
/** Stable unique key of an item. */
|
|
13
|
-
keyOf: (item: T) => string
|
|
14
|
-
/** Subtree factory — called when the item enters the window; the subtree lives until it
|
|
15
|
-
* leaves. */
|
|
16
|
-
render: (item: T) => UINodeChild
|
|
17
|
-
/** Height estimate (px) used before the first measurement — a number, or a per-item function
|
|
18
|
-
* (chat: `msg => 40 + Math.ceil(msg.text.length / 35) * 20`). Computed once when the item is
|
|
19
|
-
* added. */
|
|
20
|
-
estimatedHeight: number | ((item: T) => number)
|
|
21
|
-
/** Extra px around the viewport kept mounted. Default: one viewport height. */
|
|
22
|
-
overscan?: number
|
|
23
|
-
/** Chat mode: the first layout starts scrolled to the end, and appends auto-scroll while the
|
|
24
|
-
* list sits at the bottom. */
|
|
25
|
-
inverted?: boolean
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** A windowed (virtualized) list: only the items near the viewport are mounted — for long feeds
|
|
29
|
-
* and chats. Items are keyed; feed it via `setData`/`append`/`prepend`. */
|
|
30
|
-
export interface UIVirtualizedList<T = unknown> extends UIElementBase<UIVirtualizedListStyle> {
|
|
31
|
-
readonly type: "vlist"
|
|
32
|
-
|
|
33
|
-
/** Replace the data wholesale — vanished keys are unmounted by the host's window sync. */
|
|
34
|
-
setData(items: T[]): this
|
|
35
|
-
/** Append items to the end (auto-scrolls when `inverted` and resting at the bottom). */
|
|
36
|
-
append(...items: T[]): this
|
|
37
|
-
/** Insert at the start WITHOUT a scroll jump (the host compensates the offset). */
|
|
38
|
-
prepend(...items: T[]): this
|
|
39
|
-
/** Update items with the same keys: mounted ones re-render; the cached height persists until
|
|
40
|
-
* re-measured. */
|
|
41
|
-
update(...items: T[]): this
|
|
42
|
-
/** Remove items by key. */
|
|
43
|
-
removeByKey(...keys: string[]): this
|
|
44
|
-
|
|
45
|
-
/** Number of data items (mounted or not). */
|
|
46
|
-
readonly itemCount: number
|
|
47
|
-
/** The data item under `key`, if any. */
|
|
48
|
-
getItem(key: string): T | undefined
|
|
49
|
-
|
|
50
|
-
/** Scroll to `offset` px. */
|
|
51
|
-
scrollTo(offset: number, animated?: boolean): void
|
|
52
|
-
/** Scroll to the item with `key`. */
|
|
53
|
-
scrollToKey(key: string, animated?: boolean): void
|
|
54
|
-
/** Scroll to the end of the content. */
|
|
55
|
-
scrollToEnd(animated?: boolean): void
|
|
56
|
-
|
|
57
|
-
/** Scroll position changed (px from the start edge). */
|
|
58
|
-
onScroll(callback: (scrollPosition: number) => void): this
|
|
59
|
-
/** Pull-to-refresh: the spinner stays until the returned promise settles. Attach BEFORE the
|
|
60
|
-
* node mounts (hosts read it at creation). Native hosts only — no-op on web. */
|
|
61
|
-
onRefresh(callback: () => void | Promise<void>): this
|
|
62
|
-
/** Fires within `thresholdPx` of the content's bottom edge — once, re-armed after leaving the
|
|
63
|
-
* edge (host-side latch). Infinite-scroll loading. */
|
|
64
|
-
onEndReached(thresholdPx: number, callback: () => void): this
|
|
65
|
-
/** Same, for the top edge — chat history loading. */
|
|
66
|
-
onStartReached(thresholdPx: number, callback: () => void): this
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedList<T> {
|
|
70
|
-
|
|
71
|
-
// --- read by native side in createUINode (copyProperty / copyNodePtr) ---
|
|
72
|
-
_overscan: number
|
|
73
|
-
_inverted: boolean
|
|
74
|
-
_keys: string[] = []
|
|
75
|
-
_estimates: number[] = []
|
|
76
|
-
_endThreshold = 0
|
|
77
|
-
_startThreshold = 0
|
|
78
|
-
|
|
79
|
-
// --- списки слушателей, нативка забирает через copyPropertyListPtr ---
|
|
80
|
-
protected sl: ((scrollPosition: number) => void)[] = [] // scroll
|
|
81
|
-
protected erl: (() => void)[] = [] // endReached
|
|
82
|
-
protected strl: (() => void)[] = [] // startReached
|
|
83
|
-
|
|
84
|
-
// --- приватное состояние JS-стороны ---
|
|
85
|
-
private items = new Map<string, T>()
|
|
86
|
-
private mounted = new Map<string, UINodeChild>()
|
|
87
|
-
private keyOf: (item: T) => string
|
|
88
|
-
private renderItem: (item: T) => UINodeChild
|
|
89
|
-
private estimate: (item: T) => number
|
|
90
|
-
|
|
91
|
-
constructor(config: VListConfig<T>) {
|
|
92
|
-
// flexShrink: 1 default (SDK-owned, same as UIScrollable): an explicit height/flexBasis
|
|
93
|
-
// larger than the available space shrinks to fit instead of overflowing. The vlist's own
|
|
94
|
-
// yoga basis is 0 (items live in detached roots), so this is inert until a size is set.
|
|
95
|
-
super("vlist", { flexShrink: 1 })
|
|
96
|
-
this.keyOf = config.keyOf
|
|
97
|
-
this.renderItem = config.render
|
|
98
|
-
this.estimate = typeof config.estimatedHeight === "function"
|
|
99
|
-
? config.estimatedHeight
|
|
100
|
-
: () => config.estimatedHeight as number
|
|
101
|
-
this._overscan = config.overscan ?? -1
|
|
102
|
-
this._inverted = config.inverted ?? false
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private get isAttached(): boolean {
|
|
106
|
-
return !!this._id && this._id !== 0
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private register(items: T[]): [string[], number[]] {
|
|
110
|
-
const keys: string[] = []
|
|
111
|
-
const estimates: number[] = []
|
|
112
|
-
for (const item of items) {
|
|
113
|
-
const k = this.keyOf(item)
|
|
114
|
-
this.items.set(k, item)
|
|
115
|
-
keys.push(k)
|
|
116
|
-
estimates.push(this.estimate(item))
|
|
117
|
-
}
|
|
118
|
-
return [keys, estimates]
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
setData(items: T[]): this {
|
|
122
|
-
const prevItems = new Map(this.items)
|
|
123
|
-
const prevMounted = [...this.mounted.keys()]
|
|
124
|
-
this.items.clear()
|
|
125
|
-
const [keys, estimates] = this.register(items)
|
|
126
|
-
this._keys = keys
|
|
127
|
-
this._estimates = estimates
|
|
128
|
-
if (this.isAttached) {
|
|
129
|
-
_creatorUI.vlistSetKeys(this, keys, estimates)
|
|
130
|
-
|
|
131
|
-
for (const k of prevMounted) {
|
|
132
|
-
if (this.mounted.has(k) && this.items.get(k) !== prevItems.get(k)) {
|
|
133
|
-
_creatorUI.vlistInvalidate(this, k)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return this
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
append(...items: T[]): this {
|
|
141
|
-
const [keys, estimates] = this.register(items)
|
|
142
|
-
this._keys.push(...keys)
|
|
143
|
-
this._estimates.push(...estimates)
|
|
144
|
-
if (this.isAttached) {
|
|
145
|
-
_creatorUI.vlistInsertKeys(this, -1, keys, estimates)
|
|
146
|
-
}
|
|
147
|
-
return this
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
prepend(...items: T[]): this {
|
|
151
|
-
const [keys, estimates] = this.register(items)
|
|
152
|
-
this._keys.unshift(...keys)
|
|
153
|
-
this._estimates.unshift(...estimates)
|
|
154
|
-
if (this.isAttached) {
|
|
155
|
-
_creatorUI.vlistInsertKeys(this, 0, keys, estimates)
|
|
156
|
-
}
|
|
157
|
-
return this
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
update(...items: T[]): this {
|
|
161
|
-
for (const item of items) {
|
|
162
|
-
const k = this.keyOf(item)
|
|
163
|
-
if (!this.items.has(k)) continue
|
|
164
|
-
this.items.set(k, item)
|
|
165
|
-
if (this.isAttached && this.mounted.has(k)) {
|
|
166
|
-
_creatorUI.vlistInvalidate(this, k)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return this
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
removeByKey(...keys: string[]): this {
|
|
173
|
-
for (const k of keys) {
|
|
174
|
-
if (!this.items.delete(k)) continue
|
|
175
|
-
const i = this._keys.indexOf(k)
|
|
176
|
-
if (i >= 0) { this._keys.splice(i, 1); this._estimates.splice(i, 1) }
|
|
177
|
-
}
|
|
178
|
-
if (this.isAttached) {
|
|
179
|
-
_creatorUI.vlistRemoveKeys(this, keys)
|
|
180
|
-
}
|
|
181
|
-
return this
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
get itemCount(): number { return this.items.size }
|
|
185
|
-
getItem(key: string): T | undefined { return this.items.get(key) }
|
|
186
|
-
|
|
187
|
-
// --- команды: офсеты и позиции знает только нативка (префикс-суммы) ---
|
|
188
|
-
scrollTo(offset: number, animated = true): void {
|
|
189
|
-
_creatorUI.command(this, "scrollTo", offset, animated)
|
|
190
|
-
}
|
|
191
|
-
scrollToKey(key: string, animated = true): void {
|
|
192
|
-
_creatorUI.command(this, "scrollToKey", key, animated)
|
|
193
|
-
}
|
|
194
|
-
scrollToEnd(animated = true): void {
|
|
195
|
-
_creatorUI.command(this, "scrollToEnd", animated)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
onScroll(callback: (scrollPosition: number) => void): this {
|
|
199
|
-
this.sl.push(callback)
|
|
200
|
-
return this
|
|
201
|
-
}
|
|
202
|
-
// Читается нативкой при маунте (тот же контракт, что у UIScrollable).
|
|
203
|
-
declare _refreshCallback: any
|
|
204
|
-
onRefresh(callback: () => void | Promise<void>): this {
|
|
205
|
-
this._refreshCallback = callback
|
|
206
|
-
return this
|
|
207
|
-
}
|
|
208
|
-
onEndReached(thresholdPx: number, callback: () => void): this {
|
|
209
|
-
this._endThreshold = thresholdPx
|
|
210
|
-
this.erl.push(callback)
|
|
211
|
-
return this
|
|
212
|
-
}
|
|
213
|
-
onStartReached(thresholdPx: number, callback: () => void): this {
|
|
214
|
-
this._startThreshold = thresholdPx
|
|
215
|
-
this.strl.push(callback)
|
|
216
|
-
return this
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** @internal The one host→JS callback (via jsPtr, like button). Fires ONLY when the window
|
|
220
|
-
* crossed a boundary — not per scroll frame. Order: unmounts first (the host already tore the
|
|
221
|
-
* subtrees down and zeroed _id; only JS refs are cleaned here), then mounts. */
|
|
222
|
-
_syncWindow(unmountKeys: string[], mountKeys: string[]): void {
|
|
223
|
-
for (const k of unmountKeys) {
|
|
224
|
-
this.mounted.delete(k)
|
|
225
|
-
}
|
|
226
|
-
for (const k of mountKeys) {
|
|
227
|
-
const item = this.items.get(k)
|
|
228
|
-
if (item === undefined) continue
|
|
229
|
-
const node = this.renderItem(item)
|
|
230
|
-
this.mounted.set(k, node)
|
|
231
|
-
// createUINode в detached yoga root + измерение (width, NaN) + кэш высоты
|
|
232
|
-
_creatorUI.vlistMount(this, k, node)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** Create a virtualized list from a config (`keyOf` + `render` + `estimatedHeight` required),
|
|
238
|
-
* then feed it via `setData`/`append`/`prepend`. Give it a size (`flexGrow: 1` as a screen body). */
|
|
239
|
-
export function UIVirtualizedList<T>(config: VListConfig<T>): UIVirtualizedList<T> {
|
|
240
|
-
return new VListElement<T>(config)
|
|
241
|
-
}
|