lecodes-cli 0.9.2 → 0.10.1

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 (35) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +677 -230
  3. package/package.json +2 -2
  4. package/runtime/scene-harness.json +1 -1
  5. package/runtime/sdk/compile/aspectMacro.ts +42 -0
  6. package/runtime/sdk/compile/assetMacro.ts +45 -0
  7. package/runtime/sdk/compile/bundler.ts +239 -0
  8. package/runtime/sdk/compile/compileProject.ts +104 -0
  9. package/runtime/sdk/compile/detectEntry.ts +125 -0
  10. package/runtime/sdk/compile/fontMacro.ts +459 -0
  11. package/runtime/sdk/compile/fontRegistry.ts +78 -0
  12. package/runtime/sdk/compile/header.ts +51 -0
  13. package/runtime/sdk/compile/index.ts +38 -0
  14. package/runtime/sdk/compile/libraryImports.ts +52 -0
  15. package/runtime/sdk/compile/sceneEditor.ts +78 -0
  16. package/runtime/sdk/compile/sfnt.ts +98 -0
  17. package/runtime/sdk/compile/sourcemap.ts +25 -0
  18. package/runtime/sdk/gl/Material.ts +52 -0
  19. package/runtime/sdk/gl/Particles.ts +477 -116
  20. package/runtime/sdk/inject.ts +1 -1
  21. package/runtime/sdk/ui/NativeView.ts +5 -5
  22. package/runtime/sdk/ui/UIButton.ts +5 -5
  23. package/runtime/sdk/ui/UIContainer.ts +13 -13
  24. package/runtime/sdk/ui/UIImage.ts +5 -5
  25. package/runtime/sdk/ui/UIInput.ts +9 -9
  26. package/runtime/sdk/ui/UINode.ts +114 -34
  27. package/runtime/sdk/ui/UIPager.ts +5 -9
  28. package/runtime/sdk/ui/UIScreen.ts +5 -5
  29. package/runtime/sdk/ui/UIScrollable.ts +5 -5
  30. package/runtime/sdk/ui/UISpacer.ts +5 -5
  31. package/runtime/sdk/ui/UIText.ts +5 -5
  32. package/runtime/sdk/ui/UIVideo.ts +5 -5
  33. package/runtime/sdk/ui/UIVirtualizedList.ts +5 -5
  34. package/runtime/sdk/ui/UIWidget.ts +5 -5
  35. package/runtime/sdk-types.json +1 -1
@@ -126,4 +126,4 @@ export { Camera } from "./gl/Camera"
126
126
  export { Ray } from "./gl/Ray"
127
127
  export { Plane } from "./gl/Plane"
128
128
  export { Noise } from "./gl/Noise"
129
- export { Particles, dynamic, dynamicColor } from "./gl/Particles"
129
+ export { Particles, Trail, curve, colorCurve } from "./gl/Particles"
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { _channelCall, _channelEmit, _channelOff, _channelOn, type ChannelListeners } from "../runtime/channel"
13
13
  import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "./presentable"
14
- import { Element, type AnimateStyle, type BaseStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Style } from "./UINode"
14
+ import { Element, type AnimateStyle, type BaseStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Classes, type Style } from "./UINode"
15
15
 
16
16
  export type UINativeViewStyle = ElementStyle & DrawableStyle
17
17
 
@@ -42,10 +42,10 @@ export interface NativeView {
42
42
 
43
43
  onLayout(onLayout: OnLayoutCallback): this
44
44
  getBoundingClientRect(): BoundingClientRect | null
45
- setClass(name: string, enabled: boolean): this
46
- toggleClass(name: string): this
47
- hasClass(name: string): boolean
48
- bindClass(name: string, fn: () => boolean): this
45
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
46
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
47
+ * Classes cascade to descendants. See {@link Classes}. */
48
+ readonly class: Classes<this>
49
49
  }
50
50
 
51
51
  let nextViewId = 1
