lecodes-cli 0.10.4 → 0.12.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 +1 -1
- package/dist/index.js +48887 -6261
- package/package.json +4 -3
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk/compile/aspectMacro.ts +42 -0
- package/runtime/sdk/compile/assetIconMacro.ts +382 -0
- package/runtime/sdk/compile/assetMacro.ts +45 -0
- package/runtime/sdk/compile/assetName.ts +50 -0
- package/runtime/sdk/compile/bundler.ts +244 -0
- package/runtime/sdk/compile/compileProject.ts +106 -0
- package/runtime/sdk/compile/detectEntry.ts +125 -0
- package/runtime/sdk/compile/fontMacro.ts +459 -0
- package/runtime/sdk/compile/fontRegistry.ts +78 -0
- package/runtime/sdk/compile/header.ts +55 -0
- package/runtime/sdk/compile/index.ts +53 -0
- package/runtime/sdk/compile/libraryImports.ts +52 -0
- package/runtime/sdk/compile/sceneEditor.ts +78 -0
- package/runtime/sdk/compile/sfnt.ts +98 -0
- package/runtime/sdk/compile/sourcemap.ts +25 -0
- package/runtime/sdk/g2/Node2D.ts +3 -0
- package/runtime/sdk/g2/SpriteAnimation.ts +101 -20
- package/runtime/sdk/g2/SpriteSheet.ts +166 -0
- package/runtime/sdk/g2/Tileset.ts +71 -0
- package/runtime/sdk/g2/autotile.ts +394 -0
- package/runtime/sdk/g2/cells.ts +91 -0
- package/runtime/sdk/g2/defineScene2d.ts +381 -0
- package/runtime/sdk/g2/scenarios2d.ts +69 -0
- package/runtime/sdk/inject.ts +18 -0
- package/runtime/sdk/runtime/app.ts +21 -3
- package/runtime/sdk/scene/defineScene.ts +10 -70
- package/runtime/sdk/scene/grammar.ts +85 -0
- package/runtime/sdk/ui/NativeView.ts +9 -12
- package/runtime/sdk/ui/UI.ts +9 -1
- package/runtime/sdk/ui/UIBottomSheet.ts +10 -20
- package/runtime/sdk/ui/UIButton.ts +19 -36
- package/runtime/sdk/ui/UIContainer.ts +41 -103
- package/runtime/sdk/ui/UIImage.ts +10 -18
- package/runtime/sdk/ui/UIInput.ts +14 -26
- package/runtime/sdk/ui/UIModal.ts +10 -20
- package/runtime/sdk/ui/UINode.ts +142 -36
- package/runtime/sdk/ui/UIPager.ts +29 -28
- package/runtime/sdk/ui/UIPopover.ts +10 -16
- package/runtime/sdk/ui/UIScreen.ts +23 -40
- package/runtime/sdk/ui/UIScrollable.ts +37 -47
- package/runtime/sdk/ui/UISpacer.ts +4 -8
- package/runtime/sdk/ui/UITabs.ts +175 -0
- package/runtime/sdk/ui/UIText.ts +7 -13
- package/runtime/sdk/ui/UIVideo.ts +11 -13
- package/runtime/sdk/ui/UIVirtualizedList.ts +43 -49
- package/runtime/sdk/ui/UIWidget.ts +24 -36
- package/runtime/sdk/ui/fonts.ts +3 -0
- package/runtime/sdk/ui/router.ts +13 -0
- package/runtime/sdk/ui/theme.ts +21 -30
- package/runtime/sdk-types.json +1 -1
package/runtime/sdk/ui/UINode.ts
CHANGED
|
@@ -10,10 +10,19 @@ export interface UINode {
|
|
|
10
10
|
readonly class: Classes<this>
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** A child slot value: a node, or falsy (conditional rendering — falsy entries are skipped). */
|
|
13
14
|
export type UINodeChild = UINode | null | undefined | false
|
|
14
15
|
|
|
16
|
+
/** A factory child argument: a node (or falsy for conditionals), or an array of them — arrays are
|
|
17
|
+
* flattened one level at the argument position, so `UIColumn(header, items.map(row), footer)`
|
|
18
|
+
* needs no spread. */
|
|
19
|
+
export type UIChildArg = UINodeChild | UINodeChild[]
|
|
20
|
+
|
|
21
|
+
/** A color: CSS-style string (`"#1c1c1e"`, `"rgba(0,0,0,0.5)"`, `"var(--primaryColor)"`) or
|
|
22
|
+
* packed number; `null` clears. */
|
|
15
23
|
export type Color = number | string | null
|
|
16
24
|
|
|
25
|
+
/** Style props every element accepts. */
|
|
17
26
|
export type BaseStyle = {
|
|
18
27
|
opacity?: number | `${number}`,
|
|
19
28
|
transform?: string | null,
|
|
@@ -29,6 +38,7 @@ export type BaseStyle = {
|
|
|
29
38
|
name?: string,
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
/** Painted-box props: background, gradient, border, radius. */
|
|
32
42
|
export type DrawableStyle = {
|
|
33
43
|
backgroundImage?: FetchResponse | File | string | null,
|
|
34
44
|
bgGradient?: string,
|
|
@@ -62,6 +72,7 @@ export type DrawableStyle = {
|
|
|
62
72
|
pointerEvents?: "all" | "none",
|
|
63
73
|
}
|
|
64
74
|
|
|
75
|
+
/** Flex-layout props of a container (how it lays out its children). */
|
|
65
76
|
export type ContainerStyle = {
|
|
66
77
|
flexDirection?: "row" | "column",
|
|
67
78
|
justifyContent?: "flex-start" | "center" | "flex-end" | "space-between" | "space-evenly",
|
|
@@ -70,6 +81,7 @@ export type ContainerStyle = {
|
|
|
70
81
|
flexWrap?: "nowrap" | "wrap" | "wrap-reverse"
|
|
71
82
|
}
|
|
72
83
|
|
|
84
|
+
/** A dimension: a number (logical px) or a CSS-style unit / function string. */
|
|
73
85
|
type UIValue = number |
|
|
74
86
|
`${number}px` |
|
|
75
87
|
`${number}vw` |
|
|
@@ -84,6 +96,8 @@ type UIValue = number |
|
|
|
84
96
|
`clamp(${string})` |
|
|
85
97
|
`var(--${string})`
|
|
86
98
|
|
|
99
|
+
/** Padding, with `p`/`px`/`pl`… shorthands. `safe-*` insets by the device safe area,
|
|
100
|
+
* `comfort-*` by the theme's comfort knobs (max of both). */
|
|
87
101
|
export type PaddingStyle = {
|
|
88
102
|
paddingLeft?: UIValue | "safe-left" | "comfort-left",
|
|
89
103
|
paddingTop?: UIValue | "safe-top" | "comfort-top",
|
|
@@ -102,8 +116,7 @@ export type PaddingStyle = {
|
|
|
102
116
|
py?: UIValue | "comfort-y",
|
|
103
117
|
}
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
/** Margin, with `m`/`mx`/`ml`… shorthands; `"auto"` centers/pushes like CSS. */
|
|
107
120
|
export type MarginStyle = {
|
|
108
121
|
marginLeft?: UIValue | "auto" | "safe-left" | "comfort-left",
|
|
109
122
|
marginTop?: UIValue | "auto" | "safe-top" | "comfort-top",
|
|
@@ -122,6 +135,7 @@ export type MarginStyle = {
|
|
|
122
135
|
my?: UIValue | "auto" | "comfort-y",
|
|
123
136
|
}
|
|
124
137
|
|
|
138
|
+
/** Box position (for `position: "absolute"`) and size constraints. */
|
|
125
139
|
export type PositionStyle = {
|
|
126
140
|
top?: UIValue | `${number}%` | 'safe-top' | 'comfort-top',
|
|
127
141
|
left?: UIValue | `${number}%` | 'safe-left' | 'comfort-left',
|
|
@@ -137,6 +151,7 @@ export type PositionStyle = {
|
|
|
137
151
|
minHeight?: UIValue | `${number}%` | "auto",
|
|
138
152
|
}
|
|
139
153
|
|
|
154
|
+
/** The base style of every element: box + flex-child props. */
|
|
140
155
|
export type ElementStyle = PaddingStyle & MarginStyle & BaseStyle & PositionStyle & {
|
|
141
156
|
position?: "absolute" | "static" | "relative",
|
|
142
157
|
aspectRatio?: number,
|
|
@@ -148,6 +163,7 @@ export type ElementStyle = PaddingStyle & MarginStyle & BaseStyle & PositionStyl
|
|
|
148
163
|
display?: "none" | "flex"
|
|
149
164
|
}
|
|
150
165
|
|
|
166
|
+
/** Typography props. */
|
|
151
167
|
export type TextStyle = {
|
|
152
168
|
textAlign?: "start" | "center" | "end" | "left" | "right"
|
|
153
169
|
fontFamily?: string,
|
|
@@ -189,7 +205,7 @@ function applyBindings(el: any, fns: [string, () => any][]) {
|
|
|
189
205
|
}
|
|
190
206
|
}
|
|
191
207
|
|
|
192
|
-
//
|
|
208
|
+
// NB: returns `this` — `.style()` must chain. Don't rewrite.
|
|
193
209
|
function styleFunction(this: any, newStyle: any) {
|
|
194
210
|
// `name` is a JS-node selector (read by hosts off el.name), not a layout property — extract it
|
|
195
211
|
// exactly like the constructor so `.style({ name })` is equivalent to the first-arg style. Left
|
|
@@ -305,26 +321,21 @@ const classHandler: ProxyHandler<any> = {
|
|
|
305
321
|
}
|
|
306
322
|
}
|
|
307
323
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
// Reactive style input: every primitive-valued prop also accepts a `() => value` binding that
|
|
313
|
-
// re-applies when a signal it read changes. Nested state blocks (onPressed, onLandscape, …) stay
|
|
314
|
-
// static — bindings are extracted at the top level only.
|
|
324
|
+
/** Reactive style input: every primitive-valued prop also accepts a `() => value` binding that
|
|
325
|
+
* re-applies when a signal it read changes. Nested state blocks (onPressed, onLandscape, …) stay
|
|
326
|
+
* static — bindings are extracted at the top level only. */
|
|
315
327
|
export type Reactive<T> = { [K in keyof T]: NonNullable<T[K]> extends object ? T[K] : T[K] | (() => T[K]) }
|
|
316
328
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// tests/ui-types.test.ts.
|
|
329
|
+
/** User-defined style classes: any `$`-prefixed key in `.style()` declares a state block toggled
|
|
330
|
+
* from code via the `el.class` proxy; `duration`/`delay` animate the swap. Class state INHERITS
|
|
331
|
+
* down the tree (CSS-`.dark`-on-body style): a class set on a node also activates same-name `$`
|
|
332
|
+
* blocks on all descendants within the same root (docs/style-class-cascade-plan.md).
|
|
333
|
+
* `$pressed`/`$focused` are reserved — hosts toggle them on press/focus (they beat other
|
|
334
|
+
* classes, lose to onPressed/onFocused), so children can react to an ancestor's press.
|
|
335
|
+
*
|
|
336
|
+
* Instantiated with the node's FULL style `T` — a class block accepts everything the node's
|
|
337
|
+
* `.style()` does, like `onLandscape`. Narrowing it wrongly rejects valid props such as
|
|
338
|
+
* `$active: { color }`; guarded by tests/ui-types.test.ts. */
|
|
328
339
|
export type ClassStyles<T> = { [key: `$${string}`]: T & { duration?: number, delay?: number } }
|
|
329
340
|
|
|
330
341
|
/** Value accepted by a `el.class` write: a boolean sets the class, a `() => boolean` binds it —
|
|
@@ -351,15 +362,46 @@ export type ClassValue = boolean | (() => boolean)
|
|
|
351
362
|
* single-key writes are runtime-coerced with `!!`. */
|
|
352
363
|
export type Classes<R> = ((classes: Record<string, ClassValue>) => R) & { [key: string]: any }
|
|
353
364
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
365
|
+
/** Members every UI element shares — element interfaces extend this so docs and types live in one
|
|
366
|
+
* place. `S` is the element's style object; `A` is the subset `animateTo` accepts (defaults to `S`). */
|
|
367
|
+
export interface UIElementBase<S extends object, A extends object = S> {
|
|
368
|
+
// NB: no `name` member here. Declaring it would give every element a property in common with
|
|
369
|
+
// the style types (BaseStyle.name), letting TS's weak-type check match a NODE against a legacy
|
|
370
|
+
// style-first overload — `UIPager(UIText("x"))` must stay a type error (tests/ui-types.test.ts).
|
|
371
|
+
/** Style: `.style({...})` merges (chainable); `el.style.key = v` writes one prop, a `() => v`
|
|
372
|
+
* value binds it to signals — see {@link Style}. */
|
|
373
|
+
style: Style<this, S>
|
|
374
|
+
/** Tween to the target style — meta keys `duration`/`delay`/`loop`/…, see {@link AnimateStyle}. */
|
|
375
|
+
animateTo: AnimateStyle<this, A>
|
|
376
|
+
/** Tween from the given style to the current one (entrance animations). */
|
|
377
|
+
animateFrom: AnimateStyle<this, A>
|
|
378
|
+
/** Fires after every layout pass with the parent-relative box. */
|
|
379
|
+
onLayout(onLayout: OnLayoutCallback): this
|
|
380
|
+
/** Absolute rect in device space, read live (includes scroll); `null` before mount. */
|
|
381
|
+
getBoundingClientRect(): BoundingClientRect | null
|
|
382
|
+
/** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
|
|
383
|
+
* `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
|
|
384
|
+
* Classes cascade to descendants. See {@link Classes}. */
|
|
385
|
+
readonly class: Classes<this>
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** {@link UIElementBase} plus the child-management surface every container shares. */
|
|
389
|
+
export interface UIContainerBase<S extends object, A extends object = S> extends UIElementBase<S, A> {
|
|
390
|
+
/** Append children. */
|
|
391
|
+
append(...nodes: UINodeChild[]): this
|
|
392
|
+
/** Insert children at `index`. */
|
|
393
|
+
insert(index: number, ...nodes: UINodeChild[]): this
|
|
394
|
+
/** Remove (unmount) the given children. */
|
|
395
|
+
remove(...nodes: UINodeChild[]): this
|
|
396
|
+
/** Replace all children — an array, or a function for reactive children. */
|
|
397
|
+
setContent(nodes: UINodeChild[] | ChildrenFn): this
|
|
398
|
+
/** The children array — mutate via `append`/`insert`/`remove`/`setContent`. */
|
|
399
|
+
readonly children: UINodeChild[]
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Orientation state blocks — override props applied only in landscape / portrait (like a
|
|
403
|
+
* `$`-class, keyed by device orientation instead of `el.class`). Same full-`T` rule as class
|
|
404
|
+
* blocks. */
|
|
363
405
|
export type OrientationStyles<T> = {
|
|
364
406
|
onLandscape?: T,
|
|
365
407
|
onPortrait?: T,
|
|
@@ -368,11 +410,19 @@ export type OrientationStyles<T> = {
|
|
|
368
410
|
onPortait?: T,
|
|
369
411
|
}
|
|
370
412
|
|
|
413
|
+
// NB: do NOT annotate `this: R` on StyleFn. As a parameter it makes R contravariant, turning every
|
|
414
|
+
// node type INVARIANT (UIScreen<false> stopped being assignable to UIScreen<boolean>). Runtime
|
|
415
|
+
// `this` is already fixed by `styleFunction.bind(this)`; R stays covariant in the return, so
|
|
416
|
+
// `.style()` still chains. Guarded by tests/ui-types.test-d.ts.
|
|
371
417
|
export type StyleFn <R, T extends object> = ((style: Reactive<T> & OrientationStyles<T> & ClassStyles<T>) => R)
|
|
418
|
+
|
|
419
|
+
/** The `el.style` surface: callable — `.style({...})` merges and returns the element for
|
|
420
|
+
* chaining — and per-key readable/writable (`el.style.opacity = 0.5`; a `() => value` write
|
|
421
|
+
* installs a reactive binding). */
|
|
372
422
|
export type Style <R, T extends object> = StyleFn<R,T> & T & OrientationStyles<T> & ClassStyles<T>
|
|
373
423
|
|
|
374
|
-
|
|
375
|
-
|
|
424
|
+
/** The `animateTo`/`animateFrom` surface: target style props plus the flat meta keys below
|
|
425
|
+
* (stripped before the commit into the element's style). */
|
|
376
426
|
export type AnimateStyle<R, T extends object> = ((style: T & {
|
|
377
427
|
/** Tween length in **milliseconds** (default 225). */
|
|
378
428
|
duration?: number,
|
|
@@ -397,10 +447,11 @@ const ANIMATE_META = new Set(["duration", "delay", "layer", "commit", "loop", "l
|
|
|
397
447
|
export type AppearStyle <R, T> = (arg: { from: T, duration?: number, delay?: number }) => R
|
|
398
448
|
export type DisappearStyle <R, T> = (arg: { to: T, duration?: number, delay?: number }) => R
|
|
399
449
|
|
|
450
|
+
/** `onLayout` payload — the node's box in PARENT-relative coordinates. */
|
|
400
451
|
export type OnLayoutCallback = (layout: { left: number, top: number, width: number, height: number }) => void
|
|
401
452
|
|
|
402
|
-
|
|
403
|
-
|
|
453
|
+
/** Web-DOMRect shape on purpose (getBoundingClientRect priors must hold): all eight fields, with
|
|
454
|
+
* right/bottom/x/y derived SDK-side — hosts only report [left, top, width, height]. */
|
|
404
455
|
export type BoundingClientRect = {
|
|
405
456
|
x: number, y: number,
|
|
406
457
|
left: number, top: number, right: number, bottom: number,
|
|
@@ -462,10 +513,10 @@ export class Element<T extends string> {
|
|
|
462
513
|
return proxy
|
|
463
514
|
}
|
|
464
515
|
|
|
516
|
+
/** Tween to the target style and commit it (unless `commit: false` or looping — a loop is an
|
|
517
|
+
* effect, not a state change). Meta keys: `duration`/`delay`/`loop`/… — see {@link AnimateStyle}. */
|
|
465
518
|
animateTo(style: any): this{
|
|
466
519
|
_creatorUI.animateTo(this._id, style)
|
|
467
|
-
// A loop is an effect, not a state change — looping animations never commit. Meta keys
|
|
468
|
-
// (duration/delay/…) stay out of the stored style.
|
|
469
520
|
if (style.commit !== false && !style.loop) {
|
|
470
521
|
for (const key in style) {
|
|
471
522
|
if (!ANIMATE_META.has(key)) this._style[key] = style[key]
|
|
@@ -473,6 +524,7 @@ export class Element<T extends string> {
|
|
|
473
524
|
}
|
|
474
525
|
return this
|
|
475
526
|
}
|
|
527
|
+
/** Tween FROM the given style to the element's current one (entrance animations). */
|
|
476
528
|
animateFrom(style: any): this{
|
|
477
529
|
_creatorUI.animateFrom(this._id, style)
|
|
478
530
|
return this
|
|
@@ -511,6 +563,7 @@ export class Element<T extends string> {
|
|
|
511
563
|
}
|
|
512
564
|
|
|
513
565
|
protected ll?: OnLayoutCallback[]
|
|
566
|
+
/** Observe layout: fires after every host layout pass with the parent-relative box. */
|
|
514
567
|
onLayout(onLayout: OnLayoutCallback): this {
|
|
515
568
|
if (this.ll) {
|
|
516
569
|
this.ll.push(onLayout)
|
|
@@ -536,6 +589,8 @@ export class Element<T extends string> {
|
|
|
536
589
|
|
|
537
590
|
// ---- reactive children --------------------------------------------------------------------------
|
|
538
591
|
|
|
592
|
+
/** Reactive children: re-runs when a signal it read changes; the result is reconciled against
|
|
593
|
+
* the mounted children (kept nodes stay mounted, state intact). */
|
|
539
594
|
export type ChildrenFn = () => UINodeChild[]
|
|
540
595
|
|
|
541
596
|
// The container whose children binding is currently executing. __uiMap uses it as the cache owner;
|
|
@@ -580,6 +635,53 @@ export function __uiMap(list: any, render: (item: any, index: number) => any, sl
|
|
|
580
635
|
return out
|
|
581
636
|
}
|
|
582
637
|
|
|
638
|
+
// ---- factory argument dispatch ------------------------------------------------------------------
|
|
639
|
+
|
|
640
|
+
/** @internal Shared argument dispatch for the UI factories (`UIRow(...)`, `UIButton(...)`, …).
|
|
641
|
+
* Children are variadic; an array argument is flattened one level, so both the legacy
|
|
642
|
+
* `UIColumn([a, b])` and `UIColumn(header, items.map(row), footer)` work. A lone function
|
|
643
|
+
* argument is a reactive {@link ChildrenFn}. A plain non-node object in first position is the
|
|
644
|
+
* legacy style bag: it is spread over `defaults`, or — when `defaults` is null — stored as-is
|
|
645
|
+
* (the Element constructor mutates it in place, matching the old path). The rest-args array is
|
|
646
|
+
* taken over as the children array when nothing needs flattening — callers must hand it off. */
|
|
647
|
+
export function buildUI<R>(args: any[], defaults: any, make: (style: any, children: any[] | ChildrenFn) => R): R {
|
|
648
|
+
const n = args.length
|
|
649
|
+
const a0 = n > 0 ? args[0] : undefined
|
|
650
|
+
let style: any
|
|
651
|
+
let start = 0
|
|
652
|
+
if (a0 !== null && typeof a0 === "object" && !Array.isArray(a0) && !(a0 instanceof Element)) {
|
|
653
|
+
style = defaults ? { ...defaults, ...a0 } : a0
|
|
654
|
+
start = 1
|
|
655
|
+
} else {
|
|
656
|
+
style = defaults ? { ...defaults } : {}
|
|
657
|
+
}
|
|
658
|
+
if (n === start + 1) {
|
|
659
|
+
const a = args[start]
|
|
660
|
+
// lone function → reactive children; lone array → used as the children array directly
|
|
661
|
+
// (the legacy zero-copy path — nested arrays inside it are NOT flattened)
|
|
662
|
+
if (typeof a === "function" || Array.isArray(a)) return make(style, a)
|
|
663
|
+
}
|
|
664
|
+
// Single pass: reuse the rest-args array as the children array until the first array argument
|
|
665
|
+
// shows up, then copy the prefix once and flatten from there. Falsy entries stay — they are
|
|
666
|
+
// compacted at mount like always.
|
|
667
|
+
let flat: any[] | undefined
|
|
668
|
+
for (let i = start; i < n; i++) {
|
|
669
|
+
const a = args[i]
|
|
670
|
+
if (flat === undefined) {
|
|
671
|
+
if (Array.isArray(a)) {
|
|
672
|
+
flat = args.slice(start, i)
|
|
673
|
+
for (let j = 0; j < a.length; j++) flat.push(a[j])
|
|
674
|
+
}
|
|
675
|
+
} else if (Array.isArray(a)) {
|
|
676
|
+
for (let j = 0; j < a.length; j++) flat.push(a[j])
|
|
677
|
+
} else {
|
|
678
|
+
flat.push(a)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (flat !== undefined) return make(style, flat)
|
|
682
|
+
return make(style, start === 0 ? args : args.slice(1))
|
|
683
|
+
}
|
|
684
|
+
|
|
583
685
|
export class ContainerElement<T extends string> extends Element<T> {
|
|
584
686
|
children: any[] = []
|
|
585
687
|
|
|
@@ -641,6 +743,7 @@ export class ContainerElement<T extends string> extends Element<T> {
|
|
|
641
743
|
}
|
|
642
744
|
}
|
|
643
745
|
|
|
746
|
+
/** Append children (falsy entries are kept in the array but never mounted). */
|
|
644
747
|
append(...nodes: UINodeChild[]): this {
|
|
645
748
|
if (this._id === 0) {
|
|
646
749
|
this.children.push(...nodes)
|
|
@@ -660,6 +763,7 @@ export class ContainerElement<T extends string> extends Element<T> {
|
|
|
660
763
|
this.children.push(...nodes)
|
|
661
764
|
return this
|
|
662
765
|
}
|
|
766
|
+
/** Insert children at `index` of the children array. */
|
|
663
767
|
insert(index: number, ...nodes: UINodeChild[]): this {
|
|
664
768
|
if (this._id === 0) {
|
|
665
769
|
this.children.splice(index, 0, ...nodes)
|
|
@@ -680,6 +784,7 @@ export class ContainerElement<T extends string> extends Element<T> {
|
|
|
680
784
|
this.children.splice(index, 0, ...nodes)
|
|
681
785
|
return this
|
|
682
786
|
}
|
|
787
|
+
/** Remove (unmount) the given children. */
|
|
683
788
|
remove(...nodesToDelete: UINode[]): this {
|
|
684
789
|
const set = new Set(nodesToDelete)
|
|
685
790
|
|
|
@@ -704,6 +809,7 @@ export class ContainerElement<T extends string> extends Element<T> {
|
|
|
704
809
|
return this
|
|
705
810
|
}
|
|
706
811
|
|
|
812
|
+
/** Replace all children — a plain array, or a function for reactive children. */
|
|
707
813
|
setContent(children: UINodeChild[] | ChildrenFn): this {
|
|
708
814
|
if (typeof children === "function") {
|
|
709
815
|
this._bindChildren(children) // createBinding replaces a previous "#children" binding
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContainerElement, type
|
|
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
4
|
|
|
@@ -41,11 +41,8 @@ const _activeStack: PagerElement[] = []
|
|
|
41
41
|
*
|
|
42
42
|
* To open something over *everything* (above bars, above the pager), use `Router.push` instead.
|
|
43
43
|
*/
|
|
44
|
-
export interface UIPager {
|
|
44
|
+
export interface UIPager extends UIElementBase<UIPagerStyle, DrawableStyle & BaseStyle> {
|
|
45
45
|
readonly type: "pager",
|
|
46
|
-
style: Style<this, UIPagerStyle>,
|
|
47
|
-
animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>,
|
|
48
|
-
animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>,
|
|
49
46
|
|
|
50
47
|
/** Replace the pager's tabs — one screen per tab, in order. Every tab becomes a fresh
|
|
51
48
|
* one-screen stack (pushed screens are dropped) and the selection is clamped into range;
|
|
@@ -80,14 +77,6 @@ export interface UIPager {
|
|
|
80
77
|
readonly depth: number,
|
|
81
78
|
/** Fires when the current tab's depth changes — push, pop, replace, or a back-swipe. */
|
|
82
79
|
onChange(callback: (depth: number) => void): this,
|
|
83
|
-
|
|
84
|
-
onLayout(onLayout: OnLayoutCallback): this,
|
|
85
|
-
getBoundingClientRect(): BoundingClientRect | null,
|
|
86
|
-
|
|
87
|
-
/** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
|
|
88
|
-
* `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
|
|
89
|
-
* Classes cascade to descendants. See {@link Classes}. */
|
|
90
|
-
readonly class: Classes<this>,
|
|
91
80
|
}
|
|
92
81
|
|
|
93
82
|
export class PagerElement extends ContainerElement<"pager"> {
|
|
@@ -104,7 +93,10 @@ export class PagerElement extends ContainerElement<"pager"> {
|
|
|
104
93
|
private ncl: ((depth: number) => void)[] = [] // onChange listeners
|
|
105
94
|
|
|
106
95
|
constructor(style: UIPagerStyle | null, tabs: UIScreen[]) {
|
|
107
|
-
|
|
96
|
+
// flexShrink: 1 default (SDK-owned, like UIScrollable's): pages are separate layout roots
|
|
97
|
+
// sized to the pager's box, so yoga sees no shrinkable content — without this a pager in a
|
|
98
|
+
// flex column overflows the screen instead of leaving room for the tab bar.
|
|
99
|
+
super("pager", { flexShrink: 1, ...style }, tabs as any)
|
|
108
100
|
this._lens = tabs.map(() => 1)
|
|
109
101
|
for (const s of this.children) this._track(s)
|
|
110
102
|
}
|
|
@@ -294,13 +286,13 @@ const isScreenArg = (v: any): boolean => v != null && typeof v === "object" && v
|
|
|
294
286
|
/** Callable factory + ambient statics. The name `UIPager` is a value (this factory) and a type
|
|
295
287
|
* (the instance interface above) — a standard value/type merge. */
|
|
296
288
|
interface UIPagerConstructor {
|
|
297
|
-
/**
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
*
|
|
301
|
-
(tabs: UIScreen[]): UIPager
|
|
302
|
-
/**
|
|
303
|
-
(style: UIPagerStyle, content
|
|
289
|
+
/** Tabs — each argument is a screen or an array of screens (arrays flatten one level, falsy
|
|
290
|
+
* entries are skipped). One screen → a plain navigation stack rooted at it (push/pop, no tab
|
|
291
|
+
* swiping); several → swipeable sibling tabs, fixed at construction, each starting as its own
|
|
292
|
+
* one-screen stack. */
|
|
293
|
+
(...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
|
|
304
296
|
/** The pager that owns the currently visible screen, or `null` when the visible screen isn't
|
|
305
297
|
* hosted by one. With pagers nested inside pages, resolves to the **innermost** — so ambient
|
|
306
298
|
* calls act on the pager the user is actually looking at. */
|
|
@@ -316,14 +308,23 @@ interface UIPagerConstructor {
|
|
|
316
308
|
}
|
|
317
309
|
|
|
318
310
|
function pagerFactory(...args: any[]): UIPager {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
const tabs: any[] = []
|
|
319
|
+
for (let i = start; i < args.length; i++) {
|
|
320
|
+
const a = args[i]
|
|
321
|
+
if (Array.isArray(a)) {
|
|
322
|
+
for (let j = 0; j < a.length; j++) if (a[j]) tabs.push(a[j])
|
|
323
|
+
} else if (a) {
|
|
324
|
+
tabs.push(a)
|
|
325
|
+
}
|
|
324
326
|
}
|
|
325
|
-
|
|
326
|
-
return new PagerElement(args[0] ?? null, Array.isArray(content) ? content : content ? [content] : []) as unknown as UIPager
|
|
327
|
+
return new PagerElement(style, tabs) as unknown as UIPager
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ModalElement, type UIModal, type UIModalStyle } from "./UIModal"
|
|
2
2
|
import { Presentable } from "./presentable"
|
|
3
3
|
import { device } from "../runtime/device"
|
|
4
|
-
import type
|
|
4
|
+
import { buildUI, type BoundingClientRect, type ChildrenFn, type UIChildArg, type UINodeChild } from "./UINode"
|
|
5
5
|
|
|
6
6
|
export type UIPopoverStyle = UIModalStyle
|
|
7
7
|
|
|
@@ -87,20 +87,14 @@ export class PopoverElement extends ModalElement {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
const makePopover = (style: any, children: any) => new PopoverElement(style, children)
|
|
91
|
+
|
|
92
|
+
/** Create an anchored popover (see {@link UIPopover}) — create once at module scope, then
|
|
93
|
+
* `show(anchor)` next to an element or `{x, y}` point. Same argument forms as `UIColumn`. */
|
|
94
|
+
export function UIPopover(...children: UIChildArg[]): UIPopover;
|
|
91
95
|
export function UIPopover(children: UINodeChild[] | ChildrenFn): UIPopover;
|
|
92
|
-
|
|
93
|
-
export function UIPopover(style: UIPopoverStyle, children
|
|
94
|
-
export function UIPopover(...args: []
|
|
95
|
-
|
|
96
|
-
return new PopoverElement({}, [])
|
|
97
|
-
} else if (args.length === 1) {
|
|
98
|
-
if (Array.isArray(args[0]) || typeof args[0] === "function") {
|
|
99
|
-
return new PopoverElement({}, args[0])
|
|
100
|
-
} else {
|
|
101
|
-
return new PopoverElement(args[0], [])
|
|
102
|
-
}
|
|
103
|
-
} else {
|
|
104
|
-
return new PopoverElement(args[0], args[1])
|
|
105
|
-
}
|
|
96
|
+
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
97
|
+
export function UIPopover(style: UIPopoverStyle, children?: UINodeChild[] | ChildrenFn): UIPopover;
|
|
98
|
+
export function UIPopover(...args: any[]): UIPopover {
|
|
99
|
+
return buildUI(args, null, makePopover)
|
|
106
100
|
}
|
|
@@ -1,35 +1,28 @@
|
|
|
1
1
|
import { TouchStartEvent } from "../runtime/touch"
|
|
2
2
|
import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "./presentable"
|
|
3
|
-
import { ContainerElement,
|
|
3
|
+
import { ContainerElement, buildUI, type BaseStyle, type ContainerStyle, type DrawableStyle, type PaddingStyle, type UIChildArg, type UIContainerBase, type UINodeChild, type ChildrenFn, } from "./UINode"
|
|
4
4
|
|
|
5
5
|
export type UIScreenStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export interface UIScreen {
|
|
7
|
+
/** A screen — the root the router/host mounts, sized to its slot. A screen NEVER scrolls: it is
|
|
8
|
+
* fixed chrome (header, tab bar) plus, when the content overflows, one `UIScrollable` body child
|
|
9
|
+
* (`flexGrow: 1`). Scroll events and pull-to-refresh live on the scroll containers
|
|
10
|
+
* (`UIScrollable`, `UIVirtualizedList`). */
|
|
11
|
+
export interface UIScreen extends UIContainerBase<UIScreenStyle, DrawableStyle & BaseStyle> {
|
|
13
12
|
readonly type: "screen",
|
|
14
|
-
style: Style<this, UIScreenStyle>,
|
|
15
|
-
animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>
|
|
16
|
-
animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>
|
|
17
13
|
|
|
18
14
|
/** Show this screen as the current destination (replaces whatever is visible — a screen, a
|
|
19
15
|
* scene, a native view — suspending an active Router until `Router.restore()`). */
|
|
20
16
|
open(options?: PresentOptions): void,
|
|
17
|
+
/** Dismiss if this screen is the visible destination. */
|
|
21
18
|
close(): void
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
insert(index: number, ...nodes: UINodeChild[]): this
|
|
25
|
-
remove(...nodes: UINodeChild[]): this
|
|
26
|
-
setContent(nodes: UINodeChild[] | ChildrenFn): this
|
|
27
|
-
|
|
28
|
-
readonly children: UINodeChild[],
|
|
29
|
-
|
|
20
|
+
/** Hardware/system back while this screen is current. */
|
|
30
21
|
onBackPressed(callback: () => void): this
|
|
31
22
|
|
|
23
|
+
/** The screen became the visible destination (first open, or revealed by a pop). */
|
|
32
24
|
onOpen(callback: () => void): this
|
|
25
|
+
/** The screen stopped being visible — closed, replaced, or covered by a push. */
|
|
33
26
|
onClose(callback: () => void): this
|
|
34
27
|
|
|
35
28
|
/**
|
|
@@ -43,14 +36,7 @@ export interface UIScreen {
|
|
|
43
36
|
/** Free a `keepAlive()` screen's retained tree now. No-op unless it's currently detached-and-kept. */
|
|
44
37
|
dispose(): this
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
getBoundingClientRect(): BoundingClientRect | null
|
|
48
|
-
|
|
49
|
-
/** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
|
|
50
|
-
* `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
|
|
51
|
-
* Classes cascade to descendants. See {@link Classes}. */
|
|
52
|
-
readonly class: Classes<this>
|
|
53
|
-
|
|
39
|
+
/** Touch began on the screen; `ev.track(...)` takes over the rest of the gesture. */
|
|
54
40
|
onTouchStart(callback: (ev: TouchStartEvent<UIScreen>) => void): this,
|
|
55
41
|
}
|
|
56
42
|
|
|
@@ -121,20 +107,17 @@ export class ScreenElement extends ContainerElement<"screen"> {
|
|
|
121
107
|
}
|
|
122
108
|
}
|
|
123
109
|
|
|
124
|
-
|
|
110
|
+
const makeScreen = (style: any, children: any) => new ScreenElement("screen", style, children)
|
|
111
|
+
|
|
112
|
+
/** @internal Compiler fast path (chisel `flatten_ui` raw lowering) — see UIContainer. */
|
|
113
|
+
export const __UIScreen = (children: UINodeChild[] | ChildrenFn): UIScreen => makeScreen({}, children)
|
|
114
|
+
|
|
115
|
+
/** Create a screen. Present it with `.open()`, or via `Router` / `UIPager`. Same argument forms
|
|
116
|
+
* as `UIColumn`. */
|
|
117
|
+
export function UIScreen(...children: UIChildArg[]): UIScreen;
|
|
125
118
|
export function UIScreen(children: UINodeChild[] | ChildrenFn): UIScreen;
|
|
126
|
-
|
|
127
|
-
export function UIScreen(style: UIScreenStyle, children
|
|
128
|
-
export function UIScreen(...args: []
|
|
129
|
-
|
|
130
|
-
return new ScreenElement("screen", {}, [])
|
|
131
|
-
} else if (args.length === 1) {
|
|
132
|
-
if (Array.isArray(args[0]) || typeof args[0] === "function") {
|
|
133
|
-
return new ScreenElement("screen", {}, args[0])
|
|
134
|
-
} else {
|
|
135
|
-
return new ScreenElement("screen", args[0], [])
|
|
136
|
-
}
|
|
137
|
-
} else {
|
|
138
|
-
return new ScreenElement("screen", args[0], args[1])
|
|
139
|
-
}
|
|
119
|
+
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
120
|
+
export function UIScreen(style: UIScreenStyle, children?: UINodeChild[] | ChildrenFn): UIScreen;
|
|
121
|
+
export function UIScreen(...args: any[]): UIScreen {
|
|
122
|
+
return buildUI(args, null, makeScreen)
|
|
140
123
|
}
|