@statelyai/flow-react 0.9.6 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/dist/esm/index.d.ts +11 -3
- package/dist/esm/index.js +58 -16
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -151,8 +151,72 @@ function Flow() {
|
|
|
151
151
|
);
|
|
152
152
|
}
|
|
153
153
|
```
|
|
154
|
+
## Named appearance renderers
|
|
155
|
+
|
|
156
|
+
<!-- appearanceRenderers and renderer props from src/StatelyFlow.tsx; border observation from src/useAppearanceMeasurement.ts -->
|
|
157
|
+
|
|
158
|
+
Pass reusable renderers through `appearanceRenderers`; core resolves the renderer
|
|
159
|
+
key from the entity reference or host default. Existing `renderNode` and
|
|
160
|
+
`renderEdgeLabel` callbacks remain the fallback when a key has no renderer.
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
<StatelyFlow
|
|
164
|
+
flow={flow}
|
|
165
|
+
appearanceRenderers={{
|
|
166
|
+
asset: {
|
|
167
|
+
renderNode: ({ node, appearance }) => (
|
|
168
|
+
<AssetView data={node.data} options={appearance?.reference?.options} />
|
|
169
|
+
),
|
|
170
|
+
renderEdgeLabel: ({ edge, appearance }) => (
|
|
171
|
+
<AssetView data={edge.label!.data} options={appearance?.reference?.options} />
|
|
172
|
+
),
|
|
173
|
+
},
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Put `data-flow-connection-bounds` on the image-border element inside a custom
|
|
179
|
+
renderer. Flow measures it relative to the entity wrapper, independently of the
|
|
180
|
+
full caption/image box, and refreshes on renderer commits, resizing, and image
|
|
181
|
+
loading. The wrapper owns selection and primary drag. Use `draggable={false}`
|
|
182
|
+
and `pointerEvents: 'none'` on primary imagery; use `Port` for authored ports,
|
|
183
|
+
which automatically filters ports forbidden by registered policies. Avoid
|
|
184
|
+
independent hover dimming or gesture handlers on primary imagery.
|
|
185
|
+
|
|
186
|
+
Core policies live in `useFlow({ settings: { engine: { appearances } } })` or
|
|
187
|
+
`createFlowInstance`. Renderer functions remain React-only and never enter saved
|
|
188
|
+
references. See **Features/Appearances → Asset Presentations** in Storybook for
|
|
189
|
+
above/below captions, image-border connections, switching, and edge labels.
|
|
190
|
+
|
|
154
191
|
## Storybook
|
|
155
192
|
|
|
156
193
|
```bash
|
|
157
194
|
pnpm --filter @statelyai/flow-react storybook
|
|
158
195
|
```
|
|
196
|
+
|
|
197
|
+
<!-- connection selector measurement from ../flow-dom/src/appearance-bounds.ts and src/useAppearanceMeasurement.ts -->
|
|
198
|
+
|
|
199
|
+
Appearance definitions can choose the measured border without requiring a reserved
|
|
200
|
+
attribute in custom content:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
engine: {
|
|
204
|
+
appearances: {
|
|
205
|
+
definitions: {
|
|
206
|
+
asset: {
|
|
207
|
+
sizing: 'content',
|
|
208
|
+
connection: {
|
|
209
|
+
bounds: { selector: '[data-asset-body]' },
|
|
210
|
+
sides: ['left', 'right', 'bottom'],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
defaults: { node: { name: 'asset' } },
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Render one descendant with `data-asset-body`. Full visual bounds still include
|
|
220
|
+
captions. Connection bounds track subtree changes and resizing; selectors that
|
|
221
|
+
match zero or multiple descendants use visual bounds. Existing
|
|
222
|
+
`data-flow-connection-bounds` renderers continue to work without configuration.
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Background, BackgroundProps, BackgroundVariant, CrossBackground, DotsBackground, LinesBackground } from "./backgrounds.js";
|
|
2
2
|
import { FlowView, ResolvedFlowColorMode } from "@statelyai/flow-dom";
|
|
3
|
-
import { ActiveGraphProjection, AnchorSpec, ConnectionEndedEvent, ConnectionState, CreateEdgeInput, CreateFlowOptions, EdgePathData, FitViewExclusionSide, FlowColorMode, FlowCommitEvent, FlowConnection, FlowController, FlowEdge, FlowGraph, FlowInputMode, FlowKeymap, FlowNode, FlowProfile, FlowStoreContext, GraphEdgeAttrs, GraphNodeAttrs, LayoutRequestedEvent, Point, Rect, Viewport, VisualPort } from "@statelyai/flow";
|
|
3
|
+
import { ActiveGraphProjection, AnchorSpec, ConnectionEndedEvent, ConnectionState, CreateEdgeInput, CreateFlowOptions, EdgePathData, FitViewExclusionSide, FlowColorMode, FlowCommitEvent, FlowConnection, FlowController, FlowEdge, FlowGraph, FlowInputMode, FlowKeymap, FlowNode, FlowProfile, FlowStoreContext, GraphEdgeAttrs, GraphNodeAttrs, LayoutRequestedEvent, Point, Rect, ResolvedFlowAppearance, Viewport, VisualPort } from "@statelyai/flow";
|
|
4
4
|
import { CSSProperties, ComponentPropsWithoutRef, HTMLAttributes, ReactNode, Ref, RefCallback, RefObject } from "react";
|
|
5
5
|
export * from "@statelyai/flow-dom";
|
|
6
6
|
//#region src/createFlowInstance.d.ts
|
|
@@ -167,6 +167,7 @@ type ReactGraphNodeAttrs = GraphNodeAttrs<CSSProperties, ReactNode>;
|
|
|
167
167
|
type ReactGraphEdgeAttrs = GraphEdgeAttrs<CSSProperties, ReactNode>;
|
|
168
168
|
type ReactActiveGraphProjection = ActiveGraphProjection<any, any, CSSProperties, ReactNode>;
|
|
169
169
|
type RenderNodeProps<TNodeData = unknown> = {
|
|
170
|
+
appearance?: ResolvedFlowAppearance;
|
|
170
171
|
flow: FlowInstance<TNodeData>;
|
|
171
172
|
node: FlowNode<TNodeData>;
|
|
172
173
|
bounds: Rect;
|
|
@@ -194,6 +195,7 @@ type RenderEdgeProps<TEdgeData = unknown> = {
|
|
|
194
195
|
type RenderEdgeLabelProps<TEdgeData = unknown> = RenderEdgeProps<TEdgeData> & {
|
|
195
196
|
bounds: Rect;
|
|
196
197
|
dragging: boolean;
|
|
198
|
+
appearance?: ResolvedFlowAppearance;
|
|
197
199
|
};
|
|
198
200
|
type RenderConnectionLineProps = {
|
|
199
201
|
connection: ConnectionState;
|
|
@@ -207,7 +209,13 @@ type StatelyFlowRootProps = Omit<ComponentPropsWithoutRef<"div">, "children" | "
|
|
|
207
209
|
style?: StatelyFlowStyle;
|
|
208
210
|
[dataAttribute: `data-${string}`]: string | number | boolean | null | undefined;
|
|
209
211
|
};
|
|
212
|
+
type ReactAppearanceRenderers<TNodeData = unknown, TEdgeData = unknown> = Record<string, {
|
|
213
|
+
renderNode?: (props: RenderNodeProps<TNodeData>) => ReactNode;
|
|
214
|
+
renderEdgeLabel?: (props: RenderEdgeLabelProps<TEdgeData>) => ReactNode;
|
|
215
|
+
}>;
|
|
210
216
|
type StatelyFlowBaseProps<TNodeData = unknown, TEdgeData = unknown> = {
|
|
217
|
+
/** Reusable renderers keyed by the core-resolved renderer name. */
|
|
218
|
+
appearanceRenderers?: ReactAppearanceRenderers<TNodeData, TEdgeData>;
|
|
211
219
|
settings?: FlowInstanceSettings<TNodeData, TEdgeData>;
|
|
212
220
|
listeners?: FlowStoreListener[];
|
|
213
221
|
events?: FlowEventListeners;
|
|
@@ -433,7 +441,7 @@ type PortProps = {
|
|
|
433
441
|
* observe results via `flow.on("connectionEnded", …)` or the store's
|
|
434
442
|
* `connection` state.
|
|
435
443
|
*/
|
|
436
|
-
declare function Port(props: PortProps): import("react").JSX.Element;
|
|
444
|
+
declare function Port(props: PortProps): import("react").JSX.Element | null;
|
|
437
445
|
//#endregion
|
|
438
446
|
//#region src/DragHandle.d.ts
|
|
439
447
|
type DragHandleProps = HTMLAttributes<HTMLDivElement> & {
|
|
@@ -497,4 +505,4 @@ declare function useFlowColorMode(): {
|
|
|
497
505
|
};
|
|
498
506
|
declare function useApplyFlowColorMode(element: HTMLElement | null): void;
|
|
499
507
|
//#endregion
|
|
500
|
-
export { Background, type BackgroundProps, type BackgroundVariant, Controls, type ControlsProps, type CreateFlowInstanceOptions, CrossBackground, DotsBackground, DragHandle, type DragHandleProps, EntityAnchor, type EntityAnchorAlign, type EntityAnchorPoint, type EntityAnchorProps, type EntityAnchorSide, FLOW_BASE_CSS, FLOW_BASE_STYLES_ID, FlowColorModeScope, type FlowColorModeScopeProps, type FlowEdgeBinding, type FlowEventListeners, type FlowInstance, FlowInstanceProvider, type FlowInstanceSettings, type FlowNodeBinding, type FlowPortBinding, type FlowSelector, type FlowSelectorEquality, type FlowStoreListener, FlowToolbar, type FlowToolbarProps, type FlowToolbarState, LinesBackground, MiniMap, type MiniMapProps, PanePortal, Panel, type PanelPosition, type PanelProps, Port, type PortProps, type ReactActiveGraphProjection, type ReactGraphEdgeAttrs, type ReactGraphNodeAttrs, type RenderConnectionLineProps, type RenderEdgeLabelProps, type RenderEdgeProps, type RenderNodeProps, StatelyFlow, type StatelyFlowProps, type StatelyFlowRootProps, type StatelyFlowStyle, type UseCreateFlowInstanceOptions, type UseFitViewExclusionOptions, type UseFlowInputOptions, type UseFlowOptions, type UseFlowToolbarOptions, ViewportPortal, createFlowInstance, useApplyFlowColorMode, useConnection, useCreateFlowInstance, useFitViewExclusion, useFlow, useFlowBaseStyles, useFlowColorMode, useFlowConnections, useFlowContext, useFlowEdge, useFlowEntity, useFlowInput, useFlowInstance, useFlowMeasurement, useFlowNode, useFlowPort, useFlowSelector, useFlowToolbar, useFlowValue, useFlowView, useFlowViewport, useKeyPress, useMeasuredSize, useNodesInitialized, useOptionalFlowInstance };
|
|
508
|
+
export { Background, type BackgroundProps, type BackgroundVariant, Controls, type ControlsProps, type CreateFlowInstanceOptions, CrossBackground, DotsBackground, DragHandle, type DragHandleProps, EntityAnchor, type EntityAnchorAlign, type EntityAnchorPoint, type EntityAnchorProps, type EntityAnchorSide, FLOW_BASE_CSS, FLOW_BASE_STYLES_ID, FlowColorModeScope, type FlowColorModeScopeProps, type FlowEdgeBinding, type FlowEventListeners, type FlowInstance, FlowInstanceProvider, type FlowInstanceSettings, type FlowNodeBinding, type FlowPortBinding, type FlowSelector, type FlowSelectorEquality, type FlowStoreListener, FlowToolbar, type FlowToolbarProps, type FlowToolbarState, LinesBackground, MiniMap, type MiniMapProps, PanePortal, Panel, type PanelPosition, type PanelProps, Port, type PortProps, type ReactActiveGraphProjection, type ReactAppearanceRenderers, type ReactGraphEdgeAttrs, type ReactGraphNodeAttrs, type RenderConnectionLineProps, type RenderEdgeLabelProps, type RenderEdgeProps, type RenderNodeProps, StatelyFlow, type StatelyFlowProps, type StatelyFlowRootProps, type StatelyFlowStyle, type UseCreateFlowInstanceOptions, type UseFitViewExclusionOptions, type UseFlowInputOptions, type UseFlowOptions, type UseFlowToolbarOptions, ViewportPortal, createFlowInstance, useApplyFlowColorMode, useConnection, useCreateFlowInstance, useFitViewExclusion, useFlow, useFlowBaseStyles, useFlowColorMode, useFlowConnections, useFlowContext, useFlowEdge, useFlowEntity, useFlowInput, useFlowInstance, useFlowMeasurement, useFlowNode, useFlowPort, useFlowSelector, useFlowToolbar, useFlowValue, useFlowView, useFlowViewport, useKeyPress, useMeasuredSize, useNodesInitialized, useOptionalFlowInstance };
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as FlowInstanceProvider, i as LinesBackground, n as CrossBackground, o as useFlowInstance, r as DotsBackground, s as useOptionalFlowInstance, t as Background } from "./backgrounds2.js";
|
|
2
|
-
import { applyFlowColorMode, attachContainer, attachInput, attachViewportTransform, clientToCanvas, computeRenderableIds, createEntityGeometryObserver, createFlowView, createMeasurementObserver, nodePositionStyle, renderableIdsEqual, resolveFlowColorMode, viewportStyle } from "@statelyai/flow-dom";
|
|
3
|
-
import { CONNECTION_PREVIEW_EDGE_ID, areNodesMeasured, createConnectionPreviewGraph, createEntityChangeNotifier, createFlow, createFlowInputRuntime, getAnchorTransform, getAutoPanDelta, getMinimapProjection, getMinimapViewportCenter, getProjectedEdgeAttrs, getProjectedNodeAttrs, getToolbarPlacement, getVisibleBounds, markerToSVG, pathToSVG, profileModesForContext, profilePoliciesForContext, viewportCenteredOn } from "@statelyai/flow";
|
|
2
|
+
import { applyFlowColorMode, attachContainer, attachInput, attachViewportTransform, clientToCanvas, computeRenderableIds, createEntityGeometryObserver, createFlowView, createMeasurementObserver, nodePositionStyle, observeAppearanceBounds, renderableIdsEqual, resolveFlowColorMode, viewportStyle } from "@statelyai/flow-dom";
|
|
3
|
+
import { CONNECTION_PREVIEW_EDGE_ID, appearanceMeasurementId, areNodesMeasured, createConnectionPreviewGraph, createEntityChangeNotifier, createFlow, createFlowInputRuntime, getAnchorTransform, getAutoPanDelta, getMinimapProjection, getMinimapViewportCenter, getProjectedEdgeAttrs, getProjectedNodeAttrs, getToolbarPlacement, getVisibleBounds, markerToSVG, pathToSVG, profileModesForContext, profilePoliciesForContext, viewportCenteredOn } from "@statelyai/flow";
|
|
4
4
|
import { Fragment, createContext, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector";
|
|
@@ -372,6 +372,27 @@ function useFlowView() {
|
|
|
372
372
|
return view;
|
|
373
373
|
}
|
|
374
374
|
//#endregion
|
|
375
|
+
//#region src/useAppearanceMeasurement.ts
|
|
376
|
+
function useAppearanceMeasurement(ref, id, appearance, observer) {
|
|
377
|
+
const flow = useFlowInstance();
|
|
378
|
+
const key = appearance ? appearanceMeasurementId(id, appearance) : void 0;
|
|
379
|
+
useLayoutEffect(() => () => {
|
|
380
|
+
if (key && flow.getSnapshot().measurements.has(key)) flow.internals.store.trigger.removeMeasurements({ ids: [key] });
|
|
381
|
+
}, [flow, key]);
|
|
382
|
+
useLayoutEffect(() => {
|
|
383
|
+
const wrapper = ref.current;
|
|
384
|
+
if (!wrapper || !key || !appearance?.registered) return;
|
|
385
|
+
return observeAppearanceBounds(flow.internals.store, wrapper, key, observer, { selector: appearance.connectionSelector });
|
|
386
|
+
}, [
|
|
387
|
+
flow,
|
|
388
|
+
ref,
|
|
389
|
+
key,
|
|
390
|
+
observer,
|
|
391
|
+
appearance?.registered,
|
|
392
|
+
appearance?.connectionSelector
|
|
393
|
+
]);
|
|
394
|
+
}
|
|
395
|
+
//#endregion
|
|
375
396
|
//#region src/baseStyles.ts
|
|
376
397
|
/**
|
|
377
398
|
* Minimal base stylesheet for the flow canvas. Intentionally tiny and
|
|
@@ -718,6 +739,7 @@ function StatelyFlowView(props) {
|
|
|
718
739
|
}),
|
|
719
740
|
/* @__PURE__ */ jsx(NodeLayer, {
|
|
720
741
|
geometryObserver,
|
|
742
|
+
appearanceRenderers: props.appearanceRenderers,
|
|
721
743
|
renderNode: props.renderNode,
|
|
722
744
|
projections,
|
|
723
745
|
onlyRenderVisible: props.onlyRenderVisible
|
|
@@ -725,6 +747,7 @@ function StatelyFlowView(props) {
|
|
|
725
747
|
/* @__PURE__ */ jsx(SnapLinesLayer, {}),
|
|
726
748
|
/* @__PURE__ */ jsx(EdgeLabelLayer, {
|
|
727
749
|
geometryObserver,
|
|
750
|
+
appearanceRenderers: props.appearanceRenderers,
|
|
728
751
|
renderEdgeLabel: props.renderEdgeLabel,
|
|
729
752
|
projections,
|
|
730
753
|
onlyRenderVisible: props.onlyRenderVisible
|
|
@@ -994,6 +1017,7 @@ const EdgeView = memo(function EdgeView(props) {
|
|
|
994
1017
|
const ref = useRef(null);
|
|
995
1018
|
const { graph, edge, pathData, selected, focused, hovered, highlights } = useFlowEntity(props.id, (context) => ({
|
|
996
1019
|
graph: context.graph,
|
|
1020
|
+
configRevision: context.configRevision,
|
|
997
1021
|
edge: props.flow.getEdge(props.id) ?? null,
|
|
998
1022
|
pathData: props.flow.getAllEdgePaths().get(props.id) ?? null,
|
|
999
1023
|
geometryActive: context.canvasState === "dragging" || context.canvasState === "resizing" || context.layoutAnimation.status === "running",
|
|
@@ -1002,7 +1026,7 @@ const EdgeView = memo(function EdgeView(props) {
|
|
|
1002
1026
|
hovered: context.hoveredEntityIds.has(props.id),
|
|
1003
1027
|
highlights: props.flow.internals.engine.getHighlights(props.id)
|
|
1004
1028
|
}), { isEqual: (a, b) => {
|
|
1005
|
-
if (!(a.selected === b.selected && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1029
|
+
if (!(a.configRevision === b.configRevision && a.selected === b.selected && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1006
1030
|
if (a.geometryActive !== b.geometryActive) return b.geometryActive ? edgeRenderContentEqual(a.edge, b.edge) : false;
|
|
1007
1031
|
if (a.geometryActive) return edgeRenderContentEqual(a.edge, b.edge);
|
|
1008
1032
|
return a.graph === b.graph && a.edge === b.edge && a.pathData === b.pathData;
|
|
@@ -1266,6 +1290,7 @@ function EdgeLabelLayer(props) {
|
|
|
1266
1290
|
geometryObserver: props.geometryObserver,
|
|
1267
1291
|
id,
|
|
1268
1292
|
projections: props.projections,
|
|
1293
|
+
appearanceRenderers: props.appearanceRenderers,
|
|
1269
1294
|
renderEdgeLabel
|
|
1270
1295
|
}, id);
|
|
1271
1296
|
})
|
|
@@ -1274,6 +1299,7 @@ function EdgeLabelLayer(props) {
|
|
|
1274
1299
|
const EdgeLabelItem = memo(function EdgeLabelItem(props) {
|
|
1275
1300
|
const { graph, edge, pathData, selected, dragging, hovered, highlights } = useFlowEntity(props.id, (context) => ({
|
|
1276
1301
|
graph: context.graph,
|
|
1302
|
+
configRevision: context.configRevision,
|
|
1277
1303
|
edge: props.flow.getEdge(props.id) ?? null,
|
|
1278
1304
|
pathData: props.flow.getAllEdgePaths().get(props.id) ?? null,
|
|
1279
1305
|
geometryActive: context.canvasState === "dragging" || context.canvasState === "resizing" || context.layoutAnimation.status === "running",
|
|
@@ -1282,7 +1308,7 @@ const EdgeLabelItem = memo(function EdgeLabelItem(props) {
|
|
|
1282
1308
|
hovered: context.hoveredEntityIds.has(props.id),
|
|
1283
1309
|
highlights: props.flow.internals.engine.getHighlights(props.id)
|
|
1284
1310
|
}), { isEqual: (a, b) => {
|
|
1285
|
-
if (!(a.selected === b.selected && a.dragging === b.dragging && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1311
|
+
if (!(a.configRevision === b.configRevision && a.selected === b.selected && a.dragging === b.dragging && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1286
1312
|
if (a.geometryActive !== b.geometryActive) return b.geometryActive ? edgeRenderContentEqual(a.edge, b.edge) : false;
|
|
1287
1313
|
if (a.geometryActive) return edgeRenderContentEqual(a.edge, b.edge);
|
|
1288
1314
|
return a.graph === b.graph && a.edge === b.edge && a.pathData === b.pathData;
|
|
@@ -1307,12 +1333,16 @@ const EdgeLabelItem = memo(function EdgeLabelItem(props) {
|
|
|
1307
1333
|
hovered,
|
|
1308
1334
|
highlights,
|
|
1309
1335
|
projections: props.projections,
|
|
1336
|
+
appearanceRenderers: props.appearanceRenderers,
|
|
1310
1337
|
renderEdgeLabel: props.renderEdgeLabel
|
|
1311
1338
|
});
|
|
1312
1339
|
});
|
|
1313
1340
|
function EdgeLabelView(props) {
|
|
1314
1341
|
const ref = useRef(null);
|
|
1315
1342
|
const observer = useMeasurementObserver();
|
|
1343
|
+
const appearance = props.edge.label ? props.flow.internals.engine.resolveAppearance(props.edge.label) : void 0;
|
|
1344
|
+
const renderEdgeLabel = appearance?.renderer && props.appearanceRenderers?.[appearance.renderer]?.renderEdgeLabel || props.renderEdgeLabel;
|
|
1345
|
+
useAppearanceMeasurement(ref, props.edge.id, appearance, observer);
|
|
1316
1346
|
useLayoutEffect(() => {
|
|
1317
1347
|
const element = ref.current;
|
|
1318
1348
|
if (!element) return;
|
|
@@ -1324,7 +1354,7 @@ function EdgeLabelView(props) {
|
|
|
1324
1354
|
if (!element) return;
|
|
1325
1355
|
return props.geometryObserver.observeRect(element, {
|
|
1326
1356
|
id: props.edge.id,
|
|
1327
|
-
autoSize:
|
|
1357
|
+
autoSize: appearance?.autoSize,
|
|
1328
1358
|
getRect: () => {
|
|
1329
1359
|
const pathData = props.flow.getAllEdgePaths().get(props.edge.id);
|
|
1330
1360
|
if (!pathData) return null;
|
|
@@ -1340,14 +1370,15 @@ function EdgeLabelView(props) {
|
|
|
1340
1370
|
});
|
|
1341
1371
|
}, [
|
|
1342
1372
|
props.edge.id,
|
|
1343
|
-
|
|
1373
|
+
appearance?.autoSize,
|
|
1344
1374
|
props.flow,
|
|
1345
1375
|
props.geometryObserver,
|
|
1346
1376
|
props.projections
|
|
1347
1377
|
]);
|
|
1348
1378
|
const text = (props.edge.label?.data)?.text;
|
|
1349
1379
|
const label = props.attrs.label ?? String(text ?? props.edge.id);
|
|
1350
|
-
const customLabel =
|
|
1380
|
+
const customLabel = renderEdgeLabel?.({
|
|
1381
|
+
appearance,
|
|
1351
1382
|
flow: props.flow,
|
|
1352
1383
|
edge: props.edge,
|
|
1353
1384
|
pathData: props.pathData,
|
|
@@ -1358,7 +1389,7 @@ function EdgeLabelView(props) {
|
|
|
1358
1389
|
hovered: props.hovered,
|
|
1359
1390
|
highlights: props.highlights
|
|
1360
1391
|
});
|
|
1361
|
-
const defaultStyle =
|
|
1392
|
+
const defaultStyle = renderEdgeLabel ? void 0 : {
|
|
1362
1393
|
padding: "4px 8px",
|
|
1363
1394
|
border: `1px solid ${props.selected ? "#2563eb" : props.attrs.color ?? "#cbd5e1"}`,
|
|
1364
1395
|
borderRadius: 999,
|
|
@@ -1373,6 +1404,7 @@ function EdgeLabelView(props) {
|
|
|
1373
1404
|
ref,
|
|
1374
1405
|
"data-entity-id": props.edge.id,
|
|
1375
1406
|
"data-flow-edge-label": "",
|
|
1407
|
+
"data-flow-appearance": appearance?.registered ? appearance.reference?.name : void 0,
|
|
1376
1408
|
"data-flow-selected": props.selected ? "true" : void 0,
|
|
1377
1409
|
"data-flow-dragging": props.dragging ? "true" : void 0,
|
|
1378
1410
|
className: classNames$1("stately-flow__edge-label", props.attrs.className),
|
|
@@ -1381,8 +1413,8 @@ function EdgeLabelView(props) {
|
|
|
1381
1413
|
left: 0,
|
|
1382
1414
|
top: 0,
|
|
1383
1415
|
transform: `translate(${props.bounds.x}px, ${props.bounds.y}px)`,
|
|
1384
|
-
width:
|
|
1385
|
-
height:
|
|
1416
|
+
width: appearance?.autoSize ? void 0 : props.bounds.width,
|
|
1417
|
+
height: appearance?.autoSize ? void 0 : props.bounds.height,
|
|
1386
1418
|
boxSizing: "border-box",
|
|
1387
1419
|
...defaultStyle,
|
|
1388
1420
|
pointerEvents: "auto",
|
|
@@ -1390,7 +1422,7 @@ function EdgeLabelView(props) {
|
|
|
1390
1422
|
opacity: props.attrs.opacity,
|
|
1391
1423
|
...props.attrs.style
|
|
1392
1424
|
},
|
|
1393
|
-
children:
|
|
1425
|
+
children: renderEdgeLabel ? customLabel : label
|
|
1394
1426
|
});
|
|
1395
1427
|
}
|
|
1396
1428
|
function projectedEdgeLabelBounds(pathData, label) {
|
|
@@ -1439,6 +1471,7 @@ function NodeLayer(props) {
|
|
|
1439
1471
|
zIndex,
|
|
1440
1472
|
observer,
|
|
1441
1473
|
projections: props.projections,
|
|
1474
|
+
appearanceRenderers: props.appearanceRenderers,
|
|
1442
1475
|
renderNode: props.renderNode
|
|
1443
1476
|
}, id);
|
|
1444
1477
|
})
|
|
@@ -1483,6 +1516,7 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1483
1516
|
const current = props.flow.getNode(props.id) ?? null;
|
|
1484
1517
|
return {
|
|
1485
1518
|
graph: context.graph,
|
|
1519
|
+
configRevision: context.configRevision,
|
|
1486
1520
|
node: current,
|
|
1487
1521
|
geometryActive: context.canvasState === "dragging" || context.canvasState === "resizing" || context.layoutAnimation.status === "running",
|
|
1488
1522
|
hidden: !current || props.flow.internals.engine.resolveEntityConfig(current).hidden,
|
|
@@ -1494,12 +1528,15 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1494
1528
|
bounds: context.resizingEntityId === props.id && context.resizeCorners ? props.flow.internals.engine.getResizeBounds() ?? props.flow.internals.engine.visualBounds(props.id) : props.flow.internals.engine.visualBounds(props.id)
|
|
1495
1529
|
};
|
|
1496
1530
|
}, { isEqual: (a, b) => {
|
|
1497
|
-
if (!(a.hidden === b.hidden && a.selected === b.selected && a.dragging === b.dragging && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1531
|
+
if (!(a.configRevision === b.configRevision && a.hidden === b.hidden && a.selected === b.selected && a.dragging === b.dragging && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1498
1532
|
if (a.geometryActive !== b.geometryActive) return b.geometryActive ? nodeRenderContentEqual(a.node, b.node) : false;
|
|
1499
1533
|
if (a.geometryActive) return nodeRenderContentEqual(a.node, b.node);
|
|
1500
1534
|
return a.graph === b.graph && a.node === b.node && rectsShallowEqual(a.bounds, b.bounds);
|
|
1501
1535
|
} });
|
|
1502
|
-
const
|
|
1536
|
+
const appearance = node ? props.flow.internals.engine.resolveAppearance(node) : void 0;
|
|
1537
|
+
useAppearanceMeasurement(ref, props.id, appearance, props.observer);
|
|
1538
|
+
const renderNode = appearance?.renderer && props.appearanceRenderers?.[appearance.renderer]?.renderNode || props.renderNode;
|
|
1539
|
+
const contentOwnsSize = Boolean(node && props.flow.internals.engine.resolveAppearance(node).autoSize && node.style?.customWidth === void 0 && node.style?.customHeight === void 0);
|
|
1503
1540
|
useLayoutEffect(() => {
|
|
1504
1541
|
const element = ref.current;
|
|
1505
1542
|
if (!element) return;
|
|
@@ -1546,6 +1583,7 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1546
1583
|
},
|
|
1547
1584
|
children: /* @__PURE__ */ jsx("div", {
|
|
1548
1585
|
ref,
|
|
1586
|
+
"data-flow-appearance": appearance?.registered ? appearance.reference?.name : void 0,
|
|
1549
1587
|
"data-entity-id": props.id,
|
|
1550
1588
|
"data-flow-selected": selected ? "true" : void 0,
|
|
1551
1589
|
"data-flow-focused": focused ? "true" : void 0,
|
|
@@ -1571,13 +1609,14 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1571
1609
|
zIndex: props.zIndex,
|
|
1572
1610
|
opacity: attrs.opacity
|
|
1573
1611
|
},
|
|
1574
|
-
children:
|
|
1612
|
+
children: renderNode ? /* @__PURE__ */ jsx("div", {
|
|
1575
1613
|
"data-flow-custom-node": true,
|
|
1576
1614
|
style: {
|
|
1577
1615
|
display: "contents",
|
|
1578
1616
|
pointerEvents: "none"
|
|
1579
1617
|
},
|
|
1580
|
-
children:
|
|
1618
|
+
children: renderNode({
|
|
1619
|
+
appearance,
|
|
1581
1620
|
flow: props.flow,
|
|
1582
1621
|
node,
|
|
1583
1622
|
bounds,
|
|
@@ -2298,6 +2337,7 @@ function Port(props) {
|
|
|
2298
2337
|
const observer = useMeasurementObserver();
|
|
2299
2338
|
const measured = props.measured ?? true;
|
|
2300
2339
|
const interaction = props.interaction ?? "connect";
|
|
2340
|
+
const permitted = interaction !== "connect" || !flow.internals.engine.resolveAppearance(node).registered || flow.internals.engine.isPortAllowed(node, props.name);
|
|
2301
2341
|
const port = node.ports?.find((candidate) => candidate.name === props.name);
|
|
2302
2342
|
const portId = `${node.id}:${props.name}`;
|
|
2303
2343
|
const focusable = flow.internals.engine.resolveEntityConfig(node).focusable;
|
|
@@ -2318,7 +2358,8 @@ function Port(props) {
|
|
|
2318
2358
|
observer,
|
|
2319
2359
|
node.id,
|
|
2320
2360
|
props.name,
|
|
2321
|
-
measured
|
|
2361
|
+
measured,
|
|
2362
|
+
permitted
|
|
2322
2363
|
]);
|
|
2323
2364
|
useLayoutEffect(() => {
|
|
2324
2365
|
const element = ref.current;
|
|
@@ -2332,6 +2373,7 @@ function Port(props) {
|
|
|
2332
2373
|
if (connection.targetId !== node.id || connection.targetPort !== props.name) return null;
|
|
2333
2374
|
return connection.isValid === false ? "invalid" : "valid";
|
|
2334
2375
|
}, { flow });
|
|
2376
|
+
if (!permitted) return null;
|
|
2335
2377
|
return /* @__PURE__ */ jsx("div", {
|
|
2336
2378
|
ref,
|
|
2337
2379
|
className: props.className,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/flow-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Thin React adapter for @statelyai/flow with core and DOM re-exports.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"use-sync-external-store": "^1.6.0",
|
|
37
|
-
"@statelyai/flow": "0.
|
|
38
|
-
"@statelyai/flow-dom": "3.
|
|
37
|
+
"@statelyai/flow": "0.12.0",
|
|
38
|
+
"@statelyai/flow-dom": "3.5.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": "^18.2.0 || ^19.0.0",
|