@@ -1,6 +1,6 @@
1
1
  import { ClickEvent, TouchStartEvent, LongPressEvent } from "../runtime/touch"
2
2
  import type { ClickEvent as ClickEventType, TouchStartEvent as TouchStartEventType, LongPressEvent as LongPressEventType } from "../runtime/touch"
3
- import { type ElementStyle, type ContainerStyle, type Style, ContainerElement, type DrawableStyle, type UINodeChild, type ChildrenFn, type OnLayoutCallback, type BoundingClientRect, type BaseStyle, type AnimateStyle, type Color } from "./UINode"
3
+ import { type ElementStyle, type ContainerStyle, type Classes, type Style, ContainerElement, type DrawableStyle, type UINodeChild, type ChildrenFn, type OnLayoutCallback, type BoundingClientRect, type BaseStyle, type AnimateStyle, type Color } from "./UINode"
4
4
 
5
5
  export type UIButtonStyle = ElementStyle & DrawableStyle & ContainerStyle & { rippleColor?: Color | "default" }
6
6
 
@@ -27,10 +27,10 @@ export interface UIButton {
27
27
  onLayout(onLayout: OnLayoutCallback): this
28
28
  getBoundingClientRect(): BoundingClientRect | null
29
29
 
30
- setClass(name: string, enabled: boolean): this
31
- toggleClass(name: string): this
32
- hasClass(name: string): boolean
33
- bindClass(name: string, fn: () => boolean): this
30
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
31
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
32
+ * Classes cascade to descendants. See {@link Classes}. */
33
+ readonly class: Classes<this>
34
34
 
35
35
  readonly children: UINodeChild[],
36
36
  }
@@ -1,4 +1,4 @@
1
- import { ContainerElement, type AnimateStyle, type AppearStyle, type ChildrenFn, type ContainerStyle, type DisappearStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Style, type StyleFn, type UINode, type UINodeChild } from "./UINode"
1
+ import { ContainerElement, type AnimateStyle, type AppearStyle, type ChildrenFn, type ContainerStyle, type DisappearStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Classes, type Style, type StyleFn, type UINode, type UINodeChild } from "./UINode"
2
2
 
3
3
  export type UIContainerStyle = ElementStyle & DrawableStyle & ContainerStyle
4
4
 
@@ -16,10 +16,10 @@ export interface UIColumn {
16
16
  onLayout(onLayout: OnLayoutCallback): this
17
17
  getBoundingClientRect(): BoundingClientRect | null
18
18
 
19
- setClass(name: string, enabled: boolean): this
20
- toggleClass(name: string): this
21
- hasClass(name: string): boolean
22
- bindClass(name: string, fn: () => boolean): this
19
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
20
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
21
+ * Classes cascade to descendants. See {@link Classes}. */
22
+ readonly class: Classes<this>
23
23
 
24
24
  readonly children: UINodeChild[]
25
25
  }
@@ -55,10 +55,10 @@ export interface UIRow {
55
55
  onLayout(onLayout: OnLayoutCallback): this
56
56
  getBoundingClientRect(): BoundingClientRect | null
57
57
 
58
- setClass(name: string, enabled: boolean): this
59
- toggleClass(name: string): this
60
- hasClass(name: string): boolean
61
- bindClass(name: string, fn: () => boolean): this
58
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
59
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
60
+ * Classes cascade to descendants. See {@link Classes}. */
61
+ readonly class: Classes<this>
62
62
 
63
63
  readonly children: UINodeChild[],
64
64
  }
@@ -94,10 +94,10 @@ export interface UIBox {
94
94
  onLayout(onLayout: OnLayoutCallback): this
95
95
  getBoundingClientRect(): BoundingClientRect | null
96
96
 
97
- setClass(name: string, enabled: boolean): this
98
- toggleClass(name: string): this
99
- hasClass(name: string): boolean
100
- bindClass(name: string, fn: () => boolean): this
97
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
98
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
99
+ * Classes cascade to descendants. See {@link Classes}. */
100
+ readonly class: Classes<this>
101
101
 
102
102
  readonly children: UINodeChild[],
103
103
  }
