lecodes-cli 0.7.2 → 0.8.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.
Files changed (40) hide show
  1. package/dist/index.js +2909 -725
  2. package/package.json +2 -2
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk/canvas/Canvas.ts +16 -0
  5. package/runtime/sdk/gl/Material.ts +4 -0
  6. package/runtime/sdk/inject.ts +7 -4
  7. package/runtime/sdk/plugins/service.ts +47 -0
  8. package/runtime/sdk/runtime/app.ts +21 -1
  9. package/runtime/sdk/runtime/appEvents.ts +25 -0
  10. package/runtime/sdk/runtime/service.ts +3 -2
  11. package/runtime/sdk/ui/NativeView.ts +2 -1
  12. package/runtime/sdk/ui/UI.ts +3 -1
  13. package/runtime/sdk/ui/UIButton.ts +10 -5
  14. package/runtime/sdk/ui/UIContainer.ts +16 -9
  15. package/runtime/sdk/ui/UIImage.ts +5 -2
  16. package/runtime/sdk/ui/UIInput.ts +75 -8
  17. package/runtime/sdk/ui/UIModal.ts +149 -0
  18. package/runtime/sdk/ui/UINode.ts +113 -50
  19. package/runtime/sdk/ui/UIPager.ts +297 -0
  20. package/runtime/sdk/ui/UIPopover.ts +106 -0
  21. package/runtime/sdk/ui/UIScreen.ts +11 -10
  22. package/runtime/sdk/ui/UIScrollable.ts +2 -1
  23. package/runtime/sdk/ui/UISpacer.ts +1 -1
  24. package/runtime/sdk/ui/UIText.ts +2 -1
  25. package/runtime/sdk/ui/UIVideo.ts +2 -1
  26. package/runtime/sdk/ui/UIVirtualizedList.ts +2 -1
  27. package/runtime/sdk/ui/UIWidget.ts +19 -22
  28. package/runtime/sdk/ui/theme.ts +66 -0
  29. package/runtime/sdk-types.json +1 -1
  30. package/runtime/sdk/compile/aspectMacro.ts +0 -42
  31. package/runtime/sdk/compile/assetMacro.ts +0 -45
  32. package/runtime/sdk/compile/bundler.ts +0 -207
  33. package/runtime/sdk/compile/compileProject.ts +0 -113
  34. package/runtime/sdk/compile/header.ts +0 -46
  35. package/runtime/sdk/compile/index.ts +0 -18
  36. package/runtime/sdk/compile/libraryImports.ts +0 -47
  37. package/runtime/sdk/compile/sceneEditor.ts +0 -78
  38. package/runtime/sdk/compile/sourcemap.ts +0 -25
  39. package/runtime/sdk/kit/UITabs.ts +0 -105
  40. package/runtime/sdk/ui/UIScreenHost.ts +0 -213
@@ -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, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type PaddingStyle, type ScrollStyle, type Style, type UINodeChild, type ChildrenFn, } from "./UINode"
3
+ import { ContainerElement, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type ScrollStyle, type Style, type UINodeChild, type ChildrenFn, } from "./UINode"
4
4
 
5
5
  export type UIScreenStyle = ContainerStyle & DrawableStyle & PaddingStyle & ScrollStyle & BaseStyle & ScrollStyle & { refreshControlColor?: Color }
6
6
 
@@ -8,11 +8,11 @@ export type UIScreenStyle = ContainerStyle & DrawableStyle & PaddingStyle & Scro
8
8
  // plain `UIScreen`, and a `UIScrollableScreen` (a screen that also scrolls). `makeScrollable()`
9
9
  // upgrades a plain screen to the scrollable one, where `onScroll`/`onOverscroll`/`onRefresh` live —
10
10
  // they mean nothing without scrolling. `UIScrollableScreen extends UIScreen`, so anywhere a
11
- // `UIScreen` / `HostedScreen` is accepted, a scrollable one works too.
11
+ // `UIScreen` is accepted (router pages, `UIPager`), a scrollable one works too.
12
12
  //
13
13
  // This is a subtype split, NOT a `UIScreen<Scrollable>` generic: a generic can't work while `style`
14
14
  // is `Style<this,…>`, because `this` is then contravariant and the type becomes invariant in its own
15
- // params — that was the old `UIScreen<false>`-not-assignable-to-`HostedScreen` bug. See the StyleFn
15
+ // params — that was the old `UIScreen<false>`-not-assignable-to-`UIScreen<boolean>` bug. See the StyleFn
16
16
  // note in UINode.ts; the contract is pinned by tests/ui-types.test.ts.
