symbiote-ui 0.3.0-alpha.4
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/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/canvas/AutoLayout.js +731 -0
- package/canvas/Breadcrumb/Breadcrumb.css.js +75 -0
- package/canvas/Breadcrumb/Breadcrumb.js +96 -0
- package/canvas/Breadcrumb/Breadcrumb.tpl.js +7 -0
- package/canvas/CanvasConnectionRenderer.js +971 -0
- package/canvas/CanvasGraph/CanvasGraph.css.js +29 -0
- package/canvas/CanvasGraph/CanvasGraph.js +1697 -0
- package/canvas/CanvasGraph/CanvasGraphDrawState.js +280 -0
- package/canvas/CanvasGraph/CanvasGraphGeometry.js +194 -0
- package/canvas/CanvasViewport.js +550 -0
- package/canvas/ConnectionRenderer.js +1283 -0
- package/canvas/FlowSimulator.js +326 -0
- package/canvas/ForceLayout.js +226 -0
- package/canvas/ForceWorker.js +1303 -0
- package/canvas/FrameManager.js +223 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.css.js +136 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.js +129 -0
- package/canvas/GraphExplorerShell/GraphExplorerShell.tpl.js +12 -0
- package/canvas/GraphTabs/GraphTabs.css.js +101 -0
- package/canvas/GraphTabs/GraphTabs.js +189 -0
- package/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
- package/canvas/LODManager.js +88 -0
- package/canvas/Minimap/Minimap.css.js +73 -0
- package/canvas/Minimap/Minimap.js +210 -0
- package/canvas/Minimap/Minimap.tpl.js +7 -0
- package/canvas/NodeCanvas/NodeCanvas.css.js +398 -0
- package/canvas/NodeCanvas/NodeCanvas.js +1499 -0
- package/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
- package/canvas/NodeSearch/NodeSearch.css.js +97 -0
- package/canvas/NodeSearch/NodeSearch.js +140 -0
- package/canvas/NodeSearch/NodeSearch.tpl.js +25 -0
- package/canvas/NodeViewManager.js +748 -0
- package/canvas/PcbRouteDiagnostics.js +463 -0
- package/canvas/PcbRouter.js +1127 -0
- package/canvas/PinExpansion.js +134 -0
- package/canvas/PseudoConnection.js +84 -0
- package/canvas/SelectionSync.js +163 -0
- package/canvas/SubgraphManager.js +203 -0
- package/canvas/SubgraphRouter.js +452 -0
- package/canvas/ViewportActions.js +473 -0
- package/canvas/graph-explorer.js +339 -0
- package/canvas/graph-layout.js +148 -0
- package/canvas/graph-model.js +68 -0
- package/canvas/html-in-canvas.js +202 -0
- package/canvas/project-graph-builder.js +440 -0
- package/canvas/project-graph-model.js +183 -0
- package/chat/ChatComposer/ChatComposer.css.js +652 -0
- package/chat/ChatComposer/ChatComposer.js +304 -0
- package/chat/ChatList/ChatList.css.js +102 -0
- package/chat/ChatList/ChatList.js +99 -0
- package/chat/ChatList/ChatList.tpl.js +20 -0
- package/chat/ChatListItem/ChatListItem.css.js +117 -0
- package/chat/ChatListItem/ChatListItem.js +32 -0
- package/chat/ChatListItem/ChatListItem.tpl.js +17 -0
- package/chat/ChatMessageItem/ChatMessageItem.css.js +628 -0
- package/chat/ChatMessageItem/ChatMessageItem.js +156 -0
- package/chat/ChatSidebar/ChatSidebar.css.js +150 -0
- package/chat/ChatSidebar/ChatSidebar.js +230 -0
- package/chat/ChatSidebar/ChatSidebar.tpl.js +18 -0
- package/chat/ChatSidebar/constants.js +11 -0
- package/chat/ChatSidebarItem/ChatSidebarItem.css.js +445 -0
- package/chat/ChatSidebarItem/ChatSidebarItem.js +304 -0
- package/chat/ChatTranscript/ChatTranscript.css.js +90 -0
- package/chat/ChatTranscript/ChatTranscript.js +244 -0
- package/chat/chat-context.js +123 -0
- package/chat/message-model.js +156 -0
- package/cli.js +20 -0
- package/control/Button/Button.css.js +93 -0
- package/control/Button/Button.js +78 -0
- package/control/Button/Button.tpl.js +3 -0
- package/control/Field/Field.css.js +91 -0
- package/control/Field/Field.js +17 -0
- package/control/Field/Field.tpl.js +3 -0
- package/core/Connection.js +47 -0
- package/core/Editor.js +449 -0
- package/core/Frame.js +33 -0
- package/core/GraphMermaid.js +348 -0
- package/core/GraphText.js +228 -0
- package/core/Node.js +145 -0
- package/core/Portal.js +106 -0
- package/core/Socket.js +187 -0
- package/core/SubgraphNode.js +121 -0
- package/core/base-path.js +55 -0
- package/core/dom-utils.js +14 -0
- package/core/index.js +18 -0
- package/core/local-cache.js +26 -0
- package/core/state-sync.js +227 -0
- package/custom-elements.json +6380 -0
- package/discover.js +240 -0
- package/display/Badge/Badge.css.js +44 -0
- package/display/Badge/Badge.js +17 -0
- package/display/Badge/Badge.tpl.js +3 -0
- package/display/Banner/Banner.css.js +61 -0
- package/display/Banner/Banner.js +17 -0
- package/display/Banner/Banner.tpl.js +3 -0
- package/display/CodeBlock/CodeBlock.css.js +194 -0
- package/display/CodeBlock/CodeBlock.js +220 -0
- package/display/CodeBlock/CodeBlock.tpl.js +11 -0
- package/display/DataTable/DataTable.css.js +101 -0
- package/display/DataTable/DataTable.js +136 -0
- package/display/DataTable/DataTable.tpl.js +13 -0
- package/display/EmptyState/EmptyState.css.js +33 -0
- package/display/EmptyState/EmptyState.js +17 -0
- package/display/EmptyState/EmptyState.tpl.js +3 -0
- package/display/EventFeed/EventFeed.css.js +145 -0
- package/display/EventFeed/EventFeed.js +64 -0
- package/display/EventFeed/EventFeed.tpl.js +14 -0
- package/display/EventFeed/EventFeedItem.js +116 -0
- package/display/EventFeed/EventFeedItem.tpl.js +22 -0
- package/display/LoadingOverlay/LoadingOverlay.css.js +91 -0
- package/display/LoadingOverlay/LoadingOverlay.js +48 -0
- package/display/LoadingOverlay/LoadingOverlay.tpl.js +12 -0
- package/display/Metric/Metric.css.js +60 -0
- package/display/Metric/Metric.js +17 -0
- package/display/Metric/Metric.tpl.js +6 -0
- package/display/OutputGraphPreview/OutputGraphPreview.css.js +122 -0
- package/display/OutputGraphPreview/OutputGraphPreview.js +89 -0
- package/display/OutputGraphPreview/OutputGraphPreview.tpl.js +13 -0
- package/display/OutputListPreview/OutputListPreview.css.js +109 -0
- package/display/OutputListPreview/OutputListPreview.js +77 -0
- package/display/OutputListPreview/OutputListPreview.tpl.js +13 -0
- package/display/SourceEditor/SourceEditor.css.js +39 -0
- package/display/SourceEditor/SourceEditor.js +129 -0
- package/display/SourceEditor/SourceEditor.tpl.js +10 -0
- package/display/SourceViewer/SourceViewer.css.js +80 -0
- package/display/SourceViewer/SourceViewer.js +418 -0
- package/display/SourceViewer/SourceViewer.tpl.js +17 -0
- package/display/StatusRibbon/StatusRibbon.css.js +73 -0
- package/display/StatusRibbon/StatusRibbon.js +87 -0
- package/display/StatusRibbon/StatusRibbon.tpl.js +7 -0
- package/display/event-feed-adapter.js +72 -0
- package/display/format-utils.js +29 -0
- package/display/highlight.js +659 -0
- package/display/icons.js +37 -0
- package/display/markdown-formatter.js +60 -0
- package/display/network-approval-page.js +487 -0
- package/display/output-preview.js +261 -0
- package/effects/CellBg/CellBg.css.js +33 -0
- package/effects/CellBg/CellBg.js +410 -0
- package/effects/CellBg/CellBg.tpl.js +5 -0
- package/graph/canvas-adapter.js +223 -0
- package/graph/graph-algorithms.js +31 -0
- package/graph/index.js +46 -0
- package/graph/model.js +176 -0
- package/graph/project-graph-build.js +66 -0
- package/graph/project-graph-metadata.js +253 -0
- package/graph/project-package.js +128 -0
- package/graph/project-runtime.js +116 -0
- package/graph/project-transaction.js +284 -0
- package/graph/skeleton-utils.js +84 -0
- package/graph/theme-contract.js +36 -0
- package/graph/transaction-parser.js +56 -0
- package/icons/MaterialSymbols.js +69 -0
- package/icons/material-symbols-outlined-400.ttf +0 -0
- package/icons/material-symbols.css +24 -0
- package/index.js +95 -0
- package/inspector/InspectorPanel/InspectorPanel.css.js +375 -0
- package/inspector/InspectorPanel/InspectorPanel.js +368 -0
- package/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
- package/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
- package/inspector/TemplatePreview/TemplatePreview.js +145 -0
- package/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
- package/interactions/ConnectFlow.js +304 -0
- package/interactions/Drag.js +104 -0
- package/interactions/Selector.js +133 -0
- package/interactions/SnapGrid.js +66 -0
- package/interactions/Zoom.js +139 -0
- package/layout/ActionZone/ActionZone.css.js +88 -0
- package/layout/ActionZone/ActionZone.js +261 -0
- package/layout/ActionZone/ActionZone.tpl.js +11 -0
- package/layout/CrossLayoutPortalBridge/CrossLayoutPortalBridge.js +255 -0
- package/layout/Layout/Layout.css.js +91 -0
- package/layout/Layout/Layout.js +637 -0
- package/layout/Layout/Layout.tpl.js +27 -0
- package/layout/LayoutNode/LayoutNode.css.js +302 -0
- package/layout/LayoutNode/LayoutNode.js +509 -0
- package/layout/LayoutNode/LayoutNode.tpl.js +39 -0
- package/layout/LayoutPreview/LayoutPreview.css.js +46 -0
- package/layout/LayoutPreview/LayoutPreview.js +102 -0
- package/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
- package/layout/LayoutRouter/LayoutRouter.js +274 -0
- package/layout/LayoutRouter/SectionRegistry.js +135 -0
- package/layout/LayoutRouter/routerSync.js +250 -0
- package/layout/LayoutSidebar/LayoutSidebar.css.js +411 -0
- package/layout/LayoutSidebar/LayoutSidebar.js +368 -0
- package/layout/LayoutSidebar/LayoutSidebar.tpl.js +26 -0
- package/layout/LayoutSidebar/SidebarSection.css.js +20 -0
- package/layout/LayoutSidebar/SidebarSection.js +184 -0
- package/layout/LayoutSidebar/SidebarSection.tpl.js +22 -0
- package/layout/LayoutTree.js +373 -0
- package/layout/PanelMenu/PanelMenu.css.js +43 -0
- package/layout/PanelMenu/PanelMenu.js +95 -0
- package/layout/PanelMenu/PanelMenu.tpl.js +17 -0
- package/layout/ProjectTabs/ProjectTabs.css.js +188 -0
- package/layout/ProjectTabs/ProjectTabs.js +77 -0
- package/layout/ProjectTabs/ProjectTabs.tpl.js +15 -0
- package/layout/index.js +40 -0
- package/list/ListDetailShell/ListDetailShell.css.js +128 -0
- package/list/ListDetailShell/ListDetailShell.js +72 -0
- package/list/ListDetailShell/ListDetailShell.tpl.js +36 -0
- package/list/ListItem/ListItem.css.js +111 -0
- package/list/ListItem/ListItem.js +66 -0
- package/list/ListItem/ListItem.tpl.js +18 -0
- package/locale/index.js +503 -0
- package/manifest/component-registry.js +2446 -0
- package/manifest/graph-schema.js +285 -0
- package/manifest/index.js +6 -0
- package/manifest/project-schema-catalog.js +246 -0
- package/manifest/rule-catalog.js +201 -0
- package/manifest/theme-catalog.js +2149 -0
- package/manifest/ui-schema-catalog.js +334 -0
- package/menu/ContextMenu/ContextMenu.css.js +61 -0
- package/menu/ContextMenu/ContextMenu.js +82 -0
- package/menu/ContextMenu/ContextMenu.tpl.js +19 -0
- package/navigation/QuickOpen/QuickOpen.css.js +92 -0
- package/navigation/QuickOpen/QuickOpen.js +185 -0
- package/navigation/QuickOpen/QuickOpen.tpl.js +15 -0
- package/navigation/quick-open-utils.js +101 -0
- package/node/CtrlItem/CtrlItem.css.js +41 -0
- package/node/CtrlItem/CtrlItem.js +24 -0
- package/node/CtrlItem/CtrlItem.tpl.js +17 -0
- package/node/GraphFrame/GraphFrame.css.js +66 -0
- package/node/GraphFrame/GraphFrame.js +32 -0
- package/node/GraphFrame/GraphFrame.tpl.js +13 -0
- package/node/GraphNode/GraphNode.css.js +815 -0
- package/node/GraphNode/GraphNode.js +173 -0
- package/node/GraphNode/GraphNode.tpl.js +33 -0
- package/node/NodeCallout/NodeCallout.css.js +91 -0
- package/node/NodeCallout/NodeCallout.js +281 -0
- package/node/NodeCallout/NodeCallout.tpl.js +8 -0
- package/node/NodeSocket/NodeSocket.css.js +68 -0
- package/node/NodeSocket/NodeSocket.js +26 -0
- package/node/NodeSocket/NodeSocket.tpl.js +7 -0
- package/node/PortItem/PortItem.css.js +93 -0
- package/node/PortItem/PortItem.js +87 -0
- package/node/PortItem/PortItem.tpl.js +10 -0
- package/package.json +165 -0
- package/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
- package/palette/PaletteBrowser/PaletteBrowser.js +152 -0
- package/palette/PaletteBrowser/PaletteBrowser.tpl.js +23 -0
- package/plugins/History.js +408 -0
- package/plugins/Readonly.js +60 -0
- package/rules/symbiote-3x.json +170 -0
- package/schemas/component-descriptor-v1.json +91 -0
- package/schemas/component-descriptor-v2.json +145 -0
- package/schemas/graph-model-v1.json +179 -0
- package/schemas/graph-v1.json +91 -0
- package/schemas/project-package-v1.json +102 -0
- package/schemas/project-transaction-v1.json +114 -0
- package/schemas/runtime-ui-v1.json +80 -0
- package/schemas/theme-rule-block-v1.json +73 -0
- package/shapes/CircleShape.js +79 -0
- package/shapes/CommentShape.js +35 -0
- package/shapes/DiamondShape.js +130 -0
- package/shapes/NodeShape.js +79 -0
- package/shapes/PillShape.js +91 -0
- package/shapes/RectShape.js +84 -0
- package/shapes/SVGShape.js +525 -0
- package/shapes/index.js +63 -0
- package/surface/Card/Card.css.js +57 -0
- package/surface/Card/Card.js +17 -0
- package/surface/Card/Card.tpl.js +3 -0
- package/themes/Palette.js +30 -0
- package/themes/Skin.js +113 -0
- package/themes/Theme.js +82 -0
- package/themes/carbon.js +135 -0
- package/themes/dark.js +140 -0
- package/themes/default-dark.js +714 -0
- package/themes/default-provider.css +635 -0
- package/themes/default-provider.js +718 -0
- package/themes/ebook.js +136 -0
- package/themes/grey.js +137 -0
- package/themes/light.js +139 -0
- package/themes/neon.js +138 -0
- package/themes/pcb.js +273 -0
- package/themes/synthwave.js +138 -0
- package/tokens/base.json +29 -0
- package/tokens/themes/carbon.json +11 -0
- package/tokens/themes/dark.json +12 -0
- package/tokens/themes/default-dark.json +1543 -0
- package/tokens/themes/default-provider.json +1543 -0
- package/tokens/themes/ebook.json +11 -0
- package/tokens/themes/grey.json +11 -0
- package/tokens/themes/light.json +12 -0
- package/tokens/themes/neon.json +11 -0
- package/tokens/themes/pcb.json +11 -0
- package/tokens/themes/synthwave.json +11 -0
- package/toolbar/QuickToolbar/QuickToolbar.css.js +152 -0
- package/toolbar/QuickToolbar/QuickToolbar.js +529 -0
- package/toolbar/QuickToolbar/QuickToolbar.tpl.js +34 -0
- package/tree/TreePanel/TreePanel.css.js +112 -0
- package/tree/TreePanel/TreePanel.js +147 -0
- package/tree/TreePanel/TreePanel.tpl.js +18 -0
- package/tree/TreeView/TreeView.css.js +122 -0
- package/tree/TreeView/TreeView.js +365 -0
- package/tree/TreeView/TreeView.tpl.js +10 -0
- package/ui/dialogs.js +221 -0
- package/ui/host-adapters.js +114 -0
- package/ui/index.js +660 -0
- package/ui/locale.js +50 -0
- package/ui/overlay-stack.js +89 -0
- package/ui/shared-styles.js +26 -0
- package/webmcp.js +37 -0
- package/xr/deep-graph.js +646 -0
- package/xr/emulation.js +198 -0
- package/xr/gesture.js +228 -0
- package/xr/html-canvas-renderer.js +472 -0
- package/xr/index.js +15 -0
- package/xr/layout-projection.js +1046 -0
- package/xr/panel-frame.js +128 -0
- package/xr/panel-host.js +267 -0
- package/xr/pointer.js +258 -0
- package/xr/scene-controller.js +242 -0
- package/xr/spatial-scene.js +212 -0
- package/xr/theme-bridge.js +105 -0
- package/xr/three-webxr-adapter.js +3439 -0
- package/xr/webgl-layer-renderer.js +419 -0
- package/xr/webxr.js +679 -0
- package/xr/workbench.js +516 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RectShape — standard rectangular node card
|
|
3
|
+
*
|
|
4
|
+
* Sockets arranged vertically on left (inputs) and right (outputs).
|
|
5
|
+
* Default shape for most nodes.
|
|
6
|
+
*
|
|
7
|
+
* @module symbiote-node/shapes/RectShape
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { NodeShape } from './NodeShape.js';
|
|
11
|
+
|
|
12
|
+
export class RectShape extends NodeShape {
|
|
13
|
+
name = 'rect';
|
|
14
|
+
|
|
15
|
+
/** @type {number} */
|
|
16
|
+
#headerHeight;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} [config]
|
|
20
|
+
* @param {number} [config.headerHeight=36] - Height of header area
|
|
21
|
+
*/
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
super();
|
|
24
|
+
this.#headerHeight = config.headerHeight || 36;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getSocketPosition(side, index, total, { width, height }) {
|
|
28
|
+
let bodyHeight = height - this.#headerHeight;
|
|
29
|
+
let spacing = bodyHeight / (total + 1);
|
|
30
|
+
let y = this.#headerHeight + spacing * (index + 1);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
x: side === 'input' ? 0 : width,
|
|
34
|
+
y,
|
|
35
|
+
angle: side === 'input' ? 180 : 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get pin position on a specific side of the rectangle.
|
|
41
|
+
* Required for PCB path style — without this, ConnectionRenderer
|
|
42
|
+
* skips rect nodes entirely.
|
|
43
|
+
*
|
|
44
|
+
* @param {'top'|'right'|'bottom'|'left'} side
|
|
45
|
+
* @param {number} t - position along the side (0..1), 0.5 = center
|
|
46
|
+
* @param {{ width: number, height: number }} size
|
|
47
|
+
* @returns {{ x: number, y: number, angle: number }}
|
|
48
|
+
*/
|
|
49
|
+
getSidePosition(side, t, size) {
|
|
50
|
+
const NORMALS = { top: -90, right: 0, bottom: 90, left: 180 };
|
|
51
|
+
const MARGIN = 0.2;
|
|
52
|
+
let effectiveT = MARGIN + t * (1 - 2 * MARGIN);
|
|
53
|
+
|
|
54
|
+
let x, y;
|
|
55
|
+
switch (side) {
|
|
56
|
+
case 'top':
|
|
57
|
+
x = size.width * effectiveT;
|
|
58
|
+
y = 0;
|
|
59
|
+
break;
|
|
60
|
+
case 'right':
|
|
61
|
+
x = size.width;
|
|
62
|
+
y = size.height * effectiveT;
|
|
63
|
+
break;
|
|
64
|
+
case 'bottom':
|
|
65
|
+
x = size.width * effectiveT;
|
|
66
|
+
y = size.height;
|
|
67
|
+
break;
|
|
68
|
+
case 'left':
|
|
69
|
+
x = 0;
|
|
70
|
+
y = size.height * effectiveT;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { x, y, angle: NORMALS[side] };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getBorderRadius() {
|
|
78
|
+
return 'var(--sn-node-radius)';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getMinSize() {
|
|
82
|
+
return { minWidth: 180, minHeight: 60 };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVGShape — universal shape from any SVG path
|
|
3
|
+
*
|
|
4
|
+
* Uses SVGPathElement.getPointAtLength() for dynamic connector placement.
|
|
5
|
+
* Any SVG icon path can become a node shape — the outer contour defines
|
|
6
|
+
* the visual fill and connector positions are computed along the perimeter.
|
|
7
|
+
*
|
|
8
|
+
* Port placement strategy:
|
|
9
|
+
* - Inputs placed on left portion of path perimeter
|
|
10
|
+
* - Outputs placed on right portion of path perimeter
|
|
11
|
+
* - Connector angle = normal from center → edge point
|
|
12
|
+
* - Aspect ratio of original SVG is preserved (xMidYMid meet)
|
|
13
|
+
*
|
|
14
|
+
* @module symbiote-node/shapes/SVGShape
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { NodeShape } from './NodeShape.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Offscreen SVG namespace for path computation
|
|
21
|
+
* @type {SVGSVGElement|null}
|
|
22
|
+
*/
|
|
23
|
+
let _offscreenSVG = null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get or create an offscreen SVG element for path calculations
|
|
27
|
+
* @returns {SVGSVGElement}
|
|
28
|
+
*/
|
|
29
|
+
function getOffscreenSVG() {
|
|
30
|
+
if (_offscreenSVG) return _offscreenSVG;
|
|
31
|
+
if (typeof document === 'undefined') return null;
|
|
32
|
+
_offscreenSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
33
|
+
_offscreenSVG.style.position = 'absolute';
|
|
34
|
+
_offscreenSVG.style.width = '0';
|
|
35
|
+
_offscreenSVG.style.height = '0';
|
|
36
|
+
_offscreenSVG.style.overflow = 'hidden';
|
|
37
|
+
document.body.appendChild(_offscreenSVG);
|
|
38
|
+
return _offscreenSVG;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Compute scaling params for viewBox → element mapping
|
|
43
|
+
* Preserves aspect ratio (equivalent to SVG preserveAspectRatio="xMidYMid meet")
|
|
44
|
+
*
|
|
45
|
+
* @param {number[]} vb - [x, y, w, h] viewBox
|
|
46
|
+
* @param {{ width: number, height: number }} size - element size
|
|
47
|
+
* @returns {{ scale: number, offsetX: number, offsetY: number }}
|
|
48
|
+
*/
|
|
49
|
+
function computeMapping(vb, size) {
|
|
50
|
+
let [vx, vy, vw, vh] = vb;
|
|
51
|
+
let scale = Math.min(size.width / vw, size.height / vh);
|
|
52
|
+
let renderedW = vw * scale;
|
|
53
|
+
let renderedH = vh * scale;
|
|
54
|
+
return {
|
|
55
|
+
scale,
|
|
56
|
+
offsetX: (size.width - renderedW) / 2 - vx * scale,
|
|
57
|
+
offsetY: (size.height - renderedH) / 2 - vy * scale,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class SVGShape extends NodeShape {
|
|
62
|
+
/** @type {string} */
|
|
63
|
+
name;
|
|
64
|
+
|
|
65
|
+
/** @type {string} - SVG path d attribute */
|
|
66
|
+
pathData;
|
|
67
|
+
|
|
68
|
+
/** @type {string} - viewBox of original SVG */
|
|
69
|
+
viewBox;
|
|
70
|
+
|
|
71
|
+
/** @type {number[]} - parsed viewBox [x, y, w, h] */
|
|
72
|
+
#vb;
|
|
73
|
+
|
|
74
|
+
/** @type {boolean} - whether shape has standard header */
|
|
75
|
+
#header;
|
|
76
|
+
|
|
77
|
+
/** @type {{ minWidth: number, minHeight: number }} */
|
|
78
|
+
#minSize;
|
|
79
|
+
|
|
80
|
+
/** @type {Map<string, {x:number,y:number,angle:number}>} - position cache */
|
|
81
|
+
#posCache = new Map();
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} name - Shape identifier
|
|
85
|
+
* @param {object} options
|
|
86
|
+
* @param {string} options.pathData - SVG path d attribute
|
|
87
|
+
* @param {string} [options.viewBox='0 0 24 24'] - Original SVG viewBox
|
|
88
|
+
* @param {boolean} [options.header=false] - Show header/controls
|
|
89
|
+
* @param {{ minWidth?: number, minHeight?: number }} [options.minSize]
|
|
90
|
+
*/
|
|
91
|
+
constructor(name, { pathData, viewBox = '0 0 24 24', header = false, minSize = {} }) {
|
|
92
|
+
super();
|
|
93
|
+
this.name = name;
|
|
94
|
+
this.pathData = pathData;
|
|
95
|
+
this.viewBox = viewBox;
|
|
96
|
+
this.#vb = viewBox.split(' ').map(Number);
|
|
97
|
+
this.#header = header;
|
|
98
|
+
this.#minSize = {
|
|
99
|
+
minWidth: minSize.minWidth || 100,
|
|
100
|
+
minHeight: minSize.minHeight || 100,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Scale a point from viewBox coordinates to element pixel coordinates
|
|
106
|
+
* Uses aspect-ratio-preserving mapping (xMidYMid meet)
|
|
107
|
+
*
|
|
108
|
+
* @param {number} px - x in viewBox
|
|
109
|
+
* @param {number} py - y in viewBox
|
|
110
|
+
* @param {{ width: number, height: number }} size - element size
|
|
111
|
+
* @returns {{ x: number, y: number }}
|
|
112
|
+
*/
|
|
113
|
+
#scalePoint(px, py, size) {
|
|
114
|
+
let { scale, offsetX, offsetY } = computeMapping(this.#vb, size);
|
|
115
|
+
return {
|
|
116
|
+
x: px * scale + offsetX,
|
|
117
|
+
y: py * scale + offsetY,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get center of SVG shape in viewBox coordinates
|
|
123
|
+
* @returns {{ x: number, y: number }}
|
|
124
|
+
*/
|
|
125
|
+
#getCenter() {
|
|
126
|
+
let [vx, vy, vw, vh] = this.#vb;
|
|
127
|
+
return { x: vx + vw / 2, y: vy + vh / 2 };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get a path element for computations (uses offscreen SVG)
|
|
132
|
+
* @returns {SVGPathElement|null}
|
|
133
|
+
*/
|
|
134
|
+
#getPathElement() {
|
|
135
|
+
let svg = getOffscreenSVG();
|
|
136
|
+
if (!svg) return null;
|
|
137
|
+
let pathEl = svg.querySelector(`[data-shape="${this.name}"]`);
|
|
138
|
+
if (!pathEl) {
|
|
139
|
+
pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
140
|
+
pathEl.setAttribute('d', this.pathData);
|
|
141
|
+
pathEl.setAttribute('data-shape', this.name);
|
|
142
|
+
svg.appendChild(pathEl);
|
|
143
|
+
}
|
|
144
|
+
return pathEl;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Find the point on the SVG path that a ray from center at a given angle hits.
|
|
149
|
+
* Uses dense sampling along the path perimeter and finds the point
|
|
150
|
+
* whose angle from center best matches the target angle.
|
|
151
|
+
*
|
|
152
|
+
* @param {number} targetAngle - angle in radians from center
|
|
153
|
+
* @param {SVGPathElement} pathEl
|
|
154
|
+
* @returns {{ x: number, y: number }} - point in viewBox coordinates
|
|
155
|
+
*/
|
|
156
|
+
#findPointAtAngle(targetAngle, pathEl) {
|
|
157
|
+
let totalLen = pathEl.getTotalLength();
|
|
158
|
+
let center = this.#getCenter();
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
let bestDist = Infinity;
|
|
162
|
+
let bestLen = 0;
|
|
163
|
+
const COARSE = 128;
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i <= COARSE; i++) {
|
|
166
|
+
let len = (totalLen * i) / COARSE;
|
|
167
|
+
let pt = pathEl.getPointAtLength(len);
|
|
168
|
+
let angle = Math.atan2(pt.y - center.y, pt.x - center.x);
|
|
169
|
+
let diff = Math.abs(angle - targetAngle);
|
|
170
|
+
if (diff > Math.PI) diff = 2 * Math.PI - diff;
|
|
171
|
+
if (diff < bestDist) {
|
|
172
|
+
bestDist = diff;
|
|
173
|
+
bestLen = len;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
let searchRadius = totalLen / COARSE;
|
|
179
|
+
const FINE = 32;
|
|
180
|
+
let startLen = Math.max(0, bestLen - searchRadius);
|
|
181
|
+
let endLen = Math.min(totalLen, bestLen + searchRadius);
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i <= FINE; i++) {
|
|
184
|
+
let len = startLen + ((endLen - startLen) * i) / FINE;
|
|
185
|
+
let pt = pathEl.getPointAtLength(len);
|
|
186
|
+
let angle = Math.atan2(pt.y - center.y, pt.x - center.x);
|
|
187
|
+
let diff = Math.abs(angle - targetAngle);
|
|
188
|
+
if (diff > Math.PI) diff = 2 * Math.PI - diff;
|
|
189
|
+
if (diff < bestDist) {
|
|
190
|
+
bestDist = diff;
|
|
191
|
+
bestLen = len;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return pathEl.getPointAtLength(bestLen);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get socket position on the shape outline.
|
|
200
|
+
* Results are cached — same params always return same position.
|
|
201
|
+
*
|
|
202
|
+
* @param {'input'|'output'} side
|
|
203
|
+
* @param {number} index - ordinal index of this port
|
|
204
|
+
* @param {number} total - total ports on this side
|
|
205
|
+
* @param {{ width: number, height: number }} size - node dimensions
|
|
206
|
+
* @returns {{ x: number, y: number, angle: number }}
|
|
207
|
+
*/
|
|
208
|
+
getSocketPosition(side, index, total, size) {
|
|
209
|
+
|
|
210
|
+
let key = `${side}|${index}|${total}|${size.width}|${size.height}`;
|
|
211
|
+
if (this.#posCache.has(key)) return this.#posCache.get(key);
|
|
212
|
+
|
|
213
|
+
let pathEl = this.#getPathElement();
|
|
214
|
+
|
|
215
|
+
if (!pathEl) {
|
|
216
|
+
let y = (size.height * (index + 1)) / (total + 1);
|
|
217
|
+
let result = side === 'input' ? { x: 0, y, angle: 180 } : { x: size.width, y, angle: 0 };
|
|
218
|
+
this.#posCache.set(key, result);
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
let centerAngle = side === 'input' ? Math.PI : 0;
|
|
224
|
+
let arcSpan = Math.PI * 0.6;
|
|
225
|
+
let targetAngle;
|
|
226
|
+
|
|
227
|
+
if (total === 1) {
|
|
228
|
+
targetAngle = centerAngle;
|
|
229
|
+
} else {
|
|
230
|
+
let startAngle = centerAngle - arcSpan / 2;
|
|
231
|
+
let step = arcSpan / (total - 1);
|
|
232
|
+
targetAngle = startAngle + step * index;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let pt = this.#findPointAtAngle(targetAngle, pathEl);
|
|
236
|
+
let scaled = this.#scalePoint(pt.x, pt.y, size);
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
let totalLen = pathEl.getTotalLength();
|
|
240
|
+
let center = this.#getCenter();
|
|
241
|
+
let bestLen = 0,
|
|
242
|
+
bestDist = Infinity;
|
|
243
|
+
const SCAN = 128;
|
|
244
|
+
for (let i = 0; i <= SCAN; i++) {
|
|
245
|
+
let len = (totalLen * i) / SCAN;
|
|
246
|
+
let p = pathEl.getPointAtLength(len);
|
|
247
|
+
let dist = (p.x - pt.x) ** 2 + (p.y - pt.y) ** 2;
|
|
248
|
+
if (dist < bestDist) {
|
|
249
|
+
bestDist = dist;
|
|
250
|
+
bestLen = len;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
let delta = 0.5;
|
|
254
|
+
let prevPt = pathEl.getPointAtLength(Math.max(0, bestLen - delta));
|
|
255
|
+
let nextPt = pathEl.getPointAtLength(Math.min(totalLen, bestLen + delta));
|
|
256
|
+
let tx = nextPt.x - prevPt.x,
|
|
257
|
+
ty = nextPt.y - prevPt.y;
|
|
258
|
+
let nx = -ty,
|
|
259
|
+
ny = tx;
|
|
260
|
+
let radX = pt.x - center.x,
|
|
261
|
+
radY = pt.y - center.y;
|
|
262
|
+
if (nx * radX + ny * radY < 0) {
|
|
263
|
+
nx = ty;
|
|
264
|
+
ny = -tx;
|
|
265
|
+
}
|
|
266
|
+
let angleDeg = (Math.atan2(ny, nx) * 180) / Math.PI;
|
|
267
|
+
|
|
268
|
+
let result = { x: scaled.x, y: scaled.y, angle: angleDeg };
|
|
269
|
+
this.#posCache.set(key, result);
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get edge point at a specific angle (direction toward target node).
|
|
275
|
+
* Used for dynamic connectors that slide along the perimeter.
|
|
276
|
+
*
|
|
277
|
+
* @param {number} angle - angle in radians from center to target
|
|
278
|
+
* @param {{ width: number, height: number }} size - element dimensions
|
|
279
|
+
* @returns {{ x: number, y: number, angle: number }}
|
|
280
|
+
*/
|
|
281
|
+
getEdgePoint(angle, size) {
|
|
282
|
+
|
|
283
|
+
let rounded = Math.round(angle * 1000) / 1000;
|
|
284
|
+
let key = `edge|${rounded}|${size.width}|${size.height}`;
|
|
285
|
+
if (this.#posCache.has(key)) return this.#posCache.get(key);
|
|
286
|
+
|
|
287
|
+
let pathEl = this.#getPathElement();
|
|
288
|
+
if (!pathEl) {
|
|
289
|
+
let cx = size.width / 2;
|
|
290
|
+
let cy = size.height / 2;
|
|
291
|
+
return {
|
|
292
|
+
x: cx + Math.cos(angle) * cx,
|
|
293
|
+
y: cy + Math.sin(angle) * cy,
|
|
294
|
+
angle: (angle * 180) / Math.PI,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let pt = this.#findPointAtAngle(angle, pathEl);
|
|
299
|
+
let scaled = this.#scalePoint(pt.x, pt.y, size);
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
let totalLen = pathEl.getTotalLength();
|
|
303
|
+
let center = this.#getCenter();
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
let bestLen = 0,
|
|
307
|
+
bestDist = Infinity;
|
|
308
|
+
const SCAN = 128;
|
|
309
|
+
for (let i = 0; i <= SCAN; i++) {
|
|
310
|
+
let len = (totalLen * i) / SCAN;
|
|
311
|
+
let p = pathEl.getPointAtLength(len);
|
|
312
|
+
let dist = (p.x - pt.x) ** 2 + (p.y - pt.y) ** 2;
|
|
313
|
+
if (dist < bestDist) {
|
|
314
|
+
bestDist = dist;
|
|
315
|
+
bestLen = len;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
let delta = 0.5;
|
|
321
|
+
let prevLen = Math.max(0, bestLen - delta);
|
|
322
|
+
let nextLen = Math.min(totalLen, bestLen + delta);
|
|
323
|
+
let prevPt = pathEl.getPointAtLength(prevLen);
|
|
324
|
+
let nextPt = pathEl.getPointAtLength(nextLen);
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
let tx = nextPt.x - prevPt.x;
|
|
328
|
+
let ty = nextPt.y - prevPt.y;
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
let nx = -ty,
|
|
332
|
+
ny = tx;
|
|
333
|
+
let radX = pt.x - center.x;
|
|
334
|
+
let radY = pt.y - center.y;
|
|
335
|
+
|
|
336
|
+
if (nx * radX + ny * radY < 0) {
|
|
337
|
+
nx = ty;
|
|
338
|
+
ny = -tx;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let angleDeg = (Math.atan2(ny, nx) * 180) / Math.PI;
|
|
342
|
+
|
|
343
|
+
let result = { x: scaled.x, y: scaled.y, angle: angleDeg };
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
if (this.#posCache.size > 360) {
|
|
347
|
+
let first = this.#posCache.keys().next().value;
|
|
348
|
+
this.#posCache.delete(first);
|
|
349
|
+
}
|
|
350
|
+
this.#posCache.set(key, result);
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get a pin position on a specific side of the shape.
|
|
356
|
+
*
|
|
357
|
+
* Side-based placement: pins are placed along a side edge (top/right/bottom/left).
|
|
358
|
+
* The position within the side is controlled by t (0 = start, 1 = end).
|
|
359
|
+
*
|
|
360
|
+
* @param {'top'|'right'|'bottom'|'left'} side - which side to place pin on
|
|
361
|
+
* @param {number} t - position along the side (0..1), 0.5 = center
|
|
362
|
+
* @param {{ width: number, height: number }} size - element dimensions
|
|
363
|
+
* @returns {{ x: number, y: number, angle: number }} - angle is outward normal in degrees
|
|
364
|
+
*/
|
|
365
|
+
getSidePosition(side, t, size) {
|
|
366
|
+
let key = `side|${side}|${(t * 100) | 0}|${size.width}|${size.height}`;
|
|
367
|
+
if (this.#posCache.has(key)) return this.#posCache.get(key);
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
const NORMALS = { top: -90, right: 0, bottom: 90, left: 180 };
|
|
371
|
+
let angleDeg = NORMALS[side];
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
let pathEl = this.#getPathElement();
|
|
375
|
+
if (pathEl) {
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
const RANGES = {
|
|
379
|
+
right: { from: -Math.PI / 4, to: Math.PI / 4 },
|
|
380
|
+
bottom: { from: Math.PI / 4, to: (3 * Math.PI) / 4 },
|
|
381
|
+
left: { from: (3 * Math.PI) / 4, to: (5 * Math.PI) / 4 },
|
|
382
|
+
top: { from: (-3 * Math.PI) / 4, to: -Math.PI / 4 },
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
let range = RANGES[side];
|
|
386
|
+
|
|
387
|
+
let sideKey = `sidepts|${side}|${size.width}|${size.height}`;
|
|
388
|
+
let sidePoints;
|
|
389
|
+
|
|
390
|
+
if (this.#posCache.has(sideKey)) {
|
|
391
|
+
sidePoints = this.#posCache.get(sideKey);
|
|
392
|
+
} else {
|
|
393
|
+
sidePoints = [];
|
|
394
|
+
const SAMPLES = 16;
|
|
395
|
+
for (let i = 0; i <= SAMPLES; i++) {
|
|
396
|
+
let a = range.from + (range.to - range.from) * (i / SAMPLES);
|
|
397
|
+
let pt = this.#findPointAtAngle(a, pathEl);
|
|
398
|
+
let sp = this.#scalePoint(pt.x, pt.y, size);
|
|
399
|
+
sidePoints.push(sp);
|
|
400
|
+
}
|
|
401
|
+
this.#posCache.set(sideKey, sidePoints);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
const MARGIN = 0.2;
|
|
406
|
+
let effectiveT = MARGIN + t * (1 - 2 * MARGIN);
|
|
407
|
+
let idx = effectiveT * (sidePoints.length - 1);
|
|
408
|
+
let lo = Math.floor(idx);
|
|
409
|
+
let hi = Math.min(lo + 1, sidePoints.length - 1);
|
|
410
|
+
let frac = idx - lo;
|
|
411
|
+
|
|
412
|
+
let x = sidePoints[lo].x + (sidePoints[hi].x - sidePoints[lo].x) * frac;
|
|
413
|
+
let y = sidePoints[lo].y + (sidePoints[hi].y - sidePoints[lo].y) * frac;
|
|
414
|
+
|
|
415
|
+
let result = { x, y, angle: angleDeg };
|
|
416
|
+
this.#posCache.set(key, result);
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
let x, y;
|
|
422
|
+
switch (side) {
|
|
423
|
+
case 'top':
|
|
424
|
+
x = size.width * (0.2 + t * 0.6);
|
|
425
|
+
y = 0;
|
|
426
|
+
break;
|
|
427
|
+
case 'right':
|
|
428
|
+
x = size.width;
|
|
429
|
+
y = size.height * (0.2 + t * 0.6);
|
|
430
|
+
break;
|
|
431
|
+
case 'bottom':
|
|
432
|
+
x = size.width * (0.2 + t * 0.6);
|
|
433
|
+
y = size.height;
|
|
434
|
+
break;
|
|
435
|
+
case 'left':
|
|
436
|
+
x = 0;
|
|
437
|
+
y = size.height * (0.2 + t * 0.6);
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
let result = { x, y, angle: angleDeg };
|
|
442
|
+
this.#posCache.set(key, result);
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
getClipPath(_size) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
getOutlinePath(_size) {
|
|
451
|
+
return this.pathData;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
getBorderRadius() {
|
|
455
|
+
return '0';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
get hasHeader() {
|
|
459
|
+
return this.#header;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
get hasControls() {
|
|
463
|
+
return this.#header;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
getMinSize() {
|
|
467
|
+
return this.#minSize;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Register an SVG shape from a path string
|
|
474
|
+
* @param {string} name
|
|
475
|
+
* @param {string} pathData - SVG d attribute
|
|
476
|
+
* @param {object} [options]
|
|
477
|
+
* @returns {SVGShape}
|
|
478
|
+
*/
|
|
479
|
+
export function createSVGShape(name, pathData, options = {}) {
|
|
480
|
+
return new SVGShape(name, { pathData, ...options });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
export let SVG_PRESETS = {
|
|
485
|
+
disc: 'M12 2A10 10 0 1 1 12 22A10 10 0 1 1 12 2Z',
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
hexagon: 'M12 2L22 8.5V15.5L12 22L2 15.5V8.5Z',
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
pentagon: 'M12 2L22 9.27L18.18 21H5.82L2 9.27Z',
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
star: 'M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26Z',
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
cloud:
|
|
498
|
+
'M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z',
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
shield: 'M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4z',
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
octagon: 'M7.86 2H16.14L22 7.86V16.14L16.14 22H7.86L2 16.14V7.86Z',
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
parallelogram: 'M6 2H22L18 22H2Z',
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
trapezoid: 'M4 22H20L23 2H1Z',
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
cylinder: 'M4 6C4 4 8 2 12 2S20 4 20 6V18C20 20 16 22 12 22S4 20 4 18Z',
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
database:
|
|
517
|
+
'M12 3C7.58 3 4 4.79 4 7V17C4 19.21 7.59 21 12 21S20 19.21 20 17V7C20 4.79 16.42 3 12 3Z',
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
bolt: 'M7 2V13H10V22L17 10H13L17 2Z',
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
heart:
|
|
524
|
+
'M12 21.35L10.55 20.03C5.4 15.36 2 12.28 2 8.5C2 5.42 4.42 3 7.5 3C9.24 3 10.91 3.81 12 5.09C13.09 3.81 14.76 3 16.5 3C19.58 3 22 5.42 22 8.5C22 12.28 18.6 15.36 13.45 20.04L12 21.35Z',
|
|
525
|
+
};
|
package/shapes/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shape registry — maps shape names to implementations
|
|
3
|
+
* @module symbiote-node/shapes/index
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { NodeShape } from './NodeShape.js';
|
|
7
|
+
import { RectShape } from './RectShape.js';
|
|
8
|
+
import { PillShape } from './PillShape.js';
|
|
9
|
+
import { CircleShape } from './CircleShape.js';
|
|
10
|
+
import { DiamondShape } from './DiamondShape.js';
|
|
11
|
+
import { CommentShape } from './CommentShape.js';
|
|
12
|
+
import { SVGShape, createSVGShape, SVG_PRESETS } from './SVGShape.js';
|
|
13
|
+
|
|
14
|
+
/** @type {Map<string, NodeShape>} */
|
|
15
|
+
let registry = new Map();
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
const RECT = new RectShape();
|
|
19
|
+
const PILL = new PillShape();
|
|
20
|
+
const CIRCLE = new CircleShape();
|
|
21
|
+
const DIAMOND = new DiamondShape();
|
|
22
|
+
const COMMENT = new CommentShape();
|
|
23
|
+
|
|
24
|
+
registry.set('rect', RECT);
|
|
25
|
+
registry.set('pill', PILL);
|
|
26
|
+
registry.set('circle', CIRCLE);
|
|
27
|
+
registry.set('diamond', DIAMOND);
|
|
28
|
+
registry.set('comment', COMMENT);
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
for (const [name, pathData] of Object.entries(SVG_PRESETS)) {
|
|
32
|
+
registry.set(name, createSVGShape(name, pathData));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get shape by name
|
|
37
|
+
* @param {string} name
|
|
38
|
+
* @returns {NodeShape}
|
|
39
|
+
*/
|
|
40
|
+
export function getShape(name) {
|
|
41
|
+
return registry.get(name) || RECT;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Register custom shape
|
|
46
|
+
* @param {string} name
|
|
47
|
+
* @param {NodeShape} shape
|
|
48
|
+
*/
|
|
49
|
+
export function registerShape(name, shape) {
|
|
50
|
+
registry.set(name, shape);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
NodeShape,
|
|
55
|
+
RectShape,
|
|
56
|
+
PillShape,
|
|
57
|
+
CircleShape,
|
|
58
|
+
DiamondShape,
|
|
59
|
+
CommentShape,
|
|
60
|
+
SVGShape,
|
|
61
|
+
createSVGShape,
|
|
62
|
+
SVG_PRESETS,
|
|
63
|
+
};
|