lecodes-cli 0.11.0 → 0.13.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.
@@ -362,6 +362,43 @@ export type ClassValue = boolean | (() => boolean)
362
362
  * single-key writes are runtime-coerced with `!!`. */
363
363
  export type Classes<R> = ((classes: Record<string, ClassValue>) => R) & { [key: string]: any }
364
364
 
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
+
365
402
  /** Orientation state blocks — override props applied only in landscape / portrait (like a
366
403
  * `$`-class, keyed by device orientation instead of `el.class`). Same full-`T` rule as class
367
404
  * blocks. */
@@ -1,4 +1,4 @@
1
- import { ContainerElement, type AnimateStyle, type BaseStyle, type ChildrenFn, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Classes, type Style, type UINodeChild } from "./UINode"
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"> {
@@ -1,6 +1,6 @@
1
1
  import { TouchStartEvent } from "../runtime/touch"
2
2
  import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "./presentable"
3
- import { ContainerElement, buildUI, type AnimateStyle, type BaseStyle, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type Classes, type Style, type UIChildArg, type UINodeChild, type ChildrenFn, } from "./UINode"
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
 
@@ -8,24 +8,15 @@ export type UIScreenStyle = ContainerStyle & DrawableStyle & PaddingStyle & Base
8
8
  * fixed chrome (header, tab bar) plus, when the content overflows, one `UIScrollable` body child
9
9
  * (`flexGrow: 1`). Scroll events and pull-to-refresh live on the scroll containers
10
10
  * (`UIScrollable`, `UIVirtualizedList`). */