17
17
  export interface UIScreen {
18
18
  readonly type: "screen",
@@ -41,17 +41,18 @@ export interface UIScreen {
41
41
  onClose(callback: () => void): this
42
42
 
43
43
  /**
44
- * Keep this screen's built native tree alive when a UIScreenHost detaches it (replace / remove /
45
- * pop): instead of being destroyed, its views + layout + live state (scroll position, input text)
46
- * are retained, so re-hosting the SAME screen object restores it in place rather than rebuilding.
47
- * `onClose`/`onOpen` still fire on detach/re-attach. You own the lifetime — a kept tree is freed
48
- * only by `dispose()` or when the project unloads. (Native-only today; web rebuilds.)
44
+ * Keep this screen's built native tree alive when a host detaches it: instead of being destroyed,
45
+ * its views + layout + live state (scroll position, input text) are retained, so re-hosting the
46
+ * SAME screen object restores it in place rather than rebuilding. `onClose`/`onOpen` still fire on
47
+ * detach/re-attach. You own the lifetime — a kept tree is freed only by `dispose()` or when the
48
+ * project unloads. (Native-only today; web rebuilds.)
49
49
  */
50
50
  keepAlive(enabled?: boolean): this
51
51
  /** Free a `keepAlive()` screen's retained tree now. No-op unless it's currently detached-and-kept. */
52
52
  dispose(): this
53
53
 
54
54
  onLayout(onLayout: OnLayoutCallback): this
55
+ getBoundingClientRect(): BoundingClientRect | null
55
56
 
56
57
  setClass(name: string, enabled: boolean): this
57
58
  toggleClass(name: string): this
@@ -63,8 +64,8 @@ export interface UIScreen {
63
64
 
64
65
  /**
65
66
  * A `UIScreen` in scroll mode — what `makeScrollable()` returns. Adds the scroll lifecycle that a
66
- * plain screen doesn't carry. A subtype of `UIScreen`, so it's accepted anywhere a screen /
67
- * `HostedScreen` is (router pages, `UIScreenHost`, `UITabs`).
67
+ * plain screen doesn't carry. A subtype of `UIScreen`, so it's accepted anywhere a screen is
68
+ * (router pages, `UIPager`).
68
69
  */
69
70
  export interface UIScrollableScreen extends UIScreen {
70
71
  readonly scrollable: true,
@@ -1,5 +1,5 @@
1
1
  import type { UIContainerStyle } from "./UIContainer"
2
- import { type Style, type AnimateStyle, type UINodeChild, type ChildrenFn, type OnLayoutCallback, ContainerElement } from "./UINode"
2
+ import { type Style, type AnimateStyle, type UINodeChild, type ChildrenFn, type OnLayoutCallback, type BoundingClientRect, ContainerElement } from "./UINode"
3
3
 
4
4
  export type ScrollStyle = { showScrollbar?: boolean, overscrollMode?: "none" | "absorb" | "default" }
5
5
 
@@ -17,6 +17,7 @@ export interface UIScrollable {
17
17
  setContent(nodes: UINodeChild[] | ChildrenFn): this
18
18
 
19
19
  onLayout(onLayout: OnLayoutCallback): this
20
+ getBoundingClientRect(): BoundingClientRect | null
20
21
 
21
22
  setClass(name: string, enabled: boolean): this
22
23
  toggleClass(name: string): this
@@ -14,5 +14,5 @@ export interface UISpacer {
14
14
  export function UISpacer(): UISpacer
15
15
  export function UISpacer(style: UISpacerStyle): UISpacer
16
16
  export function UISpacer(style?: UISpacerStyle): UISpacer {
17
- return new Element("spacer", style ?? {})
17
+ return new Element("spacer", { flexGrow: 1, ...style })
18
18
  }
@@ -1,5 +1,5 @@
1
1
  import { createBinding } from "../core/signals"
2
- import { Element, type AnimateStyle, type BaseStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type Style, type TextStyle } from "./UINode"
2
+ import { Element, type AnimateStyle, type BaseStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Style, type TextStyle } from "./UINode"
3
3
 
4
4
  export type UITextStyle = ElementStyle & TextStyle & DrawableStyle
5
5
 
@@ -11,6 +11,7 @@ export interface UIText {
11
11
  animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle & TextStyle>
12
12
 
13
13
  onLayout(onLayout: OnLayoutCallback): this
14
+ getBoundingClientRect(): BoundingClientRect | null
14
15
 
15
16
  setClass(name: string, enabled: boolean): this
16
17
  toggleClass(name: string): this
@@ -1,6 +1,6 @@
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 Style, type StyleFn } from "./UINode"
3
+ import { Element, type AnimateStyle, type AppearStyle, type BaseStyle, type DisappearStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Style, type StyleFn } from "./UINode"
4
4
 
5
5
  export type UIVideoStyle = ElementStyle & DrawableStyle & { objectFit?: "cover" | "contain" | "fill" }
6
6
 
@@ -20,6 +20,7 @@ export interface UIVideo {
20
20
  onBackPressed(callback: () => void): this
21
21
 
22
22
  onLayout(onLayout: OnLayoutCallback): this
23
+ getBoundingClientRect(): BoundingClientRect | null
23
24
 
24
25
  setClass(name: string, enabled: boolean): this
25
26
  toggleClass(name: string): this
@@ -1,4 +1,4 @@
1
- import { Element, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type PaddingStyle, type Style, type UINodeChild } from "./UINode"
1
+ import { Element, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type Style, type UINodeChild } from "./UINode"
2
2
 
3
3
  export type UIVirtualizedListStyle = DrawableStyle & ElementStyle
4
4
 
@@ -45,6 +45,7 @@ export interface UIVirtualizedList<T = unknown> {
45
45
  /** Порог от верхнего края — подгрузка истории в чате */
46
46
  onStartReached(thresholdPx: number, callback: () => void): this
47
47
  onLayout(onLayout: OnLayoutCallback): this
48
+ getBoundingClientRect(): BoundingClientRect | null
48
49
 
49
50
  setClass(name: string, enabled: boolean): this
50
51
  toggleClass(name: string): this
@@ -1,6 +1,6 @@
1
1
  import { TouchStartEvent } from "../runtime/touch"
2
2
  import { _descOf, _navSupported, Presentable } from "./presentable"
3
- import { ContainerElement, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type PaddingStyle, type PositionStyle, type Style, type UINodeChild, type ChildrenFn } from "./UINode"
3
+ import { ContainerElement, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type PositionStyle, type Style, type UINodeChild, type ChildrenFn } from "./UINode"
4
4
 
5
5
 
6
6
  export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle & PositionStyle & {
@@ -17,22 +17,18 @@ export interface UIWidget {
17
17
  animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle & { overlayColor?: Color | null }>
18
18
 
19
19
  /**
20
- * Pre-bind this widget to a destination (a `Scene`, a `UIScreen`, …) that is not current yet —
21
- * e.g. HUD for an `ARScene` built before opening it, so it's in place from the first frame.
22
- * `show()` binds to the *current* destination automatically, so this is only needed ahead of
23
- * time (or to target a non-current owner). Sticky: an explicit owner wins over the automatic
24
- * one until `attachTo(null)` (= back to automatic). Set it while the widget is hidden.
20
+ * Attach this widget to a destination (a `Scene`, a `UIScreen`, …): the widget lives on that
21
+ * page's layer — it shows/hides with the page and rides its transition, and a screen pushed
22
+ * on top covers it (a scene HUD behaves like part of the scene). Sticky until `attachTo(null)`
23
+ * (= back to a global overlay). Set it while the widget is hidden.
25
24
  */
26
25
  attachTo(owner: Presentable | null): this,
27
26
 
28
- /** Show the widget, attached to the current destination (or the `attachTo` owner): it mounts
29
- * inside that page, shows/hides with it and rides its transition — a dialog opened on a screen
30
- * disappears when the user navigates away. For an overlay that survives navigation use
31
- * `showDetached()`. */
27
+ /** Show the widget. By default it is a global overlay above every destination — it stays
28
+ * visible across navigation until `hide()` (dialogs, mini-player, global loader). If the
29
+ * widget was attached via `attachTo(owner)`, it mounts on that page's layer instead and is
30
+ * only visible while the owner is presented. */
32
31
  show(): void,
33
- /** Show the widget as a global overlay above every destination — it stays visible across
34
- * navigation (mini-player, global loader). */
35
- showDetached(): void,
36
32
  hide(): void,
37
33
 
38
34
  readonly isShow: boolean,
@@ -45,6 +41,7 @@ export interface UIWidget {
45
41
  readonly children: UINodeChild[],
46
42
 
47
43
  onLayout(onLayout: OnLayoutCallback): this
44
+ getBoundingClientRect(): BoundingClientRect | null
48
45
  onTouchStart(callback: (ev: TouchStartEvent<UIWidget>) => void): this,
49
46
  onOverlayTap(callback: () => void): this,
50
47
  onBackPressed(callback: () => void): this
@@ -59,24 +56,24 @@ export interface UIWidget {
59
56
  export class WidgetElement extends ContainerElement<"widget"> {
60
57
  scrollable = false as const
61
58
  _owner?: Presentable
59
+ constructor(type: "widget", style: UIWidgetStyle, children: UINodeChild[] | ChildrenFn) {
60
+ // Widgets are absolutely positioned in device coordinates — in the constructor (not the
61
+ // factory) so subclasses like ModalElement inherit it.
62
+ super(type, { position: "absolute", ...style }, children)
63
+ }
62
64
  attachTo(owner: Presentable | null): this {
63
65
  this._owner = owner ?? undefined
64
66
  return this
65
67
  }
66
68
  show(): void {
67
- // Explicit attachTo() wins; otherwise bind to whatever is on screen right now — re-showing
68
- // the same widget from another page re-attaches it there.
69
- const owner = this._owner ?? Presentable.current
70
- if (owner && _navSupported()) {
71
- _creatorUI.showWidget(this, _descOf(owner))
69
+ // Global overlay unless explicitly attached via attachTo(owner).
70
+ if (this._owner && _navSupported()) {
71
+ _creatorUI.showWidget(this, _descOf(this._owner))
72
72
  } else {
73
- // Legacy host (or nothing presented yet): global overlay.
73
+ // Unattached (or legacy host): global overlay above navigation.
74
74
  _creatorUI.showWidget(this)
75
75
  }
76
76
  }
77
- showDetached(): void {
78
- _creatorUI.showWidget(this)
79
- }
80
77
  hide(): void {
81
78
  _creatorUI.hideWidget(this)
82
79
  }
@@ -0,0 +1,66 @@
1
+ // theme() — app theme variables (docs/ui-theme-plan.md).
2
+ //
3
+ // One global variable table lives in the UI core. Styles reference entries with CSS syntax —
4
+ // `color: "var(--primaryColor)"` — resolved at style-apply time in the core, so re-calling
5
+ // theme() with new values re-styles the LIVE UI (dark mode is just a second call). Two access
6
+ // forms with one rule: bare style tokens (`pt: "comfort-top"`) are keywords that may expand to
7
+ // formulas (max(safe-top, knob)); `var(--comfort-top)` always reads the raw table value (the
8
+ // knob itself).
9
+ //
10
+ // theme(values) MERGES into the table and returns typed accessors whose values ARE the var()
11
+ // strings — define once in a module, import where custom keys need typing:
12
+ //
13
+ // export const T = theme({ primaryColor: "#0A84FF", cardBg: "#1C1C1E" })
14
+ // card.style({ bgColor: T.cardBg }) // T.cardBg === "var(--cardBg)"
15
+ //
16
+ // System vars need no import — they're static properties on the callable itself:
17
+ // theme.primaryColor, theme.fontFamily, theme["comfort-top"], …
18
+ //
19
+ // Values: strings pass through (colors, fonts, expressions like "max(safe-top, 24px)");
20
+ // numbers are LENGTHS (px). `null` removes a key (comfort knobs reset to their defaults:
21
+ // 12 vertical, 16 horizontal — the page gutter). Env names (safe-*, vw/vh/vmin/vmax) are
22
+ // host-owned facts, never theme keys — they're skipped with a warning.
23
+
24
+ export type ThemeValues = Record<string, string | number | null>
25
+
26
+ export type ThemeAccessors<T extends ThemeValues> = {
27
+ readonly [K in keyof T]: K extends string ? `var(--${K})` : never
28
+ }
29
+
30
+ const RESERVED = ["safe-top", "safe-bottom", "safe-left", "safe-right", "vw", "vh", "vmin", "vmax"]
31
+
32
+ const setTheme = <T extends ThemeValues>(values: T): ThemeAccessors<T> => {
33
+ const accessors: Record<string, string> = {}
34
+ let send: ThemeValues = values
35
+ for (const key of Object.keys(values)) {
36
+ if (RESERVED.includes(key)) {
37
+ console.warn(`theme(): "${key}" is an environment value (host-owned) — ignored`)
38
+ if (send === values) send = { ...values }
39
+ delete send[key]
40
+ continue
41
+ }
42
+ accessors[key] = `var(--${key})`
43
+ }
44
+ // Optional during rollout: hosts without the method simply don't theme (values still type).
45
+ _creatorUI.setTheme?.(send)
46
+ return accessors as ThemeAccessors<T>
47
+ }
48
+
49
+ export const theme = Object.assign(setTheme, {
50
+ /** The app accent color. Kit components will read it via `var(--primaryColor, <fallback>)`. */
51
+ primaryColor: "var(--primaryColor)",
52
+ /** Secondary / inactive content color for kit components. */
53
+ mutedColor: "var(--mutedColor)",
54
+ /** DEFAULT text color — text without an explicit `color` follows it (hosts source their
55
+ * default from the theme; unset = the host default, white on dark screens). Set it once for
56
+ * a light theme instead of `color: "black"` on every label. */
57
+ color: "var(--color)",
58
+ /** DEFAULT text font — text without an explicit `fontFamily` follows it. */
59
+ fontFamily: "var(--fontFamily)",
60
+ /** Raw comfort knobs (logical px). The bare `comfort-top` style TOKEN applies the safe-area
61
+ * formula — these accessors read the knob value itself, for manual composition. */
62
+ "comfort-top": "var(--comfort-top)",
63
+ "comfort-bottom": "var(--comfort-bottom)",
64
+ "comfort-left": "var(--comfort-left)",
65
+ "comfort-right": "var(--comfort-right)",
66
+ } as const)