@markgrafhq/markgraf-react 0.0.41 → 0.0.44

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/README.md CHANGED
@@ -27,7 +27,7 @@ when a main-thread SVG player is required.
27
27
 
28
28
  ## `<MarkgrafPlayer src=... />`
29
29
 
30
- Headless component that draws the scene directly into the selected Canvas2D, SVG, or WebGL surface. Bring your own controls.
30
+ Headless component that draws the scene directly into the selected Canvas2D or SVG surface. Bring your own controls.
31
31
 
32
32
  ```jsx
33
33
  import { MarkgrafPlayer } from "@markgrafhq/markgraf-react";
@@ -43,7 +43,6 @@ scene v1 {
43
43
  export default function App() {
44
44
  return <MarkgrafPlayer src={src} />;
45
45
  // Or: <MarkgrafPlayer src={src} renderer="svg" />
46
- // Or: <MarkgrafPlayer src={src} renderer="sdf" />
47
46
  }
48
47
  ```
49
48
 
@@ -84,11 +83,58 @@ export function Player({ src }) {
84
83
  }
85
84
  ```
86
85
 
86
+ ### Live node views
87
+
88
+ Canvas and SVG players can replace a node front face with live React content.
89
+ Address a node by its DSL ID and, for nested `inside` graphs, by the ancestor
90
+ node IDs from outermost to innermost. The path contains ancestors only: a
91
+ root-level `api` uses `{ node: "api" }`; `api` inside `system` uses
92
+ `{ node: "api", path: ["system"] }`.
93
+
94
+ Keep the native surface and `api.viewLayer` as siblings in one stable
95
+ `position: relative` wrapper. Render the layer for the whole player lifetime,
96
+ including when the current array is empty, so adding or removing registrations
97
+ does not recreate a transferred canvas:
98
+
99
+ ```jsx
100
+ function Player({ src, connected }) {
101
+ const api = useMarkgraf(src, {
102
+ renderer: "svg",
103
+ nodeViews: [
104
+ {
105
+ node: "api",
106
+ path: ["system"],
107
+ content: <button onClick={() => alert("live")}>{connected ? "Online" : "Offline"}</button>,
108
+ },
109
+ ],
110
+ });
111
+
112
+ return (
113
+ <div style={{ position: "relative", width: 640, height: 360 }}>
114
+ <svg ref={api.elementRef} style={{ width: "100%", height: "100%" }} />
115
+ {api.viewLayer}
116
+ </div>
117
+ );
118
+ }
119
+ ```
120
+
121
+ The content box fills the node's declared logical width and height, rather
122
+ than its projected screen bounds. A registered face keeps its native outline;
123
+ an unregistered face retains its ordinary label fallback. Content stays live
124
+ (it is not a screenshot or frozen subtree), preserving React context and
125
+ component state across seeks, dives, reverse travel, and fresh JSX at the same
126
+ address. During native travel, miniature/background modes, and the
127
+ reverse-facing side it is inert; the reverse face is an opaque blank native
128
+ back, so neither the label nor a child view shows through. Flat Canvas/SVG
129
+ themes support node views; isometric themes and static exports retain the DSL
130
+ representation. SDF/WebGL does not support node views.
131
+
87
132
  ### Returned API
88
133
 
89
134
  | Field | Type | Notes |
90
135
  | -------------- | ------------------------------------- | -------------------------------------------------- |
91
136
  | `elementRef` | `Ref<HTMLCanvasElement \| SVGSVGElement>` | attach to a `<canvas>` (default) or `<svg>` |
137
+ | `viewLayer` | `ReactNode` | render beside `elementRef` in the same stable relative wrapper |
92
138
  | `time` | `number` | seconds, updates each animation frame |
93
139
  | `keyframe` | `string` | name of the current scene span |
94
140
  | `playing` | `boolean` | |
@@ -182,9 +228,10 @@ All seek methods retain the existing playing/paused state; a seek is not a pause
182
228
 
183
229
  ### Renderer choice
184
230
 
185
- - **`canvas`** (default) — Canvas2D with DPR-aware scaling and label springs. Fastest, best for many tokens.
231
+ - **`canvas`** (default) — Canvas2D with DPR-aware scaling and label springs.
186
232
  - **`svg`** — Inline SVG. Easier to inspect/style, scales crisply at any zoom, no DPR concerns. No spring labels.
187
- - **`sdf`** (alias **`webgl`**) — WebGL raymarched 3D rendering; available through `MarkgrafPlayer` only.
233
+
234
+ For readable diagrams, follow the [graph authoring guidance](https://github.com/i-am-the-slime/markgraf#writing-graphs-people-can-follow).
188
235
 
189
236
  ## License
190
237
 
@@ -1,7 +1,7 @@
1
1
  // Type definitions for @markgrafhq/markgraf-react
2
2
  // Hand-written — the underlying implementation is compiled from PureScript.
3
3
 
4
- import type { FC, MutableRefObject } from "react";
4
+ import type { FC, MutableRefObject, ReactNode } from "react";
5
5
 
6
6
  export type MarkgrafCueKind = "step" | "tokenLine";
7
7
  export type MarkgrafPlaybackDirection = "auto" | "forward" | "backward";
@@ -76,10 +76,15 @@ export interface MarkgrafCompleteEvent {
76
76
  */
77
77
  export interface MarkgrafApi<E extends Element = HTMLCanvasElement> {
78
78
  /**
79
- * Attach to a `<canvas>` (default) or `<svg>` element. The player
80
- * draws directly into the element — no wrapper div.
79
+ * Attach to a `<canvas>` (default) or `<svg>` element. The player draws
80
+ * directly into the element.
81
81
  */
82
82
  readonly elementRef: MutableRefObject<E | null>;
83
+ /**
84
+ * Render adjacent to `elementRef` inside a stable `position: relative`
85
+ * wrapper. It owns the portal targets for registered node views.
86
+ */
87
+ readonly viewLayer: ReactNode;
83
88
  /** Seconds since the start of the animation. */
84
89
  readonly time: number;
85
90
  /** Name of the scene span currently being rendered. Empty before `ready`. */
@@ -110,8 +115,18 @@ export interface MarkgrafApi<E extends Element = HTMLCanvasElement> {
110
115
  onComplete(callback: (event: MarkgrafCompleteEvent) => void): () => void;
111
116
  }
112
117
 
118
+ /**
119
+ * Live React content registered to one logical diagram node. `path` is the
120
+ * ancestor-node address, outermost first; omit it for a root-level node.
121
+ */
122
+ export interface MarkgrafNodeView {
123
+ node: string;
124
+ path?: string[];
125
+ content: ReactNode;
126
+ }
127
+
113
128
  export interface UseMarkgrafOptions<R extends "canvas" | "svg" = "canvas"> {
114
- /** `"canvas"` (default) or `"svg"`. Determines the type of `elementRef`. */
129
+ /** `"canvas"` (default) or `"svg"`. Determines the type of `elementRef`. */
115
130
  renderer?: R;
116
131
  /** Visual theme. `"light"` (default), `"dark"`, or `"blueprint"`. */
117
132
  theme?: "light" | "dark" | "blueprint";
@@ -119,21 +134,31 @@ export interface UseMarkgrafOptions<R extends "canvas" | "svg" = "canvas"> {
119
134
  transparent?: boolean;
120
135
  /** When `true`, the player holds on its current frame; `false` resumes. */
121
136
  paused?: boolean;
137
+ /** Live React content for selected Canvas/SVG node front faces. */
138
+ nodeViews?: readonly MarkgrafNodeView[];
122
139
  }
123
140
 
124
141
  /**
125
- * Mount a markgraf player and drive it imperatively.
142
+ * Mount a markgraf player and drive it imperatively. To use `nodeViews`, keep
143
+ * the canvas/SVG and returned `viewLayer` as siblings in one stable relative
144
+ * wrapper:
126
145
  *
127
146
  * ```tsx
