@inkra/react 0.11.0 → 0.13.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.
@@ -49,6 +49,13 @@ export interface NodeGraphControlLabels {
49
49
  zoomOut: string;
50
50
  fitView: string;
51
51
  }
52
+ export interface NodeGraphControlsOptions {
53
+ zoomIn?: boolean;
54
+ zoomOut?: boolean;
55
+ fitView?: boolean;
56
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
57
+ }
58
+ export type NodeGraphExtraControls = ReactNode | ((api: NodeGraphRef) => ReactNode);
52
59
  export interface NodeGraphRootProps<NodeType extends NodeGraphNode = NodeGraphNode, EdgeType extends NodeGraphEdge = NodeGraphEdge> extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onError'> {
53
60
  /** Immutable snapshots: replace the array and each changed node (including changed nested data). */
54
61
  nodes: NodeType[];
@@ -63,7 +70,8 @@ export interface NodeGraphRootProps<NodeType extends NodeGraphNode = NodeGraphNo
63
70
  renderEdgeLabel?: (edge: EdgeType) => ReactNode;
64
71
  readOnly?: boolean;
65
72
  emptyState?: ReactNode;
66
- controls?: boolean;
73
+ controls?: boolean | NodeGraphControlsOptions;
74
+ extraControls?: NodeGraphExtraControls;
67
75
  controlLabels?: Partial<NodeGraphControlLabels>;
68
76
  fitView?: boolean;
69
77
  fitViewOptions?: FitViewOptions<NodeType>;
@@ -77,6 +85,7 @@ export interface NodeGraphRootProps<NodeType extends NodeGraphNode = NodeGraphNo
77
85
  zoomOnPinch?: boolean;
78
86
  connectOnClick?: boolean;
79
87
  onlyRenderVisibleElements?: boolean;
88
+ hideAttribution?: boolean;
80
89
  }
