lecodes-sdk 2.2.3 → 2.2.5
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/global.d.ts +3 -1
- package/dist/types/animate/tween/spec.d.ts +4 -5
- package/dist/types/g2/Scene2D.d.ts +5 -4
- package/dist/types/gl/Scene.d.ts +5 -4
- package/dist/types/inject.d.ts +1 -1
- package/dist/types/server/db/fields.d.ts +6 -3
- package/dist/types/server/db/marci/query.d.ts +21 -2
- package/dist/types/server/db/types.d.ts +26 -12
- package/dist/types/server/errors.d.ts +4 -1
- package/dist/types/server/files/models.d.ts +4 -0
- package/dist/types/server/inject.d.ts +6 -0
- package/dist/types/ui/NativeView.d.ts +3 -3
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UILayer.d.ts +25 -18
- package/dist/types/ui/UINode.d.ts +8 -1
- package/dist/types/ui/UIScreen.d.ts +11 -1
- package/dist/types/ui/UIVideo.d.ts +3 -3
- package/dist/types/ui/UIVirtualizedList.d.ts +64 -18
- package/dist/types/ui/UIWidget.d.ts +24 -15
- package/dist/types/ui/presentable.d.ts +7 -7
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +2 -2
- package/prompts/3d-scene-files.md +2 -2
- package/prompts/3d-scene.md +1 -1
- package/prompts/dist/2d-game.md +13 -13
- package/prompts/dist/3d-app.md +21 -18
- package/prompts/dist/ar-app.md +13 -13
- package/prompts/dist/ui-app.md +13 -13
- package/prompts/ui.md +3 -1
- package/src/animate/tween/spec.ts +8 -13
- package/src/chisel.ts +3 -3
- package/src/compile/serverSplit.ts +5 -3
- package/src/g2/Scene2D.ts +9 -7
- package/src/gl/Scene.ts +9 -7
- package/src/inject.ts +1 -1
- package/src/server/db/defineDb.ts +17 -21
- package/src/server/db/fields.ts +7 -4
- package/src/server/db/httpTransport.ts +19 -3
- package/src/server/db/marci/query.ts +34 -8
- package/src/server/db/types.ts +33 -14
- package/src/server/errors.ts +4 -1
- package/src/server/files/db.ts +26 -7
- package/src/server/files/host.ts +45 -17
- package/src/server/files/models.ts +4 -0
- package/src/server/host.ts +3 -3
- package/src/server/inject.ts +3 -0
- package/src/server/runtime.ts +65 -4
- package/src/ui/NativeView.ts +11 -9
- package/src/ui/UI.ts +1 -1
- package/src/ui/UILayer.ts +90 -42
- package/src/ui/UINode.ts +33 -11
- package/src/ui/UIPopover.ts +3 -3
- package/src/ui/UIScreen.ts +50 -2
- package/src/ui/UIVideo.ts +11 -9
- package/src/ui/UIVirtualizedList.ts +159 -31
- package/src/ui/UIWidget.ts +41 -18
- package/src/ui/presentable.ts +8 -8
- package/src/ui/transitions.ts +1 -1
- package/src/version.ts +1 -1
- package/tests/helpers/fakeTree.ts +6 -1
|
@@ -10,20 +10,14 @@ export type UIVirtualizedListStyle = DrawableStyle & ElementStyle & {
|
|
|
10
10
|
keyboardDismissMode?: "interactive" | "scroll" | "none",
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
/** The rows, read reactively: the list follows the signals this reads, the way a container's
|
|
15
|
-
* function children do. Rows are told apart by `keyOf`; a row whose item is another object
|
|
16
|
-
* re-renders. Appended rows and rows added at the start are told from a rewrite (a chat at
|
|
17
|
-
* its bottom follows what is appended; older history above moves nothing). */
|
|
18
|
-
data: () => T[]
|
|
13
|
+
interface VListBaseConfig<T> {
|
|
19
14
|
/** Stable unique key of an item. */
|
|
20
15
|
keyOf: (item: T) => string
|
|
21
16
|
/** Subtree factory — called when the item enters the window; the subtree lives until it
|
|
22
17
|
* leaves. */
|
|
23
18
|
render: (item: T) => UINodeChild
|
|
24
19
|
/** Height estimate (px) used before the first measurement — a number, or a per-item function
|
|
25
|
-
* (chat: `msg => 40 + Math.ceil(msg.text.length / 35) * 20`).
|
|
26
|
-
* added. */
|
|
20
|
+
* (chat: `msg => 40 + Math.ceil(msg.text.length / 35) * 20`). Read when the item is added. */
|
|
27
21
|
estimatedHeight: number | ((item: T) => number)
|
|
28
22
|
/** Extra px around the viewport kept mounted. Default: one viewport height. */
|
|
29
23
|
overscan?: number
|
|
@@ -32,9 +26,26 @@ export interface VListConfig<T> {
|
|
|
32
26
|
inverted?: boolean
|
|
33
27
|
}
|
|
34
28
|
|
|
35
|
-
/** A
|
|
36
|
-
*
|
|
37
|
-
export interface
|
|
29
|
+
/** A list fed by `data`: the rows are read reactively, the way a container's function children
|
|
30
|
+
* are. Such a list has no imperative feed (`append` and the rest are not on its type). */
|
|
31
|
+
export interface VListBoundConfig<T> extends VListBaseConfig<T> {
|
|
32
|
+
/** The rows, read reactively: the list follows the signals this reads. Rows are told apart by
|
|
33
|
+
* `keyOf`; a row whose item is another object re-renders. The change is told from the keys:
|
|
34
|
+
* rows gone are removals, new rows are inserts where they stand (a chat at its bottom follows
|
|
35
|
+
* an append; rows above the reader move nothing), a reorder rewrites the keys. */
|
|
36
|
+
data: () => T[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A list fed by hand: `setData` / `append` / `prepend` / `update` / `removeByKey`. */
|
|
40
|
+
export interface VListFedConfig<T> extends VListBaseConfig<T> {
|
|
41
|
+
data?: undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type VListConfig<T> = VListBoundConfig<T> | VListFedConfig<T>
|
|
45
|
+
|
|
46
|
+
/** What both forms of the list share: the items held, scrolling, the scroll / refresh / edge
|
|
47
|
+
* callbacks. */
|
|
48
|
+
export interface UIVirtualizedListBase<T = unknown> extends UIElementBase<UIVirtualizedListStyle> {
|
|
38
49
|
readonly type: "vlist"
|
|
39
50
|
|
|
40
51
|
/** Number of data items (mounted or not). */
|
|
@@ -60,7 +71,31 @@ export interface UIVirtualizedList<T = unknown> extends UIElementBase<UIVirtuali
|
|
|
60
71
|
onStartReached(thresholdPx: number, callback: () => void): this
|
|
61
72
|
}
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
/** A virtualized list fed by `data` (a function over signals): write the signal, the list follows. */
|
|
75
|
+
export interface UIBoundVirtualizedList<T = unknown> extends UIVirtualizedListBase<T> {}
|
|
76
|
+
|
|
77
|
+
/** A virtualized list fed by hand. Each call goes straight to the runtime: `append` is O(1). */
|
|
78
|
+
export interface UIFedVirtualizedList<T = unknown> extends UIVirtualizedListBase<T> {
|
|
79
|
+
/** Replace the data wholesale — diffed by key like a `data` write (vanished keys are removed,
|
|
80
|
+
* new ones inserted where they stand, a reorder rewrites). */
|
|
81
|
+
setData(items: T[]): this
|
|
82
|
+
/** Append items to the end (auto-scrolls when `inverted` and resting at the bottom). */
|
|
83
|
+
append(...items: T[]): this
|
|
84
|
+
/** Insert at the start WITHOUT a scroll jump (the runtime compensates the offset). */
|
|
85
|
+
prepend(...items: T[]): this
|
|
86
|
+
/** Update items with the same keys: mounted ones re-render; the cached height persists until
|
|
87
|
+
* re-measured. */
|
|
88
|
+
update(...items: T[]): this
|
|
89
|
+
/** Remove items by key. */
|
|
90
|
+
removeByKey(...keys: string[]): this
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A windowed (virtualized) list: only the items near the viewport are mounted — for long feeds
|
|
94
|
+
* and chats. Fed by `data`, a function over signals, or by hand (`append` and the rest); which
|
|
95
|
+
* one is the config's and the type's. */
|
|
96
|
+
export type UIVirtualizedList<T = unknown> = UIBoundVirtualizedList<T> | UIFedVirtualizedList<T>
|
|
97
|
+
|
|
98
|
+
export class VListElement<T> extends Element<"vlist"> implements UIFedVirtualizedList<T> {
|
|
64
99
|
private readonly sl: ((scrollPosition: number) => void)[] = [] // scroll
|
|
65
100
|
private readonly erl: (() => void)[] = [] // endReached
|
|
66
101
|
private readonly strl: (() => void)[] = [] // startReached
|
|
@@ -74,6 +109,7 @@ export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedLi
|
|
|
74
109
|
private estimate: (item: T) => number
|
|
75
110
|
private readonly _overscan: number
|
|
76
111
|
private readonly _inverted: boolean
|
|
112
|
+
private readonly _bound: boolean
|
|
77
113
|
private _endThreshold = 0
|
|
78
114
|
private _startThreshold = 0
|
|
79
115
|
private _inited = false
|
|
@@ -90,10 +126,12 @@ export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedLi
|
|
|
90
126
|
: () => config.estimatedHeight as number
|
|
91
127
|
this._overscan = config.overscan ?? -1
|
|
92
128
|
this._inverted = config.inverted ?? false
|
|
129
|
+
const data = config.data
|
|
130
|
+
this._bound = data !== undefined
|
|
93
131
|
// The data binding is the element's (it goes with it); its first run waits a microtask so the
|
|
94
132
|
// edge thresholds chained after the constructor (`.onEndReached(…)`) are in the runtime's list
|
|
95
133
|
// state, which is created on the first write.
|
|
96
|
-
Promise.resolve().then(() => { if (this._h.id !== 0) createBinding(this, "data", () => this._apply(
|
|
134
|
+
if (data !== undefined) Promise.resolve().then(() => { if (this._h.id !== 0) createBinding(this, "data", () => this._apply(data())) })
|
|
97
135
|
}
|
|
98
136
|
|
|
99
137
|
// The runtime's list state is created on the first data write — after the app had its chance to
|
|
@@ -104,42 +142,68 @@ export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedLi
|
|
|
104
142
|
tree().vlistInit(this._h.id, this._overscan, this._inverted, this._startThreshold, this._endThreshold)
|
|
105
143
|
}
|
|
106
144
|
|
|
107
|
-
/** The
|
|
108
|
-
*
|
|
109
|
-
|
|
145
|
+
/** The two feeds are exclusive per list: a hand-made change to a list `data` feeds would be
|
|
146
|
+
* undone by the binding's next run, so it is refused and logged. */
|
|
147
|
+
private _fed(method: string): boolean {
|
|
148
|
+
if (!this._bound) return true
|
|
149
|
+
console.error(`UIVirtualizedList.${method}(): the list is fed by data — write the signal it reads instead`)
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The data as it is now against what the runtime holds. Keys gone are removals, keys new are
|
|
154
|
+
* inserts where they stand in the new order — both anchored by the runtime (a chat follows an
|
|
155
|
+
* append, rows above the reader move nothing) — as long as the keys kept keep their order;
|
|
156
|
+
* a reorder rewrites the keys (measured heights stay). A mounted row whose item is another
|
|
157
|
+
* object re-renders. */
|
|
110
158
|
private _apply(next: T[]): void {
|
|
111
159
|
const prevKeys = this.keys
|
|
112
160
|
const prevItems = this.items
|
|
113
161
|
const keys: string[] = []
|
|
114
|
-
const estimates: number[] = []
|
|
115
162
|
const items = new Map<string, T>()
|
|
116
163
|
for (const item of next) {
|
|
117
164
|
const k = this.keyOf(item)
|
|
118
165
|
if (items.has(k)) continue // a repeated key: the first row stands
|
|
119
166
|
items.set(k, item)
|
|
120
167
|
keys.push(k)
|
|
121
|
-
estimates.push(this.estimate(item))
|
|
122
168
|
}
|
|
123
169
|
this.keys = keys
|
|
124
170
|
this.items = items
|
|
125
171
|
this._ensure()
|
|
126
172
|
const id = this._h.id
|
|
127
173
|
if (id === 0) return
|
|
174
|
+
// the estimates of keys[from, to): read for the rows the runtime is told of, not for every row
|
|
175
|
+
const estimates = (from: number, to: number): number[] => {
|
|
176
|
+
const out: number[] = []
|
|
177
|
+
for (let i = from; i < to; i++) out.push(this.estimate(items.get(keys[i])!))
|
|
178
|
+
return out
|
|
179
|
+
}
|
|
128
180
|
const n = prevKeys.length, m = keys.length
|
|
129
|
-
const same = (from: number) => { for (let i = 0; i < n; i++) if (keys[from + i] !== prevKeys[i]) return false; return true }
|
|
130
181
|
if (n === 0 || m === 0) {
|
|
131
|
-
tree().vlistSetKeys(id, keys, estimates)
|
|
132
|
-
} else if (m >= n && same(0)) {
|
|
133
|
-
if (m > n) tree().vlistInsertKeys(id, -1, keys.slice(n), estimates.slice(n)) // the same keys: nothing to tell
|
|
134
|
-
} else if (m > n && same(m - n)) {
|
|
135
|
-
tree().vlistInsertKeys(id, 0, keys.slice(0, m - n), estimates.slice(0, m - n))
|
|
182
|
+
tree().vlistSetKeys(id, keys, estimates(0, m))
|
|
136
183
|
} else {
|
|
137
|
-
//
|
|
184
|
+
// the keys kept, in the old order, must be the keys kept in the new order
|
|
138
185
|
const gone: string[] = []
|
|
139
|
-
let j = 0
|
|
140
|
-
for (const k of prevKeys) {
|
|
141
|
-
|
|
142
|
-
|
|
186
|
+
let j = 0, ordered = true
|
|
187
|
+
for (const k of prevKeys) {
|
|
188
|
+
if (!items.has(k)) { gone.push(k); continue }
|
|
189
|
+
while (j < m && !prevItems.has(keys[j])) j++ // skip the new rows
|
|
190
|
+
if (keys[j] !== k) { ordered = false; break }
|
|
191
|
+
j++
|
|
192
|
+
}
|
|
193
|
+
if (!ordered) {
|
|
194
|
+
tree().vlistSetKeys(id, keys, estimates(0, m))
|
|
195
|
+
} else {
|
|
196
|
+
if (gone.length > 0) tree().vlistRemoveKeys(id, gone)
|
|
197
|
+
// runs of new rows, in order: everything before a run is already in the list, so the
|
|
198
|
+
// run's position in the new array is its index in the list as it stands
|
|
199
|
+
for (let i = 0; i < m;) {
|
|
200
|
+
if (prevItems.has(keys[i])) { i++; continue }
|
|
201
|
+
let e = i + 1
|
|
202
|
+
while (e < m && !prevItems.has(keys[e])) e++
|
|
203
|
+
tree().vlistInsertKeys(id, e === m ? -1 : i, keys.slice(i, e), estimates(i, e))
|
|
204
|
+
i = e
|
|
205
|
+
}
|
|
206
|
+
}
|
|
143
207
|
}
|
|
144
208
|
// a mounted row whose item is another object re-renders; the others pick theirs up when they re-enter
|
|
145
209
|
for (const k of this.mounted.keys()) {
|
|
@@ -147,6 +211,67 @@ export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedLi
|
|
|
147
211
|
}
|
|
148
212
|
}
|
|
149
213
|
|
|
214
|
+
/** The new rows of `items` (keys not held yet, repeats dropped), registered. */
|
|
215
|
+
private _register(items: T[]): [string[], number[]] {
|
|
216
|
+
const keys: string[] = []
|
|
217
|
+
const estimates: number[] = []
|
|
218
|
+
for (const item of items) {
|
|
219
|
+
const k = this.keyOf(item)
|
|
220
|
+
if (this.items.has(k)) continue
|
|
221
|
+
this.items.set(k, item)
|
|
222
|
+
keys.push(k)
|
|
223
|
+
estimates.push(this.estimate(item))
|
|
224
|
+
}
|
|
225
|
+
return [keys, estimates]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --- the hand feed: each call is the runtime's operation, no diff ---
|
|
229
|
+
setData(items: T[]): this {
|
|
230
|
+
if (this._fed("setData")) this._apply(items)
|
|
231
|
+
return this
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
append(...items: T[]): this {
|
|
235
|
+
if (!this._fed("append")) return this
|
|
236
|
+
const [keys, estimates] = this._register(items)
|
|
237
|
+
if (keys.length === 0) return this
|
|
238
|
+
this.keys.push(...keys)
|
|
239
|
+
this._ensure()
|
|
240
|
+
if (this._h.id !== 0) tree().vlistInsertKeys(this._h.id, -1, keys, estimates)
|
|
241
|
+
return this
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
prepend(...items: T[]): this {
|
|
245
|
+
if (!this._fed("prepend")) return this
|
|
246
|
+
const [keys, estimates] = this._register(items)
|
|
247
|
+
if (keys.length === 0) return this
|
|
248
|
+
this.keys = keys.concat(this.keys)
|
|
249
|
+
this._ensure()
|
|
250
|
+
if (this._h.id !== 0) tree().vlistInsertKeys(this._h.id, 0, keys, estimates)
|
|
251
|
+
return this
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
update(...items: T[]): this {
|
|
255
|
+
if (!this._fed("update")) return this
|
|
256
|
+
for (const item of items) {
|
|
257
|
+
const k = this.keyOf(item)
|
|
258
|
+
if (!this.items.has(k) || this.items.get(k) === item) continue
|
|
259
|
+
this.items.set(k, item)
|
|
260
|
+
if (this._h.id !== 0 && this.mounted.has(k)) tree().vlistInvalidate(this._h.id, k)
|
|
261
|
+
}
|
|
262
|
+
return this
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
removeByKey(...keys: string[]): this {
|
|
266
|
+
if (!this._fed("removeByKey")) return this
|
|
267
|
+
const gone: string[] = []
|
|
268
|
+
for (const k of keys) if (this.items.delete(k)) gone.push(k)
|
|
269
|
+
if (gone.length === 0) return this
|
|
270
|
+
this.keys = this.keys.filter((k) => this.items.has(k))
|
|
271
|
+
if (this._h.id !== 0) tree().vlistRemoveKeys(this._h.id, gone)
|
|
272
|
+
return this
|
|
273
|
+
}
|
|
274
|
+
|
|
150
275
|
get itemCount(): number { return this.items.size }
|
|
151
276
|
getItem(key: string): T | undefined { return this.items.get(key) }
|
|
152
277
|
|
|
@@ -212,8 +337,11 @@ export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedLi
|
|
|
212
337
|
}
|
|
213
338
|
}
|
|
214
339
|
|
|
215
|
-
/** Create a virtualized list
|
|
216
|
-
*
|
|
340
|
+
/** Create a virtualized list (`keyOf` + `render` + `estimatedHeight` required). With `data` the
|
|
341
|
+
* list follows the signals it reads and has no hand feed; without it, feed it with `setData` /
|
|
342
|
+
* `append` / `prepend` / `update` / `removeByKey`. Give it a size (`flexGrow: 1` as a screen body). */
|
|
343
|
+
export function UIVirtualizedList<T>(config: VListBoundConfig<T>): UIBoundVirtualizedList<T>
|
|
344
|
+
export function UIVirtualizedList<T>(config: VListFedConfig<T>): UIFedVirtualizedList<T>
|
|
217
345
|
export function UIVirtualizedList<T>(config: VListConfig<T>): UIVirtualizedList<T> {
|
|
218
346
|
return new VListElement<T>(config)
|
|
219
347
|
}
|
package/src/ui/UIWidget.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TouchStartEvent } from "../runtime/touch"
|
|
2
2
|
import { ContainerElement, buildUI, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type PaddingStyle, type PositionStyle, type UIChildArg, type UIContainerBase, type UINodeChild, type ChildrenFn } from "./UINode"
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { _appLayer } from "./UILayer"
|
|
4
|
+
import type { Presentable } from "./presentable"
|
|
5
|
+
import { TREE_FLAG_BACK, TREE_FLAG_INTERACTIVE, TREE_FLAG_OVERLAY_TAP, tree, unpin } from "./tree"
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle & PositionStyle & {
|
|
@@ -14,13 +15,13 @@ export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & Base
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
/** A floating overlay that belongs to a
|
|
18
|
-
* a sheet, a HUD, a mini-player. A widget is
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
/** A floating overlay that belongs to a DESTINATION, not to a box (docs/plans/widgets-plan.md): a
|
|
19
|
+
* dialog, a sheet, a HUD, a mini-player. A widget is one of the `widgets` of the place it belongs
|
|
20
|
+
* to — `UIScreen(content).widgets(dialog)`, `scene.widgets(hud)` — and is laid out and painted in
|
|
21
|
+
* the box of the outermost presented destination that contains it: absolutely positioned in that
|
|
22
|
+
* box, above everything else in it. A widget on a tab page covers the tab bar and rides the
|
|
23
|
+
* shell's push and pop. A widget among the children of a box is an error: a floating element of
|
|
24
|
+
* a box is an absolute child, not a widget.
|
|
24
25
|
*
|
|
25
26
|
* It shows with its place unless its style says `display: "none"`; `show()` / `hide()` flip that,
|
|
26
27
|
* and so does a state block (`$class: { display: "flex" }`). `UIModal` / `UIPopover` /
|
|
@@ -28,13 +29,12 @@ export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & Base
|
|
|
28
29
|
export interface UIWidget extends UIContainerBase<UIWidgetStyle, DrawableStyle & BaseStyle & { overlayColor?: Color | null }> {
|
|
29
30
|
readonly type: "widget",
|
|
30
31
|
|
|
31
|
-
/** Show the widget: `display: "flex"`, on top of its root — it moves to the end of its
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
/** Show the widget: `display: "flex"`, on top of its root — it moves to the end of its
|
|
33
|
+
* destination's widgets, so a dialog opened later covers an earlier one and a HUD declared after
|
|
34
|
+
* it. A widget that belongs nowhere goes ABOVE EVERY destination — a global loader, a dialog
|
|
35
|
+
* opened from anywhere — and stays there, hidden by `hide()`, until it is given a place. */
|
|
35
36
|
show(): void,
|
|
36
|
-
/** Hide the widget: `display: "none"`. It stays where it is
|
|
37
|
-
* for the next `show()`. */
|
|
37
|
+
/** Hide the widget: `display: "none"`. It stays where it is, ready for the next `show()`. */
|
|
38
38
|
hide(): void,
|
|
39
39
|
|
|
40
40
|
/** Shown right now by its own display (`show()` ran, `hide()` hasn't, no state block hides
|
|
@@ -42,6 +42,14 @@ export interface UIWidget extends UIContainerBase<UIWidgetStyle, DrawableStyle &
|
|
|
42
42
|
* visibility, not the widget's. */
|
|
43
43
|
readonly isShown: boolean,
|
|
44
44
|
|
|
45
|
+
/** @deprecated A widget is one of the `widgets` of the place it belongs to (2026-10-07): write
|
|
46
|
+
* `UIScreen(content).widgets(widget)` / `scene.widgets(widget)` instead. This alias is
|
|
47
|
+
* `owner.widgets.append(widget)` with the widget hidden, so the `show()` that follows flips it as
|
|
48
|
+
* on any placed widget; `attachTo(null)` takes it out, and the next `show()` puts it above every
|
|
49
|
+
* destination. For the projects compiled before the change; removed with the release that
|
|
50
|
+
* recompiles them. */
|
|
51
|
+
attachTo(owner: Presentable | null): this,
|
|
52
|
+
|
|
45
53
|
/** Touch began on the widget; `ev.track(...)` takes over the rest of the gesture. */
|
|
46
54
|
onTouchStart(callback: (ev: TouchStartEvent<UIWidget>) => void): this,
|
|
47
55
|
/** Tap on the overlay layer (requires an `overlayColor`). */
|
|
@@ -74,7 +82,7 @@ export class WidgetElement extends ContainerElement<"widget"> {
|
|
|
74
82
|
// is a display flip wherever the widget is, so its pose plays (a modal's fade) exactly as on
|
|
75
83
|
// a placed one. Appended shown, it would merely appear: a widget shows with its place, no pose.
|
|
76
84
|
this.style({ display: "none" } as any)
|
|
77
|
-
|
|
85
|
+
_appLayer().append(this as any) // pins it: _pinned reads the display
|
|
78
86
|
} else {
|
|
79
87
|
// Shown = on top of its root: a show() moves the widget to the TAIL of its parent's children
|
|
80
88
|
// (child order is the one ordering rule), so a modal opened later covers an earlier one and
|
|
@@ -97,6 +105,21 @@ export class WidgetElement extends ContainerElement<"widget"> {
|
|
|
97
105
|
get isShown(): boolean {
|
|
98
106
|
return this._shown
|
|
99
107
|
}
|
|
108
|
+
/** @deprecated see the interface */
|
|
109
|
+
attachTo(owner: Presentable | null): this {
|
|
110
|
+
if (this._h.id === 0) return this
|
|
111
|
+
const parentId = tree().parent(this._h.id)
|
|
112
|
+
if (parentId !== 0) { // one parent: a re-attach moves it
|
|
113
|
+
tree().remove(parentId, this._h.id)
|
|
114
|
+
unpin(this._h.id) // as a node's remove(): shown nowhere
|
|
115
|
+
}
|
|
116
|
+
if (owner) {
|
|
117
|
+
// Hidden first: a widget shows with its place, and the old contract showed it on show() only.
|
|
118
|
+
this.style({ display: "none" } as any)
|
|
119
|
+
owner.widgets.append(this as any)
|
|
120
|
+
}
|
|
121
|
+
return this
|
|
122
|
+
}
|
|
100
123
|
/** @internal The intent changed (the app's call, or the runtime's report of a cascade flip):
|
|
101
124
|
* subclasses hook the change (UIModal's onOpen / onClose). */
|
|
102
125
|
_setShown(shown: boolean): void {
|
|
@@ -163,8 +186,8 @@ const makeWidget = (style: any, children: any) => new WidgetElement("widget", st
|
|
|
163
186
|
/** @internal Compiler fast path (chisel `flatten_ui` raw lowering) — see UIContainer. */
|
|
164
187
|
export const __UIWidget = (children: UINodeChild[] | ChildrenFn): UIWidget => makeWidget({}, children)
|
|
165
188
|
|
|
166
|
-
/** Create a widget (same argument forms as `UIColumn`).
|
|
167
|
-
*
|
|
189
|
+
/** Create a widget (same argument forms as `UIColumn`). Give it to the destination it belongs to —
|
|
190
|
+
* `UIScreen(content).widgets(w)`, `scene.widgets(w)` — position it with absolute-style props
|
|
168
191
|
* (`top`/`left`/`bottom`/`right`), then `show()`/`hide()`. */
|
|
169
192
|
export function UIWidget(...children: UIChildArg[]): UIWidget;
|
|
170
193
|
export function UIWidget(children: UINodeChild[] | ChildrenFn): UIWidget;
|
package/src/ui/presentable.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Anything that can occupy the app's main slot implements it: UIScreen, Scene (+ARScene),
|
|
3
3
|
// Scene2D, NativeView, UIVideo. Exactly one Presentable is visible at a time — a screen no
|
|
4
4
|
// longer overlays a scene, it replaces it (with a transition); UI over a scene goes through
|
|
5
|
-
// `scene.
|
|
5
|
+
// `scene.widgets(hud)` — a widget belongs to a destination (docs/plans/widgets-plan.md).
|
|
6
6
|
//
|
|
7
7
|
// A destination is addressed by (kind, id): a screen / video by its node id, a scene by its
|
|
8
8
|
// scene id, a native view by its viewId (`_dest()`). The runtime reports the lifecycle back through
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// element code into engine-only bundles.
|
|
13
13
|
|
|
14
14
|
import type { EasingInput } from "../animate/tween/easing"
|
|
15
|
-
import type {
|
|
15
|
+
import type { WidgetSet } from "./UILayer"
|
|
16
16
|
|
|
17
17
|
/** The built-in transitions (ui/transitions.ts holds what each one is). `push` / `pop` are the
|
|
18
18
|
* stacked-navigation pair — the incoming screen slides over the other one, which drifts and dims;
|
|
@@ -101,12 +101,12 @@ export interface Presentable {
|
|
|
101
101
|
onOpen(callback: () => void): this
|
|
102
102
|
onClose(callback: () => void): this
|
|
103
103
|
onBack(callback: () => void): this
|
|
104
|
-
/** The destination's widgets (docs/plans/widgets-plan.md): a dialog, a HUD, a sheet
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
|
|
104
|
+
/** The destination's widgets (docs/plans/widgets-plan.md): a dialog, a HUD, a sheet — what floats
|
|
105
|
+
* over it. `x.widgets(a, b)` sets them, `x.widgets.append` / `.remove` manage them, `x.widgets.list`
|
|
106
|
+
* reads them; the same on a `UIScreen` (apart from its `children`), a `Scene`, a `Scene2D`, a
|
|
107
|
+
* `NativeView`, a `UIVideo`. A widget shows with its destination unless its style says
|
|
108
|
+
* `display: "none"`; `show()` / `hide()` flip that. */
|
|
109
|
+
readonly widgets: WidgetSet<this>
|
|
110
110
|
/** @internal Async work that must finish BEFORE this destination replaces the current one
|
|
111
111
|
* (camera permission, warm render, session launch — ARScene). The previous destination —
|
|
112
112
|
* typically a loading screen — stays visible while it runs; `open()` and `Router.push/replace`
|
package/src/ui/transitions.ts
CHANGED
|
@@ -128,7 +128,7 @@ type Side = { lanes: Lane[], atMs: number, durMs: number, easing: EasingSpec }
|
|
|
128
128
|
const list = <T>(v: T | T[]): T[] => (Array.isArray(v) ? v : [v])
|
|
129
129
|
|
|
130
130
|
const transformLane = (value: string | string[]): Lane | null => {
|
|
131
|
-
const parsed = list(value).map((v) => parseTransformList(v
|
|
131
|
+
const parsed = list(value).map((v) => parseTransformList(v))
|
|
132
132
|
const first = parsed[0]
|
|
133
133
|
if (!first || parsed.some((p) => p === null || p.sig !== first.sig)) {
|
|
134
134
|
warnOnce("transition:transform", "a transition's transform must be a list of translate / scale / rotate, the same functions in every keyframe — ignored")
|
package/src/version.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// same manifest). Its MAJOR is the bundle ↔ runtime contract: a bundle's `// sdk:` header line
|
|
5
5
|
// and a host's embedded version must agree on the major for the host to run the bundle; minor
|
|
6
6
|
// and patch never gate anything (features are detected, never versioned).
|
|
7
|
-
export const SDK_VERSION = "2.2.
|
|
7
|
+
export const SDK_VERSION = "2.2.5"
|
|
8
8
|
|
|
9
9
|
/** The major of a semver string, or null when it is not one. */
|
|
10
10
|
export const sdkMajor = (version: string | null | undefined): number | null => {
|
|
@@ -87,7 +87,12 @@ export class FakeTree {
|
|
|
87
87
|
if (!p || !c) return
|
|
88
88
|
if (c.parent !== 0) return
|
|
89
89
|
c.parent = parent
|
|
90
|
-
|
|
90
|
+
// tree.cpp treeInsert: a widget is the TAIL of a screen's children (its index counted past the
|
|
91
|
+
// content, clamped into the tail); a content index past the content lands before the widgets.
|
|
92
|
+
const own = p.type === "layer" ? 0 : p.children.filter((x) => this.nodes.get(x)?.type !== "widget").length
|
|
93
|
+
const i = c.type === "widget"
|
|
94
|
+
? own + Math.max(0, Math.min(index < own ? 0 : index - own, p.children.length - own))
|
|
95
|
+
: Math.max(0, Math.min(index, own))
|
|
91
96
|
p.children.splice(i, 0, child)
|
|
92
97
|
}
|
|
93
98
|
remove = (parent: number, child: number): void => {
|