lecodes-sdk 2.5.0 → 2.6.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.
@@ -41,6 +41,12 @@ export type FoliageOptions = {
41
41
  /** Coverage held over distance: the cards' alpha × (1 + coverage × mip level), so a far crown does not thin out as its
42
42
  * atlas mips away. Default 0.25, 0 = none; past ~0.5 a crown turns into a solid mass. */
43
43
  coverage?: number;
44
+ /** 0..1: THE SKY THROUGH A THIN LEAF. A crown's cards are lit by the sky on their hull normal (out of the crown's centre),
45
+ * so a card below the crown's middle faces DOWN and reads the sky's lower hemisphere — the ground of the environment
46
+ * map, which under an overcast sky is a tenth of the zenith: the lower half of every plant went black (2026-11-04). A
47
+ * leaf is thin: this share of the sky on the OTHER side of the card is added, so the underside of a crown takes this
48
+ * fraction of what its top takes. Default 0.3, 0 = the hull alone. */
49
+ skyThrough?: number;
44
50
  /** THE PLANTS' CASTER DISTANCE, metres: a plant casts the sun's shadow only this close to the camera (the LOD pass takes
45
51
  * the farther ones out of the shadow map; 3 m of hysteresis). Meant for a level with a BAKED ground, which keeps the
46
52
  * far shadows while the shadow map holds a fraction of the casters (the Forest's Radeon frame: 6 ms of 39 at 25 m).
@@ -1,4 +1,4 @@
1
- import { ModalElement, type UIModalStyle } from "./UIModal";
1
+ import { ModalElement, type UIModalShow, type UIModalStyle } from "./UIModal";
2
2
  import type { UIWidget } from "./UIWidget";
3
3
  import { type ChildrenFn, type UIChildArg, type UINodeChild } from "./UINode";
4
4
  export type UIBottomSheetStyle = UIModalStyle;
@@ -10,14 +10,18 @@ export type UIBottomSheetStyle = UIModalStyle;
10
10
  * node type — docs/bottom-sheet-plan.md): hosts without sheet support show a static bottom panel.
11
11
  *
12
12
  * With detents, content lays out at the HIGHEST detent's size; lower detents show the top
13
- * slice. The scrim, `onOpen`/`onClose`, back-button and scrim-tap dismissal all come from the
14
- * modal machinery.
13
+ * slice. The scrim, `bindShow` / `onDismiss`, `onOpen` / `onClose` all come from the modal
14
+ * machinery; the drag below the lowest detent is the sheet's third dismissal gesture, routed like
15
+ * the scrim tap. The host hears whether a drag may dismiss (`sheetDismissible`):
16
+ * `dismissible(false)`, or a bound sheet with no `onDismiss`, resists instead.
15
17
  *
16
18
  * ```ts
17
- * const actions = UIBottomSheet(rows).style({ bgColor: "#1c1c1e", borderRadius: 16 }) // content-sized
19
+ * const actions = UIBottomSheet(rows).style({ bgColor: "#1c1c1e", borderRadius: 16 }) // content-sized, show()
20
+ *
21
+ * const card = UIBottomSheet(place).bindShow(() => data.value.pick != null).onDismiss(on.unpick) // the data's
18
22
  *
19
23
  * const mapSheet = UIBottomSheet(results).style({ bgColor: "#1c1c1e", borderRadius: 16 })
20
- * .detents([0.3, 0.6, 1])
24
+ * .detents([0.3, 0.6, 1]).dismissible(false) // persistent: show() once placed
21
25
  * .onDetent(i => map.setPadding(...))
22
26
  * ```
23
27
  */
@@ -32,15 +36,20 @@ export interface UIBottomSheet extends UIWidget {
32
36
  readonly detent: number;
33
37
  /** Fires on every settle: a finger snap on native hosts, or a `setDetent()` call. */
34
38
  onDetent(callback: (index: number) => void): this;
35
- /** Mount and slide in to the current detent. No-op while already open. */
39
+ /** Bind the shown state, as on `UIModal`: a function of the data (`bindShow(() =>
40
+ * data.value.pick != null)`) or a signal. */
41
+ bindShow(value: UIModalShow): this;
42
+ /** Slide in to the current detent. No-op while already shown. Bound: as on `UIModal`. */
36
43
  show(): void;
37
- /** Slide out, fade the scrim, then unmount. No-op while closed. */
44
+ /** Slide out, fade the scrim. No-op while hidden. */
38
45
  hide(): void;
39
- /** True from `show()` until `hide()` starts. */
40
- readonly isOpen: boolean;
41
- /** Called when `show()` mounts the sheet. */
46
+ /** The user asked to close — a scrim tap, the back press, a drag below the lowest detent. The
47
+ * one hook for all three; a bound sheet needs it to be dismissible at all (it resists the drag
48
+ * otherwise). When the callback leaves the sheet open, it slides back to its detent. */
49
+ onDismiss(callback: () => void): this;
50
+ /** Called when the sheet opens. */
42
51
  onOpen(callback: () => void): this;
43
- /** Called when the sheet starts closing — scrim tap, back button, drag-dismiss, or `hide()`. */
52
+ /** Called when the sheet starts closing — a dismissal, `hide()`, the data. */
44
53
  onClose(callback: () => void): this;
45
54
  /** `dismissible(false)`: scrim taps, the back button and dragging below the lowest detent no
46
55
  * longer close the sheet (a drag collapses to the lowest detent instead). Default `true`. */
@@ -51,13 +60,11 @@ export declare class BottomSheetElement extends ModalElement {
51
60
  _detent: number;
52
61
  readonly _detentListeners: ((index: number) => void)[];
53
62
  constructor(style: UIBottomSheetStyle, children: UINodeChild[] | ChildrenFn);
63
+ _syncDismissible(): void;
54
64
  detents(list: number[]): this;
55
65
  get detent(): number;
56
66
  setDetent(index: number): void;
57
67
  onDetent(callback: (index: number) => void): this;
58
- show(): void;
59
- hide(): void;
60
- dismissible(enabled: boolean): this;
61
68
  }
62
69
  /** Create a bottom sheet (see {@link UIBottomSheet}) — content-sized by default; call
63
70
  * `.detents([...])` for snap positions. Same argument forms as `UIColumn`. */
@@ -1,5 +1,6 @@
1
1
  import { type UIWidget, type UIWidgetStyle, WidgetElement } from "./UIWidget";
2
2
  import { type BaseStyle, type ChildrenFn, type Color, type DrawableStyle, type UIChildArg, type UINodeChild } from "./UINode";
3
+ import { type Signal } from "../core/signals";
3
4
  export type UIModalStyle = UIWidgetStyle;
4
5
  /**
5
6
  * The modal's hidden pose: `show()` animates from it, `hide()` animates to it. `duration` (ms)
@@ -9,25 +10,45 @@ export type UIModalTransition = DrawableStyle & BaseStyle & {
9
10
  overlayColor?: Color | null;
10
11
  duration?: number;
11
12
  };
13
+ /** What `bindShow()` takes: a function of the data, or a signal the modal also writes (`false` on
14
+ * a dismissal, `show()` / `hide()` set it). */
15
+ export type UIModalShow = (() => boolean) | Signal<boolean>;
12
16
  /**
13
17
  * A dialog: a `UIWidget` with the modal boilerplate built in. Comes with a scrim
14
- * (`overlayColor: "rgba(0, 0, 0, 0.5)"` unless overridden), animates on `show()`/`hide()` and on
15
- * a state block flipping its `display` (a 200 ms fade by default — swap the pose via
16
- * `transition()`; the runtime plays it as tracks that never touch the style), and closes itself
17
- * on a scrim tap or the Android back button (disable via `dismissible(false)`). Everything else
18
- * is a plain widget: declare it where it belongs — among its screen's children, hidden until a
19
- * tap (`display: "none"`), or shown with `show()` from anywhere, which puts a parentless modal in
20
- * the app layer. A dialog bound to data (its display from a binding) is declared
21
- * `dismissible(false)` and routes `onOverlayTap` / `onBack` into the data's dismiss, so the data
22
- * stays what the screen shows.
18
+ * (`overlayColor: "rgba(0, 0, 0, 0.5)"` unless overridden), is HIDDEN until shown, animates on
19
+ * every show / hide (a 200 ms fade by default — swap the pose via `transition()`; the runtime
20
+ * plays it as tracks that never touch the style).
21
+ *
22
+ * One state, shown or not, driven one of two ways:
23
+ * - `show()` / `hide()` from code — an action sheet opened by a button;
24
+ * - `bindShow(…)`: a function of the data (`bindShow(() => data.value.confirm)` — the data is the
25
+ * truth, `show()` / `hide()` are refused) or a `Signal<boolean>` the modal reads AND writes.
26
+ *
27
+ * A dismissal — the scrim tap, the back press, a sheet dragged below its lowest detent — is ONE
28
+ * request, `onDismiss`. By default it hides an unbound modal and writes `false` into a bound
29
+ * signal; a bound function has nowhere to write, so a modal bound to one without `onDismiss`
30
+ * cannot be dismissed (a forced choice — the same as `dismissible(false)`). `display` is not a
31
+ * modal's switch: a `.style({ display })` on it throws (a bound display died on the first
32
+ * dismissal, which wrote a static `display: "none"` over the binding — the dialog never opened
33
+ * again).
23
34
  */
24
35
  export interface UIModal extends UIWidget {
25
- /** Show and play the entrance transition. No-op while already open. */
36
+ /**
37
+ * Bind the shown state. A function: the modal follows the data, and `show()` / `hide()` are
38
+ * errors — flip the data (`onDismiss` is where a dismissal goes). A signal: read and written —
39
+ * a dismissal sets it `false`, `show()` / `hide()` set it.
40
+ *
41
+ * ```ts
42
+ * UIModal(...).bindShow(() => data.value.confirm).onDismiss(on.cancel) // the data is the truth
43
+ * UIModal(...).bindShow(confirmOpen) // a Signal<boolean>
44
+ * ```
45
+ */
46
+ bindShow(value: UIModalShow): this;
47
+ /** Show and play the entrance transition. No-op while already shown. Bound to a signal: writes
48
+ * `true` into it; bound to a function: an error (flip the data). */
26
49
  show(): void;
27
- /** Play the exit transition, then hide. No-op while closed or already closing. */
50
+ /** Play the exit transition, then hide. No-op while hidden. Bound: as `show()`. */
28
51
  hide(): void;
29
- /** True from `show()` until `hide()` starts (already false during the exit animation). */
30
- readonly isOpen: boolean;
31
52
  /**
32
53
  * Replace the show/hide animation. `hidden` is the off-screen pose — `show()` animates from
33
54
  * it, `hide()` animates to it. Default `{ opacity: 0, duration: 200 }` (a fade); the pose
@@ -39,13 +60,17 @@ export interface UIModal extends UIWidget {
39
60
  * ```
40
61
  */
41
62
  transition(hidden: UIModalTransition): this;
42
- /** Called when the modal opens — `show()`, or a state block flipping its display. */
63
+ /** The user asked to close: a scrim tap, the back press, a sheet dragged below its lowest
64
+ * detent. Replaces the default (hide, or `false` into the signal): the callback decides —
65
+ * usually the action that flips the data the modal is bound to. */
66
+ onDismiss(callback: () => void): this;
67
+ /** Called when the modal is shown — whoever showed it. */
43
68
  onOpen(callback: () => void): this;
44
- /** Called when the modal starts closing — scrim tap, back button, a `hide()` call, or a state
45
- * block flipping its display. */
69
+ /** Called when the modal starts hiding — whoever hid it. */
46
70
  onClose(callback: () => void): this;
47
- /** `dismissible(false)` keeps scrim taps and the back button from closing the modal
48
- * (forced-choice dialogs, a dialog whose display is bound to data). Default `true`. */
71
+ /** `dismissible(false)`: the dismissal gestures are off — the scrim tap does nothing, the back
72
+ * press is swallowed, a sheet resists below its lowest detent (a forced choice, the persistent
73
+ * map sheet). Default `true`. */
49
74
  dismissible(enabled: boolean): this;
50
75
  }
51
76
  export declare class ModalElement extends WidgetElement {
@@ -53,20 +78,23 @@ export declare class ModalElement extends WidgetElement {
53
78
  _transition: UIModalTransition;
54
79
  readonly _openListeners: (() => void)[];
55
80
  readonly _closeListeners: (() => void)[];
81
+ readonly _dismissListeners: (() => void)[];
56
82
  private _poseObj?;
57
83
  private _poseKey;
58
84
  constructor(style: UIModalStyle, children: UINodeChild[] | ChildrenFn);
59
85
  private _pose;
60
86
  transition(hidden: UIModalTransition): this;
87
+ bindShow(value: UIModalShow): this;
61
88
  show(): void;
62
89
  hide(): void;
63
- get isOpen(): boolean;
90
+ _setOpen(open: boolean): void;
91
+ onDismiss(callback: () => void): this;
64
92
  onOpen(callback: () => void): this;
65
93
  onClose(callback: () => void): this;
66
94
  dismissible(enabled: boolean): this;
67
95
  }
68
- /** Create a modal dialog (see {@link UIModal}): declare it among its screen's children with
69
- * `display: "none"` and `show()` it from a tap, or `show()` a parentless one from anywhere.
70
- * Same argument forms as `UIColumn`. */
96
+ /** Create a modal dialog (see {@link UIModal}): give it to its screen (`.widgets(dialog)`) and
97
+ * `show()` it from a tap, bind it to the data with `bindShow(() => …)`, or `show()` a parentless
98
+ * one from anywhere. Same argument forms as `UIColumn`. */
71
99
  export declare function UIModal(...children: UIChildArg[]): UIModal;
72
100
  export declare function UIModal(children: UINodeChild[] | ChildrenFn): UIModal;
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "2.5.0";
1
+ export declare const SDK_VERSION = "2.6.0";
2
2
  /** The major of a semver string, or null when it is not one. */
3
3
  export declare const sdkMajor: (version: string | null | undefined) => number | null;
4
4
  /** The `// sdk: <version>` line of a compiled bundle's header (sdk/src/compile/header.ts), read from