81
90
  export interface NodeGraphPortProps extends Omit<HTMLAttributes<HTMLDivElement>, 'id' | 'children'> {
82
91
  id: string;
@@ -106,12 +115,16 @@ export interface NodeGraphEdgeProps {
106
115
  className?: string;
107
116
  }
108
117
  export interface NodeGraphControlsProps extends HTMLAttributes<HTMLDivElement> {
118
+ zoomIn?: boolean;
119
+ zoomOut?: boolean;
120
+ fitView?: boolean;
121
+ extraControls?: NodeGraphExtraControls;
109
122
  fitViewOptions?: FitViewOptions;
110
123
  zoomInLabel?: string;
111
124
  zoomOutLabel?: string;
112
125
  fitViewLabel?: string;
113
126
  }
114
- declare function NodeGraphControls(inkraProps: NodeGraphControlsProps): import("react").JSX.Element;
127
+ declare function NodeGraphControls(inkraProps: NodeGraphControlsProps): import("react").JSX.Element | null;
115
128
  export declare function applyNodeGraphNodeChanges<NodeType extends NodeGraphNode>(changes: NodeGraphNodeChange<NodeType>[], nodes: NodeType[]): NodeType[];
116
129
  export declare function applyNodeGraphEdgeChanges<EdgeType extends NodeGraphEdge>(changes: NodeGraphEdgeChange<EdgeType>[], edges: EdgeType[]): EdgeType[];
117
130
  export declare function addNodeGraphEdge<EdgeType extends NodeGraphEdge>(connection: NodeGraphConnection | EdgeType, edges: EdgeType[]): EdgeType[];
@@ -241,21 +241,30 @@ function NodeGraphConnectionLine({ fromX, fromY, toX, toY, fromPosition, toPosit
241
241
  }
242
242
  function NodeGraphControls(inkraProps) {
243
243
  const { t } = useInkraLocale();
244
- const { className, fitViewOptions, zoomInLabel = t('common.zoomIn'), zoomOutLabel = t('common.zoomOut'), fitViewLabel = t('graph.fit'), ...props } = inkraProps;
244
+ const { className, children, zoomIn = true, zoomOut = true, fitView = true, extraControls, fitViewOptions, zoomInLabel = t('common.zoomIn'), zoomOutLabel = t('common.zoomOut'), fitViewLabel = t('graph.fit'), ...props } = inkraProps;
245
245
  const flow = useReactFlow();
246
+ const api = {
247
+ zoomIn: () => flow.zoomIn(),
248
+ zoomOut: () => flow.zoomOut(),
249
+ fitView: options => flow.fitView(options ?? fitViewOptions),
250
+ getViewport: () => flow.getViewport(),
251
+ };
252
+ const extra = typeof extraControls === 'function' ? extraControls(api) : extraControls;
246
253
  const actions = [
247
- { label: zoomInLabel, paths: zoomInPaths, run: () => flow.zoomIn() },
248
- { label: zoomOutLabel, paths: zoomOutPaths, run: () => flow.zoomOut() },
249
- { label: fitViewLabel, paths: fitViewPaths, run: () => flow.fitView(fitViewOptions) },
250
- ];
251
- return _jsx("div", { ...props, className: ['inkra-node-graph__controls', 'nopan', className].filter(Boolean).join(' '), children: actions.map(action => _jsxs(PencilSurface, { as: "button", "aria-label": action.label, className: "inkra-node-graph__control", "data-tooltip": action.label, onClick: () => void action.run(), variant: "paper", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-node-graph__control-focus", radius: 6, seed: `node-graph-${action.label}-focus`, variant: "outline" }), _jsx(PencilIcon, { decorative: true, paths: action.paths, size: 22, sourceSize: 24, seed: `node-graph-${action.label}` })] }, action.label)) });
254
+ { id: 'zoomIn', enabled: zoomIn, label: zoomInLabel, paths: zoomInPaths, run: api.zoomIn },
255
+ { id: 'zoomOut', enabled: zoomOut, label: zoomOutLabel, paths: zoomOutPaths, run: api.zoomOut },
256
+ { id: 'fitView', enabled: fitView, label: fitViewLabel, paths: fitViewPaths, run: () => api.fitView() },
257
+ ].filter(action => action.enabled);
258
+ if (!actions.length && extra == null && children == null)
259
+ return null;
260
+ return _jsxs("div", { ...props, className: ['inkra-node-graph__controls', 'nopan', className].filter(Boolean).join(' '), children: [actions.map(action => _jsxs(PencilSurface, { as: "button", "aria-label": action.label, className: "inkra-node-graph__control", "data-tooltip": action.label, onClick: () => void action.run(), variant: "paper", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-node-graph__control-focus", radius: 6, seed: `node-graph-${action.label}-focus`, variant: "outline" }), _jsx(PencilIcon, { decorative: true, paths: action.paths, size: 22, sourceSize: 24, seed: `node-graph-${action.label}` })] }, action.id)), extra, children] });
252
261
  }
253
262
  function SsrFallback({ nodes, emptyState, label, }) {
254
263
  return _jsx("div", { className: "inkra-node-graph__ssr", role: "group", "aria-label": label, children: nodes.length === 0 ? _jsx("div", { className: "inkra-node-graph__empty", children: emptyState }) : _jsx("ul", { children: nodes.map(node => _jsx("li", { children: readableLabel(node.data.label, node.id) }, node.id)) }) });
255
264
  }
256
265
  function NodeGraphRootInner(inkraProps, forwardedRef) {
257
266
  const { t } = useInkraLocale();
258
- const { nodes, edges, onNodesChange, onEdgesChange, onConnect, isValidConnection, renderNode, renderEdge, renderEdgeLabel, readOnly = false, emptyState = t('graph.empty'), controls = true, controlLabels, fitView = false, fitViewOptions, defaultViewport = { x: 0, y: 0, zoom: 1 }, viewport, onViewportChange, minZoom = 0.25, maxZoom = 2, panOnDrag = true, zoomOnScroll = true, zoomOnPinch = true, connectOnClick = true, onlyRenderVisibleElements = false, className, style, 'aria-label': ariaLabel = t('graph.label'), ...props } = inkraProps;
267
+ const { nodes, edges, onNodesChange, onEdgesChange, onConnect, isValidConnection, renderNode, renderEdge, renderEdgeLabel, readOnly = false, emptyState = t('graph.empty'), controls = true, extraControls, controlLabels, fitView = false, fitViewOptions, defaultViewport = { x: 0, y: 0, zoom: 1 }, viewport, onViewportChange, minZoom = 0.25, maxZoom = 2, panOnDrag = true, zoomOnScroll = true, zoomOnPinch = true, connectOnClick = true, onlyRenderVisibleElements = false, hideAttribution = false, className, style, 'aria-label': ariaLabel = t('graph.label'), ...props } = inkraProps;
259
268
  const [mounted, setMounted] = useState(false);
260
269
  const instanceRef = useRef(null);
261
270
  useEffect(() => setMounted(true), []);
@@ -318,7 +327,7 @@ function NodeGraphRootInner(inkraProps, forwardedRef) {
318
327
  'node.a11yDescription.ariaLiveMessage': ({ x, y }) => t('graph.nodeMoved', { x, y }),
319
328
  'edge.a11yDescription.default': t('graph.edgeInstructions'),
320
329
  'handle.ariaLabel': t('graph.handle'),
321
- }, nodes: internalNodes, edges: internalEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onInit: instance => { instanceRef.current = instance; }, onNodesChange: changes => onNodesChange?.(changes), onEdgesChange: changes => onEdgesChange?.(changes), onConnect: readOnly ? undefined : onConnect, isValidConnection: isValidConnection, nodesDraggable: !readOnly, nodesConnectable: !readOnly, elementsSelectable: !readOnly, edgesReconnectable: !readOnly, nodesFocusable: true, edgesFocusable: true, deleteKeyCode: readOnly ? null : ['Backspace', 'Delete'], fitView: fitView, fitViewOptions: fitViewOptions, defaultViewport: defaultViewport, viewport: viewport, onViewportChange: onViewportChange, minZoom: minZoom, maxZoom: maxZoom, panOnDrag: panOnDrag, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, connectOnClick: connectOnClick && !readOnly, connectionLineComponent: NodeGraphConnectionLine, onlyRenderVisibleElements: onlyRenderVisibleElements, noDragClassName: "nodrag", noPanClassName: "nopan", children: controls && _jsx(Panel, { position: "bottom-left", children: _jsx(NodeGraphControls, { fitViewLabel: controlLabels?.fitView, fitViewOptions: fitViewOptions, zoomInLabel: controlLabels?.zoomIn, zoomOutLabel: controlLabels?.zoomOut }) }) }), nodes.length === 0 && _jsx("div", { className: "inkra-node-graph__empty", role: "status", children: emptyState })] }) });
330
+ }, nodes: internalNodes, edges: internalEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onInit: instance => { instanceRef.current = instance; }, onNodesChange: changes => onNodesChange?.(changes), onEdgesChange: changes => onEdgesChange?.(changes), onConnect: readOnly ? undefined : onConnect, isValidConnection: isValidConnection, nodesDraggable: !readOnly, nodesConnectable: !readOnly, elementsSelectable: !readOnly, edgesReconnectable: !readOnly, nodesFocusable: true, edgesFocusable: true, deleteKeyCode: readOnly ? null : ['Backspace', 'Delete'], fitView: fitView, fitViewOptions: fitViewOptions, defaultViewport: defaultViewport, viewport: viewport, onViewportChange: onViewportChange, minZoom: minZoom, maxZoom: maxZoom, panOnDrag: panOnDrag, zoomOnScroll: zoomOnScroll, zoomOnPinch: zoomOnPinch, connectOnClick: connectOnClick && !readOnly, connectionLineComponent: NodeGraphConnectionLine, onlyRenderVisibleElements: onlyRenderVisibleElements, proOptions: { hideAttribution }, noDragClassName: "nodrag", noPanClassName: "nopan", children: controls !== false && _jsx(Panel, { position: typeof controls === 'object' ? controls.position ?? 'bottom-left' : 'bottom-left', children: _jsx(NodeGraphControls, { zoomIn: typeof controls === 'object' ? controls.zoomIn : undefined, zoomOut: typeof controls === 'object' ? controls.zoomOut : undefined, fitView: typeof controls === 'object' ? controls.fitView : undefined, extraControls: extraControls, fitViewLabel: controlLabels?.fitView, fitViewOptions: fitViewOptions, zoomInLabel: controlLabels?.zoomIn, zoomOutLabel: controlLabels?.zoomOut }) }) }), nodes.length === 0 && _jsx("div", { className: "inkra-node-graph__empty", role: "status", children: emptyState })] }) });
322
331
  }
