@solidrt/core 0.0.50 → 0.0.52

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 (42) hide show
  1. package/AGENTS.md +102 -21
  2. package/README.md +1 -1
  3. package/agents/painting.md +61 -0
  4. package/agents/performance.md +216 -0
  5. package/docs/index.md +154 -0
  6. package/docs/reference/detached.md +85 -0
  7. package/docs/reference/drawing.md +95 -0
  8. package/docs/reference/elements.md +56 -0
  9. package/docs/reference/gpu.md +204 -0
  10. package/docs/reference/index.md +50 -0
  11. package/docs/reference/input.md +58 -0
  12. package/docs/reference/layout.md +44 -0
  13. package/docs/reference/shaders.md +46 -0
  14. package/docs/reference/text.md +46 -0
  15. package/docs/reference/transforms.md +35 -0
  16. package/docs/reference/types.md +34 -0
  17. package/examples/README.md +7 -5
  18. package/examples/{sound.tsx → audio.tsx} +1 -1
  19. package/examples/gpu-pipeline.tsx +2 -2
  20. package/examples/gpu-sprites.tsx +102 -0
  21. package/examples/line-points.tsx +145 -0
  22. package/examples/parse-svg.tsx +6 -6
  23. package/examples/responsive-grid.tsx +1 -1
  24. package/examples/scroll.tsx +2 -2
  25. package/examples/snapshot-texture.tsx +72 -0
  26. package/examples/{view-viewbox.tsx → view-design-size.tsx} +33 -15
  27. package/jsx-runtime.d.ts +15 -14
  28. package/package.json +11 -9
  29. package/src/{sound.ts → audio.ts} +68 -15
  30. package/src/color.ts +17 -18
  31. package/src/core.ts +40 -3
  32. package/src/data.ts +99 -0
  33. package/src/gpu.ts +88 -34
  34. package/src/index.ts +9 -3
  35. package/src/logo.tsx +92 -0
  36. package/src/renderer.ts +219 -53
  37. package/src/runtime-modules.d.ts +7 -2
  38. package/src/scroll.ts +51 -15
  39. package/src/svg.ts +1 -1
  40. package/src/text-input.ts +297 -61
  41. package/src/types.d.ts +291 -31
  42. package/src/window.ts +110 -14
