@graph-artifact/core 0.1.9 → 0.1.11
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/dist/ThemeContext.d.ts +5 -1
- package/dist/ThemeContext.js +17 -2
- package/dist/components/DiagramCanvas.d.ts +4 -1
- package/dist/components/DiagramCanvas.js +2 -2
- package/dist/components/GraphCanvas.d.ts +28 -0
- package/dist/components/GraphCanvas.js +113 -3
- package/dist/components/nodes/SequenceNodes.js +3 -3
- package/dist/components/nodes/StateNode.js +9 -6
- package/dist/config.d.ts +6 -0
- package/dist/config.js +8 -1
- package/dist/layout/sequenceLayout.js +6 -4
- package/dist/theme/dark.js +2 -2
- package/dist/theme/light.js +3 -2
- package/dist/theme/types.d.ts +2 -0
- package/package.json +3 -2
package/dist/ThemeContext.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import type { ThemeOverride } from './types.js';
|
|
3
3
|
import type { ThemeTokens } from './theme/index.js';
|
|
4
4
|
export interface Theme {
|
|
5
|
+
/** Theme mode used for host integration (React Flow colorMode, etc.). */
|
|
6
|
+
mode: 'dark' | 'light';
|
|
5
7
|
color: Record<string, string>;
|
|
6
8
|
font: {
|
|
7
9
|
family: string;
|
|
@@ -39,9 +41,11 @@ export interface Theme {
|
|
|
39
41
|
}
|
|
40
42
|
export declare const defaultTheme: Theme;
|
|
41
43
|
interface ThemeProviderProps {
|
|
44
|
+
/** Override the base theme used ('dark' | 'light' | custom registered theme). */
|
|
45
|
+
baseTheme?: string;
|
|
42
46
|
overrides?: ThemeOverride;
|
|
43
47
|
children: ReactNode;
|
|
44
48
|
}
|
|
45
|
-
export declare function ThemeProvider({ overrides, children }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
export declare function ThemeProvider({ baseTheme, overrides, children }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
46
50
|
export declare function useTheme(): Theme;
|
|
47
51
|
export {};
|
package/dist/ThemeContext.js
CHANGED
|
@@ -5,6 +5,8 @@ import { getThemeTokens } from './theme/index.js';
|
|
|
5
5
|
// ─── Build Theme from Tokens ──────────────────────────────────────────────────
|
|
6
6
|
function buildTheme(tokens) {
|
|
7
7
|
return {
|
|
8
|
+
// Most consumers only ship dark + light. Treat unknown themes as dark by default.
|
|
9
|
+
mode: tokens.color.darkBg2 === '#ffffff' ? 'light' : 'dark',
|
|
8
10
|
color: { ...tokens.color },
|
|
9
11
|
font: {
|
|
10
12
|
family: tokens.font.family,
|
|
@@ -72,8 +74,21 @@ function resolveTheme(instanceOverrides) {
|
|
|
72
74
|
}
|
|
73
75
|
// ─── Context & Provider ───────────────────────────────────────────────────────
|
|
74
76
|
const ThemeContext = createContext(defaultTheme);
|
|
75
|
-
export function ThemeProvider({ overrides, children }) {
|
|
76
|
-
const theme = useMemo(() =>
|
|
77
|
+
export function ThemeProvider({ baseTheme, overrides, children }) {
|
|
78
|
+
const theme = useMemo(() => {
|
|
79
|
+
if (!baseTheme)
|
|
80
|
+
return resolveTheme(overrides);
|
|
81
|
+
const tokens = getThemeTokens(baseTheme);
|
|
82
|
+
let resolved = buildTheme(tokens);
|
|
83
|
+
const { theme: configOverrides } = getConfig();
|
|
84
|
+
if (configOverrides && Object.keys(configOverrides).length > 0) {
|
|
85
|
+
resolved = deepMerge(resolved, configOverrides);
|
|
86
|
+
}
|
|
87
|
+
if (overrides && Object.keys(overrides).length > 0) {
|
|
88
|
+
resolved = deepMerge(resolved, overrides);
|
|
89
|
+
}
|
|
90
|
+
return resolved;
|
|
91
|
+
}, [baseTheme, overrides]);
|
|
77
92
|
return _jsx(ThemeContext.Provider, { value: theme, children: children });
|
|
78
93
|
}
|
|
79
94
|
export function useTheme() {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ParsedDiagram, NodeMetadata } from '../types.js';
|
|
2
2
|
import type { GraphConfig } from '../config.js';
|
|
3
|
+
import { GraphCanvas } from './GraphCanvas.js';
|
|
3
4
|
interface DiagramCanvasProps {
|
|
4
5
|
parsed: ParsedDiagram;
|
|
5
6
|
metadata?: Record<string, NodeMetadata>;
|
|
@@ -8,6 +9,8 @@ interface DiagramCanvasProps {
|
|
|
8
9
|
hideAttribution?: boolean;
|
|
9
10
|
relayoutOnMeasure?: boolean;
|
|
10
11
|
onLayoutStable?: () => void;
|
|
12
|
+
branding?: Parameters<typeof GraphCanvas>[0]['branding'];
|
|
13
|
+
download?: Parameters<typeof GraphCanvas>[0]['download'];
|
|
11
14
|
}
|
|
12
|
-
export declare function DiagramCanvas({ parsed, metadata, onNodeClick, canvasOverrides, hideAttribution, relayoutOnMeasure, onLayoutStable }: DiagramCanvasProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function DiagramCanvas({ parsed, metadata, onNodeClick, canvasOverrides, hideAttribution, relayoutOnMeasure, onLayoutStable, branding, download, }: DiagramCanvasProps): import("react/jsx-runtime").JSX.Element;
|
|
13
16
|
export {};
|
|
@@ -11,9 +11,9 @@ function UnsupportedCustomDiagram({ parsed }) {
|
|
|
11
11
|
fontSize: 14,
|
|
12
12
|
}, children: `Diagram type '${parsed.diagramType}' is detected but custom rendering is not implemented yet.` }));
|
|
13
13
|
}
|
|
14
|
-
export function DiagramCanvas({ parsed, metadata, onNodeClick, canvasOverrides, hideAttribution, relayoutOnMeasure, onLayoutStable }) {
|
|
14
|
+
export function DiagramCanvas({ parsed, metadata, onNodeClick, canvasOverrides, hideAttribution, relayoutOnMeasure, onLayoutStable, branding, download, }) {
|
|
15
15
|
if (parsed.kind === 'graph') {
|
|
16
|
-
return (_jsx(GraphCanvas, { parsed: parsed, metadata: metadata, onNodeClick: onNodeClick, canvasOverrides: canvasOverrides, hideAttribution: hideAttribution, relayoutOnMeasure: relayoutOnMeasure, onLayoutStable: onLayoutStable }));
|
|
16
|
+
return (_jsx(GraphCanvas, { parsed: parsed, metadata: metadata, onNodeClick: onNodeClick, canvasOverrides: canvasOverrides, hideAttribution: hideAttribution, relayoutOnMeasure: relayoutOnMeasure, onLayoutStable: onLayoutStable, branding: branding, download: download }));
|
|
17
17
|
}
|
|
18
18
|
return _jsx(UnsupportedCustomDiagram, { parsed: parsed });
|
|
19
19
|
}
|
|
@@ -1,12 +1,40 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
1
2
|
import '@xyflow/react/dist/style.css';
|
|
2
3
|
import type { ParsedGraph, NodeMetadata } from '../types.js';
|
|
3
4
|
import type { GraphConfig } from '../config.js';
|
|
5
|
+
type CornerPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
4
6
|
interface GraphCanvasProps {
|
|
5
7
|
parsed: ParsedGraph;
|
|
6
8
|
metadata?: Record<string, NodeMetadata>;
|
|
7
9
|
onNodeClick: (nodeId: string, nodeLabel: string) => void;
|
|
8
10
|
canvasOverrides?: Partial<GraphConfig['canvas']>;
|
|
9
11
|
hideAttribution?: boolean;
|
|
12
|
+
/** Optional branding overlays (included in exports). */
|
|
13
|
+
branding?: {
|
|
14
|
+
logo?: {
|
|
15
|
+
src: string;
|
|
16
|
+
alt?: string;
|
|
17
|
+
position?: CornerPosition;
|
|
18
|
+
width?: number;
|
|
19
|
+
height?: number;
|
|
20
|
+
opacity?: number;
|
|
21
|
+
};
|
|
22
|
+
watermark?: {
|
|
23
|
+
text: string;
|
|
24
|
+
icon?: ReactNode;
|
|
25
|
+
opacity?: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/** Optional download control (renders a button and exports the diagram as PNG). */
|
|
29
|
+
download?: {
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
position?: CornerPosition;
|
|
32
|
+
icon?: ReactNode;
|
|
33
|
+
fileName?: string;
|
|
34
|
+
pixelRatio?: number;
|
|
35
|
+
/** Background color used for export. Defaults to `canvas.containerBackground`. */
|
|
36
|
+
backgroundColor?: string;
|
|
37
|
+
};
|
|
10
38
|
/**
|
|
11
39
|
* If true, reruns dagre after React Flow measures node DOM sizes so
|
|
12
40
|
* layout reflects actual rendered content (auto-sized text/markdown).
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useCallback, useState, useEffect, useRef } from 'react';
|
|
3
|
-
import { ReactFlow, Background, Controls, MiniMap, ReactFlowProvider, useReactFlow, useNodesInitialized, useStore, useStoreApi, } from '@xyflow/react';
|
|
3
|
+
import { ReactFlow, Background, Controls, MiniMap, ReactFlowProvider, useReactFlow, useNodesInitialized, useStore, useStoreApi, Panel, } from '@xyflow/react';
|
|
4
4
|
import '@xyflow/react/dist/style.css';
|
|
5
|
+
import { toPng } from 'html-to-image';
|
|
5
6
|
import { layoutNodes, buildLayoutEdges } from '../layout/index.js';
|
|
6
7
|
import { getConfig, getNodeTypes } from '../config.js';
|
|
7
8
|
import { useTheme } from '../ThemeContext.js';
|
|
8
9
|
import { RoutedEdge } from './edges/RoutedEdge.js';
|
|
9
10
|
// Stable reference — must be outside the component to avoid infinite re-renders
|
|
10
11
|
const edgeTypes = { routed: RoutedEdge };
|
|
11
|
-
function
|
|
12
|
+
function cornerToPanelPosition(pos) {
|
|
13
|
+
return pos;
|
|
14
|
+
}
|
|
15
|
+
function defaultDownloadIcon(theme) {
|
|
16
|
+
const stroke = theme.mode === 'dark' ? theme.color.gray4 : theme.color.gray4;
|
|
17
|
+
return (_jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 3v10m0 0l-4-4m4 4l4-4M5 17v3h14v-3", fill: "none", stroke: stroke, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
18
|
+
}
|
|
19
|
+
function GraphCanvasInner({ parsed, metadata, onNodeClick, canvasOverrides, hideAttribution, branding, download, relayoutOnMeasure, onLayoutStable, }) {
|
|
12
20
|
const theme = useTheme();
|
|
13
21
|
const { canvas: baseCanvas } = getConfig();
|
|
14
22
|
const canvas = useMemo(() => ({ ...baseCanvas, ...(canvasOverrides ?? {}) }), [baseCanvas, canvasOverrides]);
|
|
@@ -264,6 +272,35 @@ function GraphCanvasInner({ parsed, metadata, onNodeClick, canvasOverrides, hide
|
|
|
264
272
|
const defaultEdgeOptions = useMemo(() => ({ animated: false, style: theme.edgeDefaults.style }), [theme.edgeDefaults.style]);
|
|
265
273
|
const miniMapNodeColor = useCallback(() => theme.color.orange1, [theme.color.orange1]);
|
|
266
274
|
const miniMapMaskColor = useMemo(() => `${theme.color.gray1}cc`, [theme.color.gray1]);
|
|
275
|
+
const exportRef = useRef(null);
|
|
276
|
+
const onDownload = useCallback(async () => {
|
|
277
|
+
if (!exportRef.current)
|
|
278
|
+
return;
|
|
279
|
+
const fileName = (download?.fileName ?? 'diagram')
|
|
280
|
+
.trim()
|
|
281
|
+
.replace(/[^\w\- ]+/g, '')
|
|
282
|
+
.replace(/\s+/g, '-')
|
|
283
|
+
.slice(0, 80) || 'diagram';
|
|
284
|
+
try {
|
|
285
|
+
const dataUrl = await toPng(exportRef.current, {
|
|
286
|
+
cacheBust: true,
|
|
287
|
+
pixelRatio: Math.max(1, download?.pixelRatio ?? 2),
|
|
288
|
+
backgroundColor: download?.backgroundColor ?? canvas.containerBackground,
|
|
289
|
+
filter: (node) => {
|
|
290
|
+
if (!(node instanceof Element))
|
|
291
|
+
return true;
|
|
292
|
+
return !node.classList.contains('ga-no-export');
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
const a = document.createElement('a');
|
|
296
|
+
a.href = dataUrl;
|
|
297
|
+
a.download = `${fileName}.png`;
|
|
298
|
+
a.click();
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// ignore export errors (e.g. CORS images, unmounted)
|
|
302
|
+
}
|
|
303
|
+
}, [canvas.containerBackground, download?.backgroundColor, download?.fileName, download?.pixelRatio]);
|
|
267
304
|
if (layoutError) {
|
|
268
305
|
return (_jsx("div", { style: {
|
|
269
306
|
width: '100%',
|
|
@@ -278,7 +315,80 @@ function GraphCanvasInner({ parsed, metadata, onNodeClick, canvasOverrides, hide
|
|
|
278
315
|
textAlign: 'center',
|
|
279
316
|
}, children: `Diagram syntax/layout error: ${layoutError}` }));
|
|
280
317
|
}
|
|
281
|
-
return (
|
|
318
|
+
return (_jsxs("div", { ref: exportRef, style: {
|
|
319
|
+
width: '100%',
|
|
320
|
+
height: '100%',
|
|
321
|
+
position: 'relative',
|
|
322
|
+
backgroundColor: canvas.containerBackground,
|
|
323
|
+
}, children: [_jsx("style", { children: `
|
|
324
|
+
/* Default React Flow background isn't transparent; force it. */
|
|
325
|
+
.react-flow {
|
|
326
|
+
background: transparent;
|
|
327
|
+
}
|
|
328
|
+
.react-flow__renderer, .react-flow__pane, .react-flow__viewport {
|
|
329
|
+
background: transparent;
|
|
330
|
+
}
|
|
331
|
+
${hideAttribution ? `
|
|
332
|
+
/* Ensure the "React Flow" attribution never shows in embeds. */
|
|
333
|
+
.react-flow__attribution { display: none !important; }
|
|
334
|
+
` : ''}
|
|
335
|
+
|
|
336
|
+
/* Theme React Flow chrome (controls/minimap) to match our tokens. */
|
|
337
|
+
.react-flow__controls-button {
|
|
338
|
+
background: ${theme.mode === 'dark' ? theme.color.darkBg3 : theme.color.white};
|
|
339
|
+
color: ${theme.mode === 'dark' ? theme.color.gray4 : theme.color.gray4};
|
|
340
|
+
border: 1px solid ${theme.color.gray2};
|
|
341
|
+
}
|
|
342
|
+
.react-flow__controls-button:hover {
|
|
343
|
+
background: ${theme.mode === 'dark' ? theme.color.gray1 : theme.color.gray1};
|
|
344
|
+
}
|
|
345
|
+
.react-flow__controls-button svg {
|
|
346
|
+
fill: currentColor;
|
|
347
|
+
}
|
|
348
|
+
.react-flow__minimap {
|
|
349
|
+
background: ${theme.mode === 'dark' ? theme.color.darkBg2 : theme.color.white};
|
|
350
|
+
border: 1px solid ${theme.color.gray2};
|
|
351
|
+
}
|
|
352
|
+
` }), branding?.logo?.src && (_jsx("div", { style: {
|
|
353
|
+
position: 'absolute',
|
|
354
|
+
zIndex: 5,
|
|
355
|
+
pointerEvents: 'none',
|
|
356
|
+
...(branding.logo.position === 'top-right' ? { top: 12, right: 12 } : {}),
|
|
357
|
+
...(branding.logo.position === 'bottom-left' ? { bottom: 12, left: 12 } : {}),
|
|
358
|
+
...(branding.logo.position === 'bottom-right' ? { bottom: 12, right: 12 } : {}),
|
|
359
|
+
...(branding.logo.position === 'top-left' || !branding.logo.position ? { top: 12, left: 12 } : {}),
|
|
360
|
+
opacity: branding.logo.opacity ?? 1,
|
|
361
|
+
}, children: _jsx("img", { src: branding.logo.src, alt: branding.logo.alt ?? 'logo', width: branding.logo.width ?? 120, height: branding.logo.height, style: { display: 'block' } }) })), branding?.watermark?.text && (_jsxs("div", { style: {
|
|
362
|
+
position: 'absolute',
|
|
363
|
+
left: '50%',
|
|
364
|
+
bottom: 14,
|
|
365
|
+
transform: 'translateX(-50%)',
|
|
366
|
+
zIndex: 5,
|
|
367
|
+
pointerEvents: 'none',
|
|
368
|
+
display: 'flex',
|
|
369
|
+
alignItems: 'center',
|
|
370
|
+
gap: 8,
|
|
371
|
+
fontFamily: theme.font.family,
|
|
372
|
+
fontSize: theme.font.size.sm,
|
|
373
|
+
color: theme.mode === 'dark' ? theme.color.gray3 : theme.color.gray4,
|
|
374
|
+
opacity: branding.watermark.opacity ?? 0.9,
|
|
375
|
+
userSelect: 'none',
|
|
376
|
+
whiteSpace: 'nowrap',
|
|
377
|
+
}, children: [branding.watermark.icon ?? null, _jsx("span", { children: branding.watermark.text })] })), _jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: resolvedNodeTypes, edgeTypes: edgeTypes, onNodeClick: handleNodeClick, fitView: canvas.fitView, minZoom: canvas.minZoom, maxZoom: canvas.maxZoom, defaultEdgeOptions: defaultEdgeOptions, zIndexMode: "manual", colorMode: theme.mode, proOptions: hideAttribution ? { hideAttribution: true } : undefined, children: [canvas.showBackground && (_jsx(Background, { color: canvas.gridColor === 'auto' ? theme.color.gray2 : canvas.gridColor, gap: canvas.backgroundGap, size: canvas.backgroundSize })), canvas.showControls && (_jsx(Controls, { position: canvas.controlsPosition,
|
|
378
|
+
// Remove the "lock" (interactive) button; we want pan/zoom always available.
|
|
379
|
+
showInteractive: false })), ((download?.enabled ?? canvas.showControls) && download?.enabled !== false) && (_jsx(Panel, { position: cornerToPanelPosition(download?.position ?? 'top-right'), className: "ga-no-export", children: _jsx("button", { type: "button", onClick: onDownload, "aria-label": "Download diagram", title: "Download", style: {
|
|
380
|
+
display: 'inline-flex',
|
|
381
|
+
alignItems: 'center',
|
|
382
|
+
justifyContent: 'center',
|
|
383
|
+
width: 34,
|
|
384
|
+
height: 34,
|
|
385
|
+
borderRadius: theme.radius.md,
|
|
386
|
+
background: theme.mode === 'dark' ? theme.color.darkBg3 : theme.color.white,
|
|
387
|
+
color: theme.color.gray4,
|
|
388
|
+
border: `1px solid ${theme.color.gray2}`,
|
|
389
|
+
boxShadow: theme.shadow.card,
|
|
390
|
+
cursor: 'pointer',
|
|
391
|
+
}, children: download?.icon ?? defaultDownloadIcon(theme) }) })), canvas.showMiniMap && (_jsx(MiniMap, { nodeColor: miniMapNodeColor, maskColor: miniMapMaskColor }))] })] }));
|
|
282
392
|
}
|
|
283
393
|
export function GraphCanvas(props) {
|
|
284
394
|
return (_jsx(ReactFlowProvider, { children: _jsx(GraphCanvasInner, { ...props }) }));
|
|
@@ -65,7 +65,7 @@ export const SequenceLifelineNode = memo(function SequenceLifelineNode({ data })
|
|
|
65
65
|
width: 2,
|
|
66
66
|
height,
|
|
67
67
|
pointerEvents: 'none',
|
|
68
|
-
}, children: _jsx("svg", { width: 2, height: height, style: { overflow: 'visible' }, children: _jsx("line", { x1: 1, y1: 0, x2: 1, y2: height, stroke: theme.color.
|
|
68
|
+
}, children: _jsx("svg", { width: 2, height: height, style: { overflow: 'visible' }, children: _jsx("line", { x1: 1, y1: 0, x2: 1, y2: height, stroke: theme.color.orange2, strokeWidth: 1.5 }) }) }));
|
|
69
69
|
});
|
|
70
70
|
// ─── Anchor Node ───────────────────────────────────────────────────────────
|
|
71
71
|
export const SequenceAnchorNode = memo(function SequenceAnchorNode() {
|
|
@@ -80,7 +80,7 @@ export const SequenceNoteNode = memo(function SequenceNoteNode({ data }) {
|
|
|
80
80
|
const ns = theme.nodeStyles.sequence;
|
|
81
81
|
return (_jsx("div", { style: {
|
|
82
82
|
width: w,
|
|
83
|
-
backgroundColor:
|
|
83
|
+
backgroundColor: ns.note.backgroundColor,
|
|
84
84
|
border: `${ns.note.borderWidth} solid ${theme.color.gray2}`,
|
|
85
85
|
borderRadius: theme.radius.sm,
|
|
86
86
|
display: 'flex',
|
|
@@ -90,7 +90,7 @@ export const SequenceNoteNode = memo(function SequenceNoteNode({ data }) {
|
|
|
90
90
|
pointerEvents: 'none',
|
|
91
91
|
boxSizing: 'border-box',
|
|
92
92
|
}, children: _jsx("span", { style: {
|
|
93
|
-
color:
|
|
93
|
+
color: ns.note.textColor,
|
|
94
94
|
fontSize: theme.font.size.sm,
|
|
95
95
|
fontFamily: theme.font.family,
|
|
96
96
|
textAlign: 'center',
|
|
@@ -38,7 +38,7 @@ export const StateNode = memo(function StateNode({ data }) {
|
|
|
38
38
|
if (shape === 'circle') {
|
|
39
39
|
return (_jsx("div", { style: {
|
|
40
40
|
width: layoutWidth, height: layoutHeight,
|
|
41
|
-
backgroundColor: theme.color.
|
|
41
|
+
backgroundColor: theme.color.orange1, borderRadius: theme.radius.full,
|
|
42
42
|
}, children: _jsx(StateHandles, { fwIn: fwIn, fwOut: fwOut }) }));
|
|
43
43
|
}
|
|
44
44
|
// End state: bullseye (outer ring + inner filled dot).
|
|
@@ -49,23 +49,26 @@ export const StateNode = memo(function StateNode({ data }) {
|
|
|
49
49
|
width: layoutWidth, height: layoutHeight,
|
|
50
50
|
boxSizing: 'border-box',
|
|
51
51
|
borderRadius: theme.radius.full,
|
|
52
|
-
border: `${theme.borderWidth.md} solid ${theme.color.
|
|
52
|
+
border: `${theme.borderWidth.md} solid ${theme.color.orange1}`,
|
|
53
53
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
54
54
|
}, children: [_jsx("div", { style: {
|
|
55
55
|
width: innerSize, height: innerSize,
|
|
56
|
-
|
|
56
|
+
// Inner fill: dark in dark mode, dark gray in light mode.
|
|
57
|
+
backgroundColor: theme.mode === 'dark' ? theme.color.darkBg2 : theme.color.gray5,
|
|
58
|
+
borderRadius: theme.radius.full,
|
|
57
59
|
} }), _jsx(StateHandles, { fwIn: fwIn, fwOut: fwOut })] }));
|
|
58
60
|
}
|
|
59
61
|
return (_jsxs("div", { style: {
|
|
60
|
-
backgroundColor: theme.color.
|
|
61
|
-
|
|
62
|
+
backgroundColor: theme.color.orange1,
|
|
63
|
+
border: `${theme.borderWidth.sm} solid ${theme.color.orange3}`,
|
|
64
|
+
borderRadius: theme.radius.pill,
|
|
62
65
|
padding: `${theme.space[3]} ${theme.space[6]}`,
|
|
63
66
|
width: layoutWidth, height: layoutHeight,
|
|
64
67
|
boxSizing: 'border-box',
|
|
65
68
|
fontFamily: theme.font.family, cursor: 'pointer',
|
|
66
69
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
67
70
|
}, children: [_jsx("div", { style: {
|
|
68
|
-
color: theme.color.
|
|
71
|
+
color: theme.color.white, fontSize: theme.font.size.lg, fontWeight: theme.font.weight.semibold,
|
|
69
72
|
textAlign: 'center', whiteSpace: 'nowrap',
|
|
70
73
|
}, children: label }), _jsx(StateHandles, { fwIn: fwIn, fwOut: fwOut })] }));
|
|
71
74
|
});
|
package/dist/config.d.ts
CHANGED
|
@@ -100,6 +100,12 @@ export interface GraphConfig {
|
|
|
100
100
|
showBackground: boolean;
|
|
101
101
|
showControls: boolean;
|
|
102
102
|
showMiniMap: boolean;
|
|
103
|
+
/** React Flow container background color. Use 'transparent' to inherit from the host app. */
|
|
104
|
+
containerBackground: string;
|
|
105
|
+
/** Background dot/grid color (if showBackground is enabled). */
|
|
106
|
+
gridColor: string;
|
|
107
|
+
/** Position of the React Flow controls cluster. */
|
|
108
|
+
controlsPosition: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
103
109
|
backgroundGap: number;
|
|
104
110
|
backgroundSize: number;
|
|
105
111
|
};
|
package/dist/config.js
CHANGED
|
@@ -69,7 +69,14 @@ function createDefaultConfig() {
|
|
|
69
69
|
fitView: true,
|
|
70
70
|
showBackground: true,
|
|
71
71
|
showControls: true,
|
|
72
|
-
|
|
72
|
+
// MiniMap is useful in dedicated viewers but noisy in embedded contexts (chat, docs).
|
|
73
|
+
// Host apps can enable it via `canvasOverrides`.
|
|
74
|
+
showMiniMap: false,
|
|
75
|
+
// Default to transparent so embeds naturally match host application background.
|
|
76
|
+
containerBackground: 'transparent',
|
|
77
|
+
// Use theme token by default (GraphCanvas resolves this when set to 'auto').
|
|
78
|
+
gridColor: 'auto',
|
|
79
|
+
controlsPosition: 'bottom-right',
|
|
73
80
|
backgroundGap: 20,
|
|
74
81
|
backgroundSize: 1,
|
|
75
82
|
},
|
|
@@ -400,6 +400,7 @@ export function layoutSequenceDiagram(sequence, theme, metadata) {
|
|
|
400
400
|
function buildSequenceEdge(id, source, target, arrowType, theme, showArrow) {
|
|
401
401
|
const isDashed = arrowType === 'dashed' || arrowType === 'dashed_open';
|
|
402
402
|
const isOpen = arrowType === 'solid_open' || arrowType === 'dashed_open';
|
|
403
|
+
const markerSize = theme.edgeDefaults.markerSize;
|
|
403
404
|
return {
|
|
404
405
|
id,
|
|
405
406
|
source,
|
|
@@ -414,8 +415,8 @@ function buildSequenceEdge(id, source, target, arrowType, theme, showArrow) {
|
|
|
414
415
|
markerEnd: {
|
|
415
416
|
type: MarkerType.ArrowClosed,
|
|
416
417
|
color: theme.color.gray3,
|
|
417
|
-
width:
|
|
418
|
-
height:
|
|
418
|
+
width: markerSize,
|
|
419
|
+
height: markerSize,
|
|
419
420
|
},
|
|
420
421
|
} : {}),
|
|
421
422
|
};
|
|
@@ -424,6 +425,7 @@ function buildSequenceRoutedEdge(id, source, target, arrowType, theme, showArrow
|
|
|
424
425
|
const isDashed = arrowType === 'dashed' || arrowType === 'dashed_open';
|
|
425
426
|
const isOpen = arrowType === 'solid_open' || arrowType === 'dashed_open';
|
|
426
427
|
const svgPath = generateRoundedPath(points, 14);
|
|
428
|
+
const markerSize = theme.edgeDefaults.markerSize;
|
|
427
429
|
return {
|
|
428
430
|
id,
|
|
429
431
|
source,
|
|
@@ -439,8 +441,8 @@ function buildSequenceRoutedEdge(id, source, target, arrowType, theme, showArrow
|
|
|
439
441
|
markerEnd: {
|
|
440
442
|
type: MarkerType.ArrowClosed,
|
|
441
443
|
color: theme.color.gray3,
|
|
442
|
-
width:
|
|
443
|
-
height:
|
|
444
|
+
width: markerSize,
|
|
445
|
+
height: markerSize,
|
|
444
446
|
},
|
|
445
447
|
} : {}),
|
|
446
448
|
};
|
package/dist/theme/dark.js
CHANGED
|
@@ -107,7 +107,7 @@ const edgeDefaults = {
|
|
|
107
107
|
labelBgBorderRadius: 4,
|
|
108
108
|
dashedPattern: '5,5',
|
|
109
109
|
thickWidth: 3,
|
|
110
|
-
markerSize:
|
|
110
|
+
markerSize: 14,
|
|
111
111
|
};
|
|
112
112
|
// ─── Shared Node Base Styles ────────────────────────────────────────────────
|
|
113
113
|
const nodeBase = {
|
|
@@ -160,7 +160,7 @@ const nodeStyles = {
|
|
|
160
160
|
sequence: {
|
|
161
161
|
number: { size: 22, fontSize: font.size.xs, borderWidth: '1.5px' },
|
|
162
162
|
block: { borderWidth: borderWidth.sm, dividerWidth: borderWidth.sm, labelCharWidth: 8, labelOffsetBase: 24 },
|
|
163
|
-
note: { borderWidth: borderWidth.sm, paddingY: space[1] },
|
|
163
|
+
note: { borderWidth: borderWidth.sm, paddingY: space[1], backgroundColor: color.darkBg3, textColor: color.gray4 },
|
|
164
164
|
},
|
|
165
165
|
subgraph: {
|
|
166
166
|
pointerEvents: 'none',
|
package/dist/theme/light.js
CHANGED
|
@@ -107,7 +107,7 @@ const edgeDefaults = {
|
|
|
107
107
|
labelBgBorderRadius: 4,
|
|
108
108
|
dashedPattern: '5,5',
|
|
109
109
|
thickWidth: 3,
|
|
110
|
-
markerSize:
|
|
110
|
+
markerSize: 14,
|
|
111
111
|
};
|
|
112
112
|
// ─── Shared Node Base Styles ────────────────────────────────────────────────
|
|
113
113
|
const nodeBase = {
|
|
@@ -160,7 +160,8 @@ const nodeStyles = {
|
|
|
160
160
|
sequence: {
|
|
161
161
|
number: { size: 22, fontSize: font.size.xs, borderWidth: '1.5px' },
|
|
162
162
|
block: { borderWidth: borderWidth.sm, dividerWidth: borderWidth.sm, labelCharWidth: 8, labelOffsetBase: 24 },
|
|
163
|
-
|
|
163
|
+
// Light mode notes: use a darker neutral so they don't disappear on white backgrounds.
|
|
164
|
+
note: { borderWidth: borderWidth.sm, paddingY: space[1], backgroundColor: color.gray2, textColor: color.gray5 },
|
|
164
165
|
},
|
|
165
166
|
subgraph: {
|
|
166
167
|
pointerEvents: 'none',
|
package/dist/theme/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graph-artifact/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Composable Mermaid-like parser, layout engine, and React renderer for interactive diagram artifacts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
"react-markdown": "^9.0.0 || ^10.0.0"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@dagrejs/dagre": "^1.1.4"
|
|
63
|
+
"@dagrejs/dagre": "^1.1.4",
|
|
64
|
+
"html-to-image": "^1.11.13"
|
|
64
65
|
},
|
|
65
66
|
"devDependencies": {
|
|
66
67
|
"typescript": "^5.7.3",
|