11
- export interface UIScreen {
11
+ export interface UIScreen extends UIContainerBase<UIScreenStyle, DrawableStyle & BaseStyle> {
12
12
  readonly type: "screen",
13
- style: Style<this, UIScreenStyle>,
14
- animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>
15
- animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>
16
13
 
17
14
  /** Show this screen as the current destination (replaces whatever is visible — a screen, a
18
15
  * scene, a native view — suspending an active Router until `Router.restore()`). */
19
16
  open(options?: PresentOptions): void,
17
+ /** Dismiss if this screen is the visible destination. */
20
18
  close(): void
21
19
 
22
- append(...nodes: UINodeChild[]): this
23
- insert(index: number, ...nodes: UINodeChild[]): this
24
- remove(...nodes: UINodeChild[]): this
25
- setContent(nodes: UINodeChild[] | ChildrenFn): this
26
-
27
- readonly children: UINodeChild[],
28
-
29
20
  /** Hardware/system back while this screen is current. */
30
21
  onBackPressed(callback: () => void): this
31
22
 
@@ -45,14 +36,7 @@ export interface UIScreen {
45
36
  /** Free a `keepAlive()` screen's retained tree now. No-op unless it's currently detached-and-kept. */
46
37
  dispose(): this
47
38
 
48
- onLayout(onLayout: OnLayoutCallback): this
49
- getBoundingClientRect(): BoundingClientRect | null
50
-
51
- /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
52
- * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
53
- * Classes cascade to descendants. See {@link Classes}. */
54
- readonly class: Classes<this>
55
-
39
+ /** Touch began on the screen; `ev.track(...)` takes over the rest of the gesture. */
56
40
  onTouchStart(callback: (ev: TouchStartEvent<UIScreen>) => void): this,
57
41
  }
58
42
 
@@ -1,5 +1,5 @@
1
1
  import type { UIContainerStyle } from "./UIContainer"
2
- import { type Classes, type Style, type AnimateStyle, type UIChildArg, type UINodeChild, type ChildrenFn, type OnLayoutCallback, type BoundingClientRect, type Color, ContainerElement, buildUI } from "./UINode"
2
+ import { type UIChildArg, type UIContainerBase, type UINodeChild, type ChildrenFn, type Color, ContainerElement, buildUI } from "./UINode"
3
3
 
4
4
  export type ScrollStyle = {
5
5
  showScrollbar?: boolean,
@@ -21,24 +21,8 @@ export type UIScrollableStyle = UIContainerStyle & ScrollStyle & { scrollDirecti
21
21
 
22
22
  /** A scroll container — vertical by default, `scrollDirection: "horizontal"` for carousels/chip
23
23
  * rows. The scrolling body of a screen (screens themselves never scroll). */
24
- export interface UIScrollable {
24
+ export interface UIScrollable extends UIContainerBase<UIScrollableStyle> {
25
25
  readonly type: "scrollable",
26
- style: Style<this, UIScrollableStyle>,
27
- animateTo: AnimateStyle<this, UIScrollableStyle>,
28
- animateFrom: AnimateStyle<this, UIScrollableStyle>,
29
-
30
- append(...nodes: UINodeChild[]): UIScrollable
31
- insert(index: number, ...nodes: UINodeChild[]): UIScrollable
32
- remove(...nodes: UINodeChild[]): UIScrollable
33
- setContent(nodes: UINodeChild[] | ChildrenFn): this
34
-
35
- onLayout(onLayout: OnLayoutCallback): this
36
- getBoundingClientRect(): BoundingClientRect | null
37
-
38
- /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
39
- * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
40
- * Classes cascade to descendants. See {@link Classes}. */
41
- readonly class: Classes<this>
42
26
 
43
27
  /** Scroll position changed (px from the start edge). */
44
28
  onScroll(callback: (scrollPosition: number) => void): UIScrollable,
@@ -49,8 +33,6 @@ export interface UIScrollable {
49
33
  /** Pull-to-refresh: the spinner stays until the returned promise settles. Attach BEFORE the
50
34
  * node mounts (hosts read it at creation). Vertical only; native hosts — no-op on web. */
51
35
  onRefresh(callback: () => void | Promise<void>): UIScrollable
52
-
53
- readonly children: UINodeChild[]
54
36
  }
55
37
 
56
38
  class ScrollableElement extends ContainerElement<"scrollable"> {
@@ -1,16 +1,10 @@
1
- import { Element, type ElementStyle, type Classes, type Style, type StyleFn } from "./UINode"
1
+ import { Element, type ElementStyle, type UIElementBase } from "./UINode"
2
2
 
3
3
  export type UISpacerStyle = ElementStyle
4
4
 
5
5
  /** A flexible gap — `flexGrow: 1` by default, pushes its siblings apart. */
6
- export interface UISpacer {
6
+ export interface UISpacer extends UIElementBase<UISpacerStyle> {
7
7
  readonly type: "spacer",
8
- style: Style<this, UISpacerStyle>,
9
-
10
- /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
11
- * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
12
- * Classes cascade to descendants. See {@link Classes}. */
13
- readonly class: Classes<this>
14
8
  }
15
9
  /** Create a flexible gap (`flexGrow: 1`) — e.g. between a header's title and its actions. */
16
10
  export function UISpacer(): UISpacer
@@ -1,24 +1,13 @@
1
1
  import { createBinding } from "../core/signals"
2
- import { Element, type AnimateStyle, type BaseStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Classes, type Style, type TextStyle } from "./UINode"
2
+ import { Element, type BaseStyle, type DrawableStyle, type ElementStyle, type TextStyle, type UIElementBase } from "./UINode"
3
3
 
4
4
  export type UITextStyle = ElementStyle & TextStyle & DrawableStyle
5
5
 
6
6
  /** A text label. */
7
- export interface UIText {
7
+ export interface UIText extends UIElementBase<UITextStyle, DrawableStyle & BaseStyle & TextStyle> {
8
8
  readonly type: "text",
9
9
  /** The displayed string — writable at any time. */
10
10
  text: string,
11
- style: Style<this, UITextStyle>,
12
- animateTo: AnimateStyle<this, DrawableStyle & BaseStyle & TextStyle>
13
- animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle & TextStyle>
14
-
15
- onLayout(onLayout: OnLayoutCallback): this
16
- getBoundingClientRect(): BoundingClientRect | null
17
-
18
- /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
19
- * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
20
- * Classes cascade to descendants. See {@link Classes}. */
21
- readonly class: Classes<this>
22
11
  }
23
12
 
24
13
  export class TextElement extends Element<"text"> {
@@ -1,34 +1,27 @@
1
1
  import type { VideoPlayer } from "../runtime/media"
2
2
  import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "./presentable"
3
- import { Element, type AnimateStyle, type AppearStyle, type BaseStyle, type DisappearStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Classes, type Style, type StyleFn } from "./UINode"
3
+ import { Element, type BaseStyle, type DrawableStyle, type ElementStyle, type UIElementBase } from "./UINode"
4
4
 
5
5
  export type UIVideoStyle = ElementStyle & DrawableStyle & { objectFit?: "cover" | "contain" | "fill" }
6
6
 
7
7
  /** A video surface showing a `VideoPlayer`'s output — embeddable in a screen AND
8
8
  * fullscreen-presentable (`open()`), with seamless promotion between the two. */
9
- export interface UIVideo {
9
+ export interface UIVideo extends UIElementBase<UIVideoStyle, DrawableStyle & BaseStyle> {
10
10
  readonly type: "video",
11
11
  /** The player driving this surface (playback control lives on it). */
12
12
  player: VideoPlayer
13
- style: Style<this, UIVideoStyle>
14
- animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>
15
- animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>
16
13
 
17
14
  /** Show fullscreen as the current destination. Promotion: if this video is mounted in a screen,
18
15
  * the SAME native player moves fullscreen — playback uninterrupted; a pop moves it back. */
19
16
  open(options?: PresentOptions): void
17
+ /** Dismiss if this is the visible destination. */
20
18
  close(): void
19
+ /** Became the visible destination. */
21
20
  onOpen(callback: () => void): this
21
+ /** Stopped being the visible destination. */
22
22
  onClose(callback: () => void): this
23
+ /** Hardware/system back while current. */
23
24
  onBackPressed(callback: () => void): this
24
-
25
- onLayout(onLayout: OnLayoutCallback): this
26
- getBoundingClientRect(): BoundingClientRect | null
27
-
28
- /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
29
- * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
30
- * Classes cascade to descendants. See {@link Classes}. */
31
- readonly class: Classes<this>
32
25
  }
33
26
 
34
27
  class VideoElement extends Element<"video"> implements Presentable {
@@ -1,4 +1,4 @@
1
- import { Element, type Color, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type Classes, type Style, type UINodeChild } from "./UINode"
1
+ import { Element, type Color, type DrawableStyle, type ElementStyle, type UIElementBase, type UINodeChild } from "./UINode"
2
2
 
3
3
  export type UIVirtualizedListStyle = DrawableStyle & ElementStyle & {
4
4
  /** Tint of the native pull-to-refresh spinner (pairs with `onRefresh`). */
@@ -27,27 +27,34 @@ export interface VListConfig<T> {
27
27
 
28
28
  /** A windowed (virtualized) list: only the items near the viewport are mounted — for long feeds
29
29
  * and chats. Items are keyed; feed it via `setData`/`append`/`prepend`. */
30
- export interface UIVirtualizedList<T = unknown> {
30
+ export interface UIVirtualizedList<T = unknown> extends UIElementBase<UIVirtualizedListStyle> {
31
31
  readonly type: "vlist"
32
- style: Style<this, UIVirtualizedListStyle>
33
32
 
34
33
  /** Replace the data wholesale — vanished keys are unmounted by the host's window sync. */
35
34
  setData(items: T[]): this
35
+ /** Append items to the end (auto-scrolls when `inverted` and resting at the bottom). */
36
36
  append(...items: T[]): this
37
37
  /** Insert at the start WITHOUT a scroll jump (the host compensates the offset). */
38
38
  prepend(...items: T[]): this
39
39
  /** Update items with the same keys: mounted ones re-render; the cached height persists until
40
40
  * re-measured. */
41
41
  update(...items: T[]): this
42
+ /** Remove items by key. */
42
43
  removeByKey(...keys: string[]): this
43
44
 
45
+ /** Number of data items (mounted or not). */
44
46
  readonly itemCount: number
47
+ /** The data item under `key`, if any. */
45
48
  getItem(key: string): T | undefined
46
49
 
50
+ /** Scroll to `offset` px. */
47
51
  scrollTo(offset: number, animated?: boolean): void
52
+ /** Scroll to the item with `key`. */
48
53
  scrollToKey(key: string, animated?: boolean): void
54
+ /** Scroll to the end of the content. */
49
55
  scrollToEnd(animated?: boolean): void
50
56
 
57
+ /** Scroll position changed (px from the start edge). */
51
58
  onScroll(callback: (scrollPosition: number) => void): this
52
59
  /** Pull-to-refresh: the spinner stays until the returned promise settles. Attach BEFORE the
53
60
  * node mounts (hosts read it at creation). Native hosts only — no-op on web. */
@@ -57,13 +64,6 @@ export interface UIVirtualizedList<T = unknown> {
57
64
  onEndReached(thresholdPx: number, callback: () => void): this
58
65
  /** Same, for the top edge — chat history loading. */
59
66
  onStartReached(thresholdPx: number, callback: () => void): this
60
- onLayout(onLayout: OnLayoutCallback): this
61
- getBoundingClientRect(): BoundingClientRect | null
62
-
63
- /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
64
- * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
65
- * Classes cascade to descendants. See {@link Classes}. */
66
- readonly class: Classes<this>
67
67
  }
68
68
 
69
69
  export class VListElement<T> extends Element<"vlist"> implements UIVirtualizedList<T> {
@@ -1,6 +1,6 @@
1
1
  import { TouchStartEvent } from "../runtime/touch"
2
2
  import { _descOf, _navSupported, Presentable } from "./presentable"
3
- import { ContainerElement, buildUI, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type PositionStyle, type Classes, type Style, type UIChildArg, type UINodeChild, type ChildrenFn } from "./UINode"
3
+ 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"
4
4
 
5
5
 
6
6
  export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle & PositionStyle & {
@@ -13,11 +13,8 @@ export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & Base
13
13
  /** A floating overlay — absolutely positioned in device coordinates, mounted above the page as
14
14
  * its own layout root (never a child of what's underneath). The base of dialogs, toasts,
15
15
  * mini-players and scene HUDs; `UIModal`/`UIPopover`/`UIBottomSheet` build on it. */
16
- export interface UIWidget {
16
+ export interface UIWidget extends UIContainerBase<UIWidgetStyle, DrawableStyle & BaseStyle & { overlayColor?: Color | null }> {
17
17
  readonly type: "widget",
18
- style: Style<this, UIWidgetStyle>,
19
- animateTo: AnimateStyle<this, DrawableStyle & BaseStyle & { overlayColor?: Color | null }>
20
- animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle & { overlayColor?: Color | null }>
21
18
 
22
19
  /**
23
20
  * Attach this widget to a destination (a `Scene`, a `UIScreen`, …): the widget lives on that
@@ -42,25 +39,12 @@ export interface UIWidget {
42
39
  /** Mounted right now (`show()` ran, `hide()` hasn't). */
43
40
  readonly isShow: boolean,
44
41
 
45
- append(...nodes: UINodeChild[]): UIWidget
46
- insert(index: number, ...nodes: UINodeChild[]): UIWidget
47
- remove(...nodes: UINodeChild[]): UIWidget
48
- setContent(nodes: UINodeChild[] | ChildrenFn): this
49
-
50
- readonly children: UINodeChild[],
51
-
52
- onLayout(onLayout: OnLayoutCallback): this
53
- getBoundingClientRect(): BoundingClientRect | null
42
+ /** Touch began on the widget; `ev.track(...)` takes over the rest of the gesture. */
54
43
  onTouchStart(callback: (ev: TouchStartEvent<UIWidget>) => void): this,
55
44
  /** Tap on the overlay layer (requires an `overlayColor`). */
56
45
  onOverlayTap(callback: () => void): this,
57
46
  /** Hardware/system back while the widget is shown. */
58
47
  onBackPressed(callback: () => void): this
59
-
60
- /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
61
- * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
62
- * Classes cascade to descendants. See {@link Classes}. */
63
- readonly class: Classes<this>
64
48
  }
65
49
 
66
50