@java-memory-playground/java-memory-playground 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2026 OpenPatch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,345 @@
1
+ # @java-memory-playground/java-memory-playground
2
+
3
+ React components behind the [Java Memory Playground](https://jmp.openpatch.org) —
4
+ interactive diagrams of the Java stack and heap.
5
+
6
+ Looking to embed the playground in a page that is not a React app? Use
7
+ [`@java-memory-playground/web-component`](../web-component) instead.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ npm install @java-memory-playground/java-memory-playground
13
+ ```
14
+
15
+ `react` and `react-dom` are peer dependencies.
16
+
17
+ ## Two playgrounds
18
+
19
+ `MemoryPlayground` is the student's: the whole diagram, every edit, and the steps
20
+ of a trace to walk through.
21
+
22
+ `MemoryPlaygroundEditor` is the teacher's: all of that, plus configuring classes
23
+ and options and authoring the steps.
24
+
25
+ ```tsx
26
+ import {
27
+ MemoryPlayground, // student
28
+ MemoryPlaygroundEditor, // teacher
29
+ } from "@java-memory-playground/java-memory-playground";
30
+ ```
31
+
32
+ Both take the same props. The split is about which tools are on screen, not
33
+ about what a student is allowed to touch — a student still builds objects,
34
+ connects references and runs the garbage collector.
35
+
36
+ ## Usage
37
+
38
+ ```tsx
39
+ import { MemoryPlayground } from "@java-memory-playground/java-memory-playground";
40
+ import "@java-memory-playground/java-memory-playground/index.css";
41
+
42
+ export function Example() {
43
+ return (
44
+ <div style={{ height: "600px" }}>
45
+ <MemoryPlayground
46
+ memory={{
47
+ klasses: { Node: { attributes: { next: "Node" } } },
48
+ objects: {},
49
+ variables: {},
50
+ methodCalls: {
51
+ 1: {
52
+ name: "App.main",
53
+ index: 0,
54
+ localVariables: {},
55
+ position: { x: 0, y: 0 },
56
+ },
57
+ },
58
+ }}
59
+ options={{ hideSidebar: true }}
60
+ language="de"
61
+ onChange={(memory) => console.log(memory)}
62
+ />
63
+ </div>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ## Props
69
+
70
+ | Prop | Type | Description |
71
+ | ------------- | ---------------------------- | -------------------------------------------------------------------------------------------- |
72
+ | `memory` | `Memory \| string` | The diagram, as an object or a JSON string. Omit it to keep whatever the store already holds. |
73
+ | `options` | `Partial<Memory["options"]>` | Overrides applied on top of `memory.options`. |
74
+ | `language` | `string` | `"en"`, `"de"`, or `"auto"` to follow the browser. Defaults to the browser language. |
75
+ | `persistence` | `boolean` | Mirror the diagram into `location.hash`. Defaults to the value set through `setPersistence`. |
76
+ | `keyBindings` | `Partial<KeyBindings>` | Overrides for the default keyboard shortcuts. |
77
+ | `step` | `number` | The step to show, zero based. Set it to drive the diagram from the page around it. |
78
+ | `onStepChange`| `(step: number) => void` | Called whenever the shown step changes. |
79
+ | `onChange` | `(memory: Memory) => void` | Called when the user presses **Save**. |
80
+ | `onEdit` | `(memory: Memory) => void` | Called on every edit, not only on Save. For a host that owns the file. |
81
+ | `mode` | `"view" \| "edit"` | Which tools to show. Prefer picking the component; this is what it sets. |
82
+
83
+ Every `MemoryPlayground` creates its own store, so several playgrounds can live
84
+ on the same page without sharing state.
85
+
86
+ ## State and saving
87
+
88
+ The diagram lives in the playground's store, not in React Flow's local state.
89
+ Every edit — dragging a node, connecting a reference, editing an attribute — is
90
+ in the store immediately, so nothing is lost by switching to the config view and
91
+ back, and when persistence is on the URL keeps up on its own.
92
+
93
+ **Save** is therefore a commit, not a rescue: it is what fires `onChange`, which
94
+ is how a host learns the user considers the diagram finished.
95
+
96
+ A host that owns the file and has a save of its own — the VS Code editor, say —
97
+ wants `onEdit` instead, which fires on every edit. Loading a `memory` prop is
98
+ not an edit, and neither is panning or zooming, so opening a diagram does not
99
+ mark it as changed.
100
+
101
+ ```tsx
102
+ <MemoryPlayground onEdit={(memory) => markDirty(memory)} />
103
+ ```
104
+
105
+ ## Steps
106
+
107
+ A diagram is a sequence of steps, which is what lets it show the stack doing the
108
+ thing that makes it a stack: a frame pushed on a call, popped on a return, and an
109
+ object turning into garbage the moment the last reference to it is overwritten.
110
+
111
+ A trace is authored by duplication — build a step, press **Add step**, and change
112
+ what the next line did. Node positions are shared across the whole diagram, so
113
+ dragging something moves it everywhere and the picture does not jump while
114
+ stepping through.
115
+
116
+ ```tsx
117
+ // Driving the diagram from the prose around it.
118
+ <MemoryPlayground step={step} onStepChange={setStep} />
119
+ ```
120
+
121
+ ### What a step changed
122
+
123
+ Walking a trace is only useful if you can see what moved, so each step marks
124
+ itself against the one before it: a green outline for what appeared, a dashed
125
+ amber one for what changed, and an amber reference for one that was assigned or
126
+ repointed. The first step of a story marks nothing, because nothing has happened
127
+ yet. Set `hideStepChanges` to turn the marking off.
128
+
129
+ `diffSteps` is exported if you want the same comparison elsewhere.
130
+
131
+ A diagram with one step is just a picture, and is still saved in the shape it
132
+ always had, so a link to a single diagram stays readable by older versions. Set
133
+ `hideSteps` to hide the bar entirely.
134
+
135
+ ### Exercises
136
+
137
+ A step can be marked as an exercise. The teacher authors it as the answer; a
138
+ student's playground starts them from the step before it and checks what they
139
+ build.
140
+
141
+ ```json
142
+ { "label": "insert at the head", "exercise": true, "objects": {}, "variables": {}, "methodCalls": {} }
143
+ ```
144
+
145
+ The check compares the *shape* reachable from each root — the named variables
146
+ and each frame's locals — not the addresses, because a student who allocates an
147
+ object gets whatever address the playground handed out. Building the right
148
+ diagram passes however it was built, and the report names the root that is
149
+ wrong rather than only saying no. `checkAgainst` and `canonicalRoots` are
150
+ exported if you want to run the comparison yourself.
151
+
152
+ Saving from a student's playground writes the exercise back as authored, not
153
+ their attempt, so a shared link stays the exercise.
154
+
155
+ ### Garbage collection
156
+
157
+ With `gcPrediction` on, the collector asks first: the student marks the objects
158
+ they think are unreachable, and the check scores them before sweeping. Reaching
159
+ for an answer before seeing it is where the learning is.
160
+
161
+ ### The call stack
162
+
163
+ Only the frame on top of the stack can return; the others say so rather than
164
+ hiding the button, because a call having to finish before the one below it
165
+ resumes is the lesson. Returning takes the frame's references with it, which is
166
+ what leaves an object unreachable for the garbage collector to find.
167
+
168
+ ## Classes from Java source
169
+
170
+ A teacher usually has the classes already — in a worksheet, in an IDE, on a
171
+ slide — so the config view takes them as Java rather than asking for them field
172
+ by field:
173
+
174
+ ```java
175
+ class Node {
176
+ int value;
177
+ Node next;
178
+ }
179
+ ```
180
+
181
+ Only the structure is read: class names, and the name and type of each field.
182
+ Method bodies are skipped whole, initialisers are dropped, and **nothing is
183
+ executed or interpreted** — the source describes the shape of the objects a
184
+ diagram will contain, not a program the playground runs. Comments, generics
185
+ (`List<Node>` is a `List`), qualified names (`java.lang.String` is a `String`),
186
+ arrays either way round (`int[] a` and `int a[]`), records, and several names in
187
+ one declaration are all understood.
188
+
189
+ Half-written source does not throw the classes away: what cannot be read is
190
+ reported above the editor and the last readable classes stay.
191
+
192
+ The **Class list** tab is the same classes as a table, for adding one field
193
+ without touching the source. `parseJavaClasses` and `toJavaSource` are exported.
194
+
195
+ ### What applying them costs
196
+
197
+ Classes belong to the whole diagram, so saving them reaches every step — pasting
198
+ a new file over the old one can delete what the objects were holding. Save says
199
+ what that is first, and only when there is something to say:
200
+
201
+ - a field an object no longer has room for, and the value or reference it held
202
+ - objects whose class is gone, which stay in the diagram but can never be made
203
+ again
204
+
205
+ A field nobody has typed into is empty and goes unmentioned, even though an
206
+ `int` shows a `0` and a `boolean` shows a box — those are what the field starts
207
+ out holding, not something to lose. Adding a field, or changing classes no
208
+ object uses, costs nothing and saves without asking: a dialog that always
209
+ appears is one nobody reads.
210
+
211
+ `klassImpact` computes the same report if you want it elsewhere, and
212
+ `defaultValueFor` is what it counts as empty.
213
+
214
+ ## Strings
215
+
216
+ A String is a reference type, so a String value lives on the heap like any other
217
+ object. Drawing each one would bury the point of a diagram that is about
218
+ something else, so they are collapsed into their owner by default and rendered
219
+ as an editable field in quotes.
220
+
221
+ ```tsx
222
+ // When the String is the lesson rather than the noise.
223
+ <MemoryPlayground options={{ inlineStrings: false }} />
224
+ ```
225
+
226
+ The collapsing is a display choice: the diagram stores the reference either way,
227
+ and a String value is an `Obj` with `klass === "String"` holding a `literal`.
228
+ Diagrams saved before Strings were modelled this way are converted when read, so
229
+ existing links keep working.
230
+
231
+ ## Density
232
+
233
+ A diagram is read as a whole — a list of five nodes, a stack three frames deep
234
+ — so padding inside a node is diagram that has to go somewhere else. The canvas
235
+ is packed tightly on purpose, and how tightly is four custom properties on the
236
+ container rather than a number repeated down the stylesheet:
237
+
238
+ ```css
239
+ .java-memory-playground {
240
+ --jmp-space: 4px;
241
+ --jmp-space-lg: 8px;
242
+ --jmp-radius: 6px;
243
+ --jmp-handle: 14px; /* also a drag target, so well above React Flow's 6px */
244
+ }
245
+ ```
246
+
247
+ Override them to loosen everything at once, for a projector at the back of a
248
+ room or a touch screen.
249
+
250
+ The palette, toolbar and step bar float over the canvas rather than sitting
251
+ beside it, so framing the diagram reserves the space each one occupies —
252
+ otherwise fitting the nodes edge to edge parks the first of them underneath a
253
+ panel. A hidden panel gives its side back.
254
+
255
+ ## Undo and redo
256
+
257
+ Undo/redo is backed by [zundo](https://github.com/charkour/zundo). Only the
258
+ diagram is undoable — opening a dialog, selecting a node or switching views does
259
+ not consume a step, and a single drag is one step rather than one per pixel.
260
+
261
+ ```tsx
262
+ import { useUndoRedo } from "@java-memory-playground/java-memory-playground";
263
+
264
+ // Inside a MemoryPlayground subtree:
265
+ const { undo, redo, canUndo, canRedo, clear } = useUndoRedo();
266
+ ```
267
+
268
+ ## Keyboard shortcuts
269
+
270
+ | Shortcut | Action |
271
+ | -------------- | -------------------------- |
272
+ | `Ctrl/Cmd + S` | Save |
273
+ | `Ctrl/Cmd + Z` | Undo |
274
+ | `Ctrl/Cmd + Y` | Redo |
275
+ | `Ctrl/Cmd + ,` | Toggle the config view (editor only) |
276
+ | `Ctrl/Cmd + +` | Zoom in |
277
+ | `Ctrl/Cmd + -` | Zoom out |
278
+ | `Ctrl/Cmd + 0` | Reset zoom |
279
+ | `Shift + 1` | Fit the diagram to the view |
280
+
281
+ **Download all steps** writes one image with every step under its label, which
282
+ is what a worksheet wants — exporting the step on screen gives you the last
283
+ picture instead. Both exports frame the whole diagram first and crop to it, so
284
+ the empty canvas and the floating panels stay out of the picture.
285
+
286
+ Edges carry their stroke as an inline style rather than taking it from the
287
+ stylesheet. Exporting deep-clones the edge SVG and drops anything a stylesheet
288
+ contributed, and a reference with no stroke is an invisible one.
289
+
290
+ Shortcuts are ignored while an input has focus. Override any of them with
291
+ `keyBindings`:
292
+
293
+ ```tsx
294
+ <MemoryPlayground keyBindings={{ save: { key: "e", ctrl: true } }} />
295
+ ```
296
+
297
+ ## Presets
298
+
299
+ `optionPresets` names the option combinations a course moves through — a teacher
300
+ picks one in the config view rather than remembering which flags belong to which
301
+ stage.
302
+
303
+ | Preset | What it is for |
304
+ | ------ | -------------- |
305
+ | `references` | Objects and the names that point at them. No stack, no steps. |
306
+ | `stack` | Method calls, so the stack and stepping come with them. |
307
+ | `everything` | Arrays, the garbage collector, and Strings as heap objects. |
308
+
309
+ ## Languages
310
+
311
+ English and German ship with the package. `language="auto"` (the default) picks
312
+ the browser language and falls back to English.
313
+
314
+ React Flow's own accessible text is translated too, through its
315
+ `ariaLabelConfig`. It ships those strings in English, so a German playground
316
+ used to announce "Zoom In" and "Press enter or space to select a node" beside
317
+ its own translated labels.
318
+
319
+ ```tsx
320
+ import { translations, getTranslations } from "@java-memory-playground/java-memory-playground";
321
+ ```
322
+
323
+ ## URL persistence
324
+
325
+ The standalone app keeps the whole diagram in `location.hash`, which is what
326
+ makes a diagram shareable as a link. That behaviour is off by default, because
327
+ an embedded playground must not take over the URL of the page hosting it. Turn
328
+ it on once during bootstrap:
329
+
330
+ ```tsx
331
+ import { setPersistence } from "@java-memory-playground/java-memory-playground";
332
+
333
+ setPersistence(true);
334
+ ```
335
+
336
+ Or per instance with the `persistence` prop. Writes are throttled and use
337
+ `history.replaceState`, so continuous syncing does not fill up the back button.
338
+
339
+ ## Development
340
+
341
+ ```sh
342
+ pnpm test # vitest
343
+ pnpm lint # tsc --noEmit
344
+ pnpm build # dist/index.js, dist/index.css and type declarations
345
+ ```
@@ -0,0 +1,8 @@
1
+ import { DataType } from "./memory";
2
+ interface ArrayCreationDialogProps {
3
+ onConfirm: (name: string, length: number, elementType: DataType) => void;
4
+ onCancel: () => void;
5
+ availableTypes: DataType[];
6
+ }
7
+ export declare const ArrayCreationDialog: ({ onConfirm, onCancel, availableTypes, }: ArrayCreationDialogProps) => import("react").JSX.Element;
8
+ export {};
@@ -0,0 +1,15 @@
1
+ import { Klasses } from "./memory";
2
+ import { Translations } from "./translations";
3
+ /**
4
+ * The classes of a diagram, written as Java.
5
+ *
6
+ * A teacher already has the classes in front of them — in a worksheet, in an
7
+ * IDE, on a slide — so pasting them beats rebuilding them field by field
8
+ * through dialogs. Only the structure is read: nothing here is executed, and
9
+ * method bodies are skipped whole.
10
+ */
11
+ export declare const ClassSource: ({ klasses, onChange, t, }: {
12
+ klasses: Klasses;
13
+ onChange: (klasses: Klasses) => void;
14
+ t: Translations;
15
+ }) => import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare const ConfigView: () => import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { ReactNode } from "react";
2
+ /**
3
+ * A yes/no dialog for a change that cannot be taken back by cancelling.
4
+ *
5
+ * The confirming button says what it does rather than "OK", so the choice can
6
+ * be read without reading the title again.
7
+ */
8
+ export declare function ConfirmDialog({ title, confirmLabel, cancelLabel, destructive, onConfirm, onCancel, children, }: {
9
+ title: string;
10
+ confirmLabel: string;
11
+ cancelLabel: string;
12
+ destructive?: boolean;
13
+ onConfirm: () => void;
14
+ onCancel: () => void;
15
+ children?: ReactNode;
16
+ }): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Edits the String object referenced from one attribute or local variable,
3
+ * showing it where the reference is rather than as its own box.
4
+ *
5
+ * Reads the store rather than React Flow, because while `inlineStrings` is on
6
+ * the String objects and their edges are deliberately left out of the drawing —
7
+ * they still exist, which is the whole point.
8
+ */
9
+ export declare function InlineString({ nodeId, handleId, readOnly, }: {
10
+ nodeId: string;
11
+ handleId: string;
12
+ readOnly?: boolean;
13
+ }): import("react").JSX.Element;
14
+ export default InlineString;
@@ -0,0 +1,7 @@
1
+ import { KeyBindings } from "./types";
2
+ interface KeyboardShortcutsProps {
3
+ keyBindings?: Partial<KeyBindings>;
4
+ }
5
+ export declare const defaultKeyBindings: KeyBindings;
6
+ export declare const KeyboardShortcuts: ({ keyBindings: customKeyBindings, }: KeyboardShortcutsProps) => null;
7
+ export default KeyboardShortcuts;
@@ -0,0 +1,74 @@
1
+ import "@xyflow/react/dist/style.css";
2
+ import "./index.css";
3
+ import { Memory } from "./memory";
4
+ import { PlaygroundMode } from "./store";
5
+ import { KeyBindings } from "./types";
6
+ export interface MemoryPlaygroundProps {
7
+ /**
8
+ * The diagram to show, either as a `Memory` object or as a JSON string.
9
+ * Omit it to keep whatever the store already holds — the standalone app
10
+ * restores that from the URL.
11
+ */
12
+ memory?: string | Memory;
13
+ /**
14
+ * Overrides for the diagram options, applied on top of the options that come
15
+ * with `memory`. Handy for hiding the sidebar or the garbage collector
16
+ * without rewriting the whole diagram.
17
+ */
18
+ options?: Partial<Memory["options"]>;
19
+ /**
20
+ * UI language: `"en"`, `"de"`, or `"auto"` to follow the browser. Defaults to
21
+ * the browser language.
22
+ */
23
+ language?: string;
24
+ /**
25
+ * Mirror the diagram into `location.hash`. Defaults to the value set through
26
+ * `setPersistence`, which is off unless a host opts in.
27
+ */
28
+ persistence?: boolean;
29
+ /** Overrides for the default keyboard shortcuts. */
30
+ keyBindings?: Partial<KeyBindings>;
31
+ /**
32
+ * The step to show, zero based. Set it to drive the diagram from the page
33
+ * around it — prose can walk a reader through a trace.
34
+ */
35
+ step?: number;
36
+ /** Called with the step index whenever the shown step changes. */
37
+ onStepChange?: (step: number) => void;
38
+ /**
39
+ * Who this playground is for. `view`, the default, is the student's: the whole
40
+ * diagram and every edit, but no class configuration and no step authoring.
41
+ * `edit` adds those. Prefer the `MemoryPlaygroundEditor` component, which is
42
+ * this with `mode` already set.
43
+ */
44
+ mode?: PlaygroundMode;
45
+ /**
46
+ * Called with the full memory whenever the user saves. The web component
47
+ * wrapper uses this to dispatch its `change` event.
48
+ */
49
+ onChange?: (memory: Memory) => void;
50
+ /**
51
+ * Called with the full memory on every edit — each drag, each value typed —
52
+ * rather than only when the user saves.
53
+ *
54
+ * A host that owns the file and has a save of its own, like an editor with a
55
+ * dirty marker, needs to hear about edits as they happen; a host that only
56
+ * wants the finished diagram wants `onChange`. Loading a new `memory` prop is
57
+ * not an edit, and neither is panning or zooming, though the viewport is
58
+ * written along with the next real edit.
59
+ */
60
+ onEdit?: (memory: Memory) => void;
61
+ }
62
+ /**
63
+ * A self-contained Java memory playground.
64
+ *
65
+ * Every instance gets its own store and React Flow provider, so a page can host
66
+ * several playgrounds side by side without them sharing state.
67
+ */
68
+ export declare function MemoryPlayground({ persistence, mode, ...props }: MemoryPlaygroundProps): import("react").JSX.Element;
69
+ /**
70
+ * The playground with the teacher's tools: everything a student can do, plus
71
+ * configuring classes and options and authoring the steps of a trace.
72
+ */
73
+ export declare function MemoryPlaygroundEditor(props: Omit<MemoryPlaygroundProps, "mode">): import("react").JSX.Element;
74
+ export default MemoryPlayground;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Where the help button goes: the documentation page served next to the app.
3
+ *
4
+ * Relative on purpose. Naming a domain here would be guessing at where any
5
+ * given playground is deployed, and a guess that is wrong is a help button
6
+ * that 404s.
7
+ */
8
+ export declare const DOCUMENTATION_URL = "documentation.html";
9
+ export declare const MemoryView: () => import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { Node, NodeProps } from "@xyflow/react";
2
+ import { MethodCall } from "./memory";
3
+ export type MethodCallNodeType = Node<MethodCall, "method-call">;
4
+ export declare function isMethodCallNode(node: Node): node is MethodCallNodeType;
5
+ declare function MethodCallNode({ id, data, onDeclareVariable, }: NodeProps<MethodCallNodeType> & {
6
+ onDeclareVariable?: (nodeId: string) => void;
7
+ }): import("react").JSX.Element;
8
+ export default MethodCallNode;
@@ -0,0 +1,7 @@
1
+ import { NodeProps, Node } from "@xyflow/react";
2
+ import { Obj } from "./memory";
3
+ import { CustomNodeType } from "./types";
4
+ export type ObjectNodeType = Node<Obj, "object">;
5
+ export declare function isObjectNode(node: CustomNodeType): node is ObjectNodeType;
6
+ declare function ObjectNode({ id, data }: NodeProps<ObjectNodeType>): import("react").JSX.Element;
7
+ export default ObjectNode;
@@ -0,0 +1,3 @@
1
+ import { Edge, EdgeProps } from '@xyflow/react';
2
+ export type ReferenceEdge = Edge<{}, "reference">;
3
+ export default function ReferenceEdge({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, markerEnd, }: EdgeProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { Memory } from "./memory";
2
+ interface DragGhostProps {
3
+ type: string | null;
4
+ }
5
+ export declare function DragGhost({ type }: DragGhostProps): import("react").JSX.Element | null;
6
+ export declare const Sidebar: ({ klasses, options, onNodeDrop, }: {
7
+ klasses: Memory["klasses"];
8
+ options: Memory["options"];
9
+ onNodeDrop: (nodeType: string, offsetX: number, offsetY: number) => void;
10
+ }) => import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,10 @@
1
+ interface SimpleInputDialogProps {
2
+ title: string;
3
+ label: string;
4
+ placeholder?: string;
5
+ initialValue?: string;
6
+ onConfirm: (value: string) => void;
7
+ onCancel: () => void;
8
+ }
9
+ export declare function SimpleInputDialog({ title, label, placeholder, initialValue, onConfirm, onCancel, }: SimpleInputDialogProps): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Walks through the steps of a diagram.
3
+ *
4
+ * A single-step diagram is just a picture, so the bar only appears once there
5
+ * is something to walk through — or as soon as the author adds a step.
6
+ */
7
+ export declare function StepBar({ editable }: {
8
+ editable?: boolean;
9
+ }): import("react").JSX.Element | null;
10
+ export default StepBar;
@@ -0,0 +1,5 @@
1
+ import { Node, NodeProps } from "@xyflow/react";
2
+ import { Variable } from "./memory";
3
+ export type VariableNode = Node<Variable, "variable">;
4
+ declare function VariableNode({ id, data }: NodeProps<VariableNode>): import("react").JSX.Element;
5
+ export default VariableNode;
@@ -0,0 +1,11 @@
1
+ import { ReactFlowProps } from "@xyflow/react";
2
+ import { Translations } from "./translations";
3
+ /**
4
+ * React Flow's own accessible text, in the playground's language.
5
+ *
6
+ * It ships these strings in English, so a German playground was announcing
7
+ * "Zoom In" and "Press enter or space to select a node" beside its own
8
+ * translated labels. `ariaLabelConfig` is the documented way to replace them,
9
+ * and every key it does not carry keeps React Flow's default.
10
+ */
11
+ export declare const ariaLabelsFor: (t: Translations) => NonNullable<ReactFlowProps["ariaLabelConfig"]>;
@@ -0,0 +1,19 @@
1
+ import { StoreStep } from "./store";
2
+ /**
3
+ * The canonical form of each root, keyed by the root's name.
4
+ *
5
+ * Comparing per root rather than as one string is what lets a check say which
6
+ * variable is wrong instead of only that something is.
7
+ */
8
+ export declare const canonicalRoots: (step: StoreStep) => Record<string, string>;
9
+ export type ExerciseResult = {
10
+ correct: boolean;
11
+ /** Roots the solution has that the attempt got right. */
12
+ matched: string[];
13
+ /** Roots whose shape differs, or that the attempt never created. */
14
+ wrong: string[];
15
+ /** Roots the attempt invented. */
16
+ extra: string[];
17
+ };
18
+ /** Compares a student's diagram with the one the exercise asks for. */
19
+ export declare const checkAgainst: (solution: StoreStep, attempt: StoreStep) => ExerciseResult;
@@ -0,0 +1,30 @@
1
+ import { Viewport } from "@xyflow/react";
2
+ import { CustomNodeType } from "./types";
3
+ /**
4
+ * A picture of the diagram alone, cropped to the nodes.
5
+ *
6
+ * The whole flow is photographed rather than just its viewport, because the
7
+ * arrowheads are SVG markers defined outside the viewport and a reference
8
+ * without its arrowhead has lost which way it points. The empty canvas around
9
+ * the diagram is then cropped away.
10
+ *
11
+ * Callers are expected to have framed the diagram first — anything scrolled out
12
+ * of view was never photographed and cannot be cropped back in.
13
+ */
14
+ export declare const captureDiagram: (flowElement: HTMLElement, nodes: CustomNodeType[], viewport: Viewport) => Promise<string | null>;
15
+ export declare const downloadStep: (flowElement: HTMLElement, nodes: CustomNodeType[], viewport: Viewport, name: string) => Promise<void>;
16
+ /**
17
+ * One image of a whole trace: every step stacked, each under its caption.
18
+ *
19
+ * A worksheet wants the sequence, not the last picture — which is what
20
+ * exporting the step on screen gives you.
21
+ */
22
+ export declare const downloadAllSteps: ({ stepCount, labelFor, showStep, captureNow, fileName, }: {
23
+ stepCount: number;
24
+ labelFor: (index: number) => string;
25
+ /** Puts a step on screen, framed and measured, ready to be photographed. */
26
+ showStep: (index: number) => Promise<void>;
27
+ /** Photographs the step now on screen. */
28
+ captureNow: () => Promise<string | null>;
29
+ fileName?: string;
30
+ }) => Promise<void>;
@@ -0,0 +1,13 @@
1
+ import { FitViewOptions } from "@xyflow/react";
2
+ import { Memory } from "./memory";
3
+ /**
4
+ * Room to leave around the diagram when framing it.
5
+ *
6
+ * The palette, the toolbar and the step bar are drawn on top of the canvas
7
+ * rather than beside it, so framing the nodes edge to edge parks them
8
+ * underneath a panel — which is how the first frame of the default diagram
9
+ * used to lose its name. Each side is only reserved when something is
10
+ * actually floating there, so a diagram with the palette hidden gets its
11
+ * width back.
12
+ */
13
+ export declare const fitPaddingFor: (options: Memory["options"]) => NonNullable<FitViewOptions["padding"]>;