flowsvg 0.1.1 → 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 +28 -0
- package/PATTERNS.md +61 -5
- package/README.md +149 -461
- package/README.zh-CN.md +149 -461
- package/dist/adapters/react.d.ts +7 -0
- package/dist/adapters/react.js +14 -2
- package/dist/adapters/vue.d.ts +29 -3
- package/dist/adapters/vue.js +20 -4
- package/dist/adapters/web-component.d.ts +14 -2
- package/dist/adapters/web-component.js +32 -3
- package/dist/browser/controls.d.ts +7 -3
- package/dist/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 +9 -1
- package/dist/browser/player.js +83 -24
- package/dist/browser/select-control.d.ts +33 -0
- package/dist/browser/select-control.js +171 -0
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +2 -1
- package/dist/core/diagram.d.ts +4 -2
- package/dist/core/diagram.js +111 -21
- package/dist/core/edge-geometry.d.ts +16 -3
- package/dist/core/edge-geometry.js +264 -37
- package/dist/core/model.d.ts +35 -9
- package/dist/core/node-content-measurement.d.ts +34 -0
- package/dist/core/node-content-measurement.js +66 -0
- package/dist/core/node-content.d.ts +17 -0
- package/dist/core/node-content.js +30 -6
- package/dist/core/timeline.d.ts +11 -3
- package/dist/core/timeline.js +29 -5
- package/dist/core/validation.d.ts +1 -1
- package/dist/core/validation.js +13 -3
- 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 +3 -1
- package/dist/index.js +1 -0
- 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/styles.css +194 -14
- package/docs/images/flowsvg-wordmark.gif +0 -0
- package/package.json +37 -6
package/README.md
CHANGED
|
@@ -1,547 +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:
|
|
17
|
+
Define the data once and use it in native browser, Vue, React, or Web Component integrations.
|
|
14
18
|
|
|
15
19
|
```sh
|
|
16
|
-
|
|
20
|
+
npm install flowsvg
|
|
17
21
|
```
|
|
18
22
|
|
|
19
|
-
|
|
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 need no framework. TypeScript declarations are included. Core: Node.js 18+ or ES2022 ESM. Browser: DOM, SVG, and `requestAnimationFrame`. Frameworks: Vue 3.5+ or React 18+.
|
|
31
|
-
|
|
32
|
-
| Import path | Purpose |
|
|
33
|
-
| --- | --- |
|
|
34
|
-
| `flowsvg` | DOM-free document validation, compilation, evaluation, hit testing, and SVG export |
|
|
35
|
-
| `flowsvg/browser` | Native browser player |
|
|
36
|
-
| `flowsvg/vue` | Vue component and handle types |
|
|
37
|
-
| `flowsvg/react` | React component and ref types |
|
|
38
|
-
| `flowsvg/web-component` | Explicit custom element registration; also available as `flowsvg/element` |
|
|
39
|
-
| `flowsvg/styles.css` | Control layout and visible focus styles |
|
|
40
|
-
|
|
41
|
-
## Quick start
|
|
42
|
-
|
|
43
|
-
### 1. Create a diagram document
|
|
44
|
-
|
|
45
|
-
Save this as `diagram.ts`. This 5.2-second example uses six nodes, three scenes and five beats. It separates authorization, order persistence, and the final response with an asynchronous event. The integration examples below reuse this file:
|
|
23
|
+
## Define a diagram
|
|
46
24
|
|
|
47
25
|
```ts
|
|
26
|
+
// diagram.ts
|
|
48
27
|
import type { DiagramDocument } from "flowsvg";
|
|
49
28
|
|
|
50
|
-
export const
|
|
29
|
+
export const diagram: DiagramDocument = {
|
|
51
30
|
version: 1,
|
|
52
|
-
|
|
53
|
-
viewport: { width: 960, height: 440 },
|
|
31
|
+
viewport: { width: 640, height: 240 },
|
|
54
32
|
nodes: [
|
|
55
|
-
{ id: "client", kind: "roundedRect", x: 40, y:
|
|
56
|
-
{ id: "
|
|
57
|
-
{ id: "auth", kind: "ellipse", x: 260, y: 40, width: 130, height: 72, label: "Auth" },
|
|
58
|
-
{ id: "orders", kind: "roundedRect", x: 500, y: 205, width: 150, height: 70, label: "Order service" },
|
|
59
|
-
{ id: "database", kind: "cylinder", x: 780, y: 65, width: 140, height: 90, label: "Orders" },
|
|
60
|
-
{ id: "events", kind: "rect", x: 780, y: 330, width: 140, height: 70, label: "Event queue" }
|
|
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" }
|
|
61
35
|
],
|
|
62
|
-
edges: [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
],
|
|
70
|
-
scenes: [
|
|
71
|
-
{ id: "request", label: "Receive and authorize", beats: [
|
|
72
|
-
{ durationMs: 800, caption: "Receive the order request", actions: [
|
|
73
|
-
{ type: "flow", target: "request", direction: "forward", text: "POST" }
|
|
74
|
-
] },
|
|
75
|
-
{ durationMs: 800, caption: "Validate the access token", actions: [
|
|
76
|
-
{ type: "flow", target: "authorize", direction: "forward", text: "Token" }
|
|
77
|
-
] }
|
|
78
|
-
] },
|
|
79
|
-
{ id: "create-order", label: "Create the order", beats: [
|
|
80
|
-
{ durationMs: 1000, caption: "Send the authorized request to the order service", actions: [
|
|
81
|
-
{ type: "flow", target: "create", direction: "forward", text: "Create" }
|
|
36
|
+
edges: [{ id: "request", from: "client", to: "service", route: "orthogonal" }],
|
|
37
|
+
scenes: [{
|
|
38
|
+
id: "request-response",
|
|
39
|
+
label: "Request and response",
|
|
40
|
+
beats: [
|
|
41
|
+
{ durationMs: 1000, caption: "Send request", actions: [
|
|
42
|
+
{ type: "flow", target: "request", direction: "forward", text: "Request" }
|
|
82
43
|
] },
|
|
83
|
-
{ durationMs: 1000, caption: "
|
|
84
|
-
{ type: "flow", target: "
|
|
85
|
-
] }
|
|
86
|
-
] },
|
|
87
|
-
{ id: "response", label: "Publish and respond", beats: [
|
|
88
|
-
{ durationMs: 1600, caption: "Publish OrderCreated and return the order number", actions: [
|
|
89
|
-
{ type: "flow", target: "publish", direction: "forward", text: "OrderCreated" },
|
|
90
|
-
{ type: "flow", target: "result", direction: "forward", text: "201 Created" },
|
|
91
|
-
{ type: "flow", target: "request", direction: "reverse", text: "Order ID" }
|
|
44
|
+
{ durationMs: 1000, caption: "Return response", actions: [
|
|
45
|
+
{ type: "flow", target: "request", direction: "reverse" }
|
|
92
46
|
] }
|
|
93
|
-
]
|
|
94
|
-
]
|
|
47
|
+
]
|
|
48
|
+
}]
|
|
95
49
|
};
|
|
96
50
|
```
|
|
97
51
|
|
|
98
|
-
|
|
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.
|
|
99
53
|
|
|
100
|
-
|
|
54
|
+
## Play
|
|
101
55
|
|
|
102
|
-
|
|
56
|
+
```html
|
|
57
|
+
<div id="diagram"></div>
|
|
58
|
+
```
|
|
103
59
|
|
|
104
60
|
```ts
|
|
105
|
-
import {
|
|
106
|
-
import
|
|
61
|
+
import { createPlayer } from "flowsvg/browser";
|
|
62
|
+
import "flowsvg/styles.css";
|
|
63
|
+
import { diagram } from "./diagram.js";
|
|
107
64
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
65
|
+
const player = createPlayer(document.querySelector<HTMLElement>("#diagram")!, diagram, {
|
|
66
|
+
theme: "light",
|
|
67
|
+
loop: true,
|
|
68
|
+
gifExport: true
|
|
69
|
+
});
|
|
111
70
|
|
|
112
|
-
|
|
113
|
-
|
|
71
|
+
player.play();
|
|
72
|
+
// On disposal: player.destroy();
|
|
114
73
|
```
|
|
115
74
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
## Vue integration
|
|
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.
|
|
119
76
|
|
|
120
|
-
|
|
77
|
+
### Vue
|
|
121
78
|
|
|
122
79
|
```vue
|
|
123
80
|
<script setup lang="ts">
|
|
124
|
-
import {
|
|
125
|
-
import { FlowPlayer, type FlowPlayerHandle } from "flowsvg/vue";
|
|
126
|
-
import type { PlayerErrorDetail } from "flowsvg/browser";
|
|
81
|
+
import { FlowPlayer } from "flowsvg/vue";
|
|
127
82
|
import "flowsvg/styles.css";
|
|
128
|
-
import {
|
|
129
|
-
|
|
130
|
-
const player = ref<FlowPlayerHandle | null>(null);
|
|
131
|
-
const timeMs = ref(0);
|
|
132
|
-
|
|
133
|
-
function handleError({ error }: PlayerErrorDetail): void {
|
|
134
|
-
console.error(error);
|
|
135
|
-
}
|
|
83
|
+
import { diagram } from "./diagram.js";
|
|
136
84
|
</script>
|
|
137
85
|
|
|
138
86
|
<template>
|
|
139
|
-
<FlowPlayer
|
|
140
|
-
ref="player"
|
|
141
|
-
:document="diagramDocument"
|
|
142
|
-
:speed="1.25"
|
|
143
|
-
theme="dark"
|
|
144
|
-
@timeupdate="timeMs = $event.timeMs"
|
|
145
|
-
@error="handleError"
|
|
146
|
-
/>
|
|
147
|
-
<button type="button" @click="player?.play()">Play</button>
|
|
148
|
-
<button type="button" @click="player?.seek(0)">Back to start</button>
|
|
149
|
-
<output>{{ timeMs }} ms</output>
|
|
87
|
+
<FlowPlayer :document="diagram" loop gif-export />
|
|
150
88
|
</template>
|
|
151
89
|
```
|
|
152
90
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
## React integration
|
|
91
|
+
### React
|
|
156
92
|
|
|
157
93
|
```tsx
|
|
158
|
-
import {
|
|
159
|
-
import { FlowPlayer, type FlowPlayerHandle } from "flowsvg/react";
|
|
94
|
+
import { FlowPlayer } from "flowsvg/react";
|
|
160
95
|
import "flowsvg/styles.css";
|
|
161
|
-
import {
|
|
162
|
-
|
|
163
|
-
export
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<section>
|
|
168
|
-
<FlowPlayer
|
|
169
|
-
ref={player}
|
|
170
|
-
document={diagramDocument}
|
|
171
|
-
speed={1.25}
|
|
172
|
-
theme="dark"
|
|
173
|
-
onError={({ error }) => console.error(error)}
|
|
174
|
-
/>
|
|
175
|
-
<button type="button" onClick={() => player.current?.play()}>Play</button>
|
|
176
|
-
<button type="button" onClick={() => player.current?.seek(0)}>Back to start</button>
|
|
177
|
-
</section>
|
|
178
|
-
);
|
|
96
|
+
import { diagram } from "./diagram.js";
|
|
97
|
+
|
|
98
|
+
export function Diagram() {
|
|
99
|
+
return <FlowPlayer document={diagram} loop gifExport />;
|
|
179
100
|
}
|
|
180
101
|
```
|
|
181
102
|
|
|
182
|
-
|
|
103
|
+
## Control playback
|
|
183
104
|
|
|
184
|
-
|
|
105
|
+
`createPlayer(host: HTMLElement, document: DiagramDocument, options?: PlayerOptions): FlowPlayer` mounts into the host, replacing its children. Invalid initial documents throw `DiagramValidationError`.
|
|
185
106
|
|
|
186
|
-
|
|
|
107
|
+
| Method / property | Behavior |
|
|
187
108
|
| --- | --- |
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
|
|
197
|
-
Changing `theme`, `controls`, `reducedMotion`, or `hoverFill` recreates the player, paused at zero, without reapplying `initialTimeMs`. Reload documents with a new object reference. Handles expose seven methods listed under [Player API](#player-api); calls before mount or after unmount throw.
|
|
198
|
-
|
|
199
|
-
Both entries support server-side imports. SSR renders a container; the player is created on client mount. Use `renderSvg` for server-side diagram previews.
|
|
200
|
-
|
|
201
|
-
## Native browser
|
|
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. |
|
|
202
118
|
|
|
203
|
-
|
|
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.
|
|
204
120
|
|
|
205
|
-
|
|
206
|
-
<div id="diagram" style="width: 100%; height: 360px;"></div>
|
|
207
|
-
```
|
|
121
|
+
### Options and component props
|
|
208
122
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
```ts
|
|
212
|
-
import { createPlayer } from "flowsvg/browser";
|
|
213
|
-
import "flowsvg/styles.css";
|
|
214
|
-
import { diagramDocument } from "./diagram.js";
|
|
215
|
-
|
|
216
|
-
const host = document.querySelector<HTMLElement>("#diagram");
|
|
217
|
-
if (!host) throw new Error("Diagram host is missing");
|
|
218
|
-
|
|
219
|
-
const player = createPlayer(host, diagramDocument, { theme: "light" });
|
|
220
|
-
player.play();
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
`createPlayer` replaces the container children. Call `destroy()` on disposal and before reusing the container. Plain HTML requires an import map or build tooling for package imports.
|
|
224
|
-
|
|
225
|
-
The [native example](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/README.md) demonstrates request/response flows. Its shared document also works with the other three integrations.
|
|
226
|
-
|
|
227
|
-
## Web Component
|
|
228
|
-
|
|
229
|
-
Place the element in HTML, then register it explicitly from a client-side module:
|
|
230
|
-
|
|
231
|
-
```html
|
|
232
|
-
<flowsvg-player controls="false"></flowsvg-player>
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
import { defineFlowPlayer, type FlowPlayerElement } from "flowsvg/web-component";
|
|
237
|
-
import "flowsvg/styles.css";
|
|
238
|
-
import { diagramDocument } from "./diagram.js";
|
|
239
|
-
|
|
240
|
-
defineFlowPlayer();
|
|
241
|
-
const element = document.querySelector<FlowPlayerElement>("flowsvg-player");
|
|
242
|
-
if (element) element.document = diagramDocument;
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Registration defaults to `<flowsvg-player>` and is idempotent. Assign objects to `element.document`, JSON strings to the HTML `document` attribute, or a JSON URL to `src`. URLs resolve against the page and follow browser CORS rules.
|
|
246
|
-
|
|
247
|
-
Attributes: `autoplay`, `controls`, `speed`, `theme`, `reduced-motion`, and `hover-fill`. Controls default on; autoplay and hover fill default off. Use `controls="false"` to hide controls. Changes to theme, controls, or motion/hover options preserve time and playback state; `autoplay` explicitly starts or pauses playback. Removal cancels requests and destroys the player; reconnection uses the retained document.
|
|
248
|
-
|
|
249
|
-
Methods: `play`, `pause`, `seek`, `step`, `setSpeed`, `getState`, and `destroy`. Update documents through `element.document`. Before mount or after destruction, `getState()` returns `undefined`. Playback/error events bubble across Shadow DOM boundaries.
|
|
250
|
-
|
|
251
|
-
Register in a DOM environment. DOM-free imports are safe, but that module instance cannot register after a DOM is added later.
|
|
252
|
-
|
|
253
|
-
## Documents and timelines
|
|
254
|
-
|
|
255
|
-
| Document field | Contract |
|
|
123
|
+
| `PlayerOptions` | Default / meaning |
|
|
256
124
|
| --- | --- |
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
|
|
264
|
-
Node and edge IDs are globally unique. Shapes: `rect`, `roundedRect`, `ellipse`, `cylinder`, `group`. `parentId` names a parent group; child coordinates are group-relative. Labels are plain text; `visible` defaults to `true`. Node styles: `fill`, `stroke`, `strokeWidth`, `textColor`, `dashed` (group borders). Edge styles: `stroke`, `strokeWidth`, `dashed`. Colors use hexadecimal RGB/RGBA, such as `#2563eb` or `#2563eb80`.
|
|
265
|
-
|
|
266
|
-
Routes: `straight` (default), `orthogonal`, or `curved`. Curves follow endpoint ports without avoiding other nodes. Orthogonal routes avoid visible ordinary nodes and use a fixed fallback if blocked; shortest paths and zero crossings are not guaranteed. Set `fromPort` / `toPort` to `top`, `right`, `bottom`, or `left`. Explicit orthogonal ports reserve outward endpoint segments; curved ports set tangents. Omitted ports follow the dominant center-to-center axis; ellipse endpoints lie on the contour. Simple labels wrap by estimated width and remain visible on height overflow, with a `text-overflow` warning.
|
|
267
|
-
|
|
268
|
-
Edges can connect nodes or group boundaries. Group endpoints follow evaluated geometry and explicit ports. Hidden ancestors mask connected edges. Transfers highlight the endpoint group, not its descendants. Group frames are excluded from routing obstacles.
|
|
269
|
-
|
|
270
|
-
### Fixed-size node content
|
|
271
|
-
|
|
272
|
-
`rect`, `roundedRect`, and `cylinder` support `content`; ellipses and groups support labels only. `content` and `label` are mutually exclusive, even with an empty label. `content.blocks` contains ordered `title`, `paragraph`, `list`, or `graph` blocks. Paragraph roles are `body` (default) and `auxiliary`. An empty block array displays only the shape.
|
|
273
|
-
|
|
274
|
-
```ts
|
|
275
|
-
import type { DiagramNode } from "flowsvg";
|
|
276
|
-
|
|
277
|
-
const service = {
|
|
278
|
-
id: "service", kind: "rect", x: 24, y: 24, width: 260, height: 312,
|
|
279
|
-
content: {
|
|
280
|
-
style: { padding: 16, blockGap: 14 },
|
|
281
|
-
blocks: [
|
|
282
|
-
{ type: "title", text: "Order service" },
|
|
283
|
-
{ type: "paragraph", role: "auxiliary", text: "Core responsibilities" },
|
|
284
|
-
{ type: "paragraph", text: "Validate and persist incoming orders." },
|
|
285
|
-
{ type: "paragraph", text: "Return the order number.", style: { color: "#2563eb", align: "right" } }
|
|
286
|
-
]
|
|
287
|
-
}
|
|
288
|
-
} satisfies DiagramNode;
|
|
289
|
-
```
|
|
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. |
|
|
290
131
|
|
|
291
|
-
|
|
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()`.
|
|
292
133
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
Text fields accept strings or `ContentTextSpan[]`: `{ text, style?: { fontSize, fontWeight, color } }`. Spans inherit block styles and share a baseline; alignment belongs to the block. Each line uses its largest font. Wrapping preserves newlines and Unicode graphemes, prefers English spaces, and breaks long words/URLs as needed. A grapheme spanning source styles uses its first character’s style. Layout uses width estimates, without DOM measurement.
|
|
296
|
-
|
|
297
|
-
Text styles: positive finite `fontSize`, integer `fontWeight` from 100–900, hexadecimal `color`, and `align` (`left`, `center`, `right`). Content `padding` and `blockGap` are finite and nonnegative. Defaults in diagram units: title 20/700, body 14/400, auxiliary 12/400, padding 16, block gap 14. Line height is `max(20, fontSize × 1.35)`. Explicit colors override theme defaults.
|
|
298
|
-
|
|
299
|
-
Lists accept `marker` (`none` by default, `bullet`, or `number`), `items`, and `style`. Items support `tags`, `status`, `body`, `auxiliary`, `style`, `auxiliaryStyle`, and `statusStyle`. List style sets body typography and nonnegative finite `itemGap` (default 12); item body style overrides it. Auxiliary text defaults to muted 12-unit text. Markers share a column followed by an 8-unit gap; continuation and auxiliary lines align with the body. Markers use the first present text baseline. Omitted items/parts take no space; explicit blank text preserves a line.
|
|
300
|
-
|
|
301
|
-
```ts
|
|
302
|
-
import type { ContentBlock } from "flowsvg";
|
|
303
|
-
|
|
304
|
-
const steps: ContentBlock = { type: "list", marker: "number", style: { itemGap: 16 }, items: [
|
|
305
|
-
{ body: "Validate the request.", auxiliary: "Input checks" },
|
|
306
|
-
{ body: [{ text: "Save", style: { fontWeight: 700 } }, { text: " the order." }] }
|
|
307
|
-
] };
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
The `steps` block rendered in a 300 × 180 rectangle with `content: { blocks: [steps] }`:
|
|
311
|
-
|
|
312
|
-

|
|
313
|
-
|
|
314
|
-
Tags accept `{ text, style?: { fontSize, fontWeight, color, fill } }`. Defaults: font size 11, padding 6 horizontal / 2 vertical, corner radius 3. List `tagGap` defaults to 6 and must be finite and nonnegative. Status is right-aligned, defaults to 12-unit accent text, and accepts `fontSize`, `fontWeight`, and `color` in `statusStyle`. Tags and status share a baseline when they fit; otherwise tags wrap first and status moves below. Body and auxiliary text follow. Explicit colors/fills override the theme.
|
|
315
|
-
|
|
316
|
-
```ts
|
|
317
|
-
import type { ContentBlock } from "flowsvg";
|
|
318
|
-
|
|
319
|
-
const records: ContentBlock = { type: "list", items: [{
|
|
320
|
-
tags: [{ text: "world" }, { text: "experience", style: { fill: "#eadfff", color: "#57319b" } }],
|
|
321
|
-
status: "new", body: "Alice joined Google", auxiliary: "Mar 2026"
|
|
322
|
-
}] };
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
The `records` block rendered in a 300 × 180 rectangle with `content: { blocks: [records] }`:
|
|
326
|
-
|
|
327
|
-

|
|
134
|
+
### Events
|
|
328
135
|
|
|
329
|
-
|
|
136
|
+
Native events are `CustomEvent`s on `player.element`; read `event.detail`. Vue handlers and React callbacks receive the detail directly.
|
|
330
137
|
|
|
331
|
-
|
|
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`. |
|
|
332
146
|
|
|
333
|
-
|
|
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.
|
|
334
148
|
|
|
335
|
-
|
|
149
|
+
## Export
|
|
336
150
|
|
|
337
151
|
```ts
|
|
338
|
-
import
|
|
152
|
+
import { createDiagram } from "flowsvg";
|
|
153
|
+
import { exportGif } from "flowsvg/export";
|
|
154
|
+
import { diagram } from "./diagram.js";
|
|
339
155
|
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
{ id: "google", label: "Google", x: 50, y: 110, highlighted: true },
|
|
345
|
-
{ id: "ml", label: "ML project", x: 190, y: 110 }
|
|
346
|
-
],
|
|
347
|
-
edges: [{ from: "alice", to: "google" }, { from: "alice", to: "ml" }]
|
|
348
|
-
};
|
|
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 });
|
|
349
160
|
```
|
|
350
161
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-

|
|
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.
|
|
354
163
|
|
|
355
|
-
|
|
164
|
+
## Core API — `flowsvg`
|
|
356
165
|
|
|
357
|
-
|
|
166
|
+
All times are milliseconds; positions and dimensions use diagram units. `options?: EvaluateOptions` accepts `theme?: "light" | "dark"` and `reducedMotion?: boolean` (default `false`).
|
|
358
167
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
Choose fixed dimensions and ordered blocks. Use unmarked lists for records, bullets for capabilities, and numbers for steps. This document works with all four integrations:
|
|
362
|
-
|
|
363
|
-
```ts
|
|
364
|
-
import { createDiagram, type DiagramDocument, type NodeContent } from "flowsvg";
|
|
365
|
-
|
|
366
|
-
const facts: NodeContent = { blocks: [
|
|
367
|
-
{ type: "title", text: "Facts" },
|
|
368
|
-
{ type: "paragraph", role: "auxiliary", text: "Confirmed records" },
|
|
369
|
-
{ type: "list", marker: "none", items: [{
|
|
370
|
-
tags: [{ text: "world" }, { text: "orders", style: { fill: "#eadfff", color: "#57319b" } }],
|
|
371
|
-
status: "new",
|
|
372
|
-
body: [{ text: "Order saved", style: { fontWeight: 700 } }, { text: " 2026-10-17", style: { fontSize: 11 } }],
|
|
373
|
-
auxiliary: "Ready for payment."
|
|
374
|
-
}] }
|
|
375
|
-
] };
|
|
376
|
-
const updated: NodeContent = { blocks: [
|
|
377
|
-
{ type: "title", text: "Facts" },
|
|
378
|
-
{ type: "list", items: [{ tags: [{ text: "verified" }], status: "done", body: "Payment confirmed", auxiliary: "2026-10-18" }] }
|
|
379
|
-
] };
|
|
380
|
-
const document: DiagramDocument = {
|
|
381
|
-
version: 1, viewport: { width: 300, height: 480 },
|
|
382
|
-
nodes: [{ id: "facts", kind: "cylinder", x: 24, y: 20, width: 240, height: 440, content: facts }], edges: [],
|
|
383
|
-
scenes: [{ id: "records", label: "Records", beats: [
|
|
384
|
-
{ durationMs: 1000, actions: [] },
|
|
385
|
-
{ durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: updated } }] },
|
|
386
|
-
{ durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { label: "Archived" } }] },
|
|
387
|
-
{ durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: facts } }] },
|
|
388
|
-
{ durationMs: 1000, actions: [{ type: "updateNode", target: "facts", value: { content: null } }] }
|
|
389
|
-
] }]
|
|
390
|
-
};
|
|
391
|
-
const diagram = createDiagram(document);
|
|
392
|
-
console.log(diagram.evaluateAt(1000).warnings);
|
|
393
|
-
const svg = diagram.renderSvg(1000, { theme: "dark", reducedMotion: true });
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
The initial light-theme frame at 0 ms and the updated dark-theme frame at 1000 ms:
|
|
397
|
-
|
|
398
|
-

|
|
399
|
-
|
|
400
|
-
Check `frame.warnings` before sharing. Increase node dimensions or shorten overflowing text; the SDK does not resize nodes or shrink fonts. Prefer default colors for theme/highlight contrast. Content has one node-level interaction target, without internal links, buttons, images, or HTML.
|
|
401
|
-
|
|
402
|
-
The [content story](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/content-story.html) combines Facts, lists, node updates, flows, and focus. Its [shared document](https://github.com/yokingma/FlowSVG/blob/HEAD/apps/native-example/content-story.js) also runs in the Vue and React examples.
|
|
403
|
-
|
|
404
|
-
Each beat has a positive `durationMs` and an `actions` array, with optional `transitionMs` and `caption`. Transition duration defaults to `min(250, durationMs)` and must be within `[0, durationMs]`. An omitted caption inherits the previous caption; an empty string clears it.
|
|
405
|
-
|
|
406
|
-
| Action | Example or update fields |
|
|
168
|
+
| Function | Result / behavior |
|
|
407
169
|
| --- | --- |
|
|
408
|
-
| `
|
|
409
|
-
| `
|
|
410
|
-
| `
|
|
411
|
-
| `
|
|
412
|
-
| `
|
|
413
|
-
| `focus` | `target` is a node/group ID; `null` restores the full viewport |
|
|
414
|
-
|
|
415
|
-
`updateNode.value.content` replaces all content at beat start, regardless of transition duration, and persists across beats/scenes. `label` switches to simple-label mode; `content` switches to structured content. `content: null` clears either mode without restoring old labels or resizing. Assigning both modes, or assigning one repeatedly within a beat, produces a field-path error.
|
|
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. |
|
|
416
175
|
|
|
417
|
-
|
|
418
|
-
// Each value belongs to a separate beat's updateNode action.
|
|
419
|
-
import type { NodeVisualState } from "flowsvg";
|
|
420
|
-
|
|
421
|
-
const replacement: NodeVisualState = { content: { blocks: [{ type: "title", text: "Updated" }] } };
|
|
422
|
-
const simpleLabel: NodeVisualState = { label: "Summary" };
|
|
423
|
-
const clearText: NodeVisualState = { content: null };
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
Scenes share a millisecond timeline and inherit the previous scene’s final state. Text changes at beat start; interpolated values change during the transition. Beats include their start and exclude their end; the final time returns the completed frame. Hidden nodes/groups mask connected edges; showing them restores each edge’s saved visibility.
|
|
427
|
-
|
|
428
|
-
A beat supports one transfer per distinct edge. Transfers share linear progress over `durationMs`, so longer paths move faster. Markers and endpoint highlights expire at beat end; the next beat supplies a new transfer group. Explicit highlights and ordinary styles persist.
|
|
429
|
-
|
|
430
|
-
Transfer `text` is escaped, stays horizontal, and uses estimated capsule widths without font measurement. It does not replace the edge label or create an interaction target. An empty string still produces a capsule.
|
|
431
|
-
|
|
432
|
-
## API
|
|
433
|
-
|
|
434
|
-
### Core API
|
|
435
|
-
|
|
436
|
-
Import from `flowsvg`:
|
|
437
|
-
|
|
438
|
-
| API | Result and behavior |
|
|
176
|
+
| Compiled `Diagram` | Result / behavior |
|
|
439
177
|
| --- | --- |
|
|
440
|
-
| `
|
|
441
|
-
| `
|
|
442
|
-
| `
|
|
443
|
-
| `
|
|
444
|
-
| `
|
|
445
|
-
| `diagram.renderSvg(timeMs = 0, options?)` | Exports SVG from the same evaluation result |
|
|
446
|
-
| `diagram.hitTest(timeMs, point, options?)` | Returns `{ id, type: "node" \| "edge" }` or `null`, using document coordinates |
|
|
447
|
-
| `wrapText(label, maxWidth, typography?)` | Wraps text with fixed width estimates and returns an array of lines |
|
|
448
|
-
|
|
449
|
-
`EvaluateOptions`: `{ theme?: "light" | "dark"; reducedMotion?: boolean }`. Times clamp to `[0, totalDuration]`; non-finite values become zero. Hit testing skips hidden/zero-opacity objects, then checks ordinary nodes, edges, and group backgrounds in that order.
|
|
450
|
-
|
|
451
|
-
`DiagramFrameState` includes time, scene/beat IDs, progress, evaluated nodes/edges, camera, caption, and warnings. Coordinates are absolute. Static documents have `null` scene/beat IDs; beat IDs use `${scene.id}:${beatIndex}` with zero-based indexes. Active edges expose `flow` direction, progress, point, and optional text. Markers are absent after the beat, on hidden edges, or with reduced motion.
|
|
452
|
-
|
|
453
|
-
Types and member JSDoc are included: `DiagramDocument`, `DiagramNode`, `DiagramEdge`, `Scene`, `Beat`, `Action`, `Diagram`, `DiagramFrameState`, `EvaluateOptions`, `ValidationIssue`, and `LayoutWarning`.
|
|
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`. |
|
|
454
183
|
|
|
455
|
-
|
|
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.
|
|
456
185
|
|
|
457
|
-
|
|
186
|
+
### Document and actions
|
|
458
187
|
|
|
459
|
-
|
|
|
188
|
+
| Type | Core fields |
|
|
460
189
|
| --- | --- |
|
|
461
|
-
| `
|
|
462
|
-
| `
|
|
463
|
-
| `
|
|
464
|
-
| `
|
|
465
|
-
| `
|
|
466
|
-
| `
|
|
467
|
-
| `getState()` | Returns `PlayerState`: `timeMs`, `totalDuration`, `playing`, `speed`, and `frame` |
|
|
468
|
-
| `destroy()` | Idempotently cleans up DOM, listeners, and scheduling; stops events, while state remains readable |
|
|
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. |
|
|
469
196
|
|
|
470
|
-
|
|
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`.
|
|
471
198
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
Default content colors adapt to the theme and highlight fill; authored colors and tag fills stay unchanged. Highlighting preserves layout and hit bounds. Content interactions report the parent node; clipped text adds no hit area.
|
|
475
|
-
|
|
476
|
-
`PlayerState.frame` and static SVG exclude hover. Browser output matches the same-time export when no node is hovered.
|
|
477
|
-
|
|
478
|
-
### Events
|
|
199
|
+
## Export API
|
|
479
200
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
| Event | `detail` |
|
|
201
|
+
| Entry / function | Return / requirements |
|
|
483
202
|
| --- | --- |
|
|
484
|
-
| `
|
|
485
|
-
| `
|
|
486
|
-
| `
|
|
487
|
-
| `error` | `PlayerErrorDetail`: retained player state and `error` |
|
|
488
|
-
| `hover` / `click` | `PlayerInteractionDetail` or `null`; aliases are `objecthover` / `objectclick` |
|
|
489
|
-
|
|
490
|
-
Interaction detail contains `id`, `type`, `point` (document coordinates), `timeMs`, `sceneId`, and `beatId`; misses return `null`. Adapters forward the four playback/error events. Web Component errors before mount may lack player state.
|
|
491
|
-
|
|
492
|
-
`hover` also updates when a frame change moves another object under a stationary pointer. Pointer leave/cancel emits `null`.
|
|
493
|
-
|
|
494
|
-
```ts
|
|
495
|
-
import type { PlayerEventMap } from "flowsvg/browser";
|
|
496
|
-
|
|
497
|
-
function handleTimeUpdate(event: Event): void {
|
|
498
|
-
const { detail } = event as PlayerEventMap["timeupdate"];
|
|
499
|
-
console.log(detail.timeMs, detail.frame.caption);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
player.element.addEventListener("timeupdate", handleTimeUpdate);
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
This example reuses `player` from the native integration. Remove any listeners your application adds when disposing of the host.
|
|
506
|
-
|
|
507
|
-
## Themes and accessibility
|
|
508
|
-
|
|
509
|
-
Theme: explicit option → document theme → light. The core ignores system preferences. Browser motion follows `prefers-reduced-motion` unless overridden; the core defaults to full motion. Reduced motion completes transitions immediately and hides transfer markers, preserving duration.
|
|
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. |
|
|
510
206
|
|
|
511
|
-
|
|
207
|
+
| `GifExportOptions` | Default / meaning |
|
|
208
|
+
| --- | --- |
|
|
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`. |
|
|
512
215
|
|
|
513
|
-
|
|
514
|
-
| --- | --- | --- |
|
|
515
|
-
| Background / node fill | `#ffffff` / `#ffffff` | `#101828` / `#1d2939` |
|
|
516
|
-
| Node border / text | `#cfd8e3` / `#1d2939` | `#475467` / `#f2f4f7` |
|
|
517
|
-
| Edge | `#8892a4` | `#98a2b3` |
|
|
518
|
-
| Highlight fill / border | `#eaf1ff` / `#2a63c8` | `#233b60` / `#8ab4f8` |
|
|
519
|
-
| Flow marker / marker text | `#2a63c8` / `#ffffff` | `#8ab4f8` / `#101828` |
|
|
520
|
-
|
|
521
|
-
Default node borders are 1.2 diagram units; edges are 2. Rounded rectangles use a radius of at most 8. Node labels use plain text; edge labels retain a background halo for readability across paths. Diagram text uses system fonts with Chinese fallbacks. Explicit styles replace ordinary defaults; timeline and hover highlights overlay those styles and restore the current ordinary paint when removed.
|
|
522
|
-
|
|
523
|
-
Import `flowsvg/styles.css` for control layout and focus styles. CSS variables customize controls only; core themes and document styles determine diagram colors:
|
|
524
|
-
|
|
525
|
-
```css
|
|
526
|
-
.flowsvg {
|
|
527
|
-
--flowsvg-control-background: #101828;
|
|
528
|
-
--flowsvg-control-color: #f4f7fb;
|
|
529
|
-
--flowsvg-control-border: #64748b;
|
|
530
|
-
--flowsvg-control-accent: #60a5fa;
|
|
531
|
-
--flowsvg-control-focus: #fbbf24;
|
|
532
|
-
}
|
|
533
|
-
```
|
|
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.
|
|
534
217
|
|
|
535
|
-
|
|
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.
|
|
536
219
|
|
|
537
|
-
##
|
|
220
|
+
## Other browser APIs
|
|
538
221
|
|
|
539
|
-
|
|
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.
|
|
540
224
|
|
|
541
|
-
|
|
225
|
+
## Authoring rules
|
|
542
226
|
|
|
543
|
-
|
|
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.
|
|
544
232
|
|
|
545
|
-
|
|
233
|
+
ESM with TypeScript declarations. Core runs without DOM on Node 18+; Node tools require Node 20+. Vue 3.5+ / React 18+.
|
|
546
234
|
|
|
547
|
-
[
|
|
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)
|