323
332
  const NodeGraphRoot = forwardRef(NodeGraphRootInner);
324
333
  export function applyNodeGraphNodeChanges(changes, nodes) {
@@ -0,0 +1,176 @@
1
+ .inkra-node-graph {
2
+ position: relative;
3
+ isolation: isolate;
4
+ inline-size: 100%;
5
+ min-inline-size: 0;
6
+ block-size: min(68vh, 620px);
7
+ min-block-size: 320px;
8
+ overflow: hidden;
9
+ color: var(--inkra-ink);
10
+ background: color-mix(in srgb, var(--inkra-paper) 92%, transparent);
11
+ border-radius: var(--inkra-radius-lg);
12
+ }
13
+
14
+ .inkra-node-graph .react-flow {
15
+ position: relative;
16
+ z-index: 0;
17
+ overflow: hidden;
18
+ direction: inherit;
19
+ inline-size: 100%;
20
+ block-size: 100%;
21
+ background: transparent;
22
+ --xy-edge-stroke: var(--inkra-ink-muted);
23
+ --xy-edge-stroke-selected: var(--inkra-accent-strong);
24
+ --xy-connectionline-stroke: var(--inkra-accent-strong);
25
+ --xy-selection-background-color: color-mix(in srgb, var(--inkra-selection-pigment) 13%, transparent);
26
+ --xy-selection-border: 0;
27
+ }
28
+
29
+ .inkra-node-graph .react-flow__container { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; }
30
+ .inkra-node-graph .react-flow__pane { z-index: 1; touch-action: none; }
31
+ .inkra-node-graph .react-flow__pane.draggable { cursor: grab; }
32
+ .inkra-node-graph .react-flow__pane.dragging { cursor: grabbing; }
33
+ .inkra-node-graph .react-flow__pane.selection { cursor: pointer; }
34
+ .inkra-node-graph .react-flow__viewport { position: absolute; inset: 0; z-index: 2; transform-origin: 0 0; pointer-events: none; }
35
+ .inkra-node-graph .react-flow__renderer { z-index: 4; }
36
+ .inkra-node-graph .react-flow__edges { position: absolute; }
37
+ .inkra-node-graph .react-flow__edges svg { position: absolute; overflow: visible; pointer-events: none; }
38
+ .inkra-node-graph .react-flow__edge { pointer-events: visibleStroke; }
39
+ .inkra-node-graph .react-flow__edge.selectable { cursor: pointer; }
40
+ .inkra-node-graph .react-flow__edge.inactive { pointer-events: none; }
41
+ .inkra-node-graph .react-flow__edge:focus,
42
+ .inkra-node-graph .react-flow__edge:focus-visible { outline: none; }
43
+ .inkra-node-graph svg.react-flow__connectionline { position: absolute; z-index: 8; overflow: visible; pointer-events: none; }
44
+ .inkra-node-graph .react-flow__nodes { pointer-events: none; transform-origin: 0 0; }
45
+ .inkra-node-graph .react-flow__node { position: absolute; box-sizing: border-box; transform-origin: 0 0; pointer-events: all; user-select: none; }
46
+ .inkra-node-graph .react-flow__node.selectable { cursor: pointer; }
47
+ .inkra-node-graph .react-flow__node.draggable { cursor: grab; }
48
+ .inkra-node-graph .react-flow__node.draggable.dragging { cursor: grabbing; }
49
+ .inkra-node-graph .react-flow__node:focus { outline: none; }
50
+ .inkra-node-graph .react-flow__nodesselection { z-index: 3; transform-origin: left top; pointer-events: none; }
51
+ .inkra-node-graph .react-flow__nodesselection-rect { position: absolute; pointer-events: all; cursor: grab; }
52
+ .inkra-node-graph .react-flow__nodesselection-rect:focus,
53
+ .inkra-node-graph .react-flow__nodesselection-rect:focus-visible { outline: none; }
54
+ .inkra-node-graph .react-flow__selection { z-index: 6; }
55
+ .inkra-node-graph .react-flow__panel { position: absolute; z-index: 7; margin: var(--inkra-space-3); }
56
+ .inkra-node-graph .react-flow__panel.top { inset-block-start: 0; }
57
+ .inkra-node-graph .react-flow__panel.bottom { inset-block-end: 0; }
58
+ .inkra-node-graph .react-flow__panel.left { inset-inline-start: 0; }
59
+ .inkra-node-graph .react-flow__panel.right { inset-inline-end: 0; }
60
+ .inkra-node-graph .react-flow__pane.selection .react-flow__panel { pointer-events: none; }
61
+ .inkra-node-graph .react-flow__selection-rect,
62
+ .inkra-node-graph .react-flow__nodesselection-rect { border: 0; background: color-mix(in srgb, var(--inkra-selection-pigment) 13%, transparent); }
63
+ .inkra-node-graph .react-flow__viewport-portal { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; pointer-events: none; user-select: none; }
64
+ .inkra-node-graph .react-flow__attribution { color: var(--inkra-ink-muted); font-size: 10px; line-height: 1.2; }
65
+ .inkra-node-graph .react-flow__attribution a { color: inherit; }
66
+
67
+ .inkra-node-graph__node-shell { position: relative; min-inline-size: 168px; max-inline-size: min(280px, 72vw); }
68
+ .inkra-node-graph__node-paper { position: relative; min-inline-size: inherit; color: var(--inkra-ink); }
69
+ .inkra-node-graph__node-selection { position: absolute; z-index: -1; inset: 2px; pointer-events: none; }
70
+ .inkra-node-graph__node-content { position: relative; z-index: 1; display: grid; gap: var(--inkra-space-2); padding: var(--inkra-space-4); min-inline-size: 0; }
71
+ .inkra-node-graph__node-label { color: var(--inkra-ink); font-size: var(--inkra-text-md); line-height: var(--inkra-line-ui); overflow-wrap: anywhere; }
72
+ .inkra-node-graph__node-description { color: var(--inkra-ink-muted); font-size: var(--inkra-text-sm); line-height: var(--inkra-line-body); overflow-wrap: anywhere; }
73
+ .inkra-node-graph__node-body { min-inline-size: 0; }
74
+ .inkra-node-graph__node-focus { position: absolute; z-index: 3; inset: -4px; opacity: 0; pointer-events: none; }
75
+ .inkra-node-graph .react-flow__node:focus-visible .inkra-node-graph__node-focus { opacity: 1; }
76
+
77
+ .inkra-node-graph__port.react-flow__handle {
78
+ position: absolute;
79
+ display: grid;
80
+ place-items: center;
81
+ inline-size: var(--inkra-touch-target);
82
+ block-size: var(--inkra-touch-target);
83
+ border: 0;
84
+ background: transparent;
85
+ pointer-events: none;
86
+ transform: translate(-50%, -50%) scale(var(--inkra-node-graph-port-scale, 1));
87
+ z-index: 4;
88
+ }
89
+ .inkra-node-graph__port.react-flow__handle.connectionindicator { pointer-events: all; cursor: crosshair; }
90
+ .inkra-node-graph__port.react-flow__handle-left { inset-inline-start: 0; inset-block-start: 50%; }
91
+ .inkra-node-graph__port.react-flow__handle-right { inset-inline-start: 100%; inset-block-start: 50%; }
92
+ .inkra-node-graph__port.react-flow__handle-top { inset-inline-start: 50%; inset-block-start: 0; }
93
+ .inkra-node-graph__port.react-flow__handle-bottom { inset-inline-start: 50%; inset-block-start: 100%; }
94
+ .inkra-node-graph__port-visual { position: relative; display: grid; place-items: center; inline-size: 18px; block-size: 18px; pointer-events: none; }
95
+ .inkra-node-graph__port-mark,
96
+ .inkra-node-graph__port-feedback { position: absolute; inset: 0; pointer-events: none; }
97
+ .inkra-node-graph__port-feedback { opacity: 0; }
98
+ .inkra-node-graph__port:hover .inkra-node-graph__port-feedback,
99
+ .inkra-node-graph__port.connectingfrom .inkra-node-graph__port-feedback,
100
+ .inkra-node-graph__port.valid .inkra-node-graph__port-feedback { opacity: 1; }
101
+
102
+ .inkra-node-graph__edge-hit { pointer-events: stroke; }
103
+ .inkra-node-graph__edge-pencil { color: var(--inkra-ink-muted); pointer-events: none; }
104
+ .inkra-node-graph__edge-pencil[data-selected] { color: var(--inkra-accent-strong); }
105
+ .inkra-node-graph__edge-measure { stroke: transparent; stroke-width: 1; pointer-events: none; }
106
+ .inkra-node-graph__edge-fallback { stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; vector-effect: non-scaling-stroke; pointer-events: none; }
107
+ .inkra-node-graph__edge-pencil[data-pencil-ready] .inkra-node-graph__edge-fallback { visibility: hidden; }
108
+ .inkra-node-graph__edge-foreign-object { overflow: visible; pointer-events: none; }
109
+ .inkra-node-graph__edge-canvas { display: block; color: inherit; overflow: visible; pointer-events: none; }
110
+ .inkra-node-graph__edge-label { position: absolute; z-index: 5; max-inline-size: 180px; padding: 4px 8px; color: var(--inkra-ink); font-size: var(--inkra-text-sm); line-height: var(--inkra-line-ui); pointer-events: all; overflow-wrap: anywhere; }
111
+ .inkra-node-graph .react-flow__edgelabel-renderer { position: absolute; inset: 0; z-index: 5; inline-size: 100%; block-size: 100%; pointer-events: none; user-select: none; }
112
+
113
+ .inkra-node-graph__controls { display: flex; gap: var(--inkra-space-1); }
114
+ .inkra-node-graph__control { appearance: none; position: relative; display: inline-grid; place-items: center; inline-size: var(--inkra-touch-target); block-size: var(--inkra-touch-target); padding: 0; border: 0; color: var(--inkra-ink); cursor: pointer; }
115
+ .inkra-node-graph__control:focus { outline: none; }
116
+ .inkra-node-graph__control-focus { position: absolute; inset: 2px; opacity: 0; pointer-events: none; }
117
+ .inkra-node-graph__control:focus-visible .inkra-node-graph__control-focus { opacity: 1; }
118
+
119
+ .inkra-node-graph__empty { position: absolute; inset: 0; z-index: 6; display: grid; place-items: center; padding: var(--inkra-space-6); color: var(--inkra-ink-muted); text-align: center; pointer-events: none; }
120
+ .inkra-node-graph__ssr { position: absolute; inset: 0; display: grid; place-items: center; padding: var(--inkra-space-5); color: var(--inkra-ink); }
121
+ .inkra-node-graph__ssr ul { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--inkra-space-3); margin: 0; padding: 0; list-style: none; }
122
+ .inkra-node-graph__ssr li { padding: var(--inkra-space-3) var(--inkra-space-4); }
123
+
124
+ @media (max-width: 390px) {
125
+ .inkra-node-graph { block-size: 440px; min-block-size: 360px; }
126
+ .inkra-node-graph__node-shell { min-inline-size: 144px; max-inline-size: 68vw; }
127
+ .inkra-node-graph__node-content { padding: var(--inkra-space-3); }
128
+ .inkra-node-graph .react-flow__panel { margin: var(--inkra-space-2); }
129
+ }
130
+
131
+ @media (prefers-reduced-motion: reduce) {
132
+ .inkra-node-graph * { scroll-behavior: auto !important; transition-duration: 0s !important; animation-duration: 0s !important; }
133
+ }
134
+
135
+ @media print, (forced-colors: active) {
136
+ .inkra-node-graph { border: 1px solid CanvasText; background: Canvas; color: CanvasText; }
137
+ .inkra-node-graph__edge-canvas { visibility: hidden; }
138
+ .inkra-node-graph__edge-fallback { visibility: visible !important; stroke: CanvasText; }
139
+ .inkra-node-graph__port-mark { outline: 1px solid CanvasText; outline-offset: -6px; }
140
+ .inkra-node-graph .react-flow__node:focus-visible .inkra-node-graph__node-shell,
141
+ .inkra-node-graph__control:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
142
+ }
143
+
144
+ .inkra-dependency-graph__node { position: relative; width: 100%; height: 100%; }
145
+ .inkra-dependency-graph__anchor.react-flow__handle { position: absolute; width: 0; height: 0; min-width: 0; min-height: 0; border: 0; opacity: 0; pointer-events: none; }
146
+ .inkra-dependency-graph__card, .inkra-dependency-graph__icon-button { box-sizing: border-box; width: 100%; height: 100%; padding: 14px 16px; border: 0; font: inherit; color: var(--inkra-ink); text-align: start; display: flex; flex-direction: column; gap: 6px; pointer-events: auto; }
147
+ .inkra-dependency-graph__card--custom { padding: 0; }
148
+ .inkra-dependency-graph__header { display: flex; align-items: center; gap: 10px; min-width: 0; }
149
+ .inkra-dependency-graph__icon { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; }
150
+ .inkra-dependency-graph__title { appearance: none; background: none; border: 0; padding: 0; margin: 0; min-width: 0; flex: 1; font: inherit; color: inherit; text-align: start; font-weight: 600; cursor: pointer; }
151
+ .inkra-dependency-graph__title > * { max-width: 100%; }
152
+ .inkra-dependency-graph__actions { display: flex; align-items: center; flex: 0 0 auto; gap: 4px; margin-inline-start: auto; }
153
+ .inkra-dependency-graph__body { min-height: 0; overflow: auto; font-size: var(--inkra-text-sm); color: var(--inkra-ink-muted); }
154
+ .inkra-dependency-graph__footer { display: flex; align-items: center; gap: 8px; margin-top: auto; }
155
+ .inkra-dependency-graph__icon-button { align-items: center; justify-content: center; padding: 10px; cursor: pointer; }
156
+ .inkra-dependency-graph__card:focus-visible, .inkra-dependency-graph__title:focus-visible, .inkra-dependency-graph__icon-button:focus-visible { outline: 0; }
157
+ .inkra-dependency-graph__focus { --inkra-accent-strong: var(--inkra-focus); position: absolute !important; inset: -5px; pointer-events: none; opacity: 0; }
158
+ .inkra-dependency-graph__card:focus-visible > .inkra-dependency-graph__focus,
159
+ .inkra-dependency-graph__card--custom:focus-visible .inkra-dependency-graph__focus,
160
+ .inkra-dependency-graph__card:has(.inkra-dependency-graph__title:focus-visible) > .inkra-dependency-graph__focus,
161
+ .inkra-dependency-graph__icon-button:focus-visible > .inkra-dependency-graph__focus { opacity: 1; }
162
+ .inkra-dependency-graph__node[data-disabled] { opacity: .55; }
163
+ .inkra-dependency-graph__title:disabled, .inkra-dependency-graph__icon-button:disabled { cursor: not-allowed; }
164
+ .inkra-dependency-graph__status { font-size: var(--inkra-text-sm); color: var(--inkra-ink-muted); }
165
+ .inkra-dependency-graph__node[data-status="completed"] .inkra-dependency-graph__status { color: var(--inkra-success); }
166
+ .inkra-dependency-graph__node[data-status="available"] .inkra-dependency-graph__status,
167
+ .inkra-dependency-graph__node[data-status="active"] .inkra-dependency-graph__status { color: var(--inkra-accent-strong); }
168
+ .inkra-dependency-graph__highlight { position: absolute !important; inset: -3px; pointer-events: none; }
169
+ .inkra-dependency-graph__node[data-dimmed], .inkra-dependency-graph__edge[data-dimmed] { opacity: .5; }
170
+ .inkra-dependency-graph__edge { color: var(--inkra-ink-muted); }
171
+ .inkra-dependency-graph__edge[data-highlighted], .inkra-dependency-graph__edge--highlighted { color: var(--inkra-accent-strong); }
172
+ @media (forced-colors: active) {
173
+ .inkra-dependency-graph__card:focus-visible, .inkra-dependency-graph__title:focus-visible, .inkra-dependency-graph__icon-button:focus-visible { outline: var(--inkra-focus-width) solid Highlight; outline-offset: 4px; }
174
+ .inkra-dependency-graph__focus { display: none !important; }
175
+ .inkra-dependency-graph__node[data-dimmed], .inkra-dependency-graph__edge[data-dimmed] { opacity: 1; }
176
+ }
@@ -1,9 +1,11 @@
1
1
  .inkra-journey-map { min-width: 0; width: 100%; color: var(--inkra-ink); }
