lecodes-cli 0.10.0 → 0.10.2

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.
@@ -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"
@@ -46,11 +46,14 @@ export const fetchLocal = (path: string): FetchResponse =>
46
46
 
47
47
  /** A handle to a picked / fetched file (see openFilePicker). */
48
48
  export class File {
49
- readonly id: number
49
+ /** @internal host buffer handle. `_`-prefixed because hosts read it off the raw object —
50
+ * same key `FetchResponse` carries, so both are accepted anywhere a buffer is expected
51
+ * (FormData entries, `UIImage` src, `Texture.load`, `share`). */
52
+ readonly _id: number
50
53
  readonly name: string
51
54
  readonly size: number
52
- constructor(id: number, name: string, size: number) {
53
- this.id = id
55
+ constructor(systemId: number, name: string, size: number) {
56
+ this._id = systemId
54
57
  this.name = name
55
58
  this.size = size
56
59
  }
@@ -59,6 +62,9 @@ export class File {
59
62
  type FormDataValue = string | number | boolean | File | FetchResponse
60
63
 
61
64
  export class FormData {
65
+ // Host-facing: hosts walk `_entries` and treat any object value carrying `_id` as binary
66
+ // (the buffer id), everything else as a text field. See viewer-lite utils/fetch.ts,
67
+ // creator-pkg creatorUtilsHandler.cpp, iOS Fetch.swift buildMultipartBody.
62
68
  private _entries: [string, FormDataValue, filename?: string][] = []
63
69
 
64
70
  append(name: string, value: FormDataValue, filename?: string): void {
@@ -5,5 +5,5 @@
5
5
  import type { File, FetchResponse } from "./fetch"
6
6
 
7
7
  export function share(media: File | FetchResponse, text?: string): void {
8
- _creatorUtils.shareMedia((media as any).id ?? (media as any)._id, text)
8
+ _creatorUtils.shareMedia((media as any)._id, text)
9
9
  }
@@ -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,
@@ -52,6 +52,12 @@ type SubmitStyle = {
52
52
  export interface UIInput {
53
53
  readonly type: "input",
54
54
 
55
+ /**
56
+ * The field's text. Writable at any time — set it BEFORE the screen opens to pre-fill a form
57
+ * (settings, edit-profile); the value is stashed on the node and applied when the host builds
58
+ * it. Reads return the live text while the screen is open, and the last value you set while it
59
+ * isn't. The user's typing is not preserved across close/open (use `keepAlive()` for that).
60
+ */
55
61
  value: string
56
62
 
57
63
  style: Style<this, UIInputStyle & SubmitStyle & { onFocused?: UIInputStyle & { duration?: number } }>,
@@ -71,15 +77,16 @@ export interface UIInput {
71
77
  /** Release focus (dismisses the keyboard when this input held it). */
72
78
  blur(): void
73
79
 
74
- setClass(name: string, enabled: boolean): this
75
- toggleClass(name: string): this
76
- hasClass(name: string): boolean
77
- bindClass(name: string, fn: () => boolean): this
80
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
81
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
82
+ * Classes cascade to descendants. See {@link Classes}. */
83
+ readonly class: Classes<this>
78
84
  }
79
85
 
80
86
  export interface UITextArea {
81
87
  readonly type: "textarea",
82
88
 
89
+ /** The field's text — same contract as {@link UIInput.value}: settable before mount to pre-fill. */
83
90
  value: string
84
91
 
85
92
  style: Style<this, UIInputStyle & { onFocused?: UIInputStyle & { duration?: number } }>,
@@ -98,10 +105,10 @@ export interface UITextArea {
98
105
  /** Release focus (dismisses the keyboard when this text area held it). */
99
106
  blur(): void
100
107
 
101
- setClass(name: string, enabled: boolean): this
102
- toggleClass(name: string): this
103
- hasClass(name: string): boolean
104
- bindClass(name: string, fn: () => boolean): this
108
+ /** Style-class proxy — read `el.class.checked`, set `el.class.checked = true`, toggle with
109
+ * `!el.class.checked`, bind `el.class.done = () => sig.value`, batch/chain `el.class({ … })`.
110
+ * Classes cascade to descendants. See {@link Classes}. */
111
+ readonly class: Classes<this>
105
112
  }
106
113
 
107
114
  class InputElement<T extends "input" | "textarea"> extends Element<T> {
@@ -111,10 +118,22 @@ class InputElement<T extends "input" | "textarea"> extends Element<T> {
111
118
  readonly bl: Function[] = []
112
119
  readonly sl: Function[] = []
113
120
 
121
+ /** @internal The value as last set by CODE. Hosts read it at node creation (after the initial
122
+ * style, so a `date` field's canonical parse and `maxLength` are already in place), which is
123
+ * what makes a value assigned before mount — a pre-filled settings form — land on the field.
124
+ * Same pre-mount stash as UIImage's `_sourceRect`. Typing does NOT sync back here: while
125
+ * mounted the HOST owns the text (IME composition, paste, the maxLength clamp, date/time
126
+ * canonicalization) and the getter reads it over the bridge. So on a remount the field comes
127
+ * back with the last code-set value, not what the user typed — screens that must keep live
128
+ * editing state across close/open use `keepAlive()`, the mechanism built for it. */
129
+ _value = ""
130
+
114
131
  get value() {
115
- return _creatorUI.getTextValue(this._id)
132
+ // Mounted: the host is authoritative. Unmounted: what code last set (0 => never mounted).
133
+ return this._id !== 0 ? _creatorUI.getTextValue(this._id) : this._value
116
134
  }
117
135
  set value(value: string) {
136
+ this._value = value
118
137
  if (this._id !== 0) {
119
138
  _creatorUI.updateNode(this._id, "value", value)
120
139
  }
@@ -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> {