flowsvg 0.1.0 → 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/CHANGELOG.md +54 -1
- package/PATTERNS.md +71 -4
- package/README.md +148 -438
- package/README.zh-CN.md +148 -438
- package/dist/adapters/react.d.ts +55 -0
- package/dist/adapters/react.js +138 -0
- package/dist/adapters/vue.d.ts +158 -0
- package/dist/adapters/vue.js +177 -0
- package/dist/adapters/web-component.d.ts +111 -0
- package/dist/adapters/web-component.js +425 -0
- package/dist/{player-controls.d.ts → browser/controls.d.ts} +7 -3
- package/dist/{player-controls.js → browser/controls.js} +23 -31
- package/dist/browser/export-controls.d.ts +32 -0
- package/dist/browser/export-controls.js +151 -0
- package/dist/browser/player.d.ts +77 -0
- package/dist/browser/player.js +412 -0
- package/dist/browser/select-control.d.ts +33 -0
- package/dist/browser/select-control.js +171 -0
- package/dist/browser.d.ts +5 -69
- package/dist/browser.js +3 -353
- package/dist/core/color.d.ts +3 -0
- package/dist/core/color.js +18 -0
- package/dist/core/diagram.d.ts +40 -0
- package/dist/core/diagram.js +481 -0
- package/dist/core/edge-geometry.d.ts +45 -0
- package/dist/core/edge-geometry.js +609 -0
- package/dist/{core.d.ts → core/model.d.ts} +37 -61
- package/dist/core/model.js +1 -0
- package/dist/core/node-content-measurement.d.ts +34 -0
- package/dist/core/node-content-measurement.js +66 -0
- package/dist/{node-content-paint.d.ts → core/node-content-paint.d.ts} +2 -2
- package/dist/{node-content-paint.js → core/node-content-paint.js} +11 -20
- package/dist/{node-content.d.ts → core/node-content.d.ts} +18 -1
- package/dist/{node-content.js → core/node-content.js} +30 -6
- package/dist/core/svg-number.d.ts +5 -0
- package/dist/core/svg-number.js +7 -0
- package/dist/{theme.d.ts → core/theme.d.ts} +6 -0
- package/dist/core/theme.js +17 -0
- package/dist/{timeline.d.ts → core/timeline.d.ts} +11 -5
- package/dist/{timeline.js → core/timeline.js} +30 -22
- package/dist/core/validation.d.ts +11 -0
- package/dist/core/validation.js +399 -0
- package/dist/export/browser.d.ts +9 -0
- package/dist/export/browser.js +172 -0
- package/dist/export/captions.d.ts +18 -0
- package/dist/export/captions.js +44 -0
- package/dist/export/plan.d.ts +25 -0
- package/dist/export/plan.js +94 -0
- package/dist/export/types.d.ts +64 -0
- package/dist/export/types.js +9 -0
- package/dist/export/worker-source.d.ts +1 -0
- package/dist/export/worker-source.js +1 -0
- package/dist/export.d.ts +3 -0
- package/dist/export.js +2 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +3 -1
- package/dist/node/cli.d.ts +2 -0
- package/dist/node/cli.js +72 -0
- package/dist/node/gif.d.ts +16 -0
- package/dist/node/gif.js +184 -0
- package/dist/node/html.d.ts +19 -0
- package/dist/node/html.js +61 -0
- package/dist/node/output.d.ts +2 -0
- package/dist/node/output.js +22 -0
- package/dist/node.d.ts +6 -0
- package/dist/node.js +3 -0
- package/dist/portable/LICENSE.esbuild.txt +21 -0
- package/dist/portable/LICENSE.flowsvg.txt +21 -0
- package/dist/portable/LICENSE.gifenc.txt +21 -0
- package/dist/portable/export.js +5 -0
- package/dist/portable/manifest.json +36 -0
- package/dist/portable/player.js +2 -0
- package/dist/portable/styles.css +227 -0
- package/dist/react.d.ts +3 -48
- package/dist/react.js +2 -126
- package/dist/styles.css +194 -14
- package/dist/vue.d.ts +3 -132
- package/dist/vue.js +2 -161
- package/dist/web-component.d.ts +3 -103
- package/dist/web-component.js +2 -396
- package/docs/images/entity-graph.png +0 -0
- package/docs/images/facts-timeline.png +0 -0
- package/docs/images/facts-timeline.zh-CN.png +0 -0
- package/docs/images/flowsvg-wordmark.gif +0 -0
- package/docs/images/node-content.png +0 -0
- package/docs/images/numbered-list.png +0 -0
- package/docs/images/numbered-list.zh-CN.png +0 -0
- package/docs/images/quick-start.png +0 -0
- package/docs/images/record-tags.png +0 -0
- package/package.json +38 -6
- package/dist/core.js +0 -1149
- package/dist/theme.js +0 -6
- /package/dist/{player-accessibility.d.ts → browser/accessibility.d.ts} +0 -0
- /package/dist/{player-accessibility.js → browser/accessibility.js} +0 -0
- /package/dist/{player-lifecycle.d.ts → browser/lifecycle.d.ts} +0 -0
- /package/dist/{player-lifecycle.js → browser/lifecycle.js} +0 -0
- /package/dist/{svg-dom.d.ts → browser/svg-dom.d.ts} +0 -0
- /package/dist/{svg-dom.js → browser/svg-dom.js} +0 -0
package/README.md
CHANGED
|
@@ -1,525 +1,235 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://cdn.jsdelivr.net/npm/flowsvg@0.2.0/docs/images/flowsvg-wordmark.gif" alt="FlowSVG" width="280">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p align="center">Declarative SVG diagrams with timeline playback and SVG/GIF export.</p>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center">English | <a href="https://cdn.jsdelivr.net/npm/flowsvg@0.2.0/README.zh-CN.md">简体中文</a></p>
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="#define-a-diagram">Quick start</a> ·
|
|
11
|
+
<a href="#control-playback">Playback API</a> ·
|
|
12
|
+
<a href="#core-api--flowsvg">Core API</a> ·
|
|
13
|
+
<a href="#document-and-actions">Document model</a> ·
|
|
14
|
+
<a href="#export-api">Export API</a>
|
|
15
|
+
</p>
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## Installation
|
|
12
|
-
|
|
13
|
-
Install from npm:
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
pnpm add flowsvg
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Install only the framework for your chosen entry:
|
|
20
|
-
|
|
21
|
-
```sh
|
|
22
|
-
# Vue applications
|
|
23
|
-
pnpm add vue
|
|
24
|
-
# React applications
|
|
25
|
-
pnpm add react react-dom
|
|
26
|
-
# React TypeScript applications
|
|
27
|
-
pnpm add -D @types/react @types/react-dom
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
The core and native browser entries require neither framework. To build a local tarball before publication, run from the repository root:
|
|
17
|
+
Define the data once and use it in native browser, Vue, React, or Web Component integrations.
|
|
31
18
|
|
|
32
19
|
```sh
|
|
33
|
-
|
|
34
|
-
pnpm --filter flowsvg exec npm pack --pack-destination /tmp
|
|
20
|
+
npm install flowsvg
|
|
35
21
|
```
|
|
36
22
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
The core requires Node.js 18+ or an environment supporting ES2022 ESM. Browser playback requires a DOM, SVG, and `requestAnimationFrame`. Minimum framework versions are Vue 3.5 and React 18. TypeScript declarations are included; React TypeScript applications also need the matching `@types/react` and `@types/react-dom` packages.
|
|
40
|
-
|
|
41
|
-
| Import path | Purpose |
|
|
42
|
-
| --- | --- |
|
|
43
|
-
| `flowsvg` | DOM-free document validation, compilation, evaluation, hit testing, and SVG export |
|
|
44
|
-
| `flowsvg/browser` | Native browser player |
|
|
45
|
-
| `flowsvg/vue` | Vue component and handle types |
|
|
46
|
-
| `flowsvg/react` | React component and ref types |
|
|
47
|
-
| `flowsvg/web-component` | Explicit custom element registration; also available as `flowsvg/element` |
|
|
48
|
-
| `flowsvg/styles.css` | Control layout and visible focus styles |
|
|
49
|
-
|
|
50
|
-
## Quick start
|
|
51
|
-
|
|
52
|
-
### 1. Create a diagram document
|
|
53
|
-
|
|
54
|
-
Save this as `diagram.ts`. The integration examples below reuse this file:
|
|
23
|
+
## Define a diagram
|
|
55
24
|
|
|
56
25
|
```ts
|
|
26
|
+
// diagram.ts
|
|
57
27
|
import type { DiagramDocument } from "flowsvg";
|
|
58
28
|
|
|
59
|
-
export const
|
|
29
|
+
export const diagram: DiagramDocument = {
|
|
60
30
|
version: 1,
|
|
61
|
-
|
|
62
|
-
viewport: { width: 640, height: 280 },
|
|
31
|
+
viewport: { width: 640, height: 240 },
|
|
63
32
|
nodes: [
|
|
64
|
-
{ id: "client", kind: "roundedRect", x: 40, y:
|
|
65
|
-
{ id: "service", kind: "
|
|
33
|
+
{ id: "client", kind: "roundedRect", x: 40, y: 90, width: 140, height: 64, label: "Client" },
|
|
34
|
+
{ id: "service", kind: "roundedRect", x: 400, y: 90, width: 180, height: 64, label: "Service" }
|
|
66
35
|
],
|
|
67
|
-
edges: [{ id: "request", from: "client", to: "service",
|
|
36
|
+
edges: [{ id: "request", from: "client", to: "service", route: "orthogonal" }],
|
|
68
37
|
scenes: [{
|
|
69
|
-
id: "request-
|
|
70
|
-
label: "Request
|
|
38
|
+
id: "request-response",
|
|
39
|
+
label: "Request and response",
|
|
71
40
|
beats: [
|
|
72
|
-
{ durationMs: 1000,
|
|
73
|
-
|
|
41
|
+
{ durationMs: 1000, caption: "Send request", actions: [
|
|
42
|
+
{ type: "flow", target: "request", direction: "forward", text: "Request" }
|
|
43
|
+
] },
|
|
44
|
+
{ durationMs: 1000, caption: "Return response", actions: [
|
|
45
|
+
{ type: "flow", target: "request", direction: "reverse" }
|
|
46
|
+
] }
|
|
74
47
|
]
|
|
75
48
|
}]
|
|
76
49
|
};
|
|
77
50
|
```
|
|
78
51
|
|
|
79
|
-
|
|
52
|
+
Nodes use explicit positions and dimensions. Routes are `straight`, `orthogonal` (rounded bends), or `curved`. Flow text produces a capsule; omitting text produces a moving dot.
|
|
53
|
+
|
|
54
|
+
## Play
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<div id="diagram"></div>
|
|
58
|
+
```
|
|
80
59
|
|
|
81
60
|
```ts
|
|
82
|
-
import {
|
|
83
|
-
import
|
|
61
|
+
import { createPlayer } from "flowsvg/browser";
|
|
62
|
+
import "flowsvg/styles.css";
|
|
63
|
+
import { diagram } from "./diagram.js";
|
|
84
64
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
65
|
+
const player = createPlayer(document.querySelector<HTMLElement>("#diagram")!, diagram, {
|
|
66
|
+
theme: "light",
|
|
67
|
+
loop: true,
|
|
68
|
+
gifExport: true
|
|
69
|
+
});
|
|
88
70
|
|
|
89
|
-
|
|
90
|
-
|
|
71
|
+
player.play();
|
|
72
|
+
// On disposal: player.destroy();
|
|
91
73
|
```
|
|
92
74
|
|
|
93
|
-
|
|
75
|
+
Players start paused, with playback controls enabled. Loop and GIF downloads default off. Import the CSS once. Call `destroy()` when disposing of a native player; framework components handle cleanup.
|
|
94
76
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Create a component in your Vue application, with `diagram.ts` in the same directory:
|
|
77
|
+
### Vue
|
|
98
78
|
|
|
99
79
|
```vue
|
|
100
80
|
<script setup lang="ts">
|
|
101
|
-
import {
|
|
102
|
-
import { FlowPlayer, type FlowPlayerHandle } from "flowsvg/vue";
|
|
103
|
-
import type { PlayerErrorDetail } from "flowsvg/browser";
|
|
81
|
+
import { FlowPlayer } from "flowsvg/vue";
|
|
104
82
|
import "flowsvg/styles.css";
|
|
105
|
-
import {
|
|
106
|
-
|
|
107
|
-
const player = ref<FlowPlayerHandle | null>(null);
|
|
108
|
-
const timeMs = ref(0);
|
|
109
|
-
|
|
110
|
-
function handleError({ error }: PlayerErrorDetail): void {
|
|
111
|
-
console.error(error);
|
|
112
|
-
}
|
|
83
|
+
import { diagram } from "./diagram.js";
|
|
113
84
|
</script>
|
|
114
85
|
|
|
115
86
|
<template>
|
|
116
|
-
<FlowPlayer
|
|
117
|
-
ref="player"
|
|
118
|
-
:document="diagramDocument"
|
|
119
|
-
:speed="1.25"
|
|
120
|
-
theme="dark"
|
|
121
|
-
@timeupdate="timeMs = $event.timeMs"
|
|
122
|
-
@error="handleError"
|
|
123
|
-
/>
|
|
124
|
-
<button type="button" @click="player?.play()">Play</button>
|
|
125
|
-
<button type="button" @click="player?.seek(0)">Back to start</button>
|
|
126
|
-
<output>{{ timeMs }} ms</output>
|
|
87
|
+
<FlowPlayer :document="diagram" loop gif-export />
|
|
127
88
|
</template>
|
|
128
89
|
```
|
|
129
90
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
## React integration
|
|
91
|
+
### React
|
|
133
92
|
|
|
134
93
|
```tsx
|
|
135
|
-
import {
|
|
136
|
-
import { FlowPlayer, type FlowPlayerHandle } from "flowsvg/react";
|
|
94
|
+
import { FlowPlayer } from "flowsvg/react";
|
|
137
95
|
import "flowsvg/styles.css";
|
|
138
|
-
import {
|
|
139
|
-
|
|
140
|
-
export
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<section>
|
|
145
|
-
<FlowPlayer
|
|
146
|
-
ref={player}
|
|
147
|
-
document={diagramDocument}
|
|
148
|
-
speed={1.25}
|
|
149
|
-
theme="dark"
|
|
150
|
-
onError={({ error }) => console.error(error)}
|
|
151
|
-
/>
|
|
152
|
-
<button type="button" onClick={() => player.current?.play()}>Play</button>
|
|
153
|
-
<button type="button" onClick={() => player.current?.seek(0)}>Back to start</button>
|
|
154
|
-
</section>
|
|
155
|
-
);
|
|
96
|
+
import { diagram } from "./diagram.js";
|
|
97
|
+
|
|
98
|
+
export function Diagram() {
|
|
99
|
+
return <FlowPlayer document={diagram} loop gifExport />;
|
|
156
100
|
}
|
|
157
101
|
```
|
|
158
102
|
|
|
159
|
-
|
|
103
|
+
## Control playback
|
|
160
104
|
|
|
161
|
-
|
|
105
|
+
`createPlayer(host: HTMLElement, document: DiagramDocument, options?: PlayerOptions): FlowPlayer` mounts into the host, replacing its children. Invalid initial documents throw `DiagramValidationError`.
|
|
162
106
|
|
|
163
|
-
|
|
|
107
|
+
| Method / property | Behavior |
|
|
164
108
|
| --- | --- |
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
|
|
174
|
-
Changing `theme`, `controls`, `reducedMotion`, or `hoverFill` recreates the player, paused at zero; `initialTimeMs` is not reapplied. Provide a new object reference when changing the document. The ref / exposed handle provides the seven control and state methods in the Player API below; the component owns cleanup. Calling a retained handle before mounting or after unmounting throws a not-mounted error.
|
|
175
|
-
|
|
176
|
-
Both framework entries are safe to import on the server. SSR renders a host container, and the player is created after client mounting. Use the core `renderSvg` for a server-rendered diagram preview.
|
|
109
|
+
| `play(): void` / `pause(): void` | Resume / pause at the current position. At the end, `play()` restarts only when Loop is enabled; static diagrams cannot play. |
|
|
110
|
+
| `seek(timeMs: number): void` | Clamp to the timeline; retain playback except at the end. Non-finite values become zero. |
|
|
111
|
+
| `step(direction: 1 \| -1): void` | Pause and move to a beat boundary. Backward from inside a beat goes to that beat's start. |
|
|
112
|
+
| `setSpeed(speed: number): void` | Set a finite positive multiplier; ignore invalid values. Default `1`. |
|
|
113
|
+
| `setLoop(loop: boolean): void` | Change whole-timeline looping without changing position or playback. |
|
|
114
|
+
| `load(document: DiagramDocument): void` | Pause at zero on success; emit `error` and retain the old document/state on failure. |
|
|
115
|
+
| `getState(): PlayerState` | Read `{ timeMs, totalDuration, playing, loop, speed, frame }`. Times are milliseconds. |
|
|
116
|
+
| `destroy(): void` | Release DOM, listeners and animation resources. Safe to repeat. Not exposed by Vue/React refs. |
|
|
117
|
+
| `element: HTMLElement` | Readonly host and event target. Not exposed by Vue/React refs. |
|
|
177
118
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
Add a container to your page:
|
|
181
|
-
|
|
182
|
-
```html
|
|
183
|
-
<div id="diagram" style="width: 100%; height: 360px;"></div>
|
|
184
|
-
```
|
|
119
|
+
Vue/React refs expose the eight playback/state methods above. Replace the `document` object to reload a component. Changes to `theme`, `controls`, `gifExport`, `reducedMotion`, or `hoverFill` recreate the player paused at zero.
|
|
185
120
|
|
|
186
|
-
|
|
121
|
+
### Options and component props
|
|
187
122
|
|
|
188
|
-
|
|
189
|
-
import { createPlayer } from "flowsvg/browser";
|
|
190
|
-
import "flowsvg/styles.css";
|
|
191
|
-
import { diagramDocument } from "./diagram.js";
|
|
192
|
-
|
|
193
|
-
const host = document.querySelector<HTMLElement>("#diagram");
|
|
194
|
-
if (!host) throw new Error("Diagram host is missing");
|
|
195
|
-
|
|
196
|
-
const player = createPlayer(host, diagramDocument, { theme: "light" });
|
|
197
|
-
player.play();
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
`createPlayer` takes over the container and replaces its children. Call `player.destroy()` when the page or component is disposed, and destroy the old instance before creating another player in the same container. Bare imports in plain HTML need an import map or application build tooling; the repository's native example includes an import map.
|
|
201
|
-
|
|
202
|
-
The repository example at `apps/native-example/` keeps the camera and layout fixed throughout an order request and response. At 0–2400 ms, three curved edges carry a dot, a `Token` capsule, and a reverse precheck capsule. At 2400–4800 ms, two other curves carry the order request and inventory confirmation. At 4800–7200 ms, response messages return to the gateway and client; the final beat ends at 8200 ms with no temporary highlights. Opposite relationship edges remain separate, and hovering a participant adds only a browser highlight.
|
|
203
|
-
|
|
204
|
-
Reuse `apps/native-example/request-chain.js` as the `document` input for native, Web Component, React, or Vue integration. Each host uses the same `seek(timeMs)`, `setSpeed(speed)`, `play()`, and `pause()` behavior. `src/business-integration.test.ts` verifies this shared example through all four public entries, including both themes, reduced motion, beat boundaries, and core SVG equality.
|
|
205
|
-
|
|
206
|
-
## Web Component
|
|
207
|
-
|
|
208
|
-
Place the element in HTML, then register it explicitly from a client-side module:
|
|
209
|
-
|
|
210
|
-
```html
|
|
211
|
-
<flowsvg-player controls="false"></flowsvg-player>
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
```ts
|
|
215
|
-
import { defineFlowPlayer, type FlowPlayerElement } from "flowsvg/web-component";
|
|
216
|
-
import "flowsvg/styles.css";
|
|
217
|
-
import { diagramDocument } from "./diagram.js";
|
|
218
|
-
|
|
219
|
-
defineFlowPlayer();
|
|
220
|
-
const element = document.querySelector<FlowPlayerElement>("flowsvg-player");
|
|
221
|
-
if (element) element.document = diagramDocument;
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
The default element name is `flowsvg-player`; repeated registration is safe. Pass objects through the `document` property. The HTML `document` attribute accepts a JSON string; `src` accepts a JSON URL resolved relative to the page. URL requests follow browser same-origin and CORS rules.
|
|
225
|
-
|
|
226
|
-
Supported options are `autoplay`, `controls`, `speed`, `theme`, `reduced-motion`, and `hover-fill`. Hover fill is off by default; set `hover-fill` to enable background and content feedback. Use `controls="false"` to hide the controls, which are visible by default. Autoplay is off by default. Option changes preserve the current time and playing/paused state. Removing the element cleans up the player and pending requests; reconnecting can mount it again from its retained document.
|
|
227
|
-
|
|
228
|
-
The element exposes `play`, `pause`, `seek`, `step`, `setSpeed`, `getState`, and `destroy`. Replace the document through the `document` property. Before mounting, `getState()` returns `undefined`. The element forwards the four playback/error event types as bubbling, composed CustomEvents that can cross Shadow DOM boundaries.
|
|
229
|
-
|
|
230
|
-
Load this entry in a DOM environment for actual registration. Importing without a DOM is safe, but module evaluation fixes the HTMLElement base class; the same module instance imported without a DOM cannot register directly after a DOM is added later.
|
|
231
|
-
|
|
232
|
-
## Documents and timelines
|
|
233
|
-
|
|
234
|
-
| Document field | Contract |
|
|
123
|
+
| `PlayerOptions` | Default / meaning |
|
|
235
124
|
| --- | --- |
|
|
236
|
-
| `
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `
|
|
240
|
-
| `
|
|
241
|
-
| `
|
|
125
|
+
| `theme?: "light" \| "dark"` | Document theme, then `"light"`. |
|
|
126
|
+
| `controls?: boolean` | `true`; show playback controls. |
|
|
127
|
+
| `loop?: boolean` | `false`; loop the entire timeline. |
|
|
128
|
+
| `gifExport?: boolean` | `false`; show GIF download controls when `controls` is enabled. |
|
|
129
|
+
| `hoverFill?: boolean` | `false`; hover marks borders only. `true` also highlights backgrounds. |
|
|
130
|
+
| `reducedMotion?: boolean` | Follow browser preference; `true` hides flow markers and completes visual transitions immediately without changing timing. |
|
|
242
131
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
Edge `route` is `straight` by default, `orthogonal`, or `curved`. Curved routes are smooth cubic paths generated from endpoint ports; they do not auto-avoid other nodes. Orthogonal paths avoid visible ordinary nodes, with a deterministic fallback when no route is found; shortest paths and zero crossings are not guaranteed. Use `fromPort` and `toPort` (`top`, `right`, `bottom`, or `left`) when a specific side is required. Explicit orthogonal ports reserve outward endpoint segments and route around endpoint bodies; curved ports set endpoint tangents. Omitted ports are selected from the dominant center-to-center axis, and ellipse ports use the ellipse contour. Simple labels wrap using deterministic width estimates. Insufficient node height preserves the full label and emits a `text-overflow` warning.
|
|
246
|
-
|
|
247
|
-
Edges may connect ordinary nodes, groups, or one of each. A group endpoint uses the evaluated group rectangle, including nested movement and explicit ports. Hiding an endpoint group or its ancestor masks the edge. A transfer highlights the endpoint group itself; descendants keep their own highlight state. Group frames remain excluded from routing obstacles.
|
|
248
|
-
|
|
249
|
-
### Fixed-size node content
|
|
250
|
-
|
|
251
|
-
A `rect`, `roundedRect`, or `cylinder` node may declare `content` instead of `label`. Ellipses and groups accept simple labels only. Declaring both fields, including an empty label, is invalid. `content.blocks` is an ordered array of `title`, `paragraph`, `list` and `graph` blocks. Paragraph `role` is `body` (default) or `auxiliary`. An empty block array displays only the shape. Import the `DiagramNode` type from `flowsvg` for this example:
|
|
252
|
-
|
|
253
|
-
```ts
|
|
254
|
-
const service = {
|
|
255
|
-
id: "service", kind: "rect", x: 24, y: 24, width: 260, height: 312,
|
|
256
|
-
content: {
|
|
257
|
-
style: { padding: 16, blockGap: 14 },
|
|
258
|
-
blocks: [
|
|
259
|
-
{ type: "title", text: "Order service" },
|
|
260
|
-
{ type: "paragraph", role: "auxiliary", text: "Core responsibilities" },
|
|
261
|
-
{ type: "paragraph", text: "Validate and persist incoming orders." },
|
|
262
|
-
{ type: "paragraph", text: "Return the order number.", style: { color: "#2563eb", align: "right" } }
|
|
263
|
-
]
|
|
264
|
-
}
|
|
265
|
-
} satisfies DiagramNode;
|
|
266
|
-
```
|
|
132
|
+
Vue/React `FlowPlayerProps` also accept required `document: DiagramDocument`, `speed?: number` (default `1`), `initialTimeMs?: number` (applied once), `className`, and `style`. Both entries export `FlowPlayerHandle` and `FlowPlayerProps`. Native players start through `play()` and change speed through `setSpeed()`.
|
|
267
133
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
Each block accepts `style.fontSize` (finite and positive), `fontWeight` (integer 100–900), hexadecimal `color`, and `align` (`left`, `center`, `right`). Content style accepts nonnegative finite `padding` and `blockGap`. Sizes and spacing use diagram units. Defaults are title 20/700, body 14/400, auxiliary 12/400, padding 16, block gap 14, and line height at least 20 or font size × 1.35. Auxiliary text uses a muted theme color; explicit content colors retain their authored values.
|
|
271
|
-
|
|
272
|
-
A list is `{ type: "list", marker?: "none" | "bullet" | "number", items: ContentListItem[], style?: ContentListStyle }`. The default marker is `none`. Each item accepts optional `tags`, `status`, `body`, `auxiliary`, body `style`, `auxiliaryStyle`, and `statusStyle`. List `style` supplies body typography and a nonnegative finite `itemGap` (default 12). Item body styles override the list's body styles; auxiliary text keeps its muted 12-unit defaults unless `auxiliaryStyle` overrides them. Marked lists reserve one common marker column, including multi-digit numbers, then an 8-unit gap. Body continuation lines and auxiliary text use the same body column. Markers align with the first present text part's baseline. Absent parts and completely absent entries consume no height or item gap; explicit empty strings and blank lines still consume authored lines. For example:
|
|
273
|
-
|
|
274
|
-
```ts
|
|
275
|
-
{ type: "list", marker: "number", style: { itemGap: 16 }, items: [
|
|
276
|
-
{ body: "Validate the request.", auxiliary: "Input checks" },
|
|
277
|
-
{ body: [{ text: "Save", style: { fontWeight: 700 } }, { text: " the order." }] }
|
|
278
|
-
] }
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Tags use `{ text: ContentText, style?: { fontSize, fontWeight, color, fill } }`. Tag font size defaults to 11; each background reserves 6 units on each horizontal side and 2 on each vertical side, with a 3-unit corner radius. List `style.tagGap` is finite and nonnegative (default 6), separating tags, tag rows and same-row status. Tags use theme background/text colors unless explicitly overridden. Status defaults to 12-unit theme accent text; `statusStyle` accepts only `fontSize`, `fontWeight`, and `color`, and status remains right-aligned. A single tag row shares a baseline with a single-line status when their natural widths fit. Otherwise tags wrap first and status starts below all tags. Long tags and status use the same mixed-style grapheme wrapping. Body follows the metadata row(s), then auxiliary text. Omitted parts reserve no space. For example:
|
|
282
|
-
|
|
283
|
-
```ts
|
|
284
|
-
{ type: "list", items: [{
|
|
285
|
-
tags: [{ text: "world" }, { text: "experience", style: { fill: "#eadfff", color: "#57319b" } }],
|
|
286
|
-
status: "new", body: "Alice joined Google", auxiliary: "Mar 2026"
|
|
287
|
-
}] }
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Node dimensions stay fixed. Rectangles apply the authored padding. Rounded rectangles reserve an additional 4 units on each side before padding. Cylinders reserve twice the current cap radius above and below content before padding; the cap radius is `min(14, height / 4)`. The safe area has nonnegative dimensions, including when shape clearance and padding consume the entire node. Content starts at the top of its safe area and wraps using deterministic width estimates. Explicit line endings and blank lines are preserved. SVG clips text to the safe area. Insufficient width or height emits a separate `text-overflow` warning with the node ID, `nodes[index].content` path, and direction. Empty safe areas remain valid. Clipped text does not extend node hit bounds. Simple labels retain their existing overflow behavior.
|
|
291
|
-
|
|
292
|
-
`EvaluatedNode.contentLayout` exposes `safeArea`, ordered `lines`, tag backgrounds `tags`, `widthOverflow`, and `heightOverflow`. Each line provides text, baseline coordinates, estimated width, typography, alignment, semantic role (`title`, `body`, `auxiliary`, `marker`, `tag` or `status`), source `blockIndex`, and optional list `itemIndex`. Tag lines also expose `tagIndex`. Each tag background provides absolute `x`, `y`, `width`, `height`, resolved `fill`, author provenance `explicitFill`, and source `blockIndex`/`itemIndex`/`tagIndex`. Each line's `spans` provide text, absolute left coordinates, shared baseline, estimated width, and resolved typography/color. `explicitColor` records whether the block or source span supplied its color. The same values drive browser SVG and static export. Content follows current absolute geometry, including interpolated dimensions and parent-group movement, ancestor visibility, and effective opacity. Content does not change ports, edge routes, transfer geometry, or node-parent rules. See the runnable [title and paragraph example](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/node-content.html) and [list example](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/list-content.html).
|
|
134
|
+
### Events
|
|
293
135
|
|
|
294
|
-
|
|
136
|
+
Native events are `CustomEvent`s on `player.element`; read `event.detail`. Vue handlers and React callbacks receive the detail directly.
|
|
295
137
|
|
|
138
|
+
| Native / Vue event | React callback | Detail |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `stepchange` | `onStepChange` | `PlayerState`; scene/beat selection changed. |
|
|
141
|
+
| `timeupdate` | `onTimeUpdate` | `PlayerState`; frame updated. |
|
|
142
|
+
| `playstatechange` | `onPlayStateChange` | `PlayerState`; playing state changed. |
|
|
143
|
+
| `loopchange` | `onLoopChange` | `PlayerState`; loop setting changed. |
|
|
144
|
+
| `error` | `onError` | `PlayerErrorDetail`: player state plus `error: Error`. |
|
|
145
|
+
| `hover`, `click` | — | Browser/Web Component: `{ id, type, point, timeMs, sceneId, beatId }`, or `null` for no hit. Aliases: `objecthover`, `objectclick`. |
|
|
296
146
|
|
|
297
|
-
|
|
147
|
+
Interaction `point` uses diagram coordinates; `type` is `"node"` or `"edge"`. A frame update emits `stepchange`, when needed, before `timeupdate`. Web Component errors before mounting may omit player-state fields.
|
|
298
148
|
|
|
299
|
-
|
|
149
|
+
## Export
|
|
300
150
|
|
|
301
151
|
```ts
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
{ id: "alice", label: "Alice", x: 120, y: 30, highlighted: true },
|
|
306
|
-
{ id: "google", label: "Google", x: 50, y: 110, highlighted: true },
|
|
307
|
-
{ id: "ml", label: "ML project", x: 190, y: 110 }
|
|
308
|
-
],
|
|
309
|
-
edges: [{ from: "alice", to: "google" }, { from: "alice", to: "ml" }]
|
|
310
|
-
};
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
`height` is positive and reserves a fixed vertical section, followed by the ordinary block gap. Entity centers `x`/`y` are nonnegative finite offsets from the section's top-left corner in diagram units. IDs must be unique within each graph block; each relation references two local IDs. Labels use escaped plain text at 12 units, centered above 4-unit dots. Selected entities use accent dots and bold labels; a relation uses accent paint when both endpoints are selected. Text and primitives share the parent content clip and theme/highlight paint. Coordinates or labels outside the graph section produce directional overflow warnings without moving the following block. Leave enough room above each dot for its label. Entities remain decorative: clicking or hovering them targets the parent node.
|
|
152
|
+
import { createDiagram } from "flowsvg";
|
|
153
|
+
import { exportGif } from "flowsvg/export";
|
|
154
|
+
import { diagram } from "./diagram.js";
|
|
314
155
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
Start with fixed shape dimensions, then choose ordered blocks. Use `marker: "none"` for records, `"bullet"` for capabilities, and `"number"` for steps. Give each entry only the parts it needs. Dates are plain text; a small inline span keeps a date on the body baseline. This complete document can be passed unchanged to any host:
|
|
320
|
-
|
|
321
|
-
```ts
|
|
322
|
-
import { createDiagram, type DiagramDocument, type NodeContent } from "flowsvg";
|
|
323
|
-
|
|
324
|
-
const facts: NodeContent = { blocks: [
|
|
325
|
-
{ type: "title", text: "Facts" },
|
|
326
|
-
{ type: "paragraph", role: "auxiliary", text: "Confirmed records" },
|
|
327
|
-
{ type: "list", marker: "none", items: [{
|
|
328
|
-
tags: [{ text: "world" }, { text: "orders", style: { fill: "#eadfff", color: "#57319b" } }],
|
|
329
|
-
status: "new",
|
|
330
|
-
body: [{ text: "Order saved", style: { fontWeight: 700 } }, { text: " 2026-10-17", style: { fontSize: 11 } }],
|
|
331
|
-
auxiliary: "Ready for payment."
|
|
332
|
-
}] }
|
|
333
|
-
] };
|
|
334
|
-
const updated: NodeContent = { blocks: [
|
|
335
|
-
{ type: "title", text: "Facts" },
|
|
336
|
-
{ type: "list", items: [{ tags: [{ text: "verified" }], status: "done", body: "Payment confirmed", auxiliary: "2026-10-18" }] }
|
|
337
|
-
] };
|
|
338
|
-
const document: DiagramDocument = {
|
|
339
|
-
version: 1, viewport: { width: 300, height: 480 },
|
|
340
|
-
nodes: [{ id: "facts", kind: "cylinder", x: 24, y: 20, width: 240, height: 440, content: facts }], edges: [],
|
|
341
|
-
scenes: [{ id: "records", label: "Records", beats: [
|
|
342
|
-
{ durationMs: 1000, actions: [] },
|
|
343
|
-
{ durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: updated } }] },
|
|
344
|
-
{ durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { label: "Archived" } }] },
|
|
345
|
-
{ durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: facts } }] },
|
|
346
|
-
{ durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: null } }] }
|
|
347
|
-
] }]
|
|
348
|
-
};
|
|
349
|
-
const diagram = createDiagram(document);
|
|
350
|
-
console.log(diagram.evaluateAt(1000).warnings);
|
|
351
|
-
const svg = diagram.renderSvg(1000, { theme: "dark", reducedMotion: true });
|
|
156
|
+
const compiled = createDiagram(diagram);
|
|
157
|
+
const frame = compiled.evaluateAt(1200);
|
|
158
|
+
const svg = compiled.renderSvg(1200, { theme: "dark" });
|
|
159
|
+
const gif = await exportGif(diagram, { width: 640, fps: 15 });
|
|
352
160
|
```
|
|
353
161
|
|
|
354
|
-
|
|
162
|
+
SVG returns a string; GIF returns `Uint8Array`. Browser GIF export supports `signal` and `onProgress`. Use `gifExport: true` for built-in download controls. Node 20+ also provides `renderHtml` and `exportGif` through `flowsvg/node`; Node GIF requires prepared Chromium.
|
|
355
163
|
|
|
356
|
-
|
|
164
|
+
## Core API — `flowsvg`
|
|
357
165
|
|
|
358
|
-
|
|
166
|
+
All times are milliseconds; positions and dimensions use diagram units. `options?: EvaluateOptions` accepts `theme?: "light" | "dark"` and `reducedMotion?: boolean` (default `false`).
|
|
359
167
|
|
|
360
|
-
|
|
|
168
|
+
| Function | Result / behavior |
|
|
361
169
|
| --- | --- |
|
|
362
|
-
| `
|
|
363
|
-
| `
|
|
364
|
-
| `
|
|
365
|
-
| `
|
|
366
|
-
| `
|
|
367
|
-
| `focus` | `target` is a node/group ID; `null` restores the full viewport |
|
|
170
|
+
| `createDiagram(document: DiagramDocument): Diagram` | Validate, copy, freeze and compile the document for repeated queries. |
|
|
171
|
+
| `renderSvg(document: DiagramDocument, timeMs = 0, options?): string` | Compile and render one static SVG. |
|
|
172
|
+
| `validateDocument(input: unknown): ValidationIssue[]` | Check fields and references; return `{ path, message }` issues. An empty array means these checks passed; compilation can still reject non-finite derived flow timing. |
|
|
173
|
+
| `measureNodeContent({ kind, width, content }): NodeContentMeasurement` | Return `{ height, diagnostics }`. `kind`: `rect`, `roundedRect`, or `cylinder`; `content`: one `NodeContent` or a nonempty array of versions. Return the largest required height across versions. |
|
|
174
|
+
| `wrapText(label: string, maxWidth: number, typography?): string[]` | Estimate wrapping without the DOM. Optional typography supplies all three numeric fields: `fontSize`, `lineHeight`, `charWidth`; omitted uses node typography. |
|
|
368
175
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
```ts
|
|
372
|
-
// Each value belongs to a separate beat's updateNode action.
|
|
373
|
-
import type { NodeVisualState } from "flowsvg";
|
|
374
|
-
|
|
375
|
-
const replacement: NodeVisualState = { content: { blocks: [{ type: "title", text: "Updated" }] } };
|
|
376
|
-
const simpleLabel: NodeVisualState = { label: "Summary" };
|
|
377
|
-
const clearText: NodeVisualState = { content: null };
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
All scenes share a global timeline in milliseconds. Each scene inherits the previous scene's final state. Labels and captions change at beat start; interpolated properties change during the transition. Beat intervals include their start and exclude their end; the final time returns the completed final frame. Hiding a node or group also masks connected edges; showing it again restores visibility according to each object's saved state.
|
|
381
|
-
|
|
382
|
-
A beat can transfer along several distinct edges at once. All transfers start at the beat start, use the same linear completion ratio over `durationMs`, and disappear at the beat end. Different path lengths therefore produce different physical speeds. Shared endpoint nodes remain highlighted while any current transfer uses them. The next beat replaces the complete transfer group, including across scene boundaries. Transfer highlights do not inherit; explicit highlights and ordinary styles retain their existing inheritance rules.
|
|
383
|
-
|
|
384
|
-
## API
|
|
385
|
-
|
|
386
|
-
Transfer text belongs to one action and does not replace the edge's static `label`. Capsule width uses deterministic Unicode code-point estimates with padding; it does not use browser font measurements. Text remains horizontal on every route and is escaped as plain text. Capsules are decorative, with no independent pointer or keyboard interaction. A provided empty string still uses a capsule.
|
|
387
|
-
|
|
388
|
-
### Core API
|
|
389
|
-
|
|
390
|
-
Import from `flowsvg`:
|
|
391
|
-
|
|
392
|
-
| API | Result and behavior |
|
|
176
|
+
| Compiled `Diagram` | Result / behavior |
|
|
393
177
|
| --- | --- |
|
|
394
|
-
| `
|
|
395
|
-
| `
|
|
396
|
-
| `
|
|
397
|
-
| `
|
|
398
|
-
| `
|
|
399
|
-
| `diagram.renderSvg(timeMs = 0, options?)` | Exports SVG from the same evaluation result |
|
|
400
|
-
| `diagram.hitTest(timeMs, point, options?)` | Returns `{ id, type: "node" \| "edge" }` or `null`, using document coordinates |
|
|
401
|
-
| `wrapText(label, maxWidth, typography?)` | Wraps text with fixed width estimates and returns an array of lines |
|
|
402
|
-
|
|
403
|
-
`EvaluateOptions` is `{ theme?: "light" | "dark"; reducedMotion?: boolean }`. Finite times are clamped to `[0, totalDuration]`; non-finite times become zero. Hit testing ignores hidden or zero-opacity objects and checks ordinary nodes, then edges, then group backgrounds. Group hover highlights only the border and preserves the evaluated fill, including any active timeline highlight.
|
|
178
|
+
| `totalDuration: number` | Readonly effective duration; `0` for static documents. |
|
|
179
|
+
| `beats: readonly DiagramBeat[]` | Compiled `{ sceneId, beatId, beatIndex, startMs, endMs, caption? }` intervals. |
|
|
180
|
+
| `evaluateAt(timeMs: number, options?): DiagramFrameState` | Return `{ timeMs, sceneId, beatId, beatProgress, nodes, edges, camera, caption?, warnings }`. |
|
|
181
|
+
| `renderSvg(timeMs = 0, options?): string` | Render without recompiling. |
|
|
182
|
+
| `hitTest(timeMs: number, point: DiagramPoint, options?): HitObject \| null` | Hit-test `{ x, y }` in diagram coordinates; return `{ id, type }` or `null`. |
|
|
404
183
|
|
|
405
|
-
|
|
184
|
+
Query times clamp to `[0, totalDuration]`; non-finite values become zero. `DiagramValidationError.issues` contains validation paths and messages. Measurement diagnostics contain `{ versionIndex, path, dimension, message }`; increasing height does not fix width overflow or overflow inside a fixed-height graph block.
|
|
406
185
|
|
|
407
|
-
|
|
186
|
+
### Document and actions
|
|
408
187
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
Import `createPlayer(host, document, options?)` from `flowsvg/browser`. `PlayerOptions` extends core visual options with `controls?: boolean` and `hoverFill?: boolean`.
|
|
412
|
-
|
|
413
|
-
| Method or property | Behavior |
|
|
188
|
+
| Type | Core fields |
|
|
414
189
|
| --- | --- |
|
|
415
|
-
| `
|
|
416
|
-
| `
|
|
417
|
-
| `
|
|
418
|
-
| `
|
|
419
|
-
| `
|
|
420
|
-
| `
|
|
421
|
-
| `getState()` | Returns `PlayerState`: `timeMs`, `totalDuration`, `playing`, `speed`, and `frame` |
|
|
422
|
-
| `destroy()` | Idempotently cleans up DOM, listeners, and scheduling; stops events, while state remains readable |
|
|
423
|
-
|
|
424
|
-
Players start paused at zero, with built-in controls enabled. Hiding the page pauses playback; making it visible again leaves playback paused. Playback stops at the final frame. Static diagrams disable timeline controls. Framework handles provide the control and state methods above, excluding `element` and `destroy`; frameworks manage the host and cleanup.
|
|
190
|
+
| `Viewport` | Required finite positive `width` and `height`; optional `fitHeight: { padding }` with finite nonnegative bottom padding. |
|
|
191
|
+
| `DiagramDocument` | `version: 1`, `viewport`, `nodes`, `edges`, `scenes`; optional `title`, `theme`. Empty `scenes` makes a static diagram. |
|
|
192
|
+
| `DiagramNode` | `id`, `kind`, `x`, `y`, `width`, `height`; optional `label` or `content` (mutually exclusive), `parentId`, `visible`, `style`. Kinds: `rect`, `roundedRect`, `ellipse`, `cylinder`, `group`. |
|
|
193
|
+
| `DiagramEdge` | `id`, `from`, `to`; optional `route` (default `straight`), `fromPort`, `toPort`, `label`, `visible`, `style`. Ports: `top`, `right`, `bottom`, `left`. |
|
|
194
|
+
| `Scene` / `Beat` | Scene: `{ id, label, beats }`. Beat: `{ durationMs, actions, transitionMs?, caption? }`. `durationMs` must be positive. `transitionMs` must be in `[0, durationMs]`, defaults to `min(250, durationMs)`, and is capped at effective beat length. |
|
|
195
|
+
| `Action` | `{ type, target }`; `updateNode` / `updateEdge` also require `value`. `flow` requires `direction` (`forward` / `reverse`), with optional `text` and `highlightParticipants` (default `true`). `focus` targets a node/group ID or `null` for full view. |
|
|
425
196
|
|
|
426
|
-
|
|
197
|
+
`updateNode.value` supports `x`, `y`, `width`, `height`, `label`, `content` (`null` clears it), `fill`, `stroke`, and `opacity`. `updateEdge.value` supports `label`, `stroke`, `strokeWidth`, and `opacity`. Node/edge IDs are globally unique; child positions are relative to their parent group. `NodeContent` and its block types are exported from `flowsvg`.
|
|
427
198
|
|
|
428
|
-
|
|
199
|
+
## Export API
|
|
429
200
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
### Events
|
|
433
|
-
|
|
434
|
-
Native players dispatch `CustomEvent` on the host element:
|
|
435
|
-
|
|
436
|
-
| Event | `detail` |
|
|
201
|
+
| Entry / function | Return / requirements |
|
|
437
202
|
| --- | --- |
|
|
438
|
-
| `
|
|
439
|
-
| `
|
|
440
|
-
| `
|
|
441
|
-
| `error` | `PlayerErrorDetail`: retained player state and `error` |
|
|
442
|
-
| `hover` / `click` | `PlayerInteractionDetail` or `null`; aliases are `objecthover` / `objectclick` |
|
|
443
|
-
|
|
444
|
-
Interaction details contain `id`, `type`, document-coordinate `point`, `timeMs`, `sceneId`, and `beatId`; a miss produces `null`. Vue, React, and Web Component wrappers forward the first four event types. Web Component errors before mounting may lack player state.
|
|
445
|
-
|
|
446
|
-
Pointer movement emits `hover`; frame updates also emit it when the object under a stationary pointer changes. Leaving or cancelling the pointer emits `null`.
|
|
447
|
-
|
|
448
|
-
```ts
|
|
449
|
-
import type { PlayerEventMap } from "flowsvg/browser";
|
|
450
|
-
|
|
451
|
-
function handleTimeUpdate(event: Event): void {
|
|
452
|
-
const { detail } = event as PlayerEventMap["timeupdate"];
|
|
453
|
-
console.log(detail.timeMs, detail.frame.caption);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
player.element.addEventListener("timeupdate", handleTimeUpdate);
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
This example reuses `player` from the native integration. Remove any listeners your application adds when disposing of the host.
|
|
460
|
-
|
|
461
|
-
## Themes and accessibility
|
|
203
|
+
| `flowsvg/export` → `exportGif(document, options?: GifExportOptions)` | `Promise<Uint8Array>`; browser canvas and worker support. |
|
|
204
|
+
| `flowsvg/node` → `renderHtml(document, options?: HtmlRenderOptions)` | `Promise<string>`; standalone offline player HTML. Node 20+, no browser required. |
|
|
205
|
+
| `flowsvg/node` → `exportGif(document, options?: NodeGifExportOptions)` | `Promise<Uint8Array>`; Node 20+ and prepared Chromium. Set `executablePath` or supply a Playwright `browser`, never both. A supplied browser remains caller-owned. |
|
|
462
206
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
Import `flowsvg/styles.css` for control layout and focus styles. CSS variables customize controls only; core themes and document styles determine diagram colors:
|
|
466
|
-
|
|
467
|
-
```css
|
|
468
|
-
.flowsvg {
|
|
469
|
-
--flowsvg-control-background: #101828;
|
|
470
|
-
--flowsvg-control-color: #f4f7fb;
|
|
471
|
-
--flowsvg-control-border: #64748b;
|
|
472
|
-
--flowsvg-control-accent: #60a5fa;
|
|
473
|
-
--flowsvg-control-focus: #fbbf24;
|
|
474
|
-
}
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
Add the `flowsvg` class to a native host to use this rule; framework components add it automatically. With the canvas focused, Space plays/pauses and the left/right arrow keys step through beats. Form controls retain their native key behavior. Captions use a polite live region, and each SVG has its own accessible name.
|
|
478
|
-
|
|
479
|
-
## Validation and errors
|
|
480
|
-
|
|
481
|
-
Loading rejects unknown fields, invalid references, cyclic groups, duplicate assignments within a beat, invalid colors, and invalid dimensions/durations. Text is escaped as plain text. To check external JSON first, call `validateDocument` and display each issue's `path` and `message`.
|
|
482
|
-
|
|
483
|
-
`createDiagram` and `createPlayer` with an invalid initial document throw `DiagramValidationError`, whose `issues` contain field paths. An existing player's `load` reports validation failure through the `error` event and preserves the old state. Text height overflow is a nonblocking layout warning; inspect `frame.warnings` to decide whether to resize nodes.
|
|
484
|
-
|
|
485
|
-
Each beat permits at most one `flow` per edge, even when directions or text differ. Repeating an edge reports `duplicate beat assignment` at the later action’s `target`, independently of other field errors. A flow can coexist with visibility, explicit highlight, and ordinary style actions on the same edge; those actions retain their own validation rules.
|
|
486
|
-
|
|
487
|
-
## Development and verification
|
|
488
|
-
|
|
489
|
-
Run these commands from the source repository root. Development uses Node.js 22.12+ and pnpm 10.12.1; Vue/React examples use Vite 7.
|
|
490
|
-
|
|
491
|
-
| Command | Purpose |
|
|
207
|
+
| `GifExportOptions` | Default / meaning |
|
|
492
208
|
| --- | --- |
|
|
493
|
-
| `
|
|
494
|
-
| `
|
|
495
|
-
| `
|
|
496
|
-
| `
|
|
497
|
-
| `
|
|
498
|
-
| `
|
|
499
|
-
| `pnpm release:verify` | Pack from a missing `dist`, verify the file allowlist and every export, then check framework-free imports, strict consumer types and all four host lifecycles |
|
|
500
|
-
| `pnpm release` | Run all release gates and npm preview; retain the verified tarball in `dist/releases/` without uploading |
|
|
501
|
-
| `pnpm release:publish` | Check npm authentication, repeat all release gates and preview, then upload the verified tarball |
|
|
502
|
-
|
|
503
|
-
Maintainers: follow the [npm release guide](https://github.com/yokingma/FlowSVG/blob/HEAD/docs/RELEASING.md). Run SDK packaging before example builds; packaging removes and rebuilds `dist`.
|
|
504
|
-
|
|
505
|
-
After building, run the Node.js static server from the repository root:
|
|
506
|
-
|
|
507
|
-
```sh
|
|
508
|
-
pnpm dlx http-server@14.1.1 . -p 4175 -c-1
|
|
509
|
-
```
|
|
209
|
+
| `theme`, `reducedMotion` | Document theme then `light`; reduced motion defaults to `false`. |
|
|
210
|
+
| `startMs`, `endMs`, `speed` | Full timeline at `1×`; range start is inclusive and end exclusive, except the global end includes the completed state. |
|
|
211
|
+
| `width`, `fps` | `min(viewport.width, 1280)` pixels; `15` samples/second (`1`–`30`, integer). |
|
|
212
|
+
| `includeCaptions`, `captionHeight` | `true`; fixed caption region of `80` pixels. |
|
|
213
|
+
| `loopCount`, `finalHoldMs` | `0` means infinite GIF looping; `1`–`65536` means total plays. Add `1000` ms of final hold by default. Independent of live player looping. |
|
|
214
|
+
| `signal`, `onProgress` | `AbortSignal`; callback receives `{ phase, completed, total }`. Phases: `preparing`, `sampling`, `encoding`, `complete`. |
|
|
510
215
|
|
|
511
|
-
|
|
216
|
+
GIF limits: 120 effective seconds, 1800 samples, 1801 encoded frames, final long edge ≤1920 pixels including captions. `GifExportError.code` is `options`, `limit`, `document`, `caption-overflow`, `environment`, `rasterization`, `encoding`, or `cancelled`. Both export entries expose `GifExportError` and their option/progress types.
|
|
512
217
|
|
|
513
|
-
|
|
218
|
+
`HtmlRenderOptions`: `theme`, `autoplay` (default `false`), `loop` (`false`), `controls` (`true`), `gifExport` (`false`). Built-in GIF downloads export the full timeline at `1×`, using the current theme.
|
|
514
219
|
|
|
515
|
-
|
|
220
|
+
## Other browser APIs
|
|
516
221
|
|
|
517
|
-
|
|
222
|
+
- `flowsvg/web-component` (alias `flowsvg/element`): call `defineFlowPlayer(tagName = "flowsvg-player")` in the browser; returns the registered constructor, or `undefined` without a custom-element registry. Set `element.document` to an object, use the `document` JSON attribute, or load a URL through `src`. Playback options use attributes such as `loop`, `gif-export`, and `reduced-motion`; `autoplay` defaults to `false`, `speed` to `1`. Use playback methods except `load()`; assign `.document` to reload. `getState()` can return `undefined` before mounting or after destruction. Events bubble and cross shadow boundaries.
|
|
223
|
+
- `flowsvg/browser`: `new SelectControl(owner: Document, label: string, onChange: (value: string) => void)`. Append `.element`, synchronize with `render(choices: readonly SelectChoice[], value: string, disabled = false)`, and call `destroy()` on disposal. Each choice is `{ value, label }`; the caller owns the committed value.
|
|
518
224
|
|
|
519
|
-
|
|
225
|
+
## Authoring rules
|
|
520
226
|
|
|
521
|
-
|
|
227
|
+
- `show`, `hide`, `highlight`, `unhighlight`, `updateNode`, `updateEdge`, and `focus` persist across beats. `flow` lasts for its beat only.
|
|
228
|
+
- A single flow moves at a reference 180 diagram units/second (minimum 400 ms), then uses a 500 ms arrival interval including highlight fades. This replaces authored `durationMs`. Multiple flows and no-flow beats retain authored timing. Read effective boundaries from `compiled.beats`; use separate no-flow beats for reading time.
|
|
229
|
+
- Structured `content` supports titles, paragraphs, lists, tags, and entity graphs in rectangles, rounded rectangles, and cylinders. Use `measureNodeContent({ kind, width, content })` before authoring fixed dimensions; inspect its `diagnostics`. It does not resize nodes during playback.
|
|
230
|
+
- `viewport.fitHeight: { padding: 24 }` fits current full-view height. GIF keeps the authored viewport dimensions and scale.
|
|
231
|
+
- Invalid documents throw `DiagramValidationError` with field paths. Text overflow appears in `frame.warnings`. `validateDocument(input)` checks external JSON before use.
|
|
522
232
|
|
|
523
|
-
|
|
233
|
+
ESM with TypeScript declarations. Core runs without DOM on Node 18+; Node tools require Node 20+. Vue 3.5+ / React 18+.
|
|
524
234
|
|
|
525
|
-
|
|
235
|
+
[Full SDK guide](https://github.com/yokingma/FlowSVG/blob/main/docs/SDK-GUIDE.md) · [Changelog](https://cdn.jsdelivr.net/npm/flowsvg@0.2.0/CHANGELOG.md) · [MIT](https://cdn.jsdelivr.net/npm/flowsvg@0.2.0/LICENSE)
|