@@ -0,0 +1,46 @@
1
+ # Text
2
+
3
+ `<text>` is a shaped paragraph, and `<span>` is a styled run inside it. Text is
4
+ laid out by the engine's own layout and shaping, not by a browser: a paragraph
5
+ is one element that wraps, aligns and truncates as a unit.
6
+
7
+ ```tsx
8
+ <text fontSize={16} maxLines={2} textOverflow="ellipsis">
9
+ Weather for <span fontWeight={700}>Tuesday</span>
10
+ </text>
11
+ ```
12
+
13
+ ## Run style
14
+
15
+ The style props of a run: paragraph defaults on `<text>`, overrides on
16
+ `<span>`.
17
+
18
+ {{ decl packages/core/src/types.d.ts TextRunProps }}
19
+
20
+ The cascade is intra-paragraph only. A span inherits from its enclosing span
21
+ and then from the `<text>`; nothing inherits across the element tree, so there
22
+ is no ambient font size to chase.
23
+
24
+ `lineHeight` is the one prop with a CSS reflex worth unlearning: it is a
25
+ multiplier of `fontSize`, not a pixel value.
26
+
27
+ ## text
28
+
29
+ {{ decl packages/core/src/types.d.ts TextProps }}
30
+
31
+ Paragraph-level behavior lives here: `textAlign`, `maxLines` with
32
+ `textOverflow`, `textIndent`, and `textWrap` for line-breaking quality
33
+ (`"balance"` for headings, `"pretty"` to avoid a lone last word).
34
+
35
+ An element child of a `<text>` is an inline atom, which is where the
36
+ [layout](/core/reference/layout/) props `float` and `clear` apply: a floated
37
+ atom leaves the flow and the lines it overlaps wrap around it.
38
+
39
+ ## span
40
+
41
+ {{ decl packages/core/src/types.d.ts SpanProps }}
42
+
43
+ Inline only: its children are text and other spans, and it has no layout box,
44
+ which is why it is the one element with no detached form. Pointer handlers on
45
+ a span fire for the boxes its text occupies on each line it spans, and bubble
46
+ to the enclosing spans and the text.
@@ -0,0 +1,35 @@
1
+ # Transforms
2
+
3
+ Transform props apply after layout, at composite time. Nothing re-records and
4
+ nothing reflows, on a laid-out `view` as much as on a `d-view`, so these are
5
+ the props to animate.
6
+
7
+ They live on `ViewOwnProps`, which means both `view` and `d-view` have them;
8
+ see [elements](/core/reference/elements/).
9
+
10
+ {{ decl packages/core/src/types.d.ts TransformProps }}
11
+
12
+ ## Origin
13
+
14
+ `originX` and `originY` are the point rotation and scale pivot around, split
15
+ per axis to match the engine's `x`/`y` prop convention.
16
+
17
+ {{ decl packages/core/src/types.d.ts OriginX }}
18
+
19
+ {{ decl packages/core/src/types.d.ts OriginY }}
20
+
21
+ A percentage origin tracks the layout size with no reactive wiring of your
22
+ own. On a `d-view` there is no box, so the origin defaults to the view's local
23
+ `(0,0)` - the origin its children's coordinates are authored against - and
24
+ `pct()` or keyword origins resolve against the inherited box, which is rarely
25
+ what you want. Pivot a `d-view` around its content by setting the origin in
26
+ pixels.
27
+
28
+ ## Opacity
29
+
30
+ `opacity` is group opacity: the children composite together and then fade as a
31
+ whole, like CSS. It costs a compositing layer while below 1, unless the view
32
+ is a `repaintBoundary`, where it is hoisted to composite time for free.
33
+
34
+ To fade a single primitive, put the alpha in its `color` instead. Paint alpha
35
+ costs nothing.
@@ -0,0 +1,34 @@
1
+ # Types
2
+
3
+ The shared aliases the element props refer to.
4
+
5
+ ## Color
6
+
7
+ {{ decl packages/core/src/types.d.ts Color }}
8
+
9
+ Anywhere a `color` prop is accepted, a `Gradient` from
10
+ `createLinearGradient` or `createRadialGradient` is accepted too. Gradient
11
+ stops are positions in 0..1, not percentages.
12
+
13
+ ## Percentages
14
+
15
+ {{ decl packages/core/src/types.d.ts Pct }}
16
+
17
+ `pct(50)` is the only way to write a percentage as a value. It is branded, so
18
+ a percentage cannot be confused with a pixel count by accident, and it
19
+ resolves against the element box wherever it is used - layout dimensions,
20
+ gaps, and transform origins alike.
21
+
22
+ ## Children
23
+
24
+ {{ decl packages/core/src/types.d.ts Children }}
25
+
26
+ The element type is SolidJS's own, so the control-flow components (`For`,
27
+ `Show`, `Switch`) return something the JSX types accept.
28
+
29
+ ## JSX plumbing
30
+
31
+ One declaration exists purely to tell TypeScript which prop receives JSX
32
+ children. It is not something an app refers to:
33
+
34
+ {{ decl packages/core/src/types.d.ts ElementChildrenAttribute }}
@@ -8,7 +8,7 @@ for the element/prop model see `@solidrt/core/AGENTS.md`.
8
8
  ## Host elements and layout
9
9
  - `window-root.tsx` - the minimal app; the root must be `<window>`.
10
10
  - `view-layout.tsx` - `<view>` as a flex container; containers do not paint.
11
- - `view-viewbox.tsx` - `viewBox` on a `<view>`: author a scene once in fixed design units and let the view uniformly scale-and-center (letterbox) that space into its box. A pure fit transform - it never sizes the element (layout still does); children live in design space (the box they inherit IS the design size, so a bare `d-rect` fills it); pointer `localX`/`localY` arrive in design units. The fixed-aspect alternative to `windowSizeClass` reflow for diagrams, slides, dashboards, game boards.
11
+ - `view-design-size.tsx` - `designSize` on a `<view>`: author a scene once in fixed design units and let the view uniformly scale-and-center (letterbox) that space into its box. Children live in design space for layout as well as paint (the box they inherit IS the design size, so a bare `d-rect` fills it and a flex row lays out against the design width, never reflowing on resize); the view itself sizes like a replaced element whose intrinsic size is the design size; pointer `localX`/`localY` arrive in design units. The fixed-aspect alternative to `windowSizeClass` reflow for diagrams, slides, dashboards, game boards, scaled panels.
12
12
  - `background-rect.tsx` - a `d-rect` filling its parent as a background.
13
13
  - `detached-positioning.tsx` - the `d-` prefix: x/y placement, no reflow, detached-only children.
14
14
  - `text-paint-styling.tsx` - the uniform `color` prop; `drawStyle="stroke"` vs fill.
