@gntik-ai/flow 0.0.0-stage → 0.5.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.
@@ -0,0 +1,217 @@
1
+
2
+ import { ReactNode } from "react";
3
+ import { Connection, DefaultEdgeOptions, Edge, EdgeMarker, Node, NodeProps, ReactFlowProps } from "@xyflow/react";
4
+ import { Theme } from "@gntik-ai/tokens/runtime";
5
+ //#region src/tones.d.ts
6
+ /** Node status → brand colour. Green = active/healthy (never severity); amber = degraded;
7
+ * red = failed; violet/rose = needs a human (handoff / review). */
8
+ type FlowTone = 'running' | 'degraded' | 'failed' | 'paused' | 'done' | 'handoff' | 'review';
9
+ /** The role of a node, which decides its handles and its icon chip. */
10
+ type FlowNodeKind = 'trigger' | 'step' | 'router' | 'output';
11
+ interface FlowToneStyle {
12
+ label: string;
13
+ /** Pill background + text (contrast-safe text aliases). */
14
+ pill: string;
15
+ /** Status dot. */
16
+ dot: string;
17
+ /** Thick card border, or null for the neutral border. */
18
+ border: string | null;
19
+ /** Brand token used for the minimap swatch. */
20
+ token: string;
21
+ /** Draws the marching segment around the card. */
22
+ march?: boolean;
23
+ }
24
+ export declare const FLOW_TONES: Record<FlowTone, FlowToneStyle>;
25
+ /** Icon chip per node kind. */
26
+ export declare const KIND_CHIP: Record<FlowNodeKind, string>;
27
+ export declare function isFlowTone(v: unknown): v is FlowTone;
28
+ /** Token for a node's minimap swatch: its tone, else muted for routers, else primary. */
29
+ export declare function nodeToken(tone: unknown, kind: unknown): string;
30
+ //#endregion
31
+ //#region src/decorations.d.ts
32
+ interface FlowNodeDecoration {
33
+ status?: FlowTone;
34
+ issues?: readonly {
35
+ code: string;
36
+ message: string;
37
+ }[];
38
+ }
39
+ /** Memoized per node: unrelated status changes do not render this badge again. */
40
+ export declare const FlowStatusBadge: import("react").MemoExoticComponent<({ status }: {
41
+ status: FlowTone;
42
+ }) => import("react").JSX.Element>;
43
+ //#endregion
44
+ //#region src/nodes.d.ts
45
+ /** Generic node content. No domain data: title, subtitle, meta, icon and a status tone. */
46
+ type FlowNodeData = {
47
+ decoration?: FlowNodeDecoration;
48
+ title: string;
49
+ subtitle?: string;
50
+ meta?: string;
51
+ /** Decorative icon element (e.g. a lucide icon); defaults to the kind's glyph. */
52
+ icon?: ReactNode;
53
+ tone?: FlowTone;
54
+ /** Override the kind's default handles. */
55
+ handles?: {
56
+ target?: boolean;
57
+ source?: boolean;
58
+ };
59
+ };
60
+ /** Built-in kinds by default; consumers can opt into custom type identifiers. */
61
+ type FlowNode<Kind extends string = FlowNodeKind> = Omit<Node<FlowNodeData, FlowNodeKind>, 'type'> & {
62
+ type: Kind;
63
+ };
64
+ /** Entry point: source handle only. */
65
+ export declare const TriggerNode: {
66
+ ({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>): import("react").JSX.Element;
67
+ displayName: string;
68
+ };
69
+ /** Processing step: target + source handles, optional meta and status. */
70
+ export declare const StepNode: {
71
+ ({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>): import("react").JSX.Element;
72
+ displayName: string;
73
+ };
74
+ /** Conditional branch: target + source handles. */
75
+ export declare const RouterNode: {
76
+ ({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>): import("react").JSX.Element;
77
+ displayName: string;
78
+ };
79
+ /** Terminal node: target handle only. */
80
+ export declare const OutputNode: {
81
+ ({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>): import("react").JSX.Element;
82
+ displayName: string;
83
+ };
84
+ /** `nodeTypes` map for `<FlowCanvas>` / `<ReactFlow>` (define it once, outside render). */
85
+ export declare const flowNodeTypes: {
86
+ trigger: {
87
+ ({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>): import("react").JSX.Element;
88
+ displayName: string;
89
+ };
90
+ step: {
91
+ ({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>): import("react").JSX.Element;
92
+ displayName: string;
93
+ };
94
+ router: {
95
+ ({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>): import("react").JSX.Element;
96
+ displayName: string;
97
+ };
98
+ output: {
99
+ ({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>): import("react").JSX.Element;
100
+ displayName: string;
101
+ };
102
+ };
103
+ //#endregion
104
+ //#region src/renderers.d.ts
105
+ /** Named connection point. Positions default to left for targets and right for sources. */
106
+ interface FlowHandle {
107
+ id: string;
108
+ kind: 'source' | 'target';
109
+ position?: 'left' | 'right' | 'top' | 'bottom';
110
+ label?: string;
111
+ }
112
+ /** Kit-owned renderer input: no React Flow component types are required by consumers. */
113
+ interface FlowNodeRenderProps {
114
+ id: string;
115
+ type: string;
116
+ data: FlowNodeData;
117
+ selected: boolean;
118
+ connectable: boolean;
119
+ }
120
+ interface FlowNodeRenderer {
121
+ type: string;
122
+ handles: readonly FlowHandle[];
123
+ /** Defaults to kit overlays. Consumer renderers may own accessible status and issue decorations. */
124
+ decorations?: 'kit' | 'consumer';
125
+ render: (props: FlowNodeRenderProps) => ReactNode;
126
+ }
127
+ //#endregion
128
+ //#region src/FlowCanvas.d.ts
129
+ interface FlowCanvasProps<N extends Node = FlowNode, E extends Edge = Edge> extends Omit<ReactFlowProps<N, E>, 'aria-label' | 'height' | 'width'> {
130
+ /** Accessible name of the canvas region (required). */
131
+ 'aria-label': string;
132
+ /** Canvas height (px or CSS length). */
133
+ height?: number | string;
134
+ showControls?: boolean;
135
+ /** Hide the control that toggles graph editing, while retaining zoom and fit controls. */
136
+ showInteractiveControls?: boolean;
137
+ showMiniMap?: boolean;
138
+ showBackground?: boolean;
139
+ /** false = static diagram: no pan/zoom/connect/select (nodes still render). */
140
+ interactive?: boolean;
141
+ /** Class for the inner `.react-flow` element (`className` goes on the wrapper). */
142
+ canvasClassName?: string;
143
+ /** Kit-owned custom node renderers with named handles. */
144
+ nodeRenderers?: readonly FlowNodeRenderer[];
145
+ }
146
+ /**
147
+ * React Flow canvas themed from the brand tokens: dotted background, controls and minimap,
148
+ * brand node types, smoothstep edges with arrows (primary when animated). Drag, connect and
149
+ * zoom/pan are on by default. Import `@gntik-ai/flow/styles.css` once.
150
+ */
151
+ export declare function FlowCanvas<N extends Node = FlowNode, E extends Edge = Edge>({ 'aria-label': label, height, showControls, showInteractiveControls, showMiniMap, showBackground, interactive, className, canvasClassName, nodeTypes, nodeRenderers, edges, defaultEdgeOptions, proOptions, children, ...rest }: FlowCanvasProps<N, E>): import("react").JSX.Element;
152
+ //#endregion
153
+ //#region src/NodeCard.d.ts
154
+ interface NodeCardProps {
155
+ kind?: FlowNodeKind;
156
+ title: string;
157
+ subtitle?: string;
158
+ /** Footer text (left), e.g. a version or an owner. */
159
+ meta?: string;
160
+ /** Icon element (decorative); defaults to the kind's glyph. */
161
+ icon?: ReactNode;
162
+ tone?: FlowTone;
163
+ selected?: boolean;
164
+ className?: string;
165
+ }
166
+ /** The brand node card, without handles (reused by the node types and static galleries). */
167
+ export declare function NodeCard({ kind, icon, title, subtitle, meta, tone, selected, className }: NodeCardProps): import("react").JSX.Element;
168
+ //#endregion
169
+ //#region src/glyphs.d.ts
170
+ /** Default decorative icon for a node kind (stroke = currentColor). */
171
+ export declare function KindGlyph({ kind, size }: {
172
+ kind: FlowNodeKind;
173
+ size?: number;
174
+ }): import("react").JSX.Element;
175
+ //#endregion
176
+ //#region src/theme.d.ts
177
+ interface FlowTheme {
178
+ theme: Theme;
179
+ /** Idle edge / arrow colour. */
180
+ edge: string;
181
+ /** Active (animated) edge / arrow colour. */
182
+ edgeActive: string;
183
+ /** Background dot pattern. */
184
+ pattern: string;
185
+ /** Resolves any brand token (e.g. "--warning") to a concrete colour. */
186
+ color: (token: string, alpha?: number) => string;
187
+ }
188
+ /**
189
+ * Brand colours for the parts React Flow paints from props (arrow markers, minimap nodes,
190
+ * background pattern), resolved through `@gntik-ai/tokens/runtime` so they also survive
191
+ * SVG/PNG export. Re-read on every theme class change.
192
+ */
193
+ export declare function useFlowTheme(): FlowTheme;
194
+ /** Closed arrow marker in a given colour. */
195
+ export declare function flowMarker(color: string): EdgeMarker;
196
+ /** Brand edge defaults: orthogonal (smoothstep) routing. */
197
+ export declare const brandEdgeOptions: DefaultEdgeOptions;
198
+ /** Adds an arrow to edges that have none: primary when animated (active), muted otherwise. */
199
+ export declare function withBrandMarkers<E extends Edge>(edges: readonly E[], t: FlowTheme): E[];
200
+ //#endregion
201
+ //#region src/useFlowState.d.ts
202
+ /**
203
+ * Local nodes/edges state wired for drag, select and connect. New connections become
204
+ * active (animated) smoothstep edges. Spread the result into `<FlowCanvas>`.
205
+ */
206
+ export declare function useFlowState<N extends Node = FlowNode, E extends Edge = Edge>(initialNodes: N[], initialEdges: E[]): {
207
+ nodes: N[];
208
+ edges: E[];
209
+ setNodes: import("react").Dispatch<import("react").SetStateAction<N[]>>;
210
+ setEdges: import("react").Dispatch<import("react").SetStateAction<E[]>>;
211
+ onNodesChange: import("@xyflow/react").OnNodesChange<N>;
212
+ onEdgesChange: import("@xyflow/react").OnEdgesChange<E>;
213
+ onConnect: (c: Connection) => void;
214
+ };
215
+ //#endregion
216
+ export type { FlowCanvasProps, FlowHandle, FlowNode, FlowNodeData, FlowNodeDecoration, FlowNodeKind, FlowNodeRenderProps, FlowNodeRenderer, FlowTheme, FlowTone, FlowToneStyle, NodeCardProps };
217
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,466 @@
1
+ 'use client';
2
+ import { memo, useCallback, useEffect, useMemo, useState } from "react";
3
+ import { Background, BackgroundVariant, Controls, Handle, MarkerType, MiniMap, Position, ReactFlow, addEdge, useEdgesState, useNodesState } from "@xyflow/react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { currentTheme, observeTheme, tokenColor } from "@gntik-ai/tokens/runtime";
6
+ //#region src/tones.ts
7
+ const FLOW_TONES = {
8
+ running: {
9
+ label: "Running",
10
+ pill: "bg-primary/14 text-primary-text",
11
+ dot: "bg-primary",
12
+ border: "border-primary/60",
13
+ token: "--primary",
14
+ march: true
15
+ },
16
+ degraded: {
17
+ label: "Degraded",
18
+ pill: "bg-warning/16 text-warning-text",
19
+ dot: "bg-warning",
20
+ border: "border-warning/65",
21
+ token: "--warning"
22
+ },
23
+ failed: {
24
+ label: "Failed",
25
+ pill: "bg-destructive/15 text-destructive-text",
26
+ dot: "bg-destructive",
27
+ border: "border-destructive/65",
28
+ token: "--destructive"
29
+ },
30
+ paused: {
31
+ label: "Paused",
32
+ pill: "bg-muted-foreground/16 text-muted-foreground",
33
+ dot: "bg-muted-foreground",
34
+ border: null,
35
+ token: "--muted-foreground"
36
+ },
37
+ done: {
38
+ label: "Done",
39
+ pill: "bg-primary/14 text-primary-text",
40
+ dot: "bg-primary",
41
+ border: "border-primary/70",
42
+ token: "--primary"
43
+ },
44
+ handoff: {
45
+ label: "Handoff",
46
+ pill: "bg-category-violet/16 text-foreground",
47
+ dot: "bg-category-violet",
48
+ border: "border-category-violet",
49
+ token: "--category-violet"
50
+ },
51
+ review: {
52
+ label: "Review",
53
+ pill: "bg-category-rose/16 text-foreground",
54
+ dot: "bg-category-rose",
55
+ border: "border-category-rose",
56
+ token: "--category-rose"
57
+ }
58
+ };
59
+ /** Icon chip per node kind. */
60
+ const KIND_CHIP = {
61
+ trigger: "bg-primary text-primary-foreground",
62
+ step: "bg-primary/14 text-primary-text",
63
+ router: "border border-border bg-secondary text-foreground",
64
+ output: "bg-accent text-accent-foreground"
65
+ };
66
+ function isFlowTone(v) {
67
+ return typeof v === "string" && v in FLOW_TONES;
68
+ }
69
+ /** Token for a node's minimap swatch: its tone, else muted for routers, else primary. */
70
+ function nodeToken(tone, kind) {
71
+ if (isFlowTone(tone)) return FLOW_TONES[tone].token;
72
+ return kind === "router" ? "--muted-foreground" : "--primary";
73
+ }
74
+ function cx(...parts) {
75
+ return parts.filter(Boolean).join(" ");
76
+ }
77
+ //#endregion
78
+ //#region src/decorations.tsx
79
+ /** Memoized per node: unrelated status changes do not render this badge again. */
80
+ const FlowStatusBadge = memo(function FlowStatusBadge({ status }) {
81
+ const tone = FLOW_TONES[status];
82
+ return /* @__PURE__ */ jsxs("span", {
83
+ className: cx("inline-flex h-[18px] shrink-0 items-center gap-1 rounded px-1.5 font-mono text-[9.5px] font-semibold", tone.pill),
84
+ children: [/* @__PURE__ */ jsx("span", {
85
+ "aria-hidden": true,
86
+ className: cx("h-1.5 w-1.5 rounded-full", tone.dot, tone.march && "animate-pulse motion-reduce:animate-none")
87
+ }), tone.label]
88
+ });
89
+ });
90
+ function FlowNodeIssues({ issues }) {
91
+ if (!issues?.length) return null;
92
+ return /* @__PURE__ */ jsxs("div", {
93
+ className: "absolute top-full left-0 flex w-full items-start gap-1 rounded border border-destructive/65 bg-card p-1 text-[11px] text-destructive-text",
94
+ children: [/* @__PURE__ */ jsx("svg", {
95
+ "aria-hidden": "true",
96
+ width: "14",
97
+ height: "14",
98
+ viewBox: "0 0 24 24",
99
+ className: "shrink-0 fill-none stroke-current",
100
+ strokeWidth: "2",
101
+ children: /* @__PURE__ */ jsx("path", { d: "m12 3 10 18H2Z M12 9v5 M12 17v1" })
102
+ }), /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
103
+ className: "sr-only",
104
+ children: "Validation issue: "
105
+ }), issues.map((issue) => issue.message).join("; ")] })]
106
+ });
107
+ }
108
+ //#endregion
109
+ //#region src/glyphs.tsx
110
+ const PATHS = {
111
+ trigger: ["M13 2 3 14h9l-1 8 10-12h-9l1-8z"],
112
+ step: ["M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z", "M9 9h6v6H9z"],
113
+ router: [
114
+ "M16 3h5v5",
115
+ "M8 3H3v5",
116
+ "M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3",
117
+ "m15 9 6-6"
118
+ ],
119
+ output: ["M20 6 9 17l-5-5"]
120
+ };
121
+ /** Default decorative icon for a node kind (stroke = currentColor). */
122
+ function KindGlyph({ kind, size = 16 }) {
123
+ return /* @__PURE__ */ jsx("svg", {
124
+ "aria-hidden": "true",
125
+ focusable: "false",
126
+ width: size,
127
+ height: size,
128
+ viewBox: "0 0 24 24",
129
+ fill: "none",
130
+ stroke: "currentColor",
131
+ strokeWidth: 2,
132
+ strokeLinecap: "round",
133
+ strokeLinejoin: "round",
134
+ children: PATHS[kind].map((d) => /* @__PURE__ */ jsx("path", { d }, d))
135
+ });
136
+ }
137
+ //#endregion
138
+ //#region src/NodeCard.tsx
139
+ /** The brand node card, without handles (reused by the node types and static galleries). */
140
+ function NodeCard({ kind = "step", icon, title, subtitle, meta, tone, selected, className }) {
141
+ const t = tone ? FLOW_TONES[tone] : void 0;
142
+ const running = Boolean(t?.march);
143
+ const border = selected ? "border-2 border-primary/60 shadow-md ring-2 ring-primary/35" : t?.border ? cx("border-2 shadow-sm", t.border) : "border border-border shadow-sm";
144
+ return /* @__PURE__ */ jsxs("div", {
145
+ className: cx("relative h-full w-full min-w-52", className),
146
+ "data-tone": tone,
147
+ children: [running && /* @__PURE__ */ jsx("svg", {
148
+ "aria-hidden": "true",
149
+ width: "100%",
150
+ height: "100%",
151
+ className: "gu-flow-run-frame pointer-events-none absolute inset-0 z-10",
152
+ children: /* @__PURE__ */ jsx("rect", {
153
+ className: "gu-flow-run fill-none stroke-primary",
154
+ strokeWidth: 2,
155
+ strokeLinecap: "round",
156
+ pathLength: 100,
157
+ strokeDasharray: "22 78"
158
+ })
159
+ }), /* @__PURE__ */ jsxs("div", {
160
+ className: cx("flex h-full w-full flex-col overflow-hidden rounded-lg bg-card transition-shadow", border),
161
+ children: [/* @__PURE__ */ jsxs("div", {
162
+ className: "flex flex-1 items-center gap-2.5 px-3 py-2.5",
163
+ children: [/* @__PURE__ */ jsx("span", {
164
+ "aria-hidden": true,
165
+ className: cx("grid h-8 w-8 shrink-0 place-items-center rounded-lg", KIND_CHIP[kind]),
166
+ children: icon ?? /* @__PURE__ */ jsx(KindGlyph, { kind })
167
+ }), /* @__PURE__ */ jsxs("div", {
168
+ className: "min-w-0 flex-1",
169
+ children: [/* @__PURE__ */ jsx("div", {
170
+ className: "truncate font-sans text-[12.5px] leading-tight font-semibold text-foreground",
171
+ children: title
172
+ }), subtitle && /* @__PURE__ */ jsx("div", {
173
+ className: "mt-0.5 truncate font-mono text-[10px] text-muted-foreground",
174
+ children: subtitle
175
+ })]
176
+ })]
177
+ }), (meta || t) && /* @__PURE__ */ jsxs("div", {
178
+ className: "flex items-center justify-between gap-2 border-t border-border/70 bg-secondary/25 px-3 py-2",
179
+ children: [meta ? /* @__PURE__ */ jsx("span", {
180
+ className: "truncate font-mono text-[10px] text-muted-foreground",
181
+ children: meta
182
+ }) : /* @__PURE__ */ jsx("span", {}), t && /* @__PURE__ */ jsx(FlowStatusBadge, { status: tone })]
183
+ })]
184
+ })]
185
+ });
186
+ }
187
+ //#endregion
188
+ //#region src/nodes.tsx
189
+ const DEFAULT_HANDLES = {
190
+ trigger: {
191
+ target: false,
192
+ source: true
193
+ },
194
+ step: {
195
+ target: true,
196
+ source: true
197
+ },
198
+ router: {
199
+ target: true,
200
+ source: true
201
+ },
202
+ output: {
203
+ target: true,
204
+ source: false
205
+ }
206
+ };
207
+ function makeNode(kind) {
208
+ function BrandNode({ data, selected, sourcePosition, targetPosition, isConnectable }) {
209
+ const handles = {
210
+ ...DEFAULT_HANDLES[kind],
211
+ ...data.handles
212
+ };
213
+ return /* @__PURE__ */ jsxs("div", {
214
+ className: "relative h-full w-full",
215
+ children: [
216
+ handles.target && /* @__PURE__ */ jsx(Handle, {
217
+ type: "target",
218
+ position: targetPosition ?? Position.Left,
219
+ isConnectable
220
+ }),
221
+ /* @__PURE__ */ jsx(NodeCard, {
222
+ kind,
223
+ title: data.title,
224
+ subtitle: data.subtitle,
225
+ meta: data.meta,
226
+ icon: data.icon,
227
+ tone: data.decoration?.status ?? data.tone,
228
+ selected
229
+ }),
230
+ /* @__PURE__ */ jsx(FlowNodeIssues, { issues: data.decoration?.issues }),
231
+ handles.source && /* @__PURE__ */ jsx(Handle, {
232
+ type: "source",
233
+ position: sourcePosition ?? Position.Right,
234
+ isConnectable
235
+ })
236
+ ]
237
+ });
238
+ }
239
+ BrandNode.displayName = `${kind[0]?.toUpperCase()}${kind.slice(1)}Node`;
240
+ return BrandNode;
241
+ }
242
+ /** Entry point: source handle only. */
243
+ const TriggerNode = makeNode("trigger");
244
+ /** Processing step: target + source handles, optional meta and status. */
245
+ const StepNode = makeNode("step");
246
+ /** Conditional branch: target + source handles. */
247
+ const RouterNode = makeNode("router");
248
+ /** Terminal node: target handle only. */
249
+ const OutputNode = makeNode("output");
250
+ /** `nodeTypes` map for `<FlowCanvas>` / `<ReactFlow>` (define it once, outside render). */
251
+ const flowNodeTypes = {
252
+ trigger: TriggerNode,
253
+ step: StepNode,
254
+ router: RouterNode,
255
+ output: OutputNode
256
+ };
257
+ //#endregion
258
+ //#region src/theme.ts
259
+ function readFlowTheme(theme) {
260
+ return {
261
+ theme,
262
+ edge: tokenColor("--muted-foreground"),
263
+ edgeActive: tokenColor("--primary"),
264
+ pattern: tokenColor("--border"),
265
+ color: (token, alpha) => tokenColor(token, alpha)
266
+ };
267
+ }
268
+ /**
269
+ * Brand colours for the parts React Flow paints from props (arrow markers, minimap nodes,
270
+ * background pattern), resolved through `@gntik-ai/tokens/runtime` so they also survive
271
+ * SVG/PNG export. Re-read on every theme class change.
272
+ */
273
+ function useFlowTheme() {
274
+ const [snapshot, setSnapshot] = useState(() => readFlowTheme(currentTheme()));
275
+ useEffect(() => observeTheme((next) => setSnapshot(readFlowTheme(next))), []);
276
+ return snapshot;
277
+ }
278
+ /** Closed arrow marker in a given colour. */
279
+ function flowMarker(color) {
280
+ return {
281
+ type: MarkerType.ArrowClosed,
282
+ width: 15,
283
+ height: 15,
284
+ color
285
+ };
286
+ }
287
+ /** Brand edge defaults: orthogonal (smoothstep) routing. */
288
+ const brandEdgeOptions = { type: "smoothstep" };
289
+ /** Adds an arrow to edges that have none: primary when animated (active), muted otherwise. */
290
+ function withBrandMarkers(edges, t) {
291
+ return edges.map((e) => e.markerEnd ? e : {
292
+ ...e,
293
+ markerEnd: flowMarker(e.animated ? t.edgeActive : t.edge)
294
+ });
295
+ }
296
+ //#endregion
297
+ //#region src/renderers.tsx
298
+ const POSITIONS = {
299
+ left: Position.Left,
300
+ right: Position.Right,
301
+ top: Position.Top,
302
+ bottom: Position.Bottom
303
+ };
304
+ /** Internal adapter; only the kit contracts above are exported from the package entry. */
305
+ function adaptNodeRenderers(renderers) {
306
+ const types = { ...flowNodeTypes };
307
+ for (const definition of renderers) {
308
+ const counts = {
309
+ left: 0,
310
+ right: 0,
311
+ top: 0,
312
+ bottom: 0
313
+ };
314
+ const handles = definition.handles.map((handle) => {
315
+ const position = handle.position ?? (handle.kind === "target" ? "left" : "right");
316
+ return {
317
+ ...handle,
318
+ position,
319
+ index: ++counts[position]
320
+ };
321
+ });
322
+ const verticalCount = Math.max(counts.left, counts.right);
323
+ const horizontalCount = Math.max(counts.top, counts.bottom);
324
+ types[definition.type] = memo(function CustomNode({ id, data, selected, isConnectable }) {
325
+ return /* @__PURE__ */ jsxs("div", {
326
+ className: "relative grid h-full w-full",
327
+ style: {
328
+ minHeight: verticalCount ? (verticalCount + 1) * 32 : void 0,
329
+ minWidth: horizontalCount ? (horizontalCount + 1) * 32 : void 0
330
+ },
331
+ children: [
332
+ handles.map((handle) => /* @__PURE__ */ jsx(Handle, {
333
+ className: "gu-flow-custom-handle",
334
+ id: handle.id,
335
+ type: handle.kind,
336
+ position: POSITIONS[handle.position],
337
+ isConnectable,
338
+ "aria-label": handle.label ?? handle.id,
339
+ role: "button",
340
+ tabIndex: isConnectable ? 0 : -1,
341
+ "aria-disabled": !isConnectable,
342
+ onKeyDown: (event) => {
343
+ if (!isConnectable || event.key !== "Enter" && event.key !== " ") return;
344
+ event.preventDefault();
345
+ event.stopPropagation();
346
+ event.currentTarget.click();
347
+ },
348
+ style: { [handle.position === "top" || handle.position === "bottom" ? "left" : "top"]: `${handle.index / (counts[handle.position] + 1) * 100}%` }
349
+ }, `${handle.kind}-${handle.id}`)),
350
+ definition.decorations !== "consumer" && /* @__PURE__ */ jsxs(Fragment, { children: [data.decoration?.status && /* @__PURE__ */ jsx("div", {
351
+ className: "pointer-events-none absolute -top-2 right-1 z-10",
352
+ children: /* @__PURE__ */ jsx(FlowStatusBadge, { status: data.decoration.status })
353
+ }), /* @__PURE__ */ jsx(FlowNodeIssues, { issues: data.decoration?.issues })] }),
354
+ definition.render({
355
+ id,
356
+ type: definition.type,
357
+ data,
358
+ selected,
359
+ connectable: isConnectable
360
+ })
361
+ ]
362
+ });
363
+ });
364
+ }
365
+ return types;
366
+ }
367
+ //#endregion
368
+ //#region src/FlowCanvas.tsx
369
+ const STATIC_PROPS = {
370
+ nodesDraggable: false,
371
+ nodesConnectable: false,
372
+ elementsSelectable: false,
373
+ panOnDrag: false,
374
+ panOnScroll: false,
375
+ zoomOnScroll: false,
376
+ zoomOnPinch: false,
377
+ zoomOnDoubleClick: false,
378
+ preventScrolling: false
379
+ };
380
+ /**
381
+ * React Flow canvas themed from the brand tokens: dotted background, controls and minimap,
382
+ * brand node types, smoothstep edges with arrows (primary when animated). Drag, connect and
383
+ * zoom/pan are on by default. Import `@gntik-ai/flow/styles.css` once.
384
+ */
385
+ function FlowCanvas({ "aria-label": label, height = 480, showControls = true, showInteractiveControls = true, showMiniMap = true, showBackground = true, interactive = true, className, canvasClassName, nodeTypes, nodeRenderers, edges, defaultEdgeOptions, proOptions, children, ...rest }) {
386
+ const t = useFlowTheme();
387
+ const customTypes = useMemo(() => nodeRenderers ? adaptNodeRenderers(nodeRenderers) : void 0, [nodeRenderers]);
388
+ const brandedEdges = useMemo(() => edges ? withBrandMarkers(edges, t) : void 0, [edges, t]);
389
+ const edgeOptions = useMemo(() => ({
390
+ ...brandEdgeOptions,
391
+ markerEnd: flowMarker(t.edge),
392
+ ...defaultEdgeOptions
393
+ }), [t, defaultEdgeOptions]);
394
+ const miniColor = useCallback((n) => t.color(nodeToken(n.data.tone, n.type)), [t]);
395
+ return /* @__PURE__ */ jsx("div", {
396
+ role: "region",
397
+ "aria-label": label,
398
+ "data-theme": t.theme,
399
+ className: cx("gu-flow relative w-full bg-background", className),
400
+ style: { height },
401
+ children: /* @__PURE__ */ jsxs(ReactFlow, {
402
+ nodeTypes: customTypes ?? nodeTypes ?? flowNodeTypes,
403
+ edges: brandedEdges,
404
+ defaultEdgeOptions: edgeOptions,
405
+ fitView: true,
406
+ fitViewOptions: {
407
+ padding: .12,
408
+ maxZoom: 1
409
+ },
410
+ minZoom: .3,
411
+ maxZoom: 1.75,
412
+ snapToGrid: true,
413
+ snapGrid: [16, 16],
414
+ proOptions: proOptions ?? (interactive ? void 0 : { hideAttribution: true }),
415
+ ...interactive ? void 0 : STATIC_PROPS,
416
+ ...rest,
417
+ className: canvasClassName,
418
+ children: [
419
+ showBackground && /* @__PURE__ */ jsx(Background, {
420
+ variant: BackgroundVariant.Dots,
421
+ gap: 20,
422
+ size: 1,
423
+ color: t.pattern
424
+ }),
425
+ interactive && showControls && /* @__PURE__ */ jsx(Controls, { showInteractive: showInteractiveControls }),
426
+ interactive && showMiniMap && /* @__PURE__ */ jsx(MiniMap, {
427
+ pannable: true,
428
+ zoomable: true,
429
+ nodeColor: miniColor,
430
+ nodeStrokeWidth: 0,
431
+ nodeBorderRadius: 3
432
+ }),
433
+ children
434
+ ]
435
+ })
436
+ });
437
+ }
438
+ //#endregion
439
+ //#region src/useFlowState.ts
440
+ /**
441
+ * Local nodes/edges state wired for drag, select and connect. New connections become
442
+ * active (animated) smoothstep edges. Spread the result into `<FlowCanvas>`.
443
+ */
444
+ function useFlowState(initialNodes, initialEdges) {
445
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
446
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
447
+ return {
448
+ nodes,
449
+ edges,
450
+ setNodes,
451
+ setEdges,
452
+ onNodesChange,
453
+ onEdgesChange,
454
+ onConnect: useCallback((c) => {
455
+ const active = Object.assign({
456
+ type: "smoothstep",
457
+ animated: true
458
+ }, c);
459
+ setEdges((eds) => addEdge(active, eds));
460
+ }, [setEdges])
461
+ };
462
+ }
463
+ //#endregion
464
+ export { FLOW_TONES, FlowCanvas, FlowStatusBadge, KIND_CHIP, KindGlyph, NodeCard, OutputNode, RouterNode, StepNode, TriggerNode, brandEdgeOptions, flowMarker, flowNodeTypes, isFlowTone, nodeToken, useFlowState, useFlowTheme, withBrandMarkers };
465
+
466
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/tones.ts","../src/decorations.tsx","../src/glyphs.tsx","../src/NodeCard.tsx","../src/nodes.tsx","../src/theme.ts","../src/renderers.tsx","../src/FlowCanvas.tsx","../src/useFlowState.ts"],"sourcesContent":["/** Node status → brand colour. Green = active/healthy (never severity); amber = degraded;\n * red = failed; violet/rose = needs a human (handoff / review). */\nexport type FlowTone = 'running' | 'degraded' | 'failed' | 'paused' | 'done' | 'handoff' | 'review';\n\n/** The role of a node, which decides its handles and its icon chip. */\nexport type FlowNodeKind = 'trigger' | 'step' | 'router' | 'output';\n\nexport interface FlowToneStyle {\n label: string;\n /** Pill background + text (contrast-safe text aliases). */\n pill: string;\n /** Status dot. */\n dot: string;\n /** Thick card border, or null for the neutral border. */\n border: string | null;\n /** Brand token used for the minimap swatch. */\n token: string;\n /** Draws the marching segment around the card. */\n march?: boolean;\n}\n\nexport const FLOW_TONES: Record<FlowTone, FlowToneStyle> = {\n running: {\n label: 'Running',\n pill: 'bg-primary/14 text-primary-text',\n dot: 'bg-primary',\n border: 'border-primary/60',\n token: '--primary',\n march: true,\n },\n degraded: {\n label: 'Degraded',\n pill: 'bg-warning/16 text-warning-text',\n dot: 'bg-warning',\n border: 'border-warning/65',\n token: '--warning',\n },\n failed: {\n label: 'Failed',\n pill: 'bg-destructive/15 text-destructive-text',\n dot: 'bg-destructive',\n border: 'border-destructive/65',\n token: '--destructive',\n },\n paused: {\n label: 'Paused',\n pill: 'bg-muted-foreground/16 text-muted-foreground',\n dot: 'bg-muted-foreground',\n border: null,\n token: '--muted-foreground',\n },\n done: {\n label: 'Done',\n pill: 'bg-primary/14 text-primary-text',\n dot: 'bg-primary',\n border: 'border-primary/70',\n token: '--primary',\n },\n handoff: {\n label: 'Handoff',\n pill: 'bg-category-violet/16 text-foreground',\n dot: 'bg-category-violet',\n border: 'border-category-violet',\n token: '--category-violet',\n },\n review: {\n label: 'Review',\n pill: 'bg-category-rose/16 text-foreground',\n dot: 'bg-category-rose',\n border: 'border-category-rose',\n token: '--category-rose',\n },\n};\n\n/** Icon chip per node kind. */\nexport const KIND_CHIP: Record<FlowNodeKind, string> = {\n trigger: 'bg-primary text-primary-foreground',\n step: 'bg-primary/14 text-primary-text',\n router: 'border border-border bg-secondary text-foreground',\n output: 'bg-accent text-accent-foreground',\n};\n\nexport function isFlowTone(v: unknown): v is FlowTone {\n return typeof v === 'string' && v in FLOW_TONES;\n}\n\n/** Token for a node's minimap swatch: its tone, else muted for routers, else primary. */\nexport function nodeToken(tone: unknown, kind: unknown): string {\n if (isFlowTone(tone)) return FLOW_TONES[tone].token;\n return kind === 'router' ? '--muted-foreground' : '--primary';\n}\n\nexport function cx(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(' ');\n}\n","import { memo } from 'react';\nimport { cx, FLOW_TONES, type FlowTone } from './tones';\n\nexport interface FlowNodeDecoration {\n status?: FlowTone;\n issues?: readonly { code: string; message: string }[];\n}\n\n/** Memoized per node: unrelated status changes do not render this badge again. */\nexport const FlowStatusBadge = memo(function FlowStatusBadge({ status }: { status: FlowTone }) {\n const tone = FLOW_TONES[status];\n return (\n <span\n className={cx(\n 'inline-flex h-[18px] shrink-0 items-center gap-1 rounded px-1.5 font-mono text-[9.5px] font-semibold',\n tone.pill,\n )}\n >\n <span\n aria-hidden\n className={cx('h-1.5 w-1.5 rounded-full', tone.dot, tone.march && 'animate-pulse motion-reduce:animate-none')}\n />\n {tone.label}\n </span>\n );\n});\n\nexport function FlowNodeIssues({ issues }: { issues: FlowNodeDecoration['issues'] }) {\n if (!issues?.length) return null;\n return (\n <div className=\"absolute top-full left-0 flex w-full items-start gap-1 rounded border border-destructive/65 bg-card p-1 text-[11px] text-destructive-text\">\n <svg\n aria-hidden=\"true\"\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n className=\"shrink-0 fill-none stroke-current\"\n strokeWidth=\"2\"\n >\n <path d=\"m12 3 10 18H2Z M12 9v5 M12 17v1\" />\n </svg>\n <span>\n <span className=\"sr-only\">Validation issue: </span>\n {issues.map((issue) => issue.message).join('; ')}\n </span>\n </div>\n );\n}\n","import type { FlowNodeKind } from './tones';\n\nconst PATHS: Record<FlowNodeKind, string[]> = {\n trigger: ['M13 2 3 14h9l-1 8 10-12h-9l1-8z'],\n step: ['M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z', 'M9 9h6v6H9z'],\n router: ['M16 3h5v5', 'M8 3H3v5', 'M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3', 'm15 9 6-6'],\n output: ['M20 6 9 17l-5-5'],\n};\n\n/** Default decorative icon for a node kind (stroke = currentColor). */\nexport function KindGlyph({ kind, size = 16 }: { kind: FlowNodeKind; size?: number }) {\n return (\n <svg\n aria-hidden=\"true\"\n focusable=\"false\"\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n {PATHS[kind].map((d) => (\n <path key={d} d={d} />\n ))}\n </svg>\n );\n}\n","import type { ReactNode } from 'react';\nimport { FlowStatusBadge } from './decorations';\nimport { KindGlyph } from './glyphs';\nimport { cx, FLOW_TONES, KIND_CHIP, type FlowNodeKind, type FlowTone } from './tones';\n\nexport interface NodeCardProps {\n kind?: FlowNodeKind;\n title: string;\n subtitle?: string;\n /** Footer text (left), e.g. a version or an owner. */\n meta?: string;\n /** Icon element (decorative); defaults to the kind's glyph. */\n icon?: ReactNode;\n tone?: FlowTone;\n selected?: boolean;\n className?: string;\n}\n\n/** The brand node card, without handles (reused by the node types and static galleries). */\nexport function NodeCard({ kind = 'step', icon, title, subtitle, meta, tone, selected, className }: NodeCardProps) {\n const t = tone ? FLOW_TONES[tone] : undefined;\n const running = Boolean(t?.march);\n const border = selected\n ? 'border-2 border-primary/60 shadow-md ring-2 ring-primary/35'\n : t?.border\n ? cx('border-2 shadow-sm', t.border)\n : 'border border-border shadow-sm';\n return (\n <div className={cx('relative h-full w-full min-w-52', className)} data-tone={tone}>\n {running && (\n <svg\n aria-hidden=\"true\"\n width=\"100%\"\n height=\"100%\"\n className=\"gu-flow-run-frame pointer-events-none absolute inset-0 z-10\"\n >\n <rect\n className=\"gu-flow-run fill-none stroke-primary\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n pathLength={100}\n strokeDasharray=\"22 78\"\n />\n </svg>\n )}\n <div className={cx('flex h-full w-full flex-col overflow-hidden rounded-lg bg-card transition-shadow', border)}>\n <div className=\"flex flex-1 items-center gap-2.5 px-3 py-2.5\">\n <span aria-hidden className={cx('grid h-8 w-8 shrink-0 place-items-center rounded-lg', KIND_CHIP[kind])}>\n {icon ?? <KindGlyph kind={kind} />}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate font-sans text-[12.5px] leading-tight font-semibold text-foreground\">{title}</div>\n {subtitle && <div className=\"mt-0.5 truncate font-mono text-[10px] text-muted-foreground\">{subtitle}</div>}\n </div>\n </div>\n {(meta || t) && (\n <div className=\"flex items-center justify-between gap-2 border-t border-border/70 bg-secondary/25 px-3 py-2\">\n {meta ? <span className=\"truncate font-mono text-[10px] text-muted-foreground\">{meta}</span> : <span />}\n {t && <FlowStatusBadge status={tone!} />}\n </div>\n )}\n </div>\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport { Handle, Position, type Node, type NodeProps, type NodeTypes } from '@xyflow/react';\nimport { FlowNodeIssues, type FlowNodeDecoration } from './decorations';\nimport { NodeCard } from './NodeCard';\nimport type { FlowNodeKind, FlowTone } from './tones';\n\n/** Generic node content. No domain data: title, subtitle, meta, icon and a status tone. */\nexport type FlowNodeData = {\n decoration?: FlowNodeDecoration;\n title: string;\n subtitle?: string;\n meta?: string;\n /** Decorative icon element (e.g. a lucide icon); defaults to the kind's glyph. */\n icon?: ReactNode;\n tone?: FlowTone;\n /** Override the kind's default handles. */\n handles?: { target?: boolean; source?: boolean };\n};\n\n/** Built-in kinds by default; consumers can opt into custom type identifiers. */\nexport type FlowNode<Kind extends string = FlowNodeKind> = Omit<Node<FlowNodeData, FlowNodeKind>, 'type'> & {\n type: Kind;\n};\n\nconst DEFAULT_HANDLES: Record<FlowNodeKind, { target: boolean; source: boolean }> = {\n trigger: { target: false, source: true },\n step: { target: true, source: true },\n router: { target: true, source: true },\n output: { target: true, source: false },\n};\n\nfunction makeNode(kind: FlowNodeKind) {\n function BrandNode({ data, selected, sourcePosition, targetPosition, isConnectable }: NodeProps<FlowNode>) {\n const handles = { ...DEFAULT_HANDLES[kind], ...data.handles };\n return (\n <div className=\"relative h-full w-full\">\n {handles.target && (\n <Handle type=\"target\" position={targetPosition ?? Position.Left} isConnectable={isConnectable} />\n )}\n <NodeCard\n kind={kind}\n title={data.title}\n subtitle={data.subtitle}\n meta={data.meta}\n icon={data.icon}\n tone={data.decoration?.status ?? data.tone}\n selected={selected}\n />\n <FlowNodeIssues issues={data.decoration?.issues} />\n {handles.source && (\n <Handle type=\"source\" position={sourcePosition ?? Position.Right} isConnectable={isConnectable} />\n )}\n </div>\n );\n }\n BrandNode.displayName = `${kind[0]?.toUpperCase()}${kind.slice(1)}Node`;\n return BrandNode;\n}\n\n/** Entry point: source handle only. */\nexport const TriggerNode = makeNode('trigger');\n/** Processing step: target + source handles, optional meta and status. */\nexport const StepNode = makeNode('step');\n/** Conditional branch: target + source handles. */\nexport const RouterNode = makeNode('router');\n/** Terminal node: target handle only. */\nexport const OutputNode = makeNode('output');\n\n/** `nodeTypes` map for `<FlowCanvas>` / `<ReactFlow>` (define it once, outside render). */\nexport const flowNodeTypes = {\n trigger: TriggerNode,\n step: StepNode,\n router: RouterNode,\n output: OutputNode,\n} satisfies NodeTypes;\n","import { useEffect, useState } from 'react';\nimport { MarkerType, type DefaultEdgeOptions, type Edge, type EdgeMarker } from '@xyflow/react';\nimport { currentTheme, observeTheme, tokenColor, type Theme } from '@gntik-ai/tokens/runtime';\n\nexport interface FlowTheme {\n theme: Theme;\n /** Idle edge / arrow colour. */\n edge: string;\n /** Active (animated) edge / arrow colour. */\n edgeActive: string;\n /** Background dot pattern. */\n pattern: string;\n /** Resolves any brand token (e.g. \"--warning\") to a concrete colour. */\n color: (token: string, alpha?: number) => string;\n}\n\nfunction readFlowTheme(theme: Theme): FlowTheme {\n return {\n theme,\n edge: tokenColor('--muted-foreground'),\n edgeActive: tokenColor('--primary'),\n pattern: tokenColor('--border'),\n color: (token, alpha) => tokenColor(token, alpha),\n };\n}\n\n/**\n * Brand colours for the parts React Flow paints from props (arrow markers, minimap nodes,\n * background pattern), resolved through `@gntik-ai/tokens/runtime` so they also survive\n * SVG/PNG export. Re-read on every theme class change.\n */\nexport function useFlowTheme(): FlowTheme {\n const [snapshot, setSnapshot] = useState<FlowTheme>(() => readFlowTheme(currentTheme()));\n useEffect(() => observeTheme((next) => setSnapshot(readFlowTheme(next))), []);\n return snapshot;\n}\n\n/** Closed arrow marker in a given colour. */\nexport function flowMarker(color: string): EdgeMarker {\n return { type: MarkerType.ArrowClosed, width: 15, height: 15, color };\n}\n\n/** Brand edge defaults: orthogonal (smoothstep) routing. */\nexport const brandEdgeOptions: DefaultEdgeOptions = { type: 'smoothstep' };\n\n/** Adds an arrow to edges that have none: primary when animated (active), muted otherwise. */\nexport function withBrandMarkers<E extends Edge>(edges: readonly E[], t: FlowTheme): E[] {\n return edges.map((e) => (e.markerEnd ? e : { ...e, markerEnd: flowMarker(e.animated ? t.edgeActive : t.edge) }));\n}\n","import { memo, type ReactNode } from 'react';\nimport { Handle, Position, type NodeProps, type NodeTypes } from '@xyflow/react';\nimport { FlowNodeIssues, FlowStatusBadge } from './decorations';\nimport { flowNodeTypes, type FlowNode, type FlowNodeData } from './nodes';\n\n/** Named connection point. Positions default to left for targets and right for sources. */\nexport interface FlowHandle {\n id: string;\n kind: 'source' | 'target';\n position?: 'left' | 'right' | 'top' | 'bottom';\n label?: string;\n}\n\n/** Kit-owned renderer input: no React Flow component types are required by consumers. */\nexport interface FlowNodeRenderProps {\n id: string;\n type: string;\n data: FlowNodeData;\n selected: boolean;\n connectable: boolean;\n}\n\nexport interface FlowNodeRenderer {\n type: string;\n handles: readonly FlowHandle[];\n /** Defaults to kit overlays. Consumer renderers may own accessible status and issue decorations. */\n decorations?: 'kit' | 'consumer';\n render: (props: FlowNodeRenderProps) => ReactNode;\n}\n\nconst POSITIONS = {\n left: Position.Left,\n right: Position.Right,\n top: Position.Top,\n bottom: Position.Bottom,\n};\n\n/** Internal adapter; only the kit contracts above are exported from the package entry. */\nexport function adaptNodeRenderers(renderers: readonly FlowNodeRenderer[]): NodeTypes {\n const types: NodeTypes = { ...flowNodeTypes };\n for (const definition of renderers) {\n const counts = { left: 0, right: 0, top: 0, bottom: 0 };\n const handles = definition.handles.map((handle) => {\n const position = handle.position ?? (handle.kind === 'target' ? 'left' : 'right');\n return { ...handle, position, index: ++counts[position] };\n });\n const verticalCount = Math.max(counts.left, counts.right);\n const horizontalCount = Math.max(counts.top, counts.bottom);\n types[definition.type] = memo(function CustomNode({\n id,\n data,\n selected,\n isConnectable,\n }: NodeProps<FlowNode<string>>) {\n return (\n <div\n className=\"relative grid h-full w-full\"\n style={{\n minHeight: verticalCount ? (verticalCount + 1) * 32 : undefined,\n minWidth: horizontalCount ? (horizontalCount + 1) * 32 : undefined,\n }}\n >\n {handles.map((handle) => (\n <Handle\n key={`${handle.kind}-${handle.id}`}\n className=\"gu-flow-custom-handle\"\n id={handle.id}\n type={handle.kind}\n position={POSITIONS[handle.position]}\n isConnectable={isConnectable}\n aria-label={handle.label ?? handle.id}\n role=\"button\"\n tabIndex={isConnectable ? 0 : -1}\n aria-disabled={!isConnectable}\n onKeyDown={(event) => {\n if (!isConnectable || (event.key !== 'Enter' && event.key !== ' ')) return;\n event.preventDefault();\n event.stopPropagation();\n event.currentTarget.click();\n }}\n style={{\n [handle.position === 'top' || handle.position === 'bottom' ? 'left' : 'top']:\n `${(handle.index / (counts[handle.position] + 1)) * 100}%`,\n }}\n />\n ))}\n {definition.decorations !== 'consumer' && (\n <>\n {data.decoration?.status && (\n <div className=\"pointer-events-none absolute -top-2 right-1 z-10\">\n <FlowStatusBadge status={data.decoration.status} />\n </div>\n )}\n <FlowNodeIssues issues={data.decoration?.issues} />\n </>\n )}\n {definition.render({\n id,\n type: definition.type,\n data,\n selected,\n connectable: isConnectable,\n })}\n </div>\n );\n });\n }\n return types;\n}\n","import { useCallback, useMemo } from 'react';\nimport {\n Background,\n BackgroundVariant,\n Controls,\n MiniMap,\n ReactFlow,\n type Edge,\n type Node,\n type NodeTypes,\n type ReactFlowProps,\n} from '@xyflow/react';\nimport { flowNodeTypes, type FlowNode } from './nodes';\nimport { brandEdgeOptions, flowMarker, useFlowTheme, withBrandMarkers } from './theme';\nimport { adaptNodeRenderers, type FlowNodeRenderer } from './renderers';\nimport { cx, nodeToken } from './tones';\n\nexport interface FlowCanvasProps<N extends Node = FlowNode, E extends Edge = Edge> extends Omit<\n ReactFlowProps<N, E>,\n 'aria-label' | 'height' | 'width'\n> {\n /** Accessible name of the canvas region (required). */\n 'aria-label': string;\n /** Canvas height (px or CSS length). */\n height?: number | string;\n showControls?: boolean;\n /** Hide the control that toggles graph editing, while retaining zoom and fit controls. */\n showInteractiveControls?: boolean;\n showMiniMap?: boolean;\n showBackground?: boolean;\n /** false = static diagram: no pan/zoom/connect/select (nodes still render). */\n interactive?: boolean;\n /** Class for the inner `.react-flow` element (`className` goes on the wrapper). */\n canvasClassName?: string;\n /** Kit-owned custom node renderers with named handles. */\n nodeRenderers?: readonly FlowNodeRenderer[];\n}\n\nconst STATIC_PROPS = {\n nodesDraggable: false,\n nodesConnectable: false,\n elementsSelectable: false,\n panOnDrag: false,\n panOnScroll: false,\n zoomOnScroll: false,\n zoomOnPinch: false,\n zoomOnDoubleClick: false,\n preventScrolling: false,\n} as const;\n\n/**\n * React Flow canvas themed from the brand tokens: dotted background, controls and minimap,\n * brand node types, smoothstep edges with arrows (primary when animated). Drag, connect and\n * zoom/pan are on by default. Import `@gntik-ai/flow/styles.css` once.\n */\nexport function FlowCanvas<N extends Node = FlowNode, E extends Edge = Edge>({\n 'aria-label': label,\n height = 480,\n showControls = true,\n showInteractiveControls = true,\n showMiniMap = true,\n showBackground = true,\n interactive = true,\n className,\n canvasClassName,\n nodeTypes,\n nodeRenderers,\n edges,\n defaultEdgeOptions,\n proOptions,\n children,\n ...rest\n}: FlowCanvasProps<N, E>) {\n const t = useFlowTheme();\n const customTypes = useMemo(() => (nodeRenderers ? adaptNodeRenderers(nodeRenderers) : undefined), [nodeRenderers]);\n const brandedEdges = useMemo(() => (edges ? withBrandMarkers(edges, t) : undefined), [edges, t]);\n const edgeOptions = useMemo(\n () => ({ ...brandEdgeOptions, markerEnd: flowMarker(t.edge), ...defaultEdgeOptions }),\n [t, defaultEdgeOptions],\n );\n const miniColor = useCallback((n: N) => t.color(nodeToken(n.data.tone, n.type)), [t]);\n return (\n <div\n role=\"region\"\n aria-label={label}\n data-theme={t.theme}\n className={cx('gu-flow relative w-full bg-background', className)}\n style={{ height }}\n >\n <ReactFlow<N, E>\n nodeTypes={customTypes ?? nodeTypes ?? (flowNodeTypes as NodeTypes)}\n edges={brandedEdges}\n defaultEdgeOptions={edgeOptions}\n fitView\n fitViewOptions={{ padding: 0.12, maxZoom: 1 }}\n minZoom={0.3}\n maxZoom={1.75}\n snapToGrid\n snapGrid={[16, 16]}\n proOptions={proOptions ?? (interactive ? undefined : { hideAttribution: true })}\n {...(interactive ? undefined : STATIC_PROPS)}\n {...rest}\n className={canvasClassName}\n >\n {showBackground && <Background variant={BackgroundVariant.Dots} gap={20} size={1} color={t.pattern} />}\n {interactive && showControls && <Controls showInteractive={showInteractiveControls} />}\n {interactive && showMiniMap && (\n <MiniMap<N> pannable zoomable nodeColor={miniColor} nodeStrokeWidth={0} nodeBorderRadius={3} />\n )}\n {children}\n </ReactFlow>\n </div>\n );\n}\n","import { useCallback } from 'react';\nimport { addEdge, useEdgesState, useNodesState, type Connection, type Edge, type Node } from '@xyflow/react';\nimport type { FlowNode } from './nodes';\n\n/**\n * Local nodes/edges state wired for drag, select and connect. New connections become\n * active (animated) smoothstep edges. Spread the result into `<FlowCanvas>`.\n */\nexport function useFlowState<N extends Node = FlowNode, E extends Edge = Edge>(initialNodes: N[], initialEdges: E[]) {\n const [nodes, setNodes, onNodesChange] = useNodesState<N>(initialNodes);\n const [edges, setEdges, onEdgesChange] = useEdgesState<E>(initialEdges);\n const onConnect = useCallback(\n (c: Connection) => {\n // A Connection plus brand edge props (addEdge keeps the extra fields).\n const active: Connection = Object.assign({ type: 'smoothstep', animated: true }, c);\n setEdges((eds) => addEdge<E>(active, eds));\n },\n [setEdges],\n );\n return { nodes, edges, setNodes, setEdges, onNodesChange, onEdgesChange, onConnect };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@gntik-ai/flow",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.5.0",
4
+ "description": "React Flow (@xyflow/react 12) canvas, nodes and edges themed by the gntik-ui brand tokens.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": [
8
+ "*.css"
9
+ ],
10
+ "files": [
11
+ "dist",
12
+ "src/styles.css"
13
+ ],
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./package.json": "./package.json",
22
+ "./styles.css": "./src/styles.css"
23
+ },
24
+ "publishConfig": {
25
+ "registry": "https://registry.npmjs.org/",
26
+ "access": "public"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/gntik-ai/gntik-ui.git",
31
+ "directory": "packages/flow"
32
+ },
33
+ "peerDependencies": {
34
+ "react": "^19.0.0",
35
+ "react-dom": "^19.0.0"
36
+ },
37
+ "dependencies": {
38
+ "@gntik-ai/tokens": "0.2.1",
39
+ "@xyflow/react": "^12.11.0"
40
+ },
41
+ "devDependencies": {
42
+ "@testing-library/jest-dom": "^7.0.1",
43
+ "@testing-library/react": "^16.3.3",
44
+ "@types/react": "^19.3.0",
45
+ "@types/react-dom": "^19.3.0",
46
+ "jsdom": "^30.1.1",
47
+ "react": "^19.3.0",
48
+ "react-dom": "^19.3.0",
49
+ "tsdown": "^0.23.0",
50
+ "typescript": "~6.0.3",
51
+ "vitest": "^5.0.2"
52
+ },
53
+ "scripts": {
54
+ "build": "tsdown",
55
+ "typecheck": "tsc -p tsconfig.json --noEmit",
56
+ "test": "vitest run"
57
+ }
6
58
  }
package/src/styles.css ADDED
@@ -0,0 +1,138 @@
1
+ /* ============================================================================
2
+ @gntik-ai/flow/styles.css — React Flow themed by the gntik-ui brand tokens.
3
+ Load once, after the token layer (@gntik-ai/tokens/brand.css). Every --xy-*
4
+ variable points at a brand token, so the three themes of brand.css
5
+ (:root = light · .dark · .high_contrast) re-skin the canvas with no JS.
6
+ Scope: anything inside `.gu-flow` (FlowCanvas adds it to its wrapper).
7
+ ============================================================================ */
8
+ @import "@xyflow/react/dist/style.css";
9
+ @source "../dist";
10
+
11
+ /* ── Variables: light (:root) and dark (.dark) share the mapping; the tokens differ ── */
12
+ .gu-flow,
13
+ .dark .gu-flow {
14
+ --xy-background-color: transparent;
15
+ --xy-background-pattern-color: hsl(var(--border));
16
+
17
+ --xy-edge-stroke: hsl(var(--muted-foreground) / 0.45);
18
+ --xy-edge-stroke-width: 1.5;
19
+ --xy-edge-stroke-selected: hsl(var(--primary));
20
+ --xy-edge-label-background-color: hsl(var(--card));
21
+ --xy-edge-label-color: hsl(var(--foreground));
22
+ --xy-connectionline-stroke: hsl(var(--primary));
23
+ --xy-connectionline-stroke-width: 1.75;
24
+
25
+ --xy-node-color: hsl(var(--card-foreground));
26
+ --xy-node-background-color: hsl(var(--card));
27
+ --xy-node-border: 1px solid hsl(var(--border));
28
+ --xy-node-border-radius: var(--radius-md);
29
+ --xy-node-group-background-color: hsl(var(--muted) / 0.4);
30
+ --xy-node-boxshadow-hover: var(--shadow-sm);
31
+ --xy-node-boxshadow-selected: 0 0 0 2px hsl(var(--primary) / 0.35);
32
+
33
+ --xy-handle-background-color: hsl(var(--card));
34
+ --xy-handle-border-color: hsl(var(--muted-foreground) / 0.55);
35
+
36
+ --xy-selection-background-color: hsl(var(--primary) / 0.08);
37
+ --xy-selection-border: 1px dashed hsl(var(--primary) / 0.6);
38
+ --xy-resize-background-color: hsl(var(--primary));
39
+
40
+ --xy-controls-button-background-color: hsl(var(--card));
41
+ --xy-controls-button-background-color-hover: hsl(var(--secondary));
42
+ --xy-controls-button-color: hsl(var(--foreground));
43
+ --xy-controls-button-color-hover: hsl(var(--foreground));
44
+ --xy-controls-button-border-color: hsl(var(--border));
45
+ --xy-controls-box-shadow: var(--shadow-md);
46
+
47
+ --xy-minimap-background-color: hsl(var(--card));
48
+ --xy-minimap-mask-background-color: hsl(var(--muted-foreground) / 0.12);
49
+ --xy-minimap-mask-stroke-color: hsl(var(--border));
50
+ --xy-minimap-mask-stroke-width: 1;
51
+ --xy-minimap-node-background-color: hsl(var(--muted-foreground) / 0.4);
52
+ --xy-minimap-node-stroke-color: transparent;
53
+
54
+ --xy-attribution-background-color: transparent;
55
+ }
56
+
57
+ /* ── High contrast: solid strokes, stronger handles and mask ─────────────────── */
58
+ .high_contrast .gu-flow {
59
+ --xy-edge-stroke: hsl(var(--foreground) / 0.75);
60
+ --xy-edge-stroke-width: 2;
61
+ --xy-handle-border-color: hsl(var(--foreground));
62
+ --xy-node-border: 2px solid hsl(var(--border));
63
+ --xy-selection-border: 2px solid hsl(var(--primary));
64
+ --xy-minimap-mask-background-color: hsl(var(--foreground) / 0.18);
65
+ }
66
+
67
+ /* ── Structure ───────────────────────────────────────────────────────────────── */
68
+ .gu-flow .react-flow__pane { cursor: grab; }
69
+ .gu-flow .react-flow__pane.dragging { cursor: grabbing; }
70
+
71
+ /* Active (animated) edges and the edge being connected are primary. */
72
+ .gu-flow .react-flow__edge.animated .react-flow__edge-path { stroke: hsl(var(--primary)); stroke-width: 1.75px; }
73
+ .gu-flow .react-flow__edge:focus-visible .react-flow__edge-path { stroke: hsl(var(--primary)); }
74
+ .gu-flow .react-flow__edge-text { font-family: var(--font-mono); font-size: 10px; font-weight: 500; }
75
+
76
+ /* Handles: small card dot → primary on hover / while connecting. */
77
+ .gu-flow .react-flow__handle {
78
+ width: 9px; height: 9px; min-width: 0; min-height: 0; border-width: 1.5px; border-radius: 9999px;
79
+ transition: background-color 0.12s, border-color 0.12s;
80
+ }
81
+ .gu-flow .react-flow__handle:hover,
82
+ .gu-flow .react-flow__handle.connectingto,
83
+ .gu-flow .react-flow__handle.connectingfrom,
84
+ .gu-flow .react-flow__handle.valid { background: hsl(var(--primary)); border-color: hsl(var(--primary)); }
85
+
86
+ /* Named handles retain the small dot inside a separate 32px pointer target.
87
+ Custom renderers reserve space per side so adjacent targets do not overlap. */
88
+ .gu-flow .react-flow__handle.gu-flow-custom-handle {
89
+ width: 32px; height: 32px; border: none; background: transparent; z-index: 1;
90
+ }
91
+ .gu-flow .gu-flow-custom-handle:focus-visible {
92
+ outline: 2px solid hsl(var(--focus-ring)); outline-offset: 2px;
93
+ }
94
+ .gu-flow .gu-flow-custom-handle::after {
95
+ content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
96
+ width: 9px; height: 9px; border-radius: 9999px; pointer-events: none;
97
+ background: var(--xy-handle-background-color); border: 1.5px solid var(--xy-handle-border-color);
98
+ transition: background-color 0.12s, border-color 0.12s;
99
+ }
100
+ .gu-flow .gu-flow-custom-handle:hover::after,
101
+ .gu-flow .gu-flow-custom-handle.connectingto::after,
102
+ .gu-flow .gu-flow-custom-handle.connectingfrom::after,
103
+ .gu-flow .gu-flow-custom-handle.valid::after {
104
+ background: var(--xy-connectionline-stroke); border-color: var(--xy-connectionline-stroke);
105
+ }
106
+
107
+ /* Nodes: the brand card draws its own border/ring; reset the built-in node boxes
108
+ (default/input/output/group) so a custom type named like one never gets the white box. */
109
+ .gu-flow .react-flow__node { font-family: var(--font-sans); }
110
+ .gu-flow .react-flow__node:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; border-radius: var(--radius-lg); }
111
+ .gu-flow .react-flow__node.selected { box-shadow: none; }
112
+ .gu-flow .react-flow__node-default,
113
+ .gu-flow .react-flow__node-input,
114
+ .gu-flow .react-flow__node-output,
115
+ .gu-flow .react-flow__node-group {
116
+ padding: 0; width: auto; background: transparent; border: none; border-radius: 0;
117
+ color: inherit; box-shadow: none; text-align: left;
118
+ }
119
+
120
+ /* Controls + minimap. */
121
+ .gu-flow .react-flow__controls { border: 1px solid hsl(var(--border)); border-radius: var(--radius-md); overflow: hidden; }
122
+ .gu-flow .react-flow__controls-button { width: 26px; height: 26px; }
123
+ .gu-flow .react-flow__controls-button:last-child { border-bottom: none; }
124
+ .gu-flow .react-flow__controls-button svg { fill: currentColor; max-width: 13px; max-height: 13px; }
125
+ .gu-flow .react-flow__minimap { border: 1px solid hsl(var(--border)); border-radius: var(--radius-md); overflow: hidden; }
126
+
127
+ /* Attribution: discreet. */
128
+ .gu-flow .react-flow__attribution { padding: 2px 5px; }
129
+ .gu-flow .react-flow__attribution a { color: hsl(var(--muted-foreground) / 0.55); font-size: 9px; }
130
+
131
+ /* "Running" node: a primary segment travels around the card border. */
132
+ .gu-flow-run-frame { overflow: visible; }
133
+ .gu-flow-run {
134
+ x: 1.25px; y: 1.25px; width: calc(100% - 2.5px); height: calc(100% - 2.5px); rx: 9px;
135
+ animation: gu-flow-run 1.8s linear infinite;
136
+ }
137
+ @keyframes gu-flow-run { to { stroke-dashoffset: -100; } }
138
+ @media (prefers-reduced-motion: reduce) { .gu-flow-run { animation: none; } }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.