@@ -2,7 +2,7 @@ import type { FetchResponse } from "../runtime/fetch"
2
2
  import type { SvgSourceValue as SvgSource } from "../runtime/misc"
3
3
  import type { File } from "../runtime/fetch"
4
4
  import { Canvas, isCanvas } from "../canvas/Canvas"
5
- import { Element, type AnimateStyle, type Color, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Style } from "./UINode"
5
+ import { Element, type AnimateStyle, type Color, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Classes, type Style } from "./UINode"
6
6
 
7
7
  export type UIImageStyle = ElementStyle & { borderRadius?: number, objectFit?: "cover" | "contain" | "fill", tintColor?: Color }
8
8
 
@@ -39,10 +39,10 @@ export interface UIImage {
39
39
  onLayout(onLayout: OnLayoutCallback): this
40
40
  getBoundingClientRect(): BoundingClientRect | null
41
41
 
42
- setClass(name: string, enabled: boolean): this
43
- toggleClass(name: string): this
44
- hasClass(name: string): boolean
45
- bindClass(name: string, fn: () => boolean): this
42
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
43
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
44
+ * Classes cascade to descendants. See {@link Classes}. */
45
+ readonly class: Classes<this>
46
46
  }
47
47
 
48
48
  class ImageElement extends Element<"image"> {
@@ -1,4 +1,4 @@
1
- import { type ElementStyle, type DrawableStyle, type Style, type StyleFn, type AppearStyle, type DisappearStyle, Element, type TextStyle, type Color, type OnLayoutCallback, type BoundingClientRect, type BaseStyle, type AnimateStyle } from "./UINode";
1
+ import { type ElementStyle, type DrawableStyle, type Classes, type Style, type StyleFn, type AppearStyle, type DisappearStyle, Element, type TextStyle, type Color, type OnLayoutCallback, type BoundingClientRect, type BaseStyle, type AnimateStyle } from "./UINode";
2
2
 
3
3
  type InputStyle = {
4
4
  placeholder?: string,
@@ -71,10 +71,10 @@ export interface UIInput {
71
71
  /** Release focus (dismisses the keyboard when this input held it). */
72
72
  blur(): void
73
73
 
74
- setClass(name: string, enabled: boolean): this
75
- toggleClass(name: string): this
76
- hasClass(name: string): boolean
77
- bindClass(name: string, fn: () => boolean): this
74
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
75
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
76
+ * Classes cascade to descendants. See {@link Classes}. */
77
+ readonly class: Classes<this>
78
78
  }
79
79
 
80
80
  export interface UITextArea {
@@ -98,10 +98,10 @@ export interface UITextArea {
98
98
  /** Release focus (dismisses the keyboard when this text area held it). */
99
99
  blur(): void
100
100
 
101
- setClass(name: string, enabled: boolean): this
102
- toggleClass(name: string): this
103
- hasClass(name: string): boolean
104
- bindClass(name: string, fn: () => boolean): this
101
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
102
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
103
+ * Classes cascade to descendants. See {@link Classes}. */
104
+ readonly class: Classes<this>
105
105
  }
106
106
 
107
107
  class InputElement<T extends "input" | "textarea"> extends Element<T> {
@@ -6,6 +6,8 @@ export interface UINode {
6
6
  /** Author-given semantic name (a stable selector for tests + AI review feedback). */
7
7
  readonly name?: string
8
8
  style: Style<this, any>
9
+ /** Style-class proxy — read/set/toggle/bind the `$`-classes declared in `.style()`. See {@link Classes}. */
10
+ readonly class: Classes<this>
9
11
  }
10
12
 
11
13
  export type UINodeChild = UINode | null | undefined | false
@@ -238,13 +240,73 @@ const handler: ProxyHandler<any> = {
238
240
  getOwnPropertyDescriptor(target, p) {
239
241
  return Object.getOwnPropertyDescriptor(target._el._style, p)
240
242
  },
241
- ownKeys(target) {
243
+ ownKeys(target) {
242
244
  return Object.keys(target._el._style)
243
245
  }
244
246
  }
245
247
 
248
+ // ---- the el.class proxy --------------------------------------------------------------------------
249
+
250
+ // `$checked` and `checked` name the same class — `$` is only the declaration-site prefix in .style().
251
+ const classKey = (name: string) => name[0] === "$" ? name.slice(1) : name
252
+
253
+ // Single-class state write, shared by the set trap, the batch form and binding re-runs. No-op when
254
+ // the state already matches, so an unchanged binding re-run doesn't resend over the bridge.
255
+ function writeClass(el: any, name: string, enabled: boolean) {
256
+ if ((el._classes?.has(name) ?? false) === enabled) return
257
+ if (enabled) {
258
+ (el._classes ??= new Set()).add(name)
259
+ } else {
260
+ el._classes.delete(name)
261
+ }
262
+ if (el._id !== 0) {
263
+ _creatorUI.setClass(el._id, name, enabled)
264
+ }
265
+ }
266
+
267
+ // One class write: a function value installs/replaces a reactive binding, a plain value replaces
268
+ // the binding (last write wins — unlike the style proxy's hot-path caveat, a stale class binding
269
+ // silently reverting a later plain write would be a footgun, and class flips are never per-frame).
270
+ function writeClassValue(el: any, name: string, value: ClassValue) {
271
+ name = classKey(name)
272
+ if (typeof value === "function") {
273
+ createBinding(el, "#class:" + name, () => { writeClass(el, name, !!value()) })
274
+ } else {
275
+ if (el._fx !== undefined) disposeBinding(el, "#class:" + name)
276
+ writeClass(el, name, !!value)
277
+ }
278
+ }
279
+
280
+ // The callable target of the el.class proxy (batch form), bound to the element like styleFunction.
281
+ function classFunction(this: any, classes: Record<string, ClassValue>) {
282
+ for (const key of Object.keys(classes)) {
283
+ writeClassValue(this, key, classes[key])
284
+ }
285
+ return this
286
+ }
287
+
288
+ const classHandler: ProxyHandler<any> = {
289
+ get(target, key) {
290
+ if (typeof key !== "string") return undefined
291
+ return target._el._classes?.has(classKey(key)) ?? false
292
+ },
293
+ set(target, key, value) {
294
+ if (typeof key === "string") writeClassValue(target._el, key, value)
295
+ return true
296
+ },
297
+ // Object.keys(el.class) / spread list the ACTIVE class names (inspection + tests).
298
+ getOwnPropertyDescriptor(target, p) {
299
+ if (typeof p !== "string" || !(target._el._classes?.has(classKey(p)) ?? false)) return undefined
300
+ return { value: true, enumerable: true, configurable: true, writable: true }
301
+ },
302
+ ownKeys(target) {
303
+ const classes = target._el._classes
304
+ return classes !== undefined ? [ ...classes ] : []
305
+ }
306
+ }
307
+
246
308
  function Style() {
247
-
309
+
248
310
  }
249
311
 
250
312
  // Reactive style input: every primitive-valued prop also accepts a `() => value` binding that
@@ -253,7 +315,7 @@ function Style() {
253
315
  export type Reactive<T> = { [K in keyof T]: NonNullable<T[K]> extends object ? T[K] : T[K] | (() => T[K]) }
254
316
 
255
317
  // User-defined style classes: any `$`-prefixed key in .style() declares a state block (like
256
- // onPressed, but with any name), toggled from code via setClass/toggleClass. `duration`/`delay`
318
+ // onPressed, but with any name), toggled from code via the `el.class` proxy. `duration`/`delay`
257
319
  // make the swap transition. Class state INHERITS down the tree (CSS-`.dark`-on-body style): a
258
320
  // class set on a node also activates same-name `$` blocks on all descendants within the same
259
321
  // root — see docs/style-class-cascade-plan.md. `$pressed`/`$focused` are reserved: hosts toggle
@@ -265,6 +327,30 @@ export type Reactive<T> = { [K in keyof T]: NonNullable<T[K]> extends object ? T
265
327
  // tests/ui-types.test.ts.
266
328
  export type ClassStyles<T> = { [key: `$${string}`]: T & { duration?: number, delay?: number } }
267
329
 
330
+ /** Value accepted by a `el.class` write: a boolean sets the class, a `() => boolean` binds it —
331
+ * the class then tracks the signals the function reads (re-evaluated on change). Reads through
332
+ * the proxy always return a plain boolean, never the bound function. */
333
+ export type ClassValue = boolean | (() => boolean)
334
+
335
+ /** The `el.class` proxy — the runtime switch for `$`-class blocks declared in `.style()`
336
+ * (see {@link ClassStyles}). One surface, mirroring `el.style`:
337
+ *
338
+ * ```ts
339
+ * el.class.checked // read: is it active on THIS element? → boolean
340
+ * el.class.checked = true // activate (false deactivates)
341
+ * el.class.open = !el.class.open // toggle
342
+ * el.class.done = () => todo.done.value // reactive binding (signals)
343
+ * el.class({ checked: true, done: () => … }) // batch form — returns the element, chainable
344
+ * ```
345
+ *
346
+ * Class names are accepted with or without the declaration-site `$` prefix. `R` is the concrete
347
+ * node type, so the batch form chains like `.style()`.
348
+ *
349
+ * Typing note: property access is `any` because an index signature can't give reads (`boolean`)
350
+ * and writes ({@link ClassValue}) different types — the batch form is the fully typed path;
351
+ * single-key writes are runtime-coerced with `!!`. */
352
+ export type Classes<R> = ((classes: Record<string, ClassValue>) => R) & { [key: string]: any }
353
+
268
354
  // NB: do NOT annotate `this: R` here. `this` is a parameter position, so it makes R contravariant,
269
355
  // which makes every node type INVARIANT in its own type params (and in `this`). That is what made
270
356
  // `UIScreen<false>` unassignable to `UIScreen<boolean>` (a navigator page), and would block any
@@ -273,7 +359,7 @@ export type ClassStyles<T> = { [key: `$${string}`]: T & { duration?: number, del
273
359
  // R stays only in the (covariant) return, so `.style()` still returns the concrete node type for
274
360
  // chaining. Guarded by the type tests in tests/ui-types.test-d.ts.
275
361
  // Orientation state blocks — override props applied only in landscape / portrait (like a `$`-class,
276
- // keyed by device orientation instead of setClass). Same full-`T` rule as class blocks.
362
+ // keyed by device orientation instead of `el.class`). Same full-`T` rule as class blocks.
277
363
  export type OrientationStyles<T> = {
278
364
  onLandscape?: T,
279
365
  onPortrait?: T,
@@ -397,37 +483,31 @@ export class Element<T extends string> {
397
483
  * re-assign _id on every open). */
398
484
  protected _classes?: Set<string>
399
485
 
400
- /** Activate/deactivate a `$`-class declared in .style() (accepts the name with or without `$`).
401
- * The class cascades: it is also active on every descendant (their same-name `$` blocks light
402
- * up), until the subtree's root — hosted screens/widgets don't inherit. `hasClass`/`toggleClass`
403
- * reflect only THIS node's own classes; there is no opt-out below an active ancestor. */
404
- setClass(name: string, enabled: boolean): this {
405
- if (name[0] === "$") name = name.slice(1)
406
- if (enabled) {
407
- (this._classes ??= new Set()).add(name)
408
- } else {
409
- this._classes?.delete(name)
410
- }
411
- if (this._id !== 0) {
412
- _creatorUI.setClass(this._id, name, enabled)
413
- }
414
- return this
415
- }
416
-
417
- toggleClass(name: string): this {
418
- return this.setClass(name, !this.hasClass(name))
419
- }
420
-
421
- hasClass(name: string): boolean {
422
- return this._classes?.has(name[0] === "$" ? name.slice(1) : name) ?? false
423
- }
486
+ protected _classProxy?: any
487
+
488
+ /** Style-class control — flips the `$`-class blocks declared in `.style()`. One proxy, same
489
+ * contract as `el.style`:
490
+ *
491
+ * - `el.class.checked` — read: is the class active on THIS element? → `boolean`
492
+ * - `el.class.checked = true` / `= false` — activate / deactivate
493
+ * - `el.class.open = !el.class.open` — toggle
494
+ * - `el.class.done = () => todo.done.value` — reactive: the class tracks the signals the
495
+ * function reads; a later write to the same class (plain or function) replaces the binding
496
+ * - `el.class({ checked: true, done: () => … })` — batch form, returns the element (chainable)
497
+ *
498
+ * Names are accepted with or without the leading `$`. A class set on an element CASCADES: it is
499
+ * also active on every descendant (their same-name `$` blocks light up), until the subtree's
500
+ * root — hosted screens/widgets don't inherit. Reads reflect only this element's own classes;
501
+ * there is no opt-out below an active ancestor. `Object.keys(el.class)` lists the active names. */
502
+ get class(): any {
503
+ if (this._classProxy) return this._classProxy
504
+ const obj = classFunction.bind(this)
505
+ // @ts-ignore
506
+ obj._el = this
424
507
 
425
- /** Reactive class binding: keeps the `$`-class active exactly while `fn()` is true — re-runs
426
- * when a signal `fn` read changes. Re-binding the same name replaces the previous binding. */
427
- bindClass(name: string, fn: () => boolean): this {
428
- if (name[0] === "$") name = name.slice(1)
429
- createBinding(this, "#class:" + name, () => { this.setClass(name, !!fn()) })
430
- return this
508
+ const proxy = new Proxy(obj, classHandler)
509
+ this._classProxy = proxy
510
+ return proxy
431
511
  }
432
512
 
433
513
  protected ll?: OnLayoutCallback[]
@@ -1,4 +1,4 @@
1
- import { ContainerElement, type AnimateStyle, type BaseStyle, type ChildrenFn, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Style, type UINodeChild } from "./UINode"
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"
2
2
  import { disposeBinding } from "../core/signals"
3
3
  import type { UIScreen } from "./UIScreen"
4
4
 
@@ -84,10 +84,10 @@ export interface UIPager {
84
84
  onLayout(onLayout: OnLayoutCallback): this,
85
85
  getBoundingClientRect(): BoundingClientRect | null,
86
86
 
87
- setClass(name: string, enabled: boolean): this,
88
- toggleClass(name: string): this,
89
- hasClass(name: string): boolean,
90
- bindClass(name: string, fn: () => boolean): this,
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
91
  }
92
92
 
93
93
  export class PagerElement extends ContainerElement<"pager"> {
@@ -268,7 +268,6 @@ export class PagerElement extends ContainerElement<"pager"> {
268
268
  // top, this pager is active. The hook reads screen._pagerOwner LIVE (not a captured `this`), so
269
269
  // re-homing a screen into another pager activates the right one.
270
270
  private _track(screen: any): void {
271
- console.log("_track()")
272
271
  screen._pagerOwner = this
273
272
  if (!screen._pagerHooked) {
274
273
  screen._pagerHooked = true
@@ -277,9 +276,7 @@ export class PagerElement extends ContainerElement<"pager"> {
277
276
  }
278
277
  }
279
278
  _activate(): void {
280
- console.log("openCount: " + this._openCount)
281
279
  if (++this._openCount === 1 && _activeStack.indexOf(this) < 0) {
282
- console.log("activate")
283
280
  _activeStack.push(this)
284
281
  }
285
282
  }
@@ -288,7 +285,6 @@ export class PagerElement extends ContainerElement<"pager"> {
288
285
  this._openCount = 0
289
286
  const i = _activeStack.indexOf(this)
290
287
  if (i >= 0) _activeStack.splice(i, 1)
291
- console.log(`Deactivate. Curent stack is ${_activeStack.length}`)
292
288
  }
293
289
  }
294
290
  }
@@ -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 ContainerStyle, type DrawableStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type Style, type UINodeChild, type ChildrenFn, } from "./UINode"
3
+ import { ContainerElement, type AnimateStyle, type BaseStyle, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type Classes, type Style, type UINodeChild, type ChildrenFn, } from "./UINode"
4
4
 
5
5
  export type UIScreenStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle
6
6
 
@@ -46,10 +46,10 @@ export interface UIScreen {
46
46
  onLayout(onLayout: OnLayoutCallback): this
47
47
  getBoundingClientRect(): BoundingClientRect | null
48
48
 
49
- setClass(name: string, enabled: boolean): this
50
- toggleClass(name: string): this
51
- hasClass(name: string): boolean
52
- bindClass(name: string, fn: () => boolean): this
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
53
 
54
54
  onTouchStart(callback: (ev: TouchStartEvent<UIScreen>) => void): this,
55
55
  }
@@ -1,5 +1,5 @@
1
1
  import type { UIContainerStyle } from "./UIContainer"
2
- import { type Style, type AnimateStyle, type UINodeChild, type ChildrenFn, type OnLayoutCallback, type BoundingClientRect, type Color, ContainerElement } from "./UINode"
2
+ import { type Classes, type Style, type AnimateStyle, type UINodeChild, type ChildrenFn, type OnLayoutCallback, type BoundingClientRect, type Color, ContainerElement } from "./UINode"
3
3
 
4
4
  // refreshControlColor tints the native pull-to-refresh spinner (pairs with onRefresh).
5
5
  // keyboardDismissMode: how a scroll gesture in THIS scrollable dismisses the keyboard.
@@ -28,10 +28,10 @@ export interface UIScrollable {
28
28
  onLayout(onLayout: OnLayoutCallback): this
29
29
  getBoundingClientRect(): BoundingClientRect | null
30
30
 
31
- setClass(name: string, enabled: boolean): this
32
- toggleClass(name: string): this
33
- hasClass(name: string): boolean
34
- bindClass(name: string, fn: () => boolean): this
31
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
32
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
33
+ * Classes cascade to descendants. See {@link Classes}. */
34
+ readonly class: Classes<this>
35
35
 
36
36
  onScroll(callback: (scrollPosition: number) => void): UIScrollable,
37
37
  onScrollRelease(callback: () => void): UIScrollable,
@@ -1,4 +1,4 @@
1
- import { Element, type ElementStyle, type Style, type StyleFn } from "./UINode"
1
+ import { Element, type ElementStyle, type Classes, type Style, type StyleFn } from "./UINode"
2
2
 
3
3
  export type UISpacerStyle = ElementStyle
4
4
 
@@ -6,10 +6,10 @@ export interface UISpacer {
6
6
  readonly type: "spacer",
7
7
  style: Style<this, UISpacerStyle>,
8
8
 
9
- setClass(name: string, enabled: boolean): this
10
- toggleClass(name: string): this
11
- hasClass(name: string): boolean
12
- bindClass(name: string, fn: () => boolean): this
9
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
10
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
11
+ * Classes cascade to descendants. See {@link Classes}. */
12
+ readonly class: Classes<this>
13
13
  }
14
14
  export function UISpacer(): UISpacer
15
15
  export function UISpacer(style: UISpacerStyle): UISpacer
@@ -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 BoundingClientRect, type Style, type TextStyle } from "./UINode"
2
+ import { Element, type AnimateStyle, type BaseStyle, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type Classes, type Style, type TextStyle } from "./UINode"
3
3
 
4
4
  export type UITextStyle = ElementStyle & TextStyle & DrawableStyle
5
5
 
@@ -13,10 +13,10 @@ export interface UIText {
13
13
  onLayout(onLayout: OnLayoutCallback): this
14
14
  getBoundingClientRect(): BoundingClientRect | null
15
15
 
16
- setClass(name: string, enabled: boolean): this
17
- toggleClass(name: string): this
18
- hasClass(name: string): boolean
19
- bindClass(name: string, fn: () => boolean): this
16
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
17
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
18
+ * Classes cascade to descendants. See {@link Classes}. */
19
+ readonly class: Classes<this>
20
20
  }
21
21
 
22
22
  export class TextElement extends Element<"text"> {
@@ -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 BoundingClientRect, 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 Classes, type Style, type StyleFn } from "./UINode"
4
4
 
5
5
  export type UIVideoStyle = ElementStyle & DrawableStyle & { objectFit?: "cover" | "contain" | "fill" }
6
6
 
@@ -22,10 +22,10 @@ export interface UIVideo {
22
22
  onLayout(onLayout: OnLayoutCallback): this
23
23
  getBoundingClientRect(): BoundingClientRect | null
24
24
 
25
- setClass(name: string, enabled: boolean): this
26
- toggleClass(name: string): this
27
- hasClass(name: string): boolean
28
- bindClass(name: string, fn: () => boolean): this
25
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
26
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
27
+ * Classes cascade to descendants. See {@link Classes}. */
28
+ readonly class: Classes<this>
29
29
  }
30
30
 
31
31
  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 Style, type UINodeChild } from "./UINode"
1
+ import { Element, type Color, type DrawableStyle, type ElementStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type Classes, type Style, type UINodeChild } from "./UINode"
2
2
 
3
3
  // refreshControlColor tints the native pull-to-refresh spinner (pairs with onRefresh).
4
4
  export type UIVirtualizedListStyle = DrawableStyle & ElementStyle & { refreshControlColor?: Color }
@@ -51,10 +51,10 @@ export interface UIVirtualizedList<T = unknown> {
51
51
  onLayout(onLayout: OnLayoutCallback): this
52
52
  getBoundingClientRect(): BoundingClientRect | null
53
53
 
54
- setClass(name: string, enabled: boolean): this
55
- toggleClass(name: string): this
56
- hasClass(name: string): boolean
57
- bindClass(name: string, fn: () => boolean): this
54
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
55
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
56
+ * Classes cascade to descendants. See {@link Classes}. */
57
+ readonly class: Classes<this>
58
58
  }
59
59
 
60
60
  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, 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"
3
+ import { ContainerElement, type AnimateStyle, type BaseStyle, type Color, type ContainerStyle, type DrawableStyle, type OnLayoutCallback, type BoundingClientRect, type PaddingStyle, type PositionStyle, type Classes, type Style, type UINodeChild, type ChildrenFn } from "./UINode"
4
4
 
5
5
 
6
6
  export type UIWidgetStyle = ContainerStyle & DrawableStyle & PaddingStyle & BaseStyle & PositionStyle & {
@@ -49,10 +49,10 @@ export interface UIWidget {
49
49
  onOverlayTap(callback: () => void): this,
50
50
  onBackPressed(callback: () => void): this
51
51
 
52
- setClass(name: string, enabled: boolean): this
53
- toggleClass(name: string): this
54
- hasClass(name: string): boolean
55
- bindClass(name: string, fn: () => boolean): this
52
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
53
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
54
+ * Classes cascade to descendants. See {@link Classes}. */
55
+ readonly class: Classes<this>
56
56
  }
57
57
 
58
58