@solidrt/core 0.0.40 → 0.0.42
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/AGENTS.md +14 -3
- package/README.md +1 -1
- package/examples/README.md +5 -4
- package/examples/gpu-instancing.tsx +70 -0
- package/examples/gpu-particles.tsx +35 -35
- package/examples/gpu-pipeline.tsx +40 -37
- package/examples/gpu-raw-program.tsx +59 -40
- package/examples/gpu-shader.tsx +48 -41
- package/examples/gpu-texture-blend.tsx +20 -19
- package/examples/inline-image.tsx +1 -1
- package/examples/parse-svg.tsx +94 -0
- package/examples/text-import.tsx +2 -2
- package/examples/wave.glsl +5 -3
- package/examples/window-shader-history.tsx +23 -23
- package/examples/window-shader.tsx +30 -28
- package/jsx-runtime.d.ts +17 -9
- package/package.json +2 -2
- package/src/camera.ts +8 -4
- package/src/color.ts +14 -2
- package/src/core.ts +254 -15
- package/src/environment.ts +16 -3
- package/src/gpu.ts +176 -75
- package/src/image.ts +12 -11
- package/src/index.ts +6 -2
- package/src/renderer.ts +32 -11
- package/src/runtime-modules.d.ts +2 -2
- package/src/speech-recognition.ts +2 -1
- package/src/svg.ts +71 -0
- package/src/types.d.ts +111 -38
- package/src/window.ts +39 -18
- package/examples/svg.tsx +0 -49
package/src/svg.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// SVG documents as data: parseSvg turns a document string into a flat list of
|
|
2
|
+
// draws that map straight onto <d-path> elements. Vector currency is path
|
|
3
|
+
// data, the same way raster currency is a texture id - there is no document
|
|
4
|
+
// element that swallows the source; the app owns the parsed data and composes
|
|
5
|
+
// ordinary primitives from it.
|
|
6
|
+
|
|
7
|
+
import { parseSvg as fluxParseSvg } from "flux:svg"
|
|
8
|
+
import { parseColor, type Gradient } from "./color"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tags an inline SVG source, returning it unchanged. Documents small enough to
|
|
12
|
+
* belong beside the code that uses them stay in the file; the tag is what
|
|
13
|
+
* makes them legible there, because editors highlight markup inside a template
|
|
14
|
+
* literal only when a known tag marks it (the name matters - `svg` is one the
|
|
15
|
+
* grammars look for). Raw semantics, like `glsl`.
|
|
16
|
+
*/
|
|
17
|
+
export let svg = String.raw
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One resolved draw in document coordinates. The keys match `PathProps`, so a
|
|
21
|
+
* draw spreads onto a `<d-path>` unchanged: `<d-path {...draw} />`. A source
|
|
22
|
+
* path with both a fill and a stroke yields two draws (fill first).
|
|
23
|
+
*/
|
|
24
|
+
export type SvgDraw = {
|
|
25
|
+
d: string
|
|
26
|
+
color: string | Gradient
|
|
27
|
+
drawStyle: "fill" | "stroke"
|
|
28
|
+
fillRule?: "nonzero" | "evenodd"
|
|
29
|
+
strokeWidth?: number
|
|
30
|
+
strokeCap?: "butt" | "round" | "square"
|
|
31
|
+
strokeJoin?: "miter" | "round" | "bevel"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A parsed document: intrinsic size (viewBox/width-height) plus the flat draw list. */
|
|
35
|
+
export type SvgDocument = {
|
|
36
|
+
width: number
|
|
37
|
+
height: number
|
|
38
|
+
draws: SvgDraw[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parses an SVG document string (an imported `.svg` asset, an icon library's
|
|
43
|
+
* string export, or a template literal) into plain draw data: geometry
|
|
44
|
+
* flattened to absolute path data with every transform baked in, paints
|
|
45
|
+
* resolved to colors or gradients. Render it by wrapping the draws in a view
|
|
46
|
+
* that fits the document's coordinate space into its box:
|
|
47
|
+
*
|
|
48
|
+
* let doc = createMemo(() => parseSvg(src))
|
|
49
|
+
* <view repaintBoundary viewBox={[doc().width, doc().height]} width={48} height={48}>
|
|
50
|
+
* {doc().draws.map((draw) => <d-path {...draw} />)}
|
|
51
|
+
* </view>
|
|
52
|
+
*
|
|
53
|
+
* The plain repaintBoundary is the recommended default: the parsed subtree is
|
|
54
|
+
* static, so it never re-records alongside changing siblings. Each `<d-path>`
|
|
55
|
+
* hit-tests its exact outline; when the document should act as ONE hit target
|
|
56
|
+
* (the usual icon case), add `pointerEvents="all"` to the wrapper - the box
|
|
57
|
+
* then matches as a whole and the per-path outline tests are skipped.
|
|
58
|
+
*
|
|
59
|
+
* `opts.color` drives `currentColor` in the document (any CSS color string),
|
|
60
|
+
* which is how monochrome icon sets (Lucide, Feather, Heroicons, ...) get
|
|
61
|
+
* recolored; explicit fills/strokes still win. Parsing is synchronous and
|
|
62
|
+
* sandboxed (no network, file, or data-URI access) and throws on an invalid
|
|
63
|
+
* document. Parse once per document under a memo, not per instance.
|
|
64
|
+
*
|
|
65
|
+
* Unsupported and skipped: clipPath, masks, filters, patterns, embedded
|
|
66
|
+
* images, and SVG text.
|
|
67
|
+
*/
|
|
68
|
+
export function parseSvg(src: string, opts?: { color?: string }): SvgDocument {
|
|
69
|
+
if (opts?.color != null) return fluxParseSvg(src, { color: parseColor(opts.color) })
|
|
70
|
+
return fluxParseSvg(src)
|
|
71
|
+
}
|
package/src/types.d.ts
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
/// <reference path="./runtime-modules.d.ts" />
|
|
3
3
|
|
|
4
4
|
import type { Gradient } from "./color"
|
|
5
|
+
import type { ProgramId, TextureId } from "flux:gpu"
|
|
6
|
+
import type { TextInputHints } from "flux:rendertree"
|
|
5
7
|
import type { Element } from "solid-js"
|
|
6
8
|
|
|
9
|
+
export type { TextInputHints }
|
|
10
|
+
|
|
7
11
|
// The "srt:*" lattice runner modules are declared in ./runtime-modules.d.ts
|
|
8
12
|
// (referenced above) - ambient `declare module` only reaches consumers from a
|
|
9
13
|
// non-module declaration file, and this file is a module.
|
|
@@ -173,6 +177,9 @@ export interface TransformProps {
|
|
|
173
177
|
// Perspective viewing distance in pixels (CSS `perspective`). Enables the 3D
|
|
174
178
|
// depth for rotateY; larger values give a shallower effect.
|
|
175
179
|
perspective?: number
|
|
180
|
+
// Subtree translation in pixels, composited post-layout (no re-record, no
|
|
181
|
+
// layout). Unlike the draw primitives' detached-only x/y, these exist on
|
|
182
|
+
// layout views too - the drag/thumb idiom animates them freely.
|
|
176
183
|
x?: number
|
|
177
184
|
y?: number
|
|
178
185
|
originX?: OriginX
|
|
@@ -235,6 +242,10 @@ export interface WheelEvent extends PointerEvent {
|
|
|
235
242
|
// layout-dependent value ("a", "!", "Enter", "ArrowLeft"); `code` is the
|
|
236
243
|
// physical, layout-independent key position ("KeyA", "Digit1", "NumpadEnter").
|
|
237
244
|
// Printable characters for text entry arrive via onTextInput, not here.
|
|
245
|
+
// Routing: keydown/keyup dispatch along the focused node's ancestor chain,
|
|
246
|
+
// leaf->root, always ending at the window root; with nothing focused they go
|
|
247
|
+
// to the window root alone. <window onKeyDown> is therefore the app-global
|
|
248
|
+
// shortcut point.
|
|
238
249
|
export interface KeyEvent {
|
|
239
250
|
key: string
|
|
240
251
|
code: string
|
|
@@ -243,6 +254,16 @@ export interface KeyEvent {
|
|
|
243
254
|
ctrlKey: boolean
|
|
244
255
|
altKey: boolean
|
|
245
256
|
metaKey: boolean
|
|
257
|
+
/** Node id whose handler is currently running (bubbling changes it per call). */
|
|
258
|
+
currentTarget: number
|
|
259
|
+
/** Node id the dispatch started at: the focused node, or the window root when nothing is focused. */
|
|
260
|
+
target: number
|
|
261
|
+
/**
|
|
262
|
+
* Stops the event from reaching ancestor handlers. A component that consumed
|
|
263
|
+
* the key calls this so enclosing handlers (and app-global shortcuts on the
|
|
264
|
+
* window) do not also act on it.
|
|
265
|
+
*/
|
|
266
|
+
stopPropagation: () => void
|
|
246
267
|
}
|
|
247
268
|
|
|
248
269
|
export interface TextEvent {
|
|
@@ -261,14 +282,70 @@ export interface PointerProps {
|
|
|
261
282
|
onKeyDown?: (event: KeyEvent) => void
|
|
262
283
|
onKeyUp?: (event: KeyEvent) => void
|
|
263
284
|
onTextInput?: (event: TextEvent) => void
|
|
285
|
+
/**
|
|
286
|
+
* IME behavior for this node's text-entry sessions (keyboard type,
|
|
287
|
+
* capitalization, autocorrect); read when a session starts. Without it the
|
|
288
|
+
* OS defaults apply - notably sentence auto-capitalization, which
|
|
289
|
+
* identifier fields and terminals want off:
|
|
290
|
+
* `textInputHints={{ capitalize: "none", autocorrect: false }}`.
|
|
291
|
+
*/
|
|
292
|
+
textInputHints?: TextInputHints
|
|
293
|
+
/**
|
|
294
|
+
* Declares the element a candidate for focus navigation, enumerable via
|
|
295
|
+
* getFocusables(). Candidacy only - it changes no behavior by itself; focus
|
|
296
|
+
* still moves through setFocus.
|
|
297
|
+
*/
|
|
298
|
+
focusable?: boolean
|
|
264
299
|
pointerEvents?: "auto" | "none" | "all"
|
|
265
300
|
}
|
|
266
301
|
|
|
267
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Detached-only geometry, in paint-space pixels. Never affects layout: these
|
|
304
|
+
* props exist only on the d-* forms, where there is no layout box and the
|
|
305
|
+
* element owns its geometry. The layout forms of the draw primitives derive
|
|
306
|
+
* their geometry from the layout box instead (size it with width/height).
|
|
307
|
+
*/
|
|
308
|
+
export interface PositionProps {
|
|
309
|
+
/** Horizontal offset of the drawn geometry; defaults to 0. */
|
|
268
310
|
x?: number
|
|
311
|
+
/** Vertical offset of the drawn geometry; defaults to 0. */
|
|
269
312
|
y?: number
|
|
270
313
|
}
|
|
271
314
|
|
|
315
|
+
/** See {@link PositionProps}: detached-only, never affects layout. */
|
|
316
|
+
export interface GeometryProps extends PositionProps {
|
|
317
|
+
/** Drawn width; defaults to the inherited box width. */
|
|
318
|
+
w?: number
|
|
319
|
+
/** Drawn height; defaults to the inherited box height. */
|
|
320
|
+
h?: number
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** See {@link PositionProps}: detached-only, never affects layout. */
|
|
324
|
+
export interface OvalGeometryProps extends PositionProps {
|
|
325
|
+
/** Bounding box width of the ellipse (not a radius); defaults to the inherited box. */
|
|
326
|
+
w?: number
|
|
327
|
+
/** Bounding box height of the ellipse (not a radius); defaults to the inherited box. */
|
|
328
|
+
h?: number
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** See {@link PositionProps}: detached-only, never affects layout. */
|
|
332
|
+
export interface TextGeometryProps extends PositionProps {
|
|
333
|
+
// Shaping (wrap) width. Detached text wraps at the inherited ancestor size
|
|
334
|
+
// by default; set w for an unwrapped natural line or an explicit wrap width.
|
|
335
|
+
w?: number
|
|
336
|
+
// Reported-bounds height only; paragraph height always falls out of the text.
|
|
337
|
+
h?: number
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** See {@link PositionProps}: detached-only, never affects layout. */
|
|
341
|
+
export interface LineGeometryProps {
|
|
342
|
+
/** Endpoints default to spanning the box: (0,0) to (box width, box height). */
|
|
343
|
+
x1?: number
|
|
344
|
+
y1?: number
|
|
345
|
+
x2?: number
|
|
346
|
+
y2?: number
|
|
347
|
+
}
|
|
348
|
+
|
|
272
349
|
// Primitives
|
|
273
350
|
|
|
274
351
|
export interface WindowProps extends LayoutProps, PointerProps {
|
|
@@ -298,7 +375,7 @@ export interface WindowProps extends LayoutProps, PointerProps {
|
|
|
298
375
|
*/
|
|
299
376
|
export interface WindowShaderProps {
|
|
300
377
|
/** Linked program handle from linkProgram. */
|
|
301
|
-
program:
|
|
378
|
+
program: ProgramId
|
|
302
379
|
/**
|
|
303
380
|
* Uniforms filled by name, paced to the next real repaint. A number drives
|
|
304
381
|
* a scalar (`float`/`int`); a flat number array drives the declared GLSL
|
|
@@ -306,7 +383,7 @@ export interface WindowShaderProps {
|
|
|
306
383
|
*/
|
|
307
384
|
params?: Record<string, number | number[]>
|
|
308
385
|
/** Extra sampler2D inputs: uniform name to texture id. */
|
|
309
|
-
textures?: Record<string,
|
|
386
|
+
textures?: Record<string, TextureId>
|
|
310
387
|
/** Vertices drawn (attributeless triangles). Default 3, the covering triangle. */
|
|
311
388
|
vertexCount?: number
|
|
312
389
|
/**
|
|
@@ -320,9 +397,23 @@ export interface WindowShaderProps {
|
|
|
320
397
|
previous?: boolean
|
|
321
398
|
}
|
|
322
399
|
|
|
323
|
-
|
|
400
|
+
// Everything a view offers besides layout: d-view uses this directly (a
|
|
401
|
+
// detached view has no taffy presence, so layout props would be rejected at
|
|
402
|
+
// runtime); the layout `view` adds LayoutProps below.
|
|
403
|
+
export interface ViewOwnProps extends TransformProps, PointerProps {
|
|
324
404
|
children?: Children
|
|
325
405
|
trace?: boolean
|
|
406
|
+
/**
|
|
407
|
+
* Design-space size `[w, h]` for the children: content drawn in that
|
|
408
|
+
* coordinate space is uniformly scaled to fit and centered in the element's
|
|
409
|
+
* box (SVG's default preserveAspectRatio, generalized). A pure fit
|
|
410
|
+
* transform - it never sizes the element, so give the box its size with
|
|
411
|
+
* layout props. Composed innermost: the transform props still operate in
|
|
412
|
+
* box space, and pointer events on children arrive in design coordinates.
|
|
413
|
+
* The natural wrapper for parseSvg draws, or any d-* subtree authored in
|
|
414
|
+
* fixed design units.
|
|
415
|
+
*/
|
|
416
|
+
viewBox?: [number, number]
|
|
326
417
|
/**
|
|
327
418
|
* Corner radii for the clip applied when overflow is non-visible (hidden,
|
|
328
419
|
* clip, scroll on both axes). A single number rounds all four corners; an
|
|
@@ -349,54 +440,38 @@ export interface ViewProps extends LayoutProps, TransformProps, PointerProps {
|
|
|
349
440
|
repaintBoundary?: boolean | "snapshot" | "snapshot-no-aa"
|
|
350
441
|
}
|
|
351
442
|
|
|
443
|
+
export interface ViewProps extends ViewOwnProps, LayoutProps {}
|
|
444
|
+
|
|
352
445
|
// draw primitives
|
|
353
446
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
447
|
+
// A stroked rect paints inside its box, like a CSS border: the stroke's outer
|
|
448
|
+
// edge sits on the box edge rather than straddling it, so nothing bleeds past
|
|
449
|
+
// the box for a clip to cut. `path` and `line` strokes stay centered on their
|
|
450
|
+
// geometry - there the geometry is the stroke, not a box.
|
|
451
|
+
export interface RectProps extends PaintProps, PointerProps {
|
|
452
|
+
// Corner radius, measured on the box (the stroke's outer edge). A single
|
|
453
|
+
// number applies to all four corners; an array is [top-left, top-right,
|
|
454
|
+
// bottom-right, bottom-left] (CSS border-radius order).
|
|
359
455
|
radius?: number | [number, number, number, number]
|
|
360
456
|
}
|
|
361
457
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
w?: number
|
|
365
|
-
/** Bounding box height of the ellipse (not a radius); defaults to the layout box. */
|
|
366
|
-
h?: number
|
|
367
|
-
}
|
|
458
|
+
// Strokes paint inside the box, same as `RectProps`.
|
|
459
|
+
export interface OvalProps extends PaintProps, PointerProps {}
|
|
368
460
|
|
|
369
461
|
export interface LineProps extends PaintProps, PointerProps {
|
|
370
|
-
x1?: number
|
|
371
|
-
y1?: number
|
|
372
|
-
x2?: number
|
|
373
|
-
y2?: number
|
|
374
462
|
/** Dash pattern in local units: the drawn segment length. Both onLength and offLength must be set to dash; with either unset the line is solid. */
|
|
375
463
|
onLength?: number
|
|
376
464
|
/** Dash pattern in local units: the gap length. Both onLength and offLength must be set to dash; with either unset the line is solid. */
|
|
377
465
|
offLength?: number
|
|
378
466
|
}
|
|
379
467
|
|
|
380
|
-
export interface PathProps extends
|
|
468
|
+
export interface PathProps extends PaintProps, PointerProps {
|
|
381
469
|
d?: string
|
|
382
470
|
fillRule?: "nonzero" | "evenodd"
|
|
383
471
|
}
|
|
384
472
|
|
|
385
|
-
export interface
|
|
386
|
-
// A whole SVG document as a string (an imported asset, a fetched string, or a
|
|
387
|
-
// template literal). Parsed and rendered as one unit; takes no JSX children.
|
|
388
|
-
src?: string
|
|
389
|
-
// Drives currentColor in the document. Explicit fills/strokes still win.
|
|
390
|
-
color?: Color
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
export interface TextProps extends Position, PaintProps, PointerProps {
|
|
473
|
+
export interface TextProps extends PaintProps, PointerProps {
|
|
394
474
|
children?: Children
|
|
395
|
-
// Shaping (wrap) width. Detached text wraps at the inherited ancestor size
|
|
396
|
-
// by default; set w for an unwrapped natural line or an explicit wrap width.
|
|
397
|
-
w?: number
|
|
398
|
-
// Reported-bounds height only; paragraph height always falls out of the text.
|
|
399
|
-
h?: number
|
|
400
475
|
fontFamily?: "sans" | "serif" | "mono" | (string & {})
|
|
401
476
|
fontSize?: number
|
|
402
477
|
/**
|
|
@@ -420,8 +495,8 @@ export interface TextProps extends Position, PaintProps, PointerProps {
|
|
|
420
495
|
* Texture alpha is premultiplied, so additive modes need no manual
|
|
421
496
|
* premultiplication.
|
|
422
497
|
*/
|
|
423
|
-
export interface TextureProps extends
|
|
424
|
-
src?:
|
|
498
|
+
export interface TextureProps extends PaintProps, PointerProps {
|
|
499
|
+
src?: TextureId
|
|
425
500
|
/**
|
|
426
501
|
* How the texture's pixels map to the element box (CSS object-fit).
|
|
427
502
|
* "fill" (default) stretches; "cover" and "none" crop; "contain" and
|
|
@@ -430,8 +505,6 @@ export interface TextureProps extends Position, PaintProps, PointerProps {
|
|
|
430
505
|
* bars and "cover" cropped edges still hit-test as part of the element.
|
|
431
506
|
*/
|
|
432
507
|
fit?: "fill" | "cover" | "contain" | "none" | "scale-down"
|
|
433
|
-
w?: number
|
|
434
|
-
h?: number
|
|
435
508
|
srcX?: number
|
|
436
509
|
srcY?: number
|
|
437
510
|
srcW?: number
|
package/src/window.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { requestFrame } 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"
|
|
6
|
-
import { getEventHandler,
|
|
7
|
-
import { scanForOrphans } from "./renderer"
|
|
6
|
+
import { getEventHandler, focusedNode, setFocus, activateTextInput, setInterestRoot } from "./core"
|
|
7
|
+
import { scanForOrphans, getNodePath } from "./renderer"
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Leaves the current app, unconditionally: back to the launcher in a dev
|
|
@@ -228,7 +228,10 @@ export function onBack(fn: (e: BackEvent) => void) {
|
|
|
228
228
|
|
|
229
229
|
// ------ Window ----------------
|
|
230
230
|
|
|
231
|
-
export function attachWindow(
|
|
231
|
+
export function attachWindow(nodeId: number) {
|
|
232
|
+
// The root carries the ambient move-interest bit for global onPointerMove
|
|
233
|
+
// subscribers (it is on every hit path); see core.setInterestRoot.
|
|
234
|
+
setInterestRoot(nodeId)
|
|
232
235
|
let unsubscribe: () => void = null!
|
|
233
236
|
let unsubDown: () => void = null!
|
|
234
237
|
let unsubUp: () => void = null!
|
|
@@ -304,11 +307,16 @@ export function attachWindow(_nodeId: number) {
|
|
|
304
307
|
|
|
305
308
|
unsubDown = on("pointerDown", (raw: RawPointer) => {
|
|
306
309
|
bubble(raw, "onPointerDown")
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
let focused =
|
|
310
|
+
// Read focus AFTER per-node handlers so a tap that moves focus to a new
|
|
311
|
+
// node is not immediately blurred again.
|
|
312
|
+
let focused = focusedNode()
|
|
310
313
|
if (focused != null && !raw.targets.includes(focused)) {
|
|
314
|
+
// Outside-tap blur.
|
|
311
315
|
setFocus(null)
|
|
316
|
+
} else if (focused != null) {
|
|
317
|
+
// A tap on the focused node is the interaction that lets a pending
|
|
318
|
+
// text session raise the on-screen keyboard.
|
|
319
|
+
activateTextInput()
|
|
312
320
|
}
|
|
313
321
|
})
|
|
314
322
|
|
|
@@ -332,19 +340,31 @@ export function attachWindow(_nodeId: number) {
|
|
|
332
340
|
bubble(raw, "onWheel")
|
|
333
341
|
})
|
|
334
342
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
343
|
+
// Key events dispatch along the focused node's ancestor chain, leaf->root
|
|
344
|
+
// (the pointer bubbling contract), so a container hears keys from focused
|
|
345
|
+
// descendants and the window root hears everything: <window onKeyDown> is
|
|
346
|
+
// the app-global shortcut point. With nothing focused the path is the
|
|
347
|
+
// window root alone - key events are never dropped. The path is resolved
|
|
348
|
+
// at dispatch time from current focus (nothing to freeze: keyup follows
|
|
349
|
+
// focus, as in the DOM).
|
|
350
|
+
let dispatchKey = (raw: any, handler: string) => {
|
|
351
|
+
let target = focusedNode() ?? nodeId
|
|
352
|
+
let stopped = false
|
|
353
|
+
let e = { ...raw, target, stopPropagation: () => (stopped = true) }
|
|
354
|
+
let path = getNodePath(target)
|
|
355
|
+
// A focused node detached this tick has no chain to the root; the
|
|
356
|
+
// window root must still hear the key.
|
|
357
|
+
if (path[path.length - 1] !== nodeId) path.push(nodeId)
|
|
358
|
+
for (let id of path) {
|
|
359
|
+
e.currentTarget = id
|
|
360
|
+
getEventHandler(id, handler)?.(e)
|
|
361
|
+
if (stopped) break
|
|
339
362
|
}
|
|
340
|
-
}
|
|
363
|
+
}
|
|
341
364
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
getEventHandler(id, "onKeyUp")?.(e)
|
|
346
|
-
}
|
|
347
|
-
})
|
|
365
|
+
unsubKeyDown = on("keydown", (raw: any) => dispatchKey(raw, "onKeyDown"))
|
|
366
|
+
|
|
367
|
+
unsubKeyUp = on("keyup", (raw: any) => dispatchKey(raw, "onKeyUp"))
|
|
348
368
|
|
|
349
369
|
unsubBack = on("back", () => {
|
|
350
370
|
let prevented = false
|
|
@@ -361,7 +381,7 @@ export function attachWindow(_nodeId: number) {
|
|
|
361
381
|
})
|
|
362
382
|
|
|
363
383
|
unsubTextInput = on("textInput", (e: any) => {
|
|
364
|
-
let id =
|
|
384
|
+
let id = focusedNode()
|
|
365
385
|
if (id != null) {
|
|
366
386
|
getEventHandler(id, "onTextInput")?.(e)
|
|
367
387
|
}
|
|
@@ -386,6 +406,7 @@ export function attachWindow(_nodeId: number) {
|
|
|
386
406
|
})
|
|
387
407
|
|
|
388
408
|
onCleanup(() => {
|
|
409
|
+
setInterestRoot(null)
|
|
389
410
|
if (unsubscribe) unsubscribe()
|
|
390
411
|
if (unsubDown) unsubDown()
|
|
391
412
|
if (unsubUp) unsubUp()
|
package/examples/svg.tsx
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// <svg> draws a whole SVG *document* passed as a STRING in the `src` prop. This
|
|
2
|
-
// is not HTML: there are no per-element <rect>/<circle>/<path> JSX children to
|
|
3
|
-
// nest. You hand it the SVG source text and usvg parses it (CSS, transforms,
|
|
4
|
-
// defs/use, gradients, clips) into a flat path tree the element renders. So an
|
|
5
|
-
// SVG is a value (a string you import, fetch, or inline), not markup you author
|
|
6
|
-
// inline with JSX.
|
|
7
|
-
//
|
|
8
|
-
// width/height set the drawn box (percentages work too); a multi-color document
|
|
9
|
-
// keeps each shape's own fill, while a monochrome icon using stroke/fill
|
|
10
|
-
// "currentColor" is recolored by the host `color` prop. For per-shape authored
|
|
11
|
-
// or animated vector art, compose <d-path> instead of this document layer.
|
|
12
|
-
//
|
|
13
|
-
// Being a vector, an <svg> is resolution-independent: it stays crisp at any
|
|
14
|
-
// drawn size x displayScale(). Prefer it over a raster <texture> (image.tsx)
|
|
15
|
-
// whenever the render size is fluid or the display DPI varies.
|
|
16
|
-
//
|
|
17
|
-
// This is how you use an existing icon library (Lucide, Heroicons, Feather,
|
|
18
|
-
// Material, etc.): those ship SVG source, so import/inline the icon string and
|
|
19
|
-
// hand it to `src`. The `currentColor` convention they follow means the `color`
|
|
20
|
-
// prop recolors them, exactly as `currentColor` would in a browser.
|
|
21
|
-
import { render } from "@solidrt/core"
|
|
22
|
-
|
|
23
|
-
// Multi-color document: each shape carries its own fill, no host color needed.
|
|
24
|
-
const HOUSE = `
|
|
25
|
-
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
|
26
|
-
<rect x="20" y="45" width="60" height="45" fill="#457b9d"/>
|
|
27
|
-
<path d="M10 50 L50 15 L90 50 Z" fill="#e63946"/>
|
|
28
|
-
<rect x="42" y="62" width="16" height="28" fill="#f1faee"/>
|
|
29
|
-
<circle cx="50" cy="35" r="6" fill="#ffd166"/>
|
|
30
|
-
</svg>`
|
|
31
|
-
|
|
32
|
-
// Monochrome icon (Lucide arrow-right) drawn with currentColor, recolored below.
|
|
33
|
-
const ARROW = `
|
|
34
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
35
|
-
stroke-linecap="round" stroke-linejoin="round">
|
|
36
|
-
<path d="M5 12h14"/>
|
|
37
|
-
<path d="M12 5l7 7-7 7"/>
|
|
38
|
-
</svg>`
|
|
39
|
-
|
|
40
|
-
function App() {
|
|
41
|
-
return (
|
|
42
|
-
<window justifyContent="center" alignItems="center" flexDirection="row" gap={32}>
|
|
43
|
-
<svg width={120} height={120} src={HOUSE} />
|
|
44
|
-
<svg width={120} height={120} src={ARROW} color="#4f8cff" />
|
|
45
|
-
</window>
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
render(() => <App />)
|