orcasvn-react-diagrams 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/README.md +15 -3
- package/dist/cjs/examples.js +2616 -291
- package/dist/cjs/index.js +1186 -153
- package/dist/cjs/types/api/createDiagramEditor.d.ts +19 -1
- package/dist/cjs/types/api/index.d.ts +1 -1
- package/dist/cjs/types/api/types.d.ts +41 -0
- package/dist/cjs/types/displaybox/DisplayBoxControls.d.ts +5 -1
- package/dist/cjs/types/displaybox/demos/AsymmetricPortMultiAnchorDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/LayoutLabelReservedSpaceDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/VertexControlLinkSessionDemoTab.d.ts +3 -0
- package/dist/cjs/types/displaybox/demos/asymmetricPortMultiAnchorDemo.d.ts +31 -0
- package/dist/cjs/types/displaybox/demos/labelStyleDemo.d.ts +2 -0
- package/dist/cjs/types/displaybox/demos/layoutLabelReservedSpaceDemo.d.ts +11 -0
- package/dist/cjs/types/displaybox/demos/portPositionLimitsDemo.d.ts +2 -0
- package/dist/cjs/types/displaybox/demos/vertexControlLinkSessionDemo.d.ts +12 -0
- package/dist/cjs/types/displaybox/useDemoControls.d.ts +4 -0
- package/dist/cjs/types/engine/AutoLayoutService.d.ts +2 -0
- package/dist/cjs/types/engine/DiagramEngine.d.ts +11 -0
- package/dist/cjs/types/engine/LinkRoutingService.d.ts +9 -1
- package/dist/cjs/types/models/PortModel.d.ts +5 -0
- package/dist/cjs/types/renderer/RenderTypes.d.ts +3 -1
- package/dist/cjs/types/renderer/konva/KonvaInteraction.d.ts +14 -0
- package/dist/cjs/types/renderer/konva/KonvaNodeFactory.d.ts +12 -0
- package/dist/cjs/types/renderer/konva/KonvaRenderer.d.ts +2 -1
- package/dist/cjs/types/shapes/BuiltInShapes.d.ts +3 -1
- package/dist/cjs/types/strategies/ObstacleRouter.d.ts +2 -0
- package/dist/cjs/types/utils/__tests__/portGeometry.test.d.ts +1 -0
- package/dist/cjs/types/utils/portGeometry.d.ts +44 -0
- package/dist/esm/examples.js +2617 -292
- package/dist/esm/examples.js.map +1 -1
- package/dist/esm/index.js +1186 -153
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/api/createDiagramEditor.d.ts +19 -1
- package/dist/esm/types/api/index.d.ts +1 -1
- package/dist/esm/types/api/types.d.ts +41 -0
- package/dist/esm/types/displaybox/DisplayBoxControls.d.ts +5 -1
- package/dist/esm/types/displaybox/demos/AsymmetricPortMultiAnchorDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/LayoutLabelReservedSpaceDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/VertexControlLinkSessionDemoTab.d.ts +3 -0
- package/dist/esm/types/displaybox/demos/asymmetricPortMultiAnchorDemo.d.ts +31 -0
- package/dist/esm/types/displaybox/demos/labelStyleDemo.d.ts +2 -0
- package/dist/esm/types/displaybox/demos/layoutLabelReservedSpaceDemo.d.ts +11 -0
- package/dist/esm/types/displaybox/demos/portPositionLimitsDemo.d.ts +2 -0
- package/dist/esm/types/displaybox/demos/vertexControlLinkSessionDemo.d.ts +12 -0
- package/dist/esm/types/displaybox/useDemoControls.d.ts +4 -0
- package/dist/esm/types/engine/AutoLayoutService.d.ts +2 -0
- package/dist/esm/types/engine/DiagramEngine.d.ts +11 -0
- package/dist/esm/types/engine/LinkRoutingService.d.ts +9 -1
- package/dist/esm/types/models/PortModel.d.ts +5 -0
- package/dist/esm/types/renderer/RenderTypes.d.ts +3 -1
- package/dist/esm/types/renderer/konva/KonvaInteraction.d.ts +14 -0
- package/dist/esm/types/renderer/konva/KonvaNodeFactory.d.ts +12 -0
- package/dist/esm/types/renderer/konva/KonvaRenderer.d.ts +2 -1
- package/dist/esm/types/shapes/BuiltInShapes.d.ts +3 -1
- package/dist/esm/types/strategies/ObstacleRouter.d.ts +2 -0
- package/dist/esm/types/utils/__tests__/portGeometry.test.d.ts +1 -0
- package/dist/esm/types/utils/portGeometry.d.ts +44 -0
- package/dist/examples.d.ts +59 -0
- package/dist/index.d.ts +67 -1
- package/package.json +2 -1
- package/src/displaybox/demos/AsymmetricPortMultiAnchorDemoTab.tsx +269 -0
- package/src/displaybox/demos/AutoLayoutDemoTab.tsx +113 -11
- package/src/displaybox/demos/DeletionEventsDemoTab.tsx +6 -1
- package/src/displaybox/demos/EngineEventsDemoTab.tsx +5 -0
- package/src/displaybox/demos/EventHandlersDemoTab.tsx +5 -0
- package/src/displaybox/demos/ExternalDragDropDemoTab.tsx +5 -0
- package/src/displaybox/demos/LayoutLabelReservedSpaceDemoTab.tsx +291 -0
- package/src/displaybox/demos/LinkCancelDemoTab.tsx +5 -0
- package/src/displaybox/demos/ObstacleRoutingDemoTab.tsx +11 -10
- package/src/displaybox/demos/ShapeHoverControlsDemoTab.tsx +6 -1
- package/src/displaybox/demos/SimpleDemo.tsx +5 -0
- package/src/displaybox/demos/SvgPathDemoTab.tsx +5 -0
- package/src/displaybox/demos/TextLayoutDemoTab.tsx +6 -1
- package/src/displaybox/demos/VertexControlLinkSessionDemoTab.tsx +302 -0
- package/src/displaybox/demos/asymmetricPortMultiAnchorDemo.ts +357 -0
- package/src/displaybox/demos/autoLayoutDemo.ts +23 -5
- package/src/displaybox/demos/index.tsx +110 -80
- package/src/displaybox/demos/labelStyleDemo.ts +101 -0
- package/src/displaybox/demos/layoutLabelReservedSpaceDemo.ts +121 -0
- package/src/displaybox/demos/obstacleRoutingDemo.ts +212 -176
- package/src/displaybox/demos/portPositionLimitsDemo.ts +211 -0
- package/src/displaybox/demos/vertexControlLinkSessionDemo.ts +145 -0
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ type DiagramContainer = {
|
|
|
22
22
|
type MoveConstraint = 'free' | 'inside' | 'border';
|
|
23
23
|
type BorderSide$1 = 'left' | 'right' | 'top' | 'bottom';
|
|
24
24
|
type HostAnchorPreset = 'vertices' | 'cardinal';
|
|
25
|
+
type PortLinkAttachMode = 'external' | 'internal';
|
|
25
26
|
type PortAnchor = {
|
|
26
27
|
id: string;
|
|
27
28
|
position: Point;
|
|
@@ -29,10 +30,32 @@ type PortAnchor = {
|
|
|
29
30
|
normal?: Point;
|
|
30
31
|
meta?: Record<string, unknown>;
|
|
31
32
|
};
|
|
33
|
+
type PortBorderTransformContext = {
|
|
34
|
+
side: BorderSide$1;
|
|
35
|
+
normal: Point;
|
|
36
|
+
hostRect: Rect;
|
|
37
|
+
attachMode: PortLinkAttachMode;
|
|
38
|
+
effectiveLinkAttachPoint: Point;
|
|
39
|
+
placementPoint: Point;
|
|
40
|
+
rotationPivot: Point;
|
|
41
|
+
portSize?: Size;
|
|
42
|
+
};
|
|
43
|
+
type PortBorderTransformResult = {
|
|
44
|
+
rotation?: number;
|
|
45
|
+
offset?: Point;
|
|
46
|
+
};
|
|
32
47
|
type PortAnchorConstraint = {
|
|
33
48
|
preset: HostAnchorPreset;
|
|
34
49
|
fallback?: 'nearest';
|
|
35
50
|
};
|
|
51
|
+
type PositionAxisLimit = {
|
|
52
|
+
min?: number;
|
|
53
|
+
max?: number;
|
|
54
|
+
};
|
|
55
|
+
type PortPositionLimits = {
|
|
56
|
+
x?: PositionAxisLimit;
|
|
57
|
+
y?: PositionAxisLimit;
|
|
58
|
+
};
|
|
36
59
|
type ResolvePortAnchorsOptions = {
|
|
37
60
|
preset: HostAnchorPreset;
|
|
38
61
|
};
|
|
@@ -143,6 +166,7 @@ type ElementShapeHoverControlActivationEvent = {
|
|
|
143
166
|
type ElementPortMovementPolicy = {
|
|
144
167
|
moveMode: MoveConstraint | 'anchors';
|
|
145
168
|
anchorConstraint?: PortAnchorConstraint;
|
|
169
|
+
positionLimits?: PortPositionLimits;
|
|
146
170
|
};
|
|
147
171
|
type LinkRoutingMode = 'auto' | 'manual';
|
|
148
172
|
type AnchorReference = 'top-left' | 'center';
|
|
@@ -153,6 +177,15 @@ type ElementLayoutMode = 'manual' | 'horizontal' | 'vertical';
|
|
|
153
177
|
type ElementLayoutAlign = 'start' | 'center' | 'end';
|
|
154
178
|
type ElementLayoutChildFitMainAxis = 'none' | 'distribute';
|
|
155
179
|
type ElementLayoutChildFitCrossAxis = 'none' | 'stretch';
|
|
180
|
+
type ElementLayoutLabelReservedSpaceMode = 'none' | 'fixed' | 'flexible';
|
|
181
|
+
type ElementLayoutLabelReservedSpacePlacement = 'top';
|
|
182
|
+
type ElementLayoutLabelReservedSpace = {
|
|
183
|
+
mode?: ElementLayoutLabelReservedSpaceMode;
|
|
184
|
+
placement?: ElementLayoutLabelReservedSpacePlacement;
|
|
185
|
+
size?: number;
|
|
186
|
+
minSize?: number;
|
|
187
|
+
maxSize?: number;
|
|
188
|
+
};
|
|
156
189
|
type TextLayoutBoundsMode = 'owner-width' | 'owner-box' | 'fixed';
|
|
157
190
|
type TextLayoutWrapMode = 'none' | 'word' | 'char';
|
|
158
191
|
type TextLayoutOverflowMode = 'clip' | 'ellipsis-end' | 'ellipsis-middle' | 'ellipsis-start';
|
|
@@ -168,6 +201,7 @@ type ElementLayout = {
|
|
|
168
201
|
childFitCrossAxis?: ElementLayoutChildFitCrossAxis;
|
|
169
202
|
childFitMinSize?: Partial<Size>;
|
|
170
203
|
childFitMaxSize?: Partial<Size>;
|
|
204
|
+
labelReservedSpace?: ElementLayoutLabelReservedSpace;
|
|
171
205
|
};
|
|
172
206
|
type TextLayout = {
|
|
173
207
|
boundsMode?: TextLayoutBoundsMode;
|
|
@@ -222,6 +256,11 @@ type PortData = {
|
|
|
222
256
|
moveMode?: MoveConstraint;
|
|
223
257
|
anchorCenter?: boolean;
|
|
224
258
|
orientToHostBorder?: boolean;
|
|
259
|
+
placementPoint?: Point;
|
|
260
|
+
linkAttachPoint?: Point;
|
|
261
|
+
externalLinkAttachPoint?: Point;
|
|
262
|
+
internalLinkAttachPoint?: Point;
|
|
263
|
+
rotationPivot?: Point;
|
|
225
264
|
currentAnchorId?: string;
|
|
226
265
|
};
|
|
227
266
|
type ShapeDrawContext = {
|
|
@@ -441,7 +480,9 @@ type DiagramEngineHandle = {
|
|
|
441
480
|
svgSize?: Size;
|
|
442
481
|
projectToBorder?: (point: Point, rect: Rect) => Point;
|
|
443
482
|
resolveBorderSide?: (point: Point, rect: Rect) => BorderSide$1;
|
|
483
|
+
resolveBorderNormal?: (point: Point, rect: Rect) => Point;
|
|
444
484
|
resolvePortAnchors?: (rect: Rect, options: ResolvePortAnchorsOptions) => PortAnchor[];
|
|
485
|
+
resolvePortBorderTransform?: (context: PortBorderTransformContext) => PortBorderTransformResult | undefined;
|
|
445
486
|
resolveHoverGeometry?: (rect: Rect) => ShapeHoverGeometry | undefined;
|
|
446
487
|
resolveEllipseMidPoints?: (rect: Rect) => ShapeEllipseMidPointTarget[] | undefined;
|
|
447
488
|
}) => void;
|
|
@@ -506,6 +547,11 @@ declare class PortModel {
|
|
|
506
547
|
moveMode?: MoveConstraint;
|
|
507
548
|
anchorCenter?: boolean;
|
|
508
549
|
orientToHostBorder: boolean;
|
|
550
|
+
placementPoint?: Point;
|
|
551
|
+
linkAttachPoint?: Point;
|
|
552
|
+
externalLinkAttachPoint?: Point;
|
|
553
|
+
internalLinkAttachPoint?: Point;
|
|
554
|
+
rotationPivot?: Point;
|
|
509
555
|
currentAnchorId?: string;
|
|
510
556
|
constructor(data: PortData);
|
|
511
557
|
setPosition(position: Point): void;
|
|
@@ -657,7 +703,9 @@ type ShapeDefinition = {
|
|
|
657
703
|
};
|
|
658
704
|
projectToBorder?: (point: Point, rect: Rect) => Point;
|
|
659
705
|
resolveBorderSide?: (point: Point, rect: Rect) => BorderSide;
|
|
706
|
+
resolveBorderNormal?: (point: Point, rect: Rect) => Point;
|
|
660
707
|
resolvePortAnchors?: (rect: Rect, options: ResolvePortAnchorsOptions) => PortAnchor[];
|
|
708
|
+
resolvePortBorderTransform?: (context: PortBorderTransformContext) => PortBorderTransformResult | undefined;
|
|
661
709
|
resolveHoverGeometry?: (rect: Rect) => ShapeHoverGeometry | undefined;
|
|
662
710
|
resolveEllipseMidPoints?: (rect: Rect) => ShapeEllipseMidPointTarget[] | undefined;
|
|
663
711
|
};
|
|
@@ -703,11 +751,29 @@ type DiagramEditorConfig = {
|
|
|
703
751
|
onChange?: (event: EngineChangeEvent) => void;
|
|
704
752
|
onSelection?: (event: EngineSelectionEvent) => void;
|
|
705
753
|
};
|
|
754
|
+
type DiagramImageExportOptions = {
|
|
755
|
+
mimeType?: string;
|
|
756
|
+
quality?: number;
|
|
757
|
+
pixelRatio?: number;
|
|
758
|
+
x?: number;
|
|
759
|
+
y?: number;
|
|
760
|
+
width?: number;
|
|
761
|
+
height?: number;
|
|
762
|
+
fitToContent?: boolean | {
|
|
763
|
+
padding?: number;
|
|
764
|
+
};
|
|
765
|
+
};
|
|
706
766
|
type DiagramEditorHandle = DiagramEngineHandle & {
|
|
767
|
+
startLinkFromPort: (sourcePortId: string, pointer?: Point) => void;
|
|
768
|
+
updateLinkPreview: (pointer: Point) => void;
|
|
769
|
+
completeLinkToPort: (targetPortId: string) => void;
|
|
770
|
+
completeLinkToElement: (targetElementId: string, pointer: Point) => void;
|
|
771
|
+
cancelLink: () => void;
|
|
772
|
+
exportImage: (options?: DiagramImageExportOptions) => string;
|
|
707
773
|
resize: (width: number, height: number) => void;
|
|
708
774
|
setElementShapeHoverControls: (controls?: ElementShapeHoverControls) => void;
|
|
709
775
|
destroy: () => void;
|
|
710
776
|
};
|
|
711
777
|
declare const createDiagramEditor: (config: DiagramEditorConfig) => DiagramEditorHandle;
|
|
712
778
|
|
|
713
|
-
export { type AnchorReference, type BorderSide$1 as BorderSide, type ClientRectLike, type DiagramContainer, type DiagramEditorConfig, type DiagramEditorHandle, type DiagramEngineHandle, type DiagramPatch, type DiagramState, type EdgeHoverControl, type ElementData, type ElementDeletedEvent, type ElementDropEvent, type ElementLayout, type ElementLayoutAlign, type ElementLayoutChildFitCrossAxis, type ElementLayoutChildFitMainAxis, type ElementLayoutMode, type ElementLinkConnectingEvent, type ElementLinkEndedEvent, type ElementLinkStartedEvent, type ElementMovedEvent, type ElementPointerEvent, type ElementPortMovementPolicy, type ElementResizedEvent, type ElementSelectedEvent, type ElementShapeControlDragEvent, type ElementShapeControlEventType, type ElementShapeHoverControlActivationEvent, type ElementShapeHoverControlInteractionEvent, type ElementShapeHoverControls, EllipseMidPoint, type EngineChangeEvent, type EngineConfigEvent, type EngineEventMap, type EnginePointerInfo, type EngineSelectionEvent, type HostAnchorPreset, type HoverControlIcon, type LinkData, type LinkDeletedEvent, type LinkRoutingMode, type MoveConstraint, type OverlayShapeConfig, type OverlayShapeHandle, type PaperClickEvent, type Point, type PortAnchor, type PortAnchorConstraint, type PortData, type PortDeletedEvent, type PortMouseEvent, type PortMovedEvent, type PortSelectedEvent, type Rect, type RerouteLinksOptions, type ResolvePortAnchorsOptions, type ShapeControlDefinition, type ShapeControlTargetKind, type ShapeControlVisibilityTrigger, type ShapeDrawContext, type ShapeEdgeTarget, type ShapeEllipseMidPointTarget, type ShapeHoverGeometry, type ShapeVertexTarget, type SimpleShape, type Size, type TextData, type TextDeletedEvent, type TextLayout, type TextLayoutBoundsMode, type TextLayoutOverflowMode, type TextLayoutWrapMode, type TextSelectedEvent, type TextUpdatedEvent, type VertexHoverControl, createDiagramEditor, createDiagramEngine };
|
|
779
|
+
export { type AnchorReference, type BorderSide$1 as BorderSide, type ClientRectLike, type DiagramContainer, type DiagramEditorConfig, type DiagramEditorHandle, type DiagramEngineHandle, type DiagramImageExportOptions, type DiagramPatch, type DiagramState, type EdgeHoverControl, type ElementData, type ElementDeletedEvent, type ElementDropEvent, type ElementLayout, type ElementLayoutAlign, type ElementLayoutChildFitCrossAxis, type ElementLayoutChildFitMainAxis, type ElementLayoutLabelReservedSpace, type ElementLayoutLabelReservedSpaceMode, type ElementLayoutLabelReservedSpacePlacement, type ElementLayoutMode, type ElementLinkConnectingEvent, type ElementLinkEndedEvent, type ElementLinkStartedEvent, type ElementMovedEvent, type ElementPointerEvent, type ElementPortMovementPolicy, type ElementResizedEvent, type ElementSelectedEvent, type ElementShapeControlDragEvent, type ElementShapeControlEventType, type ElementShapeHoverControlActivationEvent, type ElementShapeHoverControlInteractionEvent, type ElementShapeHoverControls, EllipseMidPoint, type EngineChangeEvent, type EngineConfigEvent, type EngineEventMap, type EnginePointerInfo, type EngineSelectionEvent, type HostAnchorPreset, type HoverControlIcon, type LinkData, type LinkDeletedEvent, type LinkRoutingMode, type MoveConstraint, type OverlayShapeConfig, type OverlayShapeHandle, type PaperClickEvent, type Point, type PortAnchor, type PortAnchorConstraint, type PortBorderTransformContext, type PortBorderTransformResult, type PortData, type PortDeletedEvent, type PortLinkAttachMode, type PortMouseEvent, type PortMovedEvent, type PortPositionLimits, type PortSelectedEvent, type PositionAxisLimit, type Rect, type RerouteLinksOptions, type ResolvePortAnchorsOptions, type ShapeControlDefinition, type ShapeControlTargetKind, type ShapeControlVisibilityTrigger, type ShapeDrawContext, type ShapeEdgeTarget, type ShapeEllipseMidPointTarget, type ShapeHoverGeometry, type ShapeVertexTarget, type SimpleShape, type Size, type TextData, type TextDeletedEvent, type TextLayout, type TextLayoutBoundsMode, type TextLayoutOverflowMode, type TextLayoutWrapMode, type TextSelectedEvent, type TextUpdatedEvent, type VertexHoverControl, createDiagramEditor, createDiagramEngine };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orcasvn-react-diagrams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"eventemitter3": "^5.0.1",
|
|
6
6
|
"flatten-js": "^0.6.9",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"src/displaybox/demos",
|
|
64
64
|
"src/displaybox/types.ts",
|
|
65
65
|
"README.md",
|
|
66
|
+
"CHANGELOG.md",
|
|
66
67
|
"docs",
|
|
67
68
|
"ai"
|
|
68
69
|
],
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { OverlayShapeHandle, PortData } from '../../api';
|
|
3
|
+
import DisplayBoxControls from '../DisplayBoxControls';
|
|
4
|
+
import DisplayBoxStage from '../DisplayBoxStage';
|
|
5
|
+
import useDemoControls from '../useDemoControls';
|
|
6
|
+
import useDemoEditor from '../useDemoEditor';
|
|
7
|
+
import useOffsetSequence from '../useOffsetSequence';
|
|
8
|
+
import type { DemoActionHelpers } from '../types';
|
|
9
|
+
import {
|
|
10
|
+
asymmetricPortDefaultVariantId,
|
|
11
|
+
asymmetricPortMultiAnchorDemoConfig,
|
|
12
|
+
asymmetricPortShapeVariants,
|
|
13
|
+
createAsymmetricPortMultiAnchorState,
|
|
14
|
+
multiAnchorExternalPortId,
|
|
15
|
+
multiAnchorHostId,
|
|
16
|
+
resolveAsymmetricPortShapeVariant,
|
|
17
|
+
} from './asymmetricPortMultiAnchorDemo';
|
|
18
|
+
|
|
19
|
+
type DemoSide = 'top' | 'right' | 'bottom' | 'left';
|
|
20
|
+
|
|
21
|
+
const sidePositions: Record<DemoSide, { x: number; y: number }> = {
|
|
22
|
+
top: { x: 112, y: 0 },
|
|
23
|
+
right: { x: 280, y: 68 },
|
|
24
|
+
bottom: { x: 182, y: 180 },
|
|
25
|
+
left: { x: 0, y: 130 },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const sideLabels: DemoSide[] = ['top', 'right', 'bottom', 'left'];
|
|
29
|
+
const resolvePortSide = (port: PortData, host: { size: { width: number; height: number } }): DemoSide | null => {
|
|
30
|
+
if (Math.abs(port.position.x) <= 0.001) return 'left';
|
|
31
|
+
if (Math.abs(port.position.x - host.size.width) <= 0.001) return 'right';
|
|
32
|
+
if (Math.abs(port.position.y) <= 0.001) return 'top';
|
|
33
|
+
if (Math.abs(port.position.y - host.size.height) <= 0.001) return 'bottom';
|
|
34
|
+
return null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const sideToVector = (side: DemoSide): { x: number; y: number } => {
|
|
38
|
+
if (side === 'left') return { x: -1, y: 0 };
|
|
39
|
+
if (side === 'right') return { x: 1, y: 0 };
|
|
40
|
+
if (side === 'top') return { x: 0, y: -1 };
|
|
41
|
+
return { x: 0, y: 1 };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const AsymmetricPortMultiAnchorDemo = () => {
|
|
45
|
+
const demo = asymmetricPortMultiAnchorDemoConfig;
|
|
46
|
+
const [showLegacyComparison, setShowLegacyComparison] = useState(true);
|
|
47
|
+
const [showExpectedAnchors, setShowExpectedAnchors] = useState(false);
|
|
48
|
+
const [selectedVariantId, setSelectedVariantId] = useState(asymmetricPortDefaultVariantId);
|
|
49
|
+
const overlayHandlesRef = useRef<OverlayShapeHandle[]>([]);
|
|
50
|
+
const selectedVariant = useMemo(
|
|
51
|
+
() => resolveAsymmetricPortShapeVariant(selectedVariantId),
|
|
52
|
+
[selectedVariantId],
|
|
53
|
+
);
|
|
54
|
+
const attachDistance = selectedVariant.externalLinkAttachPoint.x - selectedVariant.placementPoint.x;
|
|
55
|
+
const createState = useCallback(
|
|
56
|
+
() => createAsymmetricPortMultiAnchorState(showLegacyComparison, selectedVariantId),
|
|
57
|
+
[showLegacyComparison, selectedVariantId],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const { containerRef, editorRef, diagramState, selection, snapEnabled, setSnapEnabled } = useDemoEditor({
|
|
61
|
+
createState,
|
|
62
|
+
elementShapes: demo.elementShapes,
|
|
63
|
+
portShapes: demo.portShapes,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const nextOffset = useOffsetSequence();
|
|
67
|
+
const actionHelpers: DemoActionHelpers = useMemo(() => ({ nextOffset }), [nextOffset]);
|
|
68
|
+
const controls = useDemoControls({
|
|
69
|
+
demo,
|
|
70
|
+
editorRef,
|
|
71
|
+
diagramState,
|
|
72
|
+
selection,
|
|
73
|
+
snapEnabled,
|
|
74
|
+
setSnapEnabled,
|
|
75
|
+
actionHelpers,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const editor = editorRef.current;
|
|
80
|
+
if (!editor) return;
|
|
81
|
+
asymmetricPortShapeVariants.forEach((variant) => {
|
|
82
|
+
editor.registerShape({
|
|
83
|
+
id: variant.shapeId,
|
|
84
|
+
baseRotation: 90,
|
|
85
|
+
svgPath: variant.svgPath,
|
|
86
|
+
svgSize: variant.svgSize,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
editor.rerouteAllLinks();
|
|
90
|
+
}, [editorRef, showLegacyComparison, selectedVariantId]);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const editor = editorRef.current;
|
|
94
|
+
const state = diagramState;
|
|
95
|
+
|
|
96
|
+
overlayHandlesRef.current.forEach((handle) => handle.destroy());
|
|
97
|
+
overlayHandlesRef.current = [];
|
|
98
|
+
|
|
99
|
+
if (!showExpectedAnchors || !editor || !state) return;
|
|
100
|
+
|
|
101
|
+
const hosts = new Map(state.elements.map((element) => [element.id, element]));
|
|
102
|
+
const debugPorts = state.ports.filter((port) => port.id.startsWith('multi-anchor-port-'));
|
|
103
|
+
|
|
104
|
+
debugPorts.forEach((port) => {
|
|
105
|
+
const host = hosts.get(port.elementId);
|
|
106
|
+
if (!host) return;
|
|
107
|
+
const side = resolvePortSide(port, host);
|
|
108
|
+
if (!side) return;
|
|
109
|
+
const vector = sideToVector(side);
|
|
110
|
+
const placementWorld = {
|
|
111
|
+
x: host.position.x + port.position.x,
|
|
112
|
+
y: host.position.y + port.position.y,
|
|
113
|
+
};
|
|
114
|
+
overlayHandlesRef.current.push(
|
|
115
|
+
editor.createOverlayShape({
|
|
116
|
+
shapeId: 'default',
|
|
117
|
+
position: placementWorld,
|
|
118
|
+
size: { width: 8, height: 8 },
|
|
119
|
+
anchorCenter: true,
|
|
120
|
+
style: {
|
|
121
|
+
fill: '#ffffff',
|
|
122
|
+
stroke: '#d97706',
|
|
123
|
+
strokeWidth: 2,
|
|
124
|
+
cornerRadius: 1,
|
|
125
|
+
opacity: 0.9,
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (port.id === multiAnchorExternalPortId || port.id.endsWith('-port-top') || port.id.endsWith('-port-bottom')) {
|
|
131
|
+
overlayHandlesRef.current.push(
|
|
132
|
+
editor.createOverlayShape({
|
|
133
|
+
shapeId: 'port-dark',
|
|
134
|
+
position: {
|
|
135
|
+
x: placementWorld.x + vector.x * attachDistance,
|
|
136
|
+
y: placementWorld.y + vector.y * attachDistance,
|
|
137
|
+
},
|
|
138
|
+
size: { width: 8, height: 8 },
|
|
139
|
+
anchorCenter: true,
|
|
140
|
+
style: {
|
|
141
|
+
fill: '#dc2626',
|
|
142
|
+
stroke: '#dc2626',
|
|
143
|
+
opacity: 0.85,
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return () => {
|
|
151
|
+
overlayHandlesRef.current.forEach((handle) => handle.destroy());
|
|
152
|
+
overlayHandlesRef.current = [];
|
|
153
|
+
};
|
|
154
|
+
}, [diagramState, editorRef, showExpectedAnchors]);
|
|
155
|
+
|
|
156
|
+
const moveExternalPortToSide = (side: DemoSide) => {
|
|
157
|
+
const editor = editorRef.current;
|
|
158
|
+
const host = diagramState?.elements.find((element) => element.id === multiAnchorHostId);
|
|
159
|
+
if (!editor || !host) return;
|
|
160
|
+
const local = sidePositions[side];
|
|
161
|
+
editor.movePortTo(multiAnchorExternalPortId, host.position.x + local.x, host.position.y + local.y);
|
|
162
|
+
editor.setSelection([multiAnchorExternalPortId]);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<section>
|
|
167
|
+
<div style={{ marginBottom: 12 }}>
|
|
168
|
+
<h2 style={{ marginTop: 0, marginBottom: 4 }}>{demo.title}</h2>
|
|
169
|
+
<p style={{ marginTop: 0, marginBottom: 8 }}>{demo.description}</p>
|
|
170
|
+
<p style={{ marginTop: 0, marginBottom: 0, fontSize: 13, color: '#475569' }}>
|
|
171
|
+
The custom `svgPath` glyph is intentionally non-centered. The multi-anchor row keeps the square pivot pinned to the
|
|
172
|
+
border while links use the circle for external targets and the square pivot for parent-child internal links.
|
|
173
|
+
</p>
|
|
174
|
+
<p style={{ marginTop: 8, marginBottom: 0, fontSize: 13, color: '#475569' }}>
|
|
175
|
+
Active glyph variant: <strong>{selectedVariant.label}</strong> ({selectedVariant.description})
|
|
176
|
+
</p>
|
|
177
|
+
</div>
|
|
178
|
+
<DisplayBoxControls
|
|
179
|
+
actions={demo.actions}
|
|
180
|
+
snapEnabled={controls.snapEnabled}
|
|
181
|
+
selectedLinkRouting={controls.selectedLinkRouting}
|
|
182
|
+
canToggleLinkRouting={controls.canToggleLinkRouting}
|
|
183
|
+
onReload={controls.handleReload}
|
|
184
|
+
onZoomIn={controls.handleZoomIn}
|
|
185
|
+
onZoomOut={controls.handleZoomOut}
|
|
186
|
+
onResetViewport={controls.handleResetViewport}
|
|
187
|
+
onToggleSnap={controls.handleToggleSnap}
|
|
188
|
+
onManualRender={controls.handleManualRender}
|
|
189
|
+
onToggleLinkRouting={controls.handleToggleLinkRouting}
|
|
190
|
+
onAction={controls.handleAction}
|
|
191
|
+
onExportImage={controls.handleExportImage}
|
|
192
|
+
onClearExportPreview={controls.handleClearExportPreview}
|
|
193
|
+
exportPreviewDataUrl={controls.exportPreviewDataUrl}
|
|
194
|
+
exportError={controls.exportError}
|
|
195
|
+
/>
|
|
196
|
+
<div
|
|
197
|
+
style={{
|
|
198
|
+
display: 'grid',
|
|
199
|
+
gridTemplateColumns: 'minmax(0, 1fr) auto',
|
|
200
|
+
gap: 12,
|
|
201
|
+
alignItems: 'start',
|
|
202
|
+
marginBottom: 12,
|
|
203
|
+
padding: 12,
|
|
204
|
+
border: '1px solid #d9e2f0',
|
|
205
|
+
borderRadius: 8,
|
|
206
|
+
background: '#f8fbff',
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
210
|
+
<label htmlFor="legacy-comparison-toggle" style={{ display: 'flex', alignItems: 'center', gap: 8, fontWeight: 600 }}>
|
|
211
|
+
<input
|
|
212
|
+
id="legacy-comparison-toggle"
|
|
213
|
+
type="checkbox"
|
|
214
|
+
checked={showLegacyComparison}
|
|
215
|
+
onChange={(event) => setShowLegacyComparison(event.target.checked)}
|
|
216
|
+
/>
|
|
217
|
+
Show legacy/default comparison
|
|
218
|
+
</label>
|
|
219
|
+
<label htmlFor="expected-anchor-toggle" style={{ display: 'flex', alignItems: 'center', gap: 8, fontWeight: 600 }}>
|
|
220
|
+
<input
|
|
221
|
+
id="expected-anchor-toggle"
|
|
222
|
+
type="checkbox"
|
|
223
|
+
checked={showExpectedAnchors}
|
|
224
|
+
onChange={(event) => setShowExpectedAnchors(event.target.checked)}
|
|
225
|
+
/>
|
|
226
|
+
Show expected anchor markers
|
|
227
|
+
</label>
|
|
228
|
+
<label htmlFor="asymmetric-variant-select" style={{ display: 'flex', alignItems: 'center', gap: 8, fontWeight: 600 }}>
|
|
229
|
+
Port glyph variant
|
|
230
|
+
<select
|
|
231
|
+
id="asymmetric-variant-select"
|
|
232
|
+
value={selectedVariantId}
|
|
233
|
+
onChange={(event) => setSelectedVariantId(event.target.value)}
|
|
234
|
+
>
|
|
235
|
+
{asymmetricPortShapeVariants.map((variant) => (
|
|
236
|
+
<option key={variant.id} value={variant.id}>
|
|
237
|
+
{variant.label}
|
|
238
|
+
</option>
|
|
239
|
+
))}
|
|
240
|
+
</select>
|
|
241
|
+
</label>
|
|
242
|
+
<div style={{ fontSize: 13, color: '#334155' }}>
|
|
243
|
+
Drag any asymmetric port around the host border or use the side controls to verify top/right/bottom/left orientation.
|
|
244
|
+
</div>
|
|
245
|
+
<div style={{ fontSize: 13, color: '#334155' }}>
|
|
246
|
+
Quick visual check: the multi-anchor row should keep the square touching the border; the legacy row may shift because
|
|
247
|
+
it has no explicit placement or pivot anchors.
|
|
248
|
+
</div>
|
|
249
|
+
<div style={{ fontSize: 13, color: '#334155' }}>
|
|
250
|
+
Debug markers: amber square = expected border placement point, red dot = expected external attach point.
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
<div style={{ display: 'grid', gap: 8 }}>
|
|
254
|
+
<div style={{ fontSize: 12, fontWeight: 600, color: '#1e293b' }}>Move the external demo port</div>
|
|
255
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, justifyContent: 'flex-end' }}>
|
|
256
|
+
{sideLabels.map((side) => (
|
|
257
|
+
<button key={side} type="button" onClick={() => moveExternalPortToSide(side)} style={{ padding: '6px 10px' }}>
|
|
258
|
+
External → {side[0].toUpperCase() + side.slice(1)}
|
|
259
|
+
</button>
|
|
260
|
+
))}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
<DisplayBoxStage containerRef={containerRef} />
|
|
265
|
+
</section>
|
|
266
|
+
);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export default AsymmetricPortMultiAnchorDemo;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import type { ElementLayout, ElementLayoutMode } from '../../api';
|
|
2
|
+
import type { ElementLayout, ElementLayoutLabelReservedSpaceMode, ElementLayoutMode } from '../../api';
|
|
3
3
|
import { createId } from '../../utils/ids';
|
|
4
4
|
import DisplayBoxControls from '../DisplayBoxControls';
|
|
5
5
|
import DisplayBoxStage from '../DisplayBoxStage';
|
|
@@ -10,12 +10,15 @@ import type { DemoActionHelpers } from '../types';
|
|
|
10
10
|
import { autoLayoutDemoConfig } from './autoLayoutDemo';
|
|
11
11
|
import { gridStageStyle } from './shared';
|
|
12
12
|
|
|
13
|
-
const parentOptions = [
|
|
14
|
-
{ id: 'layout-row', label: 'Horizontal layout' },
|
|
15
|
-
{ id: 'layout-column', label: 'Vertical layout' },
|
|
16
|
-
{ id: 'layout-nested', label: 'Nested layout' },
|
|
17
|
-
{ id: 'layout-manual', label: 'Manual (compare)' },
|
|
18
|
-
];
|
|
13
|
+
const parentOptions = [
|
|
14
|
+
{ id: 'layout-row', label: 'Horizontal layout' },
|
|
15
|
+
{ id: 'layout-column', label: 'Vertical layout' },
|
|
16
|
+
{ id: 'layout-nested', label: 'Nested layout' },
|
|
17
|
+
{ id: 'layout-manual', label: 'Manual (compare)' },
|
|
18
|
+
];
|
|
19
|
+
const shortLabel = 'Parent label lane demo';
|
|
20
|
+
const longLabel =
|
|
21
|
+
'Parent label lane demo with longer content to increase flexible reserved space and push children downward.';
|
|
19
22
|
|
|
20
23
|
const AutoLayoutDemo = () => {
|
|
21
24
|
const demo = autoLayoutDemoConfig;
|
|
@@ -49,6 +52,10 @@ const AutoLayoutDemo = () => {
|
|
|
49
52
|
const [childFitMinHeight, setChildFitMinHeight] = useState<number | ''>('');
|
|
50
53
|
const [childFitMaxWidth, setChildFitMaxWidth] = useState<number | ''>('');
|
|
51
54
|
const [childFitMaxHeight, setChildFitMaxHeight] = useState<number | ''>('');
|
|
55
|
+
const [labelReservedMode, setLabelReservedMode] = useState<ElementLayoutLabelReservedSpaceMode>('none');
|
|
56
|
+
const [labelReservedFixedSize, setLabelReservedFixedSize] = useState<number>(32);
|
|
57
|
+
const [labelReservedMinSize, setLabelReservedMinSize] = useState<number | ''>('');
|
|
58
|
+
const [labelReservedMaxSize, setLabelReservedMaxSize] = useState<number | ''>('');
|
|
52
59
|
const [lastTrigger, setLastTrigger] = useState<string>('None yet');
|
|
53
60
|
|
|
54
61
|
useEffect(() => {
|
|
@@ -133,6 +140,10 @@ const AutoLayoutDemo = () => {
|
|
|
133
140
|
setChildFitMinHeight(layout?.childFitMinSize?.height ?? '');
|
|
134
141
|
setChildFitMaxWidth(layout?.childFitMaxSize?.width ?? '');
|
|
135
142
|
setChildFitMaxHeight(layout?.childFitMaxSize?.height ?? '');
|
|
143
|
+
setLabelReservedMode(layout?.labelReservedSpace?.mode ?? 'none');
|
|
144
|
+
setLabelReservedFixedSize(layout?.labelReservedSpace?.size ?? 32);
|
|
145
|
+
setLabelReservedMinSize(layout?.labelReservedSpace?.minSize ?? '');
|
|
146
|
+
setLabelReservedMaxSize(layout?.labelReservedSpace?.maxSize ?? '');
|
|
136
147
|
}, [diagramState, targetId]);
|
|
137
148
|
|
|
138
149
|
const childOrder = useMemo(() => {
|
|
@@ -161,7 +172,7 @@ const AutoLayoutDemo = () => {
|
|
|
161
172
|
});
|
|
162
173
|
}, [diagramState]);
|
|
163
174
|
|
|
164
|
-
const handleApplyLayout = () => {
|
|
175
|
+
const handleApplyLayout = () => {
|
|
165
176
|
const editor = editorRef.current;
|
|
166
177
|
if (!editor) return;
|
|
167
178
|
if (!targetElement) return;
|
|
@@ -179,6 +190,16 @@ const AutoLayoutDemo = () => {
|
|
|
179
190
|
...(childFitMaxWidth === '' ? {} : { width: childFitMaxWidth }),
|
|
180
191
|
...(childFitMaxHeight === '' ? {} : { height: childFitMaxHeight }),
|
|
181
192
|
};
|
|
193
|
+
const labelReservedSpace =
|
|
194
|
+
mode === 'manual'
|
|
195
|
+
? undefined
|
|
196
|
+
: {
|
|
197
|
+
mode: labelReservedMode,
|
|
198
|
+
placement: 'top' as const,
|
|
199
|
+
...(labelReservedMode === 'fixed' ? { size: labelReservedFixedSize } : {}),
|
|
200
|
+
...(labelReservedMinSize === '' ? {} : { minSize: labelReservedMinSize }),
|
|
201
|
+
...(labelReservedMaxSize === '' ? {} : { maxSize: labelReservedMaxSize }),
|
|
202
|
+
};
|
|
182
203
|
const layout =
|
|
183
204
|
mode === 'manual'
|
|
184
205
|
? { mode: 'manual' as const }
|
|
@@ -191,10 +212,20 @@ const AutoLayoutDemo = () => {
|
|
|
191
212
|
childFitCrossAxis,
|
|
192
213
|
childFitMinSize,
|
|
193
214
|
childFitMaxSize,
|
|
215
|
+
labelReservedSpace,
|
|
194
216
|
};
|
|
195
217
|
editor.setElementLayout(targetElement.id, layout);
|
|
196
218
|
setLastTrigger(`layout applied (${mode})`);
|
|
197
219
|
};
|
|
220
|
+
|
|
221
|
+
const handleSetLabelContent = (content: string) => {
|
|
222
|
+
const editor = editorRef.current;
|
|
223
|
+
if (!editor || !diagramState || !targetElement) return;
|
|
224
|
+
const label = diagramState.texts.find((text) => text.ownerId === targetElement.id);
|
|
225
|
+
if (!label) return;
|
|
226
|
+
editor.updateText(label.id, content);
|
|
227
|
+
setLastTrigger('label updated');
|
|
228
|
+
};
|
|
198
229
|
|
|
199
230
|
const handleAddChild = () => {
|
|
200
231
|
const editor = editorRef.current;
|
|
@@ -250,6 +281,10 @@ const AutoLayoutDemo = () => {
|
|
|
250
281
|
onManualRender={controls.handleManualRender}
|
|
251
282
|
onToggleLinkRouting={controls.handleToggleLinkRouting}
|
|
252
283
|
onAction={controls.handleAction}
|
|
284
|
+
onExportImage={controls.handleExportImage}
|
|
285
|
+
onClearExportPreview={controls.handleClearExportPreview}
|
|
286
|
+
exportPreviewDataUrl={controls.exportPreviewDataUrl}
|
|
287
|
+
exportError={controls.exportError}
|
|
253
288
|
/>
|
|
254
289
|
|
|
255
290
|
<div style={{ display: 'grid', gap: 12, marginBottom: 12 }}>
|
|
@@ -400,6 +435,55 @@ const AutoLayoutDemo = () => {
|
|
|
400
435
|
/>
|
|
401
436
|
</div>
|
|
402
437
|
|
|
438
|
+
<label htmlFor="label-reserved-mode-select" style={{ fontWeight: 600 }}>
|
|
439
|
+
Label lane
|
|
440
|
+
</label>
|
|
441
|
+
<select
|
|
442
|
+
id="label-reserved-mode-select"
|
|
443
|
+
value={labelReservedMode}
|
|
444
|
+
onChange={(event) => setLabelReservedMode(event.target.value as ElementLayoutLabelReservedSpaceMode)}
|
|
445
|
+
style={{ padding: '6px 10px', minWidth: 120 }}
|
|
446
|
+
disabled={mode === 'manual'}
|
|
447
|
+
>
|
|
448
|
+
<option value="none">None</option>
|
|
449
|
+
<option value="fixed">Fixed</option>
|
|
450
|
+
<option value="flexible">Flexible</option>
|
|
451
|
+
</select>
|
|
452
|
+
|
|
453
|
+
<label htmlFor="label-reserved-fixed-input" style={{ fontWeight: 600 }}>
|
|
454
|
+
Lane fixed/min/max
|
|
455
|
+
</label>
|
|
456
|
+
<div style={{ display: 'inline-flex', gap: 6 }}>
|
|
457
|
+
<input
|
|
458
|
+
id="label-reserved-fixed-input"
|
|
459
|
+
type="number"
|
|
460
|
+
value={labelReservedFixedSize}
|
|
461
|
+
onChange={(event) => setLabelReservedFixedSize(Math.max(0, Number(event.target.value) || 0))}
|
|
462
|
+
style={{ width: 70, padding: '6px 8px' }}
|
|
463
|
+
min={0}
|
|
464
|
+
placeholder="fixed"
|
|
465
|
+
disabled={mode === 'manual'}
|
|
466
|
+
/>
|
|
467
|
+
<input
|
|
468
|
+
type="number"
|
|
469
|
+
value={labelReservedMinSize}
|
|
470
|
+
onChange={(event) => setLabelReservedMinSize(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
471
|
+
style={{ width: 64, padding: '6px 8px' }}
|
|
472
|
+
min={0}
|
|
473
|
+
placeholder="min"
|
|
474
|
+
disabled={mode === 'manual'}
|
|
475
|
+
/>
|
|
476
|
+
<input
|
|
477
|
+
type="number"
|
|
478
|
+
value={labelReservedMaxSize}
|
|
479
|
+
onChange={(event) => setLabelReservedMaxSize(event.target.value === '' ? '' : Math.max(0, Number(event.target.value) || 0))}
|
|
480
|
+
style={{ width: 64, padding: '6px 8px' }}
|
|
481
|
+
min={0}
|
|
482
|
+
placeholder="max"
|
|
483
|
+
disabled={mode === 'manual'}
|
|
484
|
+
/>
|
|
485
|
+
</div>
|
|
486
|
+
|
|
403
487
|
<button type="button" onClick={handleApplyLayout} style={{ padding: '6px 12px', fontWeight: 600 }}>
|
|
404
488
|
Apply layout
|
|
405
489
|
</button>
|
|
@@ -420,11 +504,27 @@ const AutoLayoutDemo = () => {
|
|
|
420
504
|
alignItems: 'center',
|
|
421
505
|
}}
|
|
422
506
|
>
|
|
423
|
-
<span style={{ fontWeight: 600 }}>Target:</span> {targetElement ? targetElement.id : '—'}
|
|
507
|
+
<span style={{ fontWeight: 600 }}>Target:</span> {targetElement ? targetElement.id : '—'}
|
|
424
508
|
<span style={{ width: 6, height: 6, borderRadius: '50%', background: '#1f4d99', display: 'inline-block' }} />
|
|
425
509
|
Last trigger: <strong>{lastTrigger}</strong>
|
|
426
|
-
</span>
|
|
427
|
-
|
|
510
|
+
</span>
|
|
511
|
+
<button
|
|
512
|
+
type="button"
|
|
513
|
+
onClick={() => handleSetLabelContent(shortLabel)}
|
|
514
|
+
style={{ padding: '6px 10px' }}
|
|
515
|
+
disabled={!targetElement || mode === 'manual'}
|
|
516
|
+
>
|
|
517
|
+
Label short
|
|
518
|
+
</button>
|
|
519
|
+
<button
|
|
520
|
+
type="button"
|
|
521
|
+
onClick={() => handleSetLabelContent(longLabel)}
|
|
522
|
+
style={{ padding: '6px 10px' }}
|
|
523
|
+
disabled={!targetElement || mode === 'manual'}
|
|
524
|
+
>
|
|
525
|
+
Label long
|
|
526
|
+
</button>
|
|
527
|
+
</div>
|
|
428
528
|
|
|
429
529
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
|
430
530
|
<div>
|
|
@@ -447,6 +547,7 @@ const AutoLayoutDemo = () => {
|
|
|
447
547
|
<ul style={{ marginTop: 0, paddingLeft: 18, fontSize: 13 }}>
|
|
448
548
|
<li>Horizontal: try center vs bottom alignment and larger padding.</li>
|
|
449
549
|
<li>Vertical: see parent grow taller as children stack.</li>
|
|
550
|
+
<li>Label lane: compare none/fixed/flexible and observe children start below the reserved lane.</li>
|
|
450
551
|
<li>Fit main-axis distribute fills inner layout space across siblings.</li>
|
|
451
552
|
<li>Fit cross-axis stretch extends children across inner cross axis.</li>
|
|
452
553
|
<li>Min/max fit guards cap distributed or stretched child sizes.</li>
|
|
@@ -499,3 +600,4 @@ const AutoLayoutDemo = () => {
|
|
|
499
600
|
};
|
|
500
601
|
|
|
501
602
|
export default AutoLayoutDemo;
|
|
603
|
+
|
|
@@ -103,7 +103,11 @@ const DeletionEventsDemo = () => {
|
|
|
103
103
|
onToggleSnap={controls.handleToggleSnap}
|
|
104
104
|
onManualRender={controls.handleManualRender}
|
|
105
105
|
onToggleLinkRouting={controls.handleToggleLinkRouting}
|
|
106
|
-
onAction={controls.handleAction}
|
|
106
|
+
onAction={controls.handleAction}
|
|
107
|
+
onExportImage={controls.handleExportImage}
|
|
108
|
+
onClearExportPreview={controls.handleClearExportPreview}
|
|
109
|
+
exportPreviewDataUrl={controls.exportPreviewDataUrl}
|
|
110
|
+
exportError={controls.exportError}
|
|
107
111
|
/>
|
|
108
112
|
<div style={{ display: 'grid', gap: 12, marginBottom: 12 }}>
|
|
109
113
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
@@ -145,3 +149,4 @@ const DeletionEventsDemo = () => {
|
|
|
145
149
|
};
|
|
146
150
|
|
|
147
151
|
export default DeletionEventsDemo;
|
|
152
|
+
|
|
@@ -95,6 +95,10 @@ const EngineEventsDemo = () => {
|
|
|
95
95
|
onManualRender={controls.handleManualRender}
|
|
96
96
|
onToggleLinkRouting={controls.handleToggleLinkRouting}
|
|
97
97
|
onAction={controls.handleAction}
|
|
98
|
+
onExportImage={controls.handleExportImage}
|
|
99
|
+
onClearExportPreview={controls.handleClearExportPreview}
|
|
100
|
+
exportPreviewDataUrl={controls.exportPreviewDataUrl}
|
|
101
|
+
exportError={controls.exportError}
|
|
98
102
|
/>
|
|
99
103
|
<div style={{ display: 'grid', gap: 12, marginBottom: 12 }}>
|
|
100
104
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
@@ -149,3 +153,4 @@ const EngineEventsDemo = () => {
|
|
|
149
153
|
};
|
|
150
154
|
|
|
151
155
|
export default EngineEventsDemo;
|
|
156
|
+
|