@pauloricca/spagbol 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +330 -2
- package/dist/controls.css +122 -0
- package/dist/core/controller.d.ts +89 -0
- package/dist/core/controller.d.ts.map +1 -0
- package/dist/core/controller.js +732 -0
- package/dist/core/controller.js.map +1 -0
- package/dist/core/graph.d.ts +30 -0
- package/dist/core/graph.d.ts.map +1 -0
- package/dist/core/graph.js +318 -0
- package/dist/core/graph.js.map +1 -0
- package/dist/core/grouping.d.ts +10 -0
- package/dist/core/grouping.d.ts.map +1 -0
- package/dist/core/grouping.js +130 -0
- package/dist/core/grouping.js.map +1 -0
- package/dist/core/groups.d.ts +40 -0
- package/dist/core/groups.d.ts.map +1 -0
- package/dist/core/groups.js +164 -0
- package/dist/core/groups.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/model.d.ts +182 -0
- package/dist/core/model.d.ts.map +1 -0
- package/dist/core/model.js +149 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/observations.d.ts +10 -0
- package/dist/core/observations.d.ts.map +1 -0
- package/dist/core/observations.js +36 -0
- package/dist/core/observations.js.map +1 -0
- package/dist/core/serialization.d.ts +9 -0
- package/dist/core/serialization.d.ts.map +1 -0
- package/dist/core/serialization.js +148 -0
- package/dist/core/serialization.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/link-controls.css +123 -0
- package/dist/react/Controls.d.ts +18 -0
- package/dist/react/Controls.d.ts.map +1 -0
- package/dist/react/Controls.js +301 -0
- package/dist/react/Controls.js.map +1 -0
- package/dist/react/SpagbolCanvas.d.ts +38 -0
- package/dist/react/SpagbolCanvas.d.ts.map +1 -0
- package/dist/react/SpagbolCanvas.js +1210 -0
- package/dist/react/SpagbolCanvas.js.map +1 -0
- package/dist/react/SpagbolCanvasControls.d.ts +36 -0
- package/dist/react/SpagbolCanvasControls.d.ts.map +1 -0
- package/dist/react/SpagbolCanvasControls.js +113 -0
- package/dist/react/SpagbolCanvasControls.js.map +1 -0
- package/dist/react/SpagbolCanvasFlow.d.ts +14 -0
- package/dist/react/SpagbolCanvasFlow.d.ts.map +1 -0
- package/dist/react/SpagbolCanvasFlow.js +6 -0
- package/dist/react/SpagbolCanvasFlow.js.map +1 -0
- package/dist/react/SpagbolCanvasLinkControls.d.ts +16 -0
- package/dist/react/SpagbolCanvasLinkControls.d.ts.map +1 -0
- package/dist/react/SpagbolCanvasLinkControls.js +7 -0
- package/dist/react/SpagbolCanvasLinkControls.js.map +1 -0
- package/dist/react/SpagbolCanvasMenu.d.ts +12 -0
- package/dist/react/SpagbolCanvasMenu.d.ts.map +1 -0
- package/dist/react/SpagbolCanvasMenu.js +65 -0
- package/dist/react/SpagbolCanvasMenu.js.map +1 -0
- package/dist/react/SpagbolControlNumber.d.ts +25 -0
- package/dist/react/SpagbolControlNumber.d.ts.map +1 -0
- package/dist/react/SpagbolControlNumber.js +209 -0
- package/dist/react/SpagbolControlNumber.js.map +1 -0
- package/dist/react/SpagbolControlNumberLink.d.ts +6 -0
- package/dist/react/SpagbolControlNumberLink.d.ts.map +1 -0
- package/dist/react/SpagbolControlNumberLink.js +159 -0
- package/dist/react/SpagbolControlNumberLink.js.map +1 -0
- package/dist/react/SpagbolControlZoom.d.ts +11 -0
- package/dist/react/SpagbolControlZoom.d.ts.map +1 -0
- package/dist/react/SpagbolControlZoom.js +24 -0
- package/dist/react/SpagbolControlZoom.js.map +1 -0
- package/dist/react/SpagbolNodeFrame.d.ts +35 -0
- package/dist/react/SpagbolNodeFrame.d.ts.map +1 -0
- package/dist/react/SpagbolNodeFrame.js +169 -0
- package/dist/react/SpagbolNodeFrame.js.map +1 -0
- package/dist/react/SpagbolNodeFrameTypePicker.d.ts +9 -0
- package/dist/react/SpagbolNodeFrameTypePicker.d.ts.map +1 -0
- package/dist/react/SpagbolNodeFrameTypePicker.js +68 -0
- package/dist/react/SpagbolNodeFrameTypePicker.js.map +1 -0
- package/dist/react/SpagbolNodeGroupPort.d.ts +15 -0
- package/dist/react/SpagbolNodeGroupPort.d.ts.map +1 -0
- package/dist/react/SpagbolNodeGroupPort.js +62 -0
- package/dist/react/SpagbolNodeGroupPort.js.map +1 -0
- package/dist/react/SpagbolNodeGroupPortAdd.d.ts +5 -0
- package/dist/react/SpagbolNodeGroupPortAdd.d.ts.map +1 -0
- package/dist/react/SpagbolNodeGroupPortAdd.js +27 -0
- package/dist/react/SpagbolNodeGroupPortAdd.js.map +1 -0
- package/dist/react/context.d.ts +84 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/context.js +43 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/duplicateDrag.d.ts +19 -0
- package/dist/react/duplicateDrag.d.ts.map +1 -0
- package/dist/react/duplicateDrag.js +71 -0
- package/dist/react/duplicateDrag.js.map +1 -0
- package/dist/react/flowPresentation.d.ts +11 -0
- package/dist/react/flowPresentation.d.ts.map +1 -0
- package/dist/react/flowPresentation.js +50 -0
- package/dist/react/flowPresentation.js.map +1 -0
- package/dist/react/geometry.d.ts +13 -0
- package/dist/react/geometry.d.ts.map +1 -0
- package/dist/react/geometry.js +43 -0
- package/dist/react/geometry.js.map +1 -0
- package/dist/react/index.d.ts +11 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +11 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/portState.d.ts +10 -0
- package/dist/react/portState.d.ts.map +1 -0
- package/dist/react/portState.js +35 -0
- package/dist/react/portState.js.map +1 -0
- package/dist/react/viewport.d.ts +24 -0
- package/dist/react/viewport.d.ts.map +1 -0
- package/dist/react/viewport.js +55 -0
- package/dist/react/viewport.js.map +1 -0
- package/dist/styles.css +748 -0
- package/package.json +58 -4
- package/src/core/controller.ts +844 -0
- package/src/core/graph.ts +451 -0
- package/src/core/grouping.ts +159 -0
- package/src/core/groups.ts +125 -0
- package/src/core/index.ts +7 -0
- package/src/core/model.ts +304 -0
- package/src/core/observations.ts +31 -0
- package/src/core/serialization.ts +184 -0
- package/src/index.ts +9 -0
- package/src/react/Controls.tsx +355 -0
- package/src/react/SpagbolCanvas.tsx +1859 -0
- package/src/react/SpagbolCanvasControls.tsx +255 -0
- package/src/react/SpagbolCanvasFlow.tsx +38 -0
- package/src/react/SpagbolCanvasLinkControls.tsx +21 -0
- package/src/react/SpagbolCanvasMenu.tsx +121 -0
- package/src/react/SpagbolControlNumber.tsx +297 -0
- package/src/react/SpagbolControlNumberLink.tsx +209 -0
- package/src/react/SpagbolControlZoom.tsx +102 -0
- package/src/react/SpagbolNodeFrame.tsx +329 -0
- package/src/react/SpagbolNodeFrameTypePicker.tsx +51 -0
- package/src/react/SpagbolNodeGroupPort.tsx +111 -0
- package/src/react/SpagbolNodeGroupPortAdd.tsx +48 -0
- package/src/react/context.tsx +146 -0
- package/src/react/controls.css +122 -0
- package/src/react/duplicateDrag.ts +97 -0
- package/src/react/flowPresentation.ts +56 -0
- package/src/react/geometry.ts +53 -0
- package/src/react/index.ts +61 -0
- package/src/react/link-controls.css +123 -0
- package/src/react/portState.ts +24 -0
- package/src/react/styles.css +748 -0
- package/src/react/viewport.ts +90 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolControlZoom.js","sourceRoot":"","sources":["../../src/react/SpagbolControlZoom.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAUtE,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAAC,EACjC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,KAAK,EACL,MAAM,GAAG,MAAM,GACS;IACxB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC;IAChD,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,OAAO;YAAE,OAAO,EAAE,CAAC;;YAClB,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,iCAAiC,UAAU,GAAG,CAAC;IAC7D,IAAI,MAAM,KAAK,MAAM;QACnB,OAAO,CACL,MAAC,QAAQ,IACP,eAAe,EAAE,KAAK,EACtB,SAAS,EAAE,wBAAwB,SAAS,IAAI,EAAE,EAAE,aAEpD,KAAC,aAAa,IACZ,SAAS,EAAC,sCAAsC,EAChD,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,KAAK,gBACF,KAAK,EACjB,KAAK,EAAE,KAAK,YAEX,UAAU,GACG,EACf,KAAK,IACG,CACZ,CAAC;IACJ,OAAO,CACL,eACE,SAAS,EAAE,iEAAiE,SAAS,IAAI,EAAE,EAAE,EAC7F,IAAI,EAAC,OAAO,gBACD,sBAAsB,EACjC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EACjD,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,aAE7C,eAAK,SAAS,EAAC,4BAA4B,aACzC,iBACE,IAAI,EAAC,QAAQ,gBACF,SAAS,EACpB,KAAK,EAAC,SAAS,EACf,QAAQ,EAAE,IAAI,IAAI,CAAC,GAAG,MAAM,EAC5B,OAAO,EAAE,GAAG,EAAE;4BACZ,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;wBACrB,CAAC,kBAGM,EACT,iBACE,IAAI,EAAC,QAAQ,gBACF,UAAU,EACrB,KAAK,EAAC,UAAU,EAChB,QAAQ,EAAE,IAAI,IAAI,IAAI,GAAG,MAAM,EAC/B,OAAO,EAAE,GAAG,EAAE;4BACZ,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;wBACtB,CAAC,uBAGM,EACT,iBACE,IAAI,EAAC,QAAQ,gBACF,mBAAmB,EAC9B,KAAK,EAAC,mBAAmB,EACzB,OAAO,EAAE,GAAG,EAAE;4BACZ,KAAK,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;wBACvC,CAAC,YAED,cAAK,OAAO,EAAC,WAAW,iBAAa,MAAM,YACzC,eAAM,CAAC,EAAC,wCAAwC,GAAG,GAC/C,GACC,IACL,EACN,eAAK,SAAS,EAAC,6BAA6B,aACzC,KAAK,EACN,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,iCAAiC,gBAC/B,KAAK,EACjB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,KAAK,YAEb,UAAU,GACJ,IACL,IACF,CACP,CAAC;AACJ,CAAC","sourcesContent":["import { ControlButton, Controls, useReactFlow } from \"@xyflow/react\";\nimport type { ReactNode } from \"react\";\n\nexport interface SpagbolControlZoomProps {\n zoom: number;\n className?: string;\n onReset?(): void;\n extra?: ReactNode;\n layout?: \"flow\" | \"corner\";\n}\n/** Zoom UI reusable with either Spagbol's controller or a host-owned flow. */\nexport function SpagbolControlZoom({\n zoom,\n className,\n onReset,\n extra,\n layout = \"flow\",\n}: SpagbolControlZoomProps) {\n const flow = useReactFlow();\n const percentage = `${Math.round(zoom * 100)}%`;\n const reset = () => {\n if (onReset) onReset();\n else void flow.zoomTo(1);\n };\n const label = `Reset zoom to 100% (currently ${percentage})`;\n if (layout === \"flow\")\n return (\n <Controls\n showInteractive={false}\n className={`spagbol-control-zoom ${className ?? \"\"}`}\n >\n <ControlButton\n className=\"react-flow__controls-zoom-percentage\"\n type=\"button\"\n onClick={reset}\n aria-label={label}\n title={label}\n >\n {percentage}\n </ControlButton>\n {extra}\n </Controls>\n );\n return (\n <div\n className={`spagbol-control-zoom spagbol-control-zoom-corner nodrag nopan ${className ?? \"\"}`}\n role=\"group\"\n aria-label=\"Canvas zoom controls\"\n onPointerDown={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n >\n <div className=\"spagbol-control-zoom-stack\">\n <button\n type=\"button\"\n aria-label=\"Zoom in\"\n title=\"Zoom in\"\n disabled={zoom >= 3 - 0.0001}\n onClick={() => {\n void flow.zoomIn();\n }}\n >\n +\n </button>\n <button\n type=\"button\"\n aria-label=\"Zoom out\"\n title=\"Zoom out\"\n disabled={zoom <= 0.05 + 0.0001}\n onClick={() => {\n void flow.zoomOut();\n }}\n >\n −\n </button>\n <button\n type=\"button\"\n aria-label=\"Fit graph to view\"\n title=\"Fit graph to view\"\n onClick={() => {\n void flow.fitView({ padding: 0.18 });\n }}\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M2 6V2h4M10 2h4v4M14 10v4h-4M6 14H2v-4\" />\n </svg>\n </button>\n </div>\n <div className=\"spagbol-control-zoom-extras\">\n {extra}\n <button\n type=\"button\"\n className=\"spagbol-control-zoom-percentage\"\n aria-label={label}\n title={label}\n onClick={reset}\n >\n {percentage}\n </button>\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Position } from "@xyflow/react";
|
|
2
|
+
import { type CSSProperties, type Ref, type ReactNode } from "react";
|
|
3
|
+
import { type GraphNode, type PortDefinition } from "../core/index.js";
|
|
4
|
+
import { type SpagbolNodeViewProps } from "./context.js";
|
|
5
|
+
export interface SpagbolNodePortProps {
|
|
6
|
+
port: PortDefinition;
|
|
7
|
+
connectable?: boolean;
|
|
8
|
+
style?: CSSProperties;
|
|
9
|
+
position?: Position;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Also usable in a completely custom node renderer. */
|
|
13
|
+
export declare function SpagbolNodePort({ port, connectable, style, position, className }: SpagbolNodePortProps): import("react").JSX.Element;
|
|
14
|
+
export interface SpagbolNodeFrameProps extends SpagbolNodeViewProps {
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
overlay?: ReactNode;
|
|
17
|
+
rootRef?: Ref<HTMLDivElement>;
|
|
18
|
+
title?: ReactNode;
|
|
19
|
+
className?: string;
|
|
20
|
+
hidePorts?: boolean;
|
|
21
|
+
headerOutput?: boolean;
|
|
22
|
+
portVisible?(port: PortDefinition): boolean;
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
/** Domain widgets inside a shared port row. The canvas owns handles and gestures. */
|
|
25
|
+
renderPortContent?(port: PortDefinition): ReactNode;
|
|
26
|
+
portExtras?: ReactNode;
|
|
27
|
+
portsHeader?: ReactNode;
|
|
28
|
+
forceCompact?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function SpagbolNodeFrame(props: SpagbolNodeFrameProps): import("react").JSX.Element;
|
|
31
|
+
export declare function useNodeViewProps(node: GraphNode, selected?: boolean, projected?: boolean, groupId?: string): SpagbolNodeViewProps;
|
|
32
|
+
export declare function SpagbolNodePanel({ group }: {
|
|
33
|
+
group: GraphNode;
|
|
34
|
+
}): import("react").JSX.Element | null;
|
|
35
|
+
//# sourceMappingURL=SpagbolNodeFrame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolNodeFrame.d.ts","sourceRoot":"","sources":["../../src/react/SpagbolNodeFrame.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,QAAQ,EAIT,MAAM,eAAe,CAAC;AACvB,OAAO,EAAgF,KAAK,aAAa,EAAE,KAAK,GAAG,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACnJ,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AAKtB,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AACD,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,WAAkB,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,oBAAoB,+BAyB7G;AACD,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IACjE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC;IAC5C,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,qFAAqF;IACrF,iBAAiB,CAAC,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,CAAC;IACpD,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AACD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,+BAwL5D;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,EACf,QAAQ,UAAQ,EAChB,SAAS,UAAQ,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,oBAAoB,CAmCtB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,sCAW/D"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { graphPortStates, emptyPortState } from "./portState.js";
|
|
3
|
+
import { SpagbolNodeGroupPortAdd } from "./SpagbolNodeGroupPortAdd.js";
|
|
4
|
+
import { SpagbolNodeGroupPort } from "./SpagbolNodeGroupPort.js";
|
|
5
|
+
import { Handle, Position, NodeResizer, useUpdateNodeInternals, useNodeId, } from "@xyflow/react";
|
|
6
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { portsFor, graphAt, isGroupBoundary, spagbolGroupKinds, spagbolNewPortId, groupPanelLayout, } from "../core/index.js";
|
|
8
|
+
import { GraphContext, useEditorSelector, useSpagbol, } from "./context.js";
|
|
9
|
+
import { SpagbolControlNumber } from "./SpagbolControlNumber.js";
|
|
10
|
+
import { SpagbolNodeFrameTypePicker } from './SpagbolNodeFrameTypePicker.js';
|
|
11
|
+
import { nodeFlowId, sourceHandle, targetHandle } from "./geometry.js";
|
|
12
|
+
/** Also usable in a completely custom node renderer. */
|
|
13
|
+
export function SpagbolNodePort({ port, connectable = true, style, position, className }) {
|
|
14
|
+
const { editor } = useSpagbol();
|
|
15
|
+
const selection = useEditorSelector(editor, snapshot => snapshot.selection);
|
|
16
|
+
const mode = useEditorSelector(editor, snapshot => snapshot.mode);
|
|
17
|
+
const flowId = useNodeId();
|
|
18
|
+
const selectedLink = mode === 'edit' && selection.linkIds.length === 1
|
|
19
|
+
? editor.graph.links.find(link => link.id === selection.linkIds[0]) : undefined;
|
|
20
|
+
const nodeId = flowId?.startsWith('node:') ? flowId.slice(5) : undefined;
|
|
21
|
+
const reconnecting = selectedLink && (port.direction === 'output'
|
|
22
|
+
? selectedLink.source.nodeId === nodeId && selectedLink.source.portId === port.id
|
|
23
|
+
: selectedLink.target.type === 'port' && selectedLink.target.nodeId === nodeId && selectedLink.target.portId === port.id);
|
|
24
|
+
const direction = port.direction;
|
|
25
|
+
return (_jsx(Handle, { id: direction === "input" ? targetHandle(port.id) : sourceHandle(port.id), type: direction === "input" ? "target" : "source", position: position ?? (direction === "input" ? Position.Left : Position.Right), className: `spagbol-node-port${reconnecting ? ' spagbol-node-port-selected-link' : ''} ${className ?? ""}`, isConnectable: editor.editing && port.connectable !== false && connectable, style: style, "aria-label": `${port.label} ${direction}` }));
|
|
26
|
+
}
|
|
27
|
+
export function SpagbolNodeFrame(props) {
|
|
28
|
+
const { node, selected, projected, children } = props;
|
|
29
|
+
const { editor, views, features, connecting, diagnostics } = useSpagbol();
|
|
30
|
+
const [pointerOver, setPointerOver] = useState(false);
|
|
31
|
+
const updateInternals = useUpdateNodeInternals();
|
|
32
|
+
const flowId = useNodeId();
|
|
33
|
+
const selection = useEditorSelector(editor, snapshot => snapshot.selection);
|
|
34
|
+
const mode = useEditorSelector(editor, snapshot => snapshot.mode);
|
|
35
|
+
const path = useEditorSelector(editor, snapshot => snapshot.path);
|
|
36
|
+
const boundary = isGroupBoundary(node);
|
|
37
|
+
const reorder = useRef(null);
|
|
38
|
+
const ports = portsFor(node, editor.nodes);
|
|
39
|
+
const inputPorts = ports.filter((port) => port.direction === "input" && props.portVisible?.(port) !== false);
|
|
40
|
+
const outputPorts = ports.filter((port) => port.direction === "output" && props.portVisible?.(port) !== false);
|
|
41
|
+
const compact = !projected && views[node.kind]?.compact !== false && features.compact && (props.forceCompact || node.compact) && !(selected && selection.nodeIds.length === 1) && !(connecting && pointerOver);
|
|
42
|
+
const headerInput = !boundary ? inputPorts.find(port => port.label === 'signal') : undefined;
|
|
43
|
+
const headerOutput = !boundary && props.headerOutput !== false ? outputPorts.find(port => port.label === 'signal') ?? (outputPorts.length === 1 ? outputPorts[0] : undefined) : undefined;
|
|
44
|
+
const portState = useEditorSelector(editor, snapshot => graphPortStates(graphAt(snapshot.document, snapshot.path)).get(node.id) ?? emptyPortState);
|
|
45
|
+
const connectedInputs = portState.inputs;
|
|
46
|
+
const connectedOutputs = portState.outputs;
|
|
47
|
+
const visibleInputs = inputPorts.filter(port => port !== headerInput && (!compact || connectedInputs.has(port.id)));
|
|
48
|
+
const visibleOutputs = outputPorts.filter(port => port !== headerOutput && (!compact || connectedOutputs.has(port.id)));
|
|
49
|
+
const definition = editor.nodes.get(node.kind);
|
|
50
|
+
const view = views[node.kind];
|
|
51
|
+
const newPortDirection = boundary ? (node.kind === spagbolGroupKinds.inputs ? "output" : "input") : definition?.editablePorts;
|
|
52
|
+
const newPortLabel = definition?.newPortLabel ?? (node.kind === spagbolGroupKinds.inputs ? "new input" : "new output");
|
|
53
|
+
const errors = diagnostics.filter((item) => item.nodeId === node.id);
|
|
54
|
+
const portKey = ports
|
|
55
|
+
.map((port) => `${port.direction}:${port.id}:${port.label}`)
|
|
56
|
+
.join("|");
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (projected || !flowId)
|
|
59
|
+
return;
|
|
60
|
+
const frame = requestAnimationFrame(() => updateInternals(flowId));
|
|
61
|
+
return () => cancelAnimationFrame(frame);
|
|
62
|
+
}, [flowId, portKey, compact, projected, updateInternals]);
|
|
63
|
+
const setInput = portState.replaced;
|
|
64
|
+
function movePort(event) {
|
|
65
|
+
const drag = reorder.current;
|
|
66
|
+
if (!drag || drag.pointer !== event.pointerId)
|
|
67
|
+
return;
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
const rows = Array.from(event.currentTarget.querySelectorAll('[data-group-port]'));
|
|
71
|
+
const target = rows.findIndex(row => event.clientY < row.getBoundingClientRect().bottom);
|
|
72
|
+
editor.moveBoundaryPort(node.id, drag.portId, target < 0 ? rows.length - 1 : target);
|
|
73
|
+
}
|
|
74
|
+
function endPort(event, cancel = false) {
|
|
75
|
+
const drag = reorder.current;
|
|
76
|
+
if (!drag || drag.pointer !== event.pointerId)
|
|
77
|
+
return;
|
|
78
|
+
event.stopPropagation();
|
|
79
|
+
reorder.current = null;
|
|
80
|
+
if (drag.element.hasPointerCapture(event.pointerId))
|
|
81
|
+
drag.element.releasePointerCapture(event.pointerId);
|
|
82
|
+
if (cancel)
|
|
83
|
+
editor.cancelGesture();
|
|
84
|
+
else
|
|
85
|
+
editor.commitGesture();
|
|
86
|
+
}
|
|
87
|
+
function row(port, index) {
|
|
88
|
+
const input = port.direction === "input";
|
|
89
|
+
return (_jsxs("div", { className: `spagbol-node-port-row spagbol-node-port-row-${port.direction}`, "data-group-port": boundary ? port.id : undefined, style: { gridColumn: boundary || input || inputPorts.length === 0 ? 1 : 2, gridRow: index + 1 + (props.portsHeader ? 1 : 0) }, children: [!projected && !props.hidePorts && _jsx(SpagbolNodePort, { port: port }), props.renderPortContent ? props.renderPortContent(port) : _jsxs(_Fragment, { children: [boundary && !projected ? _jsx(SpagbolNodeGroupPort, { name: port.label, editable: editor.editing && !props.readOnly, draggable: editor.editing && !props.readOnly, selected: selection.port?.nodeId === node.id && selection.port.portId === port.id, onChange: label => { try {
|
|
90
|
+
editor.renameBoundaryPort(node.id, port.id, label);
|
|
91
|
+
}
|
|
92
|
+
catch { /* Keep the valid label. */ } }, onPointerDown: event => {
|
|
93
|
+
if (!editor.editing || props.readOnly || event.button !== 0)
|
|
94
|
+
return;
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
event.stopPropagation();
|
|
97
|
+
editor.select({ nodeIds: [node.id], port: { nodeId: node.id, portId: port.id, direction: port.direction } });
|
|
98
|
+
editor.beginGesture('Reorder group ports');
|
|
99
|
+
reorder.current = { pointer: event.pointerId, portId: port.id, element: event.currentTarget };
|
|
100
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
101
|
+
} }) : _jsx("span", { className: "spagbol-node-port-name", title: port.label, children: port.label }), ((input && node.kind !== spagbolGroupKinds.outputs) || node.kind === spagbolGroupKinds.inputs) && port.valueEditor !== false && (_jsx(SpagbolControlNumber, { value: node.values[port.id] ?? port.defaultValue ?? 0, onChange: (value) => props.setValue(port.id, value), label: `${node.label ?? definition?.label ?? node.kind}: ${port.label}`, min: port.min, max: port.max, step: port.step, integer: port.integer, disabled: props.readOnly, replaced: setInput.has(port.id), onGestureStart: () => props.beginGesture(`Change ${port.label}`), onGestureEnd: (cancel) => props.endGesture(cancel) }))] })] }, `${port.direction}:${port.id}`));
|
|
102
|
+
}
|
|
103
|
+
return (_jsxs("div", { onPointerEnter: () => setPointerOver(true), onPointerLeave: () => setPointerOver(false), ref: props.rootRef, style: { ...props.style, ...(node.scale ? { zoom: node.scale, width: view?.autoWidth ? props.style?.width ?? "max-content" : `${100 / node.scale}%`, height: `${100 / node.scale}%` } : view?.autoWidth ? { width: props.style?.width ?? "max-content" } : {}) }, onPointerMove: movePort, onPointerUp: event => endPort(event), onPointerCancel: event => endPort(event, true), className: `spagbol-node-frame${selected ? " spagbol-node-frame-selected" : ""}${node.enabled === false ? " spagbol-node-frame-disabled" : ""}${compact ? " spagbol-node-frame-compact" : ""}${props.forceCompact ? " spagbol-node-frame-auto-compact" : ""}${projected ? " spagbol-node-frame-projected nodrag nopan" : ""} ${props.className ?? ""}`, children: [!projected && view?.resizable && editor.editing && (_jsx(NodeResizer, { isVisible: selected && !compact, minWidth: view.minWidth ?? 180, minHeight: view.minHeight ?? 80, onResizeStart: () => props.beginGesture("Resize node"), onResize: (_, size) => editor.updateNode(node.id, {
|
|
104
|
+
size: { width: size.width / (node.scale ?? 1), height: size.height / (node.scale ?? 1) },
|
|
105
|
+
position: { x: size.x, y: size.y },
|
|
106
|
+
}, "layout"), onResizeEnd: () => props.endGesture() })), _jsxs("div", { style: { position: "relative" }, className: "spagbol-node-frame-header spagbol-canvas-drag-handle", onDoubleClickCapture: event => { if (projected && props.resetOverride) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
props.resetOverride();
|
|
110
|
+
} }, children: [!projected && !props.hidePorts && headerInput && _jsx(SpagbolNodePort, { port: headerInput, style: { top: "50%" } }), !projected && !props.hidePorts && headerOutput && _jsx(SpagbolNodePort, { port: headerOutput, style: { top: "50%" } }), _jsx("span", { className: "spagbol-node-frame-title", children: props.title ?? _jsx(SpagbolNodeFrameTypePicker, { node: node, compact: !!compact, selected: selected, disabled: projected || props.readOnly || !editor.editing }) }), !projected && _jsx("button", { className: "spagbol-node-frame-enabled", type: "button", "aria-label": node.enabled === false ? 'Enable node' : 'Disable node', title: node.enabled === false ? 'Enable node' : 'Disable node', "aria-pressed": node.enabled !== false, disabled: !editor.editing || props.readOnly, onPointerDown: event => event.stopPropagation(), onDoubleClick: event => event.stopPropagation(), onClick: event => { event.preventDefault(); event.stopPropagation(); editor.updateNode(node.id, { enabled: node.enabled === false }, 'structure'); }, children: _jsx("span", { className: "spagbol-node-frame-enabled-icon", "aria-hidden": "true" }) }), !projected && features.compact && !props.forceCompact && view?.compact !== false && (_jsx("button", { className: "spagbol-node-frame-header-button nodrag nopan", "aria-label": node.compact ? "Expand ports" : "Compact ports", title: node.compact ? "Expand ports" : "Compact ports", disabled: !editor.editing, onClick: (event) => {
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
editor.updateNode(node.id, { compact: !node.compact }, "layout");
|
|
113
|
+
}, children: node.compact ? "+" : "−" })), !projected && node.graph && features.nestedGraphs && (_jsx("button", { className: "spagbol-node-frame-header-button nodrag nopan", "aria-label": "Enter nested graph", title: "Enter nested graph", onClick: (event) => {
|
|
114
|
+
event.stopPropagation();
|
|
115
|
+
editor.enter(node.id);
|
|
116
|
+
}, children: "\u21B3" }))] }), _jsxs(_Fragment, { children: [children && (_jsx("div", { className: "spagbol-node-frame-body nodrag nopan", onPointerDown: (event) => event.stopPropagation(), children: children })), !props.hidePorts && (visibleInputs.length > 0 || visibleOutputs.length > 0 || newPortDirection) && (_jsxs("div", { style: inputPorts.length === 0 || outputPorts.length === 0 ? { gridTemplateColumns: "minmax(0, 1fr)" } : undefined, className: `spagbol-node-frame-ports${boundary ? " spagbol-node-frame-ports-boundary" : ""}`, children: [props.portsHeader, visibleInputs.map(row), visibleOutputs.map(row), newPortDirection && !projected && editor.editing && _jsxs("div", { style: { gridColumn: newPortDirection === "input" || inputPorts.length === 0 ? 1 : 2, gridRow: ports.length + 1 + (props.portsHeader ? 1 : 0), position: "relative" }, children: [_jsx(SpagbolNodePort, { port: { id: spagbolNewPortId, label: newPortLabel, direction: newPortDirection }, style: { top: "50%" } }), _jsx(SpagbolNodeGroupPortAdd, { label: newPortLabel, onAdd: label => { try {
|
|
117
|
+
editor.addBoundaryPort(node.id, label);
|
|
118
|
+
}
|
|
119
|
+
catch { /* Keep the valid boundary. */ } } })] })] })), !compact && props.portExtras, errors.length > 0 && (_jsx("div", { className: "spagbol-node-frame-diagnostics", role: "status", title: errors.map((error) => error.message).join("\n"), children: errors[0]?.message }))] }), props.overlay] }));
|
|
120
|
+
}
|
|
121
|
+
export function useNodeViewProps(node, selected = false, projected = false, groupId) {
|
|
122
|
+
const { editor, projection } = useSpagbol();
|
|
123
|
+
const owner = projection?.groupId ?? groupId;
|
|
124
|
+
const controlPath = projection?.path ?? [node.id];
|
|
125
|
+
const selection = useEditorSelector(editor, snapshot => snapshot.selection);
|
|
126
|
+
const mode = useEditorSelector(editor, snapshot => snapshot.mode);
|
|
127
|
+
const path = useEditorSelector(editor, snapshot => snapshot.path);
|
|
128
|
+
return useMemo(() => ({
|
|
129
|
+
node,
|
|
130
|
+
selected,
|
|
131
|
+
projected,
|
|
132
|
+
path: projection ? [...path, projection.groupId, ...projection.path.slice(0, -1)] : path,
|
|
133
|
+
resetOverride: owner ? () => editor.resetOverride(owner, controlPath) : undefined,
|
|
134
|
+
readOnly: mode === "readonly",
|
|
135
|
+
setValue: (portId, value) => owner
|
|
136
|
+
? editor.setOverride(owner, controlPath, portId, value)
|
|
137
|
+
: editor.setValue(node.id, portId, value),
|
|
138
|
+
patchData: (data, structural = false) => {
|
|
139
|
+
if (projected) {
|
|
140
|
+
if (!structural && owner)
|
|
141
|
+
editor.setDataOverride(owner, controlPath, data);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
editor.updateNode(node.id, { data: { ...node.data, ...data } }, structural ? "structure" : "values");
|
|
145
|
+
},
|
|
146
|
+
beginGesture: (label = "Edit control") => {
|
|
147
|
+
if (!editor.getSnapshot().gesture)
|
|
148
|
+
editor.beginGesture(label);
|
|
149
|
+
},
|
|
150
|
+
endGesture: (cancel = false) => cancel ? editor.cancelGesture() : editor.commitGesture(),
|
|
151
|
+
}), [editor, owner, projection, node, projected, selected, mode, path]);
|
|
152
|
+
}
|
|
153
|
+
export function SpagbolNodePanel({ group }) {
|
|
154
|
+
const context = useSpagbol();
|
|
155
|
+
const layout = groupPanelLayout(group);
|
|
156
|
+
if (!layout)
|
|
157
|
+
return null;
|
|
158
|
+
return _jsx("div", { className: "spagbol-node-panel nodrag nopan", style: { position: 'relative', width: layout.size.width, height: layout.size.height }, "aria-label": "Instance controls", onPointerDown: event => event.stopPropagation(), children: layout.nodes.map(node => _jsx(GraphContext.Provider, { value: { ...context, projection: { groupId: context.projection?.groupId ?? group.id, path: [...(context.projection?.path ?? []), node.id] } }, children: _jsx("div", { className: "spagbol-node-panel-item", style: { position: 'absolute', left: node.position.x - layout.position.x, top: node.position.y - layout.position.y, width: context.views[node.kind]?.autoWidth ? "max-content" : (node.size?.width ?? 220) * (node.scale ?? 1) }, children: _jsx(SpagbolNodePanelItem, { node: node }) }) }, node.id)) });
|
|
159
|
+
}
|
|
160
|
+
function SpagbolNodePanelItem({ node }) {
|
|
161
|
+
const { views } = useSpagbol();
|
|
162
|
+
const props = useNodeViewProps(node, false, true);
|
|
163
|
+
const Renderer = views[node.kind]?.projected ?? views[node.kind]?.render;
|
|
164
|
+
if (Renderer)
|
|
165
|
+
return _jsx(Renderer, { ...props });
|
|
166
|
+
const Body = views[node.kind]?.body;
|
|
167
|
+
return _jsx(SpagbolNodeFrame, { ...props, hidePorts: !!Body, children: Body ? _jsx(Body, { ...props }) : null });
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=SpagbolNodeFrame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolNodeFrame.js","sourceRoot":"","sources":["../../src/react/SpagbolNodeFrame.tsx"],"names":[],"mappings":"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","sourcesContent":["import { graphPortStates, emptyPortState } from \"./portState.js\";\nimport { SpagbolNodeGroupPortAdd } from \"./SpagbolNodeGroupPortAdd.js\";\nimport { SpagbolNodeGroupPort } from \"./SpagbolNodeGroupPort.js\";\nimport {\n Handle,\n Position,\n NodeResizer,\n useUpdateNodeInternals,\n useNodeId,\n} from \"@xyflow/react\";\nimport { useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent, type CSSProperties, type Ref, type ReactNode } from \"react\";\nimport {\n portsFor,\n graphAt,\n isGroupBoundary,\n spagbolGroupKinds,\n spagbolNewPortId,\n groupPanelLayout,\n type GraphNode,\n type PortDefinition,\n} from \"../core/index.js\";\nimport {\n GraphContext,\n useEditorSelector,\n useSpagbol,\n type SpagbolNodeViewProps,\n} from \"./context.js\";\nimport { SpagbolControlNumber } from \"./SpagbolControlNumber.js\";\nimport { SpagbolNodeFrameTypePicker } from './SpagbolNodeFrameTypePicker.js';\nimport { nodeFlowId, sourceHandle, targetHandle } from \"./geometry.js\";\n\nexport interface SpagbolNodePortProps {\n port: PortDefinition;\n connectable?: boolean;\n style?: CSSProperties;\n position?: Position;\n className?: string;\n}\n/** Also usable in a completely custom node renderer. */\nexport function SpagbolNodePort({ port, connectable = true, style, position, className }: SpagbolNodePortProps) {\n const { editor } = useSpagbol();\n const selection = useEditorSelector(editor, snapshot => snapshot.selection);\n const mode = useEditorSelector(editor, snapshot => snapshot.mode);\n const flowId = useNodeId();\n const selectedLink = mode === 'edit' && selection.linkIds.length === 1\n ? editor.graph.links.find(link => link.id === selection.linkIds[0]) : undefined;\n const nodeId = flowId?.startsWith('node:') ? flowId.slice(5) : undefined;\n const reconnecting = selectedLink && (port.direction === 'output'\n ? selectedLink.source.nodeId === nodeId && selectedLink.source.portId === port.id\n : selectedLink.target.type === 'port' && selectedLink.target.nodeId === nodeId && selectedLink.target.portId === port.id);\n const direction = port.direction;\n return (\n <Handle\n id={direction === \"input\" ? targetHandle(port.id) : sourceHandle(port.id)}\n type={direction === \"input\" ? \"target\" : \"source\"}\n position={position ?? (direction === \"input\" ? Position.Left : Position.Right)}\n className={`spagbol-node-port${reconnecting ? ' spagbol-node-port-selected-link' : ''} ${className ?? \"\"}`}\n isConnectable={\n editor.editing && port.connectable !== false && connectable\n }\n style={style}\n aria-label={`${port.label} ${direction}`}\n />\n );\n}\nexport interface SpagbolNodeFrameProps extends SpagbolNodeViewProps {\n children?: ReactNode;\n overlay?: ReactNode;\n rootRef?: Ref<HTMLDivElement>;\n title?: ReactNode;\n className?: string;\n hidePorts?: boolean;\n headerOutput?: boolean;\n portVisible?(port: PortDefinition): boolean;\n style?: CSSProperties;\n /** Domain widgets inside a shared port row. The canvas owns handles and gestures. */\n renderPortContent?(port: PortDefinition): ReactNode;\n portExtras?: ReactNode;\n portsHeader?: ReactNode;\n forceCompact?: boolean;\n}\nexport function SpagbolNodeFrame(props: SpagbolNodeFrameProps) {\n const { node, selected, projected, children } = props;\n const { editor, views, features, connecting, diagnostics } = useSpagbol();\n const [pointerOver, setPointerOver] = useState(false);\n const updateInternals = useUpdateNodeInternals();\n const flowId = useNodeId();\n const selection = useEditorSelector(editor, snapshot => snapshot.selection);\n const mode = useEditorSelector(editor, snapshot => snapshot.mode);\n const path = useEditorSelector(editor, snapshot => snapshot.path);\n const boundary = isGroupBoundary(node);\n const reorder = useRef<{ pointer: number; portId: string; element: HTMLElement } | null>(null);\n const ports = portsFor(node, editor.nodes);\n const inputPorts = ports.filter((port) => port.direction === \"input\" && props.portVisible?.(port) !== false);\n const outputPorts = ports.filter((port) => port.direction === \"output\" && props.portVisible?.(port) !== false);\n const compact = !projected && views[node.kind]?.compact !== false && features.compact && (props.forceCompact || node.compact) && !(selected && selection.nodeIds.length === 1) && !(connecting && pointerOver);\n const headerInput = !boundary ? inputPorts.find(port => port.label === 'signal') : undefined;\n const headerOutput = !boundary && props.headerOutput !== false ? outputPorts.find(port => port.label === 'signal') ?? (outputPorts.length === 1 ? outputPorts[0] : undefined) : undefined;\n const portState = useEditorSelector(editor, snapshot => graphPortStates(graphAt(snapshot.document, snapshot.path)).get(node.id) ?? emptyPortState);\n const connectedInputs = portState.inputs;\n const connectedOutputs = portState.outputs;\n const visibleInputs = inputPorts.filter(port => port !== headerInput && (!compact || connectedInputs.has(port.id)));\n const visibleOutputs = outputPorts.filter(port => port !== headerOutput && (!compact || connectedOutputs.has(port.id)));\n const definition = editor.nodes.get(node.kind);\n const view = views[node.kind];\n const newPortDirection = boundary ? (node.kind === spagbolGroupKinds.inputs ? \"output\" : \"input\") : definition?.editablePorts;\n const newPortLabel = definition?.newPortLabel ?? (node.kind === spagbolGroupKinds.inputs ? \"new input\" : \"new output\");\n const errors = diagnostics.filter((item) => item.nodeId === node.id);\n const portKey = ports\n .map((port) => `${port.direction}:${port.id}:${port.label}`)\n .join(\"|\");\n useEffect(() => {\n if (projected || !flowId) return;\n const frame = requestAnimationFrame(() => updateInternals(flowId));\n return () => cancelAnimationFrame(frame);\n }, [flowId, portKey, compact, projected, updateInternals]);\n const setInput = portState.replaced;\n function movePort(event: ReactPointerEvent<HTMLDivElement>) {\n const drag = reorder.current;\n if (!drag || drag.pointer !== event.pointerId) return;\n event.preventDefault(); event.stopPropagation();\n const rows = Array.from(event.currentTarget.querySelectorAll<HTMLElement>('[data-group-port]'));\n const target = rows.findIndex(row => event.clientY < row.getBoundingClientRect().bottom);\n editor.moveBoundaryPort(node.id, drag.portId, target < 0 ? rows.length - 1 : target);\n }\n function endPort(event: ReactPointerEvent<HTMLDivElement>, cancel = false) {\n const drag = reorder.current;\n if (!drag || drag.pointer !== event.pointerId) return;\n event.stopPropagation(); reorder.current = null;\n if (drag.element.hasPointerCapture(event.pointerId)) drag.element.releasePointerCapture(event.pointerId);\n if (cancel) editor.cancelGesture(); else editor.commitGesture();\n }\n function row(port: PortDefinition, index: number) {\n const input = port.direction === \"input\";\n return (\n <div\n className={`spagbol-node-port-row spagbol-node-port-row-${port.direction}`}\n key={`${port.direction}:${port.id}`}\n data-group-port={boundary ? port.id : undefined}\n style={{ gridColumn: boundary || input || inputPorts.length === 0 ? 1 : 2, gridRow: index + 1 + (props.portsHeader ? 1 : 0) }}\n >\n {!projected && !props.hidePorts && <SpagbolNodePort port={port} />}\n {props.renderPortContent ? props.renderPortContent(port) : <>{boundary && !projected ? <SpagbolNodeGroupPort name={port.label} editable={editor.editing && !props.readOnly} draggable={editor.editing && !props.readOnly} selected={selection.port?.nodeId === node.id && selection.port.portId === port.id} onChange={label => { try { editor.renameBoundaryPort(node.id, port.id, label); } catch { /* Keep the valid label. */ } }} onPointerDown={event => {\n if (!editor.editing || props.readOnly || event.button !== 0) return;\n event.preventDefault(); event.stopPropagation();\n editor.select({ nodeIds: [node.id], port: { nodeId: node.id, portId: port.id, direction: port.direction } });\n editor.beginGesture('Reorder group ports');\n reorder.current = { pointer: event.pointerId, portId: port.id, element: event.currentTarget };\n event.currentTarget.setPointerCapture(event.pointerId);\n }} /> : <span className=\"spagbol-node-port-name\" title={port.label}>{port.label}</span>}\n {((input && node.kind !== spagbolGroupKinds.outputs) || node.kind === spagbolGroupKinds.inputs) && port.valueEditor !== false && (\n <SpagbolControlNumber\n value={node.values[port.id] ?? port.defaultValue ?? 0}\n onChange={(value) => props.setValue(port.id, value)}\n label={`${node.label ?? definition?.label ?? node.kind}: ${port.label}`}\n min={port.min}\n max={port.max}\n step={port.step}\n integer={port.integer}\n disabled={props.readOnly}\n replaced={setInput.has(port.id)}\n onGestureStart={() => props.beginGesture(`Change ${port.label}`)}\n onGestureEnd={(cancel) => props.endGesture(cancel)}\n />\n )}</>}\n </div>\n );\n }\n return (\n <div\n onPointerEnter={() => setPointerOver(true)} onPointerLeave={() => setPointerOver(false)}\n ref={props.rootRef}\n style={{ ...props.style, ...(node.scale ? { zoom: node.scale, width: view?.autoWidth ? props.style?.width ?? \"max-content\" : `${100 / node.scale}%`, height: `${100 / node.scale}%` } : view?.autoWidth ? { width: props.style?.width ?? \"max-content\" } : {}) }}\n onPointerMove={movePort} onPointerUp={event => endPort(event)} onPointerCancel={event => endPort(event, true)}\n className={`spagbol-node-frame${selected ? \" spagbol-node-frame-selected\" : \"\"}${node.enabled === false ? \" spagbol-node-frame-disabled\" : \"\"}${compact ? \" spagbol-node-frame-compact\" : \"\"}${props.forceCompact ? \" spagbol-node-frame-auto-compact\" : \"\"}${projected ? \" spagbol-node-frame-projected nodrag nopan\" : \"\"} ${props.className ?? \"\"}`}\n >\n {!projected && view?.resizable && editor.editing && (\n <NodeResizer\n isVisible={selected && !compact}\n minWidth={view.minWidth ?? 180}\n minHeight={view.minHeight ?? 80}\n onResizeStart={() => props.beginGesture(\"Resize node\")}\n onResize={(_, size) =>\n editor.updateNode(\n node.id,\n {\n size: { width: size.width / (node.scale ?? 1), height: size.height / (node.scale ?? 1) },\n position: { x: size.x, y: size.y },\n },\n \"layout\",\n )\n }\n onResizeEnd={() => props.endGesture()}\n />\n )}\n <div\n style={{ position: \"relative\" }}\n className=\"spagbol-node-frame-header spagbol-canvas-drag-handle\"\n onDoubleClickCapture={event => { if (projected && props.resetOverride) { event.preventDefault(); event.stopPropagation(); props.resetOverride(); } }}\n >\n {!projected && !props.hidePorts && headerInput && <SpagbolNodePort port={headerInput} style={{ top: \"50%\" }} />}\n {!projected && !props.hidePorts && headerOutput && <SpagbolNodePort port={headerOutput} style={{ top: \"50%\" }} />}\n <span className=\"spagbol-node-frame-title\">\n {props.title ?? <SpagbolNodeFrameTypePicker node={node} compact={!!compact} selected={selected} disabled={projected || props.readOnly || !editor.editing} />}\n </span>\n {!projected && <button className=\"spagbol-node-frame-enabled\" type=\"button\" aria-label={node.enabled === false ? 'Enable node' : 'Disable node'} title={node.enabled === false ? 'Enable node' : 'Disable node'} aria-pressed={node.enabled !== false} disabled={!editor.editing || props.readOnly} onPointerDown={event => event.stopPropagation()} onDoubleClick={event => event.stopPropagation()} onClick={event => { event.preventDefault(); event.stopPropagation(); editor.updateNode(node.id, { enabled: node.enabled === false }, 'structure'); }}><span className=\"spagbol-node-frame-enabled-icon\" aria-hidden=\"true\" /></button>}\n {!projected && features.compact && !props.forceCompact && view?.compact !== false && (\n <button\n className=\"spagbol-node-frame-header-button nodrag nopan\"\n aria-label={node.compact ? \"Expand ports\" : \"Compact ports\"}\n title={node.compact ? \"Expand ports\" : \"Compact ports\"}\n disabled={!editor.editing}\n onClick={(event) => {\n event.stopPropagation();\n editor.updateNode(node.id, { compact: !node.compact }, \"layout\");\n }}\n >\n {node.compact ? \"+\" : \"−\"}\n </button>\n )}\n {!projected && node.graph && features.nestedGraphs && (\n <button\n className=\"spagbol-node-frame-header-button nodrag nopan\"\n aria-label=\"Enter nested graph\"\n title=\"Enter nested graph\"\n onClick={(event) => {\n event.stopPropagation();\n editor.enter(node.id);\n }}\n >\n ↳\n </button>\n )}\n </div>\n <>\n {children && (\n <div\n className=\"spagbol-node-frame-body nodrag nopan\"\n onPointerDown={(event) => event.stopPropagation()}\n >\n {children}\n </div>\n )}\n {!props.hidePorts && (visibleInputs.length > 0 || visibleOutputs.length > 0 || newPortDirection) && (\n <div style={inputPorts.length === 0 || outputPorts.length === 0 ? { gridTemplateColumns: \"minmax(0, 1fr)\" } : undefined} className={`spagbol-node-frame-ports${boundary ? \" spagbol-node-frame-ports-boundary\" : \"\"}`}>\n {props.portsHeader}\n {visibleInputs.map(row)}\n {visibleOutputs.map(row)}\n {newPortDirection && !projected && editor.editing && <div style={{ gridColumn: newPortDirection === \"input\" || inputPorts.length === 0 ? 1 : 2, gridRow: ports.length + 1 + (props.portsHeader ? 1 : 0), position: \"relative\" }}><SpagbolNodePort port={{ id: spagbolNewPortId, label: newPortLabel, direction: newPortDirection }} style={{ top: \"50%\" }} /><SpagbolNodeGroupPortAdd label={newPortLabel} onAdd={label => { try { editor.addBoundaryPort(node.id, label); } catch { /* Keep the valid boundary. */ } }} /></div>}\n </div>\n )}\n {!compact && props.portExtras}\n {errors.length > 0 && (\n <div\n className=\"spagbol-node-frame-diagnostics\"\n role=\"status\"\n title={errors.map((error) => error.message).join(\"\\n\")}\n >\n {errors[0]?.message}\n </div>\n )}\n </>\n {props.overlay}\n </div>\n );\n}\n\nexport function useNodeViewProps(\n node: GraphNode,\n selected = false,\n projected = false,\n groupId?: string,\n): SpagbolNodeViewProps {\n const { editor, projection } = useSpagbol();\n const owner = projection?.groupId ?? groupId;\n const controlPath = projection?.path ?? [node.id];\n const selection = useEditorSelector(editor, snapshot => snapshot.selection);\n const mode = useEditorSelector(editor, snapshot => snapshot.mode);\n const path = useEditorSelector(editor, snapshot => snapshot.path);\n return useMemo(\n () => ({\n node,\n selected,\n projected,\n path: projection ? [...path, projection.groupId, ...projection.path.slice(0, -1)] : path,\n resetOverride: owner ? () => editor.resetOverride(owner, controlPath) : undefined,\n readOnly: mode === \"readonly\",\n setValue: (portId: string, value: number) =>\n owner\n ? editor.setOverride(owner, controlPath, portId, value)\n : editor.setValue(node.id, portId, value),\n patchData: (data: Partial<GraphNode[\"data\"]>, structural = false) => {\n if (projected) { if (!structural && owner) editor.setDataOverride(owner, controlPath, data); return; }\n editor.updateNode(\n node.id,\n { data: { ...node.data, ...data } as GraphNode[\"data\"] },\n structural ? \"structure\" : \"values\",\n );\n },\n beginGesture: (label = \"Edit control\") => {\n if (!editor.getSnapshot().gesture) editor.beginGesture(label);\n },\n endGesture: (cancel = false) =>\n cancel ? editor.cancelGesture() : editor.commitGesture(),\n }),\n [editor, owner, projection, node, projected, selected, mode, path],\n );\n}\n\nexport function SpagbolNodePanel({ group }: { group: GraphNode }) {\n const context = useSpagbol();\n const layout = groupPanelLayout(group);\n if (!layout) return null;\n return <div className=\"spagbol-node-panel nodrag nopan\" style={{ position: 'relative', width: layout.size.width, height: layout.size.height }} aria-label=\"Instance controls\" onPointerDown={event => event.stopPropagation()}>\n {layout.nodes.map(node => <GraphContext.Provider key={node.id} value={{ ...context, projection: { groupId: context.projection?.groupId ?? group.id, path: [...(context.projection?.path ?? []), node.id] } }}>\n <div className=\"spagbol-node-panel-item\" style={{ position: 'absolute', left: node.position.x - layout.position.x, top: node.position.y - layout.position.y, width: context.views[node.kind]?.autoWidth ? \"max-content\" : (node.size?.width ?? 220) * (node.scale ?? 1) }}>\n <SpagbolNodePanelItem node={node} />\n </div>\n </GraphContext.Provider>)}\n </div>;\n}\nfunction SpagbolNodePanelItem({ node }: { node: GraphNode }) {\n const { views } = useSpagbol();\n const props = useNodeViewProps(node, false, true);\n const Renderer = views[node.kind]?.projected ?? views[node.kind]?.render;\n if (Renderer) return <Renderer {...props} />;\n const Body = views[node.kind]?.body;\n return <SpagbolNodeFrame {...props} hidePorts={!!Body}>{Body ? <Body {...props} /> : null}</SpagbolNodeFrame>;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GraphNode } from '../core/index.js';
|
|
2
|
+
/** Teia's inline type-editing interaction, backed by the host's node catalogue. */
|
|
3
|
+
export declare function SpagbolNodeFrameTypePicker({ node, compact, selected, disabled }: {
|
|
4
|
+
node: GraphNode;
|
|
5
|
+
compact: boolean;
|
|
6
|
+
selected: boolean;
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=SpagbolNodeFrameTypePicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolNodeFrameTypePicker.d.ts","sourceRoot":"","sources":["../../src/react/SpagbolNodeFrameTypePicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,mFAAmF;AACnF,wBAAgB,0BAA0B,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,+BA6C5J"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { useSpagbol } from './context.js';
|
|
4
|
+
/** Teia's inline type-editing interaction, backed by the host's node catalogue. */
|
|
5
|
+
export function SpagbolNodeFrameTypePicker({ node, compact, selected, disabled }) {
|
|
6
|
+
const { editor, nodeKinds, features, changeNodeKind } = useSpagbol();
|
|
7
|
+
const typeLabel = node.label ?? editor.nodes.get(node.kind)?.label ?? node.kind;
|
|
8
|
+
const label = compact && node.compactLabel?.trim() ? node.compactLabel : typeLabel;
|
|
9
|
+
const press = useRef({ time: 0, compact: false });
|
|
10
|
+
const [editingLabel, setEditingLabel] = useState(false);
|
|
11
|
+
const [open, setOpen] = useState(false);
|
|
12
|
+
const [query, setQuery] = useState(label);
|
|
13
|
+
const [highlight, setHighlight] = useState(0);
|
|
14
|
+
const input = useRef(null);
|
|
15
|
+
const menu = useRef(null);
|
|
16
|
+
const options = useMemo(() => [...editor.nodes.list().filter(item => !nodeKinds || nodeKinds.includes(item.kind)), ...(features.areas ? [{ kind: 'spagbol.area', label: 'Area' }] : [])].filter(item => `${item.kind} ${item.label}`.toLowerCase().includes(query.trim().toLowerCase())).sort((a, b) => Number(b.label.toLowerCase().startsWith(query.toLowerCase())) - Number(a.label.toLowerCase().startsWith(query.toLowerCase()))), [editor, query, nodeKinds, features.areas]);
|
|
17
|
+
useEffect(() => { if (!selected || disabled)
|
|
18
|
+
setOpen(false); }, [selected, disabled]);
|
|
19
|
+
useEffect(() => { if (open) {
|
|
20
|
+
input.current?.focus({ preventScroll: true });
|
|
21
|
+
input.current?.select();
|
|
22
|
+
} }, [open]);
|
|
23
|
+
useEffect(() => { menu.current?.querySelector('[aria-selected="true"]')?.scrollIntoView({ block: 'nearest' }); }, [highlight]);
|
|
24
|
+
function commitLabel() { editor.updateNode(node.id, { compactLabel: query.trim() }, "metadata"); setOpen(false); }
|
|
25
|
+
function choose(kind) { if (kind === 'spagbol.area')
|
|
26
|
+
editor.convertNodeToArea(node.id);
|
|
27
|
+
else if (changeNodeKind)
|
|
28
|
+
void changeNodeKind(node.id, kind);
|
|
29
|
+
else
|
|
30
|
+
editor.changeNodeKind(node.id, kind); setOpen(false); }
|
|
31
|
+
return _jsx("span", { className: "spagbol-node-frame-type-picker", children: !open ? _jsx("button", { type: "button", className: "spagbol-node-frame-type-picker-button", onPointerDown: () => { const now = performance.now(); if (now - press.current.time > 500)
|
|
32
|
+
press.current = { time: now, compact }; }, onDoubleClick: event => {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
event.stopPropagation();
|
|
35
|
+
if (disabled || !selected)
|
|
36
|
+
return;
|
|
37
|
+
const rename = press.current.compact;
|
|
38
|
+
setEditingLabel(rename);
|
|
39
|
+
setQuery(rename ? node.compactLabel ?? "" : typeLabel);
|
|
40
|
+
setHighlight(0);
|
|
41
|
+
setOpen(true);
|
|
42
|
+
}, children: label }) : _jsxs(_Fragment, { children: [_jsx("span", { className: "spagbol-node-frame-type-picker-placeholder", "aria-hidden": "true", children: label }), _jsxs("div", { className: "spagbol-node-frame-type-picker-editor nodrag nopan nowheel", onMouseDown: event => event.stopPropagation(), children: [_jsx("input", { ref: input, className: "spagbol-node-frame-type-picker-input", "aria-label": editingLabel ? "Node label" : "Node type", role: "combobox", "aria-expanded": "true", "aria-controls": `node-type-${node.id}`, "aria-autocomplete": "list", value: query, spellCheck: false, onChange: event => { setQuery(event.target.value); setHighlight(0); }, onBlur: () => editingLabel ? commitLabel() : setOpen(false), onPointerDown: event => event.stopPropagation(), onDoubleClick: event => event.stopPropagation(), onKeyDown: event => {
|
|
43
|
+
event.stopPropagation();
|
|
44
|
+
if (event.key === 'ArrowDown') {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
setHighlight(i => Math.min(i + 1, Math.max(0, options.length - 1)));
|
|
47
|
+
}
|
|
48
|
+
if (event.key === 'ArrowUp') {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
setHighlight(i => Math.max(0, i - 1));
|
|
51
|
+
}
|
|
52
|
+
if (event.key === 'Enter') {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
if (editingLabel) {
|
|
55
|
+
commitLabel();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const item = options[highlight] ?? options[0];
|
|
59
|
+
if (item)
|
|
60
|
+
choose(item.kind);
|
|
61
|
+
}
|
|
62
|
+
if (event.key === 'Escape') {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
setOpen(false);
|
|
65
|
+
}
|
|
66
|
+
} }), !editingLabel && _jsxs("div", { ref: menu, className: "spagbol-node-frame-type-picker-menu nowheel", id: `node-type-${node.id}`, role: "listbox", onMouseDown: event => { event.preventDefault(); event.stopPropagation(); }, onWheel: event => event.stopPropagation(), children: [options.map((item, index) => _jsx("button", { type: "button", role: "option", "aria-selected": index === highlight, className: index === highlight ? 'active' : '', onMouseDown: event => { event.preventDefault(); event.stopPropagation(); choose(item.kind); }, children: item.label }, item.kind)), !options.length && _jsx("div", { className: "spagbol-node-frame-type-picker-empty", children: "no match" })] })] })] }) });
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=SpagbolNodeFrameTypePicker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolNodeFrameTypePicker.js","sourceRoot":"","sources":["../../src/react/SpagbolNodeFrameTypePicker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { GraphNode } from '../core/index.js';\nimport { useSpagbol } from './context.js';\n\n/** Teia's inline type-editing interaction, backed by the host's node catalogue. */\nexport function SpagbolNodeFrameTypePicker({ node, compact, selected, disabled }: { node: GraphNode; compact: boolean; selected: boolean; disabled: boolean }) {\n const { editor, nodeKinds, features, changeNodeKind } = useSpagbol();\n const typeLabel = node.label ?? editor.nodes.get(node.kind)?.label ?? node.kind;\n const label = compact && node.compactLabel?.trim() ? node.compactLabel : typeLabel;\n const press = useRef({ time: 0, compact: false });\n const [editingLabel, setEditingLabel] = useState(false);\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(label);\n const [highlight, setHighlight] = useState(0);\n const input = useRef<HTMLInputElement>(null);\n const menu = useRef<HTMLDivElement>(null);\n const options = useMemo(() => [...editor.nodes.list().filter(item => !nodeKinds || nodeKinds.includes(item.kind)), ...(features.areas ? [{ kind: 'spagbol.area', label: 'Area' }] : [])].filter(item =>\n `${item.kind} ${item.label}`.toLowerCase().includes(query.trim().toLowerCase())\n ).sort((a, b) => Number(b.label.toLowerCase().startsWith(query.toLowerCase())) - Number(a.label.toLowerCase().startsWith(query.toLowerCase()))), [editor, query, nodeKinds, features.areas]);\n useEffect(() => { if (!selected || disabled) setOpen(false); }, [selected, disabled]);\n useEffect(() => { if (open) { input.current?.focus({ preventScroll: true }); input.current?.select(); } }, [open]);\n useEffect(() => { menu.current?.querySelector('[aria-selected=\"true\"]')?.scrollIntoView({ block: 'nearest' }); }, [highlight]);\n function commitLabel() { editor.updateNode(node.id, { compactLabel: query.trim() }, \"metadata\"); setOpen(false); }\n function choose(kind: string) { if (kind === 'spagbol.area') editor.convertNodeToArea(node.id); else if (changeNodeKind) void changeNodeKind(node.id, kind); else editor.changeNodeKind(node.id, kind); setOpen(false); }\n return <span className=\"spagbol-node-frame-type-picker\">\n {!open ? <button type=\"button\" className=\"spagbol-node-frame-type-picker-button\" onPointerDown={() => { const now = performance.now(); if (now - press.current.time > 500) press.current = { time: now, compact }; }} onDoubleClick={event => {\n event.preventDefault(); event.stopPropagation();\n if (disabled || !selected) return;\n const rename = press.current.compact; setEditingLabel(rename);\n setQuery(rename ? node.compactLabel ?? \"\" : typeLabel); setHighlight(0); setOpen(true);\n }}>{label}</button> : <>\n <span className=\"spagbol-node-frame-type-picker-placeholder\" aria-hidden=\"true\">{label}</span>\n <div className=\"spagbol-node-frame-type-picker-editor nodrag nopan nowheel\" onMouseDown={event => event.stopPropagation()}>\n <input ref={input} className=\"spagbol-node-frame-type-picker-input\" aria-label={editingLabel ? \"Node label\" : \"Node type\"} role=\"combobox\" aria-expanded=\"true\" aria-controls={`node-type-${node.id}`} aria-autocomplete=\"list\" value={query} spellCheck={false}\n onChange={event => { setQuery(event.target.value); setHighlight(0); }} onBlur={() => editingLabel ? commitLabel() : setOpen(false)}\n onPointerDown={event => event.stopPropagation()} onDoubleClick={event => event.stopPropagation()}\n onKeyDown={event => {\n event.stopPropagation();\n if (event.key === 'ArrowDown') { event.preventDefault(); setHighlight(i => Math.min(i + 1, Math.max(0, options.length - 1))); }\n if (event.key === 'ArrowUp') { event.preventDefault(); setHighlight(i => Math.max(0, i - 1)); }\n if (event.key === 'Enter') { event.preventDefault(); if (editingLabel) { commitLabel(); return; } const item = options[highlight] ?? options[0]; if (item) choose(item.kind); }\n if (event.key === 'Escape') { event.preventDefault(); setOpen(false); }\n }} />\n {!editingLabel && <div ref={menu} className=\"spagbol-node-frame-type-picker-menu nowheel\" id={`node-type-${node.id}`} role=\"listbox\" onMouseDown={event => { event.preventDefault(); event.stopPropagation(); }} onWheel={event => event.stopPropagation()}>\n {options.map((item, index) => <button key={item.kind} type=\"button\" role=\"option\" aria-selected={index === highlight} className={index === highlight ? 'active' : ''} onMouseDown={event => { event.preventDefault(); event.stopPropagation(); choose(item.kind); }}>{item.label}</button>)}\n {!options.length && <div className=\"spagbol-node-frame-type-picker-empty\">no match</div>}\n </div>}\n </div>\n </>}\n </span>;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type PointerEvent } from "react";
|
|
2
|
+
interface SpagbolNodeGroupPortProps {
|
|
3
|
+
name: string;
|
|
4
|
+
editable: boolean;
|
|
5
|
+
draggable?: boolean;
|
|
6
|
+
selected?: boolean;
|
|
7
|
+
preview?: boolean;
|
|
8
|
+
activeDragTarget?: boolean;
|
|
9
|
+
activeDragSource?: boolean;
|
|
10
|
+
onPointerDown?: (event: PointerEvent<HTMLSpanElement>) => void;
|
|
11
|
+
onChange: (nextName: string) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function SpagbolNodeGroupPort({ name, editable, draggable, selected, preview, activeDragTarget, activeDragSource, onPointerDown, onChange, }: SpagbolNodeGroupPortProps): import("react").JSX.Element;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=SpagbolNodeGroupPort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolNodeGroupPort.d.ts","sourceRoot":"","sources":["../../src/react/SpagbolNodeGroupPort.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AACvE,UAAU,yBAAyB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;IAC/D,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,QAAQ,EACR,SAAiB,EACjB,QAAgB,EAChB,OAAe,EACf,gBAAwB,EACxB,gBAAwB,EACxB,aAAa,EACb,QAAQ,GACT,EAAE,yBAAyB,+BAsF3B"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
export function SpagbolNodeGroupPort({ name, editable, draggable = false, selected = false, preview = false, activeDragTarget = false, activeDragSource = false, onPointerDown, onChange, }) {
|
|
4
|
+
const [editing, setEditing] = useState(false);
|
|
5
|
+
const [draft, setDraft] = useState(name);
|
|
6
|
+
const inputRef = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!editing) {
|
|
9
|
+
setDraft(name);
|
|
10
|
+
}
|
|
11
|
+
}, [editing, name]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!editing)
|
|
14
|
+
return;
|
|
15
|
+
const animationFrame = requestAnimationFrame(() => {
|
|
16
|
+
inputRef.current?.focus();
|
|
17
|
+
inputRef.current?.select();
|
|
18
|
+
});
|
|
19
|
+
return () => cancelAnimationFrame(animationFrame);
|
|
20
|
+
}, [editing]);
|
|
21
|
+
function commitDraft() {
|
|
22
|
+
onChange(draft);
|
|
23
|
+
setEditing(false);
|
|
24
|
+
}
|
|
25
|
+
function cancelDraft() {
|
|
26
|
+
setDraft(name);
|
|
27
|
+
setEditing(false);
|
|
28
|
+
}
|
|
29
|
+
if (editing) {
|
|
30
|
+
return (_jsx("input", { ref: inputRef, className: "spagbol-node-group-port-editor nodrag nopan", value: draft, onChange: (event) => setDraft(event.currentTarget.value), onBlur: commitDraft, onKeyDown: (event) => {
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
if (event.key === 'Enter') {
|
|
33
|
+
commitDraft();
|
|
34
|
+
}
|
|
35
|
+
if (event.key === 'Escape') {
|
|
36
|
+
cancelDraft();
|
|
37
|
+
}
|
|
38
|
+
}, onPointerDown: (event) => event.stopPropagation(), onMouseDown: (event) => event.stopPropagation(), onDoubleClick: (event) => event.stopPropagation(), spellCheck: false }));
|
|
39
|
+
}
|
|
40
|
+
return (_jsx("span", { className: [
|
|
41
|
+
'spagbol-node-group-port',
|
|
42
|
+
editable ? 'spagbol-node-group-port-editable' : '',
|
|
43
|
+
draggable ? 'spagbol-node-group-port-draggable nodrag nopan' : '',
|
|
44
|
+
selected ? 'spagbol-node-group-port-selected' : '',
|
|
45
|
+
preview ? 'spagbol-node-group-port-preview' : '',
|
|
46
|
+
activeDragTarget ? 'spagbol-node-group-port-drag-target' : '',
|
|
47
|
+
activeDragSource ? 'spagbol-node-group-port-drag-source' : '',
|
|
48
|
+
].filter(Boolean).join(' '), draggable: draggable, title: preview
|
|
49
|
+
? 'Drop a connection here to create this port'
|
|
50
|
+
: editable
|
|
51
|
+
? 'Drag to reorder. Double-click to rename'
|
|
52
|
+
: undefined, onPointerDown: (event) => {
|
|
53
|
+
onPointerDown?.(event);
|
|
54
|
+
}, onDoubleClick: (event) => {
|
|
55
|
+
if (!editable)
|
|
56
|
+
return;
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
event.stopPropagation();
|
|
59
|
+
setEditing(true);
|
|
60
|
+
}, children: name }));
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=SpagbolNodeGroupPort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolNodeGroupPort.js","sourceRoot":"","sources":["../../src/react/SpagbolNodeGroupPort.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, type PointerEvent } from \"react\";\ninterface SpagbolNodeGroupPortProps {\n name: string;\n editable: boolean;\n draggable?: boolean;\n selected?: boolean;\n preview?: boolean;\n activeDragTarget?: boolean;\n activeDragSource?: boolean;\n onPointerDown?: (event: PointerEvent<HTMLSpanElement>) => void;\n onChange: (nextName: string) => void;\n}\n\nexport function SpagbolNodeGroupPort({\n name,\n editable,\n draggable = false,\n selected = false,\n preview = false,\n activeDragTarget = false,\n activeDragSource = false,\n onPointerDown,\n onChange,\n}: SpagbolNodeGroupPortProps) {\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(name);\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (!editing) {\n setDraft(name);\n }\n }, [editing, name]);\n\n useEffect(() => {\n if (!editing) return;\n\n const animationFrame = requestAnimationFrame(() => {\n inputRef.current?.focus();\n inputRef.current?.select();\n });\n return () => cancelAnimationFrame(animationFrame);\n }, [editing]);\n\n function commitDraft() {\n onChange(draft);\n setEditing(false);\n }\n\n function cancelDraft() {\n setDraft(name);\n setEditing(false);\n }\n\n if (editing) {\n return (\n <input\n ref={inputRef}\n className=\"spagbol-node-group-port-editor nodrag nopan\"\n value={draft}\n onChange={(event) => setDraft(event.currentTarget.value)}\n onBlur={commitDraft}\n onKeyDown={(event) => {\n event.stopPropagation();\n if (event.key === 'Enter') {\n commitDraft();\n }\n if (event.key === 'Escape') {\n cancelDraft();\n }\n }}\n onPointerDown={(event) => event.stopPropagation()}\n onMouseDown={(event) => event.stopPropagation()}\n onDoubleClick={(event) => event.stopPropagation()}\n spellCheck={false}\n />\n );\n }\n\n return (\n <span\n className={[\n 'spagbol-node-group-port',\n editable ? 'spagbol-node-group-port-editable' : '',\n draggable ? 'spagbol-node-group-port-draggable nodrag nopan' : '',\n selected ? 'spagbol-node-group-port-selected' : '',\n preview ? 'spagbol-node-group-port-preview' : '',\n activeDragTarget ? 'spagbol-node-group-port-drag-target' : '',\n activeDragSource ? 'spagbol-node-group-port-drag-source' : '',\n ].filter(Boolean).join(' ')}\n draggable={draggable}\n title={preview\n ? 'Drop a connection here to create this port'\n : editable\n ? 'Drag to reorder. Double-click to rename'\n : undefined}\n onPointerDown={(event) => {\n onPointerDown?.(event);\n }}\n onDoubleClick={(event) => {\n if (!editable) return;\n event.preventDefault();\n event.stopPropagation();\n setEditing(true);\n }}\n >\n {name}\n </span>\n );\n}\n\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolNodeGroupPortAdd.d.ts","sourceRoot":"","sources":["../../src/react/SpagbolNodeGroupPortAdd.tsx"],"names":[],"mappings":"AACA,wBAAgB,uBAAuB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,+BA6CzG"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
export function SpagbolNodeGroupPortAdd({ label, onAdd }) {
|
|
4
|
+
const [editing, setEditing] = useState(false);
|
|
5
|
+
const [draft, setDraft] = useState('');
|
|
6
|
+
const inputRef = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (editing)
|
|
9
|
+
inputRef.current?.focus();
|
|
10
|
+
}, [editing]);
|
|
11
|
+
function finish() {
|
|
12
|
+
if (draft.trim())
|
|
13
|
+
onAdd(draft);
|
|
14
|
+
setDraft('');
|
|
15
|
+
setEditing(false);
|
|
16
|
+
}
|
|
17
|
+
return (_jsx("div", { className: "spagbol-node-group-port-add nodrag nopan", children: editing ? (_jsx("input", { ref: inputRef, className: "spagbol-node-group-port-add-editor nodrag nopan", "aria-label": label, value: draft, onChange: (event) => setDraft(event.currentTarget.value), onBlur: finish, onKeyDown: (event) => {
|
|
18
|
+
event.stopPropagation();
|
|
19
|
+
if (event.key === 'Enter')
|
|
20
|
+
finish();
|
|
21
|
+
if (event.key === 'Escape') {
|
|
22
|
+
setDraft('');
|
|
23
|
+
setEditing(false);
|
|
24
|
+
}
|
|
25
|
+
}, onPointerDown: (event) => event.stopPropagation(), onDoubleClick: (event) => event.stopPropagation(), spellCheck: false })) : (_jsx("button", { type: "button", className: "spagbol-node-group-port-add-placeholder nodrag nopan", onPointerDown: (event) => event.stopPropagation(), onClick: (event) => { event.stopPropagation(); setEditing(true); }, onDoubleClick: (event) => event.stopPropagation(), children: label })) }));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=SpagbolNodeGroupPortAdd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpagbolNodeGroupPortAdd.js","sourceRoot":"","sources":["../../src/react/SpagbolNodeGroupPortAdd.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpD,MAAM,UAAU,uBAAuB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAoD;IACxG,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAC;IAEvD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO;YAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IACzC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,SAAS,MAAM;QACb,IAAI,KAAK,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/B,QAAQ,CAAC,EAAE,CAAC,CAAC;QACb,UAAU,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,CACL,cAAK,SAAS,EAAC,0CAA0C,YACtD,OAAO,CAAC,CAAC,CAAC,CACT,gBACE,GAAG,EAAE,QAAQ,EACb,SAAS,EAAC,iDAAiD,gBAC/C,KAAK,EACjB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,EACxD,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;gBACnB,KAAK,CAAC,eAAe,EAAE,CAAC;gBACxB,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO;oBAAE,MAAM,EAAE,CAAC;gBACpC,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;oBAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;oBAAC,UAAU,CAAC,KAAK,CAAC,CAAC;gBAAC,CAAC;YAClE,CAAC,EACD,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EACjD,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EACjD,UAAU,EAAE,KAAK,GACjB,CACH,CAAC,CAAC,CAAC,CACF,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,sDAAsD,EAChE,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EACjD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,GAAG,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAClE,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,YACjD,KAAK,GAAU,CAClB,GACG,CACP,CAAC;AACJ,CAAC","sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nexport function SpagbolNodeGroupPortAdd({ label, onAdd }: { label: string; onAdd: (name: string) => void }) {\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (editing) inputRef.current?.focus();\n }, [editing]);\n\n function finish() {\n if (draft.trim()) onAdd(draft);\n setDraft('');\n setEditing(false);\n }\n\n return (\n <div className=\"spagbol-node-group-port-add nodrag nopan\">\n {editing ? (\n <input\n ref={inputRef}\n className=\"spagbol-node-group-port-add-editor nodrag nopan\"\n aria-label={label}\n value={draft}\n onChange={(event) => setDraft(event.currentTarget.value)}\n onBlur={finish}\n onKeyDown={(event) => {\n event.stopPropagation();\n if (event.key === 'Enter') finish();\n if (event.key === 'Escape') { setDraft(''); setEditing(false); }\n }}\n onPointerDown={(event) => event.stopPropagation()}\n onDoubleClick={(event) => event.stopPropagation()}\n spellCheck={false}\n />\n ) : (\n <button\n type=\"button\"\n className=\"spagbol-node-group-port-add-placeholder nodrag nopan\"\n onPointerDown={(event) => event.stopPropagation()}\n onClick={(event) => { event.stopPropagation(); setEditing(true); }}\n onDoubleClick={(event) => event.stopPropagation()}\n >{label}</button>\n )}\n </div>\n );\n}\n\n"]}
|