128
- * // Canvas (default)
129
- * const api = useMarkgraf(src);
130
- * return <canvas ref={api.elementRef} />;
131
- *
132
- * // SVG
133
- * const api = useMarkgraf(src, { renderer: "svg" });
134
- * return <svg ref={api.elementRef} />;
147
+ * const api = useMarkgraf(src, {
148
+ * nodeViews: [{ node: "api", path: ["system"], content: <Status /> }],
149
+ * });
150
+ * return <div style={{ position: "relative" }}>
151
+ * <canvas ref={api.elementRef} />
152
+ * {api.viewLayer}
153
+ * </div>;
135
154
  * ```
136
155
  *
156
+ * The host box fills the node's declared logical width and height. Registered
157
+ * faces retain their native outline while unregistered faces retain their
158
+ * ordinary label fallback. Host content remains live rather than frozen and is
159
+ * inert while travelling or backgrounded. The native reverse-facing back is
160
+ * opaque and blank.
161
+ *
137
162
  * When `src` or `renderer` changes the player is torn down and re-mounted.
138
163
  */
139
164
  export function useMarkgraf(src: string): MarkgrafApi<HTMLCanvasElement>;
@@ -146,28 +171,31 @@ export function useMarkgraf(
146
171
  opts: UseMarkgrafOptions<"svg">,
147
172
  ): MarkgrafApi<SVGSVGElement>;
148
173
 
149
- export interface MarkgrafPlayerProps {
150
- src: string;
151
- /**
152
- * `"canvas"` (default), `"svg"`, or `"sdf"` (alias `"webgl"`) — the WebGL
153
- * raymarched 3D renderer. The SDF renderer is self-driving: it ignores
154
- * `width`/`height` (it fills its container) and isn't available through the
155
- * lower-level `useMarkgraf` hook.
156
- */
157
- renderer?: "canvas" | "svg" | "sdf" | "webgl";
158
- /** Visual theme. `"light"` (default), `"dark"`, or `"blueprint"`. */
159
- theme?: "light" | "dark" | "blueprint";
160
- /** When `true`, skip the background fill so the page bg shows through. */
161
- transparent?: boolean;
162
- width?: number;
163
- height?: number;
164
- /** When `true`, the player holds on its current frame; `false` resumes. */
165
- paused?: boolean;
166
- }
174
+ export type MarkgrafPlayerProps =
175
+ | {
176
+ src: string;
177
+ renderer?: "canvas" | "svg";
178
+ theme?: "light" | "dark" | "blueprint";
179
+ transparent?: boolean;
180
+ width?: number;
181
+ height?: number;
182
+ paused?: boolean;
183
+ nodeViews?: readonly MarkgrafNodeView[];
184
+ }
185
+ | {
186
+ src: string;
187
+ renderer: "sdf" | "webgl";
188
+ theme?: "light" | "dark" | "blueprint";
189
+ transparent?: boolean;
190
+ width?: number;
191
+ height?: number;
192
+ paused?: boolean;
193
+ nodeViews?: never;
194
+ };
167
195
 
168
196
  /**
169
- * Minimal player component — renders the canvas or svg element directly with
170
- * the markgraf scene drawn into it. Bring your own controls via
171
- * `useMarkgraf` for anything richer.
197
+ * Minimal Canvas/SVG player. When `nodeViews` are supplied it owns the stable
198
+ * relative wrapper and overlay layer automatically. SDF/WebGL intentionally
199
+ * reject host views.
172
200
  */
173
201
  export const MarkgrafPlayer: FC<MarkgrafPlayerProps>;