@zombie-mermaid/svg-renderer 4.2.2 → 5.0.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 +15 -1
- package/dist/index.cjs +52 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -4
- package/dist/index.d.ts +23 -4
- package/dist/index.js +897 -849
- package/dist/index.js.map +1 -1
- package/package.json +14 -5
- package/src/__tests__/c4-label-clearance-1290.test.ts +0 -112
- package/src/__tests__/c4-line-ends-1208.test.ts +0 -52
- package/src/__tests__/c4-mermaid-reference.test.ts +0 -124
- package/src/__tests__/c4-svg.test.ts +0 -337
- package/src/__tests__/c4-text-width-1210.test.ts +0 -43
- package/src/__tests__/class-component-order-1249.test.ts +0 -40
- package/src/__tests__/class-diamond-markers-1232.test.ts +0 -59
- package/src/__tests__/class-member-right-padding-1238.test.ts +0 -43
- package/src/__tests__/class-namespace-1196.test.ts +0 -165
- package/src/__tests__/class-namespace-sparse-elk-1196.test.ts +0 -96
- package/src/__tests__/class-visibility-markers-1237.test.ts +0 -27
- package/src/__tests__/edge-jog-straightening-1388.test.ts +0 -57
- package/src/__tests__/elk-adapter-utils.test.ts +0 -166
- package/src/__tests__/er-attribute-right-padding-1262.test.ts +0 -47
- package/src/__tests__/er-cardinality-markers-1233.test.ts +0 -35
- package/src/__tests__/er-default-direction-1250.test.ts +0 -69
- package/src/__tests__/er-edge-label-contrast-1244.test.ts +0 -58
- package/src/__tests__/fixtures/c4-mermaid-reference.json +0 -626
- package/src/__tests__/flowchart-parallel-edge-lanes-1308.test.ts +0 -168
- package/src/__tests__/pie-layout.test.ts +0 -215
- package/src/__tests__/pie-svg.test.ts +0 -194
- package/src/__tests__/render-mermaid-svg.test.ts +0 -64
- package/src/__tests__/sequence-label-clearance-1242.test.ts +0 -162
- package/src/__tests__/sequence-nested-activation-1241.test.ts +0 -37
- package/src/__tests__/svg-edge-to-own-cluster-1310.test.ts +0 -32
- package/src/__tests__/xychart-smooth-curve.test.ts +0 -42
- package/src/__tests__/xychart-x-label-overlap-1243.test.ts +0 -62
- package/src/c4/arial-widths.ts +0 -59
- package/src/c4/layout.ts +0 -680
- package/src/c4/metrics.ts +0 -261
- package/src/c4/renderer.ts +0 -366
- package/src/class/layout.ts +0 -525
- package/src/class/renderer.ts +0 -669
- package/src/edge-curves.ts +0 -205
- package/src/elk-instance.ts +0 -292
- package/src/er/layout.ts +0 -216
- package/src/er/renderer.ts +0 -516
- package/src/generated/mono-font-subset.ts +0 -32
- package/src/index.ts +0 -277
- package/src/layout-engine/back-edges.ts +0 -73
- package/src/layout-engine/compound-flat.ts +0 -916
- package/src/layout-engine/constants.ts +0 -19
- package/src/layout-engine/edge-bundling.ts +0 -458
- package/src/layout-engine/elk-adapter-utils.ts +0 -81
- package/src/layout-engine/elk-graph-builder.ts +0 -245
- package/src/layout-engine/from-elk.ts +0 -767
- package/src/layout-engine/inner-edges.ts +0 -329
- package/src/layout-engine/jog-straightening.ts +0 -52
- package/src/layout-engine/layer-alignment.ts +0 -174
- package/src/layout-engine/layout-hints.ts +0 -27
- package/src/layout-engine/to-elk.ts +0 -855
- package/src/layout-engine.ts +0 -115
- package/src/pie/layout.ts +0 -234
- package/src/pie/renderer.ts +0 -161
- package/src/registry.ts +0 -441
- package/src/renderer.ts +0 -1510
- package/src/resolve-colors.ts +0 -339
- package/src/sequence/layout.ts +0 -766
- package/src/sequence/renderer.ts +0 -687
- package/src/shape-clipping.ts +0 -197
- package/src/styles.ts +0 -145
- package/src/title-gaps.ts +0 -161
- package/src/xychart/layout.ts +0 -727
- package/src/xychart/renderer.ts +0 -676
package/src/index.ts
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// @zombie-mermaid/svg-renderer — SVG emission and ELK-backed layout
|
|
3
|
-
//
|
|
4
|
-
// Every file re-exported below was grep-verified (zombie-mermaid#625,
|
|
5
|
-
// umbrella #620) as unreachable from `src/ascii/index.ts` except through
|
|
6
|
-
// the SVG-side modules the umbrella still owns — so nothing here can put
|
|
7
|
-
// `elkjs` on the ASCII renderer's dependency path once #623 extracts it.
|
|
8
|
-
//
|
|
9
|
-
// This package depends on `@zombie-mermaid/core` and, since #624, on
|
|
10
|
-
// `@zombie-mermaid/mermaid-parser` too — an ordinary, acyclic dependency
|
|
11
|
-
// (both `svg-renderer` and the future `ascii-renderer` depend on
|
|
12
|
-
// `mermaid-parser` directly, per the scoping doc's finding 2), not a cycle:
|
|
13
|
-
// every per-type `layout.ts`/`renderer.ts` below needs the positioned-
|
|
14
|
-
// diagram types `mermaid-parser`'s `types.ts` halves own, and
|
|
15
|
-
// `class/layout.ts` needs `formatClassMember` from `mermaid-parser`'s
|
|
16
|
-
// `class/format.ts`. It deliberately does NOT re-export the internal
|
|
17
|
-
// `layout-engine/` modules the umbrella never imports
|
|
18
|
-
// (`constants.ts` — whose `DEFAULTS` would collide with `core`'s theme
|
|
19
|
-
// `DEFAULTS` anyway — plus `edge-bundling.ts`, `from-elk.ts`,
|
|
20
|
-
// `layer-alignment.ts` and `to-elk.ts`): they are `layoutGraphSync()`'s
|
|
21
|
-
// implementation, not its API. `elk-graph-builder.ts` is the exception:
|
|
22
|
-
// `class/layout.ts` and `er/layout.ts` (moved into this package under
|
|
23
|
-
// #624) call its primitives directly (zombie-mermaid#616), so it is part
|
|
24
|
-
// of the public API alongside `elk-adapter-utils.ts`.
|
|
25
|
-
//
|
|
26
|
-
// `layout.ts` used to be a thin re-export of `layoutGraphSync` from
|
|
27
|
-
// `layout-engine.ts`; it was folded into `layout-engine.ts` directly
|
|
28
|
-
// (#1109), so `layoutGraphSync` now comes from the one star-export below.
|
|
29
|
-
//
|
|
30
|
-
// `class/`, `er/`, `sequence/`, `xychart/` hold each diagram type's
|
|
31
|
-
// renderer half (`layout.ts` + `renderer.ts`) — the other half
|
|
32
|
-
// (`parser.ts`/`types.ts`, plus `class/format.ts`, `sequence/box-color.ts`,
|
|
33
|
-
// `sequence/activation-check.ts`, `xychart/colors.ts`) moved to
|
|
34
|
-
// `@zombie-mermaid/mermaid-parser` under the same issue (#624), per the
|
|
35
|
-
// scoping doc's finding 1: each per-type directory used to mix both halves
|
|
36
|
-
// in one place, and the split point already existed at the file level.
|
|
37
|
-
//
|
|
38
|
-
// `renderMermaidSVG` (below) is the single "Mermaid text in, SVG out" front
|
|
39
|
-
// door — moved here from the umbrella's `src/index.ts` under issue #1111,
|
|
40
|
-
// for parity with `@zombie-mermaid/ascii-renderer`'s `renderMermaidASCII`.
|
|
41
|
-
// `./registry.ts` (not re-exported — same as ascii-renderer's own
|
|
42
|
-
// `registry.ts` — it's this front door's internal dispatch table, not part
|
|
43
|
-
// of the public API) drives per-diagram-type dispatch; see that file's
|
|
44
|
-
// header for why it's a separate module from the ASCII side's table.
|
|
45
|
-
// ============================================================================
|
|
46
|
-
|
|
47
|
-
export * from './edge-curves.ts'
|
|
48
|
-
export * from './elk-instance.ts'
|
|
49
|
-
export * from './layout-engine.ts'
|
|
50
|
-
export * from './layout-engine/elk-adapter-utils.ts'
|
|
51
|
-
export * from './layout-engine/elk-graph-builder.ts'
|
|
52
|
-
export * from './renderer.ts'
|
|
53
|
-
export * from './resolve-colors.ts'
|
|
54
|
-
export * from './shape-clipping.ts'
|
|
55
|
-
export * from './styles.ts'
|
|
56
|
-
|
|
57
|
-
export * from './class/layout.ts'
|
|
58
|
-
export * from './class/renderer.ts'
|
|
59
|
-
export * from './er/layout.ts'
|
|
60
|
-
export * from './er/renderer.ts'
|
|
61
|
-
export * from './sequence/layout.ts'
|
|
62
|
-
export * from './sequence/renderer.ts'
|
|
63
|
-
export * from './xychart/layout.ts'
|
|
64
|
-
export * from './xychart/renderer.ts'
|
|
65
|
-
export * from './pie/layout.ts'
|
|
66
|
-
export * from './pie/renderer.ts'
|
|
67
|
-
|
|
68
|
-
import { decodeXML } from 'entities'
|
|
69
|
-
import type {
|
|
70
|
-
RenderOptions,
|
|
71
|
-
DiagramColors,
|
|
72
|
-
SvgEmitOptions,
|
|
73
|
-
DiagramType,
|
|
74
|
-
} from '@zombie-mermaid/core'
|
|
75
|
-
import {
|
|
76
|
-
DEFAULTS,
|
|
77
|
-
themeStyleDeclarations,
|
|
78
|
-
isMonospaceFont,
|
|
79
|
-
setMonospaceMetrics,
|
|
80
|
-
detectDiagramType,
|
|
81
|
-
splitStatements,
|
|
82
|
-
} from '@zombie-mermaid/core'
|
|
83
|
-
import { resolveCssColors } from './resolve-colors.ts'
|
|
84
|
-
import { resolveFontSizes } from './styles.ts'
|
|
85
|
-
import { diagramRegistry } from './registry.ts'
|
|
86
|
-
import type { SvgRenderContext } from './registry.ts'
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Build a DiagramColors object from render options.
|
|
90
|
-
* Uses DEFAULTS for bg/fg when not provided, and passes through
|
|
91
|
-
* optional enrichment colors (line, accent, muted, surface, border).
|
|
92
|
-
*/
|
|
93
|
-
function buildColors(options: RenderOptions): DiagramColors {
|
|
94
|
-
return {
|
|
95
|
-
bg: options.bg ?? DEFAULTS.bg,
|
|
96
|
-
fg: options.fg ?? DEFAULTS.fg,
|
|
97
|
-
line: options.line,
|
|
98
|
-
accent: options.accent,
|
|
99
|
-
muted: options.muted,
|
|
100
|
-
surface: options.surface,
|
|
101
|
-
border: options.border,
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* The exact CSS declaration list the root `<svg style="…">` attribute would
|
|
107
|
-
* carry for these options — `--bg`, `--fg`, whichever enrichment colours
|
|
108
|
-
* were given, and (unless `transparent`) `background: var(--bg)`.
|
|
109
|
-
*
|
|
110
|
-
* For hosts with a strict `Content-Security-Policy`: a `style=` attribute
|
|
111
|
-
* can't be nonced, so a `style-src` without `'unsafe-inline'` drops it and
|
|
112
|
-
* the diagram loses its colours. Render with `styleAttribute: false` and
|
|
113
|
-
* put this string in your own stylesheet on the SVG (or any ancestor —
|
|
114
|
-
* custom properties inherit) instead. Pass the same options object to both
|
|
115
|
-
* calls so the declarations match what the render expects. See
|
|
116
|
-
* `RenderOptions.styleAttribute` / `RenderOptions.nonce` and issue #216.
|
|
117
|
-
*
|
|
118
|
-
* Built by the same function that fills the attribute in normal renders,
|
|
119
|
-
* so there is one variable list to keep in sync. The string is compact
|
|
120
|
-
* (`--bg:#fff;--fg:#000;background:var(--bg)`) — valid inside any rule
|
|
121
|
-
* block — and the colour values are yours, unescaped, exactly as the
|
|
122
|
-
* attribute has always carried them.
|
|
123
|
-
*
|
|
124
|
-
* @example
|
|
125
|
-
* ```ts
|
|
126
|
-
* const opts = { bg: '#1a1b26', fg: '#a9b1d6', nonce, styleAttribute: false }
|
|
127
|
-
* const svg = renderMermaidSVG('graph TD\n A --> B', opts)
|
|
128
|
-
* const css = `.diagram svg { ${themeCssVariables(opts)} }`
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
131
|
-
export function themeCssVariables(options: RenderOptions = {}): string {
|
|
132
|
-
return themeStyleDeclarations(buildColors(options), options.transparent)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Resolve the effective strict-CSP emission controls from the public
|
|
137
|
-
* options. Kept as one object so every renderer takes it as a single
|
|
138
|
-
* trailing parameter — see `SvgEmitOptions` in packages/core/src/theme.ts.
|
|
139
|
-
*/
|
|
140
|
-
function resolveSvgEmit(options: RenderOptions): SvgEmitOptions {
|
|
141
|
-
return {
|
|
142
|
-
nonce: options.nonce,
|
|
143
|
-
styleAttribute: options.styleAttribute,
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Interactivity-derived render gates — whether xychart hover tooltips
|
|
148
|
-
// render, whether flowchart/state edge animation (`e1@{ animate: true }`)
|
|
149
|
-
// plays, and whether `click`-based links/`<title>` tooltips render — all
|
|
150
|
-
// now live next to their diagram type's registry entry in ./registry.ts
|
|
151
|
-
// (`resolveAnimationEnabled`/`resolveLinksEnabled` there, duplicated rather
|
|
152
|
-
// than imported since that module is imported BY this file — see those
|
|
153
|
-
// functions' own comments for why), since every diagram type's SVG dispatch
|
|
154
|
-
// is fully handled by the registry lookup below and there is no remaining
|
|
155
|
-
// switch case here for them to serve.
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Render Mermaid diagram text to an SVG string — synchronously.
|
|
159
|
-
*
|
|
160
|
-
* Uses elk.bundled.js with a direct FakeWorker bypass (no setTimeout(0) delay).
|
|
161
|
-
* The ELK singleton is created lazily on first use and cached forever.
|
|
162
|
-
*
|
|
163
|
-
* Use this in React components with useMemo() to avoid flash:
|
|
164
|
-
* const svg = useMemo(() => renderMermaidSVG(code, opts), [code])
|
|
165
|
-
*
|
|
166
|
-
* @param text - Mermaid source text
|
|
167
|
-
* @param options - Rendering options (colors, font, spacing)
|
|
168
|
-
* @returns A self-contained SVG string
|
|
169
|
-
*
|
|
170
|
-
* @example
|
|
171
|
-
* ```ts
|
|
172
|
-
* const svg = renderMermaidSVG('graph TD\n A --> B')
|
|
173
|
-
*
|
|
174
|
-
* // With theme
|
|
175
|
-
* const svg = renderMermaidSVG('graph TD\n A --> B', {
|
|
176
|
-
* bg: '#1a1b26', fg: '#a9b1d6'
|
|
177
|
-
* })
|
|
178
|
-
*
|
|
179
|
-
* // With CSS variables (for live theme switching)
|
|
180
|
-
* const svg = renderMermaidSVG('graph TD\n A --> B', {
|
|
181
|
-
* bg: 'var(--background)', fg: 'var(--foreground)', transparent: true
|
|
182
|
-
* })
|
|
183
|
-
*
|
|
184
|
-
* // With the original source stamped onto the root <svg> as data-src —
|
|
185
|
-
* // handy for a "copy source" button or an "open in Mermaid Live" link
|
|
186
|
-
* // without re-attaching it via string surgery on the output.
|
|
187
|
-
* const svg = renderMermaidSVG('graph TD\n A --> B', { embedSource: true })
|
|
188
|
-
*
|
|
189
|
-
* // With an accessible name — role="img" + aria-labelledby pointing at a
|
|
190
|
-
* // <title> child, so assistive tech announces the diagram instead of
|
|
191
|
-
* // reading every node label individually (see issue #215).
|
|
192
|
-
* const svg = renderMermaidSVG('graph TD\n A --> B', {
|
|
193
|
-
* title: 'Flowchart: Build → Test → Ship'
|
|
194
|
-
* })
|
|
195
|
-
*
|
|
196
|
-
* // Decorative diagram — already described in surrounding prose, so it's
|
|
197
|
-
* // hidden from assistive tech (aria-hidden="true") instead of named.
|
|
198
|
-
* const svg = renderMermaidSVG('graph TD\n A --> B', { decorative: true })
|
|
199
|
-
* ```
|
|
200
|
-
*/
|
|
201
|
-
export function renderMermaidSVG(
|
|
202
|
-
text: string,
|
|
203
|
-
options: RenderOptions = {},
|
|
204
|
-
): string {
|
|
205
|
-
const svg = renderMermaidSVGRaw(text, options)
|
|
206
|
-
return options.resolveColors
|
|
207
|
-
? resolveCssColors(svg, buildColors(options))
|
|
208
|
-
: svg
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** The renderer proper — `renderMermaidSVG` minus the optional `resolveColors` post-pass. */
|
|
212
|
-
function renderMermaidSVGRaw(text: string, options: RenderOptions): string {
|
|
213
|
-
// Decode XML entities that may leak from markdown parsers (e.g. rehype-raw).
|
|
214
|
-
// Without this, escapeXml() double-encodes them: < → &lt; → literal "<" in SVG.
|
|
215
|
-
// `text` itself is left untouched so `embedSource` below stamps the exact
|
|
216
|
-
// string the caller passed in, not this entity-decoded copy used
|
|
217
|
-
// internally for parsing.
|
|
218
|
-
const decoded = decodeXML(text)
|
|
219
|
-
|
|
220
|
-
const colors = buildColors(options)
|
|
221
|
-
const font = options.font ?? 'Inter'
|
|
222
|
-
// Box sizing depends on the metrics model, so pick it before any layout runs.
|
|
223
|
-
setMonospaceMetrics(isMonospaceFont(font))
|
|
224
|
-
const transparent = options.transparent ?? false
|
|
225
|
-
const fontSizes = resolveFontSizes(options.fontSizes)
|
|
226
|
-
const diagramType: DiagramType = detectDiagramType(decoded)
|
|
227
|
-
const embedSource = options.embedSource ? text : undefined
|
|
228
|
-
const title = options.title
|
|
229
|
-
const decorative = options.decorative
|
|
230
|
-
const emit = resolveSvgEmit(options)
|
|
231
|
-
|
|
232
|
-
const lines = splitStatements(decoded)
|
|
233
|
-
|
|
234
|
-
// Registry dispatch (see ./registry.ts): every diagram type, including
|
|
235
|
-
// 'flowchart' (and the 'state' pipeline it shares), is registered there.
|
|
236
|
-
// `parse` takes both `lines` (what every already-registered type's parser
|
|
237
|
-
// wants) and `decoded` (the raw text flowchart/state's parser needs
|
|
238
|
-
// instead — see the `parse` doc comment on `DiagramModule` in
|
|
239
|
-
// ./registry.ts).
|
|
240
|
-
const registered = diagramRegistry[diagramType]
|
|
241
|
-
const diagram = registered.parse(lines, decoded)
|
|
242
|
-
const positioned = registered.layoutForSvg(diagram, options)
|
|
243
|
-
const ctx: SvgRenderContext = {
|
|
244
|
-
colors,
|
|
245
|
-
font,
|
|
246
|
-
transparent,
|
|
247
|
-
fontSizes,
|
|
248
|
-
embedSource,
|
|
249
|
-
title,
|
|
250
|
-
decorative,
|
|
251
|
-
emit,
|
|
252
|
-
}
|
|
253
|
-
return registered.renderSvg(positioned, ctx, options)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Render Mermaid diagram text to an SVG string — async.
|
|
258
|
-
*
|
|
259
|
-
* Same result as renderMermaidSVG() but returns a Promise.
|
|
260
|
-
* Useful in async contexts (server handlers, data loaders, etc.)
|
|
261
|
-
*/
|
|
262
|
-
export async function renderMermaidSVGAsync(
|
|
263
|
-
text: string,
|
|
264
|
-
options: RenderOptions = {},
|
|
265
|
-
): Promise<string> {
|
|
266
|
-
return renderMermaidSVG(text, options)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// ---------------------------------------------------------------------------
|
|
270
|
-
// Backward-compatible aliases
|
|
271
|
-
// ---------------------------------------------------------------------------
|
|
272
|
-
|
|
273
|
-
/** @deprecated Use `renderMermaidSVG` */
|
|
274
|
-
export const renderMermaidSync = renderMermaidSVG
|
|
275
|
-
|
|
276
|
-
/** @deprecated Use `renderMermaidSVGAsync` */
|
|
277
|
-
export const renderMermaid = renderMermaidSVGAsync
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Which edges of a flowchart are "back edges", found the way mermaid.js finds
|
|
3
|
-
* them.
|
|
4
|
-
*
|
|
5
|
-
* mermaid lays a flowchart out with dagre, which makes the graph acyclic by a
|
|
6
|
-
* depth-first walk: starting from each node in order and following out-edges
|
|
7
|
-
* in source order, an edge that points at a node still on the walk's stack is
|
|
8
|
-
* reversed. Which edge of a cycle gets reversed decides the whole layout. In
|
|
9
|
-
* the CI/CD sample `D -.-> A` closes the cycle `A -> B -> D -> A`, and walking
|
|
10
|
-
* `A -> B -> C -> E -> F` reaches `D` through `F --> D` first, so `D -.-> A` is
|
|
11
|
-
* the back edge and `D` ("Fix & Retry") sits after `F`.
|
|
12
|
-
*
|
|
13
|
-
* ELK's own cycle breaking picks a different edge (`F --> D`), puts `D` early,
|
|
14
|
-
* and draws the long way round. It also treats a subgraph's contents
|
|
15
|
-
* separately from the rest of the graph, so no ELK setting reproduces the
|
|
16
|
-
* dagre result (#1239). Handing ELK these edges already reversed means it never
|
|
17
|
-
* has a cycle to break.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import type { MermaidGraph } from '@zombie-mermaid/core'
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Indices into `graph.edges` of the edges a depth-first walk reverses.
|
|
24
|
-
* Self-loops are never back edges here; layout handles them on their own.
|
|
25
|
-
*
|
|
26
|
-
* `walkOrder` is the order the walk starts from; it defaults to the graph's
|
|
27
|
-
* node order, which is mermaid's (first appearance in the source).
|
|
28
|
-
*/
|
|
29
|
-
export function findBackEdgeIndexes(
|
|
30
|
-
graph: MermaidGraph,
|
|
31
|
-
walkOrder?: Iterable<string>,
|
|
32
|
-
): Set<number> {
|
|
33
|
-
const outEdges = new Map<string, number[]>()
|
|
34
|
-
graph.edges.forEach((edge, index) => {
|
|
35
|
-
if (edge.source === edge.target) return
|
|
36
|
-
const list = outEdges.get(edge.source)
|
|
37
|
-
if (list) list.push(index)
|
|
38
|
-
else outEdges.set(edge.source, [index])
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const back = new Set<number>()
|
|
42
|
-
const visited = new Set<string>()
|
|
43
|
-
const onStack = new Set<string>()
|
|
44
|
-
|
|
45
|
-
// Iterative, so a long chain can't overflow the call stack.
|
|
46
|
-
for (const root of walkOrder ?? graph.nodes.keys()) {
|
|
47
|
-
if (visited.has(root)) continue
|
|
48
|
-
const stack: Array<{ node: string; next: number }> = [
|
|
49
|
-
{ node: root, next: 0 },
|
|
50
|
-
]
|
|
51
|
-
visited.add(root)
|
|
52
|
-
onStack.add(root)
|
|
53
|
-
while (stack.length > 0) {
|
|
54
|
-
const frame = stack[stack.length - 1]!
|
|
55
|
-
const edges = outEdges.get(frame.node) ?? []
|
|
56
|
-
if (frame.next >= edges.length) {
|
|
57
|
-
onStack.delete(frame.node)
|
|
58
|
-
stack.pop()
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
const index = edges[frame.next++]!
|
|
62
|
-
const target = graph.edges[index]!.target
|
|
63
|
-
if (onStack.has(target)) {
|
|
64
|
-
back.add(index)
|
|
65
|
-
} else if (!visited.has(target)) {
|
|
66
|
-
visited.add(target)
|
|
67
|
-
onStack.add(target)
|
|
68
|
-
stack.push({ node: target, next: 0 })
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return back
|
|
73
|
-
}
|