@solidrt/core 0.0.49 → 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.
package/src/types.d.ts CHANGED
@@ -131,6 +131,21 @@ export interface LayoutProps extends FlexboxProps, GridProps {
131
131
  overflow?: "visible" | "clip" | "hidden" | "scroll"
132
132
  overflowX?: "visible" | "clip" | "hidden" | "scroll"
133
133
  overflowY?: "visible" | "clip" | "hidden" | "scroll"
134
+
135
+ /**
136
+ * As an inline atom (an element child of <text>): leave the flow and sit
137
+ * against that side of the text, at the top of the line where the atom
138
+ * occurs; the lines it overlaps wrap around its margin box. Same-side
139
+ * floats overlapping vertically sit beside each other. The text's height
140
+ * includes the float. Meaningless outside a <text>.
141
+ */
142
+ float?: "left" | "right"
143
+ /**
144
+ * As an inline atom: start a new line below the text's earlier floats on
145
+ * that side (a floated atom goes below them instead of beside). An empty
146
+ * `<view clear="both" />` is the section break after an image.
147
+ */
148
+ clear?: "left" | "right" | "both"
134
149
  }
135
150
 
136
151
  /** Colors are CSS color strings, parsed to a packed u32 by `parseColor`. */
@@ -221,6 +236,14 @@ export interface PointerEvent {
221
236
  */
222
237
  parentX: number
223
238
  parentY: number
239
+ /**
240
+ * Pointer movement since the previous move event, in logical pixels. Mouse
241
+ * reports hardware deltas (summed, never lost, and the only motion signal
242
+ * while the pointer is locked); touch reports position diffs. 0 on
243
+ * non-move events.
244
+ */
245
+ movementX: number
246
+ movementY: number
224
247
  /** Node id whose handler is currently running (bubbling changes it per call). */
225
248
  currentTarget: number
226
249
  /** Deepest node id of the event's path (the hit leaf). */
@@ -351,6 +374,154 @@ export interface LineGeometryProps {
351
374
  y2?: number
352
375
  }
353
376
 
377
+ // Native transitions (okf/done/native-transitions.md): declared once on
378
+ // the element, applied by the runtime to every later write of the covered
379
+ // properties. JS hands over targets; Rust interpolates every frame, so a
380
+ // running animation costs no JS per frame.
381
+
382
+ /** A cubic-bezier timing curve: a CSS name or [x1, y1, x2, y2] control values. */
383
+ export type TransitionCurve = "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" | [number, number, number, number]
384
+
385
+ /**
386
+ * A perceptual spring - the default kind: a bare `{ duration }` is a
387
+ * critically damped spring. `duration` (ms) is the perceptual settling
388
+ * time, `bounce` in (-1, 1] the springiness - 0 (the default) settles
389
+ * without overshoot, positive values overshoot, negative values settle
390
+ * sluggishly. A new target while the spring runs keeps position and
391
+ * velocity, so the motion stays continuous - use springs for anything
392
+ * retargeted while moving.
393
+ */
394
+ export interface TransitionSpring {
395
+ duration: number
396
+ bounce?: number
397
+ /** Hold each write for this long (ms) before it applies; a newer write during the hold replaces it and restarts the delay. */
398
+ delay?: number
399
+ /**
400
+ * Mount-time enter animation: at the element's first attach the property
401
+ * snaps to this value and animates to the value it mounted with. Numbers
402
+ * for the scalar properties; the color property takes a CSS color string
403
+ * or packed number. Per-property entries only (not under `all`); a later
404
+ * move or reorder re-runs nothing.
405
+ */
406
+ from?: number | string
407
+ /**
408
+ * Removal exit animation: an unmounted element stays visible, animates
409
+ * the property to this value (honoring `delay`), and is freed when its
410
+ * exit animations settle. Same value forms as `from`, per-property only.
411
+ * A move never plays it, the exiting element is hit-test invisible, its
412
+ * whole subtree stays painted with it, and no onTransitionEnd fires (the
413
+ * component is already disposed). An attached element keeps its layout
414
+ * slot until the exit finishes.
415
+ */
416
+ exit?: number | string
417
+ }
418
+
419
+ /**
420
+ * A duration/curve tween, opted into by naming the curve (a tween is
421
+ * always a specific curve; without one the spec reads as a spring).
422
+ * Duration in ms. A new target while the tween runs restarts it from the
423
+ * current value with the full duration (CSS semantics) - designer-timed,
424
+ * one-shot motion.
425
+ */
426
+ export interface TransitionTween {
427
+ duration: number
428
+ curve: TransitionCurve
429
+ /** Hold each write for this long (ms) before it applies; a newer write during the hold replaces it and restarts the delay. */
430
+ delay?: number
431
+ /**
432
+ * Mount-time enter animation: at the element's first attach the property
433
+ * snaps to this value and animates to the value it mounted with. Numbers
434
+ * for the scalar properties; the color property takes a CSS color string
435
+ * or packed number. Per-property entries only (not under `all`); a later
436
+ * move or reorder re-runs nothing.
437
+ */
438
+ from?: number | string
439
+ /**
440
+ * Removal exit animation: an unmounted element stays visible, animates
441
+ * the property to this value (honoring `delay`), and is freed when its
442
+ * exit animations settle. Same value forms as `from`, per-property only.
443
+ * A move never plays it, the exiting element is hit-test invisible, its
444
+ * whole subtree stays painted with it, and no onTransitionEnd fires (the
445
+ * component is already disposed). An attached element keeps its layout
446
+ * slot until the exit finishes.
447
+ */
448
+ exit?: number | string
449
+ }
450
+
451
+ /**
452
+ * The shorthand string: `"<duration>ms [curve] [<delay>ms]"` - `"300ms"` is
453
+ * a bounce-0 spring, `"300ms ease-out"` a tween, `"300ms ease-out 100ms"`
454
+ * delayed (first time value the duration, second the delay; ms only).
455
+ * Bounce, bezier control values and `from` need the object form.
456
+ */
457
+ export type TransitionShorthand = string
458
+
459
+ export type Transition = TransitionSpring | TransitionTween | TransitionShorthand
460
+
461
+ /** The property names a transition can cover (numeric scalars). */
462
+ export type TransitionPropName =
463
+ | "x"
464
+ | "y"
465
+ | "w"
466
+ | "h"
467
+ | "x1"
468
+ | "y1"
469
+ | "x2"
470
+ | "y2"
471
+ | "opacity"
472
+ | "rotate"
473
+ | "rotateX"
474
+ | "rotateY"
475
+ | "scale"
476
+ | "scaleX"
477
+ | "scaleY"
478
+ | "strokeWidth"
479
+ | "radius"
480
+ | "color"
481
+
482
+ /** Payload of onTransitionEnd: which animated property finished. */
483
+ export interface TransitionEndEvent {
484
+ property: TransitionPropName
485
+ }
486
+
487
+ export interface TransitionProps {
488
+ /**
489
+ * A runtime-side transition of one of this element's properties reached
490
+ * its target (natural settles only; a cancelled or retargeted animation
491
+ * does not fire until it finally settles). Delivered to this element
492
+ * only, no bubbling.
493
+ */
494
+ onTransitionEnd?: (event: TransitionEndEvent) => void
495
+ /**
496
+ * Animate later writes of the listed properties instead of snapping:
497
+ * `transition={{ x: { duration: 400, bounce: 0.2 }, opacity: "200ms ease-out" }}`.
498
+ * `all` covers every animatable property the element has, and a bare
499
+ * string is shorthand for it: `transition="300ms ease-out"`. Only
500
+ * properties the element carries animate (a d-rect has x, a view's x is
501
+ * its transform); the initial value never animates unless the entry sets
502
+ * `from` (an enter animation), and a non-numeric write (e.g. null)
503
+ * cancels the running animation and snaps. `null` clears the
504
+ * declaration; already-running animations finish.
505
+ */
506
+ transition?:
507
+ | ({
508
+ all?: Omit<TransitionSpring, "from" | "exit"> | Omit<TransitionTween, "from" | "exit"> | TransitionShorthand
509
+ /**
510
+ * Group stagger (ms): every descendant enter (`from`) or exit that
511
+ * begins in the same frame under this element gets `index * stagger`
512
+ * of extra delay, in occurrence order (enters and exits cascade
513
+ * separately). Nearest declaring ancestor wins; it orchestrates
514
+ * descendants only - ordinary writes and this element's own
515
+ * lifecycle are unaffected. Adds on top of a per-entry `delay`.
516
+ */
517
+ stagger?: number
518
+ } & {
519
+ [P in TransitionPropName]?: Transition
520
+ })
521
+ | TransitionShorthand
522
+ | null
523
+ }
524
+
354
525
  // Primitives
355
526
 
356
527
  export interface WindowProps extends LayoutProps, PointerProps {
@@ -447,9 +618,10 @@ export interface ViewOwnProps extends TransformProps, PointerProps {
447
618
  repaintBoundary?: boolean | "snapshot" | "snapshot-no-aa"
448
619
  /**
449
620
  * Run this view's rasterized subtree through a GPU program and composite
450
- * the result in its place. Requires repaintBoundary="snapshot" (the cost
451
- * is snapshot semantics, kept explicit; declared without it the shader is
452
- * ignored with a warning). The pass is region-sized and split from content
621
+ * the result in its place. Requires a snapshot boundary
622
+ * (repaintBoundary="snapshot" or "snapshot-no-aa"; the cost is snapshot
623
+ * semantics, kept explicit; declared without one the shader is ignored
624
+ * with a warning). The pass is region-sized and split from content
453
625
  * invalidation: a params-only change re-runs just the pass against the
454
626
  * cached snapshot, so animating an effect over a static subtree never
455
627
  * re-rasterizes it.
@@ -545,8 +717,12 @@ export interface PathProps extends PaintProps, PointerProps {
545
717
  fillRule?: "nonzero" | "evenodd"
546
718
  }
547
719
 
548
- export interface TextProps extends PaintProps, PointerProps {
549
- children?: Children
720
+ /**
721
+ * Per-run text style: the paragraph default on <text>, an override on <span>.
722
+ * The cascade is intra-paragraph only: a span inherits from its enclosing
723
+ * span, then from the <text>; nothing inherits across the tree.
724
+ */
725
+ export interface TextRunProps {
550
726
  fontFamily?: "sans" | "serif" | "mono" | (string & {})
551
727
  fontSize?: number
552
728
  /**
@@ -557,8 +733,60 @@ export interface TextProps extends PaintProps, PointerProps {
557
733
  lineHeight?: number
558
734
  fontStyle?: "normal" | "italic"
559
735
  fontWeight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
736
+ /**
737
+ * Underline in the run's own color, drawn straight through descenders
738
+ * (no skip-ink). Position and thickness come from the font's own metrics
739
+ * unless overridden; a font Impeller resolves through the system fallback
740
+ * gets the shipped Noto values.
741
+ */
742
+ textDecoration?: "none" | "underline"
743
+ /** Pixels from the baseline to the top of the underline. */
744
+ textUnderlineOffset?: number
745
+ /** Underline thickness in pixels. */
746
+ textDecorationThickness?: number
747
+ }
748
+
749
+ /**
750
+ * A styled run inside <text>. Inline text only: children are text and other
751
+ * spans. `color` takes what a text's color takes (solid or gradient).
752
+ * Pointer handlers fire for the boxes of the run's own text on each line it
753
+ * spans and bubble to the enclosing spans and text.
754
+ */
755
+ export interface SpanProps extends TextRunProps, PointerProps {
756
+ children?: Children
757
+ color?: Color | Gradient
758
+ }
759
+
760
+ export interface TextProps extends PaintProps, PointerProps, TextRunProps {
761
+ children?: Children
560
762
  textAlign?: "left" | "right" | "center" | "justify"
561
763
  maxLines?: number
764
+ /**
765
+ * What happens to text cut off by maxLines: "clip" (default), "ellipsis"
766
+ * (a U+2026 at the end of the last line), or any other string to use as
767
+ * the ellipsis. Drawn in the paragraph's default style.
768
+ */
769
+ textOverflow?: "clip" | "ellipsis" | (string & {})
770
+ /**
771
+ * A word (wrap unit) wider than the line: "anywhere" (default) splits it
772
+ * at grapheme boundaries so it stays inside the box, "normal" keeps it
773
+ * whole and lets it overflow (CSS's default).
774
+ */
775
+ overflowWrap?: "normal" | "anywhere"
776
+ /**
777
+ * First-line indent in pixels. Negative hangs: the first line starts at 0
778
+ * and every following line is indented by the magnitude. A hard break does
779
+ * not start a new first line.
780
+ */
781
+ textIndent?: number
782
+ /**
783
+ * How lines are chosen beyond greedy fitting (CSS text-wrap): "wrap"
784
+ * (default) is greedy; "balance" evens the line lengths while keeping the
785
+ * line count (headings, captions); "pretty" is greedy except that a lone
786
+ * word on the last line pulls one down from the line above. Neither
787
+ * applies once maxLines truncates.
788
+ */
789
+ textWrap?: "wrap" | "balance" | "pretty"
562
790
  }
563
791
 
564
792
  /**
package/src/window.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createSignal, onCleanup, onSettled, flush } from "@solidjs/signals"
2
- import { requestFrame } from "flux:rendertree"
2
+ import { requestFrame, setPointerLock } from "flux:rendertree"
3
3
  import { renderFrame } from "srt:render"
4
4
  import { on, once } from "srt:events"
5
5
  import { exit } from "srt:app"
@@ -38,9 +38,12 @@ let refreshRate = 60
38
38
  * the present count, and `rate` is the current refresh rate in Hz. `tick` is paced
39
39
  * by the runtime (one refresh period per present, slow-corrected toward the wall
40
40
  * clock) so animations driven off it stay smooth even when swap-return times
41
- * jitter. performance.now() and timers report/march on this same paced timeline
42
- * (so the whole time surface freezes together under the dev tools' clock
43
- * control); for real wall-clock time use Date.now().
41
+ * jitter, and it is continuous across hot reloads: every tick an instance
42
+ * sees is on one timebase, so dt is well-defined from the second call on.
43
+ * Timers freeze together with frame callbacks under the dev tools' clock
44
+ * control, but measure their delays against the wall clock, not this paced
45
+ * timeline. performance.now() is not on it either: it is real elapsed time
46
+ * for measuring work; Date.now() is calendar time.
44
47
  * Returns a cleanup function; also auto-cleans within a reactive scope.
45
48
  */
46
49
  export function onFrame(fn: (tick: number, frame: number, rate: number) => void) {
@@ -161,6 +164,33 @@ export function windowFocused(): boolean {
161
164
  return focusedAccessor()
162
165
  }
163
166
 
167
+ /**
168
+ * Lock the pointer to the window (relative mouse mode) or release it. While
169
+ * locked the cursor is hidden and confined, clientX/clientY freeze at the
170
+ * lock point, and mouse motion keeps reporting through the pointer events'
171
+ * movementX/movementY - the mouse-look primitive. Window-level, no
172
+ * permission dance: one app, one window. Observe the applied state through
173
+ * pointerLocked(); the platform can refuse (no relative mode) and the OS
174
+ * may drop the lock, e.g. on focus loss.
175
+ */
176
+ export function lockPointer(locked: boolean) {
177
+ if (typeof locked !== "boolean") throw new Error(`lockPointer: expected a boolean, got ${typeof locked}`)
178
+ setPointerLock(locked)
179
+ }
180
+
181
+ let pointerLockedAccessor: (() => boolean) | undefined
182
+
183
+ /** Whether the pointer is currently locked (relative mouse mode), as a reactive accessor. */
184
+ export function pointerLocked(): boolean {
185
+ if (!pointerLockedAccessor) {
186
+ let [locked, setLocked] = createSignal(false)
187
+ // Sticky event: a subscriber after the lock still observes the state.
188
+ on("pointerLock", ({ locked }: { locked: boolean }) => setLocked(locked))
189
+ pointerLockedAccessor = locked
190
+ }
191
+ return pointerLockedAccessor()
192
+ }
193
+
164
194
  let keyboardHeightAccessor: (() => number) | undefined
165
195
 
166
196
  /**
@@ -255,6 +285,7 @@ export function attachWindow(nodeId: number) {
255
285
  let unsubEnter: () => void = null!
256
286
  let unsubLeave: () => void = null!
257
287
  let unsubWheel: () => void = null!
288
+ let unsubTransitionEnd: () => void = null!
258
289
  let unsubKeyDown: () => void = null!
259
290
  let unsubKeyUp: () => void = null!
260
291
  let unsubBack: () => void = null!
@@ -263,8 +294,16 @@ export function attachWindow(nodeId: number) {
263
294
  let unsubRefreshRate: () => void = null!
264
295
  let unsubFirstResize: (() => void) | null = null
265
296
 
266
- function runFrame(t: number, frame: number) {
267
- if (animationFrames.size > 0) {
297
+ // `bootstrap` marks the synthetic first frame (see the first-resize
298
+ // subscription below): it flushes and paints the freshly initialized graph
299
+ // but does not invoke onFrame callbacks, because its timestamp is not a
300
+ // reading of the frame timeline - handing apps a zero tick gave every
301
+ // reloaded instance one enormous dt on the next real frame
302
+ // (okf/done/onframe-tick-reset-on-reload.md). The callbacks stay
303
+ // registered and run on the first real render event, before the first
304
+ // paint with their writes applied.
305
+ function runFrame(t: number, frame: number, bootstrap = false) {
306
+ if (!bootstrap && animationFrames.size > 0) {
268
307
  let frames = animationFrames
269
308
  animationFrames = new Map()
270
309
  for (let fn of frames.values()) fn(t, frame, refreshRate)
@@ -368,6 +407,17 @@ export function attachWindow(nodeId: number) {
368
407
  bubble(raw, "onWheel")
369
408
  })
370
409
 
410
+ // A native transition finished (runtime-side animation; see the
411
+ // `transition` prop). Target-only delivery, no bubbling: the element
412
+ // that declared the transition is the one interested in its end.
413
+ unsubTransitionEnd = on("transitionEnd", (raw: { target: number; property: string }) => {
414
+ try {
415
+ getEventHandler(raw.target, "onTransitionEnd")?.({ property: raw.property })
416
+ } catch (err) {
417
+ console.error("Error in onTransitionEnd handler:", err)
418
+ }
419
+ })
420
+
371
421
  // Key events dispatch along the focused node's ancestor chain, leaf->root
372
422
  // (the pointer bubbling contract), so a container hears keys from focused
373
423
  // descendants and the window root hears everything: <window onKeyDown> is
@@ -429,7 +479,7 @@ export function attachWindow(nodeId: number) {
429
479
  // where flush() is illegal (not reentrant). Defer runFrame to a microtask
430
480
  // so the first frame always runs after this callback returns.
431
481
  unsubFirstResize = once("resize", () => {
432
- queueMicrotask(() => runFrame(0, 0))
482
+ queueMicrotask(() => runFrame(0, 0, true))
433
483
  })
434
484
  })
435
485
 
@@ -442,6 +492,7 @@ export function attachWindow(nodeId: number) {
442
492
  if (unsubEnter) unsubEnter()
443
493
  if (unsubLeave) unsubLeave()
444
494
  if (unsubWheel) unsubWheel()
495
+ if (unsubTransitionEnd) unsubTransitionEnd()
445
496
  if (unsubKeyDown) unsubKeyDown()
446
497
  if (unsubKeyUp) unsubKeyUp()
447
498
  if (unsubBack) unsubBack()