@solidrt/core 0.0.50 → 0.0.51

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.
@@ -27,6 +27,15 @@ export type SoundOptions = {
27
27
  * Omitted means unspatialized.
28
28
  */
29
29
  pan?: number
30
+ /**
31
+ * Playback rate: 1.0 plays as loaded, higher is faster and higher-pitched
32
+ * (clamped to [0.01, 100]). Defaults to 1.0.
33
+ */
34
+ rate?: number
35
+ /** Fade each play() in from silence over this many milliseconds. */
36
+ fadeInMs?: number
37
+ /** Bus name for every voice of this sound (see flux:audio `stop({ bus })`). */
38
+ bus?: string
30
39
  /**
31
40
  * Let play() stack overlapping voices instead of restarting. Defaults to
32
41
  * true: rapid triggers overlap. Set false for a single-voice sound where each
@@ -35,6 +44,21 @@ export type SoundOptions = {
35
44
  overlap?: boolean
36
45
  }
37
46
 
47
+ /** Options for the live setters: ramp to the value instead of jumping. */
48
+ export type SoundRampOptions = {
49
+ /**
50
+ * Reach the new value over this many milliseconds, engine-smoothed (immune
51
+ * to frame hitches). Omitted (or 0) sets immediately.
52
+ */
53
+ rampMs?: number
54
+ }
55
+
56
+ /** Options for {@link Sound.stop}. */
57
+ export type SoundStopOptions = {
58
+ /** Fade to silence over this many milliseconds before stopping. */
59
+ fadeOutMs?: number
60
+ }
61
+
38
62
  /** Options for a PCM sound: `SoundOptions` plus the channel count. */
39
63
  export type PcmSoundOptions = SoundOptions & {
40
64
  /** Channel count, interleaved samples when 2. Defaults to 1 (mono). */
@@ -49,18 +73,26 @@ export type SoundStreamOptions = {
49
73
  gain?: number
50
74
  /** Stereo position in [-1, 1] (see {@link SoundOptions.pan}). */
51
75
  pan?: number
76
+ /** Playback rate (see {@link SoundOptions.rate}). Defaults to 1.0. */
77
+ rate?: number
78
+ /** Fade each play() in from silence over this many milliseconds. */
79
+ fadeInMs?: number
80
+ /** Bus name for the stream's voice (see flux:audio `stop({ bus })`). */
81
+ bus?: string
52
82
  }
53
83
 
54
84
  /** A decoded sound with reactive lifecycle. */
55
85
  export type Sound = {
56
86
  /** Start the clip. Overlaps or restarts per the `overlap` option. */
57
87
  play(): void
58
- /** Stop every voice started from this sound. */
59
- stop(): void
88
+ /** Stop every voice started from this sound, fading first if asked. */
89
+ stop(options?: SoundStopOptions): void
60
90
  /** Set the volume of every live voice, and of voices started later. */
61
- setGain(gain: number): void
91
+ setGain(gain: number, options?: SoundRampOptions): void
62
92
  /** Set the stereo position of every live voice, and of voices started later. */
63
- setPan(pan: number): void
93
+ setPan(pan: number, options?: SoundRampOptions): void
94
+ /** Set the playback rate of every live voice, and of voices started later. */
95
+ setRate(rate: number, options?: SoundRampOptions): void
64
96
  /** True after play() until stop() (does not track natural completion). */
65
97
  playing(): boolean
66
98
  /** Set if loading failed. */
@@ -69,12 +101,12 @@ export type Sound = {
69
101
 
70
102
  // Shared reactive wrapper: owns the loaded clip, tracks live voices, and
71
103
  // disposes both on cleanup. `loader` runs once (may throw -> error signal).
72
- // Gain and pan are remembered so later voices start where setGain/setPan left
73
- // the sound, not back at the initial options.
104
+ // Gain, pan and rate are remembered so later voices start where the setters
105
+ // left the sound, not back at the initial options.
74
106
  function reactiveSound(
75
107
  loader: () => Clip,
76
108
  overlap: boolean,
77
- initial: { loop?: boolean; gain?: number; pan?: number },
109
+ initial: { loop?: boolean; gain?: number; pan?: number; rate?: number; fadeInMs?: number; bus?: string },
78
110
  ): Sound {
79
111
  let [error, setError] = createSignal<Error | undefined>(undefined, { ownedWrite: true })
80
112
  let [playing, setPlaying] = createSignal(false, { ownedWrite: true })
@@ -84,6 +116,9 @@ function reactiveSound(
84
116
  let loop = initial.loop
85
117
  let gain = initial.gain
86
118
  let pan = initial.pan
119
+ let rate = initial.rate
120
+ let fadeInMs = initial.fadeInMs
121
+ let bus = initial.bus
87
122
  try {
88
123
  clip = loader()
89
124
  } catch (e) {
@@ -96,8 +131,8 @@ function reactiveSound(
96
131
  voices = voices.filter((v) => !v.ended())
97
132
  }
98
133
 
99
- let stopAll = () => {
100
- for (let v of voices) v.stop()
134
+ let stopAll = (options?: SoundStopOptions) => {
135
+ for (let v of voices) v.stop(options)
101
136
  voices = []
102
137
  setPlaying(false)
103
138
  }
@@ -115,19 +150,24 @@ function reactiveSound(
115
150
  if (!clip) return
116
151
  if (overlap) prune()
117
152
  else stopAll()
118
- voices.push(clip.play({ loop, gain, pan }))
153
+ voices.push(clip.play({ loop, gain, pan, rate, fadeInMs, bus }))
119
154
  setPlaying(true)
120
155
  },
121
156
  stop: stopAll,
122
- setGain(value) {
157
+ setGain(value, options) {
123
158
  gain = value
124
159
  prune()
125
- for (let v of voices) v.setGain(value)
160
+ for (let v of voices) v.setGain(value, options)
126
161
  },
127
- setPan(value) {
162
+ setPan(value, options) {
128
163
  pan = value
129
164
  prune()
130
- for (let v of voices) v.setPan(value)
165
+ for (let v of voices) v.setPan(value, options)
166
+ },
167
+ setRate(value, options) {
168
+ rate = value
169
+ prune()
170
+ for (let v of voices) v.setRate(value, options)
131
171
  },
132
172
  playing,
133
173
  error,
@@ -144,6 +184,9 @@ export function createSound(source: Uint8Array, options: SoundOptions = {}): Sou
144
184
  loop: options.loop,
145
185
  gain: options.gain,
146
186
  pan: options.pan,
187
+ rate: options.rate,
188
+ fadeInMs: options.fadeInMs,
189
+ bus: options.bus,
147
190
  })
148
191
  }
149
192
 
@@ -165,6 +208,9 @@ export function createPcmSound(
165
208
  loop: options.loop,
166
209
  gain: options.gain,
167
210
  pan: options.pan,
211
+ rate: options.rate,
212
+ fadeInMs: options.fadeInMs,
213
+ bus: options.bus,
168
214
  })
169
215
  }
170
216
 
@@ -178,5 +224,12 @@ export function createPcmSound(
178
224
  */
179
225
  export function createSoundStream(source: string | FluxFile, options: SoundStreamOptions = {}): Sound {
180
226
  let src = typeof source === "string" ? file(source) : source
181
- return reactiveSound(() => stream(src), false, { loop: options.loop, gain: options.gain, pan: options.pan })
227
+ return reactiveSound(() => stream(src), false, {
228
+ loop: options.loop,
229
+ gain: options.gain,
230
+ pan: options.pan,
231
+ rate: options.rate,
232
+ fadeInMs: options.fadeInMs,
233
+ bus: options.bus,
234
+ })
182
235
  }
package/src/color.ts CHANGED
@@ -1,30 +1,29 @@
1
- import { colord, extend } from "colord"
2
- import namesPlugin from "colord/plugins/names"
3
- import mixPlugin from "colord/plugins/mix"
4
- extend([namesPlugin, mixPlugin])
1
+ import * as tree from "flux:rendertree"
2
+
3
+ // Color parsing and mixing live in the runtime (alloy's color module), one
4
+ // owner for the CSS grammar and the perceptual math; these re-exports keep
5
+ // the core API surface. The renderer no longer parses at all: color strings
6
+ // cross the FFI raw and the runtime decodes them.
5
7
 
6
8
  /**
7
9
  * Parses a CSS color string (named, hex, `rgb()`, `hsl()`, ...) into a packed
8
- * `0xRRGGBBAA` u32: red in the high byte, alpha in the low byte. Alpha is scaled
9
- * from colord's 0..1 to 0..255. This is the wire format the runtime expects for
10
- * the `color` property. Throws on a string that is not a valid CSS color, so a
11
- * typo fails on the line that wrote it instead of silently painting black.
10
+ * `0xRRGGBBAA` u32: red in the high byte, alpha in the low byte. This is the
11
+ * wire format the runtime expects for the `color` property. Throws on a
12
+ * string that is not a valid CSS color, so a typo fails on the line that
13
+ * wrote it instead of silently painting black.
12
14
  */
13
15
  export function parseColor(color: string): number {
14
- let c = colord(color)
15
- if (!c.isValid()) throw new Error(`Invalid color "${color}"`)
16
- let { r, g, b, a } = c.toRgb()
17
- return (((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | ((a * 255) & 0xFF)) >>> 0
16
+ return tree.parseColor(color)
18
17
  }
19
18
 
20
19
  /**
21
- * Mixes two CSS colors in the CIE LAB color space; `t` is the fraction of `b`
22
- * (0 = pure `a`, 1 = pure `b`). Returns an opaque hex string. Use it to derive
23
- * semantic tones (muted text, subtle borders) instead of alpha overlays, so
24
- * the resulting color does not depend on what is drawn beneath it.
20
+ * Mixes two CSS colors in oklab; `t` is the fraction of `b` (0 = pure `a`,
21
+ * 1 = pure `b`). Returns a hex string. Use it to derive semantic tones
22
+ * (muted text, subtle borders) instead of alpha overlays, so the resulting
23
+ * color does not depend on what is drawn beneath it.
25
24
  */
26
25
  export function mixColors(a: string, b: string, t: number): string {
27
- return colord(a).mix(b, t).toHex()
26
+ return tree.mixColors(a, b, t)
28
27
  }
29
28
 
30
29
  /**
@@ -33,7 +32,7 @@ export function mixColors(a: string, b: string, t: number): string {
33
32
  * e.g. whether a label sits light-on-dark (see typeWeight in components).
34
33
  */
35
34
  export function brightness(color: string): number {
36
- return colord(color).brightness()
35
+ return tree.brightness(color)
37
36
  }
38
37
 
39
38
  // A color stop: `offset` is 0..1 along the gradient, `color` any CSS color string.
package/src/core.ts CHANGED
@@ -363,6 +363,21 @@ export type TextLine = {
363
363
  cursor: number
364
364
  }
365
365
 
366
+ /**
367
+ * Ink width of the wrap unit starting at unit `index`: its advance through
368
+ * every glued piece that follows, plus the last piece's ink. What must fit on
369
+ * the line for the unit to go on it.
370
+ */
371
+ export function unitInk(units: tree.TextUnit[], index: number): number {
372
+ let ink = units[index]!.width
373
+ let advance = 0
374
+ for (let j = index + 1; j < units.length && units[j]!.glue; j++) {
375
+ advance += units[j - 1]!.advance
376
+ ink = advance + units[j]!.width
377
+ }
378
+ return ink
379
+ }
380
+
366
381
  /**
367
382
  * The next line of `prepared` from unit `cursor` that fits `width`, or null
368
383
  * when the text is used up. Greedy: units go on the line while the pen plus
@@ -370,7 +385,9 @@ export type TextLine = {
370
385
  * wider than `width` on its own goes on the line whole and overflows. Draw a
371
386
  * line as `<d-text x y w={line.width + 1}>{prepared.text.slice(line.start, line.end)}</d-text>`
372
387
  * with the same font options; its words are already shaped, so that is
373
- * cheap. Floats, balancing and ellipsis are <text> features, not this.
388
+ * cheap. With `runs`, a unit crossing a run boundary is several glued units
389
+ * that always land on one line; draw such a line with a <span> per run.
390
+ * Floats, balancing and ellipsis are <text> features, not this.
374
391
  */
375
392
  export function layoutNextLine(prepared: tree.PreparedText, cursor: number, width: number): TextLine | null {
376
393
  let units = prepared.units
@@ -381,7 +398,9 @@ export function layoutNextLine(prepared: tree.PreparedText, cursor: number, widt
381
398
  let i = cursor
382
399
  while (i < units.length) {
383
400
  let unit = units[i]!
384
- if (i > cursor && pen + unit.width > width) break
401
+ // Glued pieces stay with the unit they continue: the whole wrap unit
402
+ // (this piece through the last glued one) must fit for any of it to go on.
403
+ if (i > cursor && !unit.glue && pen + unitInk(units, i) > width) break
385
404
  pen += unit.advance
386
405
  if (unit.ascent > ascent) ascent = unit.ascent
387
406
  if (unit.descent > descent) descent = unit.descent
package/src/data.ts ADDED
@@ -0,0 +1,99 @@
1
+ // Reactive SQLite. A query is a reactive value: it re-runs when any table it
2
+ // reads is written on this connection, and every consumer updates through
3
+ // normal Solid reactivity. SQLite itself reports both sides of the dependency
4
+ // graph - the statement's read-set comes from `stmt.tables()` (SQLite's
5
+ // authorizer) and writes come from `db.onWrite` (SQLite's update hook, so
6
+ // trigger and cascade writes are included). No SQL parsing, no manual
7
+ // declarations, no wrapper: queries take the plain `flux:sqlite` Database.
8
+ //
9
+ // The contract, in one sentence: a query re-runs when any table it reads is
10
+ // written on this connection. Granularity is per table; writes from another
11
+ // connection or process are not seen; WITHOUT ROWID tables do not report.
12
+ //
13
+ // The imperative primitives live in the `flux:sqlite` module and are
14
+ // framework-neutral. This module is only the thin Solid binding on top (a
15
+ // version signal per table, an async memo per query).
16
+
17
+ import { createMemo, createSignal } from "@solidjs/signals"
18
+ import type { Signal, SourceAccessor } from "@solidjs/signals"
19
+ import type { Database, Row, SqlParam, Statement } from "flux:sqlite"
20
+
21
+ export { Database } from "flux:sqlite"
22
+ export type { OpenMode, Row, RunResult, SqlParam, SqlValue, Statement } from "flux:sqlite"
23
+
24
+ /** Bind parameters: a plain array, or an accessor for reactive params. */
25
+ export type Params = SqlParam[] | (() => SqlParam[])
26
+
27
+ // Per-database dependency tracking, created lazily on the first createQuery:
28
+ // one version signal per table, bumped from the connection's write events.
29
+ // The function reads (and lazily creates) the version signal of one table.
30
+ // Keyed by the plain Database so there is no wrapper type to hand around.
31
+ let trackers = new WeakMap<Database, (table: string) => void>()
32
+
33
+ function trackerFor(db: Database): (table: string) => void {
34
+ let track = trackers.get(db)
35
+ if (track) return track
36
+
37
+ let versions = new Map<string, Signal<number>>()
38
+ // A write bumps only existing signals: a signal that was never read has no
39
+ // subscribers, so there is nothing to invalidate. The subscription lives
40
+ // for the connection's life; close() ends it.
41
+ db.onWrite((tables) => {
42
+ for (let table of tables) {
43
+ let version = versions.get(table)
44
+ if (version) version[1]((v) => v + 1)
45
+ }
46
+ })
47
+ track = (table) => {
48
+ let version = versions.get(table)
49
+ if (!version) {
50
+ version = createSignal(0)
51
+ versions.set(table, version)
52
+ }
53
+ version[0]()
54
+ }
55
+ trackers.set(db, track)
56
+ return track
57
+ }
58
+
59
+ /**
60
+ * A reactive query: an accessor over the matching rows that re-runs when any
61
+ * table the statement reads is written on this connection. Reads surface as
62
+ * pending until the first result lands - wrap in `<Loading>` or default with
63
+ * `?? []`. Pass params as an accessor to also re-run when they change.
64
+ */
65
+ export function createQuery(db: Database, sql: string, params?: Params): SourceAccessor<Row[]> {
66
+ return statementQuery(db, sql, params, (stmt, p) => stmt.all(p))
67
+ }
68
+
69
+ /**
70
+ * Like {@link createQuery}, but for single-row reads: resolves to the first
71
+ * matching row, or `undefined` when there is none.
72
+ */
73
+ export function createQueryRow(
74
+ db: Database,
75
+ sql: string,
76
+ params?: Params,
77
+ ): SourceAccessor<Row | undefined> {
78
+ return statementQuery(db, sql, params, (stmt, p) => stmt.get(p))
79
+ }
80
+
81
+ function statementQuery<T>(
82
+ db: Database,
83
+ sql: string,
84
+ params: Params | undefined,
85
+ run: (stmt: Statement, params?: SqlParam[]) => Promise<T>,
86
+ ): SourceAccessor<T> {
87
+ let track = trackerFor(db)
88
+ let stmt = db.query(sql)
89
+ // The read-set arrives async (one authorizer round-trip on the connection
90
+ // thread); computed once, it never changes for a given statement. Reads of
91
+ // the query below surface as pending until it lands, then the query runs -
92
+ // so the first execution already subscribes to its tables and no write can
93
+ // slip between subscription and execution.
94
+ let tables = createMemo(() => stmt.tables())
95
+ return createMemo(() => {
96
+ for (let table of tables()) track(table)
97
+ return run(stmt, typeof params === "function" ? params() : params)
98
+ })
99
+ }
package/src/gpu.ts CHANGED
@@ -100,6 +100,7 @@ export type { BufferId, DrawId, ProgramId, RenderPipelineId, ShaderStageId, Text
100
100
  // registered at creation keeps working and no re-registration is needed.
101
101
  export {
102
102
  destroyTexture,
103
+ endBufferWrite,
103
104
  resizeTexture,
104
105
  setTargetParams,
105
106
  setTargetSize,
@@ -575,12 +576,29 @@ export function createPipelineTexture(
575
576
  * created outside a reactive scope you must call `destroyBuffer` yourself.
576
577
  * (Destruction order relative to pipelines does not matter.)
577
578
  */
578
- export function createBuffer(data: ArrayBuffer | ArrayBufferView, opts?: CreateOptions): gpu.BufferId {
579
- let id = gpu.createBuffer(toUint8(data), opts)
579
+ export function createBuffer(data: ArrayBuffer | ArrayBufferView | number, opts?: CreateOptions): gpu.BufferId {
580
+ let id = gpu.createBuffer(typeof data === "number" ? data : toUint8(data), opts)
580
581
  if (opts?.autoFree !== false && getOwner()) onCleanup(() => gpu.destroyBuffer(id))
581
582
  return id
582
583
  }
583
584
 
585
+ /**
586
+ * Opens a zero-copy write into a vertex buffer: returns a Float32Array over
587
+ * runtime-owned memory spanning the whole buffer. Write records in place,
588
+ * then publish with {@link endBufferWrite} - the bytes move to the GPU with
589
+ * no copy on the CPU path, which is the per-frame streaming path (instanced
590
+ * sprites, dynamic geometry). Reach other element types through `.buffer`.
591
+ *
592
+ * Contents are UNSPECIFIED at begin (a recycled block holds what was
593
+ * published the time before last): fill everything you publish. One open
594
+ * write per buffer at a time. The view is detached at end/destroy - retained
595
+ * references become zero-length, so a stale write is inert, never a race.
596
+ */
597
+ export function beginBufferWrite(id: gpu.BufferId): Float32Array {
598
+ let ab = gpu.beginBufferWrite(id)
599
+ return new Float32Array(ab, 0, (ab.byteLength / 4) | 0)
600
+ }
601
+
584
602
  /**
585
603
  * Overwrites part of a vertex buffer at `byteOffset` (default 0). Every
586
604
  * pipeline drawing from the buffer re-renders with its last-applied params,
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export * from "./renderer"
2
- export { setFocus, focusedNode, startTextInput, textInputActive, getFocusables, measureText, prepareText, layoutNextLine, getBoundingBox, getBoundingBoxViewport, onPointerMove } from "./core"
2
+ export { setFocus, focusedNode, startTextInput, textInputActive, getFocusables, measureText, prepareText, layoutNextLine, unitInk, getBoundingBox, getBoundingBoxViewport, onPointerMove } from "./core"
3
3
  export type { BoundingBox, GlobalPointerEvent, TextLine } from "./core"
4
4
  export { parseColor, mixColors, brightness, createLinearGradient, createRadialGradient } from "./color"
5
5
  export type { Gradient, GradientStop } from "./color"
6
6
  export { onFrame, onLayout, onResize, onWindowFocus, onWindowBlur, onBack, exit } from "./window"
7
7
  export type { BackEvent } from "./window"
8
- export { windowSize, safeArea, displayScale, windowFocused, keyboardHeight } from "./window"
8
+ export { windowSize, safeArea, displayScale, windowFocused, keyboardHeight, lockPointer, pointerLocked } from "./window"
9
9
  export { env } from "./environment"
10
10
  export type { InputDevices, SystemTheme, Orientation, Visibility } from "./environment"
11
11
  export { gamepads } from "./gamepad"
package/src/renderer.ts CHANGED
@@ -4,7 +4,6 @@ import type { Element } from "solid-js"
4
4
  import * as tree from "flux:rendertree"
5
5
  import { attachWindow } from "./window"
6
6
  import { setEventHandler, setFocusable, setTextInputHints, cleanupNode, focusedNode, setFocus } from "./core"
7
- import { parseColor, isGradient } from "./color"
8
7
 
9
8
  export { getEventHandler } from "./core"
10
9
 
@@ -166,34 +165,49 @@ function setTreeProperty(node: ProxyNode, name: string, value: unknown): void {
166
165
  // Shared by the renderer's setProperty hook and by createElement, which since
167
166
  // the dom-expressions "universal" template passes static props inline as a
168
167
  // second argument rather than as separate setProp calls.
168
+ // A property's route is a function of its name alone, so the classifying
169
+ // regex and compares run once per unique name; per write it is one Map get.
170
+ const ROUTE_TREE = 0
171
+ const ROUTE_EVENT = 1
172
+ const ROUTE_FOCUSABLE = 2
173
+ const ROUTE_HINTS = 3
174
+ let propRoutes = new Map<string, number>()
175
+
176
+ function routeFor(name: string): number {
177
+ let route = propRoutes.get(name)
178
+ if (route === undefined) {
179
+ route = /^on[A-Z]/.test(name)
180
+ ? ROUTE_EVENT
181
+ : name === "focusable"
182
+ ? ROUTE_FOCUSABLE
183
+ : name === "textInputHints"
184
+ ? ROUTE_HINTS
185
+ : ROUTE_TREE
186
+ propRoutes.set(name, route)
187
+ }
188
+ return route
189
+ }
190
+
169
191
  function applyProp<T>(node: ProxyNode, name: string, value: T): void {
170
192
  if (!node) return
171
193
 
172
194
  // console.debug("[srt] applyProp", node.id, name, value)
173
195
 
174
- if (/^on[A-Z]/.test(name) && (value == null || typeof value === "function")) {
175
- setEventHandler(node.id, name, value as Function | null | undefined)
176
- return
177
- }
178
-
179
- if (name === "focusable") {
180
- setFocusable(node.id, value === true)
181
- return
182
- }
183
-
184
- if (name === "textInputHints") {
185
- setTextInputHints(node.id, value as any)
186
- return
187
- }
188
-
189
- if (name === "color" && isGradient(value)) {
190
- setTreeProperty(node, name, value)
191
- return
192
- }
193
-
194
- if (name === "color" && typeof value === "string") {
195
- setTreeProperty(node, name, parseColor(value))
196
- return
196
+ switch (routeFor(name)) {
197
+ case ROUTE_EVENT:
198
+ // A non-function, non-null value on an on* name is not a handler;
199
+ // fall through to the tree so the native side rejects it.
200
+ if (value == null || typeof value === "function") {
201
+ setEventHandler(node.id, name, value as Function | null | undefined)
202
+ return
203
+ }
204
+ break
205
+ case ROUTE_FOCUSABLE:
206
+ setFocusable(node.id, value === true)
207
+ return
208
+ case ROUTE_HINTS:
209
+ setTextInputHints(node.id, value as any)
210
+ return
197
211
  }
198
212
 
199
213
  setTreeProperty(node, name, value)
@@ -88,8 +88,9 @@ declare module "srt:dev" {
88
88
  /**
89
89
  * Register a named debug command, listable and callable from the dev server
90
90
  * (the list_debug / call_debug MCP tools). `args` arrives JSON-parsed; the
91
- * return value must be JSON-serializable and synchronous (promises are not
92
- * awaited). Re-registering a name replaces it; registrations reset on hot
91
+ * return value must be JSON-serializable and synchronous (an async command's
92
+ * Promise is not awaited - the call errors). Re-registering a name replaces
93
+ * it; registrations reset on hot
93
94
  * reload, so register at module init. Callable in every build, but only dev
94
95
  * clients ever invoke commands.
95
96
  */
package/src/scroll.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // scrollable region -- the offset and its clamping against the measured content
3
3
  // and viewport sizes -- and nothing with a UI opinion. Wheel/drag input,
4
4
  // momentum, scrollbars and styling are policy and belong to the component (the
5
- // "skin") that composes this, the same way createCaretScroll backs TextInput.
5
+ // "skin") that composes this, the same way createTextEditorLayout backs TextInput.
6
6
 
7
7
  import { createSignal, flush } from "@solidjs/signals"
8
8
  import { getBoundingBox } from "./core"