@@ -21,6 +21,7 @@ for the element/prop model see `@solidrt/core/AGENTS.md`.
21
21
  - `pointer-local-coords.tsx` - the three pointer coordinate frames (`clientX` window, `localX` the handling node's own frame, `parentX` its path-parent's frame - where the node's x/y live) and the transform-proof drag idiom: grab offset from `localX` at down, place with `parentX - offset` on moves. Exact inside rotated/scaled ancestors and when the pointer leaves the node mid-drag.
22
22
 
23
23
  ## Performance
24
+ - `snapshot-texture.tsx` - `snapshotTexture(ref)`: a `repaintBoundary="snapshot"` view's rasterized pixels as a live texture id; a shader texture samples the panel and a sibling `<texture>` shows the warped copy. The boundary re-rasterizes only when its content changes.
24
25
  - `repaint-boundary.tsx` - `repaintBoundary` on a `<view>` to keep static content from rebuilding while a neighbor animates: `{true}` retains the recorded draw list, `"snapshot"` also retains the rasterized pixels as a GPU texture (for raster-expensive, screen-aligned, static subtrees). `"snapshot-no-aa"` rasterizes without anti-aliasing: cheaper, fine for text and axis-aligned rects, hard-edged on vector content.
25
26
 
26
27
  ## Scrolling
@@ -28,7 +29,7 @@ for the element/prop model see `@solidrt/core/AGENTS.md`.
28
29
 
29
30
  ## Window state
30
31
  - `window-signals.tsx` - reactive `windowSize()` / `safeArea()` accessors (prefer over `onResize`).
31
- - `responsive-grid.tsx` - one app across phone/tablet/desktop: `capabilities.windowSizeClass` (Material 3 breakpoints, a reactive getter) drives the column count and `windowSize()` sizes each card; reflows on resize. The reflow answer; for fixed-aspect content use `view-viewbox.tsx` instead.
32
+ - `responsive-grid.tsx` - one app across phone/tablet/desktop: `capabilities.windowSizeClass` (Material 3 breakpoints, a reactive getter) drives the column count and `windowSize()` sizes each card; reflows on resize. The reflow answer; for fixed-aspect content use `view-design-size.tsx` instead.
32
33
 
33
34
  ## Overlays
34
35
  - `portal.tsx` - `createPortal` relocating content to the window root to escape clipping.
@@ -47,11 +48,12 @@ for the element/prop model see `@solidrt/core/AGENTS.md`.
47
48
  - `window-shader.tsx` - the `shader` prop on `<window>`: the finished frame drawn through a raw-linked warp program before present, click to toggle between warp and identity.
48
49
  - `window-shader-history.tsx` - the window shader's frame history: `previous` binds last frame as uPrevious, drawn as a one-frame motion echo behind an orbiting square; click toggles the echo term.
49
50
 
50
- ## Sound
51
- - `sound.tsx` - `createSound`: decode a clip once from bytes (here a binary import), replay cheaply; `overlap` stacking vs single-voice, `playing()` signal, release on unmount. `createPcmSound` for a synthesised clip from raw samples (a generated sine sweep). Points to `createSoundStream` for long tracks streamed from a path.
51
+ ## Audio
52
+ - `audio.tsx` - `createSound`: decode a clip once from bytes (here a binary import), replay cheaply; `overlap` stacking vs single-voice, `playing()` signal, release on unmount. `createPcmSound` for a synthesised clip from raw samples (a generated sine sweep). Points to `createSoundStream` for long tracks streamed from a path.
52
53
 
53
54
  ## Vector graphics
54
- - `parse-svg.tsx` - `parseSvg` turns a whole SVG *document string* (not HTML/JSX children) into plain draw data mapped to `<d-path>` inside a `viewBox`-fitted view; per-shape hover highlighting shows the payoff (exact-outline hit testing, recolor without re-parse), plus a `currentColor` icon recolored via the `color` option. This is how to use existing icon libraries (Lucide, Heroicons, etc.) - hand their SVG source to `parseSvg`.
55
+ - `line-points.tsx` - `points` on `line`/`d-line`: a flat `[x0, y0, x1, y1, ...]` array (or `Float32Array`) makes the polyline whose geometry is numbers - a live trace rewritten every frame with no `d` string to parse, `closed` outlines, `drawStyle="fill"` polygons (a line's paint defaults to stroke), per-segment dashing, and a laid-out `<line points>` measuring its box from the points.
56
+ - `parse-svg.tsx` - `parseSvg` turns a whole SVG *document string* (not HTML/JSX children) into plain draw data mapped to `<d-path>` inside a `designSize`-fitted view; per-shape hover highlighting shows the payoff (exact-outline hit testing, recolor without re-parse), plus a `currentColor` icon recolored via the `color` option. This is how to use existing icon libraries (Lucide, Heroicons, etc.) - hand their SVG source to `parseSvg`.
55
57
 
56
58
  ## Bundling assets
57
59
  - `binary-import.tsx` - `import bytes from "./file" with { type: "binary" }` inlines a file's bytes into the bundle as a `Uint8Array` (the bytes are in memory, so `inline-image.tsx` displays them with the synchronous `decodeImage` + `createTexture` path).
@@ -23,7 +23,7 @@
23
23
  // demand and stays off the heap. Same play()/stop()/playing() surface,
24
24
  // always single-voice.
25
25
  import { render } from "@solidrt/core"
26
- import { createPcmSound, createSound } from "@solidrt/core/sound"
26
+ import { createPcmSound, createSound } from "@solidrt/core/audio"
27
27
  import blipBytes from "./blip.wav" with { type: "binary" }
28
28
 
29
29
  const RATE = 44100
@@ -101,11 +101,11 @@ function App() {
101
101
  let [time, setTime] = createSignal(0)
102
102
  onFrame((tick) => setTime(tick / 1000))
103
103
 
104
- // Fill the window: the viewBox fits and centers the square content into
104
+ // Fill the window: the design-size fits and centers the square content into
105
105
  // the full-window view, so the projection is never stretched.
106
106
  return (
107
107
  <window>
108
- <view width={pct(100)} height={pct(100)} viewBox={[1024, 1024]}>
108
+ <view width={pct(100)} height={pct(100)} designSize={[1024, 1024]}>
109
109
  <texture src={id} params={{ uTime: time() }} width={1024} height={1024} />
110
110
  </view>
111
111
  </window>
@@ -0,0 +1,102 @@
1
+ // The zero-copy per-frame geometry path: instanced quads whose records are
2
+ // rewritten every frame through a buffer write lease. beginBufferWrite hands
3
+ // back a Float32Array over runtime-owned memory (contents unspecified - fill
4
+ // everything you publish), the frame callback writes every live record into
5
+ // it, and endBufferWrite publishes by MOVING the block to the raster thread:
6
+ // no data copy anywhere on the CPU path, and no per-sprite property writes.
7
+ // Compare gpu-instancing.tsx, where the records are static and only the draw
8
+ // range changes; here the records themselves are the animation.
9
+ //
10
+ // The buffer is created from a byte LENGTH (zeroed storage) - the natural
11
+ // create when every byte arrives through the lease. Publishing a prefix and
12
+ // setDraw({ instanceCount }) keep buffer capacity and live population
13
+ // independent: reserve the max up front, publish what exists this frame.
14
+ import { render, onFrame } from "@solidrt/core"
15
+ import { beginBufferWrite, createBuffer, createPipelineTexture, endBufferWrite, glsl, setDraw } from "@solidrt/core/gpu"
16
+
17
+ const MAX_SPRITES = 2000
18
+ // floats per record: center vec2, half-size f32, tint vec3.
19
+ const RECORD = 6
20
+
21
+ let VERTEX = glsl`
22
+ in vec2 aPos;
23
+ in vec2 iCenter;
24
+ in float iSize;
25
+ in vec3 iTint;
26
+ out vec3 vTint;
27
+
28
+ void main() {
29
+ gl_Position = vec4(iCenter + aPos * iSize, 0.0, 1.0);
30
+ vTint = iTint;
31
+ }
32
+ `
33
+
34
+ let FRAGMENT = glsl`
35
+ in vec3 vTint;
36
+
37
+ void main() {
38
+ fragColor = vec4(vTint, 1.0);
39
+ }
40
+ `
41
+
42
+ // Simulation state lives in plain arrays; the instance buffer holds only
43
+ // this frame's published snapshot of it.
44
+ let x = new Float32Array(MAX_SPRITES)
45
+ let y = new Float32Array(MAX_SPRITES)
46
+ let vx = new Float32Array(MAX_SPRITES)
47
+ let vy = new Float32Array(MAX_SPRITES)
48
+ for (let i = 0; i < MAX_SPRITES; i++) {
49
+ x[i] = Math.random() * 1.9 - 0.95
50
+ y[i] = Math.random() * 1.9 - 0.95
51
+ vx[i] = (Math.random() * 2 - 1) * 0.01
52
+ vy[i] = (Math.random() * 2 - 1) * 0.01
53
+ }
54
+
55
+ function App() {
56
+ // One unit quad (triangle strip), reused by every instance.
57
+ let quad = createBuffer(new Float32Array([-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5]), { label: "sprite-quad" })
58
+ let records = createBuffer(MAX_SPRITES * RECORD * 4, { label: "sprite-records" })
59
+ let id = createPipelineTexture(VERTEX, FRAGMENT, 720, 720, null, {
60
+ label: "sprites",
61
+ topology: "triangle-strip",
62
+ vertexCount: 4,
63
+ attributes: [{ name: "aPos", format: "vec2" }],
64
+ buffer: quad,
65
+ instanceAttributes: [
66
+ { name: "iCenter", format: "vec2" },
67
+ { name: "iSize", format: "f32" },
68
+ { name: "iTint", format: "vec3" },
69
+ ],
70
+ instanceBuffer: records,
71
+ instanceCount: MAX_SPRITES,
72
+ clearColor: [0.03, 0.03, 0.06, 1],
73
+ })
74
+
75
+ onFrame(() => {
76
+ let out = beginBufferWrite(records)
77
+ for (let i = 0; i < MAX_SPRITES; i++) {
78
+ let nx = x[i]! + vx[i]!
79
+ let ny = y[i]! + vy[i]!
80
+ if (nx < -0.95 || nx > 0.95) vx[i] = -vx[i]!
81
+ else x[i] = nx
82
+ if (ny < -0.95 || ny > 0.95) vy[i] = -vy[i]!
83
+ else y[i] = ny
84
+ let at = i * RECORD
85
+ out[at] = x[i]!
86
+ out[at + 1] = y[i]!
87
+ out[at + 2] = 0.01 + 0.008 * (i % 5)
88
+ out[at + 3] = 0.5 + 0.5 * Math.cos(i * 0.11)
89
+ out[at + 4] = 0.5 + 0.5 * Math.cos(i * 0.13 + 2.1)
90
+ out[at + 5] = 0.5 + 0.5 * Math.cos(i * 0.17 + 4.2)
91
+ }
92
+ endBufferWrite(records)
93
+ })
94
+
95
+ return (
96
+ <window alignItems="center" justifyContent="center">
97
+ <texture src={id} width={480} height={480} />
98
+ </window>
99
+ )
100
+ }
101
+
102
+ render(() => <App />)
@@ -0,0 +1,145 @@
1
+ // `points` turns a line into a polyline: a flat [x0, y0, x1, y1, ...] array in
2
+ // the element's local space, so geometry that changes every frame is one
3
+ // array write - no `d` string to format and re-parse. Three uses below:
4
+ // 1. A live trace: a Float32Array rebuilt each frame in onFrame and set as
5
+ // `points`. Typed arrays marshal like number[]; nothing is parsed.
6
+ // 2. A closed outline: `closed` strokes the segment back to the first point
7
+ // and joins there instead of capping both ends; the join style shows at
8
+ // the apex. A line's paint defaults to stroke; drawStyle="fill" fills the
9
+ // polygon instead (implicitly closed), "stroke-and-fill" does both.
10
+ // Dashing runs along the whole stroke, through the vertices.
11
+ // 3. Marching ants: `dashOffset` slides the dash pattern, so writing it every
12
+ // frame animates the dashes. The ring is dense (6 px segments under
13
+ // 12 px dashes), which only works because the phase carries across
14
+ // vertices; the two-point d-line and the d-path go through the same
15
+ // walker (a path's dashes are pieces of its curves, stroked as curves).
16
+ // 4. Partial draw: `pathLength={1}` makes the dash units fractions of the
17
+ // geometry's length, so `onLength={0.77} offLength={1}` draws the first
18
+ // 77% and an `onLength` written from 0 to 1 draws the geometry on, without
19
+ // knowing its length.
20
+ // 5. A laid-out <line points>: the points are content (like a path's `d`), so
21
+ // the box measures from their extent and takes part in the row.
22
+ // The two-endpoint form (x1..y2, on d-line only) is unchanged; while `points`
23
+ // is set it takes precedence over the endpoints.
24
+ import { render, onFrame, createSignal } from "@solidrt/core"
25
+
26
+ const SAMPLES = 200
27
+ const TRACE_W = 560
28
+ const TRACE_H = 160
29
+ const TRIANGLE = [20, 100, 70, 20, 120, 100]
30
+ const ZIGZAG = [0, 0, 30, 24, 60, 0, 90, 24, 120, 0]
31
+ const RING = ring(70, 60, 45, 48)
32
+ const CURVE = "M20 60 C 60 0, 100 120, 140 60 S 200 20, 200 60"
33
+ const ANTS_SPEED = 40 // local units per second
34
+ const DRAW_PERIOD = 3 // seconds per draw-on cycle
35
+
36
+ function ring(cx: number, cy: number, r: number, n: number): number[] {
37
+ let pts: number[] = []
38
+ for (let i = 0; i < n; i++) {
39
+ let a = (i / n) * Math.PI * 2
40
+ pts.push(cx + Math.cos(a) * r, cy + Math.sin(a) * r)
41
+ }
42
+ return pts
43
+ }
44
+
45
+ // A travelling wave inside a sine envelope, sampled into x, y pairs.
46
+ function wave(t: number): Float32Array {
47
+ let pts = new Float32Array(SAMPLES * 2)
48
+ for (let i = 0; i < SAMPLES; i++) {
49
+ let u = i / (SAMPLES - 1)
50
+ pts[2 * i] = u * TRACE_W
51
+ pts[2 * i + 1] = TRACE_H / 2 + Math.sin(u * 14 - t * 4) * Math.sin(u * Math.PI) * (TRACE_H / 2 - 8)
52
+ }
53
+ return pts
54
+ }
55
+
56
+ function App() {
57
+ let [trace, setTrace] = createSignal<Float32Array>(wave(0))
58
+ let [ants, setAnts] = createSignal(0)
59
+ let [drawn, setDrawn] = createSignal(0)
60
+ onFrame((tick) => {
61
+ setTrace(wave(tick / 1000))
62
+ setAnts((tick / 1000) * ANTS_SPEED)
63
+ setDrawn(((tick / 1000) % DRAW_PERIOD) / DRAW_PERIOD)
64
+ })
65
+
66
+ return (
67
+ <window padding={24} gap={20}>
68
+ <d-rect color="#0b0f17" />
69
+
70
+ <text fontSize={16} color="#8b949e">
71
+ live trace: a Float32Array of {SAMPLES} points written every frame
72
+ </text>
73
+ <view width={TRACE_W} height={TRACE_H} flexShrink={0}>
74
+ <d-rect radius={8} color="#151b28" />
75
+ <d-line points={trace()} color="#3fb950" strokeWidth={3} strokeJoin="round" />
76
+ </view>
77
+
78
+ <text fontSize={16} color="#8b949e">
79
+ closed (round join), open (round caps), filled, stroke-and-fill dashed
80
+ </text>
81
+ <view flexDirection="row" flexWrap="wrap" gap={20}>
82
+ <view width={140} height={120}>
83
+ <d-rect radius={8} color="#151b28" />
84
+ <d-line points={TRIANGLE} closed color="#e3b341" strokeWidth={8} strokeJoin="round" />
85
+ </view>
86
+ <view width={140} height={120}>
87
+ <d-rect radius={8} color="#151b28" />
88
+ <d-line points={TRIANGLE} color="#e3b341" strokeWidth={8} strokeCap="round" />
89
+ </view>
90
+ <view width={140} height={120}>
91
+ <d-rect radius={8} color="#151b28" />
92
+ <d-line points={TRIANGLE} drawStyle="fill" color="#a371f7" />
93
+ </view>
94
+ <view width={140} height={120}>
95
+ <d-rect radius={8} color="#151b28" />
96
+ <d-line points={TRIANGLE} closed drawStyle="stroke-and-fill" onLength={12} offLength={8} color="#f85149" strokeWidth={3} />
97
+ </view>
98
+ </view>
99
+
100
+ <text fontSize={16} color="#8b949e">
101
+ marching ants: dashOffset written every frame, on a dense ring, a segment and a path
102
+ </text>
103
+ <view flexDirection="row" flexWrap="wrap" gap={20}>
104
+ <view width={140} height={120}>
105
+ <d-rect radius={8} color="#151b28" />
106
+ <d-line points={RING} closed onLength={12} offLength={8} dashOffset={ants()} color="#e3b341" strokeWidth={3} />
107
+ </view>
108
+ <view width={300} height={120}>
109
+ <d-rect radius={8} color="#151b28" />
110
+ <d-line x1={20} y1={60} x2={280} y2={60} onLength={0} offLength={14} dashOffset={-ants()} color="#79c0ff" strokeWidth={6} strokeCap="round" />
111
+ </view>
112
+ <view width={220} height={120}>
113
+ <d-rect radius={8} color="#151b28" />
114
+ <d-path d={CURVE} drawStyle="stroke" onLength={10} offLength={6} dashOffset={ants()} color="#f778ba" strokeWidth={3} strokeCap="round" />
115
+ </view>
116
+ </view>
117
+
118
+ <text fontSize={16} color="#8b949e">
119
+ partial draw: pathLength=1 makes the pattern fractional, 77% of the curve and a triangle drawing on
120
+ </text>
121
+ <view flexDirection="row" flexWrap="wrap" gap={20}>
122
+ <view width={220} height={120}>
123
+ <d-rect radius={8} color="#151b28" />
124
+ <d-path d={CURVE} drawStyle="stroke" pathLength={1} onLength={0.77} offLength={1} color="#3fb950" strokeWidth={4} strokeCap="round" />
125
+ </view>
126
+ <view width={140} height={120}>
127
+ <d-rect radius={8} color="#151b28" />
128
+ <d-line points={TRIANGLE} closed pathLength={1} onLength={drawn()} offLength={1} color="#79c0ff" strokeWidth={4} strokeCap="round" strokeJoin="round" />
129
+ </view>
130
+ </view>
131
+
132
+ <view flexDirection="row" alignItems="center" gap={12}>
133
+ <text fontSize={16} color="#8b949e">
134
+ laid out:
135
+ </text>
136
+ <line points={ZIGZAG} color="#1f6feb" strokeWidth={3} strokeJoin="round" />
137
+ <text fontSize={16} color="#8b949e">
138
+ the box measures from the points
139
+ </text>
140
+ </view>
141
+ </window>
142
+ )
143
+ }
144
+
145
+ render(() => <App />)
@@ -3,7 +3,7 @@
3
3
  // it the source text (a string you import, fetch, or inline) and get back the
4
4
  // document's intrinsic size plus a flat list of draws whose keys match the
5
5
  // path element's props - so rendering is a map to <d-path>, wrapped in a view
6
- // whose `viewBox` fits the document's coordinate space into the box.
6
+ // whose `designSize` fits the document's coordinate space into the box.
7
7
  //
8
8
  // The point of draws-as-data over an opaque document element: every shape is
9
9
  // a real node you own. Below, the house highlights the shape under the
@@ -28,7 +28,7 @@ import { render, parseSvg, svg, createMemo, createSignal, For } from "@solidrt/c
28
28
  // the string unchanged; it exists so editors highlight the markup (like `glsl`
29
29
  // for shader sources).
30
30
  const HOUSE = svg`
31
- <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
31
+ <svg designSize="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
32
32
  <rect x="20" y="45" width="60" height="45" fill="#457b9d"/>
33
33
  <path d="M10 50 L50 15 L90 50 Z" fill="#e63946"/>
34
34
  <rect x="42" y="62" width="16" height="28" fill="#f1faee"/>
@@ -37,7 +37,7 @@ const HOUSE = svg`
37
37
 
38
38
  // Monochrome icon (Lucide arrow-right) drawn with currentColor, recolored below.
39
39
  const ARROW = svg`
40
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
40
+ <svg designSize="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
41
41
  stroke-linecap="round" stroke-linejoin="round">
42
42
  <path d="M5 12h14"/>
43
43
  <path d="M12 5l7 7-7 7"/>
@@ -54,7 +54,7 @@ function InteractiveHouse() {
54
54
  // re-records only when a draw inside it changes (hover), never because a
55
55
  // sibling elsewhere on the screen did.
56
56
  return (
57
- <view repaintBoundary width={240} height={240} viewBox={[doc().width, doc().height]}>
57
+ <view repaintBoundary width={240} height={240} designSize={[doc().width, doc().height]}>
58
58
  <For each={doc().draws}>
59
59
  {(draw, i) => (
60
60
  <d-path
@@ -69,14 +69,14 @@ function InteractiveHouse() {
69
69
  )
70
70
  }
71
71
 
72
- // The plain pattern: memoized parse, viewBox-fitted box, draws mapped to
72
+ // The plain pattern: memoized parse, designSize-fitted box, draws mapped to
73
73
  // <d-path>, and a plain repaintBoundary (the DL-reuse tier, not "snapshot")
74
74
  // so the static subtree never re-records alongside animating siblings. The
75
75
  // components-package Icon is this plus theming.
76
76
  function Svg(props: { src: string; size: number; color?: string }) {
77
77
  let doc = createMemo(() => parseSvg(props.src, { color: props.color }))
78
78
  return (
79
- <view repaintBoundary width={props.size} height={props.size} viewBox={[doc().width, doc().height]}>
79
+ <view repaintBoundary width={props.size} height={props.size} designSize={[doc().width, doc().height]}>
80
80
  <For each={doc().draws}>{(draw) => <d-path {...draw} />}</For>
81
81
  </view>
82
82
  )
@@ -12,7 +12,7 @@
12
12
  // This is the REFLOW answer, for layouts that genuinely rearrange across form
13
13
  // factors. For content with fixed internal geometry (diagrams, slides,
14
14
  // dashboards, game boards) do not branch on window size at all: author one
15
- // design space and let `viewBox` scale it to fit - see view-viewbox.tsx.
15
+ // design space and let `designSize` scale it to fit - see view-design-size.tsx.
16
16
  import { render, capabilities, windowSize, createMemo, For } from "@solidrt/core"
17
17
 
18
18
  const GAP = 16
@@ -11,7 +11,7 @@
11
11
  // Capture both with refs, pass their accessors to createScroll, then apply the
12
12
  // returned offset to the viewport's scrollX/scrollY. createScroll does no input,
13
13
  // so wire an event (here onWheel) to scroll.scrollBy - positive dy moves content
14
- // up. scrollTo(x, y) jumps to an absolute, clamped offset.
14
+ // up. scrollTo({ x, y }) jumps to an absolute, clamped offset.
15
15
  import { render, For, createScroll } from "@solidrt/core"
16
16
  import type { WheelEvent } from "@solidrt/core"
17
17
 
@@ -22,7 +22,7 @@ function App() {
22
22
  // Default axis is "vertical"; pass { axis: "horizontal" } or "both" for others.
23
23
  let scroll = createScroll(() => viewport, () => content)
24
24
 
25
- let onWheel = (e: WheelEvent) => scroll.scrollBy(e.deltaX, e.deltaY)
25
+ let onWheel = (e: WheelEvent) => scroll.scrollBy({ x: e.deltaX, y: e.deltaY })
26
26
 
27
27
  let rows = Array.from({ length: 30 }, (_, i) => i)
28
28
 
@@ -0,0 +1,72 @@
1
+ // A UI subtree as a live texture. snapshotTexture(ref) returns the texture id
2
+ // behind a repaintBoundary="snapshot" view: its rasterized pixels, as an
3
+ // ordinary texture id any GPU consumer samples. Here a shader texture binds
4
+ // it as uPanel and a sibling <texture> shows the result, so the panel on the
5
+ // left and its warped twin on the right are the same pixels. The id is
6
+ // stable; the runtime re-points it after every re-rasterization, and the
7
+ // boundary only re-rasterizes when its subtree changes - so the warp
8
+ // animates every frame while the panel's text is painted once per edit.
9
+ //
10
+ // Tap the panel to count. Both copies update, the mirror through the GPU.
11
+ import { render, onFrame, createSignal, snapshotTexture, Show } from "@solidrt/core"
12
+ import { createShaderTexture, glsl } from "@solidrt/core/gpu"
13
+
14
+ let MIRROR = glsl`
15
+ uniform sampler2D uPanel;
16
+ uniform float uTime;
17
+ void main() {
18
+ vec2 uv = vUV;
19
+ uv.x += sin(uv.y * 20.0 + uTime * 3.0) * 0.02;
20
+ vec4 panel = texture(uPanel, uv);
21
+ // Premultiplied source: tint the color, keep the alpha.
22
+ float glow = 0.5 + 0.5 * sin(uTime * 2.0 + uv.y * 6.0);
23
+ fragColor = vec4(panel.rgb * vec3(1.0, 0.7 + 0.3 * glow, 0.6), panel.a);
24
+ }
25
+ `
26
+
27
+ function App() {
28
+ let [count, setCount] = createSignal(0)
29
+ let [time, setTime] = createSignal(0)
30
+ onFrame(tick => setTime(tick / 1000))
31
+
32
+ let [panel, setPanel] = createSignal<{ id: number }>()
33
+
34
+ return (
35
+ <window alignItems="center" justifyContent="center" gap={40} flexDirection="row">
36
+ <view
37
+ ref={(n: { id: number }) => setPanel(n)}
38
+ repaintBoundary="snapshot"
39
+ onPointerDown={() => setCount(c => c + 1)}
40
+ width={240}
41
+ height={160}
42
+ padding={20}
43
+ gap={12}
44
+ flexDirection="column"
45
+ >
46
+ <rect position="absolute" width="100%" height="100%" radius={12} color="#1e2a44" />
47
+ <text fontSize={20} color="#ffffff">
48
+ Live panel
49
+ </text>
50
+ <text fontSize={40} color="#ffd166">
51
+ {String(count())}
52
+ </text>
53
+ <text fontSize={14} color="#9fb3d9">
54
+ tap to count
55
+ </text>
56
+ </view>
57
+ <Show when={panel()} keyed>
58
+ {p => {
59
+ // The id is valid as soon as the boundary exists; the texture is
60
+ // empty until its first paint, then live.
61
+ let mirror = createShaderTexture(MIRROR, 480, 320, { uTime: 0 }, {
62
+ textures: { uPanel: snapshotTexture(p) },
63
+ label: "panel-mirror",
64
+ })
65
+ return <texture src={mirror} width={240} height={160} params={{ uTime: time() }} />
66
+ }}
67
+ </Show>
68
+ </window>
69
+ )
70
+ }
71
+
72
+ render(() => <App />)