pele-animate 0.14.0 → 0.16.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/README.md +52 -43
- package/docs/api.md +1 -0
- package/docs/contributing.md +1 -0
- package/docs/diagram-types.md +168 -3
- package/docs/faq.md +1 -1
- package/docs/getting-started.md +9 -5
- package/docs/how-it-works.md +1 -1
- package/docs/icons.md +4 -2
- package/docs/recipes.md +6 -6
- package/docs/step-order.md +6 -0
- package/package.json +1 -1
- package/pele-animate.css +8 -3
- package/pele-animate.js +121 -8
- package/skills/pele-animate/SKILL.md +3 -3
- package/types/index.d.ts +2 -0
package/README.md
CHANGED
|
@@ -1,78 +1,55 @@
|
|
|
1
1
|
# pele-animate
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/pele-animate)
|
|
4
|
+
[](https://github.com/dkdev24/pele-animate/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
Mermaid diagrams that explain themselves one step at a time, with icons. You choose the order things appear in, readers
|
|
4
8
|
step through it, and nodes can carry icons. A small post-processor for [Pele](https://github.com/obsidianmd/pele) (MIT),
|
|
5
9
|
which renders the Mermaid; this adds the step order, the playback and the icons. It is not a theme package.
|
|
6
10
|
Unofficial add-on; not affiliated with or endorsed by the Pele or Obsidian teams.
|
|
7
|
-
The source stays valid Mermaid, so GitHub or Obsidian still show a normal diagram.
|
|
8
|
-
|
|
9
|
-
- Nodes appear in order (BFS from the sources, or pinned with a `%% pa:` comment), edges draw in, labels fade in.
|
|
10
|
-
- Hover or focus: tokens travel the edges in sequence. The intro plays once on scroll-in. Click (or Enter) enters step mode: previous / play-pause / next buttons, with Space (play/pause), ←/→ (step) and Esc (leave).
|
|
11
|
-
- `@{ icon: "name" }` nodes become illustrations from a glyph pack. Unknown names get a neutral rounded-square glyph.
|
|
12
|
-
- Sequence diagrams: participant boxes in a row get the tallest height (Pele sizes each to its own text). Group and box titles are bold.
|
|
13
|
-
- Idle state is the finished picture; `prefers-reduced-motion` turns the motion off. Light and dark themes.
|
|
14
|
-
|
|
15
|
-
## Compare
|
|
16
|
-
|
|
17
|
-
The same Mermaid source, three renderers. The pele-animate images loop: the intro draws in, then tokens travel the edges (as on hover).
|
|
18
|
-
Regenerate with `examples/capture.sh`.
|
|
19
|
-
|
|
20
|
-
```mermaid
|
|
21
|
-
flowchart LR
|
|
22
|
-
A[Player] -->|auth token| B[OTT service]
|
|
23
|
-
A -->|license request| C[DRM server]
|
|
24
|
-
C -->|license| A
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
| Renderer | Result |
|
|
28
|
-
|---|---|
|
|
29
|
-
| Mermaid | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/m1.png" width="560" alt="Flowchart rendered by Mermaid"> |
|
|
30
|
-
| Pele | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/p1.png" width="560" alt="Flowchart rendered by Pele"> |
|
|
31
|
-
| pele-animate | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/a1.gif" width="560" alt="Animated flowchart: nodes appear, edges draw in, tokens travel"> |
|
|
32
|
-
|
|
33
|
-
```mermaid
|
|
34
|
-
sequenceDiagram
|
|
35
|
-
Player->>OTT: sign in
|
|
36
|
-
OTT-->>Player: token
|
|
37
|
-
Player->>DRM: token
|
|
38
|
-
DRM-->>Player: license
|
|
39
|
-
```
|
|
40
11
|
|
|
41
|
-
|
|
42
|
-
|---|---|
|
|
43
|
-
| Mermaid | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/m2.png" width="560" alt="Sequence diagram rendered by Mermaid"> |
|
|
44
|
-
| Pele | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/p2.png" width="560" alt="Sequence diagram rendered by Pele"> |
|
|
45
|
-
| pele-animate | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/a2.gif" width="560" alt="Animated sequence diagram: messages appear in order, tokens travel"> |
|
|
46
|
-
|
|
47
|
-
With icons (`@{ icon }` nodes from the Tabler `computers` pack, `%% pa: 1=C 2=L 3=S` pins the order):
|
|
12
|
+
**[Website](https://pele-animate.pages.dev/) · [Docs](docs/getting-started.md) · [npm](https://www.npmjs.com/package/pele-animate) · [Changelog](CHANGELOG.md)**
|
|
48
13
|
|
|
49
14
|
<img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/a3.gif" width="560" alt="Animated scene with Tabler icons: a request, a load balancer and a cloud service">
|
|
50
15
|
|
|
16
|
+
- **You set the order.** Nodes appear in the order of a `%% pa:` comment, or by default along the edges; edges draw in and labels fade in.
|
|
17
|
+
- **Readers step through it.** Click (or Enter) starts step mode: previous / play-pause / next buttons, Space, ←/→ and Esc. Hover or focus sends tokens along the edges.
|
|
18
|
+
- **Icons.** `@{ icon: "name" }` nodes use any of 4,368 [Tabler](https://github.com/tabler/tabler-icons) icons (loaded per category) or your own. Unknown names get a neutral rounded square.
|
|
19
|
+
- Flowcharts, sequence diagrams, architecture diagrams, state diagrams, C4 context, block, class and ER diagrams, timelines, journeys, git graphs and Gantt charts. The finished picture is the resting state; `prefers-reduced-motion` turns the motion off. Light and dark themes.
|
|
20
|
+
- The source stays valid Mermaid. One catch: GitHub's own Mermaid renderer does not know the icon names and draws "?" boxes, so the icon examples in this repository are shown as source text.
|
|
21
|
+
|
|
51
22
|
## Install
|
|
52
23
|
|
|
53
24
|
```sh
|
|
54
25
|
npm i pele-animate pele
|
|
26
|
+
# or
|
|
27
|
+
pnpm add pele-animate pele
|
|
28
|
+
yarn add pele-animate pele
|
|
29
|
+
bun add pele-animate pele
|
|
55
30
|
```
|
|
56
31
|
|
|
32
|
+
`pele` is a peer dependency (`^1.7.0`): install it alongside. Both packages run in the browser and need the DOM, so mount them on the client (see [recipes](docs/recipes.md)). ESM only.
|
|
33
|
+
|
|
57
34
|
## Use
|
|
58
35
|
|
|
59
36
|
```js
|
|
60
37
|
import { mount } from 'pele';
|
|
61
|
-
import { watch } from 'pele-animate';
|
|
38
|
+
import { watch, prepare } from 'pele-animate';
|
|
62
39
|
import { icons } from 'pele-animate/glyphs';
|
|
63
40
|
import 'pele-animate/tabler/computers'; // one Tabler icon category; import as many as you need
|
|
64
41
|
import 'pele-animate/style.css';
|
|
65
42
|
|
|
66
43
|
for (const el of document.querySelectorAll('pre.mermaid')) {
|
|
67
44
|
const src = el.textContent;
|
|
68
|
-
mount(el, src, { icons });
|
|
45
|
+
mount(el, prepare(src), { icons });
|
|
69
46
|
watch(el, src); // re-applies after Pele re-lays-out on resize
|
|
70
47
|
}
|
|
71
48
|
```
|
|
72
49
|
|
|
73
50
|
Mermaid source:
|
|
74
51
|
|
|
75
|
-
```mermaid
|
|
52
|
+
```mermaid-icons
|
|
76
53
|
flowchart LR
|
|
77
54
|
%% pa: 1=C 2=L 3=S
|
|
78
55
|
C@{ icon: "http-get", form: "square", h: 88, label: "Request" }
|
|
@@ -98,6 +75,38 @@ Everything is in [`docs/`](docs/getting-started.md), and ships inside the npm pa
|
|
|
98
75
|
**Coding agents:** the package includes a skill, `skills/pele-animate/`, that wires the library into a project and checks the
|
|
99
76
|
result in a browser. See [For coding agents](docs/agents.md).
|
|
100
77
|
|
|
78
|
+
## Compare
|
|
79
|
+
|
|
80
|
+
The same Mermaid source, three renderers. The pele-animate images loop: the intro draws in, then tokens travel the edges (as on hover).
|
|
81
|
+
Regenerate with `examples/capture.sh`.
|
|
82
|
+
|
|
83
|
+
```mermaid
|
|
84
|
+
flowchart LR
|
|
85
|
+
A[Client] -->|request| B[API]
|
|
86
|
+
A -->|sign in| C[Auth server]
|
|
87
|
+
C -->|token| A
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Renderer | Result |
|
|
91
|
+
|---|---|
|
|
92
|
+
| Mermaid | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/m1.png" width="560" alt="Flowchart rendered by Mermaid"> |
|
|
93
|
+
| Pele | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/p1.png" width="560" alt="Flowchart rendered by Pele"> |
|
|
94
|
+
| pele-animate | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/a1.gif" width="560" alt="Animated flowchart: nodes appear, edges draw in, tokens travel"> |
|
|
95
|
+
|
|
96
|
+
```mermaid
|
|
97
|
+
sequenceDiagram
|
|
98
|
+
Client->>API: sign in
|
|
99
|
+
API-->>Client: token
|
|
100
|
+
Client->>Data: query with token
|
|
101
|
+
Data-->>Client: result
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| Renderer | Result |
|
|
105
|
+
|---|---|
|
|
106
|
+
| Mermaid | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/m2.png" width="560" alt="Sequence diagram rendered by Mermaid"> |
|
|
107
|
+
| Pele | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/p2.png" width="560" alt="Sequence diagram rendered by Pele"> |
|
|
108
|
+
| pele-animate | <img src="https://raw.githubusercontent.com/dkdev24/pele-animate/main/examples/a2.gif" width="560" alt="Animated sequence diagram: messages appear in order, tokens travel"> |
|
|
109
|
+
|
|
101
110
|
## Credits and license
|
|
102
111
|
|
|
103
112
|
MIT. Built on [Pele](https://github.com/obsidianmd/pele) by kepano (MIT); this is an unofficial add-on, not affiliated with or
|
package/docs/api.md
CHANGED
|
@@ -14,6 +14,7 @@ TypeScript types ship with the package (`types/`).
|
|
|
14
14
|
| Export | Signature | Description |
|
|
15
15
|
|---|---|---|
|
|
16
16
|
| `watch` | `watch(container: HTMLElement, source?: string, options?: { controls?: boolean }): void` | Marks the container as a scene (`pa-scene`), animates each SVG Pele renders into it and re-applies after Pele re-lays-out on resize. `controls: false` keeps click-to-replay instead of step mode. |
|
|
17
|
+
| `prepare` | `prepare(source?: string): string` | Returns the Mermaid source with icon-node labels padded so Pele lays out a box at least as wide as the icon (`h`). Pass its result to `mount()` and the original source to `watch()`. Without it, a label shorter than the icon puts edge ends and arrowheads under the icon. |
|
|
17
18
|
| `animate` | `animate(svg: SVGSVGElement, options?: { hover?: Element; source?: string }): void` | Animates one rendered svg. `source` is read for `%% pa:` pins. Sets `svg.paSteps`. |
|
|
18
19
|
| `replay` | `replay(svg: SVGSVGElement \| null \| undefined): void` | Plays the intro again. |
|
|
19
20
|
| `parseOrder` | `parseOrder(source?: string): Map<string, number>` | Parses the `%% pa:` comment: node id, or `'X>Y'` for an edge, to a 0-based step. |
|
package/docs/contributing.md
CHANGED
|
@@ -17,6 +17,7 @@ pnpm build # builds the site (a placeholder until the site lands)
|
|
|
17
17
|
|
|
18
18
|
- The package is plain ESM at the repository root: no build step. The site lives in `site/`.
|
|
19
19
|
- Docs are the Markdown files in `docs/`. A fenced block marked `mermaid` must render, and every `@{ icon }` name in it must exist.
|
|
20
|
+
- Fence convention: ```` ```mermaid live ```` is a diagram GitHub can also draw; the site animates it. A diagram with `@{ icon }` nodes uses ```` ```mermaid-icons live ````, because GitHub's Mermaid cannot draw icons (it shows "?" boxes), so GitHub shows the source text and the site animates it.
|
|
20
21
|
- Tabler packs are generated: `node scripts/build-tabler.mjs <path to a tabler-icons checkout>` rewrites `tabler/*.js`. Do not edit those
|
|
21
22
|
files by hand. The Tabler version is recorded in each file header and in `THIRD-PARTY-NOTICES.md`.
|
|
22
23
|
- Add an entry to `CHANGELOG.md` for any user-visible change. Breaking changes also get a section in `docs/migrating.md`.
|
package/docs/diagram-types.md
CHANGED
|
@@ -7,7 +7,7 @@ section: Start
|
|
|
7
7
|
|
|
8
8
|
# Diagram types
|
|
9
9
|
|
|
10
|
-
pele-animate supports **flowcharts** and **
|
|
10
|
+
pele-animate supports **flowcharts**, **sequence diagrams**, **architecture diagrams** and **state diagrams** and **C4 context diagrams** and **block diagrams**, **class diagrams**, **ER diagrams**, **timelines**, **user journeys**, **git graphs** and **Gantt charts**. Pele renders other diagram types too, but they are outside what
|
|
11
11
|
pele-animate is built for.
|
|
12
12
|
|
|
13
13
|
## Flowcharts
|
|
@@ -36,8 +36,173 @@ sequenceDiagram
|
|
|
36
36
|
|
|
37
37
|
**Limit:** Pele ignores `@{ icon }` on sequence participants (the box shows its id), so icons apply to flowcharts only.
|
|
38
38
|
|
|
39
|
+
## Architecture diagrams
|
|
40
|
+
|
|
41
|
+
`architecture-beta`: services in groups, joined by edges. Services appear in step order like flowchart nodes, groups stay visible,
|
|
42
|
+
and `%% pa:` pins work the same way. The icon in parentheses is looked up in your icon packs, so Tabler names and `doverunner-*`
|
|
43
|
+
names work (import the packs as for [icons](./icons.md)).
|
|
44
|
+
|
|
45
|
+
```mermaid-icons live
|
|
46
|
+
architecture-beta
|
|
47
|
+
group edge(cloud)[Edge]
|
|
48
|
+
group core(cloud)[Core]
|
|
49
|
+
service user(device-desktop)[User]
|
|
50
|
+
service cdn(cloud-computing)[CDN] in edge
|
|
51
|
+
service api(server)[API] in core
|
|
52
|
+
service db(database)[Database] in core
|
|
53
|
+
user:R --> L:cdn
|
|
54
|
+
cdn:R --> L:api
|
|
55
|
+
api:R --> L:db
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Icons here are small and fixed in size; `form` and `h` apply to flowcharts only. Edges are read from their written direction
|
|
59
|
+
(`user:R --> L:cdn` is user to cdn). A node id containing `-` can be mistaken for two ids on an edge, so use plain ids.
|
|
60
|
+
|
|
61
|
+
## State diagrams
|
|
62
|
+
|
|
63
|
+
`stateDiagram-v2`: states appear in step order, transitions draw from their source state, and transition labels fade in. The
|
|
64
|
+
start and end markers are nodes too, with the ids `root_start` and `root_end`, so pins can name them.
|
|
65
|
+
|
|
66
|
+
```mermaid live
|
|
67
|
+
stateDiagram-v2
|
|
68
|
+
%% pa: 1=root_start 2=Idle 3=Running 4=Paused 5=root_end
|
|
69
|
+
[*] --> Idle
|
|
70
|
+
Idle --> Running: start
|
|
71
|
+
Running --> Paused: pause
|
|
72
|
+
Paused --> Running: resume
|
|
73
|
+
Running --> [*]: done
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Without pins the order is breadth-first from the start marker, which shows `Paused` and the end marker together. Pele gives
|
|
77
|
+
state edges no readable ids, so pele-animate finds each transition's ends from where its path starts and stops. Composite
|
|
78
|
+
states and forks are untested. State diagrams take no icons.
|
|
79
|
+
|
|
80
|
+
## C4 context diagrams
|
|
81
|
+
|
|
82
|
+
`C4Context`: people and systems appear in step order and each `Rel` draws from its source, with its label. Pins use the ids you
|
|
83
|
+
give `Person(...)` and `System(...)`; an edge pin is `3=app>drm`.
|
|
84
|
+
|
|
85
|
+
```mermaid live
|
|
86
|
+
C4Context
|
|
87
|
+
%% pa: 1=viewer 2=app 3=drm,cdn
|
|
88
|
+
Person(viewer, "Viewer", "Watches video")
|
|
89
|
+
System(app, "Player app", "Plays protected video")
|
|
90
|
+
System_Ext(drm, "DRM service", "Issues licenses")
|
|
91
|
+
System(cdn, "CDN", "Delivers segments")
|
|
92
|
+
Rel(viewer, app, "uses")
|
|
93
|
+
Rel(app, drm, "license")
|
|
94
|
+
Rel(app, cdn, "segments")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Pele lays C4 diagrams out in rows, so a long label between two boxes in the same row can leave no room for the arrowhead:
|
|
98
|
+
keep `Rel` labels short. Only `C4Context` has been checked; the other C4 types and boundaries are untested. C4 shapes take no icons.
|
|
99
|
+
|
|
100
|
+
## Block diagrams
|
|
101
|
+
|
|
102
|
+
`block-beta`: blocks appear in step order and each arrow draws from its source block. Pins use the block ids.
|
|
103
|
+
|
|
104
|
+
```mermaid live
|
|
105
|
+
block-beta
|
|
106
|
+
columns 3
|
|
107
|
+
%% pa: 1=client 2=server 3=db
|
|
108
|
+
client["Client"] space server["Server"]
|
|
109
|
+
space space db[("Database")]
|
|
110
|
+
client --> server
|
|
111
|
+
server --> db
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Pele routes arrows between blocks in straight lines or short bends, so place linked blocks in the same row or column; a diagonal
|
|
115
|
+
neighbour can give a bent arrow that runs along another one. Block diagrams take no icons.
|
|
116
|
+
|
|
117
|
+
## Class diagrams
|
|
118
|
+
|
|
119
|
+
`classDiagram`: classes appear in step order, relationships draw from the class written first. `Animal <|-- Dog` reveals `Animal`
|
|
120
|
+
and then `Dog`, whichever way the arrowhead points. Pins use the class names.
|
|
121
|
+
|
|
122
|
+
```mermaid live
|
|
123
|
+
classDiagram
|
|
124
|
+
%% pa: 1=Animal 2=Dog,Cat 3=Owner
|
|
125
|
+
Animal <|-- Dog
|
|
126
|
+
Animal <|-- Cat
|
|
127
|
+
Dog --> Owner : has
|
|
128
|
+
Animal : +name
|
|
129
|
+
Dog : +bark()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## ER diagrams
|
|
133
|
+
|
|
134
|
+
`erDiagram`: entities appear in step order, relationships draw from the entity written first (`CUSTOMER ||--o{ ORDER` reveals
|
|
135
|
+
`CUSTOMER`, then `ORDER`).
|
|
136
|
+
|
|
137
|
+
```mermaid live
|
|
138
|
+
erDiagram
|
|
139
|
+
%% pa: 1=CUSTOMER 2=ORDER 3=LINE,PRODUCT
|
|
140
|
+
CUSTOMER ||--o{ ORDER : places
|
|
141
|
+
ORDER ||--|{ LINE : contains
|
|
142
|
+
PRODUCT ||--o{ LINE : in
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Neither takes icons.
|
|
146
|
+
|
|
147
|
+
## Timelines, journeys, git graphs and Gantt charts
|
|
148
|
+
|
|
149
|
+
These have no edges to follow, so each steps through its items in reading order and `%% pa:` pins do nothing (as for sequence
|
|
150
|
+
diagrams). Section headers, axes, grids and legends stay visible.
|
|
151
|
+
|
|
152
|
+
- **`timeline`:** one step per time period, with its events.
|
|
153
|
+
|
|
154
|
+
```mermaid live
|
|
155
|
+
timeline
|
|
156
|
+
title History
|
|
157
|
+
section Early
|
|
158
|
+
2002 : Launch : Beta
|
|
159
|
+
2004 : Growth
|
|
160
|
+
section Late
|
|
161
|
+
2010 : Scale
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **`journey`:** one step per task: the task, its face, and the line segment that joins it to the task before.
|
|
165
|
+
|
|
166
|
+
```mermaid live
|
|
167
|
+
journey
|
|
168
|
+
title My day
|
|
169
|
+
section Work
|
|
170
|
+
Make tea: 5: Me, Bob
|
|
171
|
+
Do work: 3: Me
|
|
172
|
+
section Home
|
|
173
|
+
Relax: 4: Me
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- **`gitGraph`:** one step per commit in the order written, with the edge that ends on it. A branch's label and lane appear with
|
|
177
|
+
its first commit.
|
|
178
|
+
|
|
179
|
+
```mermaid live
|
|
180
|
+
gitGraph
|
|
181
|
+
commit id: "a"
|
|
182
|
+
branch dev
|
|
183
|
+
commit id: "b"
|
|
184
|
+
checkout main
|
|
185
|
+
commit id: "c"
|
|
186
|
+
merge dev id: "m"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- **`gantt`:** one step per task, top to bottom.
|
|
190
|
+
|
|
191
|
+
```mermaid live
|
|
192
|
+
gantt
|
|
193
|
+
dateFormat YYYY-MM-DD
|
|
194
|
+
section Build
|
|
195
|
+
Design :a1, 2024-01-01, 3d
|
|
196
|
+
Code :after a1, 4d
|
|
197
|
+
section Ship
|
|
198
|
+
Test :2024-01-08, 2d
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
None of these take icons. Only these small examples have been checked in a browser; long diagrams, `todayMarker`, tags, cherry-picks and
|
|
202
|
+
parallel tasks are untested.
|
|
203
|
+
|
|
39
204
|
## Everything else
|
|
40
205
|
|
|
41
|
-
Other Mermaid types (
|
|
206
|
+
Other Mermaid types (pie, quadrant, radar and the rest) render through Pele as usual. pele-animate is not designed for
|
|
42
207
|
them and its behavior on them is untested, so use plain Pele for those. Step mode has been checked in a browser on
|
|
43
|
-
|
|
208
|
+
the types above only; sequence diagrams have not been checked the same way yet.
|
package/docs/faq.md
CHANGED
|
@@ -22,7 +22,7 @@ you want to explain in order.
|
|
|
22
22
|
**How big is it?** The core (`pele-animate.js` plus CSS) is about 20 KB unminified. Icon packs are separate modules, so you only
|
|
23
23
|
ship the categories you import (the largest, `system`, is about 199 KB unminified).
|
|
24
24
|
|
|
25
|
-
**Which diagrams does it support?** Flowcharts and
|
|
25
|
+
**Which diagrams does it support?** Flowcharts, sequence diagrams, architecture diagrams, state diagrams, C4 context, block, class and ER diagrams, timelines, journeys, git graphs and Gantt charts ([diagram types](./diagram-types.md)).
|
|
26
26
|
|
|
27
27
|
**Can I use my own icons?** Yes ([custom icons](./custom-icons.md)).
|
|
28
28
|
|
package/docs/getting-started.md
CHANGED
|
@@ -15,10 +15,14 @@ unofficial add-on for Pele and does not change how Pele lays out or draws diagra
|
|
|
15
15
|
|
|
16
16
|
```sh
|
|
17
17
|
npm i pele-animate pele
|
|
18
|
+
# or
|
|
19
|
+
pnpm add pele-animate pele
|
|
20
|
+
yarn add pele-animate pele
|
|
21
|
+
bun add pele-animate pele
|
|
18
22
|
```
|
|
19
23
|
|
|
20
|
-
`pele` is a peer dependency (`^1.7.0`). Both run in the browser
|
|
21
|
-
[recipes](./recipes.md) for frameworks).
|
|
24
|
+
`pele` is a peer dependency (`^1.7.0`): install it alongside. Both run in the browser and need the DOM, so mount them on the client (see
|
|
25
|
+
[recipes](./recipes.md) for frameworks). The packages are ESM only.
|
|
22
26
|
|
|
23
27
|
## First diagram
|
|
24
28
|
|
|
@@ -26,20 +30,20 @@ Put the Mermaid source in an element, then mount it with Pele and hand it to `wa
|
|
|
26
30
|
|
|
27
31
|
```js
|
|
28
32
|
import { mount } from 'pele';
|
|
29
|
-
import { watch } from 'pele-animate';
|
|
33
|
+
import { watch, prepare } from 'pele-animate';
|
|
30
34
|
import { icons } from 'pele-animate/glyphs';
|
|
31
35
|
import 'pele-animate/tabler/computers'; // icons: one Tabler category, import as many as you need
|
|
32
36
|
import 'pele-animate/style.css';
|
|
33
37
|
|
|
34
38
|
const el = document.querySelector('pre.mermaid');
|
|
35
39
|
const src = el.textContent;
|
|
36
|
-
mount(el, src, { icons }); // Pele draws the SVG
|
|
40
|
+
mount(el, prepare(src), { icons }); // Pele draws the SVG
|
|
37
41
|
watch(el, src); // pele-animate animates it, and re-applies when Pele re-lays-out on resize
|
|
38
42
|
```
|
|
39
43
|
|
|
40
44
|
The Mermaid source:
|
|
41
45
|
|
|
42
|
-
```mermaid live
|
|
46
|
+
```mermaid-icons live
|
|
43
47
|
flowchart LR
|
|
44
48
|
%% pa: 1=C 2=L 3=S
|
|
45
49
|
C@{ icon: "http-get", form: "square", h: 88, label: "Request" }
|
package/docs/how-it-works.md
CHANGED
|
@@ -16,7 +16,7 @@ Two layers, one diagram:
|
|
|
16
16
|
|
|
17
17
|
## The calls
|
|
18
18
|
|
|
19
|
-
- `mount(el, src, { icons })` is Pele's. The `icons` option is how glyphs get in (see [icons](./icons.md)).
|
|
19
|
+
- `mount(el, prepare(src), { icons })` is Pele's. The `icons` option is how glyphs get in (see [icons](./icons.md)).
|
|
20
20
|
- `watch(el, src)` is pele-animate's. It marks the container as a scene (class `pa-scene`), animates each SVG Pele renders into
|
|
21
21
|
it, and re-applies after Pele re-draws on resize. The intro is skipped on those re-draws once it has played.
|
|
22
22
|
- `animate(svg, { source })` does one SVG. Use it if you render with Pele yourself and do not want `watch()`.
|
package/docs/icons.md
CHANGED
|
@@ -9,17 +9,18 @@ section: Guides
|
|
|
9
9
|
|
|
10
10
|
A flowchart node becomes an illustrated node with Mermaid's own `@{ }` syntax:
|
|
11
11
|
|
|
12
|
-
```mermaid live
|
|
12
|
+
```mermaid-icons live
|
|
13
13
|
flowchart LR
|
|
14
14
|
A@{ icon: "device-desktop", form: "square", h: 88, label: "Laptop" }
|
|
15
15
|
B@{ icon: "server", form: "square", h: 88, label: "Server" }
|
|
16
|
-
A
|
|
16
|
+
A -->|request| B
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Both names are in the Tabler `devices` category, so this needs `import 'pele-animate/tabler/devices'`.
|
|
20
20
|
|
|
21
21
|
- `icon` is the glyph name. `label` is the caption.
|
|
22
22
|
- `form: "square", h: 88` makes a large frameless icon. Leave both out for a small icon inside a frame.
|
|
23
|
+
- Call `mount(el, prepare(src), { icons })` with `prepare` from `pele-animate`. Pele sizes an icon node from its label, so a label shorter than `h` would put edge ends and arrowheads under the icon; `prepare` pads the label so the box fits.
|
|
23
24
|
- Unknown names get a neutral rounded square, never a blank slot. Pele ships no icons of its own, so every name must come
|
|
24
25
|
from a pack you import.
|
|
25
26
|
|
|
@@ -60,6 +61,7 @@ Brand, Letters, Numbers, Zodiac and Gender are not included. Counts are for Tabl
|
|
|
60
61
|
|
|
61
62
|
Do not guess names. Look them up:
|
|
62
63
|
|
|
64
|
+
- The [icon browser](https://pele-animate.pages.dev/icons/) searches names and tags and copies a ready snippet; the same list is [icons.json](https://pele-animate.pages.dev/icons.json).
|
|
63
65
|
- Tabler's own site, [tabler.io/icons](https://tabler.io/icons), lists names and categories (use the outline style).
|
|
64
66
|
- In your project: `node_modules/pele-animate/tabler/<category>.js` lists every name in that category as an object key.
|
|
65
67
|
- Names that exist in more than one place are not a problem: Tabler names are plain (`server`, `lock`), `doverunner-*` names are prefixed.
|
package/docs/recipes.md
CHANGED
|
@@ -13,14 +13,14 @@ rendered.** Load them with dynamic `import()` inside the framework's mounted hoo
|
|
|
13
13
|
The shape is always:
|
|
14
14
|
|
|
15
15
|
```js
|
|
16
|
-
const [{ mount }, { watch }, { icons }] = await Promise.all([
|
|
16
|
+
const [{ mount }, { watch, prepare }, { icons }] = await Promise.all([
|
|
17
17
|
import('pele'),
|
|
18
18
|
import('pele-animate'),
|
|
19
19
|
import('pele-animate/glyphs'),
|
|
20
20
|
import('pele-animate/tabler/computers'), // your icon categories
|
|
21
21
|
import('pele-animate/style.css'),
|
|
22
22
|
]);
|
|
23
|
-
mount(el, src, { icons });
|
|
23
|
+
mount(el, prepare(src), { icons });
|
|
24
24
|
watch(el, src);
|
|
25
25
|
```
|
|
26
26
|
|
|
@@ -49,13 +49,13 @@ Notes for all of them:
|
|
|
49
49
|
<script>
|
|
50
50
|
// runs in the browser only
|
|
51
51
|
import { mount } from 'pele';
|
|
52
|
-
import { watch } from 'pele-animate';
|
|
52
|
+
import { watch, prepare } from 'pele-animate';
|
|
53
53
|
import { icons } from 'pele-animate/glyphs';
|
|
54
54
|
import 'pele-animate/tabler/computers';
|
|
55
55
|
import 'pele-animate/style.css';
|
|
56
56
|
document.querySelectorAll('pre.mermaid').forEach((el) => {
|
|
57
57
|
const src = el.textContent ?? '';
|
|
58
|
-
mount(el as HTMLElement, src, { icons });
|
|
58
|
+
mount(el as HTMLElement, prepare(src), { icons });
|
|
59
59
|
watch(el as HTMLElement, src);
|
|
60
60
|
});
|
|
61
61
|
</script>
|
|
@@ -69,10 +69,10 @@ Notes for all of them:
|
|
|
69
69
|
let el;
|
|
70
70
|
const src = 'flowchart LR\n A --> B';
|
|
71
71
|
onMount(async () => {
|
|
72
|
-
const [{ mount }, { watch }, { icons }] = await Promise.all([
|
|
72
|
+
const [{ mount }, { watch, prepare }, { icons }] = await Promise.all([
|
|
73
73
|
import('pele'), import('pele-animate'), import('pele-animate/glyphs'), import('pele-animate/style.css'),
|
|
74
74
|
]);
|
|
75
|
-
mount(el, src, { icons });
|
|
75
|
+
mount(el, prepare(src), { icons });
|
|
76
76
|
watch(el, src);
|
|
77
77
|
});
|
|
78
78
|
</script>
|
package/docs/step-order.md
CHANGED
|
@@ -14,6 +14,12 @@ them. By default, the order follows the edges. A `%% pa:` comment lets you set i
|
|
|
14
14
|
|
|
15
15
|
- **Flowchart:** nodes appear by level, counting from the nodes nothing points to. Each edge draws from its source once the source
|
|
16
16
|
is on screen, and its target appears as the edge lands. Branches of a decision appear together.
|
|
17
|
+
- **Architecture diagram:** like a flowchart: services appear by level, edges follow their written direction, pins work the same way.
|
|
18
|
+
- **Timeline, journey, git graph, Gantt chart:** one step per item in reading order (period, task, commit, task); pins do nothing.
|
|
19
|
+
- **Class and ER diagrams:** like a flowchart, but a relationship runs from the class or entity written first.
|
|
20
|
+
- **Block diagram:** like a flowchart, from the blocks nothing points to.
|
|
21
|
+
- **C4 context diagram:** like a flowchart, from the people and systems nothing points to.
|
|
22
|
+
- **State diagram:** like a flowchart, starting from the start marker (`root_start`); the end marker is `root_end`.
|
|
17
23
|
- **Sequence diagram:** actors first, then the messages in order.
|
|
18
24
|
|
|
19
25
|
## Pins
|
package/package.json
CHANGED
package/pele-animate.css
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
.pa-ready:not(.pa-in):not(.pa-step) .pele-node,
|
|
3
3
|
.pa-ready:not(.pa-in):not(.pa-step) .pele-edge-label,
|
|
4
4
|
.pa-ready:not(.pa-in):not(.pa-step) .pele-lifeline,
|
|
5
|
-
.pa-ready:not(.pa-in):not(.pa-step) .pele-marker
|
|
5
|
+
.pa-ready:not(.pa-in):not(.pa-step) .pele-marker,
|
|
6
|
+
.pa-ready:not(.pa-in):not(.pa-step) .pa-item { opacity: 0; }
|
|
6
7
|
.pa-ready:not(.pa-in):not(.pa-step) .pele-edge > path:not(.pele-marker) { stroke-dasharray: var(--len); stroke-dashoffset: var(--len); }
|
|
7
8
|
|
|
8
9
|
/* Keyframes carry the dash, so edges that are dashed in the diagram go back to their own dash pattern afterwards. */
|
|
9
10
|
.pa-ready.pa-in:not(.pa-step) .pele-node,
|
|
10
11
|
.pa-ready.pa-in:not(.pa-step) .pele-edge-label,
|
|
11
12
|
.pa-ready.pa-in:not(.pa-step) .pele-lifeline,
|
|
12
|
-
.pa-ready.pa-in:not(.pa-step) .pele-marker
|
|
13
|
+
.pa-ready.pa-in:not(.pa-step) .pele-marker,
|
|
14
|
+
.pa-ready.pa-in:not(.pa-step) .pa-item { animation: pa-fade .4s ease-out backwards; animation-delay: var(--d, 0s); }
|
|
13
15
|
.pa-ready.pa-in:not(.pa-step) .pele-edge > path:not(.pele-marker) { animation: pa-draw .4s ease-out backwards; animation-delay: var(--d, 0s); }
|
|
14
16
|
.pa-ready.pa-done:not(.pa-step) * { animation: none !important; }
|
|
15
17
|
@keyframes pa-fade { from { opacity: 0; } }
|
|
@@ -19,7 +21,7 @@
|
|
|
19
21
|
|
|
20
22
|
@media (prefers-reduced-motion: reduce) {
|
|
21
23
|
.pa-ready * { animation: none !important; }
|
|
22
|
-
.pa-ready:not(.pa-step) .pele-node, .pa-ready:not(.pa-step) .pele-edge-label, .pa-ready:not(.pa-step) .pele-lifeline, .pa-ready:not(.pa-step) .pele-marker { opacity: 1 !important; }
|
|
24
|
+
.pa-ready:not(.pa-step) .pele-node, .pa-ready:not(.pa-step) .pele-edge-label, .pa-ready:not(.pa-step) .pele-lifeline, .pa-ready:not(.pa-step) .pele-marker, .pa-ready:not(.pa-step) .pa-item { opacity: 1 !important; }
|
|
23
25
|
.pa-ready .pele-edge > path { stroke-dasharray: none; stroke-dashoffset: 0 !important; }
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -51,6 +53,9 @@
|
|
|
51
53
|
/* Edge labels sit on the line; give them the panel color so the line doesn't strike through the text. */
|
|
52
54
|
.pa-scene .pele-edge-label rect { fill: var(--pa-bg); }
|
|
53
55
|
|
|
56
|
+
/* Pele colours text on solid fills (timeline periods, gantt bars, git branch tags) with --_bg, which the scene makes transparent: use the panel surface instead. */
|
|
57
|
+
.pa-scene text[fill="var(--_bg)"] { fill: var(--pele-surface); }
|
|
58
|
+
|
|
54
59
|
/* Group and box titles read as headings. */
|
|
55
60
|
:where(.pa-scene) .pele-cluster-label { font-weight: 700; }
|
|
56
61
|
|
package/pele-animate.js
CHANGED
|
@@ -16,11 +16,33 @@ export function parseOrder(source = '') {
|
|
|
16
16
|
return levels;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Edge ids by diagram type: flowchart `L_A_B_0`, architecture and C4 `A-B` (repeat edges get `-N`), block `1-A-B`, class `id_A_B_1`,
|
|
20
|
+
// ER `id_entity-A-0_entity-B-1_0`. Ends follow the written order (`A <|-- B` is A, then B).
|
|
21
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
19
22
|
function endpoints(edgeId, ids) {
|
|
20
|
-
for (const s of ids)
|
|
23
|
+
for (const s of ids)
|
|
24
|
+
for (const d of ids)
|
|
25
|
+
if (new RegExp(`^(?:L_${esc(s)}_${esc(d)}_\\d+|(?:\\d+-)?${esc(s)}-${esc(d)}(?:-\\d+)?|id_${esc(s)}_${esc(d)}_\\d+|id_entity-${esc(s)}-\\d+_entity-${esc(d)}-\\d+_\\d+)$`).test(edgeId)) return [s, d];
|
|
21
26
|
return null;
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
// State diagrams name edges `edge0`, `edge1`, ... with no ends in the id, so read the ends off the geometry:
|
|
30
|
+
// the node nearest to where the path starts (source) and ends (target).
|
|
31
|
+
function geoEndpoints(edge, boxes) {
|
|
32
|
+
const path = edge.querySelector('path:not(.pele-marker)');
|
|
33
|
+
if (!path) return null;
|
|
34
|
+
const near = (pt) => {
|
|
35
|
+
let best = null, bd = 12; // an end more than 12 px from every node is not an end
|
|
36
|
+
for (const [id, b] of boxes) {
|
|
37
|
+
const d = Math.hypot(Math.max(b.x - pt.x, 0, pt.x - b.x - b.width), Math.max(b.y - pt.y, 0, pt.y - b.y - b.height));
|
|
38
|
+
if (d < bd) { bd = d; best = id; }
|
|
39
|
+
}
|
|
40
|
+
return best;
|
|
41
|
+
};
|
|
42
|
+
const s = near(path.getPointAtLength(0)), d = near(path.getPointAtLength(path.getTotalLength()));
|
|
43
|
+
return s && d ? [s, d] : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
function bfs(ids, pairs, pinned) {
|
|
25
47
|
const level = new Map(pinned);
|
|
26
48
|
const hasIn = new Set(pairs.map(([, d]) => d));
|
|
@@ -77,11 +99,97 @@ function equalizeActors(svg) {
|
|
|
77
99
|
}
|
|
78
100
|
}
|
|
79
101
|
|
|
102
|
+
// Pele sizes an icon node's layout box from its label alone but draws the icon at `h`, so a label narrower than the icon puts
|
|
103
|
+
// the edge ends (and arrowheads) under the icon. Pad such labels with non-breaking spaces so the box is at least `h` wide.
|
|
104
|
+
// Use as: mount(el, prepare(src), { icons }); keep passing the original `src` to watch().
|
|
105
|
+
const NBSP = '\u00a0';
|
|
106
|
+
const WJ = '\u2060'; // zero-width, not whitespace: keeps Pele from trimming the padding
|
|
107
|
+
export function prepare(source = '') {
|
|
108
|
+
return source.replace(/@\{([^}]*)\}/g, (all, body) => {
|
|
109
|
+
const h = +/\bh:\s*(\d+)/.exec(body)?.[1];
|
|
110
|
+
if (!h) return all;
|
|
111
|
+
const label = /\blabel:\s*"([^"]*)"/.exec(body)?.[1] ?? '';
|
|
112
|
+
// ponytail: a character counts as ~7 px (a space too); a real measurer would be exact. Over-padding only widens the gap.
|
|
113
|
+
const need = Math.ceil(h / 7) - label.length;
|
|
114
|
+
if (need <= 0) return all;
|
|
115
|
+
const pad = NBSP.repeat(Math.ceil(need / 2));
|
|
116
|
+
const next = `label: "${WJ}${pad}${label}${pad}${WJ}"`;
|
|
117
|
+
return `@{${/\blabel:/.test(body) ? body.replace(/\blabel:\s*"[^"]*"/, next) : `${body.replace(/\s*$/, '')}, ${next} `}}`;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
// Diagrams with no edges to follow (timeline, journey, gitGraph, gantt) step through their items in reading order.
|
|
123
|
+
// Returns [[element, ...], ...], one array per step, or null for any other diagram.
|
|
124
|
+
const pts = (path) => path.getAttribute('d').match(/-?[\d.]+/g)?.map(Number) ?? [];
|
|
125
|
+
function sequentialSteps(svg) {
|
|
126
|
+
const all = (sel) => [...svg.querySelectorAll(sel)];
|
|
127
|
+
if (svg.classList.contains('pele-timeline')) return all('.pele-period').map((p) => [p]);
|
|
128
|
+
if (svg.classList.contains('pele-gantt')) return all('.pele-task').map((t) => [t]);
|
|
129
|
+
if (svg.classList.contains('pele-journey')) {
|
|
130
|
+
const tasks = all('.pele-task'), faces = all('.pele-face');
|
|
131
|
+
const line = svg.querySelector('path.pele-edge');
|
|
132
|
+
// The satisfaction line is one polyline `M x,yL x,y...`: split it so each segment arrives with its task.
|
|
133
|
+
const segs = [];
|
|
134
|
+
if (line && /^M[-\d.,]+(L[-\d.,]+)+$/.test(line.getAttribute('d'))) {
|
|
135
|
+
const p = line.getAttribute('d').slice(1).split('L');
|
|
136
|
+
for (let i = 1; i < p.length; i++) {
|
|
137
|
+
const seg = line.cloneNode();
|
|
138
|
+
seg.setAttribute('d', `M${p[i - 1]}L${p[i]}`);
|
|
139
|
+
line.before(seg);
|
|
140
|
+
segs.push(seg);
|
|
141
|
+
}
|
|
142
|
+
line.remove();
|
|
143
|
+
}
|
|
144
|
+
return tasks.map((t, i) => [t, faces[i], segs[i - 1]].filter(Boolean));
|
|
145
|
+
}
|
|
146
|
+
if (svg.classList.contains('pele-gitGraph')) {
|
|
147
|
+
const commits = all('.pele-commit');
|
|
148
|
+
const at = (c) => { const e = c.querySelector('circle'); return [+e.getAttribute('cx'), +e.getAttribute('cy')]; };
|
|
149
|
+
const steps = commits.map((c) => [c]);
|
|
150
|
+
// an edge arrives with the commit it ends on
|
|
151
|
+
for (const e of all('.pele-edge')) {
|
|
152
|
+
const path = e.matches('path') ? e : e.querySelector('path');
|
|
153
|
+
const end = path.getPointAtLength(path.getTotalLength());
|
|
154
|
+
let best = steps.length - 1, bd = Infinity;
|
|
155
|
+
commits.forEach((c, i) => { const [x, y] = at(c), d = Math.hypot(x - end.x, y - end.y); if (d < bd) { bd = d; best = i; } });
|
|
156
|
+
steps[best]?.push(e);
|
|
157
|
+
}
|
|
158
|
+
// a branch (label and lane) appears with its first commit: the commit whose y is on the lane
|
|
159
|
+
for (const b of all('.pele-branch')) {
|
|
160
|
+
const y = pts(b.querySelector('.pele-lane'))[1];
|
|
161
|
+
const first = commits.findIndex((c) => Math.abs(at(c)[1] - y) < 1);
|
|
162
|
+
steps[first < 0 ? 0 : first].push(b);
|
|
163
|
+
}
|
|
164
|
+
return steps;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Intro for sequentialSteps: each step fades in after the one before; the same steps drive step mode.
|
|
170
|
+
function introSteps(svg, steps) {
|
|
171
|
+
const u = Math.min(UNIT, MAX_INTRO / Math.max(1, steps.length));
|
|
172
|
+
steps.forEach((els, i) => els.forEach((el) => { el.classList.add('pa-item'); el.style.setProperty('--d', `${i * u}s`); }));
|
|
173
|
+
svg.paSteps = steps.map((els) => els.map((el) => [el, [el]]));
|
|
174
|
+
reveal(svg);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function reveal(svg) {
|
|
178
|
+
if (!('IntersectionObserver' in window)) return svg.classList.add('pa-in');
|
|
179
|
+
svg.classList.remove('pa-in');
|
|
180
|
+
const io = new IntersectionObserver(([en]) => {
|
|
181
|
+
if (en.isIntersecting) { svg.classList.add('pa-in'); io.disconnect(); }
|
|
182
|
+
}, { threshold: 0.3 });
|
|
183
|
+
io.observe(svg);
|
|
184
|
+
}
|
|
185
|
+
|
|
80
186
|
export function animate(svg, { hover = svg, source = '' } = {}) {
|
|
81
187
|
fitGlyphs(svg);
|
|
82
188
|
equalizeActors(svg);
|
|
83
189
|
svg.querySelectorAll('.pa-dot').forEach((d) => d.remove());
|
|
84
190
|
svg.classList.add('pa-ready');
|
|
191
|
+
const seq = sequentialSteps(svg);
|
|
192
|
+
if (seq) return introSteps(svg, seq);
|
|
85
193
|
const nodes = [...svg.querySelectorAll('.pele-node')];
|
|
86
194
|
const edges = [...svg.querySelectorAll('.pele-edge')];
|
|
87
195
|
const ids = [...new Set(nodes.map((n) => n.dataset.id))];
|
|
@@ -96,7 +204,17 @@ export function animate(svg, { hover = svg, source = '' } = {}) {
|
|
|
96
204
|
edges.forEach((e, i) => { times.set(e, 0.5 + i * u); step.set(e, i + 1); });
|
|
97
205
|
nodes.forEach((n) => step.set(n, 0));
|
|
98
206
|
} else {
|
|
99
|
-
const
|
|
207
|
+
const boxes = new Map();
|
|
208
|
+
const pairs = edges.map((e) => {
|
|
209
|
+
const found = endpoints(e.dataset.id, ids);
|
|
210
|
+
if (found) return found;
|
|
211
|
+
if (!boxes.size)
|
|
212
|
+
for (const n of nodes) {
|
|
213
|
+
const b = n.getBBox(), t = n.transform.baseVal.consolidate()?.matrix;
|
|
214
|
+
boxes.set(n.dataset.id, { x: b.x + (t?.e ?? 0), y: b.y + (t?.f ?? 0), width: b.width, height: b.height });
|
|
215
|
+
}
|
|
216
|
+
return geoEndpoints(e, boxes);
|
|
217
|
+
});
|
|
100
218
|
const level = bfs(ids, pairs.filter(Boolean), parseOrder(source));
|
|
101
219
|
const u = Math.min(UNIT, MAX_INTRO / (Math.max(0, ...level.values()) + 1));
|
|
102
220
|
const k = u / UNIT;
|
|
@@ -164,12 +282,7 @@ export function animate(svg, { hover = svg, source = '' } = {}) {
|
|
|
164
282
|
hover.addEventListener('focusin', play);
|
|
165
283
|
hover.addEventListener('focusout', stop);
|
|
166
284
|
|
|
167
|
-
|
|
168
|
-
svg.classList.remove('pa-in');
|
|
169
|
-
const io = new IntersectionObserver(([en]) => {
|
|
170
|
-
if (en.isIntersecting) { svg.classList.add('pa-in'); io.disconnect(); }
|
|
171
|
-
}, { threshold: 0.3 });
|
|
172
|
-
io.observe(svg);
|
|
285
|
+
reveal(svg);
|
|
173
286
|
}
|
|
174
287
|
|
|
175
288
|
const STEP_MS = 1200; // autoplay pace in step mode
|
|
@@ -30,12 +30,12 @@ npm i pele-animate pele # or pnpm / yarn / bun; pele is a peer dependency
|
|
|
30
30
|
## 2. Mount (client side)
|
|
31
31
|
|
|
32
32
|
```js
|
|
33
|
-
const [{ mount }, { watch }, { icons }] = await Promise.all([
|
|
33
|
+
const [{ mount }, { watch, prepare }, { icons }] = await Promise.all([
|
|
34
34
|
import('pele'), import('pele-animate'), import('pele-animate/glyphs'),
|
|
35
35
|
import('pele-animate/tabler/computers'), // one import per icon category you use
|
|
36
36
|
import('pele-animate/style.css'),
|
|
37
37
|
]);
|
|
38
|
-
mount(el, src, { icons }); // Pele draws the SVG from the Mermaid text in `src`
|
|
38
|
+
mount(el, prepare(src), { icons }); // Pele draws the SVG from the Mermaid text in `src`
|
|
39
39
|
watch(el, src); // pele-animate animates it (also on resize)
|
|
40
40
|
```
|
|
41
41
|
|
|
@@ -58,7 +58,7 @@ TypeScript: a CSS side-effect import needs a `*.css` module declaration unless t
|
|
|
58
58
|
|
|
59
59
|
Icon nodes use Mermaid's own syntax:
|
|
60
60
|
|
|
61
|
-
```mermaid
|
|
61
|
+
```mermaid-icons
|
|
62
62
|
flowchart LR
|
|
63
63
|
%% pa: 1=C 2=L 3=S
|
|
64
64
|
C@{ icon: "http-get", form: "square", h: 88, label: "Request" }
|
package/types/index.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export function parseOrder(source?: string): Map<string, number>;
|
|
|
27
27
|
export function animate(svg: SVGSVGElement, options?: AnimateOptions): void;
|
|
28
28
|
|
|
29
29
|
/** Animate every svg Pele renders into `container`, re-applying after Pele re-lays-out on resize. */
|
|
30
|
+
/** Pad icon-node labels narrower than `h` so Pele's layout box is at least as wide as the drawn icon. Use as `mount(el, prepare(src), { icons })`; pass the original `src` to `watch()`. */
|
|
31
|
+
export function prepare(source?: string): string;
|
|
30
32
|
export function watch(container: HTMLElement, source?: string, options?: WatchOptions): void;
|
|
31
33
|
|
|
32
34
|
/** Play the intro again. */
|