2
2
  .inkra-journey-map__viewport { position: relative; overflow: hidden; isolation: isolate; background: var(--inkra-paper); }
3
3
  .inkra-journey-map__host { position: absolute; inset: 0; background: transparent; font: inherit; }
4
- .inkra-journey-map__ink { position: absolute; inset: 0; pointer-events: none; z-index: 350; }
4
+ .inkra-journey-map__ink { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 350; }
5
5
  .inkra-journey-map__tools { position: absolute; z-index: 600; inset-block-end: 9px; inset-inline-start: 9px; display: grid; gap: 4px; }
6
6
  .inkra-journey-map__marker { display: grid; place-items: center; }
7
+ .inkra-journey-map__symbol, .inkra-journey-map__stop { display: block; width: 44px; height: 44px; }
8
+ .inkra-journey-map__stop { pointer-events: none; }
7
9
  .inkra-journey-map__pin { position: absolute; top: 37px; left: 50%; transform: translateX(-50%); width: max-content; max-width: 140px; text-align: center; color: var(--inkra-ink); font: 14px/1.4 var(--inkra-font-hand); }
8
10
  .inkra-journey-map__marker[aria-pressed="true"] .inkra-journey-map__pin { color: var(--inkra-accent-strong); }
9
11
  .inkra-journey-map__orientation { position: absolute; bottom: 18px; right: 18px; z-index: 600; display: flex; align-items: end; gap: 12px; pointer-events: none; }
