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,105 +0,0 @@
1
- // UITabs — a self-contained tab component: a swipe-disabled UIScreenHost of tab screens + a bar of
2
- // tap-to-switch buttons. Pure TS over UIScreenHost, no native work. It's just an element — you place
3
- // it inside your own UIScreen and own the routing; UITabs only switches the visible tab.
4
- //
5
- // Router.push(UIScreen([
6
- // UITabs([
7
- // { icon: homeSvg, label: "Home", content: HomeScreen() },
8
- // { icon: userSvg, label: "Profile", content: ProfileScreen() },
9
- // ]),
10
- // ]))
11
- //
12
- // A tab's content is a full UIScreen (screens are the only page kind UIScreenHost accepts): it gets
13
- // onOpen/onClose from the host's settle lifecycle, plus makeScrollable/onRefresh. All tabs stay
14
- // mounted, so switching is instant and per-tab state survives.
15
-
16
- import { UIScreenHost, type HostedScreen } from "../ui/UIScreenHost"
17
- import { UIRow, UIColumn, type UIColumn as UIColumnType } from "../ui/UIContainer"
18
- import { UIButton } from "../ui/UIButton"
19
- import { UIText } from "../ui/UIText"
20
- import { UIImage, type ImageSource } from "../ui/UIImage"
21
- import type { UINodeChild } from "../ui/UINode"
22
-
23
- export interface UITabItem {
24
- /** Icon image source (SvgSource / url / File / Canvas). Optional. */
25
- icon?: ImageSource
26
- /** Label under the icon. Optional. */
27
- label?: string
28
- /** Tab content — a full UIScreen (wrap a plain element: `UIScreen([el])`). */
29
- content: HostedScreen
30
- }
31
-
32
- /** The tabs element (a UIColumn) plus a small control surface. Embed it like any element. */
33
- export interface UITabs extends UIColumnType {
34
- /** Switch the visible tab. */
35
- select(index: number): this
36
- onChange(callback: (index: number) => void): this
37
- /** Active tab index. */
38
- readonly index: number
39
- /** The bar row — style it or its item buttons directly. */
40
- readonly bar: UIRow
41
- }
42
-
43
- const clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(max, v))
44
-
45
- function tabButton(item: UITabItem, i: number, onSelect: (i: number) => void): ReturnType<typeof UIButton> {
46
- const parts: UINodeChild[] = []
47
- if (item.icon) parts.push(UIImage({ width: 24, height: 24 }, item.icon))
48
- if (item.label) parts.push(UIText({ fontSize: 11, lineHeight: 14, textAlign: "center" }, item.label))
49
-
50
- // Chained .style() (not the legacy first-arg object) — required for the `$active` class block.
51
- const btn = UIButton(parts).style({
52
- flexGrow: 1,
53
- flexBase: 0,
54
- flexDirection: "column",
55
- alignItems: "center",
56
- justifyContent: "center",
57
- gap: 3,
58
- paddingVertical: 4,
59
- opacity: 0.55,
60
- $active: { opacity: 1, duration: 150 },
61
- })
62
- btn.onClick(() => onSelect(i))
63
- return btn
64
- }
65
-
66
- export function UITabs(items: UITabItem[]): UITabs {
67
- let index = 0
68
- const listeners: ((index: number) => void)[] = []
69
-
70
- const host = UIScreenHost(items.map(it => it.content))
71
- .style({ swipeEnabled: false, flexGrow: 1, flexShrink: 1 })
72
-
73
- const barItems = items.map((it, i) => tabButton(it, i, select))
74
- const bar = UIRow({
75
- flexShrink: 0,
76
- paddingTop: 8,
77
- paddingBottom: "safe-bottom",
78
- borderTopWidth: 1,
79
- borderTopColor: "#00000022",
80
- }, barItems)
81
-
82
- const applyActive = (i: number) => {
83
- for (let k = 0; k < barItems.length; k++) barItems[k].setClass("active", k === i)
84
- }
85
-
86
- function select(i: number): UITabs {
87
- i = clamp(i, 0, Math.max(0, items.length - 1))
88
- if (i !== index) {
89
- index = i
90
- host.setPage(i, false)
91
- applyActive(i)
92
- for (const cb of listeners) cb(i)
93
- }
94
- return view
95
- }
96
-
97
- applyActive(0)
98
-
99
- const view = UIColumn([ host, bar ]) as unknown as UITabs
100
- ;(view as any).select = select
101
- ;(view as any).onChange = (cb: (index: number) => void) => { listeners.push(cb); return view }
102
- ;(view as any).bar = bar
103
- Object.defineProperty(view, "index", { get: () => index })
104
- return view
105
- }
@@ -1,213 +0,0 @@
1
- import { ContainerElement, type AnimateStyle, type BaseStyle, type ChildrenFn, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type Style, type UINodeChild } from "./UINode"
2
- import type { UIScreen } from "./UIScreen"
3
-
4
- // UIScreenHost — the system's ONE screen host: an element whose pages are full UIScreens, each laid
5
- // out to the host's box (the same mechanism screens use, with the "device" swapped for the slot).
6
- // With several screens it pages between them (wrapping each platform's native pager); with a single
7
- // screen it simply embeds it — a refreshable region under a fixed header, or a screen-swap slot via
8
- // setContent(next). UITabs is pure TS over a swipe-disabled UIScreenHost.
9
- //
10
- // Pages are screens ONLY — no plain elements. Every page then carries the full screen contract
11
- // (onOpen/onClose from the settle lifecycle, makeScrollable()/onRefresh), and every platform
12
- // implements exactly one page kind: mount the screen root in the slot, lay it out to the slot size.
13
- // To host a plain element, wrap it: UIScreenHost(UIScreen([el])).
14
-
15
- /** A hosted page: any UIScreen (a `UIScrollableScreen` is a subtype, so it's accepted too). Sizing
16
- * styles on it are ignored — screens are roots sized by their host. */
17
- export type HostedScreen = UIScreen
18
- /** Reactive pages — ChildrenFn narrowed to screens. */
19
- export type ScreensFn = () => (HostedScreen | null | undefined | false)[]
20
-
21
- /**
22
- * Transition for `replace()`. The `Router.replace` catalogue, plus `"push"`/`"pop"` — the native
23
- * UINavigationController feel (the incoming/outgoing screen slides full-width while the other
24
- * parallaxes ~30% under a dim overlay with a soft edge shadow). Use `"push"` to go forward, `"pop"`
25
- * to go back. (Animated transitions are native-only; web swaps instantly.)
26
- */
27
- export type ScreenHostTransition =
28
- | "push" | "pop"
29
- | "slide-from-left" | "slide-from-right" | "slide-from-top" | "slide-from-bottom"
30
- | "zoom" | "zoom-in" | "zoom-out" | "fade" | "none"
31
-
32
- export type UIScreenHostStyle = ElementStyle & DrawableStyle & {
33
- // Paging axis. Default "horizontal". Read by the host at mount; live-updatable via .style().
34
- scrollDirection?: "horizontal" | "vertical",
35
- // false = programmatic-only paging (no user swipe) — the mode UITabs uses. Default true.
36
- swipeEnabled?: boolean,
37
- }
38
-
39
- export interface UIScreenHost {
40
- readonly type: "screenhost",
41
- style: Style<this, UIScreenHostStyle>,
42
- animateTo: AnimateStyle<this, DrawableStyle & BaseStyle>,
43
- animateFrom: AnimateStyle<this, DrawableStyle & BaseStyle>,
44
-
45
- /** Current settled page index. */
46
- readonly page: number,
47
- /** Programmatic page switch. `animated` defaults to true (ignored when swipe physics can't run). */
48
- setPage(index: number, animated?: boolean): this,
49
- /** Fires when the settled page changes (after the swipe/scroll comes to rest). */
50
- onPageChange(callback: (index: number) => void): this,
51
- /** Continuous fractional scroll position (e.g. 1.35) — for tab-strip indicators / page dots. */
52
- onPageScroll(callback: (position: number) => void): this,
53
-
54
- append(...screens: HostedScreen[]): this,
55
- insert(index: number, ...screens: HostedScreen[]): this,
56
- remove(...screens: HostedScreen[]): this,
57
- /** Replace the pages. A single screen swaps the hosted screen in place (old page onClose → new page onOpen). */
58
- setContent(screens: HostedScreen | HostedScreen[] | ScreensFn): this,
59
- /**
60
- * Replace the page at `index` with a new screen, animating the swap when that page is the visible
61
- * one (off-screen pages swap instantly). The settled page index is preserved — a replace is not a
62
- * page change. This is the push/pop primitive: keep your own stack of screens and replace the
63
- * visible slot with a forward transition to push, a backward one to pop.
64
- */
65
- replace(index: number, screen: HostedScreen, transition?: ScreenHostTransition): this,
66
-
67
- readonly children: UINodeChild[],
68
-
69
- onLayout(onLayout: OnLayoutCallback): this,
70
-
71
- setClass(name: string, enabled: boolean): this,
72
- toggleClass(name: string): this,
73
- hasClass(name: string): boolean,
74
- bindClass(name: string, fn: () => boolean): this,
75
- }
76
-
77
- export class ScreenHostElement extends ContainerElement<"screenhost"> {
78
- // --- read by the host at mount (createScreenHostSystem) ---
79
- _page = 0 // settled index; host writes it back on settle
80
- _dir: "horizontal" | "vertical" = "horizontal"
81
- _swipe = true
82
-
83
- // --- listeners the host invokes directly (same pattern as UIScrollable.sl / vlist) ---
84
- readonly pcl: ((index: number) => void)[] = [] // onPageChange
85
- readonly psl: ((position: number) => void)[] = [] // onPageScroll
86
-
87
- get page(): number {
88
- return this._page
89
- }
90
-
91
- setPage(index: number, animated = true): this {
92
- if (this._id !== 0) {
93
- _creatorUI.command(this, "setPage", index, animated)
94
- } else {
95
- this._page = index // remembered as the initial page until the host mounts
96
- }
97
- return this
98
- }
99
-
100
- onPageChange(callback: (index: number) => void): this {
101
- this.pcl.push(callback)
102
- return this
103
- }
104
- onPageScroll(callback: (position: number) => void): this {
105
- this.psl.push(callback)
106
- return this
107
- }
108
-
109
- // Pages are separate layout roots, not yoga children, so the generic insertNode/removeNode path
110
- // doesn't apply. Keep `children` in sync and ask the host to reconcile pages by node identity.
111
- append(...nodes: UINodeChild[]): this {
112
- this.children.push(...nodes)
113
- if (this._id !== 0) _creatorUI.command(this, "syncPages")
114
- return this
115
- }
116
- insert(index: number, ...nodes: UINodeChild[]): this {
117
- this.children.splice(index, 0, ...nodes)
118
- if (this._id !== 0) _creatorUI.command(this, "syncPages")
119
- return this
120
- }
121
- remove(...nodes: UINodeChild[]): this {
122
- const set = new Set(nodes)
123
- this.children = this.children.filter(c => !set.has(c as UINodeChild))
124
- if (this._id !== 0) _creatorUI.command(this, "syncPages")
125
- return this
126
- }
127
- setContent(nodes: UINodeChild | UINodeChild[] | ChildrenFn): this {
128
- if (typeof nodes === "function") {
129
- // Reactive pages: the base binding drives _reconcile, which calls our insert/remove above.
130
- return super.setContent(nodes)
131
- }
132
- this.children = Array.isArray(nodes) ? [...nodes] : [nodes]
133
- if (this._id !== 0) _creatorUI.command(this, "syncPages")
134
- return this
135
- }
136
-
137
- // Replace one page in place, animated. The node travels via children[index] (the command channel
138
- // carries only index + transition); the host reads it back and swaps the page, animating the swap
139
- // when `index` is the visible page. Before mount it just seeds the initial content at that index.
140
- replace(index: number, screen: UINodeChild, transition: ScreenHostTransition = "fade"): this {
141
- if (index < 0 || index >= this.children.length) return this
142
- this.children[index] = screen
143
- if (this._id !== 0) _creatorUI.command(this, "replace", index, transition)
144
- return this
145
- }
146
-
147
- // scrollDirection/swipeEnabled are host behavior, not layout — intercept them here so they never
148
- // reach the layout engine, and forward live changes to the host. Everything else flows to the base
149
- // style proxy unchanged (including property get/set via the forwarding Proxy below).
150
- private _hostStyleProxy?: any
151
- get style(): any {
152
- if (this._hostStyleProxy) return this._hostStyleProxy
153
- const base = super.style
154
- const self = this
155
- const apply = (s: any) => {
156
- if (s && typeof s === "object") {
157
- if ("scrollDirection" in s && typeof s.scrollDirection !== "function") {
158
- self._dir = s.scrollDirection
159
- delete s.scrollDirection
160
- if (self._id !== 0) _creatorUI.command(self, "setDirection", self._dir)
161
- }
162
- if ("swipeEnabled" in s && typeof s.swipeEnabled !== "function") {
163
- self._swipe = s.swipeEnabled
164
- delete s.swipeEnabled
165
- if (self._id !== 0) _creatorUI.command(self, "setSwipe", self._swipe)
166
- }
167
- }
168
- return base(s)
169
- }
170
- this._hostStyleProxy = new Proxy(apply, {
171
- apply: (_t, _thisArg, args) => apply(args[0]),
172
- get: (_t, k) => (base as any)[k],
173
- set: (_t, k, v) => { (base as any)[k] = v; return true },
174
- })
175
- return this._hostStyleProxy
176
- }
177
- }
178
-
179
- function makeScreenHost(style: UIScreenHostStyle | null, children: UINodeChild[] | ChildrenFn): ScreenHostElement {
180
- const dir = style?.scrollDirection
181
- const swipe = style?.swipeEnabled
182
- if (style) {
183
- delete style.scrollDirection
184
- delete style.swipeEnabled
185
- }
186
- const el = new ScreenHostElement("screenhost", style ?? {}, children)
187
- if (dir !== undefined) el._dir = dir
188
- if (swipe !== undefined) el._swipe = swipe
189
- return el
190
- }
191
-
192
- // A screen is the only non-array, non-function, non-style argument the factory accepts.
193
- const isScreenArg = (v: any): boolean => v != null && typeof v === "object" && v.type === "screen"
194
- const asPages = (v: any): UINodeChild[] | ChildrenFn => (Array.isArray(v) || typeof v === "function") ? v : [v]
195
-
196
- export function UIScreenHost(): UIScreenHost
197
- export function UIScreenHost(screen: HostedScreen): UIScreenHost
198
- export function UIScreenHost(screens: HostedScreen[] | ScreensFn): UIScreenHost
199
- export function UIScreenHost(style: UIScreenHostStyle): UIScreenHost
200
- export function UIScreenHost(style: UIScreenHostStyle, screens: HostedScreen | HostedScreen[] | ScreensFn): UIScreenHost
201
- export function UIScreenHost(...args: [] | [HostedScreen | HostedScreen[] | ScreensFn | UIScreenHostStyle] | [UIScreenHostStyle, HostedScreen | HostedScreen[] | ScreensFn]): UIScreenHost {
202
- if (args.length === 0) {
203
- return makeScreenHost({}, [])
204
- } else if (args.length === 1) {
205
- if (Array.isArray(args[0]) || typeof args[0] === "function" || isScreenArg(args[0])) {
206
- return makeScreenHost({}, asPages(args[0]))
207
- } else {
208
- return makeScreenHost(args[0] as UIScreenHostStyle, [])
209
- }
210
- } else {
211
- return makeScreenHost(args[0] as UIScreenHostStyle, asPages(args[1]))
212
- }
213
- }