@stephen-netu/design-system 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/package.json +130 -0
- package/src/b0-app-shell/clarification-overlay/ClarificationOverlay.svelte +221 -0
- package/src/b0-app-shell/clarification-overlay/index.ts +1 -0
- package/src/b0-app-shell/command-bar/CommandBar.svelte +565 -0
- package/src/b0-app-shell/index.ts +14 -0
- package/src/base.css +152 -0
- package/src/d0-data-viz/BarChart.svelte +236 -0
- package/src/d0-data-viz/CheckpointBar.svelte +574 -0
- package/src/d0-data-viz/CheckpointPill.svelte +297 -0
- package/src/d0-data-viz/MetricCard.svelte +159 -0
- package/src/d0-data-viz/SparkLine.svelte +66 -0
- package/src/d0-data-viz/StorageMap.svelte +182 -0
- package/src/d0-data-viz/Timeline.svelte +188 -0
- package/src/d0-data-viz/checkpoint-types.ts +162 -0
- package/src/d0-data-viz/d0-tokens.css +32 -0
- package/src/d0-data-viz/index.ts +58 -0
- package/src/d0-data-viz/storage-map-types.ts +142 -0
- package/src/d0-data-viz/theme-integration.ts +134 -0
- package/src/d0-data-viz/types.ts +53 -0
- package/src/demos/BspSizingInfographic.svelte +386 -0
- package/src/demos/BspZoneDemo.svelte +255 -0
- package/src/demos/ControlRoomDemo.svelte +570 -0
- package/src/demos/NodepadBlocksShowcase.svelte +189 -0
- package/src/demos/index.ts +7 -0
- package/src/editor/CodeEditor.svelte +188 -0
- package/src/editor/FindReplaceDialog.svelte +516 -0
- package/src/editor/MarkdownEditor.svelte +660 -0
- package/src/editor/index.ts +2 -0
- package/src/entity-graph-view/WorldView.svelte +171 -0
- package/src/entity-graph-view/index.ts +6 -0
- package/src/entity-graph-view/types.ts +364 -0
- package/src/epistemic/index.ts +2 -0
- package/src/epistemic/types.ts +134 -0
- package/src/epistemic/utils.ts +32 -0
- package/src/f0-forms/checkbox/Checkbox.svelte +158 -0
- package/src/f0-forms/field/FormField.svelte +91 -0
- package/src/f0-forms/index.ts +11 -0
- package/src/f0-forms/radio/RadioGroup.svelte +192 -0
- package/src/f0-forms/search/SearchBar.svelte +99 -0
- package/src/f0-forms/select/Select.svelte +198 -0
- package/src/f0-forms/textarea/TextArea.svelte +170 -0
- package/src/f0-forms/toggle/FormToggle.svelte +147 -0
- package/src/f0-forms/validation/validate.ts +47 -0
- package/src/fabric/ai/ChatPanel.svelte +648 -0
- package/src/fabric/ai/index.ts +11 -0
- package/src/fabric/data/index.ts +21 -0
- package/src/fabric/feedback/StatusBar.svelte +274 -0
- package/src/fabric/feedback/ToastContainer.svelte +122 -0
- package/src/fabric/feedback/index.ts +14 -0
- package/src/fabric/index.ts +37 -0
- package/src/fabric/input/CommandBar.svelte +79 -0
- package/src/fabric/input/KeyboardShortcuts.svelte +388 -0
- package/src/fabric/input/index.ts +12 -0
- package/src/fabric/layout/GlassBox.svelte +193 -0
- package/src/fabric/layout/KanbanBoard.svelte +158 -0
- package/src/fabric/layout/KanbanCard.svelte +226 -0
- package/src/fabric/layout/KanbanColumn.svelte +278 -0
- package/src/fabric/layout/index.ts +17 -0
- package/src/fabric/navigation/ActivityRail.svelte +271 -0
- package/src/fabric/navigation/index.ts +11 -0
- package/src/g0-geo/SovereignMap.svelte +149 -0
- package/src/g0-geo/index.ts +2 -0
- package/src/index.ts +68 -0
- package/src/l0-layout/block-writer/BlockConnectionLines.svelte +98 -0
- package/src/l0-layout/block-writer/BlockContent.svelte +213 -0
- package/src/l0-layout/block-writer/BlockHeader.svelte +230 -0
- package/src/l0-layout/block-writer/BlockItem.svelte +177 -0
- package/src/l0-layout/block-writer/BlockWriter.svelte +219 -0
- package/src/l0-layout/block-writer/block-writer-types.ts +63 -0
- package/src/l0-layout/block-writer/index.ts +24 -0
- package/src/l0-layout/block-writer/markdown.ts +110 -0
- package/src/l0-layout/chat-view/ChatViewLayout.svelte +61 -0
- package/src/l0-layout/epistemic/AllayDetailPanel.svelte +113 -0
- package/src/l0-layout/epistemic/AssayDetailPanel.svelte +164 -0
- package/src/l0-layout/epistemic/AvenueTrace.svelte +253 -0
- package/src/l0-layout/epistemic/KnowledgeDetailPanel.svelte +204 -0
- package/src/l0-layout/epistemic/ParadigmLens.svelte +170 -0
- package/src/l0-layout/epistemic/ProvenanceChain.svelte +164 -0
- package/src/l0-layout/epistemic/TrustView.svelte +195 -0
- package/src/l0-layout/epistemic/index.ts +7 -0
- package/src/l0-layout/grove/AccordCard.svelte +156 -0
- package/src/l0-layout/grove/GroveCard.svelte +197 -0
- package/src/l0-layout/grove/MemberBadge.svelte +96 -0
- package/src/l0-layout/grove/MemberList.svelte +59 -0
- package/src/l0-layout/grove/grove-types.ts +29 -0
- package/src/l0-layout/grove/index.ts +14 -0
- package/src/l0-layout/index.ts +45 -0
- package/src/l0-layout/kanban/KanbanBoard.svelte +71 -0
- package/src/l0-layout/kanban/KanbanCard.svelte +276 -0
- package/src/l0-layout/kanban/KanbanColumn.svelte +198 -0
- package/src/l0-layout/kanban/KanbanViewLayout.svelte +61 -0
- package/src/l0-layout/workspace/WorkspaceLayout.svelte +93 -0
- package/src/n0-node-graph/GraphEditor.svelte +342 -0
- package/src/n0-node-graph/GraphHistory.svelte +122 -0
- package/src/n0-node-graph/Node.svelte +269 -0
- package/src/n0-node-graph/NodeCanvas.svelte +187 -0
- package/src/n0-node-graph/NodeEdge.svelte +152 -0
- package/src/n0-node-graph/NodePalette.svelte +318 -0
- package/src/n0-node-graph/NodePort.svelte +128 -0
- package/src/n0-node-graph/index.ts +33 -0
- package/src/n0-node-graph/n0-tokens.css +29 -0
- package/src/n0-node-graph/node-port-types.ts +19 -0
- package/src/n0-node-graph/types.ts +96 -0
- package/src/n0-node-graph/validate.test.ts +722 -0
- package/src/n0-node-graph/validate.ts +371 -0
- package/src/n1-flow-canvas/FlowCanvas.svelte +202 -0
- package/src/n1-flow-canvas/graph-state.svelte.ts +95 -0
- package/src/n1-flow-canvas/index.ts +21 -0
- package/src/n1-flow-canvas/layout.ts +118 -0
- package/src/n1-flow-canvas/layout.worker.ts +128 -0
- package/src/n1-flow-canvas/types.ts +102 -0
- package/src/n1-flow-canvas/utils.ts +146 -0
- package/src/n2-manifold-disk/ManifoldDisk.svelte +314 -0
- package/src/n2-manifold-disk/index.ts +6 -0
- package/src/n2-manifold-disk/poincare.ts +184 -0
- package/src/n3-force-graph/ForceGraph.svelte +180 -0
- package/src/n3-force-graph/GraphTooltip.svelte +99 -0
- package/src/n3-force-graph/animated-edge.ts +107 -0
- package/src/n3-force-graph/health-node.ts +128 -0
- package/src/n3-force-graph/index.ts +31 -0
- package/src/n3-force-graph/types.ts +94 -0
- package/src/n4-adaptive-manifold/AdaptiveManifold.svelte +1165 -0
- package/src/n4-adaptive-manifold/DetailCard.svelte +212 -0
- package/src/n4-adaptive-manifold/SuggestionIndicator.svelte +91 -0
- package/src/n4-adaptive-manifold/classifier.ts +139 -0
- package/src/n4-adaptive-manifold/index.ts +45 -0
- package/src/n4-adaptive-manifold/n4-tokens.css +155 -0
- package/src/n4-adaptive-manifold/orchestrator.ts +301 -0
- package/src/n4-adaptive-manifold/phase-theme.ts +92 -0
- package/src/n4-adaptive-manifold/pinnacle-events.ts +135 -0
- package/src/n4-adaptive-manifold/providers/gravitational-provider.ts +191 -0
- package/src/n4-adaptive-manifold/providers/manifold-provider.ts +276 -0
- package/src/n4-adaptive-manifold/transition.ts +90 -0
- package/src/n4-adaptive-manifold/types.ts +313 -0
- package/src/p0-primitives/index.ts +6 -0
- package/src/p0-primitives/tokens/control-room.css +309 -0
- package/src/p0-primitives/tokens/index.ts +6 -0
- package/src/p0-primitives/tokens/tokens.css +140 -0
- package/src/p0-primitives/tokens/tokens.json +132 -0
- package/src/p0-primitives/types/index.ts +123 -0
- package/src/p0-primitives/types/plugin-types.ts +74 -0
- package/src/p0-primitives/utils/index.ts +39 -0
- package/src/platform/hud-protocol.ts +20 -0
- package/src/platform/index.ts +3 -0
- package/src/s0-state/create-store.ts +84 -0
- package/src/s0-state/derived.ts +41 -0
- package/src/s0-state/index.ts +6 -0
- package/src/showcase/Showcase.svelte +401 -0
- package/src/spatial/BspTilingCanvas.svelte +232 -0
- package/src/spatial/ForceCanvas.svelte +289 -0
- package/src/spatial/LodRenderer.svelte +334 -0
- package/src/spatial/SnapZoomCamera.svelte +185 -0
- package/src/spatial/ZoneTiler.svelte +311 -0
- package/src/spatial/camera-types.ts +45 -0
- package/src/spatial/content-proportional-bsp.ts +253 -0
- package/src/spatial/force-canvas-types.ts +102 -0
- package/src/spatial/index.ts +15 -0
- package/src/spatial/lod-types.ts +70 -0
- package/src/spatial/zone-tiler-types.ts +177 -0
- package/src/stores/vaultStore.svelte +14 -0
- package/src/t0-transport/connection.ts +34 -0
- package/src/t0-transport/error.ts +37 -0
- package/src/t0-transport/events.ts +50 -0
- package/src/t0-transport/index.ts +6 -0
- package/src/t0-transport/invoke.ts +30 -0
- package/src/tauri/index.ts +13 -0
- package/src/u0-primitives/allay-gauge/AllayGauge.svelte +90 -0
- package/src/u0-primitives/allay-gauge/index.ts +1 -0
- package/src/u0-primitives/animated-icon/AnimatedIcon.svelte +273 -0
- package/src/u0-primitives/animated-icon/README.md +53 -0
- package/src/u0-primitives/animated-icon/index.ts +31 -0
- package/src/u0-primitives/assay-badge/AssayBadge.svelte +121 -0
- package/src/u0-primitives/assay-badge/AssaySignalBar.svelte +83 -0
- package/src/u0-primitives/assay-badge/index.ts +2 -0
- package/src/u0-primitives/avatar/Avatar.svelte +177 -0
- package/src/u0-primitives/badge/Badge.svelte +149 -0
- package/src/u0-primitives/button/Button.svelte +304 -0
- package/src/u0-primitives/card/Card.svelte +182 -0
- package/src/u0-primitives/dropdown/Dropdown.svelte +305 -0
- package/src/u0-primitives/expert-badge/ExpertBadge.svelte +89 -0
- package/src/u0-primitives/expert-badge/index.ts +1 -0
- package/src/u0-primitives/index.ts +35 -0
- package/src/u0-primitives/input/Input.svelte +237 -0
- package/src/u0-primitives/modal/Modal.svelte +331 -0
- package/src/u0-primitives/modal/index.ts +1 -0
- package/src/u0-primitives/spinner/Spinner.svelte +129 -0
- package/src/u0-primitives/tabs/Tabs.svelte +225 -0
- package/src/u0-primitives/tabs/tabs-types.ts +11 -0
- package/src/u0-primitives/toggle/Toggle.svelte +161 -0
- package/src/u0-primitives/tooltip/Tooltip.svelte +234 -0
- package/src/v0-diagrams/DiagramPanel.svelte +68 -0
- package/src/v0-diagrams/MarkmapDiagram.svelte +56 -0
- package/src/v0-diagrams/MermaidDiagram.svelte +53 -0
- package/src/v0-diagrams/index.ts +4 -0
- package/src/v0-diagrams/types.ts +28 -0
- package/src/v0-diagrams/v0-tokens.css +6 -0
- package/src/vite-env.d.ts +7 -0
- package/src/x0-enchanted-blocks/ENCHANTED_BLOCKS_SPEC.md +457 -0
- package/src/x0-enchanted-blocks/components/EnchantedBlock.svelte +485 -0
- package/src/x0-enchanted-blocks/components/NodepadBlock.svelte +262 -0
- package/src/x0-enchanted-blocks/core/block-layout.ts +211 -0
- package/src/x0-enchanted-blocks/core/block-types.ts +227 -0
- package/src/x0-enchanted-blocks/index.ts +46 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type {
|
|
4
|
+
GraphSchema,
|
|
5
|
+
NodeInstance,
|
|
6
|
+
NodeDefinition,
|
|
7
|
+
EdgeDefinition,
|
|
8
|
+
ValidationResult,
|
|
9
|
+
Transform,
|
|
10
|
+
} from './types';
|
|
11
|
+
import type { PortDragDetail, PortDropDetail } from './node-port-types';
|
|
12
|
+
import { validateGraph } from './validate';
|
|
13
|
+
import NodeCanvas from './NodeCanvas.svelte';
|
|
14
|
+
import Node from './Node.svelte';
|
|
15
|
+
import NodeEdge from './NodeEdge.svelte';
|
|
16
|
+
|
|
17
|
+
interface GraphEditorProps {
|
|
18
|
+
schema: GraphSchema;
|
|
19
|
+
readonly?: boolean;
|
|
20
|
+
snapToGrid?: boolean;
|
|
21
|
+
gridSize?: number;
|
|
22
|
+
nodeContent?: Snippet<[NodeInstance, NodeDefinition]>;
|
|
23
|
+
onchange?: (schema: GraphSchema) => void;
|
|
24
|
+
onselect?: (nodeIds: string[]) => void;
|
|
25
|
+
onvalidate?: (result: ValidationResult) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
schema,
|
|
30
|
+
readonly = false,
|
|
31
|
+
snapToGrid = false,
|
|
32
|
+
gridSize = 20,
|
|
33
|
+
nodeContent,
|
|
34
|
+
onchange,
|
|
35
|
+
onselect,
|
|
36
|
+
onvalidate,
|
|
37
|
+
}: GraphEditorProps = $props();
|
|
38
|
+
|
|
39
|
+
// --- Interaction state ---
|
|
40
|
+
let selectedNodeIds = $state<Set<string>>(new Set());
|
|
41
|
+
let selectedEdgeIds = $state<Set<string>>(new Set());
|
|
42
|
+
let transform = $state<Transform>({ x: 0, y: 0, scale: 1 });
|
|
43
|
+
|
|
44
|
+
// Edge creation state
|
|
45
|
+
let creatingEdge = $state<{
|
|
46
|
+
sourceNodeId: string;
|
|
47
|
+
sourcePortId: string;
|
|
48
|
+
sourceType: string;
|
|
49
|
+
sourcePosition: { x: number; y: number };
|
|
50
|
+
cursorPosition: { x: number; y: number };
|
|
51
|
+
} | null>(null);
|
|
52
|
+
|
|
53
|
+
// --- Derived: port positions for edge rendering ---
|
|
54
|
+
const HEADER_HEIGHT = 32;
|
|
55
|
+
const PORT_SPACING = 24;
|
|
56
|
+
const PORT_PADDING = 12;
|
|
57
|
+
|
|
58
|
+
function getPortPosition(
|
|
59
|
+
node: NodeInstance,
|
|
60
|
+
def: NodeDefinition,
|
|
61
|
+
portId: string
|
|
62
|
+
): { x: number; y: number } | undefined {
|
|
63
|
+
const port = def.ports.find(p => p.id === portId);
|
|
64
|
+
if (!port) return undefined;
|
|
65
|
+
|
|
66
|
+
const sameDirPorts = def.ports.filter(p => p.direction === port.direction);
|
|
67
|
+
const idx = sameDirPorts.findIndex(p => p.id === portId);
|
|
68
|
+
|
|
69
|
+
const x = port.direction === 'input' ? node.position.x : node.position.x + node.size.width;
|
|
70
|
+
let y: number;
|
|
71
|
+
if (sameDirPorts.length === 1) {
|
|
72
|
+
y = node.position.y + HEADER_HEIGHT + PORT_PADDING + PORT_SPACING / 2;
|
|
73
|
+
} else {
|
|
74
|
+
y = node.position.y + HEADER_HEIGHT + PORT_PADDING + idx * PORT_SPACING;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { x, y };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Build a lookup: nodeId -> NodeDefinition
|
|
81
|
+
let definitionMap = $derived(
|
|
82
|
+
new Map(schema.nodeDefinitions.map(d => [d.kind, d]))
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
function getDefinition(kind: string): NodeDefinition | undefined {
|
|
86
|
+
return definitionMap.get(kind);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Edge position pairs ---
|
|
90
|
+
interface EdgeWithPositions {
|
|
91
|
+
edge: EdgeDefinition;
|
|
92
|
+
source: { x: number; y: number };
|
|
93
|
+
target: { x: number; y: number };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let edgesWithPositions = $derived.by((): EdgeWithPositions[] => {
|
|
97
|
+
const result: EdgeWithPositions[] = [];
|
|
98
|
+
for (const edge of schema.edges) {
|
|
99
|
+
const sourceNode = schema.nodes.find(n => n.id === edge.sourceNodeId);
|
|
100
|
+
const targetNode = schema.nodes.find(n => n.id === edge.targetNodeId);
|
|
101
|
+
if (!sourceNode || !targetNode) continue;
|
|
102
|
+
|
|
103
|
+
const sourceDef = getDefinition(sourceNode.kind);
|
|
104
|
+
const targetDef = getDefinition(targetNode.kind);
|
|
105
|
+
if (!sourceDef || !targetDef) continue;
|
|
106
|
+
|
|
107
|
+
const source = getPortPosition(sourceNode, sourceDef, edge.sourcePortId);
|
|
108
|
+
const target = getPortPosition(targetNode, targetDef, edge.targetPortId);
|
|
109
|
+
if (!source || !target) continue;
|
|
110
|
+
|
|
111
|
+
result.push({ edge, source, target });
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// --- Emit helpers ---
|
|
117
|
+
function emitChange(newSchema: GraphSchema) {
|
|
118
|
+
const result = validateGraph(newSchema);
|
|
119
|
+
onvalidate?.(result);
|
|
120
|
+
onchange?.(newSchema);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function emitSelection() {
|
|
124
|
+
onselect?.([...selectedNodeIds]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- Snap helper ---
|
|
128
|
+
function snap(val: number): number {
|
|
129
|
+
if (!snapToGrid) return val;
|
|
130
|
+
return Math.round(val / gridSize) * gridSize;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --- Node interaction handlers ---
|
|
134
|
+
function handleNodeMove(id: string, position: { x: number; y: number }) {
|
|
135
|
+
if (readonly) return;
|
|
136
|
+
const snapped = { x: snap(position.x), y: snap(position.y) };
|
|
137
|
+
const newNodes = schema.nodes.map(n =>
|
|
138
|
+
n.id === id ? { ...n, position: snapped } : n
|
|
139
|
+
);
|
|
140
|
+
emitChange({ ...schema, nodes: newNodes });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function handleNodeSelect(id: string) {
|
|
144
|
+
selectedEdgeIds = new Set();
|
|
145
|
+
selectedNodeIds = new Set([id]);
|
|
146
|
+
emitSelection();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// --- Edge interaction handlers ---
|
|
150
|
+
function handleEdgeSelect(edgeId: string) {
|
|
151
|
+
selectedNodeIds = new Set();
|
|
152
|
+
selectedEdgeIds = new Set([edgeId]);
|
|
153
|
+
emitSelection();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function handleEdgeDelete(edgeId: string) {
|
|
157
|
+
if (readonly) return;
|
|
158
|
+
const newEdges = schema.edges.filter(e => e.id !== edgeId);
|
|
159
|
+
emitChange({ ...schema, edges: newEdges });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- Port drag (edge creation) ---
|
|
163
|
+
function handlePortDragStart(detail: PortDragDetail) {
|
|
164
|
+
if (readonly) return;
|
|
165
|
+
if (detail.direction !== 'output') return;
|
|
166
|
+
creatingEdge = {
|
|
167
|
+
sourceNodeId: detail.nodeId,
|
|
168
|
+
sourcePortId: detail.portId,
|
|
169
|
+
sourceType: detail.type,
|
|
170
|
+
sourcePosition: detail.position,
|
|
171
|
+
cursorPosition: detail.position,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function handlePortDrop(detail: PortDropDetail) {
|
|
176
|
+
if (!creatingEdge || readonly) return;
|
|
177
|
+
if (detail.direction !== 'input') {
|
|
178
|
+
creatingEdge = null;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (detail.type !== creatingEdge.sourceType) {
|
|
182
|
+
creatingEdge = null;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (detail.nodeId === creatingEdge.sourceNodeId) {
|
|
186
|
+
creatingEdge = null;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const newEdge: EdgeDefinition = {
|
|
191
|
+
id: `edge-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
192
|
+
sourceNodeId: creatingEdge.sourceNodeId,
|
|
193
|
+
sourcePortId: creatingEdge.sourcePortId,
|
|
194
|
+
targetNodeId: detail.nodeId,
|
|
195
|
+
targetPortId: detail.portId,
|
|
196
|
+
metadata: {},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
creatingEdge = null;
|
|
200
|
+
emitChange({ ...schema, edges: [...schema.edges, newEdge] });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// --- Canvas pointer tracking for rubber-band edge ---
|
|
204
|
+
function handleCanvasPointerMove(e: PointerEvent) {
|
|
205
|
+
if (!creatingEdge) return;
|
|
206
|
+
const svg = (e.currentTarget as Element)?.closest('svg');
|
|
207
|
+
if (!svg) return;
|
|
208
|
+
const rect = svg.getBoundingClientRect();
|
|
209
|
+
creatingEdge = {
|
|
210
|
+
...creatingEdge,
|
|
211
|
+
cursorPosition: {
|
|
212
|
+
x: (e.clientX - rect.left - transform.x) / transform.scale,
|
|
213
|
+
y: (e.clientY - rect.top - transform.y) / transform.scale,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function handleCanvasPointerUp() {
|
|
219
|
+
creatingEdge = null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function handleCanvasClick() {
|
|
223
|
+
selectedNodeIds = new Set();
|
|
224
|
+
selectedEdgeIds = new Set();
|
|
225
|
+
emitSelection();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// --- Keyboard shortcuts ---
|
|
229
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
230
|
+
if (readonly) return;
|
|
231
|
+
|
|
232
|
+
if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
233
|
+
if (selectedNodeIds.size > 0) {
|
|
234
|
+
const newNodes = schema.nodes.filter(n => !selectedNodeIds.has(n.id));
|
|
235
|
+
const newEdges = schema.edges.filter(
|
|
236
|
+
e => !selectedNodeIds.has(e.sourceNodeId) && !selectedNodeIds.has(e.targetNodeId)
|
|
237
|
+
);
|
|
238
|
+
selectedNodeIds = new Set();
|
|
239
|
+
emitChange({ ...schema, nodes: newNodes, edges: newEdges });
|
|
240
|
+
} else if (selectedEdgeIds.size > 0) {
|
|
241
|
+
const newEdges = schema.edges.filter(e => !selectedEdgeIds.has(e.id));
|
|
242
|
+
selectedEdgeIds = new Set();
|
|
243
|
+
emitChange({ ...schema, edges: newEdges });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (e.key === 'a' && (e.metaKey || e.ctrlKey)) {
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
selectedNodeIds = new Set(schema.nodes.map(n => n.id));
|
|
250
|
+
emitSelection();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (e.key === 'Escape') {
|
|
254
|
+
selectedNodeIds = new Set();
|
|
255
|
+
selectedEdgeIds = new Set();
|
|
256
|
+
creatingEdge = null;
|
|
257
|
+
emitSelection();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function handleTransform(t: Transform) {
|
|
262
|
+
transform = t;
|
|
263
|
+
}
|
|
264
|
+
</script>
|
|
265
|
+
|
|
266
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
267
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
268
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
269
|
+
<div
|
|
270
|
+
class="n0-graph-editor"
|
|
271
|
+
onkeydown={handleKeydown}
|
|
272
|
+
onpointermove={handleCanvasPointerMove}
|
|
273
|
+
onpointerup={handleCanvasPointerUp}
|
|
274
|
+
tabindex="0"
|
|
275
|
+
role="application"
|
|
276
|
+
aria-label="Graph editor"
|
|
277
|
+
>
|
|
278
|
+
<NodeCanvas
|
|
279
|
+
{transform}
|
|
280
|
+
ontransform={handleTransform}
|
|
281
|
+
gridVisible={true}
|
|
282
|
+
>
|
|
283
|
+
{#snippet children()}
|
|
284
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
285
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
286
|
+
<g onclick={handleCanvasClick}>
|
|
287
|
+
<!-- Edges layer (behind nodes) -->
|
|
288
|
+
{#each edgesWithPositions as { edge, source, target } (edge.id)}
|
|
289
|
+
<NodeEdge
|
|
290
|
+
{edge}
|
|
291
|
+
sourcePosition={source}
|
|
292
|
+
targetPosition={target}
|
|
293
|
+
selected={selectedEdgeIds.has(edge.id)}
|
|
294
|
+
onselect={handleEdgeSelect}
|
|
295
|
+
ondelete={handleEdgeDelete}
|
|
296
|
+
/>
|
|
297
|
+
{/each}
|
|
298
|
+
|
|
299
|
+
<!-- Rubber-band edge during creation -->
|
|
300
|
+
{#if creatingEdge}
|
|
301
|
+
<NodeEdge
|
|
302
|
+
sourcePosition={creatingEdge.sourcePosition}
|
|
303
|
+
targetPosition={creatingEdge.cursorPosition}
|
|
304
|
+
creating={true}
|
|
305
|
+
/>
|
|
306
|
+
{/if}
|
|
307
|
+
|
|
308
|
+
<!-- Nodes layer -->
|
|
309
|
+
{#each schema.nodes as node (node.id)}
|
|
310
|
+
{@const def = getDefinition(node.kind)}
|
|
311
|
+
{#if def}
|
|
312
|
+
<Node
|
|
313
|
+
{node}
|
|
314
|
+
definition={def}
|
|
315
|
+
selected={selectedNodeIds.has(node.id)}
|
|
316
|
+
onmove={handleNodeMove}
|
|
317
|
+
onselect={handleNodeSelect}
|
|
318
|
+
onportdragstart={handlePortDragStart}
|
|
319
|
+
onportdrop={handlePortDrop}
|
|
320
|
+
>
|
|
321
|
+
{#snippet children()}
|
|
322
|
+
{#if nodeContent}
|
|
323
|
+
{@render nodeContent(node, def)}
|
|
324
|
+
{/if}
|
|
325
|
+
{/snippet}
|
|
326
|
+
</Node>
|
|
327
|
+
{/if}
|
|
328
|
+
{/each}
|
|
329
|
+
</g>
|
|
330
|
+
{/snippet}
|
|
331
|
+
</NodeCanvas>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<style>
|
|
335
|
+
.n0-graph-editor {
|
|
336
|
+
width: 100%;
|
|
337
|
+
height: 100%;
|
|
338
|
+
position: relative;
|
|
339
|
+
outline: none;
|
|
340
|
+
overflow: hidden;
|
|
341
|
+
}
|
|
342
|
+
</style>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type {
|
|
4
|
+
GraphSchema,
|
|
5
|
+
NodeInstance,
|
|
6
|
+
NodeDefinition,
|
|
7
|
+
ValidationResult,
|
|
8
|
+
} from './types';
|
|
9
|
+
import GraphEditor from './GraphEditor.svelte';
|
|
10
|
+
|
|
11
|
+
interface GraphHistoryProps {
|
|
12
|
+
schema: GraphSchema;
|
|
13
|
+
maxHistory?: number;
|
|
14
|
+
readonly?: boolean;
|
|
15
|
+
snapToGrid?: boolean;
|
|
16
|
+
gridSize?: number;
|
|
17
|
+
nodeContent?: Snippet<[NodeInstance, NodeDefinition]>;
|
|
18
|
+
onchange?: (schema: GraphSchema) => void;
|
|
19
|
+
onselect?: (nodeIds: string[]) => void;
|
|
20
|
+
onvalidate?: (result: ValidationResult) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
schema,
|
|
25
|
+
maxHistory = 50,
|
|
26
|
+
readonly = false,
|
|
27
|
+
snapToGrid = false,
|
|
28
|
+
gridSize = 20,
|
|
29
|
+
nodeContent,
|
|
30
|
+
onchange,
|
|
31
|
+
onselect,
|
|
32
|
+
onvalidate,
|
|
33
|
+
}: GraphHistoryProps = $props();
|
|
34
|
+
|
|
35
|
+
// --- History state ---
|
|
36
|
+
// svelte-ignore state_referenced_locally -- intentional: history starts with initial schema snapshot
|
|
37
|
+
let history = $state<GraphSchema[]>([schema]);
|
|
38
|
+
let historyIndex = $state<number>(0);
|
|
39
|
+
|
|
40
|
+
let canUndo = $derived(historyIndex > 0);
|
|
41
|
+
let canRedo = $derived(historyIndex < history.length - 1);
|
|
42
|
+
let currentSchema = $derived(history[historyIndex]);
|
|
43
|
+
|
|
44
|
+
// --- History operations ---
|
|
45
|
+
function pushSchema(newSchema: GraphSchema) {
|
|
46
|
+
// Discard any forward history beyond current position
|
|
47
|
+
history = [...history.slice(0, historyIndex + 1), newSchema];
|
|
48
|
+
|
|
49
|
+
// Trim to max depth (drop oldest entries)
|
|
50
|
+
if (history.length > maxHistory) {
|
|
51
|
+
const overflow = history.length - maxHistory;
|
|
52
|
+
history = history.slice(overflow);
|
|
53
|
+
historyIndex = history.length - 1;
|
|
54
|
+
} else {
|
|
55
|
+
historyIndex = history.length - 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onchange?.(newSchema);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function undo() {
|
|
62
|
+
if (!canUndo) return;
|
|
63
|
+
historyIndex--;
|
|
64
|
+
onchange?.(history[historyIndex]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function redo() {
|
|
68
|
+
if (!canRedo) return;
|
|
69
|
+
historyIndex++;
|
|
70
|
+
onchange?.(history[historyIndex]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// --- Editor change handler ---
|
|
74
|
+
function handleEditorChange(newSchema: GraphSchema) {
|
|
75
|
+
pushSchema(newSchema);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- Keyboard shortcuts (captured before GraphEditor) ---
|
|
79
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
80
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
81
|
+
if (!mod) return;
|
|
82
|
+
|
|
83
|
+
if (e.key === 'z' && !e.shiftKey) {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
undo();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if ((e.key === 'z' && e.shiftKey) || e.key === 'y') {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
redo();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Expose reactive state and methods for parent binding
|
|
99
|
+
export { canUndo, canRedo, undo, redo };
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
103
|
+
<div class="n0-graph-history" onkeydown={handleKeydown}>
|
|
104
|
+
<GraphEditor
|
|
105
|
+
schema={currentSchema}
|
|
106
|
+
{readonly}
|
|
107
|
+
{snapToGrid}
|
|
108
|
+
{gridSize}
|
|
109
|
+
{nodeContent}
|
|
110
|
+
onchange={handleEditorChange}
|
|
111
|
+
{onselect}
|
|
112
|
+
{onvalidate}
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<style>
|
|
117
|
+
.n0-graph-history {
|
|
118
|
+
width: 100%;
|
|
119
|
+
height: 100%;
|
|
120
|
+
position: relative;
|
|
121
|
+
}
|
|
122
|
+
</style>
|