@@ -1,4 +1,5 @@
1
1
  .inkra-markdown {
2
+ inline-size: 100%;
2
3
  color: var(--inkra-ink);
3
4
  font-family: var(--inkra-font-body);
4
5
  font-size: var(--inkra-text-md);
@@ -203,6 +204,7 @@
203
204
  .inkra-markdown__code-text > .inkra-pencil-text__source { font-family: inherit; }
204
205
 
205
206
  .inkra-markdown__code-frame {
207
+ inline-size: 100%;
206
208
  margin-block: 0 var(--inkra-space-4);
207
209
  max-width: 100%;
208
210
  min-width: 0;
package/dist/styles.css CHANGED
@@ -7301,6 +7301,40 @@ div.inkra-choice-grid > .inkra-radio-group {
7301
7301
  .inkra-node-graph__control:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
7302
7302
  }
7303
7303
 
7304
+ .inkra-dependency-graph__node { position: relative; width: 100%; height: 100%; }
7305
+ .inkra-dependency-graph__anchor.react-flow__handle { position: absolute; width: 0; height: 0; min-width: 0; min-height: 0; border: 0; opacity: 0; pointer-events: none; }
7306
+ .inkra-dependency-graph__card, .inkra-dependency-graph__icon-button { box-sizing: border-box; width: 100%; height: 100%; padding: 14px 16px; border: 0; font: inherit; color: var(--inkra-ink); text-align: start; display: flex; flex-direction: column; gap: 6px; pointer-events: auto; }
7307
+ .inkra-dependency-graph__card--custom { padding: 0; }
7308
+ .inkra-dependency-graph__header { display: flex; align-items: center; gap: 10px; min-width: 0; }
7309
+ .inkra-dependency-graph__icon { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; }
7310
+ .inkra-dependency-graph__title { appearance: none; background: none; border: 0; padding: 0; margin: 0; min-width: 0; flex: 1; font: inherit; color: inherit; text-align: start; font-weight: 600; cursor: pointer; }
7311
+ .inkra-dependency-graph__title > * { max-width: 100%; }
7312
+ .inkra-dependency-graph__actions { display: flex; align-items: center; flex: 0 0 auto; gap: 4px; margin-inline-start: auto; }
7313
+ .inkra-dependency-graph__body { min-height: 0; overflow: auto; font-size: var(--inkra-text-sm); color: var(--inkra-ink-muted); }
7314
+ .inkra-dependency-graph__footer { display: flex; align-items: center; gap: 8px; margin-top: auto; }
7315
+ .inkra-dependency-graph__icon-button { align-items: center; justify-content: center; padding: 10px; cursor: pointer; }
7316
+ .inkra-dependency-graph__card:focus-visible, .inkra-dependency-graph__title:focus-visible, .inkra-dependency-graph__icon-button:focus-visible { outline: 0; }
7317
+ .inkra-dependency-graph__focus { --inkra-accent-strong: var(--inkra-focus); position: absolute !important; inset: -5px; pointer-events: none; opacity: 0; }
7318
+ .inkra-dependency-graph__card:focus-visible > .inkra-dependency-graph__focus,
7319
+ .inkra-dependency-graph__card--custom:focus-visible .inkra-dependency-graph__focus,
7320
+ .inkra-dependency-graph__card:has(.inkra-dependency-graph__title:focus-visible) > .inkra-dependency-graph__focus,
7321
+ .inkra-dependency-graph__icon-button:focus-visible > .inkra-dependency-graph__focus { opacity: 1; }
7322
+ .inkra-dependency-graph__node[data-disabled] { opacity: .55; }
7323
+ .inkra-dependency-graph__title:disabled, .inkra-dependency-graph__icon-button:disabled { cursor: not-allowed; }
7324
+ .inkra-dependency-graph__status { font-size: var(--inkra-text-sm); color: var(--inkra-ink-muted); }
7325
+ .inkra-dependency-graph__node[data-status="completed"] .inkra-dependency-graph__status { color: var(--inkra-success); }
7326
+ .inkra-dependency-graph__node[data-status="available"] .inkra-dependency-graph__status,
7327
+ .inkra-dependency-graph__node[data-status="active"] .inkra-dependency-graph__status { color: var(--inkra-accent-strong); }
7328
+ .inkra-dependency-graph__highlight { position: absolute !important; inset: -3px; pointer-events: none; }
7329
+ .inkra-dependency-graph__node[data-dimmed], .inkra-dependency-graph__edge[data-dimmed] { opacity: .5; }
7330
+ .inkra-dependency-graph__edge { color: var(--inkra-ink-muted); }
7331
+ .inkra-dependency-graph__edge[data-highlighted], .inkra-dependency-graph__edge--highlighted { color: var(--inkra-accent-strong); }
7332
+ @media (forced-colors: active) {
7333
+ .inkra-dependency-graph__card:focus-visible, .inkra-dependency-graph__title:focus-visible, .inkra-dependency-graph__icon-button:focus-visible { outline: var(--inkra-focus-width) solid Highlight; outline-offset: 4px; }
7334
+ .inkra-dependency-graph__focus { display: none !important; }
7335
+ .inkra-dependency-graph__node[data-dimmed], .inkra-dependency-graph__edge[data-dimmed] { opacity: 1; }
7336
+ }
7337
+
7304
7338
  .inkra-overflow-list {
7305
7339
  --inkra-overflow-list-gap: var(--inkra-space-2);
7306
7340
 
@@ -8747,6 +8781,7 @@ div.inkra-choice-grid > .inkra-radio-group {
8747
8781
  }
8748
8782
 
8749
8783
  .inkra-markdown {
8784
+ inline-size: 100%;
8750
8785
  color: var(--inkra-ink);
8751
8786
  font-family: var(--inkra-font-body);
8752
8787
  font-size: var(--inkra-text-md);
@@ -8951,6 +8986,7 @@ div.inkra-choice-grid > .inkra-radio-group {
8951
8986
  .inkra-markdown__code-text > .inkra-pencil-text__source { font-family: inherit; }
8952
8987
 
8953
8988
  .inkra-markdown__code-frame {
8989
+ inline-size: 100%;
8954
8990
  margin-block: 0 var(--inkra-space-4);
8955
8991
  max-width: 100%;
8956
8992
  min-width: 0;
@@ -9984,9 +10020,11 @@ svg.leaflet-image-layer.leaflet-interactive path {
9984
10020
  .inkra-journey-map { min-width: 0; width: 100%; color: var(--inkra-ink); }
9985
10021
  .inkra-journey-map__viewport { position: relative; overflow: hidden; isolation: isolate; background: var(--inkra-paper); }
9986
10022
  .inkra-journey-map__host { position: absolute; inset: 0; background: transparent; font: inherit; }
9987
- .inkra-journey-map__ink { position: absolute; inset: 0; pointer-events: none; z-index: 350; }
10023
+ .inkra-journey-map__ink { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 350; }
9988
10024
  .inkra-journey-map__tools { position: absolute; z-index: 600; inset-block-end: 9px; inset-inline-start: 9px; display: grid; gap: 4px; }
9989
10025
  .inkra-journey-map__marker { display: grid; place-items: center; }
10026
+ .inkra-journey-map__symbol, .inkra-journey-map__stop { display: block; width: 44px; height: 44px; }
10027
+ .inkra-journey-map__stop { pointer-events: none; }
9990
10028
  .inkra-journey-map__pin { position: absolute; top: 37px; left: 50%; transform: translateX(-50%); width: max-content; max-width: 140px; text-align: center; color: var(--inkra-ink); font: 14px/1.4 var(--inkra-font-hand); }
9991
10029
  .inkra-journey-map__marker[aria-pressed="true"] .inkra-journey-map__pin { color: var(--inkra-accent-strong); }
9992
10030
  .inkra-journey-map__orientation { position: absolute; bottom: 18px; right: 18px; z-index: 600; display: flex; align-items: end; gap: 12px; pointer-events: none; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkra/react",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Hand-drawn React controls with native semantics and deterministic decoration.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -489,7 +489,13 @@
489
489
  "./styles/tree-select.css": "./dist/styles/tree-select.css",
490
490
  "./styles/typewriter.css": "./dist/styles/typewriter.css",
491
491
  "./styles/upload.css": "./dist/styles/upload.css",
492
- "./styles/virtual-list.css": "./dist/styles/virtual-list.css"
492
+ "./styles/virtual-list.css": "./dist/styles/virtual-list.css",
493
+ "./dependency-graph": {
494
+ "types": "./dist/dependency-graph/index.d.ts",
495
+ "import": "./dist/dependency-graph/index.js",
496
+ "default": "./dist/dependency-graph/index.js"
497
+ },
498
+ "./styles/dependency-graph.css": "./dist/styles/dependency-graph.css"
493
499
  },
494
500
  "files": [
495
501
  "dist",