Repository: bpmn-io/bpmn-js Branch: develop Commit: 0c1e5560a0cb Files: 766 Total size: 4.1 MB Directory structure: gitextract_zf5udxc2/ ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── BUG_REPORT.md │ │ ├── FEATURE_REQUEST.md │ │ ├── TASK.md │ │ └── config.yml │ ├── merge-me.yml │ └── workflows/ │ ├── CI.yml │ ├── CODE_SCANNING.yml │ ├── COMMENT_TARGETS_MAIN.yml │ ├── MERGE_MAIN_TO_DEVELOP.yml │ ├── POST_RELEASE.yml │ └── RELEASE.yml ├── .gitignore ├── .release-please-manifest.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets/ │ └── bpmn-js.css ├── docs/ │ ├── project/ │ │ ├── SETUP.md │ │ ├── setup-alternative.sh │ │ ├── setup.bat │ │ └── setup.sh │ └── translations.json ├── eslint.config.mjs ├── lib/ │ ├── BaseModeler.js │ ├── BaseModeler.spec.ts │ ├── BaseViewer.js │ ├── BaseViewer.spec.ts │ ├── Modeler.js │ ├── Modeler.spec.ts │ ├── NavigatedViewer.js │ ├── NavigatedViewer.spec.ts │ ├── Viewer.js │ ├── Viewer.spec.ts │ ├── core/ │ │ └── index.js │ ├── draw/ │ │ ├── BpmnRenderUtil.js │ │ ├── BpmnRenderer.js │ │ ├── PathMap.js │ │ ├── TextRenderer.js │ │ ├── TextRenderer.spec.ts │ │ └── index.js │ ├── features/ │ │ ├── align-elements/ │ │ │ ├── AlignElementsContextPadProvider.js │ │ │ ├── AlignElementsIcons.js │ │ │ ├── AlignElementsMenuProvider.js │ │ │ ├── BpmnAlignElements.js │ │ │ └── index.js │ │ ├── append-preview/ │ │ │ ├── AppendPreview.js │ │ │ └── index.js │ │ ├── auto-place/ │ │ │ ├── BpmnAutoPlace.js │ │ │ ├── BpmnAutoPlaceUtil.js │ │ │ └── index.js │ │ ├── auto-resize/ │ │ │ ├── BpmnAutoResize.js │ │ │ ├── BpmnAutoResizeProvider.js │ │ │ └── index.js │ │ ├── context-pad/ │ │ │ ├── ContextPadProvider.js │ │ │ └── index.js │ │ ├── copy-paste/ │ │ │ ├── BpmnCopyPaste.js │ │ │ ├── ModdleCopy.js │ │ │ └── index.js │ │ ├── di-ordering/ │ │ │ ├── BpmnDiOrdering.js │ │ │ └── index.js │ │ ├── distribute-elements/ │ │ │ ├── BpmnDistributeElements.js │ │ │ ├── DistributeElementsIcons.js │ │ │ ├── DistributeElementsMenuProvider.js │ │ │ └── index.js │ │ ├── drilldown/ │ │ │ ├── DrilldownBreadcrumbs.js │ │ │ ├── DrilldownCentering.js │ │ │ ├── DrilldownOverlayBehavior.js │ │ │ ├── SubprocessCompatibility.js │ │ │ └── index.js │ │ ├── editor-actions/ │ │ │ ├── BpmnEditorActions.js │ │ │ └── index.js │ │ ├── grid-snapping/ │ │ │ ├── BpmnGridSnapping.js │ │ │ ├── behavior/ │ │ │ │ ├── GridSnappingAutoPlaceBehavior.js │ │ │ │ ├── GridSnappingLayoutConnectionBehavior.js │ │ │ │ ├── GridSnappingParticipantBehavior.js │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── interaction-events/ │ │ │ ├── BpmnInteractionEvents.js │ │ │ └── index.js │ │ ├── keyboard/ │ │ │ ├── BpmnKeyboardBindings.js │ │ │ └── index.js │ │ ├── label-editing/ │ │ │ ├── LabelEditingPreview.js │ │ │ ├── LabelEditingProvider.js │ │ │ ├── LabelUtil.js │ │ │ ├── cmd/ │ │ │ │ └── UpdateLabelHandler.js │ │ │ └── index.js │ │ ├── label-link/ │ │ │ ├── LabelLink.js │ │ │ └── index.js │ │ ├── modeling/ │ │ │ ├── BpmnFactory.js │ │ │ ├── BpmnLayouter.js │ │ │ ├── BpmnUpdater.js │ │ │ ├── ElementFactory.js │ │ │ ├── ElementFactory.test.ts │ │ │ ├── Modeling.js │ │ │ ├── Modeling.test.ts │ │ │ ├── behavior/ │ │ │ │ ├── AdaptiveLabelPositioningBehavior.js │ │ │ │ ├── AppendBehavior.js │ │ │ │ ├── AssociationBehavior.js │ │ │ │ ├── AttachEventBehavior.js │ │ │ │ ├── BoundaryEventBehavior.js │ │ │ │ ├── CompensateBoundaryEventBehavior.js │ │ │ │ ├── CreateBehavior.js │ │ │ │ ├── CreateDataObjectBehavior.js │ │ │ │ ├── CreateParticipantBehavior.js │ │ │ │ ├── DataInputAssociationBehavior.js │ │ │ │ ├── DataStoreBehavior.js │ │ │ │ ├── DeleteLaneBehavior.js │ │ │ │ ├── DetachEventBehavior.js │ │ │ │ ├── DropOnFlowBehavior.js │ │ │ │ ├── EventBasedGatewayBehavior.js │ │ │ │ ├── FixHoverBehavior.js │ │ │ │ ├── GroupBehavior.js │ │ │ │ ├── ImportDockingFix.js │ │ │ │ ├── IsHorizontalFix.js │ │ │ │ ├── LabelBehavior.js │ │ │ │ ├── LayoutConnectionBehavior.js │ │ │ │ ├── MessageFlowBehavior.js │ │ │ │ ├── NonInterruptingBehavior.js │ │ │ │ ├── RemoveElementBehavior.js │ │ │ │ ├── RemoveEmbeddedLabelBoundsBehavior.js │ │ │ │ ├── RemoveParticipantBehavior.js │ │ │ │ ├── ReplaceConnectionBehavior.js │ │ │ │ ├── ReplaceElementBehaviour.js │ │ │ │ ├── ResizeBehavior.js │ │ │ │ ├── ResizeLaneBehavior.js │ │ │ │ ├── RootElementReferenceBehavior.js │ │ │ │ ├── SetCompensationActivityAfterPasteBehavior.js │ │ │ │ ├── SpaceToolBehavior.js │ │ │ │ ├── SubProcessPlaneBehavior.js │ │ │ │ ├── SubProcessStartEventBehavior.js │ │ │ │ ├── TextAnnotationBehavior.js │ │ │ │ ├── ToggleCollapseConnectionBehaviour.js │ │ │ │ ├── ToggleElementCollapseBehaviour.js │ │ │ │ ├── UnclaimIdBehavior.js │ │ │ │ ├── UnsetDefaultFlowBehavior.js │ │ │ │ ├── UpdateFlowNodeRefsBehavior.js │ │ │ │ ├── index.js │ │ │ │ └── util/ │ │ │ │ ├── CategoryUtil.js │ │ │ │ ├── ConnectionLayoutUtil.js │ │ │ │ ├── GeometricUtil.js │ │ │ │ ├── LabelLayoutUtil.js │ │ │ │ ├── LayoutUtil.js │ │ │ │ ├── LineAttachmentUtil.js │ │ │ │ ├── LineIntersect.js │ │ │ │ ├── NonInterruptingUtil.js │ │ │ │ └── ResizeUtil.js │ │ │ ├── cmd/ │ │ │ │ ├── AddLaneHandler.js │ │ │ │ ├── IdClaimHandler.js │ │ │ │ ├── ResizeLaneHandler.js │ │ │ │ ├── SetColorHandler.js │ │ │ │ ├── SplitLaneHandler.js │ │ │ │ ├── UpdateCanvasRootHandler.js │ │ │ │ ├── UpdateFlowNodeRefsHandler.js │ │ │ │ ├── UpdateModdlePropertiesHandler.js │ │ │ │ ├── UpdatePropertiesHandler.js │ │ │ │ └── UpdateSemanticParentHandler.js │ │ │ ├── index.js │ │ │ └── util/ │ │ │ ├── LaneUtil.js │ │ │ └── ModelingUtil.js │ │ ├── modeling-feedback/ │ │ │ ├── ModelingFeedback.js │ │ │ └── index.js │ │ ├── ordering/ │ │ │ ├── BpmnOrderingProvider.js │ │ │ └── index.js │ │ ├── outline/ │ │ │ ├── OutlineProvider.js │ │ │ ├── OutlineUtil.js │ │ │ └── index.js │ │ ├── palette/ │ │ │ ├── PaletteProvider.js │ │ │ └── index.js │ │ ├── popup-menu/ │ │ │ ├── ReplaceMenuProvider.js │ │ │ ├── index.js │ │ │ └── util/ │ │ │ ├── Icons.js │ │ │ └── TypeUtil.js │ │ ├── replace/ │ │ │ ├── BpmnReplace.js │ │ │ ├── ReplaceOptions.js │ │ │ └── index.js │ │ ├── replace-preview/ │ │ │ ├── BpmnReplacePreview.js │ │ │ └── index.js │ │ ├── rules/ │ │ │ ├── BpmnRules.js │ │ │ └── index.js │ │ ├── search/ │ │ │ ├── BpmnSearchProvider.js │ │ │ └── index.js │ │ ├── snapping/ │ │ │ ├── BpmnConnectSnapping.js │ │ │ ├── BpmnCreateMoveSnapping.js │ │ │ ├── BpmnSnappingUtil.js │ │ │ └── index.js │ │ └── space-tool/ │ │ ├── BpmnSpaceTool.js │ │ └── index.js │ ├── import/ │ │ ├── BpmnImporter.js │ │ ├── BpmnTreeWalker.js │ │ ├── Importer.js │ │ ├── Util.js │ │ └── index.js │ ├── index.js │ ├── model/ │ │ └── Types.ts │ └── util/ │ ├── AnnotationUtil.js │ ├── CompatibilityUtil.js │ ├── DiUtil.js │ ├── DrilldownUtil.js │ ├── LabelUtil.js │ ├── ModelUtil.js │ ├── PoweredByUtil.js │ ├── Types.js │ └── Types.ts ├── package.json ├── renovate.json ├── resources/ │ ├── banner-min.txt │ ├── banner.txt │ └── initial.bpmn ├── rollup.config.js ├── tasks/ │ ├── build-distro.mjs │ ├── stages/ │ │ ├── await-published │ │ ├── update-demo │ │ ├── update-examples │ │ ├── update-integration-test │ │ ├── update-translations │ │ └── update-website │ └── test-distro.mjs ├── test/ │ ├── TestHelper.js │ ├── config/ │ │ ├── karma.distro.js │ │ ├── karma.unit.js │ │ └── translation-reporter.js │ ├── coverageBundle.js │ ├── distro/ │ │ ├── bpmn-modeler.js │ │ ├── bpmn-navigated-viewer.js │ │ ├── bpmn-viewer.js │ │ └── helper.js │ ├── fixtures/ │ │ ├── bpmn/ │ │ │ ├── align-elements.bpmn │ │ │ ├── basic.bpmn │ │ │ ├── boundary-events.bpmn │ │ │ ├── collaboration/ │ │ │ │ ├── collaboration-data-store.bpmn │ │ │ │ ├── collaboration-empty-participant.bpmn │ │ │ │ ├── collaboration-message-flows.bpmn │ │ │ │ ├── collaboration-participant.bpmn │ │ │ │ ├── process-empty.bpmn │ │ │ │ └── process.bpmn │ │ │ ├── collaboration-data-items.bpmn │ │ │ ├── collaboration-message-flows.bpmn │ │ │ ├── collaboration-sequence-flows.bpmn │ │ │ ├── collaboration-vertical.bpmn │ │ │ ├── collaboration.bpmn │ │ │ ├── collapsed-sub-process-legacy.bpmn │ │ │ ├── collapsed-sub-process.bpmn │ │ │ ├── complex.bpmn │ │ │ ├── conditions.bpmn │ │ │ ├── containers.bpmn │ │ │ ├── distribute-elements-filtering.bpmn │ │ │ ├── distribute-elements-filtering.collaboration.bpmn │ │ │ ├── distribute-elements.bpmn │ │ │ ├── draw/ │ │ │ │ ├── activity-markers-combination.bpmn │ │ │ │ ├── activity-markers-simple.bpmn │ │ │ │ ├── activity-markers.bpmn │ │ │ │ ├── associations.bpmn │ │ │ │ ├── boundary-event-with-refnode.bpmn │ │ │ │ ├── boundary-event-without-refnode.bpmn │ │ │ │ ├── boundary-event-z-index.bpmn │ │ │ │ ├── call-activity.bpmn │ │ │ │ ├── conditional-flow-default.bpmn │ │ │ │ ├── conditional-flow-gateways.bpmn │ │ │ │ ├── conditional-flow-typed-task.bpmn │ │ │ │ ├── conditional-flow.bpmn │ │ │ │ ├── data-objects.bpmn │ │ │ │ ├── event-subprocess-icons.bpmn │ │ │ │ ├── event-subprocesses-collapsed.bpmn │ │ │ │ ├── event-subprocesses-expanded.bpmn │ │ │ │ ├── events-interrupting.bpmn │ │ │ │ ├── events.bpmn │ │ │ │ ├── gateway-type-default.bpmn │ │ │ │ ├── gateways.bpmn │ │ │ │ ├── group-name.bpmn │ │ │ │ ├── group.bpmn │ │ │ │ ├── message-label.bpmn │ │ │ │ ├── message-marker.bpmn │ │ │ │ ├── pools-with-collection-marker.bpmn │ │ │ │ ├── pools.bpmn │ │ │ │ ├── task-types.bpmn │ │ │ │ ├── text-annotation.bpmn │ │ │ │ ├── vertical-pools.bpmn │ │ │ │ └── xor.bpmn │ │ │ ├── empty-definitions.bpmn │ │ │ ├── error/ │ │ │ │ ├── categoryValue.bpmn │ │ │ │ ├── di-plane-no-bpmn-element.bpmn │ │ │ │ ├── duplicate-ids.bpmn │ │ │ │ ├── invalid-child.bpmn │ │ │ │ ├── missing-namespace.bpmn │ │ │ │ ├── no-process-collaboration.bpmn │ │ │ │ └── no-xml.txt │ │ │ ├── event-sub-processes.bpmn │ │ │ ├── extension/ │ │ │ │ ├── camunda.bpmn │ │ │ │ ├── custom-override.bpmn │ │ │ │ └── custom.bpmn │ │ │ ├── features/ │ │ │ │ ├── drop/ │ │ │ │ │ ├── drop.bpmn │ │ │ │ │ └── recursive-task.bpmn │ │ │ │ ├── replace/ │ │ │ │ │ ├── 01_replace.bpmn │ │ │ │ │ ├── association-gateways.bpmn │ │ │ │ │ ├── cancel-events.bpmn │ │ │ │ │ ├── connection.bpmn │ │ │ │ │ ├── copy-properties.bpmn │ │ │ │ │ ├── data-elements.bpmn │ │ │ │ │ ├── data-stores-positioned-against-participant.bpmn │ │ │ │ │ └── participants.bpmn │ │ │ │ └── rules/ │ │ │ │ ├── event-based-gateway-outgoing-edge.bpmn │ │ │ │ ├── link-event.bpmn │ │ │ │ └── text-annotation-association.bpmn │ │ │ ├── flow-markers.bpmn │ │ │ ├── import/ │ │ │ │ ├── boundaryEvent.bpmn │ │ │ │ ├── collaboration.bpmn │ │ │ │ ├── collapsed/ │ │ │ │ │ ├── collaboration.bpmn │ │ │ │ │ ├── process.bpmn │ │ │ │ │ └── processWithChildren.bpmn │ │ │ │ ├── collapsed-subprocess.bpmn │ │ │ │ ├── data-store.inside-participant.bpmn │ │ │ │ ├── data-store.outside-participant.dangling.bpmn │ │ │ │ ├── data-store.outside-participant.participant.bpmn │ │ │ │ ├── data-store.outside-participant.subprocess.bpmn │ │ │ │ ├── default-attrs.bpmn │ │ │ │ ├── error/ │ │ │ │ │ ├── boundaryEvent-invalidAttachToRef.bpmn │ │ │ │ │ ├── boundaryEvent-missingAttachToRef.bpmn │ │ │ │ │ ├── dangling-process-message-flow.bpmn │ │ │ │ │ ├── invalid-flow-element.bpmn │ │ │ │ │ └── multiple-dis.bpmn │ │ │ │ ├── groups.bpmn │ │ │ │ ├── labels/ │ │ │ │ │ ├── collaboration-message-flows.bpmn │ │ │ │ │ ├── collaboration.bpmn │ │ │ │ │ ├── embedded.bpmn │ │ │ │ │ ├── external-no-di.bpmn │ │ │ │ │ └── external.bpmn │ │ │ │ ├── multiple-diagrams.bpmn │ │ │ │ ├── position/ │ │ │ │ │ └── position-testcase.bpmn │ │ │ │ ├── process.bpmn │ │ │ │ └── text-annotation-message-flow.bpmn │ │ │ ├── kitchen-sink.bpmn │ │ │ ├── multiple-diagrams-lanesets.bpmn │ │ │ ├── multiple-diagrams-overlapping-di.bpmn │ │ │ ├── multiple-diagrams.bpmn │ │ │ ├── multiple-nested-processes.bpmn │ │ │ ├── nested-subprocesses.bpmn │ │ │ ├── sequence-flows.bpmn │ │ │ ├── simple-resizable.bpmn │ │ │ └── simple.bpmn │ │ └── json/ │ │ └── model/ │ │ ├── camunda.json │ │ ├── custom-override.json │ │ └── custom.json │ ├── helper/ │ │ ├── TranslationCollector.js │ │ └── index.js │ ├── integration/ │ │ ├── CustomElementsSpec.js │ │ ├── ReimportSpec.js │ │ ├── SimpleModelingSpec.js │ │ ├── custom-elements/ │ │ │ ├── CustomElementFactory.js │ │ │ ├── CustomRenderer.js │ │ │ ├── CustomRules.js │ │ │ ├── CustomUpdater.js │ │ │ └── index.js │ │ └── model/ │ │ └── BpmnModdleSpec.js │ ├── matchers/ │ │ ├── BoundsMatchers.js │ │ ├── ConnectionMatchers.js │ │ └── JSONMatcher.js │ ├── spec/ │ │ ├── BaseModelerSpec.js │ │ ├── BaseViewerSpec.js │ │ ├── Modeler.copy-paste.a.bpmn │ │ ├── Modeler.copy-paste.b.bpmn │ │ ├── Modeler.copy-paste.complex.bpmn │ │ ├── Modeler.copy-paste.empty.bpmn │ │ ├── ModelerSpec.js │ │ ├── NavigatedViewerSpec.js │ │ ├── ViewerSpec.js │ │ ├── draw/ │ │ │ ├── BpmnRenderUtilSpec.js │ │ │ ├── BpmnRenderer.colors.bpmn │ │ │ ├── BpmnRenderer.connection-colors.bpmn │ │ │ ├── BpmnRenderer.group-colors.bpmn │ │ │ ├── BpmnRenderer.labels.bpmn │ │ │ ├── BpmnRenderer.no-event-icons.bpmn │ │ │ ├── BpmnRenderer.sequenceFlow-no-source.bpmn │ │ │ ├── BpmnRenderer.simple-cropping.bpmn │ │ │ ├── BpmnRendererSpec.js │ │ │ ├── TextRenderer.bpmn │ │ │ ├── TextRendererSpec.js │ │ │ └── custom-renderer/ │ │ │ ├── CustomRenderer.js │ │ │ └── index.js │ │ ├── environment/ │ │ │ └── MockingSpec.js │ │ ├── features/ │ │ │ ├── align-elements/ │ │ │ │ ├── AlignElementsContextPadProviderSpec.js │ │ │ │ ├── AlignElementsMenuProviderSpec.js │ │ │ │ └── BpmnAlignElementsSpec.js │ │ │ ├── append-preview/ │ │ │ │ ├── AppendPreview.bpmn │ │ │ │ └── AppendPreviewSpec.js │ │ │ ├── auto-place/ │ │ │ │ ├── BpmnAutoPlace.boundary-events.bpmn │ │ │ │ ├── BpmnAutoPlace.bpmn │ │ │ │ ├── BpmnAutoPlace.multi-connection.bpmn │ │ │ │ ├── BpmnAutoPlace.subprocess.bpmn │ │ │ │ ├── BpmnAutoPlace.subprocess.horizontal.bpmn │ │ │ │ ├── BpmnAutoPlace.subprocess.vertical.bpmn │ │ │ │ ├── BpmnAutoPlace.vertical.boundary-events.bpmn │ │ │ │ ├── BpmnAutoPlace.vertical.bpmn │ │ │ │ ├── BpmnAutoPlace.vertical.multi-connection.bpmn │ │ │ │ └── BpmnAutoPlaceSpec.js │ │ │ ├── auto-resize/ │ │ │ │ ├── AutoResize.lanes.bpmn │ │ │ │ ├── AutoResize.multi-selection.bpmn │ │ │ │ ├── AutoResize.nested-sub-processes.bpmn │ │ │ │ ├── AutoResize.participant.bpmn │ │ │ │ ├── AutoResize.space-tool.bpmn │ │ │ │ ├── AutoResize.sub-processes.bpmn │ │ │ │ └── AutoResizeSpec.js │ │ │ ├── context-pad/ │ │ │ │ ├── ContextPad.activation.bpmn │ │ │ │ └── ContextPadProviderSpec.js │ │ │ ├── copy-paste/ │ │ │ │ ├── BpmnCopyPasteSpec.js │ │ │ │ ├── ModdleCopySpec.js │ │ │ │ ├── basic.bpmn │ │ │ │ ├── collaboration-multiple.bpmn │ │ │ │ ├── collaboration.bpmn │ │ │ │ ├── collapsed-subprocess.bpmn │ │ │ │ ├── complex.bpmn │ │ │ │ ├── copy-properties.bpmn │ │ │ │ ├── data-associations.bpmn │ │ │ │ ├── event-based-gateway.bpmn │ │ │ │ ├── nested-subprocess-annotations.bpmn │ │ │ │ └── properties.bpmn │ │ │ ├── distribute-elements/ │ │ │ │ ├── BpmnDistributeElementsSpec.js │ │ │ │ └── DistributeElementsMenuProviderSpec.js │ │ │ ├── drilldown/ │ │ │ │ ├── DrilldownIntegrationSpec.js │ │ │ │ ├── DrilldownOverlayBehaviorSpec.bpmn │ │ │ │ ├── DrilldownOverlaysBehaviorSpec.js │ │ │ │ ├── DrilldownSpec.js │ │ │ │ ├── collaboration-subprocesses.bpmn │ │ │ │ ├── diagram-missing-plane.bpmn │ │ │ │ ├── legacy-subprocesses.bpmn │ │ │ │ ├── nested-subprocesses.bpmn │ │ │ │ ├── plane-missing-bpmnelement.bpmn │ │ │ │ ├── process-missing-bpmndiagram.bpmn │ │ │ │ ├── subprocess-missing-bpmndiagram.bpmn │ │ │ │ └── subprocess-missing-di.bpmn │ │ │ ├── editor-actions/ │ │ │ │ └── BpmnEditorActionsSpec.js │ │ │ ├── grid-snapping/ │ │ │ │ ├── BpmnGridSnapping.bpmn │ │ │ │ ├── BpmnGridSnappingSpec.js │ │ │ │ ├── basic.bpmn │ │ │ │ └── behavior/ │ │ │ │ ├── AutoPlaceBehavior.bpmn │ │ │ │ ├── AutoPlaceBehaviorSpec.js │ │ │ │ ├── AutoResizeBehavior.bpmn │ │ │ │ ├── AutoResizeBehaviorSpec.js │ │ │ │ ├── CreateParticipantBehavior.bpmn │ │ │ │ ├── CreateParticipantBehaviorSpec.js │ │ │ │ ├── LayoutConnectionBehavior.bpmn │ │ │ │ └── LayoutConnectionBehaviorSpec.js │ │ │ ├── interaction-events/ │ │ │ │ └── BpmnInteractionEventsSpec.js │ │ │ ├── keyboard/ │ │ │ │ └── BpmnKeyboardBindingsSpec.js │ │ │ ├── keyboard-move-selection/ │ │ │ │ ├── KeyboardMoveSelectionSpec.js │ │ │ │ └── keyboard-move-selection.bpmn │ │ │ ├── label-editing/ │ │ │ │ ├── LabelEditing.bpmn │ │ │ │ ├── LabelEditingPreviewSpec.js │ │ │ │ └── LabelEditingProviderSpec.js │ │ │ ├── label-link/ │ │ │ │ ├── LabelLink.bpmn │ │ │ │ └── LabelLinkSpec.js │ │ │ ├── modeling/ │ │ │ │ ├── AppendShapeSpec.js │ │ │ │ ├── BendpointsSpec.js │ │ │ │ ├── BpmnFactorySpec.js │ │ │ │ ├── BpmnUpdater.bpmn │ │ │ │ ├── BpmnUpdater.incompleteDi.bpmn │ │ │ │ ├── BpmnUpdaterSpec.js │ │ │ │ ├── CreateConnectionSpec.js │ │ │ │ ├── DeleteConnectionSpec.js │ │ │ │ ├── DeleteParticipantSpec.js │ │ │ │ ├── DeleteShape.cropping.bpmn │ │ │ │ ├── DeleteShapeSpec.js │ │ │ │ ├── DropSpec.js │ │ │ │ ├── ElementFactory.bpmn │ │ │ │ ├── ElementFactorySpec.js │ │ │ │ ├── IdClaim.bpmn │ │ │ │ ├── IdClaimSpec.js │ │ │ │ ├── LabelBoundsSpec.js │ │ │ │ ├── LabelBoundsSpec.simple.bpmn │ │ │ │ ├── LabelLayouting.initial.bpmn │ │ │ │ ├── LabelLayouting.integration.bpmn │ │ │ │ ├── LabelLayouting.move.bpmn │ │ │ │ ├── LabelLayoutingSpec.js │ │ │ │ ├── LoggingCroppingConnectionDocking.js │ │ │ │ ├── MoveConnectionSpec.js │ │ │ │ ├── MoveElements.boundary-connection.bpmn │ │ │ │ ├── MoveElements.centered-connection.bpmn │ │ │ │ ├── MoveElements.collaboration-association.bpmn │ │ │ │ ├── MoveElements.data-input-output.bpmn │ │ │ │ ├── MoveElements.eventBasedTargets.bpmn │ │ │ │ ├── MoveElements.flow-collaboration.bpmn │ │ │ │ ├── MoveElementsSpec.js │ │ │ │ ├── MoveRulesSpec.js │ │ │ │ ├── MoveShapeSpec.js │ │ │ │ ├── MoveStress.bpmn │ │ │ │ ├── MoveStressSpec.js │ │ │ │ ├── ResizeShapeSpec.js │ │ │ │ ├── SetColor.bpmn │ │ │ │ ├── SetColorSpec.js │ │ │ │ ├── UpdateAttachmentSpec.js │ │ │ │ ├── UpdateLabel.bpmn │ │ │ │ ├── UpdateLabelSpec.js │ │ │ │ ├── UpdateModdleProperties.dataObject.bpmn │ │ │ │ ├── UpdateModdleProperties.error.bpmn │ │ │ │ ├── UpdateModdlePropertiesSpec.js │ │ │ │ ├── UpdatePropertiesSpec.js │ │ │ │ ├── UpdateSemanticParent.bpmn │ │ │ │ ├── UpdateSemanticParentSpec.js │ │ │ │ ├── append/ │ │ │ │ │ └── TextAnnotationSpec.js │ │ │ │ ├── behavior/ │ │ │ │ │ ├── AdaptiveLabelPositioningBehavior.basics.bpmn │ │ │ │ │ ├── AdaptiveLabelPositioningBehavior.boundary-events.bpmn │ │ │ │ │ ├── AdaptiveLabelPositioningBehaviorSpec.js │ │ │ │ │ ├── AssociationBehavior.bpmn │ │ │ │ │ ├── AssociationBehaviorSpec.js │ │ │ │ │ ├── AttachEventBehavior.bpmn │ │ │ │ │ ├── AttachEventBehaviorSpec.js │ │ │ │ │ ├── BoundaryEvent.bpmn │ │ │ │ │ ├── BoundaryEventBehaviorSpec.js │ │ │ │ │ ├── CompensateBoundaryEventBehavior.bpmn │ │ │ │ │ ├── CompensateBoundaryEventBehaviorSpec.js │ │ │ │ │ ├── CompensationAssociationBehavior.bpmn │ │ │ │ │ ├── CompensationAssociationBehaviorSpec.js │ │ │ │ │ ├── CreateBehavior.bpmn │ │ │ │ │ ├── CreateBehaviorSpec.js │ │ │ │ │ ├── CreateParticipantBehaviorSpec.js │ │ │ │ │ ├── DataInputAssociationBehavior.bpmn │ │ │ │ │ ├── DataInputAssociationBehaviorSpec.js │ │ │ │ │ ├── DataObjectBehavior.create-data-association.bpmn │ │ │ │ │ ├── DataObjectBehavior.data-object-reference.bpmn │ │ │ │ │ ├── DataObjectBehavior.remove-data-association.bpmn │ │ │ │ │ ├── DataObjectBehaviorSpec.js │ │ │ │ │ ├── DataStoreBehavior.bpmn │ │ │ │ │ ├── DataStoreBehavior.collaboration.bpmn │ │ │ │ │ ├── DataStoreBehavior.connect.bpmn │ │ │ │ │ ├── DataStoreBehavior.empty-pool.bpmn │ │ │ │ │ ├── DataStoreBehavior.process.bpmn │ │ │ │ │ ├── DataStoreBehavior.remove-participant.bpmn │ │ │ │ │ ├── DataStoreBehaviorSpec.js │ │ │ │ │ ├── DetachEventBehavior.bpmn │ │ │ │ │ ├── DetachEventBehaviorSpec.js │ │ │ │ │ ├── DropOnFlowBehavior.bpmn │ │ │ │ │ ├── DropOnFlowBehaviorSpec.js │ │ │ │ │ ├── EventBasedGatewayBehavior.bpmn │ │ │ │ │ ├── EventBasedGatewayBehaviorSpec.js │ │ │ │ │ ├── FixHoverBehavior.annotation.bpmn │ │ │ │ │ ├── FixHoverBehavior.group.bpmn │ │ │ │ │ ├── FixHoverBehavior.label.bpmn │ │ │ │ │ ├── FixHoverBehavior.lane-connect.bpmn │ │ │ │ │ ├── FixHoverBehavior.participant.bpmn │ │ │ │ │ ├── FixHoverBehaviorSpec.js │ │ │ │ │ ├── GroupBehaviorSpec.bpmn │ │ │ │ │ ├── GroupBehaviorSpec.js │ │ │ │ │ ├── ImportDockingFix.bpmn │ │ │ │ │ ├── ImportDockingFixSpec.js │ │ │ │ │ ├── IsHorizontalFix.bpmn │ │ │ │ │ ├── IsHorizontalFixSpec.js │ │ │ │ │ ├── LabelBehavior.bpmn │ │ │ │ │ ├── LabelBehavior.copyPaste.bpmn │ │ │ │ │ ├── LabelBehaviorSpec.js │ │ │ │ │ ├── LayoutConnectionBehavior.bpmn │ │ │ │ │ ├── LayoutConnectionBehaviorSpec.js │ │ │ │ │ ├── MessageFlowBehavior.bpmn │ │ │ │ │ ├── MessageFlowBehaviorSpec.js │ │ │ │ │ ├── NonInterruptingBehavior.bpmn │ │ │ │ │ ├── NonInterruptingBehaviorSpec.js │ │ │ │ │ ├── ReconnectConnection.data-association.bpmn │ │ │ │ │ ├── ReconnectConnectionSpec.js │ │ │ │ │ ├── RemoveElementBehavior.bpmn │ │ │ │ │ ├── RemoveElementBehavior.diagonal.bpmn │ │ │ │ │ ├── RemoveElementBehavior.perpendicular.bpmn │ │ │ │ │ ├── RemoveElementBehavior.vertical.diagonal.bpmn │ │ │ │ │ ├── RemoveElementBehaviorSpec.js │ │ │ │ │ ├── RemoveEmbeddedLabelBoundsBehavior.bpmn │ │ │ │ │ ├── RemoveEmbeddedLabelBoundsBehaviorSpec.js │ │ │ │ │ ├── RemoveParticipantBehaviorSpec.js │ │ │ │ │ ├── ReplaceConnectionBehavior.association.bpmn │ │ │ │ │ ├── ReplaceConnectionBehavior.boundary-events.bpmn │ │ │ │ │ ├── ReplaceConnectionBehavior.message-sequence-flow.bpmn │ │ │ │ │ ├── ReplaceConnectionBehaviorSpec.js │ │ │ │ │ ├── ReplaceElementBehaviourSpec.js │ │ │ │ │ ├── ResizeBehavior.lanes.bpmn │ │ │ │ │ ├── ResizeBehavior.lanes.vertical.bpmn │ │ │ │ │ ├── ResizeBehavior.participant.bpmn │ │ │ │ │ ├── ResizeBehavior.participant.vertical.bpmn │ │ │ │ │ ├── ResizeBehavior.subProcess.bpmn │ │ │ │ │ ├── ResizeBehavior.textAnnotation.bpmn │ │ │ │ │ ├── ResizeBehavior.utility.lanes-flowNodes.bpmn │ │ │ │ │ ├── ResizeBehavior.utility.lanes.bpmn │ │ │ │ │ ├── ResizeBehavior.utility.lanes.vertical-flowNodes.bpmn │ │ │ │ │ ├── ResizeBehavior.utility.lanes.vertical.bpmn │ │ │ │ │ ├── ResizeBehaviorSpec.js │ │ │ │ │ ├── RootElementReferenceBehavior.bpmn │ │ │ │ │ ├── RootElementReferenceBehaviorSpec.js │ │ │ │ │ ├── SetCompensationActivityAfterPasteBehaviorSpec.bpmn │ │ │ │ │ ├── SetCompensationActivityAfterPasteBehaviorSpec.js │ │ │ │ │ ├── SpaceToolBehaviorSpec.group.bpmn │ │ │ │ │ ├── SpaceToolBehaviorSpec.js │ │ │ │ │ ├── SpaceToolBehaviorSpec.participant.bpmn │ │ │ │ │ ├── SpaceToolBehaviorSpec.participant.vertical.bpmn │ │ │ │ │ ├── SpaceToolBehaviorSpec.subprocess.bpmn │ │ │ │ │ ├── SubProcessBehavior.copy-paste.bpmn │ │ │ │ │ ├── SubProcessBehavior.multiple-planes.bpmn │ │ │ │ │ ├── SubProcessBehavior.nested-subprocess-annotations.bpmn │ │ │ │ │ ├── SubProcessBehavior.planes.bpmn │ │ │ │ │ ├── SubProcessBehavior.start-event.bpmn │ │ │ │ │ ├── SubProcessPlaneBehaviorSpec.js │ │ │ │ │ ├── SubProcessStartEventBehaviorSpec.js │ │ │ │ │ ├── TextAnnotationBehaviorSpec.bpmn │ │ │ │ │ ├── TextAnnotationBehaviorSpec.js │ │ │ │ │ ├── ToggleCollapseConnectionBehaviourSpec.bpmn │ │ │ │ │ ├── ToggleCollapseConnectionBehaviourSpec.js │ │ │ │ │ ├── ToggleElementCollapseBehaviour.bpmn │ │ │ │ │ ├── ToggleElementCollapseBehaviourSpec.js │ │ │ │ │ ├── UnclaimIdBehaviorSpec.bpmn │ │ │ │ │ ├── UnclaimIdBehaviorSpec.js │ │ │ │ │ ├── UnsetDefaultFlowBehaviorSpec.bpmn │ │ │ │ │ ├── UnsetDefaultFlowBehaviorSpec.js │ │ │ │ │ └── util/ │ │ │ │ │ ├── GeometricUtilSpec.js │ │ │ │ │ ├── LabelLayoutUtilSpec.js │ │ │ │ │ ├── LineAttachmentUtilSpec.js │ │ │ │ │ ├── LineIntersectSpec.js │ │ │ │ │ └── ResizeUtilSpec.js │ │ │ │ ├── input-output/ │ │ │ │ │ └── DataInputOutput.bpmn │ │ │ │ ├── lanes/ │ │ │ │ │ ├── AddLaneSpec.js │ │ │ │ │ ├── DeleteLaneSpec.js │ │ │ │ │ ├── ResizeLaneSpec.js │ │ │ │ │ ├── SplitLane.nested.bpmn │ │ │ │ │ ├── SplitLane.nested.vertical.bpmn │ │ │ │ │ ├── SplitLaneSpec.js │ │ │ │ │ ├── UpdateFlowNodeRefs.basic.bpmn │ │ │ │ │ ├── UpdateFlowNodeRefsSpec.js │ │ │ │ │ ├── lanes-flow-nodes-vertical.bpmn │ │ │ │ │ ├── lanes-flow-nodes.bpmn │ │ │ │ │ ├── lanes.bpmn │ │ │ │ │ ├── lanes.only.bpmn │ │ │ │ │ ├── lanes.only.vertical.bpmn │ │ │ │ │ ├── lanes.vertical.bpmn │ │ │ │ │ ├── participant-lane-vertical.bpmn │ │ │ │ │ ├── participant-lane.bpmn │ │ │ │ │ ├── participant-no-lane-vertical.bpmn │ │ │ │ │ ├── participant-no-lane.bpmn │ │ │ │ │ └── participant-single-lane.bpmn │ │ │ │ └── layout/ │ │ │ │ ├── Helper.js │ │ │ │ ├── LayoutAssociationSpec.js │ │ │ │ ├── LayoutConnectionSpec.js │ │ │ │ ├── LayoutDataAssociationSpec.js │ │ │ │ ├── LayoutMessageFlowSpec.bpmn │ │ │ │ ├── LayoutMessageFlowSpec.js │ │ │ │ ├── LayoutMessageFlowSpec.vertical.bpmn │ │ │ │ ├── LayoutSequenceFlowSpec.boundaryEvents.bpmn │ │ │ │ ├── LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn │ │ │ │ ├── LayoutSequenceFlowSpec.flowElements.bpmn │ │ │ │ ├── LayoutSequenceFlowSpec.js │ │ │ │ ├── LayoutSequenceFlowSpec.subProcess.bpmn │ │ │ │ ├── LayoutSequenceFlowSpec.vertical.boundaryEvents.bpmn │ │ │ │ ├── LayoutSequenceFlowSpec.vertical.boundaryEventsLoops.bpmn │ │ │ │ ├── LayoutSequenceFlowSpec.vertical.flowElements.bpmn │ │ │ │ └── LayoutSequenceFlowSpec.vertical.subProcess.bpmn │ │ │ ├── modeling-feedback/ │ │ │ │ ├── ModelingFeedback.bpmn │ │ │ │ └── ModelingFeedbackSpec.js │ │ │ ├── ordering/ │ │ │ │ ├── BpmnDiOrderingSpec.js │ │ │ │ ├── BpmnOrderingProviderSpec.js │ │ │ │ ├── Helper.js │ │ │ │ ├── collapsed-subprocess.bpmn │ │ │ │ ├── data-association.bpmn │ │ │ │ ├── groups.bpmn │ │ │ │ ├── ordering-start-event.bpmn │ │ │ │ ├── ordering-subprocesses.bpmn │ │ │ │ ├── ordering.bpmn │ │ │ │ └── wrong-di-order.bpmn │ │ │ ├── outline/ │ │ │ │ ├── OutlineProvider.bpmn │ │ │ │ └── OutlineProviderSpec.js │ │ │ ├── palette/ │ │ │ │ └── PaletteProviderSpec.js │ │ │ ├── popup-menu/ │ │ │ │ ├── ReplaceMenuProvider.collapsedSubProcess.bpmn │ │ │ │ ├── ReplaceMenuProvider.compensation-activity.bpmn │ │ │ │ ├── ReplaceMenuProvider.conditionalFlows.bpmn │ │ │ │ ├── ReplaceMenuProvider.defaultFlows.bpmn │ │ │ │ ├── ReplaceMenuProvider.defaultFlowsFromComplexGateways.bpmn │ │ │ │ ├── ReplaceMenuProvider.defaultFlowsFromInclusiveGateways.bpmn │ │ │ │ ├── ReplaceMenuProvider.events.bpmn │ │ │ │ ├── ReplaceMenuProvider.pools.bpmn │ │ │ │ ├── ReplaceMenuProvider.subProcesses.bpmn │ │ │ │ └── ReplaceMenuProviderSpec.js │ │ │ ├── replace/ │ │ │ │ ├── BpmnReplace.collaboration.bpmn │ │ │ │ ├── BpmnReplace.collaboration.vertical.bpmn │ │ │ │ ├── BpmnReplace.collapsedSubProcess.bpmn │ │ │ │ ├── BpmnReplace.compensation.bpmn │ │ │ │ ├── BpmnReplace.dataObjects.bpmn │ │ │ │ ├── BpmnReplace.eventSubProcesses.bpmn │ │ │ │ ├── BpmnReplace.poolMessageFlows.bpmn │ │ │ │ ├── BpmnReplace.poolMessageFlows.vertical.bpmn │ │ │ │ ├── BpmnReplaceSpec.js │ │ │ │ ├── ReplaceRulesSpec.js │ │ │ │ └── SubProcess-collapsed.bpmn │ │ │ ├── replace-preview/ │ │ │ │ ├── BpmnReplacePreview.bpmn │ │ │ │ └── BpmnReplacePreviewSpec.js │ │ │ ├── rules/ │ │ │ │ ├── BpmnRules.attaching.bpmn │ │ │ │ ├── BpmnRules.boundaryEvent.bpmn │ │ │ │ ├── BpmnRules.collaboration-dataAssociation.bpmn │ │ │ │ ├── BpmnRules.collaboration-empty.bpmn │ │ │ │ ├── BpmnRules.collaboration-lanes.bpmn │ │ │ │ ├── BpmnRules.collaboration.bpmn │ │ │ │ ├── BpmnRules.collapsedPools.bpmn │ │ │ │ ├── BpmnRules.compensation-collaboration.bpmn │ │ │ │ ├── BpmnRules.compensation.bpmn │ │ │ │ ├── BpmnRules.connectOnCreate.bpmn │ │ │ │ ├── BpmnRules.dataAssociation.bpmn │ │ │ │ ├── BpmnRules.dataInputOutput.collaboration.bpmn │ │ │ │ ├── BpmnRules.dataInputOutput.process.bpmn │ │ │ │ ├── BpmnRules.detaching.bpmn │ │ │ │ ├── BpmnRules.eventBasedGatewayBasic.bpmn │ │ │ │ ├── BpmnRules.groups.bpmn │ │ │ │ ├── BpmnRules.insert.bpmn │ │ │ │ ├── BpmnRules.messageFlow.bpmn │ │ │ │ ├── BpmnRules.moveLane.bpmn │ │ │ │ ├── BpmnRules.multiSelectionPools.bpmn │ │ │ │ ├── BpmnRules.process.bpmn │ │ │ │ ├── BpmnRules.subProcess-dataAssociation.bpmn │ │ │ │ ├── BpmnRulesSpec.js │ │ │ │ └── Helper.js │ │ │ ├── search/ │ │ │ │ ├── BpmnSearchProviderSpec.js │ │ │ │ ├── bpmn-search-collaboration.bpmn │ │ │ │ ├── bpmn-search-sorting.bpmn │ │ │ │ └── bpmn-search.bpmn │ │ │ ├── snapping/ │ │ │ │ ├── BpmnConnectSnapping.bpmn │ │ │ │ ├── BpmnConnectSnappingSpec.js │ │ │ │ ├── BpmnCreateMoveSnapping.boundary-events.bpmn │ │ │ │ ├── BpmnCreateMoveSnapping.collaboration.bpmn │ │ │ │ ├── BpmnCreateMoveSnapping.docking-create-mode.bpmn │ │ │ │ ├── BpmnCreateMoveSnapping.docking-points.bpmn │ │ │ │ ├── BpmnCreateMoveSnapping.process.bpmn │ │ │ │ ├── BpmnCreateMoveSnapping.sequence-flows.bpmn │ │ │ │ ├── BpmnCreateMoveSnapping.trbl-snapping.bpmn │ │ │ │ └── BpmnCreateMoveSnappingSpec.js │ │ │ └── space-tool/ │ │ │ ├── BpmnSpaceTool.artifacts.bpmn │ │ │ ├── BpmnSpaceTool.basics.bpmn │ │ │ ├── BpmnSpaceTool.boundary-events.bpmn │ │ │ ├── BpmnSpaceTool.participants.bpmn │ │ │ ├── BpmnSpaceTool.text-annotations.bpmn │ │ │ └── BpmnSpaceToolSpec.js │ │ ├── helper/ │ │ │ └── InjectSpec.js │ │ ├── i18n/ │ │ │ ├── custom-translate/ │ │ │ │ ├── custom-translate.js │ │ │ │ └── index.js │ │ │ └── translateSpec.js │ │ ├── import/ │ │ │ ├── BpmnTreeWalkerSpec.js │ │ │ ├── ImporterSpec.js │ │ │ ├── ModelWiringSpec.js │ │ │ ├── data-association.bpmn │ │ │ ├── elements/ │ │ │ │ ├── AssociationSpec.collaboration.bpmn │ │ │ │ ├── AssociationSpec.compensation.bpmn │ │ │ │ ├── AssociationSpec.data-association.bpmn │ │ │ │ ├── AssociationSpec.data-input-output.bpmn │ │ │ │ ├── AssociationSpec.events.bpmn │ │ │ │ ├── AssociationSpec.js │ │ │ │ ├── AssociationSpec.text-annotation.bpmn │ │ │ │ ├── CollapsedSpec.js │ │ │ │ ├── DataInputOutput.bpmn │ │ │ │ ├── DataInputOutputSpec.js │ │ │ │ ├── Groups.bpmn │ │ │ │ ├── GroupsSpec.js │ │ │ │ └── LabelSpec.js │ │ │ ├── lane-flowNodes.bpmn │ │ │ ├── lane-missing-flowNodeRef.bpmn │ │ │ ├── missing-di-plane-root-element.bpmn │ │ │ ├── missing-di-plane.bpmn │ │ │ ├── sequenceFlow-missingWaypoints.bpmn │ │ │ └── sequenceFlow-ordering.bpmn │ │ └── util/ │ │ ├── ModelUtilSpec.js │ │ └── svgHelpersSpec.js │ ├── testBundle.js │ └── util/ │ ├── KeyEvents.js │ ├── MockEvents.js │ ├── custom-rules/ │ │ ├── CustomRules.js │ │ └── index.js │ └── svgHelpers.js └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CONTRIBUTING.md ================================================ # How to Contribute Great to see you! Help us out by [filing bugs or feature requests](#work-with-issues), assisting others [in our forums](https://forum.bpmn.io/), or [contributing improvements](#contribute-improvements). ## Table of Contents * [Work with Issues](#work-with-issues) * [Create an Issue](#create-an-issue) * [Help Out](#help-out) * [Contribute Improvements](#contribute-improvements) * [Setup the Project](#setup-the-project) * [Build and Run the Project](#build-and-run-the-project) * [Discuss Code Changes](#discuss-code-changes) * [Adhere to the Unit Test Style](#adhere-to-the-unit-test-style) * [Create a Pull Request](#create-a-pull-request) ## Work with Issues We use our [issue tracker](https://github.com/bpmn-io/bpmn-js/issues) for project communication, discussion, and planning. ### Create an Issue File bug reports or feature requests via [our issue tracker](https://github.com/bpmn-io/bpmn-js/issues/new/choose). Please mind the existing issue templates. These guide you and ensure you provide the details needed for us to follow up on your issue. ### Help Out * Share your perspective on issues * Be helpful and respect others when commenting ## Contribute Improvements Learn how to set up the project locally, make changes, and contribute bug fixes and new features through pull requests. ### Setup the Project The project development runs on top of the [diagram-js](https://github.com/bpmn-io/diagram-js) `develop` branch. The following code snippet sets up both libraries linking diagram-js to bpmn-js. ```sh mkdir bpmn.io cd bpmn.io git clone git@github.com:bpmn-io/diagram-js.git -b develop (cd diagram-js && npm i) git clone git@github.com:bpmn-io/bpmn-js.git (cd bpmn-js && npm install && npm link ../diagram-js) ``` For details consult our in depth [setup guide](../docs/project/SETUP.md). ### Build and Run the Project Spin up a single modeler instance for local inspection: ```sh npm start ``` Spin up the development environment, re-run tests with every file change: ```sh npm run dev ``` You may also run against different browsers: ```sh TEST_BROWSERS=Firefox npm run dev ``` Build, lint, and test the project, just as the CI does. ```sh npm run all ``` ### Discuss Code Changes Create a [pull request](#create-a-pull-request) if you would like to have an in-depth discussion about some piece of code. ### Adhere to the Unit Test Style In order to retrieve a sign-off for your contribution, it needs to be sufficiently and well tested. Please structure your unit tests into __given__, __when__ and __then__ ([ModelerSpec example](https://github.com/bpmn-io/bpmn-js/blob/develop/test/spec/ModelerSpec.js#L116), [ResizeBehaviorSpec example](https://github.com/bpmn-io/bpmn-js/blob/develop/test/spec/features/modeling/behavior/ResizeBehaviorSpec.js#L38)). To increase overall readability and understandability please also leave two empty lines before `describe(...)`, `it(...)` or _setup_ blocks on the same indentation level ([ModelerSpec example](https://github.com/bpmn-io/bpmn-js/blob/develop/test/spec/ModelerSpec.js#L49), [ResizeBehaviorSpec example](https://github.com/bpmn-io/bpmn-js/blob/develop/test/spec/features/modeling/behavior/ResizeBehaviorSpec.js#L36)). ### Create a Pull Request We use pull requests for feature additions and bug fixes. If you are not yet familiar with pull requests, [read this excellent guide](https://gun.io/blog/how-to-github-fork-branch-and-pull-request). Some things that make it easier for us to accept your pull requests * The code adheres to our conventions * spaces instead of tabs * single-quotes * ... * The code is tested * The `npm run all` build passes (executes tests + linting) * The work is combined into a single commit * The commit messages adhere to the [conventional commits guidelines](https://www.conventionalcommits.org) We'd be glad to assist you if you do not get these things right in the first place. --- Thanks for your interest in our library. :heart: from the bpmn.io team. ================================================ FILE: .github/ISSUE_TEMPLATE/BUG_REPORT.md ================================================ --- name: Bug report about: Report a problem and help us fix it. labels: "bug" --- ### Describe the Bug ### Steps to Reproduce 1. do this 2. do that ### Expected Behavior ### Environment - Browser: [e.g. IE 11, Chrome 69] - OS: [e.g. Windows 7] - Library version: [e.g. 2.0.0] ================================================ FILE: .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md ================================================ --- name: Feature request about: Suggest an idea or general improvement. labels: "enhancement" --- ### Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] ### Describe the solution you'd like A clear and concise description of what you want to happen. ### Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered. ### Additional context Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/TASK.md ================================================ --- name: Task about: Describe a generic activity we should carry out. --- ### What should we do? ### Why should we do it? ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Ask a question url: https://forum.bpmn.io about: Head over to our community forum to ask questions and get answers. ================================================ FILE: .github/merge-me.yml ================================================ reviewTeams: - modeling-dev - modeling-design ================================================ FILE: .github/workflows/CI.yml ================================================ name: CI on: [ push, pull_request ] jobs: build_browsers: strategy: fail-fast: false matrix: os: [ ubuntu-latest ] browser: [ Firefox, ChromeHeadless ] runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v6 - name: Use Node.js uses: actions/setup-node@v6 with: node-version: 24 cache: 'npm' - name: Install dependencies run: npm ci - name: Setup project uses: bpmn-io/actions/setup@latest - name: Build (with coverage) if: matrix.browser == 'ChromeHeadless' env: COVERAGE: 1 TEST_BROWSERS: ${{ matrix.browser }} run: npm run all - name: Build if: matrix.browser == 'Firefox' env: TEST_BROWSERS: ${{ matrix.browser }} run: xvfb-run npm run all - name: Upload coverage if: matrix.browser == 'ChromeHeadless' uses: codecov/codecov-action@v5 with: fail_ci_if_error: true env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} build_os: strategy: fail-fast: false matrix: os: [ macos-latest, windows-latest ] browser: [ ChromeHeadless ] runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v6 - name: Use Node.js uses: actions/setup-node@v6 with: node-version: 24 cache: 'npm' - name: Install dependencies run: npm ci - name: Setup project uses: bpmn-io/actions/setup@latest - name: Build env: TEST_BROWSERS: ${{ matrix.browser }} run: npm run all ================================================ FILE: .github/workflows/CODE_SCANNING.yml ================================================ name: "Code Scanning" on: push: branches: [ main, develop ] pull_request: branches: [ main, develop ] paths-ignore: - '**/*.md' jobs: codeql-build: # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest runs-on: ubuntu-latest permissions: # required for all workflows security-events: write steps: - name: Checkout repository uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: javascript config: | paths-ignore: - '**/test' - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/COMMENT_TARGETS_MAIN.yml ================================================ name: COMMENT_TARGETS_MAIN on: pull_request: types: - opened branches: - main permissions: pull-requests: write jobs: comment: name: Comment on targeting main branch runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: Create comment env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} NUMBER: ${{ github.event.number }} BODY: | This pull request targets the `main` branch. Please target `main` for bug fixes only. Target `develop` for regular feature development. run: gh issue comment "$NUMBER" --body "$BODY" ================================================ FILE: .github/workflows/MERGE_MAIN_TO_DEVELOP.yml ================================================ name: MERGE_MAIN_TO_DEVELOP on: push: branches: - "main" jobs: merge_main_to_develop: runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout develop uses: actions/checkout@v6 with: token: ${{ secrets.BPMN_IO_TOKEN }} persist-credentials: true ref: develop fetch-depth: 0 - name: Merge main to develop and push run: | git config user.name '${{ secrets.BPMN_IO_USERNAME }}' git config user.email '${{ secrets.BPMN_IO_EMAIL }}' git merge -m 'chore: merge main to develop' --no-edit origin/main git push - name: Notify failure on Slack if: failure() uses: slackapi/slack-github-action@v3 with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} text: "Automatic merge of to failed." ================================================ FILE: .github/workflows/POST_RELEASE.yml ================================================ name: POST_RELEASE on: push: tags: - 'v[0-9]+.[0-9]+.[0-9]+' jobs: post_release: strategy: matrix: os: [ ubuntu-latest ] node-version: [ 20 ] runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v6 - name: Use Node.js uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: 'npm' - name: Setup project uses: bpmn-io/actions/setup@latest - name: Set TAG run: echo "TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - name: Wait for published env: PKG: 'bpmn-js@${{ env.TAG }}' run: tasks/stages/await-published - name: Update integration test env: BPMN_IO_TOKEN: ${{ secrets.BPMN_IO_TOKEN }} BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }} BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }} run: tasks/stages/update-integration-test - name: Update demo env: BPMN_IO_TOKEN: ${{ secrets.BPMN_IO_TOKEN }} BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }} BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }} BPMN_IO_DEMO_ENDPOINT: ${{ secrets.BPMN_IO_DEMO_ENDPOINT }} run: tasks/stages/update-demo - name: Update examples env: BPMN_IO_TOKEN: ${{ secrets.BPMN_IO_TOKEN }} BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }} BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }} run: tasks/stages/update-examples - name: Update website env: BPMN_IO_TOKEN: ${{ secrets.BPMN_IO_TOKEN }} BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }} BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }} run: tasks/stages/update-website - name: Update translations env: GITHUB_TOKEN: ${{ secrets.BPMN_IO_TOKEN }} BPMN_IO_EMAIL: ${{ secrets.BPMN_IO_EMAIL }} BPMN_IO_USERNAME: ${{ secrets.BPMN_IO_USERNAME }} REVIEWERS: 'bpmn-io/modeling-dev' TAG: ${{ env.TAG }} run: tasks/stages/update-translations ================================================ FILE: .github/workflows/RELEASE.yml ================================================ name: RELEASE on: push: branches: - main - develop jobs: release_please: outputs: release_created: ${{ steps.release.outputs.release_created }} tag: ${{ steps.release.outputs.tag_name }} permissions: contents: write # to create release commit (google-github-actions/release-please-action) pull-requests: write # to create release PR (google-github-actions/release-please-action) runs-on: ubuntu-latest steps: - uses: google-github-actions/release-please-action@v4 id: release with: token: ${{ secrets.GITHUB_TOKEN }} target-branch: main release-type: node publish: needs: release_please if: ${{ needs.release_please.outputs.release_created }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Use Node.js uses: actions/setup-node@v6 with: node-version: 24 cache: 'npm' registry-url: 'https://registry.npmjs.org' - name: Install dependencies run: npm ci - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ================================================ FILE: .gitignore ================================================ node_modules/ dist/ coverage/ lib/**/*.d.ts .idea *.iml .DS_Store ================================================ FILE: .release-please-manifest.json ================================================ { "pull-request-title-pattern": "chore: release v${version}", "changelog-sections": "[{\"type\":\"feat\",\"section\":\"Features\",\"hidden\":false},{\"type\":\"fix\",\"section\":\"Bug Fixes\",\"hidden\":false},{\"type\":\"deps\",\"section\":\"Dependency Updates\",\"hidden\":false}]", "changelog-type": "github", ".": "18.13.0" } ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to [bpmn-js](https://github.com/bpmn-io/bpmn-js) are documented here. We use [semantic versioning](http://semver.org/) for releases. ## Unreleased ___Note:__ Yet to be released changes appear here._ * `FIX`: disable grouping in popup menu during search ([bpmn-io/diagram-js#1014](https://github.com/bpmn-io/diagram-js/pull/1014)) * `FIX`: correct handling of annotations during sub-process collapse/expand, copy/paste, and remove actions ([#2388](https://github.com/bpmn-io/bpmn-js/pull/2388)) * `FIX`: allow undo of pasted sub-process ([#2388](https://github.com/bpmn-io/bpmn-js/pull/2388), [#2269](https://github.com/bpmn-io/bpmn-js/issues/2269)) * `DEPS`: update to `diagram-js@15.10.0` ## 18.13.1 * `FIX`: correct sequence flow layout for corner boundary events whose target is strictly axis-aligned ([#2270](https://github.com/bpmn-io/bpmn-js/issues/2270)) ## 18.13.0 * `FEAT`: allow to create child elements from the context pad ([#2391](https://github.com/bpmn-io/bpmn-js/issues/2391)) ## 18.12.1 * `FIX`: correctly replace non-interrupting event with an interrupting one ([#2313](https://github.com/bpmn-io/bpmn-js/issues/2313)) ## 18.12.0 * `FEAT`: activate wheel zoom/scoll on `mouseover` ([#1008](https://github.com/bpmn-io/diagram-js/pull/1008)) * `FEAT`: prevent keyboard movement for boundary events without host ([#2386](https://github.com/bpmn-io/bpmn-js/pull/2386)) * `FIX`: prevent accidental creation of intermediate events during keyboard move ([#1803](https://github.com/bpmn-io/bpmn-js/issues/1803), [#1876](https://github.com/bpmn-io/bpmn-js/issues/1876)) * `DEPS`: update to `diagram-js@15.9.0` ## 18.11.0 * `FEAT`: add `cut` action and keyboard shortcut ([bpmn-io/diagram-js#1006](https://github.com/bpmn-io/diagram-js/pull/1006)) * `DEPS`: update to `diagram-js@15.7.0` * `DEPS`: update to `bpmn-moddle@10.0.0` * `DEPS`: update to `min-dash@5.0.0` * `DEPS`: update to `ids@3.0.0` * `DEPS`: update to `tiny-svg@4.1.4` * `DEPS`: update to `diagram-js-direct-editing@3.3.0` * `DEPS`: update to `min-dom@5.2.0` ## 18.10.1 * `DEPS`: update to `min-dash@4.2.3` * `DEPS`: update to `tiny-svg@3.1.3` ## 18.10.0 * `FEAT`: add ability to duplicate elements ([bpmn-io/diagram-js#998](https://github.com/bpmn-io/diagram-js/pull/998)) * `DEPS`: update to `diagram-js@15.5.0` ## 18.9.1 * `FIX`: only draw links for currently selected elements ([#2365](https://github.com/bpmn-io/bpmn-js/pull/2365)) ## 18.9.0 * `FEAT`: visually link external label with its target ([#2328](https://github.com/bpmn-io/bpmn-js/pull/2328)) * `FEAT`: add support for labels to `OutlineProvider#getOutline` * `FIX`: ensure `BpmnRenderer#getShapePath` returns correct path for labels ## 18.8.0 * `FEAT`: allow copying data object references and `isCollection` property ([#2348](https://github.com/bpmn-io/bpmn-js/pull/2348)) ## 18.7.0 * `FEAT`: support disabled entries in popup menu ([bpmn-io/diagram-js#987](https://github.com/bpmn-io/diagram-js/pull/987)) * `DEPS`: update to `diagram-js@15.4.0` ## 18.6.5 * `FIX`: ensure popup menu keyboard navigation accounts for group order ([bpmn-io/diagram-js#989](https://github.com/bpmn-io/diagram-js/pull/989)) * `DEPS`: update to `diagram-js@15.3.1` ## 18.6.4 * `FIX`: revert `AdHocSubProcess#cancelRemainingInstances` default value removal ([bpmn-io/bpmn-moddle#132](https://github.com/bpmn-io/bpmn-moddle/pull/132)) * `DEPS`: update to `bpmn-moddle@9.0.4` ## 18.6.3 * `FIX`: remove `AdHocSubProcess#cancelRemainingInstances` default value ([bpmn-io/bpmn-moddle#131](https://github.com/bpmn-io/bpmn-moddle/issues/131)) * `DEPS`: update to `bpmn-moddle@9.0.3` ## 18.6.2 * `FIX`: center task markers ([#1995](https://github.com/bpmn-io/bpmn-js/issues/1995)) ## 18.6.1 * `FIX`: copy error, escalation, message and signal references when copying elements ([#1906](https://github.com/bpmn-io/bpmn-js/issues/1906), [#2249](https://github.com/bpmn-io/bpmn-js/issues/2249), [#2301](https://github.com/bpmn-io/bpmn-js/pull/2301)) ## 18.6.0 * `FEAT`: support searching through arrays in popup menu ([bpmn-io/diagram-js#970](https://github.com/bpmn-io/diagram-js/pull/970)) * `FEAT`: prioritize `search` over `description` when matching popup menu entries ([bpmn-io/diagram-js#963](https://github.com/bpmn-io/diagram-js/pull/963)) * `FEAT`: sort `search` terms across all keys ([bpmn-io/diagram-js#963](https://github.com/bpmn-io/diagram-js/pull/963)) * `FIX`: always select first search entry ([bpmn-io/diagram-js#967](https://github.com/bpmn-io/diagram-js/pull/967)) * `DEPS`: update to `diagram-js@15.3.0` ## 18.5.0 * `FEAT`: allow text annotations for message flows ([#2281](https://github.com/bpmn-io/bpmn-js/issues/2281)) ## 18.4.0 * `FEAT`: render collapsed event subprocess icons ([#50](https://github.com/bpmn-io/bpmn-js/issues/50)) ## 18.3.2 * `FIX`: remove default start event for ad-hoc subprocess ([#2295](https://github.com/bpmn-io/bpmn-js/issues/2295)) * `FIX`: show modeling feedback error for data objects ([#2290](https://github.com/bpmn-io/bpmn-js/pull/2290)) ## 18.3.1 * `FIX`: move artifacts when a participant is resized by space tool ([#2285](https://github.com/bpmn-io/bpmn-js/pull/2285)) ## 18.3.0 * `FEAT`: allow to replace between variants of typed events ([#2282](https://github.com/bpmn-io/bpmn-js/pull/2282)) ## 18.2.0 * `FEAT`: add ad-hoc subprocess option to replace menu ([#2276](https://github.com/bpmn-io/bpmn-js/pull/2276)) ## 18.1.2 * `FIX`: canvas `autoFocus` must explicitly be enabled ([bpmn-io/diagram-js#956](https://github.com/bpmn-io/diagram-js/pull/956)) * `FIX`: properly integrate `zoomscroll` with canvas focus ([bpmn-io/diagram-js#956](https://github.com/bpmn-io/diagram-js/pull/956)) * `FIX`: properly integrate `movecanvas` with canvas focus ([bpmn-io/diagram-js#956](https://github.com/bpmn-io/diagram-js/pull/956)) ## 18.1.1 * `FIX`: adjust search to prioritize start of word and exact matches ([bpmn-io/diagram-js#953](https://github.com/bpmn-io/diagram-js/pull/953)) * `FIX`: ignore whitespace when searching ([bpmn-io/diagram-js#954](https://github.com/bpmn-io/diagram-js/pull/954)) ## 18.1.0 * `FIX`: clear selection when opening search pad ([bpmn-io/diagram-js#947](https://github.com/bpmn-io/diagram-js/pull/947)) * `FIX`: correct dangling selection after search pad interaction ([bpmn-io/diagram-js#947](https://github.com/bpmn-io/diagram-js/pull/947)) * `DEPS`: update to `diagram-js@15.2.2` ## 18.0.0 * `FEAT`: remove `outline` from `Viewer` modules ([#2135](https://github.com/bpmn-io/bpmn-js/issues/2135)) * `FEAT`: make `Canvas` a focusable element ([bpmn-io/diagram-js#662](https://github.com/bpmn-io/diagram-js/pull/662)) * `FEAT`: implicit keyboard binding ([bpmn-io/diagram-js#662](https://github.com/bpmn-io/diagram-js/pull/662)) * `FEAT`: integrate with global `search` ([#2235](https://github.com/bpmn-io/bpmn-js/pull/2235)) * `FEAT`: integrate `popup-menu` with `search` ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932)) * `FEAT`: recognize modern `search` tokens in `search-pad` ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932)) * `FIX`: correctly handle duplicate entries and whitespace in `search` ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932)) * `FIX`: find `search` terms across all keys ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932)) * `FIX`: `search` always returns tokens for matched items ([bpmn-io/diagram-js#932](https://github.com/bpmn-io/diagram-js/pull/932)) * `FIX`: prevent crash during label adjustment ([#2239](https://github.com/bpmn-io/bpmn-js/issues/2239)) * `FIX`: keep existing loop characteristics when toggling through the replace menu ([#2251](https://github.com/bpmn-io/bpmn-js/pull/2251)) * `FIX`: prevent covering multi selection with black box in `Viewer` ([#2135](https://github.com/bpmn-io/bpmn-js/issues/2135)) * `FIX`: generate types for main entry ([`986e2bb`](https://github.com/bpmn-io/bpmn-js/commit/986e2bb51ea301e6e0df56f3606a27424fb90179)) * `FIX`: correct handling of group name with whitespace only ([#2231](https://github.com/bpmn-io/bpmn-js/issues/2231)) * `DEPS`: update to `bpmn-moddle@9` ([#2114](https://github.com/bpmn-io/bpmn-js/pull/2114)) * `DEPS`: update to `diagram-js@15.1.0` * `DEPS`: update to `diagram-js-direct-editing@3.2.0` ### Breaking Changes * Require `Node >= 20` * `Canvas` is now a focusable element and provides better support for native browser behaviors. Focus can be controlled with new `focus` and `restoreFocus` APIs ([bpmn-io/diagram-js#662](https://github.com/bpmn-io/diagram-js/pull/662)). * Keyboard is now implicitly bound to canvas SVG element. Calls to `keyboard.bind` and `keyboard.bindTo` now result with a descriptive console error and have no effect ([bpmn-io/diagram-js#662](https://github.com/bpmn-io/diagram-js/pull/662)). * Selection outline is no longer included in the viewer. If needed, add it as an additional module ([#2253](https://github.com/bpmn-io/bpmn-js/pull/2253)). ## 17.11.1 * `FIX`: handle searching elements without labels ([#2232](https://github.com/bpmn-io/bpmn-js/issues/2232), [#2234](https://github.com/bpmn-io/bpmn-js/pull/2234)) ## 17.11.0 * `FEAT`: align search styles with other popups ([#2187](https://github.com/bpmn-io/bpmn-js/pull/2187)) * `FEAT`: prioritize start of tokens in search results ([#2187](https://github.com/bpmn-io/bpmn-js/pull/2187)) * `FIX`: do not commit viewport changes on `ESC` ([#2189](https://github.com/bpmn-io/bpmn-js/issues/2189), [#2187](https://github.com/bpmn-io/bpmn-js/pull/2187)) * `DEPS`: update to `diagram-js@14.10.0` ## 17.10.0 * `CHORE`: correct various type hints ([#2228](https://github.com/bpmn-io/bpmn-js/issues/2228)) * `FIX`: pasting compensation activity without boundary event ([#2070](https://github.com/bpmn-io/bpmn-js/issues/2070)) * `FIX`: lane resize constraints for se and nw direction ([#2209](https://github.com/bpmn-io/bpmn-js/issues/2209)) * `FIX`: auto place elements vertically in sub-processes ([#2127](https://github.com/bpmn-io/bpmn-js/issues/2127)) * `FIX`: hide lane label during direct editing * `DEPS`: update to `diagram-js@14.9.0` ## 17.9.2 * `FIX`: keep direction when collapsing pools ([#2208](https://github.com/bpmn-io/bpmn-js/issues/2208)) ## 17.9.1 * `FIX`: show delete action for labels ([#2163](https://github.com/bpmn-io/bpmn-js/issues/2163)) ## 17.9.0 * `FIX`: remove incorrect attribute in replace menu ([#2196](https://github.com/bpmn-io/bpmn-js/pull/2196)) * `DEPS`: update to diagram-js@14.7.2 ## 17.8.3 * `FIX`: add accessible label to drill down button ([#2194](https://github.com/bpmn-io/bpmn-js/pull/2194)) ## 17.8.2 * `FIX`: do not suggest root elements in search ([#2143](https://github.com/bpmn-io/bpmn-js/issues/2143)) ## 17.8.1 * `FIX`: gracefully handle missing process DI in drilldown ([#2180](https://github.com/bpmn-io/bpmn-js/pull/2180)) * `FIX`: do not cause HTML validation errors on move preview ([#2179](https://github.com/bpmn-io/bpmn-js/issues/2179)) * `DEPS`: update to `diagram-js@14.7.1` ## 17.8.0 * `FEAT`: keep global elements when deleting last participant ([#2175](https://github.com/bpmn-io/bpmn-js/pull/2175)) * `FIX`: allow undo after deleting last participants and data store ([#1676](https://github.com/bpmn-io/bpmn-js/issues/1676)) * `FIX`: allow styling markers with `canvas.addMarker` and css ([#2173](https://github.com/bpmn-io/bpmn-js/pull/2173)) * `CHORE`: render flow markers as part of `djs-visual` ([#2173](https://github.com/bpmn-io/bpmn-js/pull/2173)) * `DEPS`: update to `diagram-js@14.7.0` ## 17.7.1 * `FIX`: correct call activity outline ([#2167](https://github.com/bpmn-io/bpmn-js/issues/2167)) * `FIX`: gracefully handle missing `BPMNDiagram#plane` ([#2172](https://github.com/bpmn-io/bpmn-js/pull/2172), [#2171](https://github.com/bpmn-io/bpmn-js/pull/2171)) ## 17.7.0 * `DEPS`: update to `diagram-js@14.6.0` ## 17.6.4 * `DEPS`: update to `diagram-js@14.5.4` ## 17.6.3 * `DEPS`: update to `diagram-js@14.5.3` ## 17.6.2 * `DEPS`: update to `diagram-js@14.5.2` ([#2158](https://github.com/bpmn-io/bpmn-js/pull/2158)) ## 17.6.1 * `DEPS`: update to `diagram-js@14.5.1` ([#2157](https://github.com/bpmn-io/bpmn-js/pull/2157)) ## 17.6.0 * `FEAT`: add ability to type services and events ([#2121](https://github.com/bpmn-io/bpmn-js/issues/2121), [#2153](https://github.com/bpmn-io/bpmn-js/pull/2153)) * `FIX`: remove preview on context pad close ([#2150](https://github.com/bpmn-io/bpmn-js/issues/2150)) * `FIX`: use tagged template in error logging ([#2151](https://github.com/bpmn-io/bpmn-js/pull/2151)) ## 17.5.0 * `FEAT`: remove direct editing outline for embedded labels ([#2147](https://github.com/bpmn-io/bpmn-js/pull/2147)) * `FEAT`: do not translate technical errors ([#2145](https://github.com/bpmn-io/bpmn-js/pull/2145)) * `DEPS`: update to `diagram-js-direct-editing@3.0.1` ## 17.4.0 * `FEAT`: do not scale popup menu and context pad * `DEPS`: update to `diagram-js@14.4.1` ## 17.3.0 * `FEAT`: auto-place elements vertically ([#2110](https://github.com/bpmn-io/bpmn-js/issues/2110)) ## 17.2.2 * `FIX`: correct navigated viewer outline ([#2133](https://github.com/bpmn-io/bpmn-js/issues/2133)) ## 17.2.1 * `FIX`: render popup menu on top * `DEPS`: update to `diagram-js@14.3.1` ## 17.2.0 * `FEAT`: make popup menu keyboard navigatable * `FIX`: address various accessibility issues * `FIX`: correct various typing issues * `DEPS`: update to `diagram-js@14.3.0` * `DEPS`: update to `diagram-js-direct-editing@2.1.2` ## 17.1.0 * `FEAT`: handle splitting vertical lanes ([#2101](https://github.com/bpmn-io/bpmn-js/pull/2101)) ## 17.0.2 * `FIX`: create hit boxes for vertical lanes ([#2093](https://github.com/bpmn-io/bpmn-js/issues/2093)) ## 17.0.1 * `FIX`: fix rendering of gateway without marker ([#2102](https://github.com/bpmn-io/bpmn-js/pull/2102)) ## 17.0.0 * `FEAT`: add to selection through SHIFT ([bpmn-io/diagram-js#796](https://github.com/bpmn-io/diagram-js/pull/851), [#2053](https://github.com/bpmn-io/bpmn-js/issues/2053)) * `CHORE`: remove broken touch interaction ([bpmn-io/diagram-js#796](https://github.com/bpmn-io/diagram-js/issues/796)) * `DEPS`: update to `diagram-js@14.0.0` ### Breaking Changes * Migrated to `diagram-js@14` which removes touch interaction module, and dependency on unsupported `hammerjs` package. If you rely on touch interaction, you need to support touch interaction on your own. ## 16.5.0 * `FEAT`: handle adding vertical lanes ([#2086](https://github.com/bpmn-io/bpmn-js/issues/2086)) * `FIX`: don't fill multiple parallel events ([#2085](https://github.com/bpmn-io/bpmn-js/issues/2085)) ## 16.4.0 * `FEAT`: handle resizing of vertical lanes ([#2062](https://github.com/bpmn-io/bpmn-js/issues/2062)) * `FEAT`: allow text annotations to overlap with the borders of subprocesses and pools ([#2049](https://github.com/bpmn-io/bpmn-js/issues/2049)) * `FEAT`: support modeling of gateway without marker ([#2063](https://github.com/bpmn-io/bpmn-js/issues/2063)) * `FIX`: correctly remove vertical lanes ([#2081](https://github.com/bpmn-io/bpmn-js/pull/2081)) * `FIX`: do not set label on planes ([#2033](https://github.com/bpmn-io/bpmn-js/issues/2033)) ## 16.3.2 * `FIX`: support core replace in compensation behavior ([#2073](https://github.com/bpmn-io/bpmn-js/issues/2073)) ## 16.3.1 * `FIX`: do not remove connection that is being created when pasting compensation boundary event and handler ([#2069](https://github.com/bpmn-io/bpmn-js/pull/2069)) ## 16.3.0 * `FEAT`: improve handling of compensation association ([#2038](https://github.com/bpmn-io/bpmn-js/issues/2038)) ## 16.2.0 * `DEPS`: update to `bpmn-moddle@8.1.0` ## 16.1.0 * `DEPS`: update to `diagram-js@13.4.0` * `DEPS`: update to `diagram-js-direct-editing@2.1.1` * `DEPS`: drop unused `object-refs` dependency ## 16.0.0 * `FEAT`: render vertical pools and lanes ([#2024](https://github.com/bpmn-io/bpmn-js/pull/2024)) * `FEAT`: sentence case titles and labels ([#2023](https://github.com/bpmn-io/bpmn-js/issues/2023)) * `FIX`: ensure all error translations are collected ([#2040](https://github.com/bpmn-io/bpmn-js/pull/2040)) * `DEPS` update to diagram-js@13.0.0 ### Breaking Changes * Major updates to [diagram-js@13](https://github.com/bpmn-io/diagram-js/blob/develop/CHANGELOG.md#1300) and [didi@10](https://github.com/nikku/didi/blob/main/CHANGELOG.md#1000). Make sure to check out the linked changelog updates. * Multiple translation labels has been updated to sentence case. If you rely on the old casing, you need to update your translations. ## 15.2.2 * `FIX`: use correct types in BpmnRenderUtil ([#2036](https://github.com/bpmn-io/bpmn-js/pull/2036)) ## 15.2.1 * `DEPS`: update to `diagram-js@13.8.1` ## 15.2.0 * `FEAT`: remove selection outline from connections ([diagram-js#826](https://github.com/bpmn-io/diagram-js/pull/826)) * `FEAT`: position context pad according to last waypoint for connections ([diagram-js#826](https://github.com/bpmn-io/diagram-js/pull/826)) * `FIX`: prevent access of non-existing connection bounds ([diagram-js#824](https://github.com/bpmn-io/diagram-js/pull/824)) * `FIX`: correct selection outline size for end event ([#2026](https://github.com/bpmn-io/bpmn-js/pull/2026)) * `DEPS`: update to `diagram-js@13.8.0` ## 15.1.3 * `FIX`: revert `djs-dragging` CSS class changes ([#2016](https://github.com/bpmn-io/bpmn-js/pull/2016)) * `FIX`: clear context pad hover timeout on close ([#2016](https://github.com/bpmn-io/bpmn-js/pull/2016)) * `DEPS`: update to `diagram-js@12.7.2` ## 15.1.2 * `FIX`: revert selection outline removal for connections ([#2011](https://github.com/bpmn-io/bpmn-js/pull/2011)) * `DEPS`: update to `diagram-js@12.7.1` ## 15.1.1 * `FIX`: adjust selection outline to external label ([#2001](https://github.com/bpmn-io/bpmn-js/issues/2001)) ## 15.1.0 * `FEAT`: add toggle for non-interrupting events ([#2000](https://github.com/bpmn-io/bpmn-js/pull/2000)) * `FEAT`: keep events non-interrupting when using `bpmnReplace` by default ([#2000](https://github.com/bpmn-io/bpmn-js/pull/2000)) * `DEPS`: update to `diagram-js@12.7.0` ## 15.0.0 * `FEAT`: align selection outline with element's shape ([#1996](https://github.com/bpmn-io/bpmn-js/issues/1996)) * `FEAT`: preview append on hover ([#1985](https://github.com/bpmn-io/bpmn-js/pull/1985)) * `FEAT`: allow overriding `fill`, `stroke`, `width` and `height` when rendering elements ([#1985](https://github.com/bpmn-io/bpmn-js/pull/1985)) * `FIX`: renderer only renders actual flow elements ([#1985](https://github.com/bpmn-io/bpmn-js/pull/1985)) * `DEPS`: update to `diagram-js@12.6.0` ### Breaking Changes * `BpmnRenderer` only renders actual flow elements (e.g. `bpmn:IntermediateCatchEvent` but not `bpmn:MessageEventDefinition`) ## 14.2.0 * `FEAT`: make spacetool local per default ([bpmn-io/diagram-js#811](https://github.com/bpmn-io/diagram-js/pull/811), [#1975](https://github.com/bpmn-io/bpmn-js/issues/1975)) * `FEAT`: add complex preview feature ([bpmn-io/diagram-js#807](https://github.com/bpmn-io/diagram-js/pull/807)) * `CHORE`: mark connection as dragging when moving bendpoint ([bpmn-io/diagram-js#807](https://github.com/bpmn-io/diagram-js/pull/807)) * `DEPS`: update to `diagram-js@12.5.0` ## 14.1.3 * `CHORE`: correctly output tag in [#1982](https://github.com/bpmn-io/bpmn-js/pull/1982) ## 14.1.2 * `CHORE`: fix POST_RELEASE job in [#1980](https://github.com/bpmn-io/bpmn-js/pull/1980) ## 14.1.1 * `FIX`: asset path by [__@nikku__](https://github.com/nikku) in [#1977](https://github.com/bpmn-io/bpmn-js/pull/1977) ## 14.1.0 * `FEAT`: ensure lanes aren't resized when using space tool in [#1972](https://github.com/bpmn-io/bpmn-js/pull/1972) * `DOCS`: update translations for v14.0.0 by [__@bpmn-io-bot__](https://github.com/bpmn-io-bot) in [#1948](https://github.com/bpmn-io/bpmn-js/pull/1948) ## 14.0.0 * `FEAT`: do not hide overlays on canvas move per default ([diagram-js#798](https://github.com/bpmn-io/diagram-js/issues/798)) * `FEAT`: translate _Append TextAnnotation_ context pad action ([#1932](https://github.com/bpmn-io/bpmn-js/pull/1932)) * `FIX`: allow to create connection + event-based gateway ([#1490](https://github.com/bpmn-io/bpmn-js/issues/1490)) * `FIX`: make breadcrumb styling more robust ([#1945](https://github.com/bpmn-io/bpmn-js/pull/1945)) * `FIX`: correct copy of default sequence flow elements ([#1935](https://github.com/bpmn-io/bpmn-js/issues/1935)) * `CHORE`: extract `modeling-feedback` into dedicated module ([#1940](https://github.com/bpmn-io/bpmn-js/pull/1940)) * `CHORE`: drop deprecated callback support from public API * `CHORE`: drop deprecated `import.parse.complete` event member `context` * `DEPS`: update to `diagram-js@12.3.0` * `DEPS`: update to `bpmn-moddle@8.0.1` * `DEPS`: update to `ids@1.0.3` ### Breaking Changes * Deprecated callback style API removed. Migrate to promise-based APIs, released with `v7.0.0`. * Deprecated `import.parse.complete` event member `context` removed. Access the same information via the event itself, as released with `v7.0.0`. ## 13.2.2 * `FIX`: do not vertically resize empty pools using the space tool ([#1769](https://github.com/bpmn-io/bpmn-js/issues/1769)) ## 13.2.1 * `FIX`: improve regular expression ([#1927](https://github.com/bpmn-io/bpmn-js/pull/1927)) * `FIX`: show non-interrupting event version in replace menu ([#1924](https://github.com/bpmn-io/bpmn-js/pull/1924)) ## 13.2.0 * `CHORE`: provide align and distribute context pad and popup menu icons as html ([#1920](https://github.com/bpmn-io/bpmn-js/pull/1920)) * `DEPS`: update to `diagram-js@12.2.0` ## 13.1.0 * `FEAT`: allow event rendering without icons ([#1917](https://github.com/bpmn-io/bpmn-js/pull/1917)) ## 13.0.9 * `CHORE`: update translations infra ## 13.0.8 _Republish of v13.0.7._ ## 13.0.7 _Republish of v13.0.6._ ## 13.0.6 * `DOCS`: update translations ## 13.0.5 * `DEPS`: update to `diagram-js@12.1.0` ## 13.0.4 * `DEPS`: bump to `diagram-js@12.0.2` ## 13.0.3 * `FIX`: update label on `modeling.updateModdleProperties` ([#1872](https://github.com/bpmn-io/bpmn-js/issues/1872)) ## 13.0.2 * `FIX`: export types as `type` ([#1897](https://github.com/bpmn-io/bpmn-js/pull/1897)) * `DEPS`: bump to `diagram-js@12.0.1` ## 13.0.1 * `FIX`: correct some type definitions ([#1896](https://github.com/bpmn-io/bpmn-js/pull/1896)) ## 13.0.0 * `FEAT`: rework and complete type definitions ([#1886](https://github.com/bpmn-io/bpmn-js/pull/1886)) * `DEPS`: update to `diagram-js@12.0.0` ## 12.1.1 * `DEPS`: update to `diagram-js@11.13.0` ## 12.1.0 * `FIX`: correct `Viewer#saveXML` type definition ([#1885](https://github.com/bpmn-io/bpmn-js/pull/1885)) * `FIX`: correct `Viewer` constructor type definition ([#1882](https://github.com/bpmn-io/bpmn-js/issues/1882)) ## 12.0.0 * `FEAT`: move `create-append-anything` to [external module](https://github.com/bpmn-io/bpmn-js-create-append-anything) ([#1873](https://github.com/bpmn-io/bpmn-js/pull/1873), [#1862](https://github.com/bpmn-io/bpmn-js/issues/1862)) * `CHORE`: use `diagram-js@11.11.0` built-in selection after replace feature ([#1857](https://github.com/bpmn-io/bpmn-js/pull/1857)) * `DEPS`: update to `diagram-js@11.12.0` ### Breaking Changes * The create/append anything features moved to an [external module](https://github.com/bpmn-io/bpmn-js-create-append-anything). Include it to restore the `v11` create/append behavior. ## 11.5.0 * `FEAT`: add root elements to definitions when provided via `modeling#update(Moddle)Properties` ## 11.4.1 * `FIX`: correct redo triggering on international keyboard layouts ([#1842](https://github.com/bpmn-io/bpmn-js/issues/1842)) ## 11.4.0 * `FEAT`: translate append menu entry labels and groups ([#1810](https://github.com/bpmn-io/bpmn-js/pull/1810)) * `FEAT`: activate direct editing on participant creation ([#1845](https://github.com/bpmn-io/bpmn-js/pull/1845)) * `FIX`: dragging append menu entries creates element connection ([#1843](https://github.com/bpmn-io/bpmn-js/pull/1843)) * `FIX`: append shortcut triggers create menu if append not allowed ([#1840](https://github.com/bpmn-io/bpmn-js/issues/1840)) * `FIX`: restore marker rendering workaround ([`9c6e475`](https://github.com/bpmn-io/bpmn-js/commit/9c6e475681dd6b6a418b2fbc1dac19a9df360953)) ## 11.3.1 _Republish of `v11.3.0`._ ## 11.3.0 * `FEAT`: feature `service` and `user` tasks more prominently in replace menu ([#1836](https://github.com/bpmn-io/bpmn-js/pull/1836)) * `FEAT`: hide rare items initially from create/append menus ([#1836](https://github.com/bpmn-io/bpmn-js/pull/1836)) * `FEAT`: retrieve instantiation modules with context ([#1835](https://github.com/bpmn-io/bpmn-js/pull/1835)) * `DEPS`: update to `diagram-js@11.9.0` ## 11.2.0 _Adds create/append anything._ * `FEAT`: append menu available via context pad ([#1802](https://github.com/bpmn-io/bpmn-js/pull/1802), [#1809](https://github.com/bpmn-io/bpmn-js/pull/1809), [#1815](https://github.com/bpmn-io/bpmn-js/pull/1815), [#1818](https://github.com/bpmn-io/bpmn-js/pull/1818), [#1831](https://github.com/bpmn-io/bpmn-js/pull/1831)) * `FEAT`: create menu available via palette ([#1811](https://github.com/bpmn-io/bpmn-js/pull/1811), [#1809](https://github.com/bpmn-io/bpmn-js/pull/1809), [#1817](https://github.com/bpmn-io/bpmn-js/pull/1817)) * `FEAT`: simplify connection-multi icon ([#1822](https://github.com/bpmn-io/bpmn-js/pull/1822)) * `FEAT`: join paths `round` by default ([1827](https://github.com/bpmn-io/bpmn-js/pull/1827)) * `FEAT`: improved BPMN symbol rendering ([#1830](https://github.com/bpmn-io/bpmn-js/pull/1830)) * `FEAT`: round connection corners ([#1828](https://github.com/bpmn-io/bpmn-js/pull/1828)) * `FEAT`: improve visibility of popup menu ([#1812](https://github.com/bpmn-io/bpmn-js/issues/1812)) * `FIX`: missing special attributes in `bpmnElementFactory` ([#1807](https://github.com/bpmn-io/bpmn-js/pull/1807)) * `FIX`: handle `bpmn:DataObjectReference` without data object in replace menu ([#1823](https://github.com/bpmn-io/bpmn-js/pull/1823)) * `DEPS`: update to `diagram-js@11.8.0` ## 11.1.1 * `FIX`: correct popup menu display in fullscreen ([#1795](https://github.com/bpmn-io/bpmn-js/issues/1795)) * `DEPS`: update to `diagram-js@11.4.3` ## 11.1.0 * `FEAT`: add replace element keyboard binding ([#1785](https://github.com/bpmn-io/bpmn-js/pull/1785)) * `FEAT`: add `replaceElement` editor action ([#1785](https://github.com/bpmn-io/bpmn-js/pull/1785)) * `DEPS`: update to `diagram-js@11.4.1` ## 11.0.5 * `DEPS`: update to `diagram-js@11.3.0` ## 11.0.4 * `DEPS`: update to `diagram-js@11.2.0` ## 11.0.3 _Re-release of `v11.0.2`._ ## 11.0.2 * `FIX`: correct test for replace options ([#1787](https://github.com/bpmn-io/bpmn-js/pull/1787)) ## 11.0.1 * `DEPS`: update to `diagram-js@11.1.1` ## 11.0.0 _Reworks popup menu UI._ * `FEAT`: integrate new popup menu UI ([#1776](https://github.com/bpmn-io/bpmn-js/pull/1776)) * `DEPS`: update to `diagram-js@11.1.0` ([#1776](https://github.com/bpmn-io/bpmn-js/pull/1776)) ### Breaking Changes * New popup menu UI introduced with `diagram-js@11`. See [`diagram-js` breaking changes and migration guide](https://github.com/bpmn-io/diagram-js/blob/develop/CHANGELOG.md#breaking-changes). * Keyboard-related features no longer use `KeyboardEvent#keyCode`. Use a polyfill (e.g. [keyboardevent-key-polyfill](https://www.npmjs.com/package/keyboardevent-key-polyfill)) if you need to support old browsers. ## 10.3.0 * `FEAT`: add BPMN specific space tool ([#1344](https://github.com/bpmn-io/bpmn-js/pull/1344)) * `FIX`: do not resize `bpmn:TextAnnotation` when using space tool ([#1344](https://github.com/bpmn-io/bpmn-js/pull/1344)) * `FIX`: correct attachers left hanging when using space tool ([#1344](https://github.com/bpmn-io/bpmn-js/pull/1344)) * `FIX`: stick labels to label targets when using space tool ([#1344](https://github.com/bpmn-io/bpmn-js/pull/1344), [#1302](https://github.com/bpmn-io/bpmn-js/issues/1302)) * `DEPS`: update to `diagram-js@10` ## 10.2.1 * `FIX`: correct preserving of outgoing connections on event-based gateway morph ([#1738](https://github.com/bpmn-io/bpmn-js/issues/1738)) ## 10.2.0 * `DEPS`: update to `bpmn-moddle@8` ## 10.1.0 * `DEPS`: update to `diagram-js@9.1.0` ## 10.0.0 _Updates the library target to ES2018._ * `FEAT`: use ES2018 syntax ([#1737](https://github.com/bpmn-io/bpmn-js/pull/1737)) ### Breaking Changes * Migrated to ES2018 syntax. [Read the blog post with details and a migration guide](https://bpmn.io/blog/posts/2022-migration-to-es2018.html). ## 9.4.1 * `FIX`: ignore elements which cannot be colored ([#1734](https://github.com/bpmn-io/bpmn-js/pull/1734)) ## 9.4.0 * `FEAT`: allow clipboard to be serialized ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707)) * `FEAT`: allow cloning of elements ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707)) * `FEAT`: copy groups in a safe manner ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707)) * `FIX`: make clipboard contents immutable ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707)) * `FIX`: do not alter inputs passed to `ElementFactory#create` ([#1711](https://github.com/bpmn-io/bpmn-js/pull/1711)) * `FIX`: prevent bogus meta-data to be attached on paste ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707)) * `FIX`: only claim existing IDs ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707)) * `FIX`: prevent double paste on label creation ([#1707](https://github.com/bpmn-io/bpmn-js/pull/1707)) * `FIX`: move labels when collapsing sub-process ([#1695](https://github.com/bpmn-io/bpmn-js/issues/1695)) * `FIX`: assign default size when expanding element ([#1687](https://github.com/bpmn-io/bpmn-js/issues/1687)) * `FIX`: render sequence flows always on top ([#1716](https://github.com/bpmn-io/bpmn-js/issues/1716)) * `DEPS`: update to `diagram-js@8.9.0` * `DEPS`: update to `bpmn-moddle@7.1.3` ## 9.3.2 * `FIX`: prevent unnecessary scrollbar ([#1692](https://github.com/bpmn-io/bpmn-js/issues/1692)) * `FIX`: check for replacement using actual target ([#1699](https://github.com/bpmn-io/bpmn-js/pull/1699)) * `DEPS`: update to `diagram-js@8.7.1` ## 9.3.1 * `FIX`: properly size icons for distribute/align menu ([#1694](https://github.com/bpmn-io/bpmn-js/pull/1694)) ## 9.3.0 * `FEAT`: add aligment and distribution menu ([#1680](https://github.com/bpmn-io/bpmn-js/issues/1680), [#1691](https://github.com/bpmn-io/bpmn-js/issues/1691)) * `DEPS`: update to `diagram-js@8.7.0` ## 9.2.2 * `FIX`: correctly toggle loop characteristics ([#1673](https://github.com/bpmn-io/bpmn-js/issues/1673)) ## 9.2.1 * `FIX`: cancel direct editing before shape deletion ([#1677](https://github.com/bpmn-io/bpmn-js/issues/1677)) ## 9.2.0 * `FEAT`: rework select and hover interaction on the diagram ([#1616](https://github.com/bpmn-io/bpmn-js/issues/1616), [#640](https://github.com/bpmn-io/diagram-js/pull/640), [#643](https://github.com/bpmn-io/diagram-js/pull/643)) * `FEAT`: rework diagram interaction handles ([#640](https://github.com/bpmn-io/diagram-js/pull/640)) * `FEAT`: clearly distinguish select and hover states ([#1616](https://github.com/bpmn-io/bpmn-js/issues/1616)) * `FEAT`: allow text annotation on sequence flows ([#1652](https://github.com/bpmn-io/bpmn-js/pull/1652)) * `FEAT`: add multi-element context pad ([#1525](https://github.com/bpmn-io/bpmn-js/pull/1525)) * `FEAT`: change default color to off black ([#1656](https://github.com/bpmn-io/bpmn-js/pull/1656)) * `FEAT`: select connection after connect ([#644](https://github.com/bpmn-io/diagram-js/pull/644)) * `FIX`: copy elements with `string` extension properties ([#1518](https://github.com/bpmn-io/bpmn-js/issues/1518)) * `FIX`: cancel direct editing before shape deletion ([#1664](https://github.com/bpmn-io/bpmn-js/issues/1664)) * `FIX`: remove connection on source connection deletion ([#1663](https://github.com/bpmn-io/bpmn-js/issues/1663)) * `FIX`: set correct label color when batch coloring elements ([#1653](https://github.com/bpmn-io/bpmn-js/issues/1653)) * `FIX`: always reconnect labels and associations ([#1659](https://github.com/bpmn-io/bpmn-js/pull/1659)) * `FIX`: correct connection drop highlighting * `DEPS`: replace `inherits` with `inherits-browser` * `DEPS`: bump to `diagram-js@8.5.0` ## 9.1.0 * `FEAT`: allow to select participant and subprocess via click on body ([#1646](https://github.com/bpmn-io/bpmn-js/pull/1646)) * `FIX`: comply with strict style-src CSP ([#1625](https://github.com/bpmn-io/bpmn-js/issues/1625)) * `FIX`: complete direct editing when selection changes ([#1648](https://github.com/bpmn-io/bpmn-js/pull/1648)) * `DEPS`: update to `diagram-js@8.3.0` * `DEPS`: update to `min-dom@3.2.0` ## 9.0.4 * `FIX`: remove `label` property on empty label ([#1637](https://github.com/bpmn-io/bpmn-js/issues/1637)) * `FIX`: create drilldown overlays on `viewer.open` ([`574a67438`](https://github.com/bpmn-io/bpmn-js/commit/574a674381d6449b509396b6d17c4ca94674ea1c)) * `FIX`: render data association inside collapsed sub-processes ([#1619](https://github.com/bpmn-io/bpmn-js/issues/1619)) * `FIX`: preserve multi-instance properties when toggling between parallel and sequential ([#1581](https://github.com/bpmn-io/bpmn-js/issues/1581)) * `FIX`: correct hanging sequence flow label after collapsing sub-process ([#1617](https://github.com/bpmn-io/bpmn-js/issues/1617)) * `FIX`: correct start event not added to newly created sub-process ([#1631](https://github.com/bpmn-io/bpmn-js/issues/1631)) ## 9.0.3 * `FIX`: submit direct editing result on drilldown ([#1609](https://github.com/bpmn-io/bpmn-js/issues/1609)) * `DEPS`: bump to `diagram-js@8.2.0` ([2bac149](https://github.com/bpmn-io/bpmn-js/commit/2bac1495058601fec203c134b41efe5600e5fc97)) ## 9.0.2 * `FIX`: support modeling of groups in collapsed subporcesses ([#1606](https://github.com/bpmn-io/bpmn-js/issues/1606)) * `FIX`: override default padding of breadcrumb element ([#1608](https://github.com/bpmn-io/bpmn-js/pull/1608)) ## 9.0.1 * `FIX`: use ES5 everywhere ([#1605](https://github.com/bpmn-io/bpmn-js/pull/1605)) * `FIX`: support DIs without associated business object ([#1605](https://github.com/bpmn-io/bpmn-js/pull/1605)) * `DEPS`: bump to `diagram-js@8.1.2` ([bdf9cf3](https://github.com/bpmn-io/bpmn-js/commit/bdf9cf3e752254a4c8172031d8a493955a9fca9c)) ## 9.0.0 * `FEAT`: support drilldown and modeling of collapsed subprocesses ([#1443](https://github.com/bpmn-io/bpmn-js/issues/1443)) * `FEAT`: update embedded label bounds when shape is moved ([#1586](https://github.com/bpmn-io/bpmn-js/pull/1586)) * `FIX`: create di for embedded labels ([#1579](https://github.com/bpmn-io/bpmn-js/pull/1579)) * `CHORE`: expose `BpmnRenderer` extension points ([#1585](https://github.com/bpmn-io/bpmn-js/pull/1585)) * `DEPS`: bump to `diagram-js@8.1.1` ### Breaking Changes * Reworked the link of elements to bpmn DIs. You must access the `di` directly from the diagram element instead of the `businessObject` [#1472](https://github.com/bpmn-io/bpmn-js/issues/1472). * Reworked `viewer.open` behavior for single planes ([#1576](https://github.com/bpmn-io/bpmn-js/pull/1576)). * Reworked import and `BpmnFactory` APIs [#1472](https://github.com/bpmn-io/bpmn-js/issues/1472). * Added `bpmn-js.css`, which is required to display drilldown overlays correctly. ## 8.10.0 * `CHORE`: provide `ModelUtil#isAny` utility ([#1604](https://github.com/bpmn-io/bpmn-js/pull/1604)) * `CHORE`: provide `ModelUtil#getDi` utility ([#1604](https://github.com/bpmn-io/bpmn-js/pull/1604)) ## 8.9.1 * `FIX`: re-use process for redo of first participant ([#1439](https://github.com/bpmn-io/bpmn-js/issues/1439)) * `FIX`: ensure IDs are claimed when used ([#1555](https://github.com/bpmn-io/bpmn-js/issues/1555)) * `FIX`: prevent morphing data stores outside participants ([#1508](https://github.com/bpmn-io/bpmn-js/issues/1508)) ## 8.9.0 * `FEAT`: select newly created sub-process ([`6214772b`](https://github.com/bpmn-io/bpmn-js/commit/6214772b8519cb82f61c4867b16c112bc6344922)) * `FEAT`: select newly created group for immediate resizing ([`56eb34cc`](https://github.com/bpmn-io/bpmn-js/commit/56eb34cc826ca0dc8ee788575a504d5fda301292)) * `FEAT`: simplify color scheme * `FIX`: set label color on `bpmndi:BPMNLabel#color` ([#1543](https://github.com/bpmn-io/bpmn-js/pull/1543)) * `FIX`: don't create illegal `bpmndi:BPMNEdge#waypoints` property ([#1544](https://github.com/bpmn-io/bpmn-js/issues/1544)) * `FIX`: correct direct editing on touch devices * `DEPS`: update to `diagram-js@7.8.2` ## 8.8.3 * `FIX`: correct resize handles hidden behind element ([#1520](https://github.com/bpmn-io/bpmn-js/issues/1520)) * `FIX`: handle close to source or target drop on flow ([#1541](https://github.com/bpmn-io/bpmn-js/issues/1541)) * `CHORE`: bump to `diagram-js@7.6.3` ## 8.8.2 * `FIX`: properly re-use ID of a copied element if available ([#1503](https://github.com/bpmn-io/bpmn-js/pull/1509)) ## 8.8.1 * `FIX`: re-use ID of a copied element if available ([#1503](https://github.com/bpmn-io/bpmn-js/pull/1503)) * `CHORE`: unbuild circular dependency with `ResizeUtil` ([#1500](https://github.com/bpmn-io/bpmn-js/pull/1500)) ## 8.8.0 * `FEAT`: give `keyboard` fine-grained control over which events to handle ([#1493](https://github.com/bpmn-io/bpmn-js/issues/1493)) * `FIX`: correct keyboard shortcuts not working in direct editing mode ([#1493](https://github.com/bpmn-io/bpmn-js/issues/1493)) * `DEPS`: update to `diagram-js@7.15` ## 8.7.3 * `FIX`: convert file to `ES6` module ([#1478](https://github.com/bpmn-io/bpmn-js/pull/1478)) ## 8.7.2 * `CHORE`: improve error recovery in ordering provider * `DEPS`: update build dependencies ## 8.7.1 * `FIX`: allow connecting `bpmn:MessageFlow` to `bpmn:CallActivity` ([#1467](https://github.com/bpmn-io/bpmn-js/issues/1467)) * `DEPS`: update to `bpmn-moddle@7.1.2` ## 8.7.0 * `FEAT`: support BPMN in Color ([#1453](https://github.com/bpmn-io/bpmn-js/pull/1453)) * `DEPS`: update to `bpmn-moddle@7.1.1` ## 8.6.2 * `DEPS`: update diagram-js-direct-editing to v1.6.3 ## 8.6.1 * `FIX`: serialize `bpmn:DataStoreReference` correctly in case if first participant is an empty pool ([#1456](https://github.com/bpmn-io/bpmn-js/issues/1456)) ## 8.6.0 * `FEAT`: support Promise in `inject` test helper ([#1450](https://github.com/bpmn-io/bpmn-js/pull/1450)) * `DEPS`: update to `hosted-git@2.8.9` ([#1447](https://github.com/bpmn-io/bpmn-js/pull/1447)) ## 8.5.0 * `FEAT`: reconnect message flows when participant is collapsed ([#1432](https://github.com/bpmn-io/bpmn-js/pull/1432)) * `FEAT`: replace elements on create ([#1340](https://github.com/bpmn-io/bpmn-js/issues/1340)) * `FEAT`: show message name on message flow ([#777](https://github.com/bpmn-io/bpmn-js/issues/777)) * `FEAT`: ensure auto-placed elements are visible * `FIX`: fix reversed connection preview ([#1431](https://github.com/bpmn-io/bpmn-js/issues/1431)) * `FIX`: copy root element references on replace ([#1430](https://github.com/bpmn-io/bpmn-js/issues/1431)) * `DEPS`: update to `diagram-js@7.3.0` ## 8.4.0 * `FIX`: disallow inserting multiple elements on a sequence flow ([#1440](https://github.com/bpmn-io/bpmn-js/issues/1440)) ## 8.3.1 * `FIX`: correctly serialize `xml` attributes on `Any` elements * `DEPS`: update bump to `bpmn-moddle@7.0.5` ## 8.3.0 * `FEAT`: enable connection tool for text annotations ([#1428](https://github.com/bpmn-io/bpmn-js/pull/1428)) ## 8.2.2 * `FIX`: always emit `saveXML.done` * `FIX`: correct path intersections not being detected in certain cases * `CHORE`: bump to `diagram-js@7.2.3` ## 8.2.1 * `FIX`: prevent bendpoint hover error ([#1387](https://github.com/bpmn-io/bpmn-js/issues/1387)) ## 8.2.0 * `FIX`: correct label colors on connect / hover ([#1380](https://github.com/bpmn-io/bpmn-js/issues/1380)) * `FIX`: correct new parent indicator when leaving lane ([#1413](https://github.com/bpmn-io/bpmn-js/issues/1413)) * `CHORE`: update to `diagram-js@7.2.0` ## 8.1.0 * `TEST`: simplify markup created by built-in test helpers ## 8.0.1 * `FIX`: activate, not toggle global connect tool on palette click * `FIX`: only allow cancel boundary events on transactions * `CHORE`: add `npm start` script for demo purposes ## 8.0.0 * `FEAT`: improve replace label for collapsed pools ([`8faee2bd`](https://github.com/bpmn-io/bpmn-js/commit/8faee2bde9a74b75b4b6bb9b003507652e75c9c5)) * `FEAT`: allow participant multiplicity marker to be toggled ([#533](https://github.com/bpmn-io/bpmn-js/issues/533)) * `FEAT`: support soft breaks / discretionary hyphens in labels ([#1383](https://github.com/bpmn-io/bpmn-js/issues/1383)) * `FEAT`: improve tool activation via keyboard shortcuts or editor actions * `FEAT`: allow components to react to auxiliary mouse button interactions * `FEAT`: move canvas on auxiliary button mouse down * `CHORE`: bump to `diagram-js@7` ### Breaking Changes * Auxiliary mouse button events will now be passed as `element.*` mouse events to components. You must filter your event listeners to prevent reactions to these events ([`1063f7c1`](https://github.com/bpmn-io/diagram-js/commit/1063f7c18474096d3d7c9e400ce82a1bf762a157)). ## 7.5.0 * `FEAT`: update translatable strings ([#1364](https://github.com/bpmn-io/bpmn-js/pull/1364)) * `FEAT`: add collection marker to DataObjectReference ([#381](https://github.com/bpmn-io/bpmn-js/issues/381)) * `FEAT`: provide generic command for updating moddle properties ([#1376](https://github.com/bpmn-io/bpmn-js/pull/1376)) * `FEAT`: add switch between DataStoreReference and DataObjectReference in replace menu ([#1372](https://github.com/bpmn-io/bpmn-js/issues/1372)) * `FIX`: align collection and parallel instance markers style ([#1371](https://github.com/bpmn-io/bpmn-js/issues/1371)) ## 7.4.2 * `FIX`: correctly emit out `element.event` after drop-on-flow ([#1366](https://github.com/bpmn-io/bpmn-js/issues/1366)) ## 7.4.1 * `FIX`: correct keyboard zoom in key on international keyboard shortcuts ([#1362](https://github.com/bpmn-io/bpmn-js/issues/1362)) ## 7.4.0 * `CHORE`: bump to `diagram-js@6.8.0` * `CHORE`: migrate to `travis-ci.com` ## 7.3.1 * `CHORE`: bump to `diagram-js@6.7.1` ## 7.3.0 * `FEAT`: disallow typed start events inside non-event based sub processes ([#831](https://github.com/bpmn-io/bpmn-js/issues/831)) * `CHORE`: bump to `diagram-js@6.7.0` ## 7.2.1 * `FIX`: disallow boundary events as message flow targets ([#1300](https://github.com/bpmn-io/bpmn-js/issues/1300)) ## 7.2.0 _Republish of `v7.1.0`._ ## 7.1.0 * `FEAT`: allow annotating groups ([#1327](https://github.com/bpmn-io/bpmn-js/issues/1327)) ## 7.0.1 * `FIX`: roundtrip default `xml` namespace ([#1319](https://github.com/bpmn-io/bpmn-js/issues/1319)) * `CHORE`: bump to `bpmn-moddle@7.0.3` ## 7.0.0 * `FEAT`: make import and export APIs awaitable ([#812](https://github.com/bpmn-io/bpmn-js/issues/812)) * `FEAT`: update watermark ([#1281](https://github.com/bpmn-io/bpmn-js/pull/1281)) * `CHORE`: deprecated `import.parse.complete` context payload ([`157aec6e`](https://github.com/bpmn-io/bpmn-js/commit/157aec6e)) * `CHORE`: clarify license terms ([`bc98a637`](https://github.com/bpmn-io/bpmn-js/commit/bc98a63712f6ac5c66d39f59bf93e296e59ad1e0)) * `CHORE`: bump to `bpmn-moddle@7.0.1` ### Breaking Changes * The toolkit now requires the ES6 `Promise` to be present. To support IE11 you must polyfill it. ## 6.5.1 * `FIX`: correct namespaces being removed on diagram export ([#1310](https://github.com/bpmn-io/bpmn-js/issues/1310)) * `CHORE`: bump to `bpmn-moddle@6.0.6` ## 6.5.0 * `FEAT`: prefer straight layout for sub-process connections ([#1309](https://github.com/bpmn-io/bpmn-js/pull/1309)) * `FEAT`: move common auto-place feature to diagram-js, add BPMN-specific auto-place feature ([#1284](https://github.com/bpmn-io/bpmn-js/pull/1284)) * `CHORE`: make bpmn-font a development dependency ([`63045bdf`](https://github.com/bpmn-io/bpmn-js/commit/63045bdfa87b9f1989a2a7a509facbeb4616acda)) * `CHORE`: bump to `diagram-js@6.6.1` ## 6.4.2 * `CHORE`: bump to `bpmn-moddle@6.0.5` ## 6.4.1 * `FIX`: parse `>` in attribute names and body tag * `CHORE`: bump to `bpmn-moddle@6.0.4` ## 6.4.0 * `FEAT`: serialize link events with an empty name ([#1296](https://github.com/bpmn-io/bpmn-js/issues/1296)) ## 6.3.5 * `FIX`: correct accidental resizing of label target ([#1294](https://github.com/bpmn-io/bpmn-js/issues/1294)) ## 6.3.4 * `FIX`: export BPMNDI in correct order ([#985](https://github.com/bpmn-io/bpmn-js/issues/985)) ## 6.3.3 * `FIX`: resize empty text annotations * `CHORE`: bump `min-dom` version * `CHORE`: bump to `diagram-js@6.4.1` ## 6.3.2 * `FIX`: correctly move flows when adding lane ([#1287](https://github.com/bpmn-io/bpmn-js/pull/1287)) * `FIX`: restore semantic IDs for non flow nodes ([#1285](https://github.com/bpmn-io/bpmn-js/issues/1285)) ## 6.3.1 * `FIX`: prevent editor crash in some strict execution environments ([#1283](https://github.com/bpmn-io/bpmn-js/pull/1283)) ## 6.3.0 * `FEAT`: generate more generic IDs for new elements ([`035bb0c1`](https://github.com/bpmn-io/bpmn-js/commit/035bb0c1fd01adbaab8a340cb1075aa57736540d)) * `FEAT`: copy referenced root elements (message, signal, ...) ([`dc5a566e`](https://github.com/bpmn-io/bpmn-js/commit/dc5a566e107bc156505a40de3331b3832afc4b8d)) * `FEAT`: ensure minimum size when resizing elements with space tool ([`7ee304f4`](https://github.com/bpmn-io/bpmn-js/commit/7ee304f424d1c9db46633523165d25ca1fabba1b)) * `FIX`: correct interaction events inside `bpmn:Group` elements ([#1278](https://github.com/bpmn-io/bpmn-js/issues/1278)) * `FIX`: correct copy and paste of collapsed sub-processes ([#1270](https://github.com/bpmn-io/bpmn-js/issues/1270)) * `FIX`: correct various space tool related issues ([#1019](https://github.com/bpmn-io/bpmn-js/issues/1019), [#878](https://github.com/bpmn-io/bpmn-js/issues/878)) * `CHORE`: rework space tool * `CHORE`: update to `diagram-js@6.4.0` ## 6.2.1 * `FIX`: correct serialization of `DataAssociation#assignment` * `CHORE`: update to `bpmn-moddle@6.0.2` ## 6.2.0 * `FIX`: keep non-duplicate outgoing connection when dropping on flows ([#1263](https://github.com/bpmn-io/bpmn-js/issues/1263)) * `FIX`: properly reconnect message flows when collapsing participant * `CHORE`: update to `diagram-js@6.3.0` * `CHORE`: update to `bpmn-moddle@6.0.1` ## 6.1.2 * `FIX`: translate _Append ReceiveTask_ * `FIX`: allow associations where data associations are allowed, too ([`4a675b37`](https://github.com/bpmn-io/bpmn-js/commit/4a675b378027532db413186ea292daeac087285b)) * `FIX`: correct origin snapping on multi-element create ([`27fec8bd`](https://github.com/bpmn-io/bpmn-js/commit/27fec8bdf1c6236e7ca09b5721b74b1b45b45d39)) * `CHORE`: update to `diagram-js@6.2.2` ## 6.1.1 _Republish of `v6.1.0`._ ## 6.1.0 * `FEAT`: copy signals, escalations and errors ([#1245](https://github.com/bpmn-io/bpmn-js/pull/1245)) * `FEAT`: provide base viewer / modeler distributions ([`bb94b206`](https://github.com/bpmn-io/bpmn-js/commit/bb94b206a7c9ab3b80e283d6513600a9591c437d)) * `FEAT`: add horizontal and vertical resize handles * `FEAT`: improve connection cropping (bump to `path-intersection@2`) * `FIX`: correctly mark elements as changed on `{shape|connection}.create` undo * `FIX`: do not open replace menu after multi create ([#1255](https://github.com/bpmn-io/bpmn-js/pull/1255)) * `CHORE`: update to `diagram-js@6.2.0` ## 6.0.7 * `FIX`: disable waypoints-cropping after pasting connections ([`9f8a724e`](https://github.com/bpmn-io/bpmn-js/commit/9f8a724e9a3ff66bfce14e06ab38066189111a95)) ## 6.0.6 * `FIX`: create nested lanes in the correct parent again ([#1256](https://github.com/bpmn-io/bpmn-js/issues/1256), [#1253](https://github.com/bpmn-io/bpmn-js/issues/1253), [#1254](https://github.com/bpmn-io/bpmn-js/issues/1254)) ## 6.0.5 * `FIX`: only update `Lane#flownNodeRefs` once during paste ([`4455c3fc`](https://github.com/bpmn-io/bpmn-js/commit/4455c3fc35290e51220566fb6539a1efc4d3612f)) * `FIX`: do not adjust labels on paste ([`b2b607f5`](https://github.com/bpmn-io/bpmn-js/commit/b2b607f5582d3409c789d831a0896aaa55949899)) * `FIX`: do not snap connection waypoints on paste ([`d769e6dd`](https://github.com/bpmn-io/bpmn-js/commit/d769e6ddb0cb2dc8befb2e7b31682925089ba8f1)) ## 6.0.4 * `FIX`: correctly fix hover on cleanup ([#1247](https://github.com/bpmn-io/bpmn-js/pull/1247)) ## 6.0.3 * `FIX`: render colored BPMN groups ([#1246](https://github.com/bpmn-io/bpmn-js/pull/1246)) * `CHORE`: bump to `diagram-js@6.0.2` ## 6.0.2 * `CHORE`: bump `diagram-js-direct-editing` dependency ## 6.0.1 * `CHORE`: bump to `diagram-js@6.0.1` ## 6.0.0 * `FEAT`: rework (re-)connecting of shapes ([#427](https://github.com/bpmn-io/bpmn-js/pull/1230)) ### Breaking Changes Connecting and re-connecting shapes got reworked via [#427](https://github.com/bpmn-io/bpmn-js/pull/1230): * The rules `connection.reconnectStart` and `connection.reconnectEnd` got replaced with `connection.reconnect` rule * `BpmnLayouter#layoutConnection` waypoints can be specified via hint ## 5.1.2 * `FIX`: account for label pasting in label behavior ([#1227](https://github.com/bpmn-io/bpmn-js/issues/1227)) ## 5.1.1 * `FIX`: re-select only existing elements when dragging is finished ([#1225](https://github.com/bpmn-io/bpmn-js/issues/1225)) * `FIX`: correctly hide nested children of a collapsed shape * `CHORE`: bump to [`diagram-js@5.1.1`](https://github.com/bpmn-io/diagram-js/blob/develop/CHANGELOG.md#511) ## 5.1.0 * `FEAT`: adjust label position post creation ([`41c6af18`](https://github.com/bpmn-io/bpmn-js/commit/41c6af183014626a0f84e0bda0f8e39018f9151e)) * `FEAT`: copy and paste boundary events ([`2e27d743`](https://github.com/bpmn-io/bpmn-js/commit/2e27d7430642439e30806941d0df43018ca729eb)) * `FIX`: ordering after moving boundary events between hosts ([#1207](https://github.com/bpmn-io/bpmn-js/issues/1207)) * `FIX`: do not remove sequence flow condition on type change ([`b2900786`](https://github.com/bpmn-io/bpmn-js/commit/b290078600ae4e45e7c72bd37919732e3f8fcbea)) * `FIX`: do not remove default sequence flow on type change ([`37bcd070`](https://github.com/bpmn-io/bpmn-js/commit/37bcd070e8406a43a7316893c6b68debeaae5e26)) * `FIX`: do not duplicate flow node references ([`168a1493`](https://github.com/bpmn-io/bpmn-js/commit/168a1493b26c3059d2440a70f7aa5991745b51e5)) * `FIX`: ignore labels that are being created in adaptive label positioning ([`44cceb5d`](https://github.com/bpmn-io/bpmn-js/commit/44cceb5da287a0ad01d9389f475284c88eda7f7b)) ## 5.0.5 * `FIX`: snap connections to task mid ([`86c61b0`](https://github.com/bpmn-io/bpmn-js/commit/86c61b0c0d6dcf776adda94b6d72b621644c2abe)) * `FIX`: snap connections to sub process mid ([`83e9f05`](https://github.com/bpmn-io/bpmn-js/commit/83e9f05efab6fbe57100e11d0443291a561bdfe4)) * `FIX`: complete direct editing when auto place starts ([`dcf440b`](https://github.com/bpmn-io/bpmn-js/commit/dcf440b07684339bdb52ba97cd1c83f9eb234044)) * `FIX`: do not clear diagram if no diagram to clear ([#1181](https://github.com/bpmn-io/bpmn-js/issues/1181)) * `FIX`: copy boundary events attachments ([#1190](https://github.com/bpmn-io/bpmn-js/issues/1190)) * `FIX`: do not copy generic properties ([`a74d83`](https://github.com/bpmn-io/bpmn-js/commit/a74d838dc78aceddf88e07231cf85a4cf9e0dd95)) ## 5.0.4 * `FIX`: correct sequence flow layout after drop on flow ([#1178](https://github.com/bpmn-io/bpmn-js/issues/1178)) ## 5.0.3 _Republish of `v5.0.2`._ ## 5.0.2 * `FIX`: allow reconnecting to loops ([#1121](https://github.com/bpmn-io/bpmn-js/issues/1121)) * `CHORE`: bump to `diagram-js@5.0.1` ## 5.0.1 * `FIX`: import boundary event associations ([#1170](https://github.com/bpmn-io/bpmn-js/issues/1170)) ## 5.0.0 * `FEAT`: add two-step copy and paste ([#1137](https://github.com/bpmn-io/bpmn-js/pull/1137)) * `FEAT` add `elements.create` rule for creating multiple elements ([#1137](https://github.com/bpmn-io/bpmn-js/pull/1137)) * `FEAT`: make containers draggable via their borders / labels only ([#1097](https://github.com/bpmn-io/bpmn-js/pull/1097), [#957](https://github.com/bpmn-io/bpmn-js/issues/957)) * `FEAT`: allow copied elements to be filtered ([#888](https://github.com/bpmn-io/bpmn-js/issues/888)) * `FIX`: prevent accidental dragging of participants and sub-processes ([#1097](https://github.com/bpmn-io/bpmn-js/pull/1097), [#957](https://github.com/bpmn-io/bpmn-js/issues/957)) * `FIX`: keep labels during pool extraction ([#921](https://github.com/bpmn-io/bpmn-js/issues/921)) * `FIX`: duplicate `bpmn:CategoryValue` when copying groups ([#1055](https://github.com/bpmn-io/bpmn-js/issues/1055)) * `FIX`: translate group creation entry in palette ([#1146](https://github.com/bpmn-io/bpmn-js/issues/1146)) * `CHORE`: use `element.copyProperty` event to copy category value when copying group ([`12bedca5`](https://github.com/bpmn-io/bpmn-js/pull/1137/commits/12bedca5ba2a05791591e53f554dc2310f6c1a6f)) * `CHORE`: bump to `diagram-js@5` ### Breaking Changes Copy and paste as well as create is completely reworked: * `CopyPaste`: remove `ModelCloneHelper` in favor of `ModdleCopy` service, remove `property.clone` event, add `moddleCopy.canCopyProperties`, `moddleCopy.canCopyProperty` and `moddleCopy.canSetCopiedProperty` event * `BpmnRules`: removed `elements.paste` rule in favor of `elements.create` rule * `BpmnRules`: removed `element.paste` rule * `ElementFactory`: use `attrs.di` property instead of `attrs.colors` for fill and stroke when creating element through `ElementFactory#createBpmnElement` * To prevent additional behavior on create after paste you should check for the `createElementsBehavior` hint, cf. [`bf180321`](https://github.com/bpmn-io/bpmn-js/commit/bf180321a3a40428c3f87b639b87cc3fc578066e#diff-2f0de25761fb7459e88071f83fd845c5R22) ## 4.0.4 * `FIX`: creating `bpmn:Participant` on single `bpmn:Group` throwing error ([#1133](https://github.com/bpmn-io/bpmn-js/issues/1133)) * `CHORE`: bump to `diagram-js@4.0.3` ## 4.0.3 * `FIX`: prevent dropping on labels and `bpmn:Group` elements ([#1131](https://github.com/bpmn-io/bpmn-js/pull/1131)) ## 4.0.2 * `FIX`: correct element positioning update ([#1129](https://github.com/bpmn-io/bpmn-js/issues/1129)) * `CHORE`: bump to `diagram-js@4.0.2` ## 4.0.1 * `FIX`: prevent adding lane from crashing IE ([#746](https://github.com/bpmn-io/bpmn-js/issues/746)) * `FIX`: correct inverse space tool visuals ([#1105](https://github.com/bpmn-io/bpmn-js/issues/1105)) * `CHORE`: update `diagram-js-direct-editing` to prevent install warning * `CHORE`: update to `diagram-js@4.0.1` ## 4.0.0 * `FEAT`: add top, right, bottom, left snapping with container elements ([#1108](https://github.com/bpmn-io/bpmn-js/pull/1108)) * `FEAT`: add grid snapping ([#987](https://github.com/bpmn-io/bpmn-js/pull/987)) * `FEAT`: allow modeling of groups ([#343](https://github.com/bpmn-io/bpmn-js/issues/343)) * `FEAT`: improve modeling rules behind event-based gateways ([#1006](https://github.com/bpmn-io/bpmn-js/pull/1006)) * `FEAT`: adjust default collapsed pool to standard height ([`5affe2570`](https://github.com/bpmn-io/bpmn-js/commit/5affe25705082937beace6b4a568f176a0527baf)) * `FEAT`: add connection previews ([#743](https://github.com/bpmn-io/bpmn-js/issues/743)) * `FEAT`: create expanded sub-process with start event included ([#1039](https://github.com/bpmn-io/bpmn-js/pull/1039)) * `FEAT`: improve automatic label adjustment for boundary events ([#1064](https://github.com/bpmn-io/bpmn-js/pull/1064)) * `FEAT`: improve creation of initial participant ([#1046](https://github.com/bpmn-io/bpmn-js/pull/1046)) * `FEAT`: improve boundary to host loop layout ([#1070](https://github.com/bpmn-io/bpmn-js/pull/1070)) * `FEAT`: make connection segment move the primary connection drag behavior * `FEAT`: allow label and group movement everywhere ([#1080](https://github.com/bpmn-io/bpmn-js/pull/1080)) * `FEAT`: improve message flow to participant connection in the presence of lanes ([#950](https://github.com/bpmn-io/bpmn-js/issues/950)) * `FEAT`: allow detaching of boundary and attaching of intermediate events ([#1045](https://github.com/bpmn-io/bpmn-js/issues/1045)) * `FEAT`: simplify requested palette and context pad translations ([#1027](https://github.com/bpmn-io/bpmn-js/pull/1027)) * `FEAT`: simplify participant dragging in the presence of nested lanes ([`fdb299dc`](https://github.com/bpmn-io/bpmn-js/commit/fdb299dc888a7dcdb3f7674b6ed2a857864df457)) * `FEAT`: correctly render all kinds of multiple events ([#1091](https://github.com/bpmn-io/bpmn-js/pull/1091)) * `CHORE`: validate BPMN 2.0 XML ids as QNames ([`92c03679a`](https://github.com/bpmn-io/bpmn-js/commit/92c03679a4fd3c92a1c5ce3c97f7d366e2a5753a)) * `FIX`: correctly handle flow reconnection + type replacement ([#896](https://github.com/bpmn-io/bpmn-js/issues/896), [#1008](https://github.com/bpmn-io/bpmn-js/issues/1008)) ### Breaking Changes * `CHORE`: bump to [`diagram-js@4.0.0`](https://github.com/bpmn-io/diagram-js/blob/main/CHANGELOG.md#400) ## 3.5.0 * `FEAT`: restore `Viewer#importDefinitions` and make it public API ([#1112](https://github.com/bpmn-io/bpmn-js/pull/1112)) ## 3.4.3 * `FIX`: prevent HTML injection in search ([diagram-js#362](https://github.com/bpmn-io/diagram-js/pull/362)) ## 2.5.4 * `FIX`: prevent HTML injection in search ([diagram-js#362](https://github.com/bpmn-io/diagram-js/pull/362)) * `CHORE`: bump to `diagram-js@2.6.2` ## 3.4.2 * `FIX`: do not evaluate pasted text as HTML ([#1073](https://github.com/bpmn-io/bpmn-js/issues/1073)) ## 2.5.3 * `FIX`: do not evaluate pasted text as HTML ([#1073](https://github.com/bpmn-io/bpmn-js/issues/1073)) ## 3.4.1 _Republish of `v3.4.0` without `.git` folder._ ## 3.4.0 * `FIX`: properly render colored connection markers ([#981](https://github.com/bpmn-io/bpmn-js/issues/981)) * `FEAT`: add ability to open different DI diagrams ([#87](https://github.com/bpmn-io/bpmn-js/issues/87)) * `FIX`: correctly layout straight boundary to target connections ([#891](https://github.com/bpmn-io/bpmn-js/issues/891)) * `FEAT`: resize participant to standard size on collapse ([#975](https://github.com/bpmn-io/bpmn-js/pull/975)) * `FEAT`: consistently layout connection on reconnect start and end ([#971](https://github.com/bpmn-io/bpmn-js/pull/971)) * `FEAT`: layout connection on element removal ([#989](https://github.com/bpmn-io/bpmn-js/issues/989)) * `FIX`: properly crop sequence flow ends on undo/redo ([#940](https://github.com/bpmn-io/bpmn-js/issues/940)) * `CHORE`: bump to [`diagram-js@3.3.0`](https://github.com/bpmn-io/diagram-js/blob/main/CHANGELOG.md#330) ## 3.3.1 * `FIX`: ignore unchanged direct editing completion * `CHORE`: update to `diagram-js-direct-editing@1.4.2` ## 3.3.0 * `FEAT`: display `DataInput` / `DataOutput` labels ([`89719de3b`](https://github.com/bpmn-io/bpmn-js/commit/89719de3be50d9270227fd04216f7f19f0d018a2)) * `FEAT`: support basic `DataInput` / `DataOutput` move ([#962](https://github.com/bpmn-io/bpmn-js/pull/962)) * `FIX`: properly handle `DataInput` / `DataOutput` move ([#961](https://github.com/bpmn-io/bpmn-js/issues/961)) ## 3.2.3 * `FIX`: update to `diagram-js-direct-editing@1.4.1` to trim trailing/leading whitespace in task names ([#763](https://github.com/bpmn-io/bpmn-js/issues/763)) ## 3.2.2 * `FIX`: gracefully handle missing waypoints ([`45486f2`](https://github.com/bpmn-io/bpmn-js/commit/45486f2afe7f42fcac31be9ca477a7c94babe7d8)) ## 3.2.1 * `FIX`: bump to `diagram-js@3.1.3` / `tiny-svg@2.2.1` to work around MS Edge bug ([`ed798a15`](https://github.com/bpmn-io/bpmn-js/commit/ed798a152539a613dbc9de9d61231ebbfb50987a)) ## 3.2.0 * `FEAT`: set isHorizontal=true for new and updated participant/lane DIs ([#934](https://github.com/bpmn-io/bpmn-js/issues/934)) ## 3.1.1 * `CHORE`: update to `diagram-js@3.1.1` ## 3.1.0 * `CHORE`: update to `diagram-js@3.1` ## 3.0.4 * `FIX`: render labels always on top ([#920](https://github.com/bpmn-io/bpmn-js/pull/920)) ## 3.0.3 * `FIX`: do not join incoming/outgoing flows other than sequence flows on element deletion ([#917](https://github.com/bpmn-io/bpmn-js/issues/917)) ## 3.0.2 * `FIX`: correct IE 11 delete keybinding ([#904](https://github.com/bpmn-io/bpmn-js/issues/904)) ## 3.0.1 * `FIX`: restore copy-paste behavior ## 3.0.0 * `FEAT`: improve context pad tooltip titles for `EventBasedGateway` ([`350a5ab`](https://github.com/bpmn-io/bpmn-js/commit/350a5ab75ed675991599faff9615e4bbe184d491)) * `FEAT`: display group names ([#844](https://github.com/bpmn-io/bpmn-js/issues/844)) * `FEAT`: add ability to move selection with keyboard arrows ([#376](https://github.com/bpmn-io/bpmn-js/issues/376)) * `FEAT`: support `SHIFT` modifier to move elements / canvas with keyboard arrows at accelerated speed * `FEAT`: require `Ctrl/Cmd` to be pressed as a modifier key to move the canvas via keyboard errors * `FEAT`: auto-expand elements when children resize ([#786](https://github.com/bpmn-io/bpmn-js/issues/786)) * `CHORE`: bind editor actions and keyboard shortcuts for explicitly added features only ([#887](https://github.com/bpmn-io/bpmn-js/pull/887)) * `CHORE`: update to [`diagram-js@3.0.0`](https://github.com/bpmn-io/diagram-js/blob/main/CHANGELOG.md#300) * `FIX`: disallow attaching of `BoundaryEvent` to a `ReceiveTask` following an `EventBasedGateway` ([#874](https://github.com/bpmn-io/bpmn-js/issues/874)) * `FIX`: fix date in license ([#882](https://github.com/bpmn-io/bpmn-js/pull/882)) ### Breaking Changes * `BpmnGlobalConnect` provider got removed. Use `connection.start` rule to customize whether connection should allowed to be started ([#565](https://github.com/bpmn-io/bpmn-js/issues/565), [#870](https://github.com/bpmn-io/bpmn-js/issues/870)) * `EditorActions` / `Keyboard` do not pull in features implicitly anymore. If you roll your own editor, include features you would like to ship with manually to provide the respective actions / keyboard bindings ([`645265ad`](https://github.com/bpmn-io/bpmn-js/commit/645265ad7e4a47e80657c671068a027752d7504f)) * Moving the canvas with keyboard arrows now requires the `Ctrl/Cmd` modifiers to be pressed. ## 2.5.2 * `FIX`: correct horizontal embedded label padding ## 2.5.1 * `FIX`: prevent error to be thrown on lane move ([#855](https://github.com/bpmn-io/bpmn-js/issues/855)) ## 2.5.0 * `FEAT`: snap message flows to `bpmn:Event` center during connect ([#850](https://github.com/bpmn-io/bpmn-js/issues/850)) * `CHORE`: bump to `diagram-js@2.6.0` * `FIX`: allow label movement over message flow ([#849](https://github.com/bpmn-io/bpmn-js/issues/849)) ## 2.4.1 * `FIX`: make viewer IE 9 compatible * `FIX`: prevent duplicate connections after drop on flow ([#774](https://github.com/bpmn-io/bpmn-js/issues/774)) * `FIX`: fix rules not preventing redundant loop ([#836](https://github.com/bpmn-io/bpmn-js/issues/836)) ## 2.4.0 * `FEAT`: improve layouting of boundary event to host loops ([#467](https://github.com/bpmn-io/bpmn-js/issues/467)) * `FEAT`: allow circular activity to activity loops ([#824](https://github.com/bpmn-io/bpmn-js/issues/824)) * `FEAT`: create label on appropriate free position ([#825](https://github.com/bpmn-io/bpmn-js/issues/825)) * `CHORE`: bump to `diagram-js@2.5.0` * `FIX`: repair label position not being adapted on host move ## 2.3.1 * `FIX`: revert to `Arial` as the default rendering font ([#819](https://github.com/bpmn-io/bpmn-js/issues/819)) * `FIX`: keep event definitions when switching from interrupting to non-interrupting boundary event ([#799](https://github.com/bpmn-io/bpmn-js/issues/799)) ## 2.3.0 * `CHORE`: update to `diagram-js@2.4.0` ## 2.2.1 * `FIX`: correct updating of multiple data stores ([`300e7010`](https://github.com/bpmn-io/bpmn-js/commit/300e7010c4e1862394d147988dc4c4bcc09b07bc)) ## 2.2.0 * `FEAT`: emit export events ([#813](https://github.com/bpmn-io/bpmn-js/issues/813)) * `FEAT`: unset businessObject name if empty ([`6c081d85`](https://github.com/bpmn-io/bpmn-js/commit/6c081d854fa8a4e87eb7cdd1744be37c78652667)) * `FEAT`: resize text annotation on text change ([`100f3fb2`](https://github.com/bpmn-io/bpmn-js/commit/100f3fb2ee6373cd4b7ad0b76e520a1afb70887e)) * `FIX`: apply data store behavior in collaboration only ([`5cc28d5d`](https://github.com/bpmn-io/bpmn-js/commit/5cc28d5d5571287a798b189aed75095f1fd0189e)) * `FIX`: create/update labels when updating element name via `Modeling#updateProperties` ([`4a0f6da8`](https://github.com/bpmn-io/bpmn-js/commit/4a0f6da814c45268e8a324e73a53479bd2435bbe)) ## 2.1.0 * `FEAT`: support specifying `lineHeight` for text rendering ([#256](https://github.com/bpmn-io/diagram-js/pull/256)) * `FEAT`: `bpmn:LaneSet` elements get an ID assigned on creation * `FEAT`: external labels can be deleted, clearing the elements name ([#791](https://github.com/bpmn-io/bpmn-js/pull/791)) * `FEAT`: add ability to override default element colors ([#713](https://github.com/bpmn-io/bpmn-js/issues/713)) * `FEAT`: add ability to override font family and size of rendered labels ([`4bb270f1`](https://github.com/bpmn-io/bpmn-js/commit/4bb270f19279db40f9cc3c179e09ee3a9a114e7c)) ## 2.0.1 _Republish of `v2.0.0` due to registry error._ ## 2.0.0 * `FEAT`: allow data store to be modeled between participants ([#483](https://github.com/bpmn-io/bpmn-js/issues/483)) * `CHORE`: update to [`diagram-js@2.0.0`](https://github.com/bpmn-io/diagram-js/blob/main/CHANGELOG.md#200) * `FIX`: correctly handle missing `bpmndi:Label` bounds during model updating ([#794](https://github.com/bpmn-io/bpmn-js/issues/794)) ### Breaking Changes * The `PopupMenu` API got rewritten, cf. [`b1852e1d`](https://github.com/bpmn-io/diagram-js/pull/254/commits/b1852e1d71f67bd36ae1eb02748d2d0cbf124625) ## 1.3.3 * `CHORE`: update to [`bpmn-moddle@5.1.5`](https://github.com/bpmn-io/bpmn-moddle/blob/main/CHANGELOG.md#515) ## 1.3.2 * `FIX`: correctly serialize extension attributes on `bpmn:Expression` ## 1.3.1 * `FIX`: correctly auto-place from boundary events attached to host edges ([#788](https://github.com/bpmn-io/bpmn-js/issues/788)) ## 1.3.0 * `FEAT`: expose additional `BpmnTreeWalker` APIs for advanced import use-cases * `CHORE`: bump diagram-js and object-refs version ## 1.2.1 * `FIX`: correct side-effects config to not include `*.css` files ## 1.2.0 * `FEAT`: add initial snapping when creating associations * `CHORE`: update to `diagram-js@1.3.0` * `FIX`: allow message flows between collapsed pools * `FIX`: complete direct editing on popup menu use * `FIX`: focus label editing box on element creation ## 1.1.1 * `FIX`: escape `data-element-id` in CSS selectors ## 1.1.0 * `FEAT`: show gateway icon on context pad without marker ([`15dfab6b`](https://github.com/bpmn-io/bpmn-js/commit/15dfab6b5b12dd184acf070f2ab3ad205d1b245c)) ## 1.0.4 * `FIX`: properly wire `$parent` on copy + paste * `FIX`: improve boundary event rendering to correct SVG to image conversion ## 1.0.3 * `FIX`: re-expose `TestHelper#bootstrapBpmnJS` util ## 1.0.2 * `FIX`: correct library default export ## 1.0.1 _Republished 1.0.0 with CHANGELOG entries._ ## 1.0.0 * `CHORE`: convert code base to ES modules * `CHORE`: update utility toolbelt ### Breaking Changes * You must now configure a module transpiler such as Babel or Webpack to handle ES module imports and exports. ## 0.31.0 * `FEAT`: encode entities in body properties during XML export * `CHORE`: bump to [`bpmn-moddle@4.0.0`](https://github.com/bpmn-io/bpmn-moddle/releases/tag/v4.0.0) * `CHORE`: bump utility version ## 0.30.0 * `CHORE`: bump to [`diagram-js@0.31.0`](https://github.com/bpmn-io/diagram-js/releases/tag/v0.31.0) ## ... Check `git log` for earlier history. ================================================ FILE: LICENSE ================================================ Copyright (c) 2014-present Camunda Services GmbH Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The source code responsible for displaying the bpmn.io project watermark that links back to https://bpmn.io as part of rendered diagrams MUST NOT be removed or changed. When this software is being used in a website or application, the watermark must stay fully visible and not visually overlapped by other elements. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # bpmn-js - BPMN 2.0 for the web [![Build Status](https://github.com/bpmn-io/bpmn-js/workflows/CI/badge.svg)](https://github.com/bpmn-io/bpmn-js/actions?query=workflow%3ACI) View and edit BPMN 2.0 diagrams in the browser. [![bpmn-js screencast](./resources/screencast.gif "bpmn-js in action")](http://demo.bpmn.io/s/start) ## Installation Use the library [pre-packaged](https://github.com/bpmn-io/bpmn-js-examples/tree/main/pre-packaged) or include it [via npm](https://github.com/bpmn-io/bpmn-js-examples/tree/main/bundling) into your node-style web-application. ## Usage To get started, create a [bpmn-js](https://github.com/bpmn-io/bpmn-js) instance and render [BPMN 2.0 diagrams](https://www.omg.org/spec/BPMN/2.0.2/) in the browser: ```javascript const xml = '...'; // my BPMN 2.0 xml const viewer = new BpmnJS({ container: 'body' }); try { const { warnings } = await viewer.importXML(xml); console.log('rendered'); } catch (err) { console.log('error rendering', err); } ``` Checkout our [examples](https://github.com/bpmn-io/bpmn-js-examples) for many more supported usage scenarios. ## Resources * [Demo](http://demo.bpmn.io) * [Issues](https://github.com/bpmn-io/bpmn-js/issues) * [Examples](https://github.com/bpmn-io/bpmn-js-examples) * [Forum](https://forum.bpmn.io) * [Changelog](./CHANGELOG.md) ## Build and Run Prepare the project by installing all dependencies: ```sh npm install ``` Then, depending on your use-case you may run any of the following commands: ```sh # build the library and run all tests npm run all # spin up a single local modeler instance npm start # run the full development setup npm run dev ``` You may need to perform [additional project setup](./docs/project/SETUP.md) when building the latest development snapshot. ## Related bpmn-js builds on top of a few powerful tools: * [bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle): Read / write support for BPMN 2.0 XML in the browsers * [diagram-js](https://github.com/bpmn-io/diagram-js): Diagram rendering and editing toolkit It is an extensible toolkit, complemented by many [additional utilities](https://github.com/bpmn-io/awesome-bpmn-io). ## License Use under the terms of the [bpmn.io license](http://bpmn.io/license). ================================================ FILE: assets/bpmn-js.css ================================================ .bjs-container { --bjs-font-family: Arial, sans-serif; --color-grey-225-10-15: hsl(225, 10%, 15%); --color-grey-225-10-35: hsl(225, 10%, 35%); --color-grey-225-10-55: hsl(225, 10%, 55%); --color-grey-225-10-75: hsl(225, 10%, 75%); --color-grey-225-10-80: hsl(225, 10%, 80%); --color-grey-225-10-85: hsl(225, 10%, 85%); --color-grey-225-10-90: hsl(225, 10%, 90%); --color-grey-225-10-95: hsl(225, 10%, 95%); --color-grey-225-10-97: hsl(225, 10%, 97%); --color-blue-205-100-45: hsl(205, 100%, 45%); --color-blue-205-100-45-opacity-30: hsla(205, 100%, 45%, 30%); --color-blue-205-100-50: hsl(205, 100%, 50%); --color-blue-205-100-95: hsl(205, 100%, 95%); --color-green-150-86-44: hsl(150, 86%, 44%); --color-red-360-100-40: hsl(360, 100%, 40%); --color-red-360-100-45: hsl(360, 100%, 45%); --color-red-360-100-92: hsl(360, 100%, 92%); --color-red-360-100-97: hsl(360, 100%, 97%); --color-white: hsl(0, 0%, 100%); --color-black: hsl(0, 0%, 0%); --color-black-opacity-05: hsla(0, 0%, 0%, 5%); --color-black-opacity-10: hsla(0, 0%, 0%, 10%); --breadcrumbs-font-family: var(--bjs-font-family); --breadcrumbs-item-color: var(--color-blue-205-100-50); --breadcrumbs-arrow-color: var(--color-black); --drilldown-fill-color: var(--color-white); --drilldown-background-color: var(--color-blue-205-100-50); } .bjs-breadcrumbs { position: absolute; display: none; flex-wrap: wrap; align-items: center; top: 30px; left: 30px; padding: 0px; margin: 0px; font-family: var(--breadcrumbs-font-family); font-size: 16px; line-height: normal; } .bjs-breadcrumbs-shown .bjs-breadcrumbs { display: flex; } .djs-palette-shown .bjs-breadcrumbs { left: 90px; } .djs-palette-shown.djs-palette-two-column .bjs-breadcrumbs { left: 140px; } .bjs-breadcrumbs li { display: inline-flex; padding-bottom: 5px; align-items: center; } .bjs-breadcrumbs li a { cursor: pointer; color: var(--breadcrumbs-item-color); } .bjs-breadcrumbs li:last-of-type a { color: inherit; cursor: default; } .bjs-breadcrumbs li:not(:first-child)::before { content: url('data:image/svg+xml;utf8,'); padding: 0 8px; color: var(--breadcrumbs-arrow-color); height: 1em; } .bjs-breadcrumbs .bjs-crumb { display: inline-block; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .bjs-drilldown { width: 20px; height: 20px; padding: 0px; margin-left: -20px; cursor: pointer; border: none; border-radius: 2px; outline: none; fill: var(--drilldown-fill-color); background-color: var(--drilldown-background-color); } .bjs-drilldown-empty { display: none; } .selected .bjs-drilldown-empty { display: inherit; } [data-popup="align-elements"] .djs-popup-results { display: flex; } [data-popup="align-elements"] .djs-popup-body [data-group] + [data-group] { border-left: 1px solid var(--popup-border-color); } [data-popup="align-elements"] [data-group="align"] { display: grid; grid-template-columns: repeat(3, 1fr); } [data-popup="align-elements"] .djs-popup-body .entry { padding: 6px 8px; } [data-popup="align-elements"] .djs-popup-body .entry:not(:first-child) { margin-top: 0; } [data-popup="align-elements"] .djs-popup-entry-icon { display: block; margin: 0; height: 20px; width: 20px; } ================================================ FILE: docs/project/SETUP.md ================================================ # Project Setup This document describes the necessary steps to setup a `bpmn-js` development environment. ## TLDR; On Linux, OS X or Windows? [git](http://git-scm.com), [NodeJS](https://nodejs.org) and [npm](https://www.npmjs.org/doc/cli/npm.html) ready? Check out the [setup script section](#setup-via-script) below. ## Manual Steps Make sure you have [git](http://git-scm.com), [NodeJS](https://nodejs.org) and [npm](https://www.npmjs.org/doc/cli/npm.html) installed before you continue. ### Get Project + Dependencies The following projects from the [bpmn-io](https://github.com/bpmn-io) project on GitHub * [bpmn-js](https://github.com/bpmn-io/bpmn-js) * [diagram-js](https://github.com/bpmn-io/diagram-js) * [bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle) and clone them into a common directory via ```sh git clone git@github.com:bpmn-io/bpmn-js.git git clone git@github.com:bpmn-io/diagram-js.git git clone git@github.com:bpmn-io/bpmn-moddle.git ``` ### Link Projects [Link dependent projects](https://docs.npmjs.com/cli/link) between each other to pick up changes immediately. ```plain . ├─bpmn-js │ └─node_modules │ ├─diagram-js │ └─bpmn-moddle ├─diagram-js ├─bpmn-moddle ``` #### On OS X, Linux Use [npm-link](https://docs.npmjs.com/cli/link) or `ln -s `. #### On Windows Use `mklink /d ` [(docs)](http://technet.microsoft.com/en-us/library/cc753194.aspx). ### Install Dependencies Execute `npm install` on each of the projects to grab their dependencies. ### Verify Things are O.K. Execute `npm run all` on each project. Things should be fine. ### Setup via Script The whole setup can be automated through setup scripts for [Linux/OS X](./setup.sh) and [Windows](./setup.bat). ================================================ FILE: docs/project/setup-alternative.sh ================================================ #!/bin/bash ### # Setup script to be executed in a bpmn.io project root (some empty folder chosen by YOU). Use if you do not want to rely on npm link. ### base=`pwd` echo cloning repositories git clone git@github.com:bpmn-io/diagram-js.git git clone git@github.com:bpmn-io/bpmn-js.git git clone git@github.com:bpmn-io/bpmn-moddle.git echo done. echo setup diagram-js cd $base/diagram-js npm install echo setup bpmn-moddle cd $base/bpmn-moddle npm install echo setup bpmn-js cd $base/bpmn-js mkdir node_modules ln -s $base/bpmn-moddle node_modules/bpmn-moddle ln -s $base/diagram-js node_modules/diagram-js npm install cd $base echo all done. ================================================ FILE: docs/project/setup.bat ================================================ @echo off rem ### rem # Setup script to be executed in a bpmn.io project root (some empty folder chosen by YOU) rem ## set BASE=%CD% echo cloning repositories git clone git@github.com:bpmn-io/diagram-js.git git clone git@github.com:bpmn-io/bpmn-js.git git clone git@github.com:bpmn-io/bpmn-moddle.git echo done. echo setup diagram-js cd %BASE%\diagram-js npm install echo setup bpmn-moddle cd %BASE%\bpmn-moddle npm install echo prepare setup bpmn-js mkdir %BASE%\bpmn-js\node_modules rem link bpmn-js mklink /D %BASE%\bpmn-js\node_modules\bpmn-moddle %BASE%\bpmn-moddle mklink /D %BASE%\bpmn-js\node_modules\diagram-js %BASE%\diagram-js echo setup bpmn-js cd %BASE%\bpmn-js npm install cd %BASE% ================================================ FILE: docs/project/setup.sh ================================================ #!/bin/bash ### # Setup script to be executed in a bpmn.io project root (some empty folder chosen by YOU) ### base=`pwd` echo cloning repositories git clone git@github.com:bpmn-io/diagram-js.git git clone git@github.com:bpmn-io/bpmn-js.git git clone git@github.com:bpmn-io/bpmn-moddle.git echo done. echo setup diagram-js cd $base/diagram-js npm install echo setup bpmn-moddle cd $base/bpmn-moddle npm install echo setup bpmn-js cd $base/bpmn-js npm install npm link ../diagram-js npm link ../bpmn-moddle cd $base echo all done. ================================================ FILE: docs/translations.json ================================================ [ "Activate create/remove space tool", "Activate global connect tool", "Activate hand tool", "Activate lasso tool", "Ad-hoc sub-process", "Ad-hoc sub-process (collapsed)", "Ad-hoc sub-process (expanded)", "Add lane above", "Add lane below", "Add text annotation", "Align elements", "Align elements bottom", "Align elements center", "Align elements left", "Align elements middle", "Align elements right", "Align elements top", "Append compensation activity", "Append conditional intermediate catch event", "Append end event", "Append gateway", "Append intermediate/boundary event", "Append message intermediate catch event", "Append receive task", "Append signal intermediate catch event", "Append task", "Append timer intermediate catch event", "Business rule task", "Call activity", "Cancel boundary event", "Cancel end event", "Change element", "Collection", "Compensation boundary event", "Compensation end event", "Compensation intermediate throw event", "Compensation start event", "Complex gateway", "Conditional boundary event", "Conditional boundary event (non-interrupting)", "Conditional flow", "Conditional intermediate catch event", "Conditional start event", "Conditional start event (non-interrupting)", "Connect to other element", "Connect using association", "Connect using data input association", "Create data object reference", "Create data store reference", "Create end event", "Create expanded sub-process", "Create gateway", "Create group", "Create intermediate/boundary event", "Create pool/participant", "Create start event", "Create task", "Data object must be placed within a pool/participant.", "Data object reference", "Data store reference", "Default flow", "Delete", "Distribute elements horizontally", "Distribute elements vertically", "Divide into three lanes", "Divide into two lanes", "Empty pool/participant", "Empty pool/participant (removes content)", "End event", "Error boundary event", "Error end event", "Error start event", "Escalation boundary event", "Escalation boundary event (non-interrupting)", "Escalation end event", "Escalation intermediate throw event", "Escalation start event", "Escalation start event (non-interrupting)", "Event sub-process", "Event-based gateway", "Exclusive gateway", "Inclusive gateway", "Intermediate throw event", "Link intermediate catch event", "Link intermediate throw event", "Loop", "Manual task", "Message boundary event", "Message boundary event (non-interrupting)", "Message end event", "Message intermediate catch event", "Message intermediate throw event", "Message start event", "Message start event (non-interrupting)", "Open {element}", "Parallel gateway", "Parallel multi-instance", "Participant multiplicity", "Receive task", "Script task", "Search in diagram", "Send task", "Sequence flow", "Sequential multi-instance", "Service task", "Signal boundary event", "Signal boundary event (non-interrupting)", "Signal end event", "Signal intermediate catch event", "Signal intermediate throw event", "Signal start event", "Signal start event (non-interrupting)", "Start event", "Sub-process", "Sub-process (collapsed)", "Sub-process (expanded)", "Task", "Terminate end event", "Timer boundary event", "Timer boundary event (non-interrupting)", "Timer intermediate catch event", "Timer start event", "Timer start event (non-interrupting)", "Toggle non-interrupting", "Transaction", "User task", "flow elements must be children of pools/participants" ] ================================================ FILE: eslint.config.mjs ================================================ import bpmnIoPlugin from 'eslint-plugin-bpmn-io'; const files = { ignored: [ 'dist', 'coverage' ], build: [ 'test/config/*.js', 'tasks/**/*.mjs', '*.js', '*.mjs' ], test: [ 'test/**/*.js' ] }; export default [ { ignores: files.ignored }, // build ...bpmnIoPlugin.configs.node.map(config => { return { ...config, files: files.build }; }), // lib + test ...bpmnIoPlugin.configs.browser.map(config => { return { ...config, ignores: files.build }; }), // test ...bpmnIoPlugin.configs.mocha.map(config => { return { ...config, files: files.test, }; }), { languageOptions: { globals: { sinon: true, require: true } }, files: files.test } ]; ================================================ FILE: lib/BaseModeler.js ================================================ import inherits from 'inherits-browser'; import { Ids } from 'ids'; import BaseViewer from './BaseViewer'; /** * @typedef {import('./BaseViewer').BaseViewerOptions} BaseViewerOptions * @typedef {import('./BaseViewer').ModdleElementsById} ModdleElementsById * * @typedef {import('./model/Types').ModdleElement} ModdleElement */ /** * A base modeler for BPMN 2.0 diagrams. * * See {@link bpmn-js/lib/Modeler} for a fully-featured modeler. * * @template [ServiceMap=null] * * @extends BaseViewer * * @param {BaseViewerOptions} [options] The options to configure the modeler. */ export default function BaseModeler(options) { BaseViewer.call(this, options); // hook ID collection into the modeler this.on('import.parse.complete', function(event) { if (!event.error) { this._collectIds(event.definitions, event.elementsById); } }, this); this.on('diagram.destroy', function() { this.get('moddle').ids.clear(); }, this); } inherits(BaseModeler, BaseViewer); /** * Create a moddle instance, attaching IDs to it. * * @param {BaseViewerOptions} options * * @return {Moddle} */ BaseModeler.prototype._createModdle = function(options) { var moddle = BaseViewer.prototype._createModdle.call(this, options); // attach ids to moddle to be able to track and validated ids in the BPMN 2.0 // XML document tree moddle.ids = new Ids([ 32, 36, 1 ]); return moddle; }; /** * Collect IDs processed during parsing of the definitions object. * * @param {ModdleElement} definitions * @param {ModdleElementsById} elementsById */ BaseModeler.prototype._collectIds = function(definitions, elementsById) { var moddle = definitions.$model, ids = moddle.ids, id; // remove references from previous import ids.clear(); for (id in elementsById) { ids.claim(id, elementsById[ id ]); } }; ================================================ FILE: lib/BaseModeler.spec.ts ================================================ import Canvas from 'diagram-js/lib/core/Canvas'; import EventBus from 'diagram-js/lib/core/EventBus'; import BaseModeler from './BaseModeler'; import { testViewer } from './BaseViewer.spec'; const modeler = new BaseModeler({ container: 'container' }); testViewer(modeler); const otherModeler = new BaseModeler({ container: 'container' }); const extendedModeler = new BaseModeler({ container: 'container', alignToOrigin: false, propertiesPanel: { attachTo: '#properties-panel' } }); // typed API usage type FooEvent = { /** * Very cool field! */ foo: string; }; type EventMap = { foo: FooEvent }; type TypeMap = { canvas: Canvas, eventBus: EventBus }; const typedModeler = new BaseModeler(); const bus = typedModeler.get('eventBus'); const canvas = typedModeler.get('canvas'); canvas.zoom('fit-viewport'); typedModeler.on('foo', event => { console.log(event.foo); }); typedModeler.get('eventBus').on('foo', e => console.log(e.foo)); ================================================ FILE: lib/BaseViewer.js ================================================ /** * The code in the area * must not be changed. * * @see http://bpmn.io/license for more information. */ import { assign, find, isNumber, omit } from 'min-dash'; import { domify, assignStyle, query as domQuery, remove as domRemove } from 'min-dom'; import { innerSVG } from 'tiny-svg'; import Diagram from 'diagram-js'; import { BpmnModdle } from 'bpmn-moddle'; import inherits from 'inherits-browser'; import { importBpmnDiagram } from './import/Importer'; /** * @template T * * @typedef { import('diagram-js/lib/core/EventBus').default } EventBus */ /** * @template T * * @typedef {import('diagram-js/lib/core/EventBus').EventBusEventCallback} EventBusEventCallback */ /** * @typedef {import('didi').ModuleDeclaration} ModuleDeclaration * * @typedef {import('./model/Types').Moddle} Moddle * @typedef {import('./model/Types').ModdleElement} ModdleElement * @typedef {import('./model/Types').ModdleExtension} ModdleExtension * * @typedef { { * width?: number|string; * height?: number|string; * position?: string; * container?: string|HTMLElement; * moddleExtensions?: ModdleExtensions; * additionalModules?: ModuleDeclaration[]; * } & Record } BaseViewerOptions * * @typedef {Record} ModdleElementsById * * @typedef { { * [key: string]: ModdleExtension; * } } ModdleExtensions * * @typedef { { * warnings: string[]; * } } ImportXMLResult * * @typedef {ImportXMLResult & Error} ImportXMLError * * @typedef {ImportXMLResult} ImportDefinitionsResult * * @typedef {ImportXMLError} ImportDefinitionsError * * @typedef {ImportXMLResult} OpenResult * * @typedef {ImportXMLError} OpenError * * @typedef { { * format?: boolean; * preamble?: boolean; * } } SaveXMLOptions * * @typedef { { * xml?: string; * error?: Error; * } } SaveXMLResult * * @typedef { { * svg: string; * } } SaveSVGResult * * @typedef { { * xml: string; * } } ImportParseStartEvent * * @typedef { { * error?: ImportXMLError; * definitions?: ModdleElement; * elementsById?: ModdleElementsById; * references?: ModdleElement[]; * warnings: string[]; * } } ImportParseCompleteEvent * * @typedef { { * error?: ImportXMLError; * warnings: string[]; * } } ImportDoneEvent * * @typedef { { * definitions: ModdleElement; * } } SaveXMLStartEvent * * @typedef {SaveXMLResult} SaveXMLDoneEvent * * @typedef { { * error?: Error; * svg: string; * } } SaveSVGDoneEvent */ /** * @template Type * * @typedef { Type extends { eventBus: EventBus } ? X : never } EventMap */ /** * A base viewer for BPMN 2.0 diagrams. * * Have a look at {@link bpmn-js/lib/Viewer}, {@link bpmn-js/lib/NavigatedViewer} or {@link bpmn-js/lib/Modeler} for * bundles that include actual features. * * @template [ServiceMap=null] * * @extends Diagram * * @param {BaseViewerOptions} [options] The options to configure the viewer. */ export default function BaseViewer(options) { /** * @type {BaseViewerOptions} */ options = assign({}, DEFAULT_OPTIONS, options); /** * @type {Moddle} */ this._moddle = this._createModdle(options); /** * @type {HTMLElement} */ this._container = this._createContainer(options); this._init(this._container, this._moddle, options); /* */ addProjectLogo(this._container); /* */ } inherits(BaseViewer, Diagram); /** * Parse and render a BPMN 2.0 diagram. * * Once finished the viewer reports back the result to the * provided callback function with (err, warnings). * * ## Life-Cycle Events * * During import the viewer will fire life-cycle events: * * * import.parse.start (about to read model from XML) * * import.parse.complete (model read; may have worked or not) * * import.render.start (graphical import start) * * import.render.complete (graphical import finished) * * import.done (everything done) * * You can use these events to hook into the life-cycle. * * @throws {ImportXMLError} An error thrown during the import of the XML. * * @fires BaseViewer#ImportParseStartEvent * @fires BaseViewer#ImportParseCompleteEvent * @fires Importer#ImportRenderStartEvent * @fires Importer#ImportRenderCompleteEvent * @fires BaseViewer#ImportDoneEvent * * @param {string} xml The BPMN 2.0 XML to be imported. * @param {ModdleElement|string} [bpmnDiagram] The optional diagram or Id of the BPMN diagram to open. * * @return {Promise} A promise resolving with warnings that were produced during the import. */ BaseViewer.prototype.importXML = async function importXML(xml, bpmnDiagram) { const self = this; function ParseCompleteEvent(data) { return self.get('eventBus').createEvent(data); } let aggregatedWarnings = []; try { // hook in pre-parse listeners + // allow xml manipulation /** * A `import.parse.start` event. * * @event BaseViewer#ImportParseStartEvent * @type {ImportParseStartEvent} */ xml = this._emit('import.parse.start', { xml: xml }) || xml; let parseResult; try { parseResult = await this._moddle.fromXML(xml, 'bpmn:Definitions'); } catch (error) { this._emit('import.parse.complete', { error }); throw error; } let definitions = parseResult.rootElement; const references = parseResult.references; const parseWarnings = parseResult.warnings; const elementsById = parseResult.elementsById; aggregatedWarnings = aggregatedWarnings.concat(parseWarnings); // hook in post parse listeners + // allow definitions manipulation /** * A `import.parse.complete` event. * * @event BaseViewer#ImportParseCompleteEvent * @type {ImportParseCompleteEvent} */ definitions = this._emit('import.parse.complete', ParseCompleteEvent({ error: null, definitions: definitions, elementsById: elementsById, references: references, warnings: aggregatedWarnings })) || definitions; const importResult = await this.importDefinitions(definitions, bpmnDiagram); aggregatedWarnings = aggregatedWarnings.concat(importResult.warnings); /** * A `import.parse.complete` event. * * @event BaseViewer#ImportDoneEvent * @type {ImportDoneEvent} */ this._emit('import.done', { error: null, warnings: aggregatedWarnings }); return { warnings: aggregatedWarnings }; } catch (err) { let error = err; aggregatedWarnings = aggregatedWarnings.concat(error.warnings || []); addWarningsToError(error, aggregatedWarnings); error = checkValidationError(error); this._emit('import.done', { error, warnings: error.warnings }); throw error; } }; /** * Import parsed definitions and render a BPMN 2.0 diagram. * * Once finished the viewer reports back the result to the * provided callback function with (err, warnings). * * ## Life-Cycle Events * * During import the viewer will fire life-cycle events: * * * import.render.start (graphical import start) * * import.render.complete (graphical import finished) * * You can use these events to hook into the life-cycle. * * @throws {ImportDefinitionsError} An error thrown during the import of the definitions. * * @param {ModdleElement} definitions The definitions. * @param {ModdleElement|string} [bpmnDiagram] The optional diagram or ID of the BPMN diagram to open. * * @return {Promise} A promise resolving with warnings that were produced during the import. */ BaseViewer.prototype.importDefinitions = async function importDefinitions(definitions, bpmnDiagram) { this._setDefinitions(definitions); const result = await this.open(bpmnDiagram); return { warnings: result.warnings }; }; /** * Open diagram of previously imported XML. * * Once finished the viewer reports back the result to the * provided callback function with (err, warnings). * * ## Life-Cycle Events * * During switch the viewer will fire life-cycle events: * * * import.render.start (graphical import start) * * import.render.complete (graphical import finished) * * You can use these events to hook into the life-cycle. * * @throws {OpenError} An error thrown during opening. * * @param {ModdleElement|string} bpmnDiagramOrId The diagram or Id of the BPMN diagram to open. * * @return {Promise} A promise resolving with warnings that were produced during opening. */ BaseViewer.prototype.open = async function open(bpmnDiagramOrId) { const definitions = this._definitions; let bpmnDiagram = bpmnDiagramOrId; if (!definitions) { const error = new Error('no XML imported'); addWarningsToError(error, []); throw error; } if (typeof bpmnDiagramOrId === 'string') { bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId); if (!bpmnDiagram) { const error = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found'); addWarningsToError(error, []); throw error; } } // clear existing rendered diagram // catch synchronous exceptions during #clear() try { this.clear(); } catch (error) { addWarningsToError(error, []); throw error; } // perform graphical import const { warnings } = await importBpmnDiagram(this, definitions, bpmnDiagram); return { warnings }; }; /** * Export the currently displayed BPMN 2.0 diagram as * a BPMN 2.0 XML document. * * ## Life-Cycle Events * * During XML saving the viewer will fire life-cycle events: * * * saveXML.start (before serialization) * * saveXML.serialized (after xml generation) * * saveXML.done (everything done) * * You can use these events to hook into the life-cycle. * * @throws {Error} An error thrown during export. * * @fires BaseViewer#SaveXMLStart * @fires BaseViewer#SaveXMLDone * * @param {SaveXMLOptions} [options] The options. * * @return {Promise} A promise resolving with the XML. */ BaseViewer.prototype.saveXML = async function saveXML(options) { options = options || {}; let definitions = this._definitions, error, xml; try { if (!definitions) { throw new Error('no definitions loaded'); } // allow to fiddle around with definitions /** * A `saveXML.start` event. * * @event BaseViewer#SaveXMLStartEvent * @type {SaveXMLStartEvent} */ definitions = this._emit('saveXML.start', { definitions }) || definitions; const result = await this._moddle.toXML(definitions, options); xml = result.xml; xml = this._emit('saveXML.serialized', { xml }) || xml; } catch (err) { error = err; } const result = error ? { error } : { xml }; /** * A `saveXML.done` event. * * @event BaseViewer#SaveXMLDoneEvent * @type {SaveXMLDoneEvent} */ this._emit('saveXML.done', result); if (error) { throw error; } return result; }; /** * Export the currently displayed BPMN 2.0 diagram as * an SVG image. * * ## Life-Cycle Events * * During SVG saving the viewer will fire life-cycle events: * * * saveSVG.start (before serialization) * * saveSVG.done (everything done) * * You can use these events to hook into the life-cycle. * * @throws {Error} An error thrown during export. * * @fires BaseViewer#SaveSVGDone * * @return {Promise} A promise resolving with the SVG. */ BaseViewer.prototype.saveSVG = async function saveSVG() { this._emit('saveSVG.start'); let svg, err; try { const canvas = this.get('canvas'); const contentNode = canvas.getActiveLayer(), defsNode = domQuery(':scope > defs', canvas._svg); const contents = innerSVG(contentNode), defs = defsNode ? '' + innerSVG(defsNode) + '' : ''; const bbox = contentNode.getBBox(); svg = '\n' + '\n' + '\n' + '' + defs + contents + ''; } catch (e) { err = e; } /** * A `saveSVG.done` event. * * @event BaseViewer#SaveSVGDoneEvent * @type {SaveSVGDoneEvent} */ this._emit('saveSVG.done', { error: err, svg: svg }); if (err) { throw err; } return { svg }; }; BaseViewer.prototype._setDefinitions = function(definitions) { this._definitions = definitions; }; /** * Return modules to instantiate with. * * @return {ModuleDeclaration[]} The modules. */ BaseViewer.prototype.getModules = function() { return this._modules; }; /** * Remove all drawn elements from the viewer. * * After calling this method the viewer can still be reused for opening another * diagram. */ BaseViewer.prototype.clear = function() { if (!this.getDefinitions()) { // no diagram to clear return; } // remove drawn elements Diagram.prototype.clear.call(this); }; /** * Destroy the viewer instance and remove all its remainders from the document * tree. */ BaseViewer.prototype.destroy = function() { // diagram destroy Diagram.prototype.destroy.call(this); // dom detach domRemove(this._container); }; /** * @overlord * * Register an event listener for events with the given name. * * The callback will be invoked with `event, ...additionalArguments` * that have been passed to {@link EventBus#fire}. * * Returning false from a listener will prevent the events default action * (if any is specified). To stop an event from being processed further in * other listeners execute {@link Event#stopPropagation}. * * Returning anything but `undefined` from a listener will stop the listener propagation. * * @template T * * @param {string|string[]} events The event(s) to listen to. * @param {number} [priority] The priority with which to listen. * @param {EventBusEventCallback} callback The callback. * @param {any} [that] Value of `this` the callback will be called with. */ /** * Register an event listener for events with the given name. * * The callback will be invoked with `event, ...additionalArguments` * that have been passed to {@link EventBus#fire}. * * Returning false from a listener will prevent the events default action * (if any is specified). To stop an event from being processed further in * other listeners execute {@link Event#stopPropagation}. * * Returning anything but `undefined` from a listener will stop the listener propagation. * * @template {keyof EventMap} EventName * * @param {EventName} events to subscribe to * @param {number} [priority=1000] listen priority * @param {EventBusEventCallback<(EventMap)[EventName]>} callback * @param {any} [that] callback context */ BaseViewer.prototype.on = function(events, priority, callback, that) { return this.get('eventBus').on(events, priority, callback, that); }; /** * Remove an event listener. * * @param {string|string[]} events The event(s). * @param {Function} [callback] The callback. */ BaseViewer.prototype.off = function(events, callback) { this.get('eventBus').off(events, callback); }; /** * Attach the viewer to an HTML element. * * @param {HTMLElement} parentNode The parent node to attach to. */ BaseViewer.prototype.attachTo = function(parentNode) { if (!parentNode) { throw new Error('parentNode required'); } // ensure we detach from the // previous, old parent this.detach(); // unwrap jQuery if provided if (parentNode.get && parentNode.constructor.prototype.jquery) { parentNode = parentNode.get(0); } if (typeof parentNode === 'string') { parentNode = domQuery(parentNode); } parentNode.appendChild(this._container); this._emit('attach', {}); this.get('canvas').resized(); }; /** * Get the definitions model element. * * @return {ModdleElement} The definitions model element. */ BaseViewer.prototype.getDefinitions = function() { return this._definitions; }; /** * Detach the viewer. * * @fires BaseViewer#DetachEvent */ BaseViewer.prototype.detach = function() { const container = this._container, parentNode = container.parentNode; if (!parentNode) { return; } /** * A `detach` event. * * @event BaseViewer#DetachEvent * @type {Object} */ this._emit('detach', {}); parentNode.removeChild(container); }; BaseViewer.prototype._init = function(container, moddle, options) { const baseModules = options.modules || this.getModules(options), additionalModules = options.additionalModules || [], staticModules = [ { bpmnjs: [ 'value', this ], moddle: [ 'value', moddle ] } ]; const diagramModules = [].concat(staticModules, baseModules, additionalModules); const diagramOptions = assign(omit(options, [ 'additionalModules' ]), { canvas: assign({}, options.canvas, { container: container }), modules: diagramModules }); // invoke diagram constructor Diagram.call(this, diagramOptions); if (options && options.container) { this.attachTo(options.container); } }; /** * Emit an event on the underlying {@link EventBus} * * @param {string} type * @param {Object} event * * @return {Object} The return value after calling all event listeners. */ BaseViewer.prototype._emit = function(type, event) { return this.get('eventBus').fire(type, event); }; /** * @param {BaseViewerOptions} options * * @return {HTMLElement} */ BaseViewer.prototype._createContainer = function(options) { const container = domify('
'); assignStyle(container, { width: ensureUnit(options.width), height: ensureUnit(options.height), position: options.position }); return container; }; /** * @param {BaseViewerOptions} options * * @return {Moddle} */ BaseViewer.prototype._createModdle = function(options) { const moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions); return new BpmnModdle(moddleOptions); }; BaseViewer.prototype._modules = []; // helpers /////////////// function addWarningsToError(err, warningsAry) { err.warnings = warningsAry; return err; } function checkValidationError(err) { // check if we can help the user by indicating wrong BPMN 2.0 xml // (in case he or the exporting tool did not get that right) const pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/; const match = pattern.exec(err.message); if (match) { err.message = 'unparsable content <' + match[1] + '> detected; ' + 'this may indicate an invalid BPMN 2.0 diagram file' + match[2]; } return err; } const DEFAULT_OPTIONS = { width: '100%', height: '100%', position: 'relative' }; /** * Ensure the passed argument is a proper unit (defaulting to px) */ function ensureUnit(val) { return val + (isNumber(val) ? 'px' : ''); } /** * Find BPMNDiagram in definitions by ID * * @param {ModdleElement} definitions * @param {string} diagramId * * @return {ModdleElement|null} */ function findBPMNDiagram(definitions, diagramId) { if (!diagramId) { return null; } return find(definitions.diagrams, function(element) { return element.id === diagramId; }) || null; } /* */ import { open as openPoweredBy, BPMNIO_IMG, LOGO_STYLES, LINK_STYLES } from './util/PoweredByUtil'; import { event as domEvent } from 'min-dom'; /** * Adds the project logo to the diagram container as * required by the bpmn.io license. * * @see http://bpmn.io/license * * @param {Element} container */ function addProjectLogo(container) { const img = BPMNIO_IMG; const linkMarkup = '' + img + ''; const linkElement = domify(linkMarkup); assignStyle(domQuery('svg', linkElement), LOGO_STYLES); assignStyle(linkElement, LINK_STYLES, { position: 'absolute', bottom: '15px', right: '15px', zIndex: '100' }); container.appendChild(linkElement); domEvent.bind(linkElement, 'click', function(event) { openPoweredBy(); event.preventDefault(); }); } /* */ ================================================ FILE: lib/BaseViewer.spec.ts ================================================ import CommandStack from 'diagram-js/lib/command/CommandStack'; import EventBus, { Event } from 'diagram-js/lib/core/EventBus'; import BaseViewer, { ImportDoneEvent, ImportParseCompleteEvent, ImportParseStartEvent, SaveXMLDoneEvent, SaveXMLStartEvent } from './BaseViewer'; import OverlaysModule from 'diagram-js/lib/features/overlays'; import Canvas from 'diagram-js/lib/core/Canvas'; const viewer = new BaseViewer(); const configuredViewer = new BaseViewer({ width: 100, height: 100, position: 'absolute', container: 'container', moddleExtensions: { foo: {} }, additionalModules: [ OverlaysModule ] }); testViewer(viewer); const extendedViewer = new BaseViewer({ container: 'container', alignToOrigin: false, propertiesPanel: { attachTo: '#properties-panel' } }); export function testViewer(viewer: BaseViewer) { viewer.importXML('', 'BPMNDiagram_1'); viewer.importXML('') .then(({ warnings }) => { console.log(warnings); }) .catch(error => { const { message, warnings } = error; console.log(message, warnings); }); viewer.importDefinitions({ $type: 'bpmn:Definitions' }, 'BPMNDiagram_1'); viewer.importDefinitions({ $type: 'bpmn:Definitions' }) .then(({ warnings }) => { console.log(warnings); }) .catch(error => { const { message, warnings } = error; console.log(message, warnings); }); viewer.open('BPMNDiagram_1'); viewer.open({ $type: 'bpmn:BPMNDiagram' }) .then(({ warnings }) => { console.log(warnings); }) .catch(error => { const { message, warnings } = error; console.log(message, warnings); }); viewer.saveXML({ format: true, preamble: false }) .then(({ xml, error }) => { if (error) { console.log(error); } else { console.log(xml); } }) .catch(error => { console.log(error); }); viewer.saveXML(); viewer.saveSVG(); viewer.getModules(); viewer.clear(); viewer.destroy(); viewer.get('commandStack').undo(); viewer.invoke((commandStack: CommandStack) => commandStack.undo()); viewer.on('foo', () => console.log('foo')); viewer.on([ 'foo', 'bar' ], () => console.log('foo')); viewer.on('foo', 2000, () => console.log('foo')); viewer.on('foo', 2000, () => console.log('foo'), { foo: 'bar' }); viewer.off('foo', () => console.log('foo')); viewer.attachTo(document.createElement('div')); viewer.getDefinitions(); viewer.detach(); viewer.on('import.parse.start', ({ xml }) => { console.log(xml); }); viewer.on('import.parse.complete', ({ error, definitions, elementsById, references, warnings }) => { if (error) { console.error(error); } if (warnings.length) { warnings.forEach(warning => console.log(warning)); } console.log(definitions, elementsById, references); }); viewer.on('import.done', ({ error, warnings }) => { if (error) { console.error(error); } if (warnings.length) { warnings.forEach(warning => console.log(warning)); } }); viewer.on('saveXML.start', ({ definitions }) => { console.log(definitions); }); viewer.on('saveXML.done', ({ error, xml }) => { if (error) { console.error(error); } else { console.log(xml); } }); viewer.on('detach', () => {}); } // typed API usage type FooEvent = { /** * Very cool field! */ foo: string; }; type EventMap = { foo: FooEvent }; type TypeMap = { canvas: Canvas, eventBus: EventBus }; const typedViewer = new BaseViewer(); const bus = typedViewer.get('eventBus'); const canvas = typedViewer.get('canvas'); canvas.zoom('fit-viewport'); typedViewer.on('foo', event => { console.log(event.foo); }); typedViewer.get('eventBus').on('foo', e => console.log(e.foo)); ================================================ FILE: lib/Modeler.js ================================================ import inherits from 'inherits-browser'; import BaseModeler from './BaseModeler'; import Viewer from './Viewer'; import NavigatedViewer from './NavigatedViewer'; import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move'; import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'; import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll'; import AlignElementsModule from './features/align-elements'; import AutoPlaceModule from './features/auto-place'; import AutoResizeModule from './features/auto-resize'; import AutoScrollModule from 'diagram-js/lib/features/auto-scroll'; import BendpointsModule from 'diagram-js/lib/features/bendpoints'; import ConnectModule from 'diagram-js/lib/features/connect'; import ConnectionPreviewModule from 'diagram-js/lib/features/connection-preview'; import ContextPadModule from './features/context-pad'; import CopyPasteModule from './features/copy-paste'; import CreateModule from 'diagram-js/lib/features/create'; import DistributeElementsModule from './features/distribute-elements'; import EditorActionsModule from './features/editor-actions'; import GridSnappingModule from './features/grid-snapping'; import InteractionEventsModule from './features/interaction-events'; import KeyboardModule from './features/keyboard'; import KeyboardMoveSelectionModule from 'diagram-js/lib/features/keyboard-move-selection'; import LabelEditingModule from './features/label-editing'; import LabelLink from './features/label-link'; import ModelingModule from './features/modeling'; import ModelingFeedbackModule from './features/modeling-feedback'; import MoveModule from 'diagram-js/lib/features/move'; import PaletteModule from './features/palette'; import ReplacePreviewModule from './features/replace-preview'; import ResizeModule from 'diagram-js/lib/features/resize'; import SnappingModule from './features/snapping'; import SearchModule from './features/search'; import OutlineModule from './features/outline'; var initialDiagram = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; /** * @typedef {import('./BaseViewer').BaseViewerOptions} BaseViewerOptions * @typedef {import('./BaseViewer').ImportXMLResult} ImportXMLResult */ /** * A modeler for BPMN 2.0 diagrams. * * ## Extending the Modeler * * In order to extend the viewer pass extension modules to bootstrap via the * `additionalModules` option. An extension module is an object that exposes * named services. * * The following example depicts the integration of a simple * logging component that integrates with interaction events: * * * ```javascript * * // logging component * function InteractionLogger(eventBus) { * eventBus.on('element.hover', function(event) { * console.log() * }) * } * * InteractionLogger.$inject = [ 'eventBus' ]; // minification save * * // extension module * var extensionModule = { * __init__: [ 'interactionLogger' ], * interactionLogger: [ 'type', InteractionLogger ] * }; * * // extend the viewer * var bpmnModeler = new Modeler({ additionalModules: [ extensionModule ] }); * bpmnModeler.importXML(...); * ``` * * * ## Customizing / Replacing Components * * You can replace individual diagram components by redefining them in override modules. * This works for all components, including those defined in the core. * * Pass in override modules via the `options.additionalModules` flag like this: * * ```javascript * function CustomContextPadProvider(contextPad) { * * contextPad.registerProvider(this); * * this.getContextPadEntries = function(element) { * // no entries, effectively disable the context pad * return {}; * }; * } * * CustomContextPadProvider.$inject = [ 'contextPad' ]; * * var overrideModule = { * contextPadProvider: [ 'type', CustomContextPadProvider ] * }; * * var bpmnModeler = new Modeler({ additionalModules: [ overrideModule ]}); * ``` * * @template [ServiceMap=null] * * @extends BaseModeler * * @param {BaseViewerOptions} [options] The options to configure the modeler. */ export default function Modeler(options) { BaseModeler.call(this, options); } inherits(Modeler, BaseModeler); Modeler.Viewer = Viewer; Modeler.NavigatedViewer = NavigatedViewer; /** * Create a new diagram to start modeling. * * @throws {ImportXMLError} An error thrown during the import of the XML. * * @return {Promise} A promise resolving with warnings that were produced during the import. */ Modeler.prototype.createDiagram = function createDiagram() { return this.importXML(initialDiagram); }; Modeler.prototype._interactionModules = [ // non-modeling components KeyboardMoveModule, MoveCanvasModule, ZoomScrollModule ]; Modeler.prototype._modelingModules = [ // modeling components AlignElementsModule, AutoPlaceModule, AutoScrollModule, AutoResizeModule, BendpointsModule, ConnectModule, ConnectionPreviewModule, ContextPadModule, CopyPasteModule, CreateModule, DistributeElementsModule, EditorActionsModule, GridSnappingModule, InteractionEventsModule, KeyboardModule, KeyboardMoveSelectionModule, LabelEditingModule, LabelLink, ModelingModule, ModelingFeedbackModule, MoveModule, PaletteModule, ReplacePreviewModule, ResizeModule, SnappingModule, SearchModule, OutlineModule ]; // modules the modeler is composed of // // - viewer modules // - interaction modules // - modeling modules Modeler.prototype._modules = [].concat( Viewer.prototype._modules, Modeler.prototype._interactionModules, Modeler.prototype._modelingModules ); ================================================ FILE: lib/Modeler.spec.ts ================================================ import Canvas from 'diagram-js/lib/core/Canvas'; import EventBus from 'diagram-js/lib/core/EventBus'; import Modeler from './Modeler'; import { testViewer } from './BaseViewer.spec'; const modeler = new Modeler({ container: 'container' }); testViewer(modeler); modeler.createDiagram(); const otherModeler = new Modeler({ container: 'container' }); const extendedModeler = new Modeler({ container: 'container', alignToOrigin: false, propertiesPanel: { attachTo: '#properties-panel' } }); // typed API usage type FooEvent = { /** * Very cool field! */ foo: string; }; type EventMap = { foo: FooEvent }; type TypeMap = { canvas: Canvas, eventBus: EventBus }; const typedViewer = new Modeler(); const bus = typedViewer.get('eventBus'); const canvas = typedViewer.get('canvas'); canvas.zoom('fit-viewport'); typedViewer.on('foo', event => { console.log(event.foo); }); typedViewer.get('eventBus').on('foo', e => console.log(e.foo)); ================================================ FILE: lib/NavigatedViewer.js ================================================ import inherits from 'inherits-browser'; import Viewer from './Viewer'; import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move'; import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'; import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll'; /** * @typedef { import('./BaseViewer').BaseViewerOptions } BaseViewerOptions */ /** * A viewer with mouse and keyboard navigation features. * * @template [ServiceMap=null] * * @extends Viewer * * @param {BaseViewerOptions} [options] */ export default function NavigatedViewer(options) { Viewer.call(this, options); } inherits(NavigatedViewer, Viewer); NavigatedViewer.prototype._navigationModules = [ KeyboardMoveModule, MoveCanvasModule, ZoomScrollModule ]; NavigatedViewer.prototype._modules = [].concat( Viewer.prototype._modules, NavigatedViewer.prototype._navigationModules ); ================================================ FILE: lib/NavigatedViewer.spec.ts ================================================ import Canvas from 'diagram-js/lib/core/Canvas'; import EventBus from 'diagram-js/lib/core/EventBus'; import NavigatedViewer from './NavigatedViewer'; import { testViewer } from './BaseViewer.spec'; const viewer = new NavigatedViewer({ container: 'container' }); testViewer(viewer); const extendedViewer = new NavigatedViewer({ container: 'container', alignToOrigin: false, propertiesPanel: { attachTo: '#properties-panel' } }); // typed API usage type FooEvent = { /** * Very cool field! */ foo: string; }; type EventMap = { foo: FooEvent }; type TypeMap = { canvas: Canvas, eventBus: EventBus }; const typedViewer = new NavigatedViewer(); const bus = typedViewer.get('eventBus'); const canvas = typedViewer.get('canvas'); canvas.zoom('fit-viewport'); typedViewer.on('foo', event => { console.log(event.foo); }); typedViewer.get('eventBus').on('foo', e => console.log(e.foo)); ================================================ FILE: lib/Viewer.js ================================================ import inherits from 'inherits-browser'; import CoreModule from './core'; import DrilldownModdule from './features/drilldown'; import OverlaysModule from 'diagram-js/lib/features/overlays'; import SelectionModule from 'diagram-js/lib/features/selection'; import TranslateModule from 'diagram-js/lib/i18n/translate'; import BaseViewer from './BaseViewer'; /** * @typedef { import('./BaseViewer').BaseViewerOptions } BaseViewerOptions */ /** * A viewer for BPMN 2.0 diagrams. * * Have a look at {@link bpmn-js/lib/NavigatedViewer} or {@link bpmn-js/lib/Modeler} for bundles that include * additional features. * * * ## Extending the Viewer * * In order to extend the viewer pass extension modules to bootstrap via the * `additionalModules` option. An extension module is an object that exposes * named services. * * The following example depicts the integration of a simple * logging component that integrates with interaction events: * * * ```javascript * * // logging component * function InteractionLogger(eventBus) { * eventBus.on('element.hover', function(event) { * console.log() * }) * } * * InteractionLogger.$inject = [ 'eventBus' ]; // minification save * * // extension module * var extensionModule = { * __init__: [ 'interactionLogger' ], * interactionLogger: [ 'type', InteractionLogger ] * }; * * // extend the viewer * var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] }); * bpmnViewer.importXML(...); * ``` * * @template [ServiceMap=null] * * @extends BaseViewer * * @param {BaseViewerOptions} [options] The options to configure the viewer. */ export default function Viewer(options) { BaseViewer.call(this, options); } inherits(Viewer, BaseViewer); // modules the viewer is composed of Viewer.prototype._modules = [ CoreModule, DrilldownModdule, OverlaysModule, SelectionModule, TranslateModule ]; // default moddle extensions the viewer is composed of Viewer.prototype._moddleExtensions = {}; ================================================ FILE: lib/Viewer.spec.ts ================================================ import Canvas from 'diagram-js/lib/core/Canvas'; import EventBus from 'diagram-js/lib/core/EventBus'; import Viewer from './Viewer'; import { testViewer } from './BaseViewer.spec'; const viewer = new Viewer({ container: 'container' }); testViewer(viewer); const extendedViewer = new Viewer({ container: 'container', alignToOrigin: false, propertiesPanel: { attachTo: '#properties-panel' } }); // typed API usage type FooEvent = { /** * Very cool field! */ foo: string; }; type EventMap = { foo: FooEvent }; type TypeMap = { canvas: Canvas, eventBus: EventBus }; const typedViewer = new Viewer(); const bus = typedViewer.get('eventBus'); const canvas = typedViewer.get('canvas'); canvas.zoom('fit-viewport'); typedViewer.on('foo', event => { console.log(event.foo); }); typedViewer.get('eventBus').on('foo', e => console.log(e.foo)); ================================================ FILE: lib/core/index.js ================================================ import DrawModule from '../draw'; import ImportModule from '../import'; export default { __depends__: [ DrawModule, ImportModule ] }; ================================================ FILE: lib/draw/BpmnRenderUtil.js ================================================ import { has, some } from 'min-dash'; import { getDi } from '../util/ModelUtil'; import { componentsToPath } from 'diagram-js/lib/util/RenderUtil'; /** * @typedef {import('../model').ModdleElement} ModdleElement * @typedef {import('../model').Element} Element * * @typedef {import('../model').ShapeLike} ShapeLike * * @typedef {import('diagram-js/lib/util/Types').Dimensions} Dimensions * @typedef {import('diagram-js/lib/util/Types').Rect} Rect */ // re-export for compatibility export { getDi, getBusinessObject as getSemantic } from '../util/ModelUtil'; export var black = 'hsl(225, 10%, 15%)'; export var white = 'white'; // element utils ////////////////////// /** * Checks if eventDefinition of the given element matches with semantic type. * * @param {ModdleElement} event * @param {string} eventDefinitionType * * @return {boolean} */ export function isTypedEvent(event, eventDefinitionType) { return some(event.eventDefinitions, function(definition) { return definition.$type === eventDefinitionType; }); } /** * Check if element is a throw event. * * @param {ModdleElement} event * * @return {boolean} */ export function isThrowEvent(event) { return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent'); } /** * Check if element is a throw event. * * @param {ModdleElement} element * * @return {boolean} */ export function isCollection(element) { var dataObject = element.dataObjectRef; return element.isCollection || (dataObject && dataObject.isCollection); } // color access ////////////////////// /** * @param {Element} element * @param {string} [defaultColor] * @param {string} [overrideColor] * * @return {string} */ export function getFillColor(element, defaultColor, overrideColor) { var di = getDi(element); return overrideColor || di.get('color:background-color') || di.get('bioc:fill') || defaultColor || white; } /** * @param {Element} element * @param {string} [defaultColor] * @param {string} [overrideColor] * * @return {string} */ export function getStrokeColor(element, defaultColor, overrideColor) { var di = getDi(element); return overrideColor || di.get('color:border-color') || di.get('bioc:stroke') || defaultColor || black; } /** * @param {Element} element * @param {string} [defaultColor] * @param {string} [defaultStrokeColor] * @param {string} [overrideColor] * * @return {string} */ export function getLabelColor(element, defaultColor, defaultStrokeColor, overrideColor) { var di = getDi(element), label = di.get('label'); return overrideColor || (label && label.get('color:color')) || defaultColor || getStrokeColor(element, defaultStrokeColor); } // cropping path customizations ////////////////////// /** * @param {ShapeLike} shape * * @return {string} path */ export function getCirclePath(shape) { var cx = shape.x + shape.width / 2, cy = shape.y + shape.height / 2, radius = shape.width / 2; var circlePath = [ [ 'M', cx, cy ], [ 'm', 0, -radius ], [ 'a', radius, radius, 0, 1, 1, 0, 2 * radius ], [ 'a', radius, radius, 0, 1, 1, 0, -2 * radius ], [ 'z' ] ]; return componentsToPath(circlePath); } /** * @param {ShapeLike} shape * @param {number} [borderRadius] * * @return {string} path */ export function getRoundRectPath(shape, borderRadius) { var x = shape.x, y = shape.y, width = shape.width, height = shape.height; var roundRectPath = [ [ 'M', x + borderRadius, y ], [ 'l', width - borderRadius * 2, 0 ], [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius ], [ 'l', 0, height - borderRadius * 2 ], [ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius ], [ 'l', borderRadius * 2 - width, 0 ], [ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius ], [ 'l', 0, borderRadius * 2 - height ], [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius ], [ 'z' ] ]; return componentsToPath(roundRectPath); } /** * @param {ShapeLike} shape * * @return {string} path */ export function getDiamondPath(shape) { var width = shape.width, height = shape.height, x = shape.x, y = shape.y, halfWidth = width / 2, halfHeight = height / 2; var diamondPath = [ [ 'M', x + halfWidth, y ], [ 'l', halfWidth, halfHeight ], [ 'l', -halfWidth, halfHeight ], [ 'l', -halfWidth, -halfHeight ], [ 'z' ] ]; return componentsToPath(diamondPath); } /** * @param {ShapeLike} shape * * @return {string} path */ export function getRectPath(shape) { var x = shape.x, y = shape.y, width = shape.width, height = shape.height; var rectPath = [ [ 'M', x, y ], [ 'l', width, 0 ], [ 'l', 0, height ], [ 'l', -width, 0 ], [ 'z' ] ]; return componentsToPath(rectPath); } /** * Get width and height from element or overrides. * * @param {Dimensions|Rect|ShapeLike} bounds * @param {Object} overrides * * @returns {Dimensions} */ export function getBounds(bounds, overrides = {}) { return { width: getWidth(bounds, overrides), height: getHeight(bounds, overrides) }; } /** * Get width from element or overrides. * * @param {Dimensions|Rect|ShapeLike} bounds * @param {Object} overrides * * @returns {number} */ export function getWidth(bounds, overrides = {}) { return has(overrides, 'width') ? overrides.width : bounds.width; } /** * Get height from element or overrides. * * @param {Dimensions|Rect|ShapeLike} bounds * @param {Object} overrides * * @returns {number} */ export function getHeight(bounds, overrides = {}) { return has(overrides, 'height') ? overrides.height : bounds.height; } ================================================ FILE: lib/draw/BpmnRenderer.js ================================================ import inherits from 'inherits-browser'; import { assign, forEach, isObject } from 'min-dash'; import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'; import { isExpanded, isHorizontal, isEventSubProcess } from '../util/DiUtil'; import { getLabel, isLabel } from '../util/LabelUtil'; import { is } from '../util/ModelUtil'; import { createLine } from 'diagram-js/lib/util/RenderUtil'; import { isTypedEvent, isThrowEvent, isCollection, getBounds, getDi, getSemantic, getCirclePath, getRoundRectPath, getDiamondPath, getRectPath, getFillColor, getStrokeColor, getLabelColor, getHeight, getWidth } from './BpmnRenderUtil'; import { query as domQuery } from 'min-dom'; import { append as svgAppend, attr as svgAttr, create as svgCreate, classes as svgClasses } from 'tiny-svg'; import { rotate, transform, translate } from 'diagram-js/lib/util/SvgTransformUtil'; import { Ids } from 'ids'; import { black } from './BpmnRenderUtil'; var markerIds = new Ids(); var ELEMENT_LABEL_DISTANCE = 10, INNER_OUTER_DIST = 3, PARTICIPANT_STROKE_WIDTH = 1.5, TASK_BORDER_RADIUS = 10, EXTERNAL_LABEL_BORDER_RADIUS = 4; var DEFAULT_OPACITY = 0.95, FULL_OPACITY = 1, LOW_OPACITY = 0.25; /** * @typedef { Partial<{ * defaultFillColor: string, * defaultStrokeColor: string, * defaultLabelColor: string * }> } BpmnRendererConfig * * @typedef { Partial<{ * fill: string, * stroke: string, * width: string, * height: string * }> } Attrs */ /** * @typedef { import('../model/Types').Element } Element * @typedef { import('../model/Types').Shape } Shape * @typedef { import('../model/Types').Connection } Connection */ /** * A renderer for BPMN elements * * @param {BpmnRendererConfig} config * @param {import('diagram-js/lib/core/EventBus').default} eventBus * @param {import('diagram-js/lib/draw/Styles').default} styles * @param {import('./PathMap').default} pathMap * @param {import('diagram-js/lib/core/Canvas').default} canvas * @param {import('./TextRenderer').default} textRenderer * @param {number} [priority] */ export default function BpmnRenderer( config, eventBus, styles, pathMap, canvas, textRenderer, priority) { BaseRenderer.call(this, eventBus, priority); var defaultFillColor = config && config.defaultFillColor, defaultStrokeColor = config && config.defaultStrokeColor, defaultLabelColor = config && config.defaultLabelColor; function shapeStyle(attrs) { return styles.computeStyle(attrs, { strokeLinecap: 'round', strokeLinejoin: 'round', stroke: black, strokeWidth: 2, fill: 'white' }); } function lineStyle(attrs) { return styles.computeStyle(attrs, [ 'no-fill' ], { strokeLinecap: 'round', strokeLinejoin: 'round', stroke: black, strokeWidth: 2 }); } function addMarker(id, options) { var { ref = { x: 0, y: 0 }, scale = 1, element, parentGfx = canvas._svg } = options; var marker = svgCreate('marker', { id: id, viewBox: '0 0 20 20', refX: ref.x, refY: ref.y, markerWidth: 20 * scale, markerHeight: 20 * scale, orient: 'auto' }); svgAppend(marker, element); var defs = domQuery(':scope > defs', parentGfx); if (!defs) { defs = svgCreate('defs'); svgAppend(parentGfx, defs); } svgAppend(defs, marker); } function marker(parentGfx, type, fill, stroke) { var id = markerIds.nextPrefixed('marker-'); createMarker(parentGfx, id, type, fill, stroke); return 'url(#' + id + ')'; } function createMarker(parentGfx, id, type, fill, stroke) { if (type === 'sequenceflow-end') { var sequenceflowEnd = svgCreate('path', { d: 'M 1 5 L 11 10 L 1 15 Z', ...shapeStyle({ fill: stroke, stroke: stroke, strokeWidth: 1 }) }); addMarker(id, { element: sequenceflowEnd, ref: { x: 11, y: 10 }, scale: 0.5, parentGfx }); } if (type === 'messageflow-start') { var messageflowStart = svgCreate('circle', { cx: 6, cy: 6, r: 3.5, ...shapeStyle({ fill, stroke: stroke, strokeWidth: 1, // fix for safari / chrome / firefox bug not correctly // resetting stroke dash array strokeDasharray: [ 10000, 1 ] }) }); addMarker(id, { element: messageflowStart, ref: { x: 6, y: 6 }, parentGfx }); } if (type === 'messageflow-end') { var messageflowEnd = svgCreate('path', { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z', ...shapeStyle({ fill, stroke: stroke, strokeWidth: 1, // fix for safari / chrome / firefox bug not correctly // resetting stroke dash array strokeDasharray: [ 10000, 1 ] }) }); addMarker(id, { element: messageflowEnd, ref: { x: 8.5, y: 5 }, parentGfx }); } if (type === 'association-start') { var associationStart = svgCreate('path', { d: 'M 11 5 L 1 10 L 11 15', ...lineStyle({ fill: 'none', stroke, strokeWidth: 1.5, // fix for safari / chrome / firefox bug not correctly // resetting stroke dash array strokeDasharray: [ 10000, 1 ] }) }); addMarker(id, { element: associationStart, ref: { x: 1, y: 10 }, scale: 0.5, parentGfx }); } if (type === 'association-end') { var associationEnd = svgCreate('path', { d: 'M 1 5 L 11 10 L 1 15', ...lineStyle({ fill: 'none', stroke, strokeWidth: 1.5, // fix for safari / chrome / firefox bug not correctly // resetting stroke dash array strokeDasharray: [ 10000, 1 ] }) }); addMarker(id, { element: associationEnd, ref: { x: 11, y: 10 }, scale: 0.5, parentGfx }); } if (type === 'conditional-flow-marker') { var conditionalFlowMarker = svgCreate('path', { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z', ...shapeStyle({ fill, stroke: stroke }) }); addMarker(id, { element: conditionalFlowMarker, ref: { x: -1, y: 10 }, scale: 0.5, parentGfx }); } if (type === 'conditional-default-flow-marker') { var defaultFlowMarker = svgCreate('path', { d: 'M 6 4 L 10 16', ...shapeStyle({ stroke: stroke, fill: 'none' }) }); addMarker(id, { element: defaultFlowMarker, ref: { x: 0, y: 10 }, scale: 0.5, parentGfx }); } } function drawCircle(parentGfx, width, height, offset, attrs = {}) { if (isObject(offset)) { attrs = offset; offset = 0; } offset = offset || 0; attrs = shapeStyle(attrs); var cx = width / 2, cy = height / 2; var circle = svgCreate('circle', { cx: cx, cy: cy, r: Math.round((width + height) / 4 - offset), ...attrs }); svgAppend(parentGfx, circle); return circle; } function drawRect(parentGfx, width, height, r, offset, attrs) { if (isObject(offset)) { attrs = offset; offset = 0; } offset = offset || 0; attrs = shapeStyle(attrs); var rect = svgCreate('rect', { x: offset, y: offset, width: width - offset * 2, height: height - offset * 2, rx: r, ry: r, ...attrs }); svgAppend(parentGfx, rect); return rect; } function drawDiamond(parentGfx, width, height, attrs) { var x_2 = width / 2; var y_2 = height / 2; var points = [ { x: x_2, y: 0 }, { x: width, y: y_2 }, { x: x_2, y: height }, { x: 0, y: y_2 } ]; var pointsString = points.map(function(point) { return point.x + ',' + point.y; }).join(' '); attrs = shapeStyle(attrs); var polygon = svgCreate('polygon', { ...attrs, points: pointsString }); svgAppend(parentGfx, polygon); return polygon; } /** * @param {SVGElement} parentGfx * @param {Point[]} waypoints * @param {any} attrs * @param {number} [radius] * * @return {SVGElement} */ function drawLine(parentGfx, waypoints, attrs, radius) { attrs = lineStyle(attrs); var line = createLine(waypoints, attrs, radius); svgAppend(parentGfx, line); return line; } /** * @param {SVGElement} parentGfx * @param {Point[]} waypoints * @param {any} attrs * * @return {SVGElement} */ function drawConnectionSegments(parentGfx, waypoints, attrs) { return drawLine(parentGfx, waypoints, attrs, 5); } function drawPath(parentGfx, d, attrs) { attrs = lineStyle(attrs); var path = svgCreate('path', { ...attrs, d }); svgAppend(parentGfx, path); return path; } function drawMarker(type, parentGfx, path, attrs) { return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs)); } function renderer(type) { return handlers[type]; } function as(type) { return function(parentGfx, element, attrs) { return renderer(type)(parentGfx, element, attrs); }; } var eventIconRenderers = { 'bpmn:MessageEventDefinition': function(parentGfx, element, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_MESSAGE', { xScaleFactor: 0.9, yScaleFactor: 0.9, containerWidth: attrs.width || element.width, containerHeight: attrs.height || element.height, position: { mx: 0.235, my: 0.315 } }); var fill = isThrowing ? getStrokeColor(element, defaultStrokeColor, attrs.stroke) : getFillColor(element, defaultFillColor, attrs.fill); var stroke = isThrowing ? getFillColor(element, defaultFillColor, attrs.fill) : getStrokeColor(element, defaultStrokeColor, attrs.stroke); var messagePath = drawPath(parentGfx, pathData, { fill, stroke, strokeWidth: 1 }); return messagePath; }, 'bpmn:TimerEventDefinition': function(parentGfx, element, attrs = {}) { var baseWidth = attrs.width || element.width; var baseHeight = attrs.height || element.height; // use a lighter stroke for event suprocess icons var strokeWidth = attrs.width ? 1 : 2; var circle = drawCircle(parentGfx, baseWidth, baseHeight, 0.2 * baseHeight, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: strokeWidth }); var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', { xScaleFactor: 0.75, yScaleFactor: 0.75, containerWidth: baseWidth, containerHeight: baseHeight, position: { mx: 0.5, my: 0.5 } }); drawPath(parentGfx, pathData, { stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: strokeWidth }); for (var i = 0; i < 12; i++) { var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', { xScaleFactor: 0.75, yScaleFactor: 0.75, containerWidth: baseWidth, containerHeight: baseHeight, position: { mx: 0.5, my: 0.5 } }); var width = baseWidth / 2, height = baseHeight / 2; drawPath(parentGfx, linePathData, { strokeWidth: 1, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')' }); } return circle; }, 'bpmn:EscalationEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_ESCALATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.5, my: 0.2 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:ConditionalEventDefinition': function(parentGfx, event, attrs = {}) { var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.5, my: 0.222 } }); return drawPath(parentGfx, pathData, { fill: getFillColor(event, defaultFillColor, attrs.fill), stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:LinkEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_LINK', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.57, my: 0.263 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:ErrorEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_ERROR', { xScaleFactor: 1.1, yScaleFactor: 1.1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.2, my: 0.722 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:CancelEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', { xScaleFactor: 1.0, yScaleFactor: 1.0, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.638, my: -0.055 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : 'none'; var path = drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); rotate(path, 45); return path; }, 'bpmn:CompensateEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.22, my: 0.5 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:SignalEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_SIGNAL', { xScaleFactor: 0.9, yScaleFactor: 0.9, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.5, my: 0.2 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { strokeWidth: 1, fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke) }); }, 'bpmn:MultipleEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', { xScaleFactor: 1.1, yScaleFactor: 1.1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.211, my: 0.36 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event, attrs = {}) { var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', { xScaleFactor: 1.2, yScaleFactor: 1.2, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.458, my: 0.194 } }); return drawPath(parentGfx, pathData, { fill: getFillColor(event, defaultFillColor, attrs.fill), stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:TerminateEventDefinition': function(parentGfx, element, attrs = {}) { var circle = drawCircle(parentGfx, element.width, element.height, 8, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 4 }); return circle; } }; function renderEventIcon(element, parentGfx, attrs = {}, proxyElement) { var semantic = getSemantic(element), isThrowing = isThrowEvent(semantic); var nodeElement = proxyElement || element; if (semantic.get('eventDefinitions') && semantic.get('eventDefinitions').length > 1) { if (semantic.get('parallelMultiple')) { return eventIconRenderers[ 'bpmn:ParallelMultipleEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } else { return eventIconRenderers[ 'bpmn:MultipleEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } } if (isTypedEvent(semantic, 'bpmn:MessageEventDefinition')) { return eventIconRenderers[ 'bpmn:MessageEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:TimerEventDefinition')) { return eventIconRenderers[ 'bpmn:TimerEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:ConditionalEventDefinition')) { return eventIconRenderers[ 'bpmn:ConditionalEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:SignalEventDefinition')) { return eventIconRenderers[ 'bpmn:SignalEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:EscalationEventDefinition')) { return eventIconRenderers[ 'bpmn:EscalationEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:LinkEventDefinition')) { return eventIconRenderers[ 'bpmn:LinkEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:ErrorEventDefinition')) { return eventIconRenderers[ 'bpmn:ErrorEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:CancelEventDefinition')) { return eventIconRenderers[ 'bpmn:CancelEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:CompensateEventDefinition')) { return eventIconRenderers[ 'bpmn:CompensateEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:TerminateEventDefinition')) { return eventIconRenderers[ 'bpmn:TerminateEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } return null; } var taskMarkerRenderers = { 'ParticipantMultiplicityMarker': function(parentGfx, element, attrs = {}) { var width = getWidth(element, attrs), height = getHeight(element, attrs); var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: ((width / 2 - 6) / width), my: (height - 15) / height } }); drawMarker('participant-multiplicity', parentGfx, markerPath, { strokeWidth: 2, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'SubProcessMarker': function(parentGfx, element, attrs = {}) { var markerRect = drawRect(parentGfx, 14, 14, 0, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); translate(markerRect, element.width / 2 - 7.5, element.height - 20); var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', { xScaleFactor: 1.5, yScaleFactor: 1.5, containerWidth: element.width, containerHeight: element.height, position: { mx: (element.width / 2 - 7.5) / element.width, my: (element.height - 20) / element.height } }); drawMarker('sub-process', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'ParallelMarker': function(parentGfx, element, attrs) { var width = getWidth(element, attrs), height = getHeight(element, attrs); var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: ((width / 2 + attrs.parallel) / width), my: (height - 20) / height } }); drawMarker('parallel', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'SequentialMarker': function(parentGfx, element, attrs) { var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + attrs.seq) / element.width), my: (element.height - 19) / element.height } }); drawMarker('sequential', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'CompensationMarker': function(parentGfx, element, attrs) { var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + attrs.compensation) / element.width), my: (element.height - 13) / element.height } }); drawMarker('compensation', parentGfx, markerMath, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'LoopMarker': function(parentGfx, element, attrs) { var width = getWidth(element, attrs), height = getHeight(element, attrs); var markerPath = pathMap.getScaledPath('MARKER_LOOP', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: ((width / 2 + attrs.loop) / width), my: (height - 7) / height } }); drawMarker('loop', parentGfx, markerPath, { strokeWidth: 1.5, fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeMiterlimit: 0.5 }); }, 'AdhocMarker': function(parentGfx, element, attrs) { var width = getWidth(element, attrs), height = getHeight(element, attrs); var markerPath = pathMap.getScaledPath('MARKER_ADHOC', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: ((width / 2 + attrs.adhoc) / width), my: (height - 15) / height } }); drawMarker('adhoc', parentGfx, markerPath, { strokeWidth: 1, fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); } }; function renderTaskMarker(type, parentGfx, element, attrs) { taskMarkerRenderers[ type ](parentGfx, element, attrs); } function renderTaskMarkers(parentGfx, element, taskMarkers = [], attrs = {}) { attrs = { fill: attrs.fill, stroke: attrs.stroke, width: getWidth(element, attrs), height: getHeight(element, attrs) }; var semantic = getSemantic(element); var subprocess = taskMarkers.includes('SubProcessMarker'); if (subprocess) { attrs = { ...attrs, seq: -21, parallel: -22, compensation: -25, loop: -18, adhoc: 10 }; } else { attrs = { ...attrs, seq: -5, parallel: -6, compensation: -7, loop: 0, adhoc: -8 }; } if (semantic.get('isForCompensation')) { taskMarkers.push('CompensationMarker'); } if (is(semantic, 'bpmn:AdHocSubProcess')) { taskMarkers.push('AdhocMarker'); if (!subprocess) { assign(attrs, { compensation: attrs.compensation - 18 }); } } var loopCharacteristics = semantic.get('loopCharacteristics'), isSequential = loopCharacteristics && loopCharacteristics.get('isSequential'); if (loopCharacteristics) { assign(attrs, { compensation: attrs.compensation - 18, }); if (taskMarkers.includes('AdhocMarker')) { assign(attrs, { seq: -23, loop: -18, parallel: -24 }); } if (isSequential === undefined) { taskMarkers.push('LoopMarker'); } if (isSequential === false) { taskMarkers.push('ParallelMarker'); } if (isSequential === true) { taskMarkers.push('SequentialMarker'); } } if (taskMarkers.includes('CompensationMarker') && taskMarkers.length === 1) { assign(attrs, { compensation: -8 }); } forEach(taskMarkers, function(marker) { renderTaskMarker(marker, parentGfx, element, attrs); }); } function renderLabel(parentGfx, label, attrs = {}) { attrs = assign({ size: { width: 100 } }, attrs); var text = textRenderer.createText(label || '', attrs); svgClasses(text).add('djs-label'); svgAppend(parentGfx, text); return text; } function renderEmbeddedLabel(parentGfx, element, align, attrs = {}) { var semantic = getSemantic(element); var box = getBounds({ x: element.x, y: element.y, width: element.width, height: element.height }, attrs); return renderLabel(parentGfx, semantic.name, { align, box, padding: 7, style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) } }); } function renderExternalLabel(parentGfx, element, attrs = {}) { var box = { width: 90, height: 30, x: element.width / 2 + element.x, y: element.height / 2 + element.y }; return renderLabel(parentGfx, getLabel(element), { box: box, fitBox: true, style: assign( {}, textRenderer.getExternalStyle(), { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) } ) }); } function renderLaneLabel(parentGfx, text, element, attrs = {}) { var isHorizontalLane = isHorizontal(element); var textBox = renderLabel(parentGfx, text, { box: { height: 30, width: isHorizontalLane ? getHeight(element, attrs) : getWidth(element, attrs), }, align: 'center-middle', style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) } }); if (isHorizontalLane) { var top = -1 * getHeight(element, attrs); transform(textBox, 0, -top, 270); } } function renderActivity(parentGfx, element, attrs = {}) { var { width, height } = getBounds(element, attrs); return drawRect(parentGfx, width, height, TASK_BORDER_RADIUS, { ...attrs, fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: DEFAULT_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); } function renderAssociation(parentGfx, element, attrs = {}) { var semantic = getSemantic(element); var fill = getFillColor(element, defaultFillColor, attrs.fill), stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke); if (semantic.get('associationDirection') === 'One' || semantic.get('associationDirection') === 'Both') { attrs.markerEnd = marker(parentGfx, 'association-end', fill, stroke); } if (semantic.get('associationDirection') === 'Both') { attrs.markerStart = marker(parentGfx, 'association-start', fill, stroke); } attrs = pickAttrs(attrs, [ 'markerStart', 'markerEnd' ]); return drawConnectionSegments(parentGfx, element.waypoints, { ...attrs, stroke, strokeDasharray: '0, 5' }); } function renderDataObject(parentGfx, element, attrs = {}) { var fill = getFillColor(element, defaultFillColor, attrs.fill), stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke); var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.474, my: 0.296 } }); var dataObject = drawPath(parentGfx, pathData, { fill, fillOpacity: DEFAULT_OPACITY, stroke }); var semantic = getSemantic(element); if (isCollection(semantic)) { var collectionPathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.33, my: (element.height - 18) / element.height } }); drawPath(parentGfx, collectionPathData, { strokeWidth: 2, fill, stroke }); } return dataObject; } function renderEvent(parentGfx, element, attrs = {}) { return drawCircle(parentGfx, element.width, element.height, { fillOpacity: DEFAULT_OPACITY, ...attrs, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); } function renderGateway(parentGfx, element, attrs = {}) { return drawDiamond(parentGfx, element.width, element.height, { fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: DEFAULT_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); } function renderLane(parentGfx, element, attrs = {}) { var lane = drawRect(parentGfx, getWidth(element, attrs), getHeight(element, attrs), 0, { fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: attrs.fillOpacity || DEFAULT_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1.5 }); var semantic = getSemantic(element); if (is(semantic, 'bpmn:Lane')) { var text = semantic.get('name'); renderLaneLabel(parentGfx, text, element, attrs); } return lane; } function renderSubProcess(parentGfx, element, attrs = {}) { var activity = renderActivity(parentGfx, element, attrs); var expanded = isExpanded(element); if (isEventSubProcess(element)) { svgAttr(activity, { strokeDasharray: '0, 5.5', strokeWidth: 2.5 }); if (!expanded) { var flowElements = getSemantic(element).flowElements || []; var startEvents = flowElements.filter(e => is(e, 'bpmn:StartEvent')); if (startEvents.length === 1) { renderEventSubProcessIcon(startEvents[0], parentGfx, attrs, element); } } } renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle', attrs); if (expanded) { renderTaskMarkers(parentGfx, element, undefined, attrs); } else { renderTaskMarkers(parentGfx, element, [ 'SubProcessMarker' ], attrs); } return activity; } function renderEventSubProcessIcon(startEvent, parentGfx, attrs, proxyElement) { var iconSize = 22; // match the colors of the enclosing subprocess var proxyAttrs = { fill: getFillColor(proxyElement, defaultFillColor, attrs.fill), stroke: getStrokeColor(proxyElement, defaultStrokeColor, attrs.stroke), width: iconSize, height: iconSize }; var interrupting = getSemantic(startEvent).isInterrupting; var strokeDasharray = interrupting ? 0 : 3; // better visibility for non-interrupting events var strokeWidth = interrupting ? 1 : 1.2; // make the icon look larger by drawing a smaller circle var circleSize = 20; var shift = (iconSize - circleSize) / 2; var transform = 'translate(' + shift + ',' + shift + ')'; drawCircle(parentGfx, circleSize, circleSize, { fill: proxyAttrs.fill, stroke: proxyAttrs.stroke, strokeWidth, strokeDasharray, transform }); renderEventIcon(startEvent, parentGfx, proxyAttrs, proxyElement); } function renderTask(parentGfx, element, attrs = {}) { var activity = renderActivity(parentGfx, element, attrs); renderEmbeddedLabel(parentGfx, element, 'center-middle', attrs); renderTaskMarkers(parentGfx, element, undefined, attrs); return activity; } var handlers = this.handlers = { 'bpmn:AdHocSubProcess': function(parentGfx, element, attrs = {}) { if (isExpanded(element)) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); } else { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); } return renderSubProcess(parentGfx, element, attrs); }, 'bpmn:Association': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderAssociation(parentGfx, element, attrs); }, 'bpmn:BoundaryEvent': function(parentGfx, element, attrs = {}) { var { renderIcon = true } = attrs; attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var semantic = getSemantic(element), cancelActivity = semantic.get('cancelActivity'); attrs = { strokeWidth: 1.5, fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: FULL_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }; if (!cancelActivity) { attrs.strokeDasharray = '6'; } var event = renderEvent(parentGfx, element, attrs); drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, { ...attrs, fill: 'none' }); if (renderIcon) { renderEventIcon(element, parentGfx, attrs); } return event; }, 'bpmn:BusinessRuleTask': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var task = renderTask(parentGfx, element, attrs); var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', { abspos: { x: 8, y: 8 } }); var businessPath = drawPath(parentGfx, headerData); svgAttr(businessPath, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', { abspos: { x: 8, y: 8 } }); var businessHeaderPath = drawPath(parentGfx, headerPathData); svgAttr(businessHeaderPath, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return task; }, 'bpmn:CallActivity': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderSubProcess(parentGfx, element, { strokeWidth: 5, ...attrs }); }, 'bpmn:ComplexGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var gateway = renderGateway(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', { xScaleFactor: 0.5, yScaleFactor:0.5, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.46, my: 0.26 } }); drawPath(parentGfx, pathData, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return gateway; }, 'bpmn:DataInput': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var arrowPathData = pathMap.getRawPath('DATA_ARROW'); var dataObject = renderDataObject(parentGfx, element, attrs); drawPath(parentGfx, arrowPathData, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return dataObject; }, 'bpmn:DataInputAssociation': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderAssociation(parentGfx, element, { ...attrs, markerEnd: marker(parentGfx, 'association-end', getFillColor(element, defaultFillColor, attrs.fill), getStrokeColor(element, defaultStrokeColor, attrs.stroke)) }); }, 'bpmn:DataObject': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderDataObject(parentGfx, element, attrs); }, 'bpmn:DataObjectReference': as('bpmn:DataObject'), 'bpmn:DataOutput': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var arrowPathData = pathMap.getRawPath('DATA_ARROW'); var dataObject = renderDataObject(parentGfx, element, attrs); drawPath(parentGfx, arrowPathData, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); return dataObject; }, 'bpmn:DataOutputAssociation': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderAssociation(parentGfx, element, { ...attrs, markerEnd: marker(parentGfx, 'association-end', getFillColor(element, defaultFillColor, attrs.fill), getStrokeColor(element, defaultStrokeColor, attrs.stroke)) }); }, 'bpmn:DataStoreReference': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var dataStorePath = pathMap.getScaledPath('DATA_STORE', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0, my: 0.133 } }); return drawPath(parentGfx, dataStorePath, { fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: DEFAULT_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 2 }); }, 'bpmn:EndEvent': function(parentGfx, element, attrs = {}) { var { renderIcon = true } = attrs; attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var event = renderEvent(parentGfx, element, { ...attrs, strokeWidth: 4 }); if (renderIcon) { renderEventIcon(element, parentGfx, attrs); } return event; }, 'bpmn:EventBasedGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var semantic = getSemantic(element); var diamond = renderGateway(parentGfx, element, attrs); drawCircle(parentGfx, element.width, element.height, element.height * 0.20, { fill: getFillColor(element, 'none', attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); var type = semantic.get('eventGatewayType'), instantiate = !!semantic.get('instantiate'); function drawEvent() { var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', { xScaleFactor: 0.18, yScaleFactor: 0.18, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.36, my: 0.44 } }); drawPath(parentGfx, pathData, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 2 }); } if (type === 'Parallel') { var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', { xScaleFactor: 0.4, yScaleFactor: 0.4, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.474, my: 0.296 } }); drawPath(parentGfx, pathData, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); } else if (type === 'Exclusive') { if (!instantiate) { drawCircle(parentGfx, element.width, element.height, element.height * 0.26, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); } drawEvent(); } return diamond; }, 'bpmn:ExclusiveGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var gateway = renderGateway(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', { xScaleFactor: 0.4, yScaleFactor: 0.4, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.32, my: 0.3 } }); var di = getDi(element); if (di.get('isMarkerVisible')) { drawPath(parentGfx, pathData, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); } return gateway; }, 'bpmn:Gateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderGateway(parentGfx, element, attrs); }, 'bpmn:Group': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, { stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1.5, strokeDasharray: '10, 6, 0, 6', fill: 'none', pointerEvents: 'none', width: getWidth(element, attrs), height: getHeight(element, attrs) }); }, 'bpmn:InclusiveGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var gateway = renderGateway(parentGfx, element, attrs); drawCircle(parentGfx, element.width, element.height, element.height * 0.24, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 2.5 }); return gateway; }, 'bpmn:IntermediateEvent': function(parentGfx, element, attrs = {}) { var { renderIcon = true } = attrs; attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var outer = renderEvent(parentGfx, element, { ...attrs, strokeWidth: 1.5 }); drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1.5 }); if (renderIcon) { renderEventIcon(element, parentGfx, attrs); } return outer; }, 'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'), 'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'), 'bpmn:Lane': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); return renderLane(parentGfx, element, { ...attrs, fillOpacity: LOW_OPACITY }); }, 'bpmn:ManualTask': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var task = renderTask(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', { abspos: { x: 17, y: 15 } }); drawPath(parentGfx, pathData, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 0.5 }); return task; }, 'bpmn:MessageFlow': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var semantic = getSemantic(element), di = getDi(element); var fill = getFillColor(element, defaultFillColor, attrs.fill), stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke); var path = drawConnectionSegments(parentGfx, element.waypoints, { markerEnd: marker(parentGfx, 'messageflow-end', fill, stroke), markerStart: marker(parentGfx, 'messageflow-start', fill, stroke), stroke, strokeDasharray: '10, 11', strokeWidth: 1.5 }); if (semantic.get('messageRef')) { var midPoint = path.getPointAtLength(path.getTotalLength() / 2); var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', { abspos: { x: midPoint.x, y: midPoint.y } }); var messageAttrs = { strokeWidth: 1 }; if (di.get('messageVisibleKind') === 'initiating') { messageAttrs.fill = fill; messageAttrs.stroke = stroke; } else { messageAttrs.fill = stroke; messageAttrs.stroke = fill; } var message = drawPath(parentGfx, markerPathData, messageAttrs); var messageRef = semantic.get('messageRef'), name = messageRef.get('name'); var label = renderLabel(parentGfx, name, { align: 'center-top', fitBox: true, style: { fill: stroke } }); var messageBounds = message.getBBox(), labelBounds = label.getBBox(); var translateX = midPoint.x - labelBounds.width / 2, translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE; transform(label, translateX, translateY, 0); } return path; }, 'bpmn:ParallelGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var diamond = renderGateway(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', { xScaleFactor: 0.6, yScaleFactor: 0.6, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.46, my: 0.2 } }); drawPath(parentGfx, pathData, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return diamond; }, 'bpmn:Participant': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); var participant = renderLane(parentGfx, element, attrs); var expandedParticipant = isExpanded(element); var horizontalParticipant = isHorizontal(element); var semantic = getSemantic(element), name = semantic.get('name'); if (expandedParticipant) { var waypoints = horizontalParticipant ? [ { x: 30, y: 0 }, { x: 30, y: getHeight(element, attrs) } ] : [ { x: 0, y: 30 }, { x: getWidth(element, attrs), y: 30 } ]; drawLine(parentGfx, waypoints, { stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: PARTICIPANT_STROKE_WIDTH }); renderLaneLabel(parentGfx, name, element, attrs); } else { var bounds = getBounds(element, attrs); if (!horizontalParticipant) { bounds.height = getWidth(element, attrs); bounds.width = getHeight(element, attrs); } var textBox = renderLabel(parentGfx, name, { box: bounds, align: 'center-middle', style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) } }); if (!horizontalParticipant) { var top = -1 * getHeight(element, attrs); transform(textBox, 0, -top, 270); } } if (semantic.get('participantMultiplicity')) { renderTaskMarker('ParticipantMultiplicityMarker', parentGfx, element, attrs); } return participant; }, 'bpmn:ReceiveTask' : function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var semantic = getSemantic(element); var task = renderTask(parentGfx, element, attrs); var pathData; if (semantic.get('instantiate')) { drawCircle(parentGfx, 28, 28, 20 * 0.22, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', { abspos: { x: 7.77, y: 9.52 } }); } else { pathData = pathMap.getScaledPath('TASK_TYPE_SEND', { xScaleFactor: 0.9, yScaleFactor: 0.9, containerWidth: 21, containerHeight: 14, position: { mx: 0.3, my: 0.4 } }); } drawPath(parentGfx, pathData, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return task; }, 'bpmn:ScriptTask': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var task = renderTask(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', { abspos: { x: 15, y: 20 } }); drawPath(parentGfx, pathData, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return task; }, 'bpmn:SendTask': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var task = renderTask(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: 21, containerHeight: 14, position: { mx: 0.285, my: 0.357 } }); drawPath(parentGfx, pathData, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getFillColor(element, defaultFillColor, attrs.fill), strokeWidth: 1 }); return task; }, 'bpmn:SequenceFlow': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var fill = getFillColor(element, defaultFillColor, attrs.fill), stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke); var connection = drawConnectionSegments(parentGfx, element.waypoints, { markerEnd: marker(parentGfx, 'sequenceflow-end', fill, stroke), stroke }); var semantic = getSemantic(element); var { source } = element; if (source) { var sourceSemantic = getSemantic(source); // conditional flow marker if (semantic.get('conditionExpression') && is(sourceSemantic, 'bpmn:Activity')) { svgAttr(connection, { markerStart: marker(parentGfx, 'conditional-flow-marker', fill, stroke) }); } // default marker if (sourceSemantic.get('default') && (is(sourceSemantic, 'bpmn:Gateway') || is(sourceSemantic, 'bpmn:Activity')) && sourceSemantic.get('default') === semantic) { svgAttr(connection, { markerStart: marker(parentGfx, 'conditional-default-flow-marker', fill, stroke) }); } } return connection; }, 'bpmn:ServiceTask': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var task = renderTask(parentGfx, element, attrs); drawCircle(parentGfx, 10, 10, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: 'none', transform: 'translate(6, 6)' }); var pathDataService1 = pathMap.getScaledPath('TASK_TYPE_SERVICE', { abspos: { x: 12, y: 18 } }); drawPath(parentGfx, pathDataService1, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); drawCircle(parentGfx, 10, 10, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: 'none', transform: 'translate(11, 10)' }); var pathDataService2 = pathMap.getScaledPath('TASK_TYPE_SERVICE', { abspos: { x: 17, y: 22 } }); drawPath(parentGfx, pathDataService2, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return task; }, 'bpmn:StartEvent': function(parentGfx, element, attrs = {}) { var { renderIcon = true } = attrs; attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var semantic = getSemantic(element); if (!semantic.get('isInterrupting')) { attrs = { ...attrs, strokeDasharray: '6' }; } var event = renderEvent(parentGfx, element, attrs); if (renderIcon) { renderEventIcon(element, parentGfx, attrs); } return event; }, 'bpmn:SubProcess': function(parentGfx, element, attrs = {}) { if (isExpanded(element)) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); } else { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); } return renderSubProcess(parentGfx, element, attrs); }, 'bpmn:Task': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderTask(parentGfx, element, attrs); }, 'bpmn:TextAnnotation': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); var { width, height } = getBounds(element, attrs); var textElement = drawRect(parentGfx, width, height, 0, 0, { fill: 'none', stroke: 'none' }); var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: 0.0, my: 0.0 } }); drawPath(parentGfx, textPathData, { stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); var semantic = getSemantic(element), text = semantic.get('text') || ''; renderLabel(parentGfx, text, { align: 'left-top', box: getBounds(element, attrs), padding: 7, style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) } }); return textElement; }, 'bpmn:Transaction': function(parentGfx, element, attrs = {}) { if (isExpanded(element)) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); } else { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); } var outer = renderSubProcess(parentGfx, element, { strokeWidth: 1.5, ...attrs }); var innerAttrs = styles.style([ 'no-fill', 'no-events' ], { stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1.5 }); var expanded = isExpanded(element); if (!expanded) { attrs = {}; } drawRect( parentGfx, getWidth(element, attrs), getHeight(element, attrs), TASK_BORDER_RADIUS - INNER_OUTER_DIST, INNER_OUTER_DIST, innerAttrs ); return outer; }, 'bpmn:UserTask': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var task = renderTask(parentGfx, element, attrs); var x = 15; var y = 12; var pathDataUser1 = pathMap.getScaledPath('TASK_TYPE_USER_1', { abspos: { x: x, y: y } }); drawPath(parentGfx, pathDataUser1, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 0.5 }); var pathDataUser2 = pathMap.getScaledPath('TASK_TYPE_USER_2', { abspos: { x: x, y: y } }); drawPath(parentGfx, pathDataUser2, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 0.5 }); var pathDataUser3 = pathMap.getScaledPath('TASK_TYPE_USER_3', { abspos: { x: x, y: y } }); drawPath(parentGfx, pathDataUser3, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 0.5 }); return task; }, 'label': function(parentGfx, element, attrs = {}) { return renderExternalLabel(parentGfx, element, attrs); } }; // extension API, use at your own risk this._drawPath = drawPath; this._renderer = renderer; } inherits(BpmnRenderer, BaseRenderer); BpmnRenderer.$inject = [ 'config.bpmnRenderer', 'eventBus', 'styles', 'pathMap', 'canvas', 'textRenderer' ]; /** * @param {Element} element * * @return {boolean} */ BpmnRenderer.prototype.canRender = function(element) { return is(element, 'bpmn:BaseElement'); }; /** * Draw shape into parentGfx. * * @param {SVGElement} parentGfx * @param {Shape} shape * @param {Attrs} [attrs] * * @return {SVGElement} mainGfx */ BpmnRenderer.prototype.drawShape = function(parentGfx, shape, attrs = {}) { var { type } = shape; var handler = this._renderer(type); return handler(parentGfx, shape, attrs); }; /** * Draw connection into parentGfx. * * @param {SVGElement} parentGfx * @param {Connection} connection * @param {Attrs} [attrs] * * @return {SVGElement} mainGfx */ BpmnRenderer.prototype.drawConnection = function(parentGfx, connection, attrs = {}) { var { type } = connection; var handler = this._renderer(type); return handler(parentGfx, connection, attrs); }; /** * Get shape path. * * @param {Shape} shape * * @return {string} path */ BpmnRenderer.prototype.getShapePath = function(shape) { if (isLabel(shape)) { return getRoundRectPath(shape, EXTERNAL_LABEL_BORDER_RADIUS); } if (is(shape, 'bpmn:Event')) { return getCirclePath(shape); } if (is(shape, 'bpmn:Activity')) { return getRoundRectPath(shape, TASK_BORDER_RADIUS); } if (is(shape, 'bpmn:Gateway')) { return getDiamondPath(shape); } return getRectPath(shape); }; /** * Pick attributes if they exist. * * @param {Object} attrs * @param {string[]} keys * * @returns {Object} */ function pickAttrs(attrs, keys = []) { return keys.reduce((pickedAttrs, key) => { if (attrs[ key ]) { pickedAttrs[ key ] = attrs[ key ]; } return pickedAttrs; }, {}); } ================================================ FILE: lib/draw/PathMap.js ================================================ /** * Map containing SVG paths needed by BpmnRenderer */ export default function PathMap() { /** * Contains a map of path elements * *

Path definition

* A parameterized path is defined like this: *
   * 'GATEWAY_PARALLEL': {
   *   d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
          '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
   *   height: 17.5,
   *   width:  17.5,
   *   heightElements: [2.5, 7.5],
   *   widthElements: [2.5, 7.5]
   * }
   * 
*

It's important to specify a correct height and width for the path as the scaling * is based on the ratio between the specified height and width in this object and the * height and width that is set as scale target (Note x,y coordinates will be scaled with * individual ratios).

*

The 'heightElements' and 'widthElements' array must contain the values that will be scaled. * The scaling is based on the computed ratios. * Coordinates on the y axis should be in the heightElement's array, they will be scaled using * the computed ratio coefficient. * In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets. *

    *
  • The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....
  • *
  • The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....
  • *
* The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index. *

*/ this.pathMap = { 'EVENT_MESSAGE': { d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}', height: 36, width: 36, heightElements: [ 6, 14 ], widthElements: [ 10.5, 21 ] }, 'EVENT_SIGNAL': { d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z', height: 36, width: 36, heightElements: [ 18 ], widthElements: [ 10, 20 ] }, 'EVENT_ESCALATION': { d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z', height: 36, width: 36, heightElements: [ 20, 7 ], widthElements: [ 8 ] }, 'EVENT_CONDITIONAL': { d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' + 'M {e.x2},{e.y3} l {e.x0},0 ' + 'M {e.x2},{e.y4} l {e.x0},0 ' + 'M {e.x2},{e.y5} l {e.x0},0 ' + 'M {e.x2},{e.y6} l {e.x0},0 ' + 'M {e.x2},{e.y7} l {e.x0},0 ' + 'M {e.x2},{e.y8} l {e.x0},0 ', height: 36, width: 36, heightElements: [ 8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5 ], widthElements: [ 10.5, 14.5, 12.5 ] }, 'EVENT_LINK': { d: 'm {mx},{my} 0,{e.y0} -{e.x1},0 0,{e.y1} {e.x1},0 0,{e.y0} {e.x0},-{e.y2} -{e.x0},-{e.y2} z', height: 36, width: 36, heightElements: [ 4.4375, 6.75, 7.8125 ], widthElements: [ 9.84375, 13.5 ] }, 'EVENT_ERROR': { d: 'm {mx},{my} {e.x0},-{e.y0} {e.x1},-{e.y1} {e.x2},{e.y2} {e.x3},-{e.y3} -{e.x4},{e.y4} -{e.x5},-{e.y5} z', height: 36, width: 36, heightElements: [ 0.023, 8.737, 8.151, 16.564, 10.591, 8.714 ], widthElements: [ 0.085, 6.672, 6.97, 4.273, 5.337, 6.636 ] }, 'EVENT_CANCEL_45': { d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' + '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z', height: 36, width: 36, heightElements: [ 4.75, 8.5 ], widthElements: [ 4.75, 8.5 ] }, 'EVENT_COMPENSATION': { d: 'm {mx},{my} {e.x0},-{e.y0} 0,{e.y1} z m {e.x1},-{e.y2} {e.x2},-{e.y3} 0,{e.y1} -{e.x2},-{e.y3} z', height: 36, width: 36, heightElements: [ 6.5, 13, 0.4, 6.1 ], widthElements: [ 9, 9.3, 8.7 ] }, 'EVENT_TIMER_WH': { d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ', height: 36, width: 36, heightElements: [ 10, 2 ], widthElements: [ 3, 7 ] }, 'EVENT_TIMER_LINE': { d: 'M {mx},{my} ' + 'm {e.x0},{e.y0} l -{e.x1},{e.y1} ', height: 36, width: 36, heightElements: [ 10, 3 ], widthElements: [ 0, 0 ] }, 'EVENT_MULTIPLE': { d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z', height: 36, width: 36, heightElements: [ 6.28099, 12.56199 ], widthElements: [ 3.1405, 9.42149, 12.56198 ] }, 'EVENT_PARALLEL_MULTIPLE': { d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' + '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z', height: 36, width: 36, heightElements: [ 2.56228, 7.68683 ], widthElements: [ 2.56228, 7.68683 ] }, 'GATEWAY_EXCLUSIVE': { d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' + '{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' + '{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z', height: 17.5, width: 17.5, heightElements: [ 8.5, 6.5312, -6.5312, -8.5 ], widthElements: [ 6.5, -6.5, 3, -3, 5, -5 ] }, 'GATEWAY_PARALLEL': { d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' + '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z', height: 30, width: 30, heightElements: [ 5, 12.5 ], widthElements: [ 5, 12.5 ] }, 'GATEWAY_EVENT_BASED': { d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z', height: 11, width: 11, heightElements: [ -6, 6, 12, -12 ], widthElements: [ 9, -3, -12 ] }, 'GATEWAY_COMPLEX': { d:'m {mx},{my} 0,{e.y0} -{e.x0},-{e.y1} -{e.x1},{e.y2} {e.x0},{e.y1} -{e.x2},0 0,{e.y3} ' + '{e.x2},0 -{e.x0},{e.y1} l {e.x1},{e.y2} {e.x0},-{e.y1} 0,{e.y0} {e.x3},0 0,-{e.y0} {e.x0},{e.y1} ' + '{e.x1},-{e.y2} -{e.x0},-{e.y1} {e.x2},0 0,-{e.y3} -{e.x2},0 {e.x0},-{e.y1} -{e.x1},-{e.y2} ' + '-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z', height: 17.125, width: 17.125, heightElements: [ 4.875, 3.4375, 2.125, 3 ], widthElements: [ 3.4375, 2.125, 4.875, 3 ] }, 'DATA_OBJECT_PATH': { d:'m 0,0 {e.x1},0 {e.x0},{e.y0} 0,{e.y1} -{e.x2},0 0,-{e.y2} {e.x1},0 0,{e.y0} {e.x0},0', height: 61, width: 51, heightElements: [ 10, 50, 60 ], widthElements: [ 10, 40, 50, 60 ] }, 'DATA_OBJECT_COLLECTION_PATH': { d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10', height: 10, width: 10, heightElements: [], widthElements: [] }, 'DATA_ARROW': { d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z', height: 61, width: 51, heightElements: [], widthElements: [] }, 'DATA_STORE': { d:'m {mx},{my} ' + 'l 0,{e.y2} ' + 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' + 'l 0,-{e.y2} ' + 'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' + 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' + 'm -{e.x2},{e.y0}' + 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' + 'm -{e.x2},{e.y0}' + 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0', height: 61, width: 61, heightElements: [ 7, 10, 45 ], widthElements: [ 2, 58, 60 ] }, 'TEXT_ANNOTATION': { d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0', height: 30, width: 10, heightElements: [ 30 ], widthElements: [ 10 ] }, 'MARKER_SUB_PROCESS': { d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0', height: 10, width: 10, heightElements: [], widthElements: [] }, 'MARKER_PARALLEL': { d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10', height: 10, width: 10, heightElements: [], widthElements: [] }, 'MARKER_SEQUENTIAL': { d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0', height: 10, width: 10, heightElements: [], widthElements: [] }, 'MARKER_COMPENSATION': { d: 'm {mx},{my} 7,-5 0,10 z m 7.1,-0.3 6.9,-4.7 0,10 -6.9,-4.7 z', height: 10, width: 21, heightElements: [], widthElements: [] }, 'MARKER_LOOP': { d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' + '-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' + '0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' + 'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902', height: 13.9, width: 13.7, heightElements: [], widthElements: [] }, 'MARKER_ADHOC': { d: 'm {mx},{my} m 0.84461,2.64411 c 1.05533,-1.23780996 2.64337,-2.07882 4.29653,-1.97997996 2.05163,0.0805 ' + '3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' + '1.2775,-0.64078 1.7542,-1.17511 0,0.56023 0,1.12046 0,1.6807 -0.98706,0.96237996 -2.29792,1.62393996 ' + '-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' + '-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z', height: 4, width: 15, heightElements: [], widthElements: [] }, 'TASK_TYPE_SEND': { d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}', height: 14, width: 21, heightElements: [ 6, 14 ], widthElements: [ 10.5, 21 ] }, 'TASK_TYPE_SCRIPT': { d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' + 'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' + 'm -7,-12 l 5,0 ' + 'm -4.5,3 l 4.5,0 ' + 'm -3,3 l 5,0' + 'm -4,3 l 5,0', height: 15, width: 12.6, heightElements: [ 6, 14 ], widthElements: [ 10.5, 21 ] }, 'TASK_TYPE_USER_1': { d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' + '-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' + '0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' + 'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' + 'm -8,6 l 0,5.5 m 11,0 l 0,-5' }, 'TASK_TYPE_USER_2': { d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' + '-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 ' }, 'TASK_TYPE_USER_3': { d: 'm {mx},{my} m -6.9,-3.80 c 0,0 2.25099998,-2.358 4.27399998,-1.177 2.024,1.181 4.221,1.537 ' + '4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' + '-4.20799998,3.36699999 -4.20699998,4.34799999 z' }, 'TASK_TYPE_MANUAL': { d: 'm {mx},{my} c 0.234,-0.01 5.604,0.008 8.029,0.004 0.808,0 1.271,-0.172 1.417,-0.752 0.227,-0.898 ' + '-0.334,-1.314 -1.338,-1.316 -2.467,-0.01 -7.886,-0.004 -8.108,-0.004 -0.014,-0.079 0.016,-0.533 0,-0.61 ' + '0.195,-0.042 8.507,0.006 9.616,0.002 0.877,-0.007 1.35,-0.438 1.353,-1.208 0.003,-0.768 -0.479,-1.09 ' + '-1.35,-1.091 -2.968,-0.002 -9.619,-0.013 -9.619,-0.013 v -0.591 c 0,0 5.052,-0.016 7.225,-0.016 ' + '0.888,-0.002 1.354,-0.416 1.351,-1.193 -0.006,-0.761 -0.492,-1.196 -1.361,-1.196 -3.473,-0.005 ' + '-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' + '2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' + '-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' + '-1.516,1.253 -1.882,2.19 -0.37000002,0.95 -0.17,2.01 -0.166,2.979 0.004,0.718 -0.27300002,1.345 ' + '-0.055,2.063 0.629,2.087 2.425,3.312 4.859,3.318 4.6179995,0.014 9.2379995,-0.139 13.8569995,-0.158 ' + '0.755,-0.004 1.171,-0.301 1.182,-1.033 0.012,-0.754 -0.423,-0.969 -1.183,-0.973 -1.778,-0.01 ' + '-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z' }, 'TASK_TYPE_INSTANTIATING_SEND': { d: 'm {mx},{my} l 0,8.4 l 12.6,0 l 0,-8.4 z l 6.3,3.6 l 6.3,-3.6' }, 'TASK_TYPE_SERVICE': { d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' + '0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' + '-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' + 'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' + '-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' + '-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' + 'h -2.241173 l 0.0042,1.63124 c -0.353763,0.0736 -0.705369,0.17977 -1.049785,0.32371 -0.344415,0.14437 ' + '-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' + 'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' + 'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' + '0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' + 'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' + 'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' + '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' + '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z' }, 'TASK_TYPE_SERVICE_FILL': { d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' + '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' + '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z' }, 'TASK_TYPE_BUSINESS_RULE_HEADER': { d: 'm {mx},{my} 0,4 20,0 0,-4 z' }, 'TASK_TYPE_BUSINESS_RULE_MAIN': { d: 'm {mx},{my} 0,12 20,0 0,-12 z' + 'm 0,8 l 20,0 ' + 'm -13,-4 l 0,8' }, 'MESSAGE_FLOW_MARKER': { d: 'm {mx},{my} m -10.5 ,-7 l 0,14 l 21,0 l 0,-14 z l 10.5,6 l 10.5,-6' } }; /** * Return raw path for the given ID. * * @param {string} pathId * * @return {string} raw path */ this.getRawPath = function getRawPath(pathId) { return this.pathMap[pathId].d; }; /** * Scales the path to the given height and width. *

Use case

*

Use case is to scale the content of elements (event, gateways) based * on the element bounding box's size. *

*

Why not transform

*

Scaling a path with transform() will also scale the stroke and IE does not support * the option 'non-scaling-stroke' to prevent this. * Also there are use cases where only some parts of a path should be * scaled.

* * @param {string} pathId The ID of the path. * @param {Object} param

* Example param object scales the path to 60% size of the container (data.width, data.height). *

   *   {
   *     xScaleFactor: 0.6,
   *     yScaleFactor:0.6,
   *     containerWidth: data.width,
   *     containerHeight: data.height,
   *     position: {
   *       mx: 0.46,
   *       my: 0.2,
   *     }
   *   }
   *   
*
    *
  • targetpathwidth = xScaleFactor * containerWidth
  • *
  • targetpathheight = yScaleFactor * containerHeight
  • *
  • Position is used to set the starting coordinate of the path. M is computed: *
      *
    • position.x * containerWidth
    • *
    • position.y * containerHeight
    • *
    * Center of the container
     position: {
       *       mx: 0.5,
       *       my: 0.5,
       *     }
    * Upper left corner of the container *
     position: {
       *       mx: 0.0,
       *       my: 0.0,
       *     }
    *
  • *
*

* * @return {string} scaled path */ this.getScaledPath = function getScaledPath(pathId, param) { var rawPath = this.pathMap[pathId]; // positioning // compute the start point of the path var mx, my; if (param.abspos) { mx = param.abspos.x; my = param.abspos.y; } else { mx = param.containerWidth * param.position.mx; my = param.containerHeight * param.position.my; } var coordinates = {}; // map for the scaled coordinates if (param.position) { // path var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor; var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor; // Apply height ratio for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) { coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio; } // Apply width ratio for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) { coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio; } } // Apply value to raw path var path = format( rawPath.d, { mx: mx, my: my, e: coordinates } ); return path; }; } // helpers ////////////////////// // copied and adjusted from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js var tokenRegex = /\{([^{}]+)\}/g, objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties function replacer(all, key, obj) { var res = obj; key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) { name = name || quotedName; if (res) { if (name in res) { res = res[name]; } typeof res == 'function' && isFunc && (res = res()); } }); res = (res == null || res == obj ? all : res) + ''; return res; } function format(str, obj) { return String(str).replace(tokenRegex, function(all, key) { return replacer(all, key, obj); }); } ================================================ FILE: lib/draw/TextRenderer.js ================================================ import { assign } from 'min-dash'; import TextUtil from 'diagram-js/lib/util/Text'; var DEFAULT_FONT_SIZE = 12; var LINE_HEIGHT_RATIO = 1.2; var MIN_TEXT_ANNOTATION_HEIGHT = 30; /** * @typedef { { * fontFamily: string; * fontSize: number; * fontWeight: string; * lineHeight: number; * } } TextRendererStyle * * @typedef { { * defaultStyle?: Partial; * externalStyle?: Partial; * } } TextRendererConfig * * @typedef { import('diagram-js/lib/util/Text').TextLayoutConfig } TextLayoutConfig * * @typedef { import('diagram-js/lib/util/Types').Rect } Rect */ /** * Renders text and computes text bounding boxes. * * @param {TextRendererConfig} [config] */ export default function TextRenderer(config) { var defaultStyle = assign({ fontFamily: 'Arial, sans-serif', fontSize: DEFAULT_FONT_SIZE, fontWeight: 'normal', lineHeight: LINE_HEIGHT_RATIO }, config && config.defaultStyle || {}); var fontSize = parseInt(defaultStyle.fontSize, 10) - 1; var externalStyle = assign({}, defaultStyle, { fontSize: fontSize }, config && config.externalStyle || {}); var textUtil = new TextUtil({ style: defaultStyle }); /** * Get the new bounds of an externally rendered, * layouted label. * * @param {Rect} bounds * @param {string} text * * @return {Rect} */ this.getExternalLabelBounds = function(bounds, text) { var layoutedDimensions = textUtil.getDimensions(text, { box: { width: 90, height: 30 }, style: externalStyle }); // resize label shape to fit label text return { x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2), y: Math.round(bounds.y), width: Math.ceil(layoutedDimensions.width), height: Math.ceil(layoutedDimensions.height) }; }; /** * Get the new bounds of text annotation. * * @param {Rect} bounds * @param {string} text * * @return {Rect} */ this.getTextAnnotationBounds = function(bounds, text) { var layoutedDimensions = textUtil.getDimensions(text, { box: bounds, style: defaultStyle, align: 'left-top', padding: 5 }); return { x: bounds.x, y: bounds.y, width: bounds.width, height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height)) }; }; /** * Create a layouted text element. * * @param {string} text * @param {TextLayoutConfig} [options] * * @return {SVGElement} rendered text */ this.createText = function(text, options) { return textUtil.createText(text, options || {}); }; /** * Get default text style. */ this.getDefaultStyle = function() { return defaultStyle; }; /** * Get the external text style. */ this.getExternalStyle = function() { return externalStyle; }; } TextRenderer.$inject = [ 'config.textRenderer' ]; ================================================ FILE: lib/draw/TextRenderer.spec.ts ================================================ import TextRenderer from './TextRenderer'; new TextRenderer({ defaultStyle: { fontFamily: 'foo' } }); const textRenderer = new TextRenderer(); const externalLabelBounds = textRenderer.getExternalLabelBounds({ x: 100, y: 100, width: 100, height: 100 }, 'FOO\nBAR\n\BAZ'); const textAnnotationBounds = textRenderer.getTextAnnotationBounds({ x: 100, y: 100, width: 100, height: 100 }, 'FOO\nBAR\n\BAZ'); let text = textRenderer.createText('foo'); text = textRenderer.createText('foo', { align: 'center-top', padding: 10 }); const defaultStyle = textRenderer.getDefaultStyle(); const externalStyle = textRenderer.getExternalStyle(); ================================================ FILE: lib/draw/index.js ================================================ import BpmnRenderer from './BpmnRenderer'; import TextRenderer from './TextRenderer'; import PathMap from './PathMap'; export default { __init__: [ 'bpmnRenderer' ], bpmnRenderer: [ 'type', BpmnRenderer ], textRenderer: [ 'type', TextRenderer ], pathMap: [ 'type', PathMap ] }; ================================================ FILE: lib/features/align-elements/AlignElementsContextPadProvider.js ================================================ import { assign } from 'min-dash'; import ICONS from './AlignElementsIcons'; /** * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas * @typedef {import('diagram-js/lib/features/context-pad/ContextPad').default} ContextPad * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate * * @typedef {import('../../model/Types').Element} Element * @typedef {import('diagram-js/lib/features/context-pad/ContextPad').ContextPadEntries} ContextPadEntries * @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').default} ContextPadProvider */ var LOW_PRIORITY = 900; /** * A provider for the `Align elements` context pad entry. * * @implements {ContextPadProvider} * * @param {ContextPad} contextPad * @param {PopupMenu} popupMenu * @param {Translate} translate * @param {Canvas} canvas */ export default function AlignElementsContextPadProvider(contextPad, popupMenu, translate, canvas) { contextPad.registerProvider(LOW_PRIORITY, this); this._contextPad = contextPad; this._popupMenu = popupMenu; this._translate = translate; this._canvas = canvas; } AlignElementsContextPadProvider.$inject = [ 'contextPad', 'popupMenu', 'translate', 'canvas' ]; /** * @param {Element[]} elements * * @return {ContextPadEntries} */ AlignElementsContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) { var actions = {}; if (this._isAllowed(elements)) { assign(actions, this._getEntries(elements)); } return actions; }; AlignElementsContextPadProvider.prototype._isAllowed = function(elements) { return !this._popupMenu.isEmpty(elements, 'align-elements'); }; AlignElementsContextPadProvider.prototype._getEntries = function() { var self = this; return { 'align-elements': { group: 'align-elements', title: self._translate('Align elements'), html: `
${ICONS['align']}
`, action: { click: function(event, target) { var position = self._getMenuPosition(target); assign(position, { cursor: { x: event.x, y: event.y } }); self._popupMenu.open(target, 'align-elements', position); } } } }; }; AlignElementsContextPadProvider.prototype._getMenuPosition = function(elements) { var Y_OFFSET = 5; var pad = this._contextPad.getPad(elements).html; var padRect = pad.getBoundingClientRect(); var pos = { x: padRect.left, y: padRect.bottom + Y_OFFSET }; return pos; }; ================================================ FILE: lib/features/align-elements/AlignElementsIcons.js ================================================ /** * To change the icons, modify the SVGs in `./resources`, execute `npx svgo -f resources --datauri enc -o dist`, * and then replace respective icons with the optimized data URIs in `./dist`. */ var icons = { align: ` `, bottom: ` `, center: ` `, left: ` `, right: ` `, top: ` `, middle: ` ` }; export default icons; ================================================ FILE: lib/features/align-elements/AlignElementsMenuProvider.js ================================================ import ICONS from './AlignElementsIcons'; import { assign, forEach, } from 'min-dash'; /** * @typedef {import('diagram-js/lib/features/align-elements/AlignElements').default} AlignElements * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate * * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuEntries} PopupMenuEntries * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget */ var ALIGNMENT_OPTIONS = [ 'left', 'center', 'right', 'top', 'middle', 'bottom' ]; /** * A provider for the `Align elements` popup menu. * * @implements {PopupMenuProvider} * * @param {PopupMenu} popupMenu * @param {AlignElements} alignElements * @param {Translate} translate * @param {Rules} rules */ export default function AlignElementsMenuProvider(popupMenu, alignElements, translate, rules) { this._alignElements = alignElements; this._translate = translate; this._popupMenu = popupMenu; this._rules = rules; popupMenu.registerProvider('align-elements', this); } AlignElementsMenuProvider.$inject = [ 'popupMenu', 'alignElements', 'translate', 'rules' ]; /** * @param {PopupMenuTarget} target * * @return {PopupMenuEntries} */ AlignElementsMenuProvider.prototype.getPopupMenuEntries = function(target) { var entries = {}; if (this._isAllowed(target)) { assign(entries, this._getEntries(target)); } return entries; }; AlignElementsMenuProvider.prototype._isAllowed = function(target) { return this._rules.allowed('elements.align', { elements: target }); }; /** * @param {PopupMenuTarget} target * * @return {PopupMenuEntries} */ AlignElementsMenuProvider.prototype._getEntries = function(target) { var alignElements = this._alignElements, translate = this._translate, popupMenu = this._popupMenu; var entries = {}; forEach(ALIGNMENT_OPTIONS, function(alignment) { entries[ 'align-elements-' + alignment ] = { group: 'align', title: translate('Align elements ' + alignment), className: 'bjs-align-elements-menu-entry', imageHtml: ICONS[ alignment ], action: function() { alignElements.trigger(target, alignment); popupMenu.close(); } }; }); return entries; }; ================================================ FILE: lib/features/align-elements/BpmnAlignElements.js ================================================ import inherits from 'inherits-browser'; import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider'; import { getParents } from 'diagram-js/lib/util/Elements'; import { filter } from 'min-dash'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus */ /** * Rule provider for aligning BPMN elements. * * @param {EventBus} eventBus */ export default function BpmnAlignElements(eventBus) { RuleProvider.call(this, eventBus); } BpmnAlignElements.$inject = [ 'eventBus' ]; inherits(BpmnAlignElements, RuleProvider); BpmnAlignElements.prototype.init = function() { this.addRule('elements.align', function(context) { var elements = context.elements; // filter out elements which cannot be aligned var filteredElements = filter(elements, function(element) { return !(element.waypoints || element.host || element.labelTarget); }); // filter out elements which are children of any of the selected elements filteredElements = getParents(filteredElements); if (filteredElements.length < 2) { return false; } return filteredElements; }); }; ================================================ FILE: lib/features/align-elements/index.js ================================================ import AlignElementsModule from 'diagram-js/lib/features/align-elements'; import ContextPadModule from 'diagram-js/lib/features/context-pad'; import PopupMenuModule from 'diagram-js/lib/features/popup-menu'; import AlignElementsContextPadProvider from './AlignElementsContextPadProvider'; import AlignElementsMenuProvider from './AlignElementsMenuProvider'; import BpmnAlignElements from './BpmnAlignElements'; export default { __depends__: [ AlignElementsModule, ContextPadModule, PopupMenuModule ], __init__: [ 'alignElementsContextPadProvider', 'alignElementsMenuProvider', 'bpmnAlignElements' ], alignElementsContextPadProvider: [ 'type', AlignElementsContextPadProvider ], alignElementsMenuProvider: [ 'type', AlignElementsMenuProvider ], bpmnAlignElements: [ 'type', BpmnAlignElements ] }; ================================================ FILE: lib/features/append-preview/AppendPreview.js ================================================ import { assign, isNil } from 'min-dash'; const round = Math.round; /** * @typedef {import('diagram-js/lib/features/complex-preview/ComplexPreview').default} ComplexPreview * @typedef {import('diagram-js/lib/layout/ConnectionDocking').default} ConnectionDocking * @typedef {import('../modeling/ElementFactory').default} ElementFactory * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('../modeling/BpmnLayouter').default} BpmnLayouter * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules * * @typedef {import('../../model/Types').Shape} Shape */ /** * A preview for appending. * * @param {ComplexPreview} complexPreview * @param {ConnectionDocking} connectionDocking * @param {ElementFactory} elementFactory * @param {EventBus} eventBus * @param {BpmnLayouter} layouter * @param {Rules} rules */ export default function AppendPreview(complexPreview, connectionDocking, elementFactory, eventBus, layouter, rules) { this._complexPreview = complexPreview; this._connectionDocking = connectionDocking; this._elementFactory = elementFactory; this._eventBus = eventBus; this._layouter = layouter; this._rules = rules; } /** * Create a preview of appending a shape of the given type to the given source. * * @param {Shape} source * @param {string} type * @param {Partial} options */ AppendPreview.prototype.create = function(source, type, options) { const complexPreview = this._complexPreview, connectionDocking = this._connectionDocking, elementFactory = this._elementFactory, eventBus = this._eventBus, layouter = this._layouter, rules = this._rules; const shape = elementFactory.createShape(assign({ type }, options)); const position = eventBus.fire('autoPlace', { source, shape }); if (!position) { return; } assign(shape, { x: position.x - round(shape.width / 2), y: position.y - round(shape.height / 2) }); const connectionCreateAllowed = rules.allowed('connection.create', { source, target: shape, hints: { targetParent: source.parent } }); let connection = null; if (connectionCreateAllowed) { connection = elementFactory.createConnection(connectionCreateAllowed); connection.waypoints = layouter.layoutConnection(connection, { source, target: shape }); connection.waypoints = connectionDocking.getCroppedWaypoints(connection, source, shape); } complexPreview.create({ created: [ shape, connection ].filter((element) => !isNil(element)) }); }; AppendPreview.prototype.cleanUp = function() { this._complexPreview.cleanUp(); }; AppendPreview.$inject = [ 'complexPreview', 'connectionDocking', 'elementFactory', 'eventBus', 'layouter', 'rules' ]; ================================================ FILE: lib/features/append-preview/index.js ================================================ import AutoPlaceModule from '../auto-place'; import ComplexPreviewModule from 'diagram-js/lib/features/complex-preview'; import ModelingModule from '../modeling'; import AppendPreview from './AppendPreview'; export default { __depends__: [ AutoPlaceModule, ComplexPreviewModule, ModelingModule ], __init__: [ 'appendPreview' ], appendPreview: [ 'type', AppendPreview ] }; ================================================ FILE: lib/features/auto-place/BpmnAutoPlace.js ================================================ import { getNewShapePosition } from './BpmnAutoPlaceUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry */ /** * BPMN auto-place behavior. * * @param {EventBus} eventBus * @param {ElementRegistry} elementRegistry */ export default function AutoPlace(eventBus, elementRegistry) { eventBus.on('autoPlace', function(context) { var shape = context.shape, source = context.source; return getNewShapePosition(source, shape, elementRegistry); }); } AutoPlace.$inject = [ 'eventBus', 'elementRegistry' ]; ================================================ FILE: lib/features/auto-place/BpmnAutoPlaceUtil.js ================================================ import { is } from '../../util/ModelUtil'; import { isAny, isDirectionHorizontal } from '../modeling/util/ModelingUtil'; import { getMid, asTRBL, getOrientation } from 'diagram-js/lib/layout/LayoutUtil'; import { findFreePosition, generateGetNextPosition, getConnectedDistance } from 'diagram-js/lib/features/auto-place/AutoPlaceUtil'; import { isConnection } from 'diagram-js/lib/util/ModelUtil'; /** * @typedef {import('../../model/Types').Shape} Shape * * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry * @typedef {import('diagram-js/lib/util/Types').Point} Point * @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL */ /** * Get the position for given new target relative to the source it will be * connected to. * * @param {Shape} source * @param {Shape} element * @param {ElementRegistry} elementRegistry * * @return {Point} */ export function getNewShapePosition(source, element, elementRegistry) { var placeHorizontally = isDirectionHorizontal(source, elementRegistry); if (is(element, 'bpmn:TextAnnotation')) { return getTextAnnotationPosition(source, element, placeHorizontally); } if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) { return getDataElementPosition(source, element, placeHorizontally); } if (is(element, 'bpmn:FlowNode')) { return getFlowNodePosition(source, element, placeHorizontally); } } /** * Get the position for given new flow node. Try placing the flow node right/bottom of * the source. * * @param {Shape} source * @param {Shape} element * @param {boolean} placeHorizontally Whether to place the new element horizontally * * @return {Point} */ export function getFlowNodePosition(source, element, placeHorizontally) { var sourceTrbl = asTRBL(source); var sourceMid = getMid(source); var placement = placeHorizontally ? { directionHint: 'e', minDistance: 80, baseOrientation: 'left', boundaryOrientation: 'top', start: 'top', end: 'bottom' } : { directionHint: 's', minDistance: 90, baseOrientation: 'top', boundaryOrientation: 'left', start: 'left', end: 'right' }; var connectedDistance = getConnectedDistance(source, { filter: function(connection) { return is(connection, 'bpmn:SequenceFlow'); }, direction: placement.directionHint }); var margin = 30, minDistance = placement.minDistance, orientation = placement.baseOrientation; if (is(source, 'bpmn:BoundaryEvent')) { orientation = getOrientation(source, source.host, -25); if (orientation.indexOf(placement.boundaryOrientation) !== -1) { margin *= -1; } } var position = placeHorizontally ? { x: sourceTrbl.right + connectedDistance + element.width / 2, y: sourceMid.y + getDistance(orientation, minDistance, placement) } : { x: sourceMid.x + getDistance(orientation, minDistance, placement), y: sourceTrbl.bottom + connectedDistance + element.height / 2 }; var nextPosition = { margin: margin, minDistance: minDistance }; var nextPositionDirection = placeHorizontally ? { y: nextPosition } : { x: nextPosition }; return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection)); } /** * @param {DirectionTRBL} orientation * @param {number} minDistance * @param {{ start: DirectionTRBL, end: DirectionTRBL }} placement * * @return {number} */ function getDistance(orientation, minDistance, placement) { if (orientation.includes(placement.start)) { return -1 * minDistance; } else if (orientation.includes(placement.end)) { return minDistance; } else { return 0; } } /** * Get the position for given text annotation. Try placing the text annotation * top-right of the source (bottom-right in vertical layouts). * * @param {Shape} source * @param {Shape} element * @param {boolean} placeHorizontally Whether to place the new element horizontally * * @return {Point} */ export function getTextAnnotationPosition(source, element, placeHorizontally) { var sourceTrbl = asTRBL(source); var position = placeHorizontally ? { x: sourceTrbl.right + element.width / 2, y: sourceTrbl.top - 50 - element.height / 2 } : { x: sourceTrbl.right + 50 + element.width / 2, y: sourceTrbl.bottom + element.height / 2 }; if (isConnection(source)) { position = getMid(source); if (placeHorizontally) { position.x += 100; position.y -= 50; } else { position.x += 100; position.y += 50; } } var nextPosition = { margin: placeHorizontally ? -30 : 30, minDistance: 20 }; var nextPositionDirection = placeHorizontally ? { y: nextPosition } : { x: nextPosition }; return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection)); } /** * Get the position for given new data element. Try placing the data element * bottom-right of the source (bottom-left in vertical layouts). * * @param {Shape} source * @param {Shape} element * @param {boolean} placeHorizontally Whether to place the new element horizontally * * @return {Point} */ export function getDataElementPosition(source, element, placeHorizontally) { var sourceTrbl = asTRBL(source); var position = placeHorizontally ? { x: sourceTrbl.right - 10 + element.width / 2, y: sourceTrbl.bottom + 40 + element.width / 2 } : { x: sourceTrbl.left - 40 - element.width / 2, y: sourceTrbl.bottom - 10 + element.height / 2 }; var nextPosition = { margin: 30, minDistance: 30 }; var nextPositionDirection = placeHorizontally ? { x: nextPosition } : { y: nextPosition }; return findFreePosition(source, element, position, generateGetNextPosition(nextPositionDirection)); } ================================================ FILE: lib/features/auto-place/index.js ================================================ import AutoPlaceModule from 'diagram-js/lib/features/auto-place'; import BpmnAutoPlace from './BpmnAutoPlace'; export default { __depends__: [ AutoPlaceModule ], __init__: [ 'bpmnAutoPlace' ], bpmnAutoPlace: [ 'type', BpmnAutoPlace ] }; ================================================ FILE: lib/features/auto-resize/BpmnAutoResize.js ================================================ import AutoResize from 'diagram-js/lib/features/auto-resize/AutoResize'; import inherits from 'inherits-browser'; import { is } from '../../util/ModelUtil'; /** * @typedef {import('didi').Injector} Injector * * @typedef {import('../../model/Types').Shape} Shape * * @typedef {import('diagram-js/lib/util/Types').Rect} Rect */ /** * BPMN-specific resize behavior. * * @param {Injector} injector */ export default function BpmnAutoResize(injector) { injector.invoke(AutoResize, this); } BpmnAutoResize.$inject = [ 'injector' ]; inherits(BpmnAutoResize, AutoResize); /** * Perform BPMN-specific resizing of participants. * * @param {Shape} target * @param {Rect} newBounds * @param {Object} [hints] * @param {string} [hints.autoResize] */ BpmnAutoResize.prototype.resize = function(target, newBounds, hints) { if (is(target, 'bpmn:Participant')) { this._modeling.resizeLane(target, newBounds, null, hints); } else { this._modeling.resizeShape(target, newBounds, null, hints); } }; ================================================ FILE: lib/features/auto-resize/BpmnAutoResizeProvider.js ================================================ import { is } from '../../util/ModelUtil'; import { isLabel } from '../../util/LabelUtil'; import inherits from 'inherits-browser'; import { forEach } from 'min-dash'; import AutoResizeProvider from 'diagram-js/lib/features/auto-resize/AutoResizeProvider'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('../modeling/Modeling').default} Modeling * * @typedef {import('../../model/Types').Shape} Shape */ /** * BPMN-specific provider for automatic resizung. * * @param {EventBus} eventBus * @param {Modeling} modeling */ export default function BpmnAutoResizeProvider(eventBus, modeling) { AutoResizeProvider.call(this, eventBus); this._modeling = modeling; } inherits(BpmnAutoResizeProvider, AutoResizeProvider); BpmnAutoResizeProvider.$inject = [ 'eventBus', 'modeling' ]; /** * BPMN-specific check whether given elements can be resized. * * @param {Shape[]} elements * @param {Shape} target * * @return {boolean} */ BpmnAutoResizeProvider.prototype.canResize = function(elements, target) { // do not resize plane elements: // root elements, collapsed sub-processes if (is(target.di, 'bpmndi:BPMNPlane')) { return false; } if (!is(target, 'bpmn:Participant') && !is(target, 'bpmn:Lane') && !(is(target, 'bpmn:SubProcess'))) { return false; } var canResize = true; forEach(elements, function(element) { if (is(element, 'bpmn:Lane') || isLabel(element)) { canResize = false; return; } }); return canResize; }; ================================================ FILE: lib/features/auto-resize/index.js ================================================ import BpmnAutoResize from './BpmnAutoResize'; import BpmnAutoResizeProvider from './BpmnAutoResizeProvider'; export default { __init__: [ 'bpmnAutoResize', 'bpmnAutoResizeProvider' ], bpmnAutoResize: [ 'type', BpmnAutoResize ], bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ] }; ================================================ FILE: lib/features/context-pad/ContextPadProvider.js ================================================ import { assign, forEach, isArray, every } from 'min-dash'; import { is } from '../../util/ModelUtil'; import { isExpanded, isHorizontal, isEventSubProcess } from '../../util/DiUtil'; import { isAny } from '../modeling/util/ModelingUtil'; import { getChildLanes } from '../modeling/util/LaneUtil'; import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse'; /** * @typedef {import('didi').Injector} Injector * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/features/context-pad/ContextPad').default} ContextPad * @typedef {import('../modeling/Modeling').default} Modeling * @typedef {import('../modeling/ElementFactory').default} ElementFactory * @typedef {import('../append-preview/AppendPreview').default} AppendPreview * @typedef {import('diagram-js/lib/features/connect/Connect').default} Connect * @typedef {import('diagram-js/lib/features/create/Create').default} Create * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu * @typedef {import('diagram-js/lib/features/canvas/Canvas').default} Canvas * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate * * @typedef {import('../../model/Types').Element} Element * @typedef {import('../../model/Types').ModdleElement} ModdleElement * * @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').default} BaseContextPadProvider * @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').ContextPadEntries} ContextPadEntries * @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').ContextPadEntry} ContextPadEntry * * @typedef { { autoPlace?: boolean; } } ContextPadConfig */ /** * BPMN-specific context pad provider. * * @implements {BaseContextPadProvider} * * @param {ContextPadConfig} config * @param {Injector} injector * @param {EventBus} eventBus * @param {ContextPad} contextPad * @param {Modeling} modeling * @param {ElementFactory} elementFactory * @param {Connect} connect * @param {Create} create * @param {PopupMenu} popupMenu * @param {Canvas} canvas * @param {Rules} rules * @param {Translate} translate * @param {AppendPreview} appendPreview */ export default function ContextPadProvider( config, injector, eventBus, contextPad, modeling, elementFactory, connect, create, popupMenu, canvas, rules, translate, appendPreview) { config = config || {}; contextPad.registerProvider(this); this._contextPad = contextPad; this._modeling = modeling; this._elementFactory = elementFactory; this._connect = connect; this._create = create; this._popupMenu = popupMenu; this._canvas = canvas; this._rules = rules; this._translate = translate; this._eventBus = eventBus; this._appendPreview = appendPreview; if (config.autoPlace !== false) { this._autoPlace = injector.get('autoPlace', false); } eventBus.on('create.end', 250, function(event) { var context = event.context, shape = context.shape; if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) { return; } var entries = contextPad.getEntries(shape); if (entries.replace) { entries.replace.action.click(event, shape); } }); eventBus.on('contextPad.close', function() { appendPreview.cleanUp(); }); } ContextPadProvider.$inject = [ 'config.contextPad', 'injector', 'eventBus', 'contextPad', 'modeling', 'elementFactory', 'connect', 'create', 'popupMenu', 'canvas', 'rules', 'translate', 'appendPreview' ]; /** * @param {Element[]} elements * * @return {ContextPadEntries} */ ContextPadProvider.prototype.getMultiElementContextPadEntries = function(elements) { var modeling = this._modeling; var actions = {}; if (this._isDeleteAllowed(elements)) { assign(actions, { 'delete': { group: 'edit', className: 'bpmn-icon-trash', title: this._translate('Delete'), action: { click: function(event, elements) { modeling.removeElements(elements.slice()); } } } }); } return actions; }; /** * @param {Element[]} elements * * @return {boolean} */ ContextPadProvider.prototype._isDeleteAllowed = function(elements) { var baseAllowed = this._rules.allowed('elements.delete', { elements: elements }); if (isArray(baseAllowed)) { return every(elements, el => baseAllowed.includes(el)); } return baseAllowed; }; /** * @param {Element} element * * @return {ContextPadEntries} */ ContextPadProvider.prototype.getContextPadEntries = function(element) { var contextPad = this._contextPad, modeling = this._modeling, elementFactory = this._elementFactory, connect = this._connect, create = this._create, popupMenu = this._popupMenu, autoPlace = this._autoPlace, translate = this._translate, appendPreview = this._appendPreview; var actions = {}; if (element.type === 'label') { if (this._isDeleteAllowed([ element ])) { assign(actions, deleteAction()); } return actions; } var businessObject = element.businessObject; function startConnect(event, element) { connect.start(event, element); } function removeElement(e, element) { modeling.removeElements([ element ]); } function deleteAction() { return { 'delete': { group: 'edit', className: 'bpmn-icon-trash', title: translate('Delete'), action: { click: removeElement } } }; } function getReplaceMenuPosition(element) { var Y_OFFSET = 5; var pad = contextPad.getPad(element).html; var padRect = pad.getBoundingClientRect(); var pos = { x: padRect.left, y: padRect.bottom + Y_OFFSET }; return pos; } /** * Create an append action. * * @param {string} type * @param {string} className * @param {string} title * @param {Object} [options] * * @return {ContextPadEntry} */ function appendAction(type, className, title, options) { function appendStart(event, element) { var shape = elementFactory.createShape(assign({ type: type }, options)); create.start(event, shape, { source: element }); } var append = autoPlace ? function(_, element) { var shape = elementFactory.createShape(assign({ type: type }, options)); autoPlace.append(element, shape); } : appendStart; var previewAppend = autoPlace ? function(_, element) { // mouseover appendPreview.create(element, type, options); return () => { // mouseout appendPreview.cleanUp(); }; } : null; return { group: 'model', className: className, title: title, action: { dragstart: appendStart, click: append, hover: previewAppend } }; } function splitLaneHandler(count) { return function(_, element) { // actual split modeling.splitLane(element, count); // refresh context pad after split to // get rid of split icons contextPad.open(element, true); }; } if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(element)) { var childLanes = getChildLanes(element); assign(actions, { 'lane-insert-above': { group: 'lane-insert-above', className: 'bpmn-icon-lane-insert-above', title: translate('Add lane above'), action: { click: function(event, element) { modeling.addLane(element, 'top'); } } } }); if (childLanes.length < 2) { if (isHorizontal(element) ? element.height >= 120 : element.width >= 120) { assign(actions, { 'lane-divide-two': { group: 'lane-divide', className: 'bpmn-icon-lane-divide-two', title: translate('Divide into two lanes'), action: { click: splitLaneHandler(2) } } }); } if (isHorizontal(element) ? element.height >= 180 : element.width >= 180) { assign(actions, { 'lane-divide-three': { group: 'lane-divide', className: 'bpmn-icon-lane-divide-three', title: translate('Divide into three lanes'), action: { click: splitLaneHandler(3) } } }); } } assign(actions, { 'lane-insert-below': { group: 'lane-insert-below', className: 'bpmn-icon-lane-insert-below', title: translate('Add lane below'), action: { click: function(event, element) { modeling.addLane(element, 'bottom'); } } } }); } if (is(businessObject, 'bpmn:FlowNode')) { if (is(businessObject, 'bpmn:EventBasedGateway')) { assign(actions, { 'append.receive-task': appendAction( 'bpmn:ReceiveTask', 'bpmn-icon-receive-task', translate('Append receive task') ), 'append.message-intermediate-event': appendAction( 'bpmn:IntermediateCatchEvent', 'bpmn-icon-intermediate-event-catch-message', translate('Append message intermediate catch event'), { eventDefinitionType: 'bpmn:MessageEventDefinition' } ), 'append.timer-intermediate-event': appendAction( 'bpmn:IntermediateCatchEvent', 'bpmn-icon-intermediate-event-catch-timer', translate('Append timer intermediate catch event'), { eventDefinitionType: 'bpmn:TimerEventDefinition' } ), 'append.condition-intermediate-event': appendAction( 'bpmn:IntermediateCatchEvent', 'bpmn-icon-intermediate-event-catch-condition', translate('Append conditional intermediate catch event'), { eventDefinitionType: 'bpmn:ConditionalEventDefinition' } ), 'append.signal-intermediate-event': appendAction( 'bpmn:IntermediateCatchEvent', 'bpmn-icon-intermediate-event-catch-signal', translate('Append signal intermediate catch event'), { eventDefinitionType: 'bpmn:SignalEventDefinition' } ) }); } else if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) { assign(actions, { 'append.compensation-activity': appendAction( 'bpmn:Task', 'bpmn-icon-task', translate('Append compensation activity'), { isForCompensation: true } ) }); } else if (!is(businessObject, 'bpmn:EndEvent') && !businessObject.isForCompensation && !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') && !isEventSubProcess(businessObject)) { assign(actions, { 'append.end-event': appendAction( 'bpmn:EndEvent', 'bpmn-icon-end-event-none', translate('Append end event') ), 'append.gateway': appendAction( 'bpmn:ExclusiveGateway', 'bpmn-icon-gateway-none', translate('Append gateway') ), 'append.append-task': appendAction( 'bpmn:Task', 'bpmn-icon-task', translate('Append task') ), 'append.intermediate-event': appendAction( 'bpmn:IntermediateThrowEvent', 'bpmn-icon-intermediate-event-none', translate('Append intermediate/boundary event') ) }); } } if (!popupMenu.isEmpty(element, 'bpmn-replace')) { // Replace menu entry assign(actions, { 'replace': { group: 'edit', className: 'bpmn-icon-screw-wrench', title: translate('Change element'), action: { click: function(event, element) { var position = assign(getReplaceMenuPosition(element), { cursor: { x: event.x, y: event.y } }); popupMenu.open(element, 'bpmn-replace', position, { title: translate('Change element'), width: 300, search: true }); } } } }); } if (is(businessObject, 'bpmn:SequenceFlow')) { assign(actions, { 'append.text-annotation': appendAction( 'bpmn:TextAnnotation', 'bpmn-icon-text-annotation', translate('Add text annotation') ) }); } if (is(businessObject, 'bpmn:MessageFlow')) { assign(actions, { 'append.text-annotation': appendAction( 'bpmn:TextAnnotation', 'bpmn-icon-text-annotation', translate('Add text annotation') ) }); } if ( isAny(businessObject, [ 'bpmn:FlowNode', 'bpmn:InteractionNode', 'bpmn:DataObjectReference', 'bpmn:DataStoreReference', ]) ) { assign(actions, { 'append.text-annotation': appendAction( 'bpmn:TextAnnotation', 'bpmn-icon-text-annotation', translate('Add text annotation') ), 'connect': { group: 'connect', className: 'bpmn-icon-connection-multi', title: translate('Connect to other element'), action: { click: startConnect, dragstart: startConnect, }, }, }); } if (is(businessObject, 'bpmn:TextAnnotation')) { assign(actions, { 'connect': { group: 'connect', className: 'bpmn-icon-connection-multi', title: translate('Connect using association'), action: { click: startConnect, dragstart: startConnect, }, }, }); } if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) { assign(actions, { 'connect': { group: 'connect', className: 'bpmn-icon-connection-multi', title: translate('Connect using data input association'), action: { click: startConnect, dragstart: startConnect } } }); } if (is(businessObject, 'bpmn:Group')) { assign(actions, { 'append.text-annotation': appendAction( 'bpmn:TextAnnotation', 'bpmn-icon-text-annotation', translate('Add text annotation') ) }); } if (this._isDeleteAllowed([ element ])) { assign(actions, deleteAction()); } return actions; }; // helpers ///////// /** * @param {ModdleElement} businessObject * @param {string} type * @param {string} eventDefinitionType * * @return {boolean} */ function isEventType(businessObject, type, eventDefinitionType) { var isType = businessObject.$instanceOf(type); var isDefinition = false; var definitions = businessObject.eventDefinitions || []; forEach(definitions, function(def) { if (def.$type === eventDefinitionType) { isDefinition = true; } }); return isType && isDefinition; } ================================================ FILE: lib/features/context-pad/index.js ================================================ import AppendPreviewModule from '../append-preview'; import DirectEditingModule from 'diagram-js-direct-editing'; import ContextPadModule from 'diagram-js/lib/features/context-pad'; import SelectionModule from 'diagram-js/lib/features/selection'; import ConnectModule from 'diagram-js/lib/features/connect'; import CreateModule from 'diagram-js/lib/features/create'; import PopupMenuModule from '../popup-menu'; import ContextPadProvider from './ContextPadProvider'; export default { __depends__: [ AppendPreviewModule, DirectEditingModule, ContextPadModule, SelectionModule, ConnectModule, CreateModule, PopupMenuModule ], __init__: [ 'contextPadProvider' ], contextPadProvider: [ 'type', ContextPadProvider ] }; ================================================ FILE: lib/features/copy-paste/BpmnCopyPaste.js ================================================ import { getBusinessObject, getDi, is } from '../../util/ModelUtil'; import { collectElementsAnnotations } from '../../util/AnnotationUtil'; import { forEach, isArray, isUndefined, omit, reduce } from 'min-dash'; import { isLabel } from '../../util/LabelUtil'; /** * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('./ModdleCopy').default} ModdleCopy */ function copyProperties(source, target, properties) { if (!isArray(properties)) { properties = [ properties ]; } forEach(properties, function(property) { if (!isUndefined(source[property])) { target[property] = source[property]; } }); } var LOW_PRIORITY = 750; /** * BPMN-specific copy & paste. * * @param {BpmnFactory} bpmnFactory * @param {EventBus} eventBus * @param {ModdleCopy} moddleCopy */ export default function BpmnCopyPaste(bpmnFactory, eventBus, moddleCopy) { function copy(bo, clone) { var targetBo = bpmnFactory.create(bo.$type); return moddleCopy.copyElement(bo, targetBo, null, clone); } eventBus.on('copyPaste.copyElement', LOW_PRIORITY, function(context) { var descriptor = context.descriptor, element = context.element, businessObject = getBusinessObject(element); // do not copy business object + di for labels; // will be pulled from the referenced label target if (isLabel(element)) { return descriptor; } var businessObjectCopy = descriptor.businessObject = copy(businessObject, true); var diCopy = descriptor.di = copy(getDi(element), true); diCopy.bpmnElement = businessObjectCopy; copyProperties(businessObjectCopy, descriptor, 'name'); copyProperties(diCopy, descriptor, 'isExpanded'); // default sequence flow if (businessObject.default) { descriptor.default = businessObject.default.id; } }); var referencesKey = '-bpmn-js-refs'; function getReferences(cache) { return (cache[referencesKey] = cache[referencesKey] || {}); } function setReferences(cache, references) { cache[referencesKey] = references; } function resolveReferences(descriptor, cache, references) { var businessObject = getBusinessObject(descriptor); // default sequence flows if (descriptor.default) { // relationship cannot be resolved immediately references[ descriptor.default ] = { element: businessObject, property: 'default' }; } // boundary events if (descriptor.host) { // relationship can be resolved immediately getBusinessObject(descriptor).attachedToRef = getBusinessObject(cache[ descriptor.host ]); } return omit(references, reduce(references, function(array, reference, key) { var element = reference.element, property = reference.property; if (key === descriptor.id) { element.set(property, businessObject); array.push(descriptor.id); } return array; }, [])); } eventBus.on('copyPaste.pasteElement', function(context) { var cache = context.cache, descriptor = context.descriptor, businessObject = descriptor.businessObject, di = descriptor.di; // wire existing di + businessObject for external label if (isLabel(descriptor)) { descriptor.businessObject = getBusinessObject(cache[ descriptor.labelTarget ]); descriptor.di = getDi(cache[ descriptor.labelTarget ]); return; } businessObject = descriptor.businessObject = copy(businessObject); di = descriptor.di = copy(di); di.bpmnElement = businessObject; copyProperties(descriptor, businessObject, [ 'isExpanded', 'name' ]); descriptor.type = businessObject.$type; }); // copy + paste processRef with participant eventBus.on('copyPaste.copyElement', LOW_PRIORITY, function(context) { var descriptor = context.descriptor, element = context.element; if (!is(element, 'bpmn:Participant')) { return; } var participantBo = getBusinessObject(element); if (participantBo.processRef) { descriptor.processRef = copy(participantBo.processRef, true); } }); eventBus.on('copyPaste.pasteElement', function(context) { var descriptor = context.descriptor, processRef = descriptor.processRef; if (processRef) { descriptor.processRef = copy(processRef); } }); eventBus.on('copyPaste.createTree', function(context) { var element = context.element, children = context.children; if (!is(element, 'bpmn:SubProcess')) { return; } // add TextAnnotations to copy the closure, // since by default they are children of a global process, not subprocess forEach(collectElementsAnnotations(children), (entry) => { children.push(entry.annotation); }); }); // resolve references eventBus.on('copyPaste.pasteElement', LOW_PRIORITY, function(context) { var cache = context.cache, descriptor = context.descriptor; // resolve references e.g. default sequence flow setReferences( cache, resolveReferences(descriptor, cache, getReferences(cache)) ); }); } BpmnCopyPaste.$inject = [ 'bpmnFactory', 'eventBus', 'moddleCopy' ]; ================================================ FILE: lib/features/copy-paste/ModdleCopy.js ================================================ import { find, forEach, has, isArray, isDefined, isObject, matchPattern, reduce, sortBy } from 'min-dash'; import { is } from '../../util/ModelUtil'; const DISALLOWED_PROPERTIES = [ 'artifacts', 'dataInputAssociations', 'dataOutputAssociations', 'default', 'flowElements', 'lanes', 'incoming', 'outgoing', 'categoryValue' ]; const ALLOWED_REFERENCES = [ 'errorRef', 'escalationRef', 'messageRef', 'signalRef', 'dataObjectRef' ]; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory * @typedef {import('../../model/Types').Moddle} Moddle * * @typedef {import('../../model/Types').ModdleElement} ModdleElement */ /** * Utility for copying model properties from source element to target element. * * @param {EventBus} eventBus * @param {BpmnFactory} bpmnFactory * @param {Moddle} moddle */ export default function ModdleCopy(eventBus, bpmnFactory, moddle) { this._bpmnFactory = bpmnFactory; this._eventBus = eventBus; this._moddle = moddle; // copy extension elements last eventBus.on('moddleCopy.canCopyProperties', (context) => { const { propertyNames } = context; if (!propertyNames || !propertyNames.length) { return; } return sortBy(propertyNames, (propertyName) => { return propertyName === 'extensionElements'; }); }); // default check whether property can be copied eventBus.on('moddleCopy.canCopyProperty', (context) => { const { parent, property, propertyName } = context; const parentDescriptor = isObject(parent) && parent.$descriptor; if (propertyName && ALLOWED_REFERENCES.includes(propertyName)) { // allow copying reference return property; } if (propertyName && DISALLOWED_PROPERTIES.includes(propertyName)) { // disallow copying property return false; } if (propertyName && parentDescriptor && !find(parentDescriptor.properties, matchPattern({ name: propertyName }))) { // disallow copying property return false; } }); // do NOT allow to copy empty extension elements eventBus.on('moddleCopy.canSetCopiedProperty', (context) => { const { property } = context; if (is(property, 'bpmn:ExtensionElements') && (!property.values || !property.values.length)) { // disallow setting copied property return false; } }); } ModdleCopy.$inject = [ 'eventBus', 'bpmnFactory', 'moddle' ]; /** * Copy model properties of source element to target element. * * @param {ModdleElement} sourceElement * @param {ModdleElement} targetElement * @param {string[]} [propertyNames] * @param {boolean} [clone=false] * * @return {ModdleElement} */ ModdleCopy.prototype.copyElement = function(sourceElement, targetElement, propertyNames, clone = false) { if (propertyNames && !isArray(propertyNames)) { propertyNames = [ propertyNames ]; } propertyNames = propertyNames || getPropertyNames(sourceElement.$descriptor); const canCopyProperties = this._eventBus.fire('moddleCopy.canCopyProperties', { propertyNames: propertyNames, sourceElement: sourceElement, targetElement: targetElement, clone: clone }); if (canCopyProperties === false) { return targetElement; } if (isArray(canCopyProperties)) { propertyNames = canCopyProperties; } // copy properties forEach(propertyNames, (propertyName) => { let sourceProperty; if (has(sourceElement, propertyName)) { sourceProperty = sourceElement.get(propertyName); } const copiedProperty = this.copyProperty(sourceProperty, targetElement, propertyName, clone); if (!isDefined(copiedProperty)) { return; } const canSetProperty = this._eventBus.fire('moddleCopy.canSetCopiedProperty', { parent: targetElement, property: copiedProperty, propertyName: propertyName }); if (canSetProperty === false) { return; } // TODO(nikku): unclaim old IDs if ID property is copied over // this._moddle.getPropertyDescriptor(parent, propertyName) targetElement.set(propertyName, copiedProperty); }); return targetElement; }; /** * Copy model property. * * @param {any} property * @param {ModdleElement} parent * @param {string} propertyName * @param {boolean} [clone=false] * * @return {any} */ ModdleCopy.prototype.copyProperty = function(property, parent, propertyName, clone = false) { // allow others to copy property let copiedProperty = this._eventBus.fire('moddleCopy.canCopyProperty', { parent: parent, property: property, propertyName: propertyName, clone: clone }); // return if copying is NOT allowed if (copiedProperty === false) { return; } if (copiedProperty) { if (isObject(copiedProperty) && copiedProperty.$type && !copiedProperty.$parent) { copiedProperty.$parent = parent; } return copiedProperty; } const propertyDescriptor = this._moddle.getPropertyDescriptor(parent, propertyName); // do NOT copy references if (propertyDescriptor.isReference) { return; } // copy id if (propertyDescriptor.isId) { return property && this._copyId(property, parent, clone); } // copy arrays if (isArray(property)) { return reduce(property, (childProperties, childProperty) => { // recursion const copiedProperty = this.copyProperty(childProperty, parent, propertyName, clone); // copying might NOT be allowed if (copiedProperty) { return childProperties.concat(copiedProperty); } return childProperties; }, []); } // copy model elements if (isObject(property) && property.$type) { if (this._moddle.getElementDescriptor(property).isGeneric) { return; } copiedProperty = this._bpmnFactory.create(property.$type); copiedProperty.$parent = parent; // recursion copiedProperty = this.copyElement(property, copiedProperty, null, clone); return copiedProperty; } // copy primitive properties return property; }; ModdleCopy.prototype._copyId = function(id, element, clone = false) { if (clone) { return id; } // disallow if already taken if (this._moddle.ids.assigned(id)) { return; } else { this._moddle.ids.claim(id, element); return id; } }; // helpers ////////// export function getPropertyNames(descriptor, keepDefaultProperties) { return reduce(descriptor.properties, (properties, property) => { if (keepDefaultProperties && property.default) { return properties; } return properties.concat(property.name); }, []); } ================================================ FILE: lib/features/copy-paste/index.js ================================================ import CopyPasteModule from 'diagram-js/lib/features/copy-paste'; import BpmnCopyPaste from './BpmnCopyPaste'; import ModdleCopy from './ModdleCopy'; export default { __depends__: [ CopyPasteModule ], __init__: [ 'bpmnCopyPaste', 'moddleCopy' ], bpmnCopyPaste: [ 'type', BpmnCopyPaste ], moddleCopy: [ 'type', ModdleCopy ] }; ================================================ FILE: lib/features/di-ordering/BpmnDiOrdering.js ================================================ import { getDi } from '../../util/ModelUtil'; import { filter, forEach, map } from 'min-dash'; import { selfAndAllChildren } from 'diagram-js/lib/util/Elements'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas */ var HIGH_PRIORITY = 2000; /** * @param {EventBus} eventBus * @param {Canvas} canvas */ export default function BpmnDiOrdering(eventBus, canvas) { eventBus.on('saveXML.start', HIGH_PRIORITY, orderDi); function orderDi() { var rootElements = canvas.getRootElements(); forEach(rootElements, function(root) { var rootDi = getDi(root), elements, diElements; elements = selfAndAllChildren([ root ], false); // only bpmndi:Shape and bpmndi:Edge can be direct children of bpmndi:Plane elements = filter(elements, function(element) { return element !== root && !element.labelTarget; }); diElements = map(elements, getDi); rootDi.set('planeElement', diElements); }); } } BpmnDiOrdering.$inject = [ 'eventBus', 'canvas' ]; ================================================ FILE: lib/features/di-ordering/index.js ================================================ import BpmnDiOrdering from '../di-ordering/BpmnDiOrdering'; export default { __init__: [ 'bpmnDiOrdering' ], bpmnDiOrdering: [ 'type', BpmnDiOrdering ] }; ================================================ FILE: lib/features/distribute-elements/BpmnDistributeElements.js ================================================ import inherits from 'inherits-browser'; import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider'; import { getParents } from 'diagram-js/lib/util/Elements'; import { filter } from 'min-dash'; import { isAny } from '../modeling/util/ModelingUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus */ /** * Registers element exclude filters for elements that currently do not support * distribution. * * @param {EventBus} eventBus */ export default function BpmnDistributeElements(eventBus) { RuleProvider.call(this, eventBus); } BpmnDistributeElements.$inject = [ 'eventBus' ]; inherits(BpmnDistributeElements, RuleProvider); BpmnDistributeElements.prototype.init = function() { this.addRule('elements.distribute', function(context) { var elements = context.elements; elements = filter(elements, function(element) { var cannotDistribute = isAny(element, [ 'bpmn:Association', 'bpmn:BoundaryEvent', 'bpmn:DataInputAssociation', 'bpmn:DataOutputAssociation', 'bpmn:Lane', 'bpmn:MessageFlow', 'bpmn:SequenceFlow', 'bpmn:TextAnnotation' ]); return !(element.labelTarget || cannotDistribute); }); // filter out elements which are children of any of the selected elements elements = getParents(elements); if (elements.length < 3) { return false; } return elements; }); }; ================================================ FILE: lib/features/distribute-elements/DistributeElementsIcons.js ================================================ /** * To change the icons, modify the SVGs in `./resources`, execute `npx svgo -f resources --datauri enc -o dist`, * and then replace respective icons with the optimized data URIs in `./dist`. */ var icons = { horizontal: ` `, vertical: ` ` }; export default icons; ================================================ FILE: lib/features/distribute-elements/DistributeElementsMenuProvider.js ================================================ import ICONS from './DistributeElementsIcons'; import { assign } from 'min-dash'; /** * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu * @typedef {import('./BpmnDistributeElements').default} DistributeElements * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate * @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules * * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').PopupMenuEntries} PopupMenuEntries * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenuProvider').default} PopupMenuProvider * @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').PopupMenuTarget} PopupMenuTarget */ var LOW_PRIORITY = 900; /** * A provider for the distribute elements popup menu. * * @implements {PopupMenuProvider} * * @param {PopupMenu} popupMenu * @param {DistributeElements} distributeElements * @param {Translate} translate * @param {Rules} rules */ export default function DistributeElementsMenuProvider( popupMenu, distributeElements, translate, rules) { this._distributeElements = distributeElements; this._translate = translate; this._popupMenu = popupMenu; this._rules = rules; popupMenu.registerProvider('align-elements', LOW_PRIORITY, this); } DistributeElementsMenuProvider.$inject = [ 'popupMenu', 'distributeElements', 'translate', 'rules' ]; /** * @param {PopupMenuTarget} target * * @return {PopupMenuEntries} */ DistributeElementsMenuProvider.prototype.getPopupMenuEntries = function(target) { var entries = {}; if (this._isAllowed(target)) { assign(entries, this._getEntries(target)); } return entries; }; DistributeElementsMenuProvider.prototype._isAllowed = function(elements) { return this._rules.allowed('elements.distribute', { elements: elements }); }; DistributeElementsMenuProvider.prototype._getEntries = function(elements) { var distributeElements = this._distributeElements, translate = this._translate, popupMenu = this._popupMenu; var entries = { 'distribute-elements-horizontal': { group: 'distribute', title: translate('Distribute elements horizontally'), className: 'bjs-align-elements-menu-entry', imageHtml: ICONS['horizontal'], action: function(event, entry) { distributeElements.trigger(elements, 'horizontal'); popupMenu.close(); } }, 'distribute-elements-vertical': { group: 'distribute', title: translate('Distribute elements vertically'), imageHtml: ICONS['vertical'], action: function(event, entry) { distributeElements.trigger(elements, 'vertical'); popupMenu.close(); } }, }; return entries; }; ================================================ FILE: lib/features/distribute-elements/index.js ================================================ import DistributeElementsModule from 'diagram-js/lib/features/distribute-elements'; import PopupMenuModule from 'diagram-js/lib/features/popup-menu'; import BpmnDistributeElements from './BpmnDistributeElements'; import DistributeElementsMenuProvider from './DistributeElementsMenuProvider'; export default { __depends__: [ PopupMenuModule, DistributeElementsModule ], __init__: [ 'bpmnDistributeElements', 'distributeElementsMenuProvider' ], bpmnDistributeElements: [ 'type', BpmnDistributeElements ], distributeElementsMenuProvider: [ 'type', DistributeElementsMenuProvider ] }; ================================================ FILE: lib/features/drilldown/DrilldownBreadcrumbs.js ================================================ import { domify, classes } from 'min-dom'; import { find } from 'min-dash'; import { escapeHTML } from 'diagram-js/lib/util/EscapeUtil'; import { getBusinessObject, is } from '../../util/ModelUtil'; import { getPlaneIdFromShape } from '../../util/DrilldownUtil'; /** * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * * @typedef {import('../../model/Types').Element} Element * @typedef {import('../../model/Types').Shape} Shape */ var OPEN_CLASS = 'bjs-breadcrumbs-shown'; /** * Adds overlays that allow switching planes on collapsed subprocesses. * * @param {EventBus} eventBus * @param {ElementRegistry} elementRegistry * @param {Canvas} canvas */ export default function DrilldownBreadcrumbs(eventBus, elementRegistry, canvas) { var breadcrumbs = domify('
    '); var container = canvas.getContainer(); var containerClasses = classes(container); container.appendChild(breadcrumbs); var businessObjectParents = []; // update breadcrumbs if name or ID of the primary shape changes eventBus.on('element.changed', function(event) { var shape = event.element, businessObject = getBusinessObject(shape); var isPresent = find(businessObjectParents, function(element) { return element === businessObject; }); if (!isPresent) { return; } updateBreadcrumbs(); }); /** * Updates the displayed breadcrumbs. If no element is provided, only the * labels are updated. * * @param {Element} [element] */ function updateBreadcrumbs(element) { if (element) { businessObjectParents = getBusinessObjectParentChain(element); } var path = businessObjectParents.flatMap(function(parent) { var parentPlane = canvas.findRoot(getPlaneIdFromShape(parent)) || canvas.findRoot(parent.id); // when the root is a collaboration, the process does not have a // corresponding element in the elementRegisty. Instead, we search // for the corresponding participant if (!parentPlane && is(parent, 'bpmn:Process')) { var participant = elementRegistry.find(function(element) { var businessObject = getBusinessObject(element); return businessObject && businessObject.get('processRef') === parent; }); parentPlane = participant && canvas.findRoot(participant.id); } if (!parentPlane) { return []; } var title = escapeHTML(parent.name || parent.id); var link = domify('
  • ' + title + '
  • '); link.addEventListener('click', function() { canvas.setRootElement(parentPlane); }); return link; }); breadcrumbs.innerHTML = ''; // show breadcrumbs and expose state to .djs-container var visible = path.length > 1; containerClasses.toggle(OPEN_CLASS, visible); path.forEach(function(element) { breadcrumbs.appendChild(element); }); } eventBus.on('root.set', function(event) { updateBreadcrumbs(event.element); }); } DrilldownBreadcrumbs.$inject = [ 'eventBus', 'elementRegistry', 'canvas' ]; // helpers ////////// /** * Returns the parents for the element using the business object chain, * starting with the root element. * * @param {Shape} child * * @return {Shape} */ function getBusinessObjectParentChain(child) { var businessObject = getBusinessObject(child); var parents = []; for (var element = businessObject; element; element = element.$parent) { if (is(element, 'bpmn:SubProcess') || is(element, 'bpmn:Process')) { parents.push(element); } } return parents.reverse(); } ================================================ FILE: lib/features/drilldown/DrilldownCentering.js ================================================ import { is } from '../../util/ModelUtil'; /** * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus */ /** * Move collapsed subprocesses into view when drilling down. * * Zoom and scroll are saved in a session. * * @param {EventBus} eventBus * @param {Canvas} canvas */ export default function DrilldownCentering(eventBus, canvas) { var currentRoot = null; var positionMap = new Map(); eventBus.on('root.set', function(event) { var newRoot = event.element; var currentViewbox = canvas.viewbox(); var storedViewbox = positionMap.get(newRoot); positionMap.set(currentRoot, { x: currentViewbox.x, y: currentViewbox.y, zoom: currentViewbox.scale }); currentRoot = newRoot; // Keep viewbox when replacing root elements if (!is(newRoot, 'bpmn:SubProcess') && !storedViewbox) { return; } storedViewbox = storedViewbox || { x: 0, y: 0, zoom: 1 }; var dx = (currentViewbox.x - storedViewbox.x) * currentViewbox.scale, dy = (currentViewbox.y - storedViewbox.y) * currentViewbox.scale; if (dx !== 0 || dy !== 0) { canvas.scroll({ dx: dx, dy: dy }); } if (storedViewbox.zoom !== currentViewbox.scale) { canvas.zoom(storedViewbox.zoom, { x: 0, y: 0 }); } }); eventBus.on('diagram.clear', function() { positionMap.clear(); currentRoot = null; }); } DrilldownCentering.$inject = [ 'eventBus', 'canvas' ]; /** * ES5 Map implementation. Works. */ function Map() { this._entries = []; this.set = function(key, value) { var found = false; for (var k in this._entries) { if (this._entries[k][0] === key) { this._entries[k][1] = value; found = true; break; } } if (!found) { this._entries.push([ key, value ]); } }; this.get = function(key) { for (var k in this._entries) { if (this._entries[k][0] === key) { return this._entries[k][1]; } } return null; }; this.clear = function() { this._entries.length = 0; }; this.remove = function(key) { var idx = -1; for (var k in this._entries) { if (this._entries[k][0] === key) { idx = k; break; } } if (idx !== -1) { this._entries.splice(idx, 1); } }; } ================================================ FILE: lib/features/drilldown/DrilldownOverlayBehavior.js ================================================ import inherits from 'inherits-browser'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; import { getBusinessObject, is } from '../../util/ModelUtil'; import { classes, domify } from 'min-dom'; import { getPlaneIdFromShape } from '../../util/DrilldownUtil'; /** * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/features/overlays/Overlays').default} Overlays * @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate * * @typedef {import('../../model/Types').Element} Element * @typedef {import('../../model/Types').Parent} Parent * @typedef {import('../../model/Types').Shape} Shape */ var LOW_PRIORITY = 250; var ARROW_DOWN_SVG = ''; var EMPTY_MARKER = 'bjs-drilldown-empty'; /** * @param {Canvas} canvas * @param {EventBus} eventBus * @param {ElementRegistry} elementRegistry * @param {Overlays} overlays * @param {Translate} translate */ export default function DrilldownOverlayBehavior( canvas, eventBus, elementRegistry, overlays, translate ) { CommandInterceptor.call(this, eventBus); this._canvas = canvas; this._eventBus = eventBus; this._elementRegistry = elementRegistry; this._overlays = overlays; this._translate = translate; var self = this; this.executed('shape.toggleCollapse', LOW_PRIORITY, function(context) { var shape = context.shape; // Add overlay to the collapsed shape if (self._canDrillDown(shape)) { self._addOverlay(shape); } else { self._removeOverlay(shape); } }, true); this.reverted('shape.toggleCollapse', LOW_PRIORITY, function(context) { var shape = context.shape; // Add overlay to the collapsed shape if (self._canDrillDown(shape)) { self._addOverlay(shape); } else { self._removeOverlay(shape); } }, true); this.executed([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY, function(context) { var oldParent = context.oldParent, newParent = context.newParent || context.parent, shape = context.shape; // Add overlay to the collapsed shape if (self._canDrillDown(shape)) { self._addOverlay(shape); } self._updateDrilldownOverlay(oldParent); self._updateDrilldownOverlay(newParent); self._updateDrilldownOverlay(shape); }, true); this.reverted([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY, function(context) { var oldParent = context.oldParent, newParent = context.newParent || context.parent, shape = context.shape; // Add overlay to the collapsed shape if (self._canDrillDown(shape)) { self._addOverlay(shape); } self._updateDrilldownOverlay(oldParent); self._updateDrilldownOverlay(newParent); self._updateDrilldownOverlay(shape); }, true); eventBus.on('import.render.complete', function() { elementRegistry.filter(function(e) { return self._canDrillDown(e); }).map(function(el) { self._addOverlay(el); }); }); } inherits(DrilldownOverlayBehavior, CommandInterceptor); /** * @param {Shape} shape */ DrilldownOverlayBehavior.prototype._updateDrilldownOverlay = function(shape) { var canvas = this._canvas; if (!shape) { return; } var root = canvas.findRoot(shape); if (root) { this._updateOverlayVisibility(root); } }; /** * @param {Element} element * * @return {boolean} */ DrilldownOverlayBehavior.prototype._canDrillDown = function(element) { var canvas = this._canvas; return is(element, 'bpmn:SubProcess') && canvas.findRoot(getPlaneIdFromShape(element)); }; /** * Update the visibility of the drilldown overlay. If the plane has no elements, * the drilldown will only be shown when the element is selected. * * @param {Parent} element The collapsed root or shape. */ DrilldownOverlayBehavior.prototype._updateOverlayVisibility = function(element) { var overlays = this._overlays; var businessObject = getBusinessObject(element); var overlay = overlays.get({ element: businessObject.id, type: 'drilldown' })[0]; if (!overlay) { return; } var hasFlowElements = businessObject && businessObject.get('flowElements') && businessObject.get('flowElements').length; classes(overlay.html).toggle(EMPTY_MARKER, !hasFlowElements); }; /** * Add a drilldown button to the given element assuming the plane has the same * ID as the element. * * @param {Shape} element The collapsed shape. */ DrilldownOverlayBehavior.prototype._addOverlay = function(element) { var canvas = this._canvas, overlays = this._overlays, bo = getBusinessObject(element); var existingOverlays = overlays.get({ element: element, type: 'drilldown' }); if (existingOverlays.length) { this._removeOverlay(element); } var button = domify(''), elementName = bo.get('name') || bo.get('id'), title = this._translate('Open {element}', { element: elementName }); button.setAttribute('title', title); button.addEventListener('click', function() { canvas.setRootElement(canvas.findRoot(getPlaneIdFromShape(element))); }); overlays.add(element, 'drilldown', { position: { bottom: -7, right: -8 }, html: button }); this._updateOverlayVisibility(element); }; DrilldownOverlayBehavior.prototype._removeOverlay = function(element) { var overlays = this._overlays; overlays.remove({ element: element, type: 'drilldown' }); }; DrilldownOverlayBehavior.$inject = [ 'canvas', 'eventBus', 'elementRegistry', 'overlays', 'translate' ]; ================================================ FILE: lib/features/drilldown/SubprocessCompatibility.js ================================================ import { asBounds, asTRBL } from 'diagram-js/lib/layout/LayoutUtil'; import { is, isAny } from '../../util/ModelUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('../../model/Types').Moddle} Moddle * * @typedef {import('../../model/Types').Element} Element * @typedef {import('../../model/Types').Shape} Shape * * @typedef {import('diagram-js/lib/core/Canvas').CanvasPlane} CanvasPlane * * @typedef {import('diagram-js/lib/util/Types').Rect} Rect */ var DEFAULT_POSITION = { x: 180, y: 160 }; /** * Hook into `import.render.start` and create new planes for diagrams with * collapsed subprocesses and all DI elements on the same plane. * * @param {EventBus} eventBus * @param {Moddle} moddle */ export default function SubprocessCompatibility(eventBus, moddle) { this._eventBus = eventBus; this._moddle = moddle; var self = this; eventBus.on('import.render.start', 1500, function(e, context) { self._handleImport(context.definitions); }); } /** * @param {ModdleElement} definitions */ SubprocessCompatibility.prototype._handleImport = function(definitions) { if (!definitions.diagrams) { return; } var self = this; this._definitions = definitions; this._processToDiagramMap = {}; definitions.diagrams.forEach(function(diagram) { if (!diagram.plane || !diagram.plane.bpmnElement) { return; } self._processToDiagramMap[diagram.plane.bpmnElement.id] = diagram; }); var newDiagrams = definitions.diagrams .filter(diagram => diagram.plane) .flatMap(diagram => self._createNewDiagrams(diagram.plane)); newDiagrams.forEach(function(diagram) { self._movePlaneElementsToOrigin(diagram.plane); }); }; /** * Moves all DI elements from collapsed subprocesses to a new plane. * * @param {CanvasPlane} plane * * @return {ModdleElement[]} new diagrams created for the collapsed subprocesses */ SubprocessCompatibility.prototype._createNewDiagrams = function(plane) { var self = this; var collapsedElements = []; var elementsToMove = []; plane.get('planeElement').forEach(function(diElement) { var businessObject = diElement.bpmnElement; if (!businessObject) { return; } var parent = businessObject.$parent; if (is(businessObject, 'bpmn:SubProcess') && !diElement.isExpanded) { collapsedElements.push(businessObject); } if (shouldMoveToPlane(businessObject, plane)) { // don't change the array while we iterate over it elementsToMove.push({ diElement: diElement, parent: parent }); } }); var newDiagrams = []; // create new planes for all collapsed subprocesses, even when they are empty collapsedElements.forEach(function(element) { if (!self._processToDiagramMap[ element.id ]) { var diagram = self._createDiagram(element); self._processToDiagramMap[element.id] = diagram; newDiagrams.push(diagram); } }); elementsToMove.forEach(function(element) { var diElement = element.diElement; var parent = element.parent; // parent is expanded, get nearest collapsed parent while (parent && collapsedElements.indexOf(parent) === -1) { parent = parent.$parent; } // false positive, all parents are expanded if (!parent) { return; } var diagram = self._processToDiagramMap[ parent.id ]; self._moveToDiPlane(diElement, diagram.plane); }); return newDiagrams; }; /** * @param {CanvasPlane} plane */ SubprocessCompatibility.prototype._movePlaneElementsToOrigin = function(plane) { var elements = plane.get('planeElement'); // get bounding box of all elements var planeBounds = getPlaneBounds(plane); var offset = { x: planeBounds.x - DEFAULT_POSITION.x, y: planeBounds.y - DEFAULT_POSITION.y }; elements.forEach(function(diElement) { if (diElement.waypoint) { diElement.waypoint.forEach(function(waypoint) { waypoint.x = waypoint.x - offset.x; waypoint.y = waypoint.y - offset.y; }); } else if (diElement.bounds) { diElement.bounds.x = diElement.bounds.x - offset.x; diElement.bounds.y = diElement.bounds.y - offset.y; } }); }; /** * @param {ModdleElement} diElement * @param {CanvasPlane} newPlane */ SubprocessCompatibility.prototype._moveToDiPlane = function(diElement, newPlane) { var containingDiagram = findRootDiagram(diElement); // remove DI from old Plane and add it to the new one var parentPlaneElement = containingDiagram.plane.get('planeElement'); parentPlaneElement.splice(parentPlaneElement.indexOf(diElement), 1); newPlane.get('planeElement').push(diElement); }; /** * @param {ModdleElement} businessObject * * @return {ModdleElement} */ SubprocessCompatibility.prototype._createDiagram = function(businessObject) { var plane = this._moddle.create('bpmndi:BPMNPlane', { bpmnElement: businessObject }); var diagram = this._moddle.create('bpmndi:BPMNDiagram', { plane: plane }); plane.$parent = diagram; plane.bpmnElement = businessObject; diagram.$parent = this._definitions; this._definitions.diagrams.push(diagram); return diagram; }; SubprocessCompatibility.$inject = [ 'eventBus', 'moddle' ]; // helpers ////////// function findRootDiagram(element) { if (is(element, 'bpmndi:BPMNDiagram')) { return element; } else { return findRootDiagram(element.$parent); } } /** * @param {CanvasPlane} plane * * @return {Rect} */ function getPlaneBounds(plane) { var planeTrbl = { top: Infinity, right: -Infinity, bottom: -Infinity, left: Infinity }; plane.planeElement.forEach(function(element) { if (!element.bounds) { return; } var trbl = asTRBL(element.bounds); planeTrbl.top = Math.min(trbl.top, planeTrbl.top); planeTrbl.left = Math.min(trbl.left, planeTrbl.left); }); return asBounds(planeTrbl); } /** * @param {ModdleElement} businessObject * @param {CanvasPlane} plane * * @return {boolean} */ function shouldMoveToPlane(businessObject, plane) { var parent = businessObject.$parent; // don't move elements that are already on the plane if (!is(parent, 'bpmn:SubProcess') || parent === plane.bpmnElement) { return false; } // dataAssociations are children of the subprocess but rendered on process level // cf. https://github.com/bpmn-io/bpmn-js/issues/1619 if (isAny(businessObject, [ 'bpmn:DataInputAssociation', 'bpmn:DataOutputAssociation' ])) { return false; } return true; } ================================================ FILE: lib/features/drilldown/index.js ================================================ import OverlaysModule from 'diagram-js/lib/features/overlays'; import ChangeSupportModule from 'diagram-js/lib/features/change-support'; import RootElementsModule from 'diagram-js/lib/features/root-elements'; import DrilldownBreadcrumbs from './DrilldownBreadcrumbs'; import DrilldownCentering from './DrilldownCentering'; import SubprocessCompatibility from './SubprocessCompatibility'; import DrilldownOverlayBehavior from './DrilldownOverlayBehavior'; export default { __depends__: [ OverlaysModule, ChangeSupportModule, RootElementsModule ], __init__: [ 'drilldownBreadcrumbs', 'drilldownOverlayBehavior', 'drilldownCentering', 'subprocessCompatibility' ], drilldownBreadcrumbs: [ 'type', DrilldownBreadcrumbs ], drilldownCentering: [ 'type', DrilldownCentering ], drilldownOverlayBehavior: [ 'type', DrilldownOverlayBehavior ], subprocessCompatibility: [ 'type', SubprocessCompatibility ] }; ================================================ FILE: lib/features/editor-actions/BpmnEditorActions.js ================================================ import inherits from 'inherits-browser'; import EditorActions from 'diagram-js/lib/features/editor-actions/EditorActions'; import { filter } from 'min-dash'; import { is } from '../../util/ModelUtil'; import { getBBox } from 'diagram-js/lib/util/Elements'; /** * @typedef {import('didi').Injector} Injector */ /** * Registers and executes BPMN specific editor actions. * * @param {Injector} injector */ export default function BpmnEditorActions(injector) { injector.invoke(EditorActions, this); } inherits(BpmnEditorActions, EditorActions); BpmnEditorActions.$inject = [ 'injector' ]; /** * Register default actions. * * @param {Injector} injector */ BpmnEditorActions.prototype._registerDefaultActions = function(injector) { // (0) invoke super method EditorActions.prototype._registerDefaultActions.call(this, injector); // (1) retrieve optional components to integrate with var canvas = injector.get('canvas', false); var elementRegistry = injector.get('elementRegistry', false); var selection = injector.get('selection', false); var spaceTool = injector.get('spaceTool', false); var lassoTool = injector.get('lassoTool', false); var handTool = injector.get('handTool', false); var globalConnect = injector.get('globalConnect', false); var distributeElements = injector.get('distributeElements', false); var alignElements = injector.get('alignElements', false); var directEditing = injector.get('directEditing', false); var searchPad = injector.get('searchPad', false); var modeling = injector.get('modeling', false); var contextPad = injector.get('contextPad', false); // (2) check components and register actions if (canvas && elementRegistry && selection) { this._registerAction('selectElements', function() { // select all elements except for the invisible // root element var rootElement = canvas.getRootElement(); var elements = elementRegistry.filter(function(element) { return element !== rootElement; }); selection.select(elements); return elements; }); } if (spaceTool) { this._registerAction('spaceTool', function() { spaceTool.toggle(); }); } if (lassoTool) { this._registerAction('lassoTool', function() { lassoTool.toggle(); }); } if (handTool) { this._registerAction('handTool', function() { handTool.toggle(); }); } if (globalConnect) { this._registerAction('globalConnectTool', function() { globalConnect.toggle(); }); } if (selection && distributeElements) { this._registerAction('distributeElements', function(opts) { var currentSelection = selection.get(), type = opts.type; if (currentSelection.length) { distributeElements.trigger(currentSelection, type); } }); } if (selection && alignElements) { this._registerAction('alignElements', function(opts) { var currentSelection = selection.get(), aligneableElements = [], type = opts.type; if (currentSelection.length) { aligneableElements = filter(currentSelection, function(element) { return !is(element, 'bpmn:Lane'); }); alignElements.trigger(aligneableElements, type); } }); } if (selection && modeling) { this._registerAction('setColor', function(opts) { var currentSelection = selection.get(); if (currentSelection.length) { modeling.setColor(currentSelection, opts); } }); } if (selection && directEditing) { this._registerAction('directEditing', function() { var currentSelection = selection.get(); if (currentSelection.length) { directEditing.activate(currentSelection[0]); } }); } if (searchPad) { this._registerAction('find', function() { searchPad.toggle(); }); } if (canvas && modeling) { this._registerAction('moveToOrigin', function() { var rootElement = canvas.getRootElement(), boundingBox, elements; if (is(rootElement, 'bpmn:Collaboration')) { elements = elementRegistry.filter(function(element) { return is(element.parent, 'bpmn:Collaboration'); }); } else { elements = elementRegistry.filter(function(element) { return element !== rootElement && !is(element.parent, 'bpmn:SubProcess'); }); } boundingBox = getBBox(elements); modeling.moveElements( elements, { x: -boundingBox.x, y: -boundingBox.y }, rootElement ); }); } if (selection && contextPad) { this._registerAction('replaceElement', function(event) { contextPad.triggerEntry('replace', 'click', event); }); } }; ================================================ FILE: lib/features/editor-actions/index.js ================================================ import EditorActionsModule from 'diagram-js/lib/features/editor-actions'; import BpmnEditorActions from './BpmnEditorActions'; export default { __depends__: [ EditorActionsModule ], editorActions: [ 'type', BpmnEditorActions ] }; ================================================ FILE: lib/features/grid-snapping/BpmnGridSnapping.js ================================================ import { isAny } from '../modeling/util/ModelingUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus */ /** * @param {EventBus} eventBus */ export default function BpmnGridSnapping(eventBus) { eventBus.on([ 'create.init', 'shape.move.init' ], function(event) { var context = event.context, shape = event.shape; if (isAny(shape, [ 'bpmn:Participant', 'bpmn:SubProcess', 'bpmn:TextAnnotation' ])) { if (!context.gridSnappingContext) { context.gridSnappingContext = {}; } context.gridSnappingContext.snapLocation = 'top-left'; } }); } BpmnGridSnapping.$inject = [ 'eventBus' ]; ================================================ FILE: lib/features/grid-snapping/behavior/GridSnappingAutoPlaceBehavior.js ================================================ import { getNewShapePosition } from '../../auto-place/BpmnAutoPlaceUtil'; import { getMid } from 'diagram-js/lib/layout/LayoutUtil'; import { is } from '../../../util/ModelUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry * @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping * * @typedef {import('diagram-js/lib/util/Types').Axis} Axis */ var HIGH_PRIORITY = 2000; /** * @param {EventBus} eventBus * @param {GridSnapping} gridSnapping * @param {ElementRegistry} elementRegistry */ export default function GridSnappingAutoPlaceBehavior(eventBus, gridSnapping, elementRegistry) { eventBus.on('autoPlace', HIGH_PRIORITY, function(context) { var source = context.source, sourceMid = getMid(source), shape = context.shape; var position = getNewShapePosition(source, shape, elementRegistry); [ 'x', 'y' ].forEach(function(axis) { var options = {}; // do not snap if x/y equal if (position[ axis ] === sourceMid[ axis ]) { return; } if (position[ axis ] > sourceMid[ axis ]) { options.min = position[ axis ]; } else { options.max = position[ axis ]; } if (is(shape, 'bpmn:TextAnnotation')) { if (isHorizontal(axis)) { options.offset = -shape.width / 2; } else { options.offset = -shape.height / 2; } } position[ axis ] = gridSnapping.snapValue(position[ axis ], options); }); // must be returned to be considered by auto place return position; }); } GridSnappingAutoPlaceBehavior.$inject = [ 'eventBus', 'gridSnapping', 'elementRegistry' ]; // helpers ////////// /** * @param {Axis} axis * * @return {boolean} */ function isHorizontal(axis) { return axis === 'x'; } ================================================ FILE: lib/features/grid-snapping/behavior/GridSnappingLayoutConnectionBehavior.js ================================================ import inherits from 'inherits-browser'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; import { pointsAligned } from 'diagram-js/lib/util/Geometry'; import { assign } from 'min-dash'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping * @typedef {import('../../modeling/Modeling').default} Modeling * * @typedef {import('diagram-js/lib/util/Types').Point} Point */ var HIGH_PRIORITY = 3000; /** * Snaps connections with Manhattan layout. * * @param {EventBus} eventBus * @param {GridSnapping} gridSnapping * @param {Modeling} modeling */ export default function GridSnappingLayoutConnectionBehavior(eventBus, gridSnapping, modeling) { CommandInterceptor.call(this, eventBus); this._gridSnapping = gridSnapping; var self = this; this.postExecuted([ 'connection.create', 'connection.layout' ], HIGH_PRIORITY, function(event) { var context = event.context, connection = context.connection, hints = context.hints || {}, waypoints = connection.waypoints; if (hints.connectionStart || hints.connectionEnd || hints.createElementsBehavior === false) { return; } if (!hasMiddleSegments(waypoints)) { return; } modeling.updateWaypoints(connection, self.snapMiddleSegments(waypoints)); }); } GridSnappingLayoutConnectionBehavior.$inject = [ 'eventBus', 'gridSnapping', 'modeling' ]; inherits(GridSnappingLayoutConnectionBehavior, CommandInterceptor); /** * Snap middle segments of a given connection. * * @param {Point[]} waypoints * * @return {Point[]} */ GridSnappingLayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) { var gridSnapping = this._gridSnapping, snapped; waypoints = waypoints.slice(); for (var i = 1; i < waypoints.length - 2; i++) { snapped = snapSegment(gridSnapping, waypoints[i], waypoints[i + 1]); waypoints[i] = snapped[0]; waypoints[i + 1] = snapped[1]; } return waypoints; }; // helpers ////////// /** * Check whether a connection has a middle segments. * * @param {Point[]} waypoints * * @return {boolean} */ function hasMiddleSegments(waypoints) { return waypoints.length > 3; } /** * Check whether an alignment is horizontal. * * @param {string} aligned * * @return {boolean} */ function horizontallyAligned(aligned) { return aligned === 'h'; } /** * Check whether an alignment is vertical. * * @param {string} aligned * * @return {boolean} */ function verticallyAligned(aligned) { return aligned === 'v'; } /** * Get middle segments from a given connection. * * @param {Point[]} waypoints * * @return {Point[]} */ function snapSegment(gridSnapping, segmentStart, segmentEnd) { var aligned = pointsAligned(segmentStart, segmentEnd); var snapped = {}; if (horizontallyAligned(aligned)) { // snap horizontally snapped.y = gridSnapping.snapValue(segmentStart.y); } if (verticallyAligned(aligned)) { // snap vertically snapped.x = gridSnapping.snapValue(segmentStart.x); } if ('x' in snapped || 'y' in snapped) { segmentStart = assign({}, segmentStart, snapped); segmentEnd = assign({}, segmentEnd, snapped); } return [ segmentStart, segmentEnd ]; } ================================================ FILE: lib/features/grid-snapping/behavior/GridSnappingParticipantBehavior.js ================================================ import { is } from '../../../util/ModelUtil'; /** * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/features/grid-snapping/GridSnapping').default} GridSnapping */ var HIGHER_PRIORITY = 1750; /** * @param {Canvas} canvas * @param {EventBus} eventBus * @param {GridSnapping} gridSnapping */ export default function GridSnappingParticipantBehavior(canvas, eventBus, gridSnapping) { eventBus.on([ 'create.start', 'shape.move.start' ], HIGHER_PRIORITY, function(event) { var context = event.context, shape = context.shape, rootElement = canvas.getRootElement(); if (!is(shape, 'bpmn:Participant') || !is(rootElement, 'bpmn:Process') || !rootElement.children.length) { return; } var createConstraints = context.createConstraints; if (!createConstraints) { return; } shape.width = gridSnapping.snapValue(shape.width, { min: shape.width }); shape.height = gridSnapping.snapValue(shape.height, { min: shape.height }); }); } GridSnappingParticipantBehavior.$inject = [ 'canvas', 'eventBus', 'gridSnapping' ]; ================================================ FILE: lib/features/grid-snapping/behavior/index.js ================================================ import GridSnappingAutoPlaceBehavior from './GridSnappingAutoPlaceBehavior'; import GridSnappingParticipantBehavior from './GridSnappingParticipantBehavior'; import GridSnappingLayoutConnectionBehavior from './GridSnappingLayoutConnectionBehavior'; export default { __init__: [ 'gridSnappingAutoPlaceBehavior', 'gridSnappingParticipantBehavior', 'gridSnappingLayoutConnectionBehavior', ], gridSnappingAutoPlaceBehavior: [ 'type', GridSnappingAutoPlaceBehavior ], gridSnappingParticipantBehavior: [ 'type', GridSnappingParticipantBehavior ], gridSnappingLayoutConnectionBehavior: [ 'type', GridSnappingLayoutConnectionBehavior ] }; ================================================ FILE: lib/features/grid-snapping/index.js ================================================ import BpmnGridSnapping from './BpmnGridSnapping'; import GridSnappingModule from 'diagram-js/lib/features/grid-snapping'; import GridSnappingBehaviorModule from './behavior'; export default { __depends__: [ GridSnappingModule, GridSnappingBehaviorModule ], __init__: [ 'bpmnGridSnapping' ], bpmnGridSnapping: [ 'type', BpmnGridSnapping ] }; ================================================ FILE: lib/features/interaction-events/BpmnInteractionEvents.js ================================================ import { is } from '../../util/ModelUtil'; import { isExpanded, isHorizontal } from '../../util/DiUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/features/interaction-events/InteractionEvents').default} InteractionEvents * * @typedef {import('../../model/Types').Element} Element * @typedef {import('../../model/Types').Shape} Shape */ var LABEL_WIDTH = 30, LABEL_HEIGHT = 30; /** * BPMN-specific hit zones and interaction fixes. * * @param {EventBus} eventBus * @param {InteractionEvents} interactionEvents */ export default function BpmnInteractionEvents(eventBus, interactionEvents) { this._interactionEvents = interactionEvents; var self = this; eventBus.on([ 'interactionEvents.createHit', 'interactionEvents.updateHit' ], function(context) { var element = context.element, gfx = context.gfx; if (is(element, 'bpmn:Lane')) { return self._createParticipantHit(element, gfx); } else if (is(element, 'bpmn:Participant')) { if (isExpanded(element)) { return self._createParticipantHit(element, gfx); } else { return self._createDefaultHit(element, gfx); } } else if (is(element, 'bpmn:SubProcess')) { if (isExpanded(element)) { return self._createSubProcessHit(element, gfx); } else { return self._createDefaultHit(element, gfx); } } }); } BpmnInteractionEvents.$inject = [ 'eventBus', 'interactionEvents' ]; /** * @param {Element} element * @param {SVGElement} gfx * * @return {boolean} */ BpmnInteractionEvents.prototype._createDefaultHit = function(element, gfx) { this._interactionEvents.removeHits(gfx); this._interactionEvents.createDefaultHit(element, gfx); // indicate that we created a hit return true; }; /** * @param {Shape} element * @param {SVGElement} gfx * * @return {boolean} */ BpmnInteractionEvents.prototype._createParticipantHit = function(element, gfx) { // remove existing hits this._interactionEvents.removeHits(gfx); // add body hit this._interactionEvents.createBoxHit(gfx, 'no-move', { width: element.width, height: element.height }); // add outline hit this._interactionEvents.createBoxHit(gfx, 'click-stroke', { width: element.width, height: element.height }); // add label hit var box = isHorizontal(element) ? { width: LABEL_WIDTH, height: element.height } : { width: element.width, height: LABEL_HEIGHT }; this._interactionEvents.createBoxHit(gfx, 'all', box); // indicate that we created a hit return true; }; /** * @param {Shape} element * @param {SVGElement} gfx * * @return {boolean} */ BpmnInteractionEvents.prototype._createSubProcessHit = function(element, gfx) { // remove existing hits this._interactionEvents.removeHits(gfx); // add body hit this._interactionEvents.createBoxHit(gfx, 'no-move', { width: element.width, height: element.height }); // add outline hit this._interactionEvents.createBoxHit(gfx, 'click-stroke', { width: element.width, height: element.height }); // add label hit this._interactionEvents.createBoxHit(gfx, 'all', { width: element.width, height: LABEL_HEIGHT }); // indicate that we created a hit return true; }; ================================================ FILE: lib/features/interaction-events/index.js ================================================ import BpmnInteractionEvents from './BpmnInteractionEvents'; export default { __init__: [ 'bpmnInteractionEvents' ], bpmnInteractionEvents: [ 'type', BpmnInteractionEvents ] }; ================================================ FILE: lib/features/keyboard/BpmnKeyboardBindings.js ================================================ import inherits from 'inherits-browser'; import KeyboardBindings from 'diagram-js/lib/features/keyboard/KeyboardBindings'; /** * @typedef {import('didi').Injector} Injector * @typedef {import('diagram-js/lib/features/editor-actions/EditorActions').default} EditorActions * @typedef {import('diagram-js/lib/features/keyboard/Keyboard').default} Keyboard */ /** * BPMN 2.0 specific keyboard bindings. * * @param {Injector} injector */ export default function BpmnKeyboardBindings(injector) { injector.invoke(KeyboardBindings, this); } inherits(BpmnKeyboardBindings, KeyboardBindings); BpmnKeyboardBindings.$inject = [ 'injector' ]; /** * Register available keyboard bindings. * * @param {Keyboard} keyboard * @param {EditorActions} editorActions */ BpmnKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) { // inherit default bindings KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions); /** * Add keyboard binding if respective editor action * is registered. * * @param {string} action name * @param {Function} fn that implements the key binding */ function addListener(action, fn) { if (editorActions.isRegistered(action)) { keyboard.addListener(fn); } } // select all elements // CTRL + A addListener('selectElements', function(context) { var event = context.keyEvent; if (keyboard.isKey([ 'a', 'A' ], event) && keyboard.isCmd(event)) { editorActions.trigger('selectElements'); return true; } }); // search labels // CTRL + F addListener('find', function(context) { var event = context.keyEvent; if (keyboard.isKey([ 'f', 'F' ], event) && keyboard.isCmd(event)) { editorActions.trigger('find'); return true; } }); // activate space tool // S addListener('spaceTool', function(context) { var event = context.keyEvent; if (keyboard.hasModifier(event)) { return; } if (keyboard.isKey([ 's', 'S' ], event)) { editorActions.trigger('spaceTool'); return true; } }); // activate lasso tool // L addListener('lassoTool', function(context) { var event = context.keyEvent; if (keyboard.hasModifier(event)) { return; } if (keyboard.isKey([ 'l', 'L' ], event)) { editorActions.trigger('lassoTool'); return true; } }); // activate hand tool // H addListener('handTool', function(context) { var event = context.keyEvent; if (keyboard.hasModifier(event)) { return; } if (keyboard.isKey([ 'h', 'H' ], event)) { editorActions.trigger('handTool'); return true; } }); // activate global connect tool // C addListener('globalConnectTool', function(context) { var event = context.keyEvent; if (keyboard.hasModifier(event)) { return; } if (keyboard.isKey([ 'c', 'C' ], event)) { editorActions.trigger('globalConnectTool'); return true; } }); // activate direct editing // E addListener('directEditing', function(context) { var event = context.keyEvent; if (keyboard.hasModifier(event)) { return; } if (keyboard.isKey([ 'e', 'E' ], event)) { editorActions.trigger('directEditing'); return true; } }); // activate replace element // R addListener('replaceElement', function(context) { var event = context.keyEvent; if (keyboard.hasModifier(event)) { return; } if (keyboard.isKey([ 'r', 'R' ], event)) { editorActions.trigger('replaceElement', event); return true; } }); }; ================================================ FILE: lib/features/keyboard/index.js ================================================ import KeyboardModule from 'diagram-js/lib/features/keyboard'; import BpmnKeyboardBindings from './BpmnKeyboardBindings'; export default { __depends__: [ KeyboardModule ], __init__: [ 'keyboardBindings' ], keyboardBindings: [ 'type', BpmnKeyboardBindings ] }; ================================================ FILE: lib/features/label-editing/LabelEditingPreview.js ================================================ import { append as svgAppend, attr as svgAttr, create as svgCreate, remove as svgRemove } from 'tiny-svg'; import { getDi, is } from '../../util/ModelUtil'; import { translate } from 'diagram-js/lib/util/SvgTransformUtil'; /** * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('../../draw/PathMap').default} PathMap */ var MARKER_HIDDEN = 'djs-element-hidden', MARKER_LABEL_HIDDEN = 'djs-label-hidden'; /** * @param {EventBus} eventBus * @param {Canvas} canvas * @param {PathMap} pathMap */ export default function LabelEditingPreview(eventBus, canvas, pathMap) { var self = this; var defaultLayer = canvas.getDefaultLayer(); var element, absoluteElementBBox, gfx; eventBus.on('directEditing.activate', function(context) { var activeProvider = context.active; element = activeProvider.element.label || activeProvider.element; // text annotation if (is(element, 'bpmn:TextAnnotation')) { absoluteElementBBox = canvas.getAbsoluteBBox(element); gfx = svgCreate('g'); var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.0, my: 0.0 } }); var path = self.path = svgCreate('path'); svgAttr(path, { d: textPathData, strokeWidth: 2, stroke: getStrokeColor(element) }); svgAppend(gfx, path); svgAppend(defaultLayer, gfx); translate(gfx, element.x, element.y); } if (is(element, 'bpmn:TextAnnotation') || element.labelTarget) { canvas.addMarker(element, MARKER_HIDDEN); } else if (is(element, 'bpmn:Task') || is(element, 'bpmn:CallActivity') || is(element, 'bpmn:SubProcess') || is(element, 'bpmn:Participant') || is(element, 'bpmn:Lane')) { canvas.addMarker(element, MARKER_LABEL_HIDDEN); } }); eventBus.on('directEditing.resize', function(context) { // text annotation if (is(element, 'bpmn:TextAnnotation')) { var height = context.height, dy = context.dy; var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0); var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: newElementHeight, position: { mx: 0.0, my: 0.0 } }); svgAttr(self.path, { d: textPathData }); } }); eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) { var activeProvider = context.active; if (activeProvider) { canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN); canvas.removeMarker(element, MARKER_LABEL_HIDDEN); } element = undefined; absoluteElementBBox = undefined; if (gfx) { svgRemove(gfx); gfx = undefined; } }); } LabelEditingPreview.$inject = [ 'eventBus', 'canvas', 'pathMap' ]; // helpers ////////// function getStrokeColor(element, defaultColor) { var di = getDi(element); return di.get('stroke') || defaultColor || 'black'; } ================================================ FILE: lib/features/label-editing/LabelEditingProvider.js ================================================ import { assign } from 'min-dash'; import { getLabel } from '../../util/LabelUtil'; import { is } from '../../util/ModelUtil'; import { isAny } from '../modeling/util/ModelingUtil'; import { isExpanded, isHorizontal } from '../../util/DiUtil'; import { getExternalLabelMid, isLabelExternal, hasExternalLabel, isLabel } from '../../util/LabelUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('../modeling/BpmnFactory').default} BpmnFactory * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas * @typedef {import('diagram-js-direct-editing/lib/DirectEditing').default} DirectEditing * @typedef {import('../modeling/Modeling').default} Modeling * @typedef {import('diagram-js/lib/features/resize/ResizeHandles').default} ResizeHandles * @typedef {import('../../draw/TextRenderer').default} TextRenderer * * @typedef {import('../../model/Types').Element} Element * * @typedef { { * bounds: { * x: number; * y: number; * width: number; * height: number; * minWidth?: number; * minHeight?: number; * }; * style: Object; * } } DirectEditingContext */ var HIGH_PRIORITY = 2000; /** * @param {EventBus} eventBus * @param {BpmnFactory} bpmnFactory * @param {Canvas} canvas * @param {DirectEditing} directEditing * @param {Modeling} modeling * @param {ResizeHandles} resizeHandles * @param {TextRenderer} textRenderer */ export default function LabelEditingProvider( eventBus, bpmnFactory, canvas, directEditing, modeling, resizeHandles, textRenderer) { this._bpmnFactory = bpmnFactory; this._canvas = canvas; this._modeling = modeling; this._textRenderer = textRenderer; directEditing.registerProvider(this); // listen to dblclick on non-root elements eventBus.on('element.dblclick', function(event) { activateDirectEdit(event.element, true); }); // complete on followup canvas operation eventBus.on([ 'autoPlace.start', 'canvas.viewbox.changing', 'drag.init', 'element.mousedown', 'popupMenu.open', 'root.set', 'selection.changed' ], function() { if (directEditing.isActive()) { directEditing.complete(); } }); eventBus.on([ 'shape.remove', 'connection.remove' ], HIGH_PRIORITY, function(event) { if (directEditing.isActive(event.element)) { directEditing.cancel(); } }); // cancel on command stack changes eventBus.on([ 'commandStack.changed' ], function(e) { if (directEditing.isActive()) { directEditing.cancel(); } }); eventBus.on('directEditing.activate', function(event) { resizeHandles.removeResizers(); }); eventBus.on('create.end', 500, function(event) { var context = event.context, element = context.shape, canExecute = event.context.canExecute, isTouch = event.isTouch; // TODO(nikku): we need to find a way to support the // direct editing on mobile devices; right now this will // break for desworkflowediting on mobile devices // as it breaks the user interaction workflow // TODO(nikku): we should temporarily focus the edited element // here and release the focused viewport after the direct edit // operation is finished if (isTouch) { return; } if (!canExecute) { return; } if (context.hints && context.hints.createElementsBehavior === false) { return; } activateDirectEdit(element); }); eventBus.on('autoPlace.end', 500, function(event) { activateDirectEdit(event.shape); }); function activateDirectEdit(element, force) { if (force || isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation', 'bpmn:Participant' ]) || isCollapsedSubProcess(element)) { directEditing.activate(element); } } } LabelEditingProvider.$inject = [ 'eventBus', 'bpmnFactory', 'canvas', 'directEditing', 'modeling', 'resizeHandles', 'textRenderer' ]; /** * Activate direct editing for activities and text annotations. * * @param {Element} element * * @return { { * text: string; * options?: { * autoResize?: boolean; * centerVertically?: boolean; * resizable?: boolean; * } * } & DirectEditingContext } */ LabelEditingProvider.prototype.activate = function(element) { // text var text = getLabel(element); if (text === undefined) { return; } var context = { text: text }; // bounds var bounds = this.getEditingBBox(element); assign(context, bounds); var options = {}; var style = context.style || {}; // Remove background and border assign(style, { backgroundColor: null, border: null }); // tasks if ( isAny(element, [ 'bpmn:Task', 'bpmn:Participant', 'bpmn:Lane', 'bpmn:CallActivity' ]) || isCollapsedSubProcess(element) ) { assign(options, { centerVertically: true }); } // external labels if (isLabelExternal(element)) { assign(options, { autoResize: true }); // keep background and border for external labels assign(style, { backgroundColor: '#ffffff', border: '1px solid #ccc' }); } // text annotations if (is(element, 'bpmn:TextAnnotation')) { assign(options, { resizable: true, autoResize: true }); // keep background and border for text annotations assign(style, { backgroundColor: '#ffffff', border: '1px solid #ccc' }); } assign(context, { options: options, style: style }); return context; }; /** * Get the editing bounding box based on the element's size and position. * * @param {Element} element * * @return {DirectEditingContext} */ LabelEditingProvider.prototype.getEditingBBox = function(element) { var canvas = this._canvas; var target = element.label || element; var bbox = canvas.getAbsoluteBBox(target); var mid = { x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height / 2 }; // default position var bounds = { x: bbox.x, y: bbox.y }; var zoom = canvas.zoom(); var defaultStyle = this._textRenderer.getDefaultStyle(), externalStyle = this._textRenderer.getExternalStyle(); // take zoom into account var externalFontSize = externalStyle.fontSize * zoom, externalLineHeight = externalStyle.lineHeight, defaultFontSize = defaultStyle.fontSize * zoom, defaultLineHeight = defaultStyle.lineHeight; var style = { fontFamily: this._textRenderer.getDefaultStyle().fontFamily, fontWeight: this._textRenderer.getDefaultStyle().fontWeight }; // adjust for expanded pools AND lanes if (is(element, 'bpmn:Lane') || isExpandedPool(element)) { var isHorizontalLane = isHorizontal(element); var laneBounds = isHorizontalLane ? { width: bbox.height, height: 30 * zoom, x: bbox.x - bbox.height / 2 + (15 * zoom), y: mid.y - (30 * zoom) / 2 } : { width: bbox.width, height: 30 * zoom }; assign(bounds, laneBounds); assign(style, { fontSize: defaultFontSize + 'px', lineHeight: defaultLineHeight, paddingTop: (7 * zoom) + 'px', paddingBottom: (7 * zoom) + 'px', paddingLeft: (5 * zoom) + 'px', paddingRight: (5 * zoom) + 'px', transform: isHorizontalLane ? 'rotate(-90deg)' : null }); } // internal labels for collapsed participants if (isCollapsedPool(element)) { var isHorizontalPool = isHorizontal(element); var poolBounds = isHorizontalPool ? { width: bbox.width, height: bbox.height } : { width: bbox.height, height: bbox.width, x: mid.x - bbox.height / 2, y: mid.y - bbox.width / 2 }; assign(bounds, poolBounds); assign(style, { fontSize: defaultFontSize + 'px', lineHeight: defaultLineHeight, paddingTop: (7 * zoom) + 'px', paddingBottom: (7 * zoom) + 'px', paddingLeft: (5 * zoom) + 'px', paddingRight: (5 * zoom) + 'px', transform: isHorizontalPool ? null : 'rotate(-90deg)' }); } // internal labels for tasks and collapsed call activities // and sub processes if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity' ]) || isCollapsedSubProcess(element)) { assign(bounds, { width: bbox.width, height: bbox.height }); assign(style, { fontSize: defaultFontSize + 'px', lineHeight: defaultLineHeight, paddingTop: (7 * zoom) + 'px', paddingBottom: (7 * zoom) + 'px', paddingLeft: (5 * zoom) + 'px', paddingRight: (5 * zoom) + 'px' }); } // internal labels for expanded sub processes if (isExpandedSubProcess(element)) { assign(bounds, { width: bbox.width, x: bbox.x }); assign(style, { fontSize: defaultFontSize + 'px', lineHeight: defaultLineHeight, paddingTop: (7 * zoom) + 'px', paddingBottom: (7 * zoom) + 'px', paddingLeft: (5 * zoom) + 'px', paddingRight: (5 * zoom) + 'px' }); } var width = 90 * zoom, paddingTop = 7 * zoom, paddingBottom = 4 * zoom; // external labels for events, data elements, gateways, groups and connections if (target.labelTarget) { assign(bounds, { width: width, height: bbox.height + paddingTop + paddingBottom, x: mid.x - width / 2, y: bbox.y - paddingTop }); assign(style, { fontSize: externalFontSize + 'px', lineHeight: externalLineHeight, paddingTop: paddingTop + 'px', paddingBottom: paddingBottom + 'px' }); } // external label not yet created if (isLabelExternal(target) && !hasExternalLabel(target) && !isLabel(target)) { var externalLabelMid = getExternalLabelMid(element); var absoluteBBox = canvas.getAbsoluteBBox({ x: externalLabelMid.x, y: externalLabelMid.y, width: 0, height: 0 }); var height = externalFontSize + paddingTop + paddingBottom; assign(bounds, { width: width, height: height, x: absoluteBBox.x - width / 2, y: absoluteBBox.y - height / 2 }); assign(style, { fontSize: externalFontSize + 'px', lineHeight: externalLineHeight, paddingTop: paddingTop + 'px', paddingBottom: paddingBottom + 'px' }); } // text annotations if (is(element, 'bpmn:TextAnnotation')) { assign(bounds, { width: bbox.width, height: bbox.height, minWidth: 30 * zoom, minHeight: 10 * zoom }); assign(style, { textAlign: 'left', paddingTop: (5 * zoom) + 'px', paddingBottom: (7 * zoom) + 'px', paddingLeft: (7 * zoom) + 'px', paddingRight: (5 * zoom) + 'px', fontSize: defaultFontSize + 'px', lineHeight: defaultLineHeight }); } return { bounds: bounds, style: style }; }; LabelEditingProvider.prototype.update = function( element, newLabel, activeContextText, bounds) { var newBounds, bbox; if (is(element, 'bpmn:TextAnnotation')) { bbox = this._canvas.getAbsoluteBBox(element); newBounds = { x: element.x, y: element.y, width: element.width / bbox.width * bounds.width, height: element.height / bbox.height * bounds.height }; } if (isEmptyText(newLabel)) { newLabel = null; } this._modeling.updateLabel(element, newLabel, newBounds); }; // helpers ////////// function isCollapsedSubProcess(element) { return is(element, 'bpmn:SubProcess') && !isExpanded(element); } function isExpandedSubProcess(element) { return is(element, 'bpmn:SubProcess') && isExpanded(element); } function isCollapsedPool(element) { return is(element, 'bpmn:Participant') && !isExpanded(element); } function isExpandedPool(element) { return is(element, 'bpmn:Participant') && isExpanded(element); } function isEmptyText(label) { return !label || !label.trim(); } ================================================ FILE: lib/features/label-editing/LabelUtil.js ================================================ export { getLabel, setLabel } from '../../util/LabelUtil'; ================================================ FILE: lib/features/label-editing/cmd/UpdateLabelHandler.js ================================================ import { setLabel, getLabel } from '../../../util/LabelUtil'; import { getExternalLabelMid, isLabelExternal, hasExternalLabel, isLabel } from '../../../util/LabelUtil'; import { is } from '../../../util/ModelUtil'; var NULL_DIMENSIONS = { width: 0, height: 0 }; /** * @typedef {import('../../modeling/Modeling').default} Modeling * @typedef {import('../../../draw/TextRenderer').default} TextRenderer * @typedef {import('../../modeling/BpmnFactory').default} BpmnFactory * * @typedef {import('../../../model/Types').Element} Element */ /** * A handler that updates the text of a BPMN element. * * @param {Modeling} modeling * @param {TextRenderer} textRenderer * @param {BpmnFactory} bpmnFactory */ export default function UpdateLabelHandler(modeling, textRenderer, bpmnFactory) { /** * Set the label and return the changed elements. * * Element parameter can be label itself or connection (i.e. sequence flow). * * @param {Element} element * @param {string} text */ function setText(element, text) { // external label if present var label = element.label || element; var labelTarget = element.labelTarget || element; setLabel(label, text, labelTarget !== label); return [ label, labelTarget ]; } function preExecute(ctx) { var element = ctx.element, businessObject = element.businessObject, newLabel = ctx.newLabel; if (!isLabel(element) && isLabelExternal(element) && !hasExternalLabel(element) && !isEmptyText(newLabel)) { // create label var paddingTop = 7; var labelCenter = getExternalLabelMid(element); labelCenter = { x: labelCenter.x, y: labelCenter.y + paddingTop }; modeling.createLabel(element, labelCenter, { id: businessObject.id + '_label', businessObject: businessObject, di: element.di }); } } function execute(ctx) { ctx.oldLabel = getLabel(ctx.element); return setText(ctx.element, ctx.newLabel); } function revert(ctx) { return setText(ctx.element, ctx.oldLabel); } function postExecute(ctx) { var element = ctx.element, label = element.label || element, newLabel = ctx.newLabel, newBounds = ctx.newBounds, hints = ctx.hints || {}; // ignore internal labels for elements except text annotations if (!isLabel(label) && !is(label, 'bpmn:TextAnnotation')) { return; } if (isLabel(label) && isEmptyText(newLabel)) { if (hints.removeShape !== false) { modeling.removeShape(label, { unsetLabel: false }); } return; } var text = getLabel(element); // resize element based on label _or_ pre-defined bounds if (typeof newBounds === 'undefined') { newBounds = textRenderer.getExternalLabelBounds(label, text); } // setting newBounds to false or _null_ will // disable the postExecute resize operation if (newBounds) { modeling.resizeShape(label, newBounds, NULL_DIMENSIONS); } } // API this.preExecute = preExecute; this.execute = execute; this.revert = revert; this.postExecute = postExecute; } UpdateLabelHandler.$inject = [ 'modeling', 'textRenderer', 'bpmnFactory' ]; // helpers ////////// function isEmptyText(label) { return !label || !label.trim(); } ================================================ FILE: lib/features/label-editing/index.js ================================================ import ChangeSupportModule from 'diagram-js/lib/features/change-support'; import ResizeModule from 'diagram-js/lib/features/resize'; import DirectEditingModule from 'diagram-js-direct-editing'; import LabelEditingProvider from './LabelEditingProvider'; import LabelEditingPreview from './LabelEditingPreview'; export default { __depends__: [ ChangeSupportModule, ResizeModule, DirectEditingModule ], __init__: [ 'labelEditingProvider', 'labelEditingPreview' ], labelEditingProvider: [ 'type', LabelEditingProvider ], labelEditingPreview: [ 'type', LabelEditingPreview ] }; ================================================ FILE: lib/features/label-link/LabelLink.js ================================================ import { queryAll as domQueryAll } from 'min-dom'; import { append as svgAppend, attr as svgAttr, remove as svgRemove, } from 'tiny-svg'; import { createLine, updateLine } from 'diagram-js/lib/util/RenderUtil'; import { getMid, getElementLineIntersection } from 'diagram-js/lib/layout/LayoutUtil'; import { getDistancePointPoint } from 'diagram-js/lib/features/bendpoints/GeometricUtil'; import { isLabel } from 'diagram-js/lib/util/ModelUtil'; import { isAny } from '../modeling/util/ModelingUtil'; import { getRoundRectPath, getCirclePath } from '../../draw/BpmnRenderUtil'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('diagram-js/lib/core/Canvas').default} Canvas * @typedef {import('diagram-js/lib/core/GraphicsFactory').default} GraphicsFactory * @typedef {import('../outline/OutlineProvider').default} Outline * @typedef {import('diagram-js/lib/features/selection').default} Selection * * @typedef {import('diagram-js/lib/model/Types').Element} Element */ const ALLOWED_ELEMENTS = [ 'bpmn:Event', 'bpmn:SequenceFlow', 'bpmn:Gateway' ]; const LINE_STYLE = { class: 'bjs-label-link', stroke: 'var(--element-selected-outline-secondary-stroke-color)', strokeDasharray: '5, 5', }; const DISTANCE_THRESHOLD = 15; const PATH_OFFSET = 2; /** * Render a line between an external label and its target element, * when either is selected. * * @param {EventBus} eventBus * @param {Canvas} canvas * @param {GraphicsFactory} graphicsFactory * @param {Outline} outline */ export default function LabelLink(eventBus, canvas, graphicsFactory, outline, selection) { const layer = canvas.getLayer('overlays'); eventBus.on([ 'selection.changed', 'shape.changed' ], function() { cleanUp(); }); eventBus.on('selection.changed', function({ newSelection }) { const allowedElements = newSelection.filter(element => isAny(element, ALLOWED_ELEMENTS)); if (allowedElements.length === 1) { const element = allowedElements[0]; if (isLabel(element)) { createLink(element, element.labelTarget, newSelection); } else if (element.labels?.length) { createLink(element.labels[0], element, newSelection); } } // Only allowed when both label and its target are selected if (allowedElements.length === 2) { const label = allowedElements.find(isLabel); const target = allowedElements.find(el => el.labels?.includes(label)); if (label && target) { createLink(label, target, newSelection); } } }); eventBus.on('shape.changed', function({ element }) { if (!isAny(element, ALLOWED_ELEMENTS) || !isElementSelected(element)) { return; } if (isLabel(element)) { createLink(element, element.labelTarget, selection.get()); } else if (element.labels?.length) { createLink(element.labels[0], element, selection.get()); } }); /** * Render a line between an external label and its target. * * @param {Element} label * @param {Element} target * @param {Element[]} selection */ function createLink(label, target, selection = []) { // Create an auxiliary line between label and target mid points const line = createLine( [ getMid(target), getMid(label) ], LINE_STYLE ); const linePath = line.getAttribute('d'); // Calculate the intersection point between line and label const labelSelected = selection.includes(label); const labelPath = labelSelected ? getElementOutlinePath(label) : getElementPath(label); const labelInter = getElementLineIntersection(labelPath, linePath); // Label on top of the target if (!labelInter) { return; } // Calculate the intersection point between line and label // If the target is a sequence flow, there is no intersection, // so we link to the middle of it. const targetSelected = selection.includes(target); const targetPath = targetSelected ? getElementOutlinePath(target) : getElementPath(target); const targetInter = getElementLineIntersection(targetPath, linePath) || getMid(target); // Do not draw a link if the points are too close const distance = getDistancePointPoint(targetInter, labelInter); if (distance < DISTANCE_THRESHOLD) { return; } // Connect the actual closest points updateLine(line, [ targetInter, labelInter ]); svgAppend(layer, line); } /** * Remove all existing label links. */ function cleanUp() { domQueryAll(`.${LINE_STYLE.class}`, layer).forEach(svgRemove); } /** * Get element's slightly expanded outline path. * * @param {Element} element * @returns {string} svg path */ function getElementOutlinePath(element) { const outlineShape = outline.getOutline(element); const outlineOffset = outline.offset; if (!outlineShape) { return getElementPath(element); } if (outlineShape.x) { const shape = { x: element.x + parseSvgNumAttr(outlineShape, 'x') - PATH_OFFSET, y: element.y + parseSvgNumAttr(outlineShape, 'y') - PATH_OFFSET, width: parseSvgNumAttr(outlineShape, 'width') + PATH_OFFSET * 2, height: parseSvgNumAttr(outlineShape, 'height') + PATH_OFFSET * 2 }; return getRoundRectPath(shape, parseSvgNumAttr(outlineShape, 'rx')); } if (outlineShape.cx) { const shape = { x: element.x - outlineOffset, y: element.y - outlineOffset, width: parseSvgNumAttr(outlineShape, 'r') * 2, height: parseSvgNumAttr(outlineShape, 'r') * 2, }; return getCirclePath(shape); } } function getElementPath(element) { return graphicsFactory.getShapePath(element); } function isElementSelected(element) { return selection.get().includes(element); } } LabelLink.$inject = [ 'eventBus', 'canvas', 'graphicsFactory', 'outline', 'selection' ]; /** * Get numeric attribute from SVG element * or 0 if not present. * * @param {SVGElement} node * @param {string} attr * @returns {number} */ function parseSvgNumAttr(node, attr) { return parseFloat(svgAttr(node, attr) || 0); } ================================================ FILE: lib/features/label-link/index.js ================================================ import SelectionModule from 'diagram-js/lib/features/selection'; import OutlineModule from 'diagram-js/lib/features/outline'; import LabelLink from './LabelLink'; export default { __depends__: [ SelectionModule, OutlineModule ], __init__: [ 'labelLink' ], labelLink: [ 'type', LabelLink ] }; ================================================ FILE: lib/features/modeling/BpmnFactory.js ================================================ import { map, assign, pick } from 'min-dash'; import { isAny } from './util/ModelingUtil'; import { is } from '../../util/ModelUtil'; /** * @typedef {import('../../model/Types').Moddle} Moddle * @typedef {import('../../model/Types').ModdleElement} ModdleElement * * @typedef {import('diagram-js/lib/util/Types').Point} Point */ /** * A factory for BPMN elements. * * @param {Moddle} moddle */ export default function BpmnFactory(moddle) { this._model = moddle; } BpmnFactory.$inject = [ 'moddle' ]; /** * @param {ModdleElement} element * * @return {boolean} */ BpmnFactory.prototype._needsId = function(element) { return isAny(element, [ 'bpmn:RootElement', 'bpmn:FlowElement', 'bpmn:MessageFlow', 'bpmn:DataAssociation', 'bpmn:Artifact', 'bpmn:Participant', 'bpmn:Lane', 'bpmn:LaneSet', 'bpmn:Process', 'bpmn:Collaboration', 'bpmndi:BPMNShape', 'bpmndi:BPMNEdge', 'bpmndi:BPMNDiagram', 'bpmndi:BPMNPlane', 'bpmn:Property', 'bpmn:CategoryValue' ]); }; /** * @param {ModdleElement} element */ BpmnFactory.prototype._ensureId = function(element) { if (element.id) { this._model.ids.claim(element.id, element); return; } // generate semantic ids for elements // bpmn:SequenceFlow -> SequenceFlow_ID var prefix; if (is(element, 'bpmn:Activity')) { prefix = 'Activity'; } else if (is(element, 'bpmn:Event')) { prefix = 'Event'; } else if (is(element, 'bpmn:Gateway')) { prefix = 'Gateway'; } else if (isAny(element, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ])) { prefix = 'Flow'; } else { prefix = (element.$type || '').replace(/^[^:]*:/g, ''); } prefix += '_'; if (!element.id && this._needsId(element)) { element.id = this._model.ids.nextPrefixed(prefix, element); } }; /** * Create BPMN element. * * @param {string} type * @param {Object} [attrs] * * @return {ModdleElement} */ BpmnFactory.prototype.create = function(type, attrs) { var element = this._model.create(type, attrs || {}); this._ensureId(element); return element; }; /** * @return {ModdleElement} */ BpmnFactory.prototype.createDiLabel = function() { return this.create('bpmndi:BPMNLabel', { bounds: this.createDiBounds() }); }; /** * @param {ModdleElement} semantic * @param {Object} [attrs] * @return {ModdleElement} */ BpmnFactory.prototype.createDiShape = function(semantic, attrs) { return this.create('bpmndi:BPMNShape', assign({ bpmnElement: semantic, bounds: this.createDiBounds() }, attrs)); }; /** * @return {ModdleElement} */ BpmnFactory.prototype.createDiBounds = function(bounds) { return this.create('dc:Bounds', bounds); }; /** * @param {Point[]} waypoints * * @return {ModdleElement[]} */ BpmnFactory.prototype.createDiWaypoints = function(waypoints) { var self = this; return map(waypoints, function(pos) { return self.createDiWaypoint(pos); }); }; /** * @param {Point} point * * @return {ModdleElement} */ BpmnFactory.prototype.createDiWaypoint = function(point) { return this.create('dc:Point', pick(point, [ 'x', 'y' ])); }; /** * @param {ModdleElement} semantic * @param {Object} [attrs] * * @return {ModdleElement} */ BpmnFactory.prototype.createDiEdge = function(semantic, attrs) { return this.create('bpmndi:BPMNEdge', assign({ bpmnElement: semantic, waypoint: this.createDiWaypoints([]) }, attrs)); }; /** * @param {ModdleElement} semantic * @param {Object} [attrs] * * @return {ModdleElement} */ BpmnFactory.prototype.createDiPlane = function(semantic, attrs) { return this.create('bpmndi:BPMNPlane', assign({ bpmnElement: semantic }, attrs)); }; ================================================ FILE: lib/features/modeling/BpmnLayouter.js ================================================ import inherits from 'inherits-browser'; import { assign } from 'min-dash'; import BaseLayouter from 'diagram-js/lib/layout/BaseLayouter'; import { repairConnection, withoutRedundantPoints } from 'diagram-js/lib/layout/ManhattanLayout'; import { getMid, getOrientation } from 'diagram-js/lib/layout/LayoutUtil'; import { isExpanded } from '../../util/DiUtil'; import { is } from '../../util/ModelUtil'; import { isDirectionHorizontal } from './util/ModelingUtil'; /** * @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry * * @typedef {import('diagram-js/lib/util/Types').Point} Point * * @typedef {import('../../model/Types').Connection} Connection * @typedef {import('../../model/Types').Element} Element * * @typedef {import('diagram-js/lib/layout/BaseLayouter').LayoutConnectionHints} LayoutConnectionHints * * @typedef { { * source?: Element; * target?: Element; * waypoints?: Point[]; * connectionStart?: Point; * connectionEnd?: Point; * } & LayoutConnectionHints } BpmnLayoutConnectionHints */ var ATTACH_ORIENTATION_PADDING = -10, BOUNDARY_TO_HOST_THRESHOLD = 40; // layout all connection between flow elements h:h, except for // (1) outgoing of boundary events -> layout based on attach orientation and target orientation // (2) incoming/outgoing of gateways -> v:h for outgoing, h:v for incoming // (3) loops connect sides clockwise var PREFERRED_LAYOUTS_HORIZONTAL = { default: [ 'h:h' ], fromGateway: [ 'v:h' ], toGateway: [ 'h:v' ], loop: { fromTop: [ 't:r' ], fromRight: [ 'r:b' ], fromLeft: [ 'l:t' ], fromBottom: [ 'b:l' ] }, boundaryLoop: { alternateHorizontalSide: 'b', alternateVerticalSide: 'l', default: 'v' }, messageFlow: [ 'straight', 'v:v' ], subProcess: [ 'straight', 'h:h' ], isHorizontal: true }; // for vertical layouts, switch h and v and loop counter-clockwise var PREFERRED_LAYOUTS_VERTICAL = { default: [ 'v:v' ], fromGateway: [ 'h:v' ], toGateway: [ 'v:h' ], loop: { fromTop: [ 't:l' ], fromRight: [ 'r:t' ], fromLeft: [ 'l:b' ], fromBottom: [ 'b:r' ] }, boundaryLoop: { alternateHorizontalSide: 't', alternateVerticalSide: 'r', default: 'h' }, messageFlow: [ 'straight', 'h:h' ], subProcess: [ 'straight', 'v:v' ], isHorizontal: false }; var oppositeOrientationMapping = { 'top': 'bottom', 'top-right': 'bottom-left', 'top-left': 'bottom-right', 'right': 'left', 'bottom': 'top', 'bottom-right': 'top-left', 'bottom-left': 'top-right', 'left': 'right' }; var orientationDirectionMapping = { top: 't', right: 'r', bottom: 'b', left: 'l' }; export default function BpmnLayouter(elementRegistry) { this._elementRegistry = elementRegistry; } inherits(BpmnLayouter, BaseLayouter); /** * Returns waypoints of laid out connection. * * @param {Connection} connection * @param {BpmnLayoutConnectionHints} [hints] * * @return {Point[]} */ BpmnLayouter.prototype.layoutConnection = function(connection, hints) { if (!hints) { hints = {}; } var source = hints.source || connection.source, target = hints.target || connection.target, waypoints = hints.waypoints || connection.waypoints, connectionStart = hints.connectionStart, connectionEnd = hints.connectionEnd, elementRegistry = this._elementRegistry; var manhattanOptions, updatedWaypoints; if (!connectionStart) { connectionStart = getConnectionDocking(waypoints && waypoints[ 0 ], source); } if (!connectionEnd) { connectionEnd = getConnectionDocking(waypoints && waypoints[ waypoints.length - 1 ], target); } if (is(connection, 'bpmn:Association') || is(connection, 'bpmn:DataAssociation')) { if (waypoints && !isCompensationAssociation(source, target)) { return [].concat([ connectionStart ], waypoints.slice(1, -1), [ connectionEnd ]); } } var layout = isDirectionHorizontal(source, elementRegistry) ? PREFERRED_LAYOUTS_HORIZONTAL : PREFERRED_LAYOUTS_VERTICAL; if (is(connection, 'bpmn:MessageFlow')) { manhattanOptions = getMessageFlowManhattanOptions(source, target, layout); } else if (is(connection, 'bpmn:SequenceFlow') || isCompensationAssociation(source, target)) { if (source === target) { manhattanOptions = { preferredLayouts: getLoopPreferredLayout(source, connection, layout) }; } else if (is(source, 'bpmn:BoundaryEvent')) { manhattanOptions = { preferredLayouts: getBoundaryEventPreferredLayouts(source, target, connectionEnd, layout) }; } else if (isExpandedSubProcess(source) || isExpandedSubProcess(target)) { manhattanOptions = { preferredLayouts: layout.subProcess, preserveDocking: getSubProcessPreserveDocking(source) }; } else if (is(source, 'bpmn:Gateway')) { manhattanOptions = { preferredLayouts: layout.fromGateway }; } else if (is(target, 'bpmn:Gateway')) { manhattanOptions = { preferredLayouts: layout.toGateway }; } else { manhattanOptions = { preferredLayouts: layout.default }; } } if (manhattanOptions) { manhattanOptions = assign(manhattanOptions, hints); updatedWaypoints = withoutRedundantPoints(repairConnection( source, target, connectionStart, connectionEnd, waypoints, manhattanOptions )); } return updatedWaypoints || [ connectionStart, connectionEnd ]; }; // helpers ////////// function getAttachOrientation(attachedElement) { var hostElement = attachedElement.host; return getOrientation(getMid(attachedElement), hostElement, ATTACH_ORIENTATION_PADDING); } function getMessageFlowManhattanOptions(source, target, layout) { return { preferredLayouts: layout.messageFlow, preserveDocking: getMessageFlowPreserveDocking(source, target) }; } function getMessageFlowPreserveDocking(source, target) { // (1) docking element connected to participant has precedence if (is(target, 'bpmn:Participant')) { return 'source'; } if (is(source, 'bpmn:Participant')) { return 'target'; } // (2) docking element connected to expanded sub-process has precedence if (isExpandedSubProcess(target)) { return 'source'; } if (isExpandedSubProcess(source)) { return 'target'; } // (3) docking event has precedence if (is(target, 'bpmn:Event')) { return 'target'; } if (is(source, 'bpmn:Event')) { return 'source'; } return null; } function getSubProcessPreserveDocking(source) { return isExpandedSubProcess(source) ? 'target' : 'source'; } function getConnectionDocking(point, shape) { return point ? (point.original || point) : getMid(shape); } function isCompensationAssociation(source, target) { return is(target, 'bpmn:Activity') && is(source, 'bpmn:BoundaryEvent') && target.businessObject.isForCompensation; } function isExpandedSubProcess(element) { return is(element, 'bpmn:SubProcess') && isExpanded(element); } function isSame(a, b) { return a === b; } function isAnyOrientation(orientation, orientations) { return orientations.indexOf(orientation) !== -1; } function getHorizontalOrientation(orientation) { var matches = /right|left/.exec(orientation); return matches && matches[0]; } function getVerticalOrientation(orientation) { var matches = /top|bottom/.exec(orientation); return matches && matches[0]; } function isOppositeOrientation(a, b) { return oppositeOrientationMapping[a] === b; } function isOppositeHorizontalOrientation(a, b) { var horizontalOrientation = getHorizontalOrientation(a); var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation]; return b.indexOf(oppositeHorizontalOrientation) !== -1; } function isOppositeVerticalOrientation(a, b) { var verticalOrientation = getVerticalOrientation(a); var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation]; return b.indexOf(oppositeVerticalOrientation) !== -1; } function isHorizontalOrientation(orientation) { return orientation === 'right' || orientation === 'left'; } function getLoopPreferredLayout(source, connection, layout) { var waypoints = connection.waypoints; var orientation = waypoints && waypoints.length && getOrientation(waypoints[0], source); if (orientation === 'top') { return layout.loop.fromTop; } else if (orientation === 'right') { return layout.loop.fromRight; } else if (orientation === 'left') { return layout.loop.fromLeft; } return layout.loop.fromBottom; } function getBoundaryEventPreferredLayouts(source, target, end, layout) { var sourceMid = getMid(source), targetMid = getMid(target), attachOrientation = getAttachOrientation(source), sourceLayout, targetLayout; var isLoop = isSame(source.host, target); var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]); var targetOrientation = getOrientation(targetMid, sourceMid, { x: source.width / 2 + target.width / 2, y: source.height / 2 + target.height / 2 }); if (isLoop) { return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end, layout); } // source layout sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, layout.isHorizontal); // target layout targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, layout.isHorizontal); return [ sourceLayout + ':' + targetLayout ]; } function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end, layout) { var orientation = attachedToSide ? attachOrientation : layout.isHorizontal ? getVerticalOrientation(attachOrientation) : getHorizontalOrientation(attachOrientation), sourceLayout = orientationDirectionMapping[ orientation ], targetLayout; if (attachedToSide) { if (isHorizontalOrientation(attachOrientation)) { targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : layout.boundaryLoop.alternateHorizontalSide; } else { targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : layout.boundaryLoop.alternateVerticalSide; } } else { targetLayout = layout.boundaryLoop.default; } return [ sourceLayout + ':' + targetLayout ]; } function shouldConnectToSameSide(axis, source, target, end) { var threshold = BOUNDARY_TO_HOST_THRESHOLD; return !( areCloseOnAxis(axis, end, target, threshold) || areCloseOnAxis(axis, end, { x: target.x + target.width, y: target.y + target.height }, threshold) || areCloseOnAxis(axis, end, getMid(source), threshold) ); } function areCloseOnAxis(axis, a, b, threshold) { return Math.abs(a[ axis ] - b[ axis ]) < threshold; } function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isHorizontal) { // attached to either top, right, bottom or left side if (attachedToSide) { return orientationDirectionMapping[ attachOrientation ]; } // attached to either top-right, top-left, bottom-right or bottom-left corner var verticalAttachOrientation = getVerticalOrientation(attachOrientation), horizontalAttachOrientation = getHorizontalOrientation(attachOrientation), verticalTargetOrientation = getVerticalOrientation(targetOrientation), horizontalTargetOrientation = getHorizontalOrientation(targetOrientation); if (isHorizontal) { // same vertical or opposite horizontal orientation if ( isSame(verticalAttachOrientation, verticalTargetOrientation) || isOppositeOrientation(horizontalAttachOrientation, horizontalTargetOrientation) ) { return orientationDirectionMapping[ verticalAttachOrientation ]; } } else { // same horizontal or opposite vertical orientation if ( isSame(horizontalAttachOrientation, horizontalTargetOrientation) || isOppositeOrientation(verticalAttachOrientation, verticalTargetOrientation) ) { return orientationDirectionMapping[ horizontalAttachOrientation ]; } } // fallback return orientationDirectionMapping[ isHorizontal ? horizontalAttachOrientation : verticalAttachOrientation ]; } function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isHorizontal) { // attached to either top, right, bottom or left side if (attachedToSide) { if (isHorizontalOrientation(attachOrientation)) { // orientation is right or left // opposite horizontal orientation or same orientation if ( isOppositeHorizontalOrientation(attachOrientation, targetOrientation) || isSame(attachOrientation, targetOrientation) ) { return 'h'; } // fallback return 'v'; } else { // orientation is top or bottom // opposite vertical orientation or same orientation if ( isOppositeVerticalOrientation(attachOrientation, targetOrientation) || isSame(attachOrientation, targetOrientation) ) { return 'v'; } // fallback return 'h'; } } // attached to either top-right, top-left, bottom-right or bottom-left corner, // or strictly above/below or left/right of the target. In the corner case, // the orientation is compared on the counter-axis to decide the layout. var verticalAttachOrientation = getVerticalOrientation(attachOrientation), horizontalAttachOrientation = getHorizontalOrientation(attachOrientation), verticalTargetOrientation = getVerticalOrientation(targetOrientation), horizontalTargetOrientation = getHorizontalOrientation(targetOrientation); // If the target is strictly above/below (no horizontal orientation) if (verticalTargetOrientation && !horizontalTargetOrientation) { return 'v'; } // If the target is strictly left/right (no vertical orientation) if (horizontalTargetOrientation && !verticalTargetOrientation) { return 'h'; } if (isHorizontal) { if (isSame(verticalAttachOrientation, verticalTargetOrientation)) { return 'h'; } else { return 'v'; } } else { if (isSame(horizontalAttachOrientation, horizontalTargetOrientation)) { return 'v'; } else { return 'h'; } } } BpmnLayouter.$inject = [ 'elementRegistry' ]; ================================================ FILE: lib/features/modeling/BpmnUpdater.js ================================================ import { assign, forEach } from 'min-dash'; import inherits from 'inherits-browser'; import { add as collectionAdd, remove as collectionRemove } from 'diagram-js/lib/util/Collections'; import { getBusinessObject, getDi, is } from '../../util/ModelUtil'; import { isAny } from './util/ModelingUtil'; import { getLabel, isLabel, isLabelExternal } from '../../util/LabelUtil'; import { isPlane } from '../../util/DrilldownUtil'; import { delta } from 'diagram-js/lib/util/PositionUtil'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; /** * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus * @typedef {import('./BpmnFactory').default} BpmnFactory * @typedef {import('diagram-js/lib/layout/CroppingConnectionDocking').default} CroppingConnectionDocking * * @typedef {import('../../model/Types').Connection} Connection * @typedef {import('../../model/Types').Element} Element * @typedef {import('../../model/Types').Shape} Shape * @typedef {import('../../model/Types').Parent} Parent * @typedef {import('../../model/Types').ModdleElement} ModdleElement */ /** * A handler responsible for updating the underlying BPMN 2.0 XML & DI * once changes on the diagram happen. * * @param {EventBus} eventBus * @param {BpmnFactory} bpmnFactory * @param {CroppingConnectionDocking} connectionDocking */ export default function BpmnUpdater( eventBus, bpmnFactory, connectionDocking ) { CommandInterceptor.call(this, eventBus); this._bpmnFactory = bpmnFactory; var self = this; // connection cropping ////////////////////// // crop connection ends during create/update function cropConnection(e) { var context = e.context, hints = context.hints || {}, connection; if (!context.cropped && hints.createElementsBehavior !== false) { connection = context.connection; connection.waypoints = connectionDocking.getCroppedWaypoints(connection); context.cropped = true; } } this.executed([ 'connection.layout', 'connection.create' ], cropConnection); this.reverted([ 'connection.layout' ], function(e) { delete e.context.cropped; }); // BPMN + DI update ////////////////////// // update parent function updateParent(e) { var context = e.context; self.updateParent(context.shape || context.connection, context.oldParent); } function reverseUpdateParent(e) { var context = e.context; var element = context.shape || context.connection, // oldParent is the (old) new parent, because we are undoing oldParent = context.parent || context.newParent; self.updateParent(element, oldParent); } this.executed([ 'shape.move', 'shape.create', 'shape.delete', 'connection.create', 'connection.move', 'connection.delete' ], ifBpmn(updateParent)); this.reverted([ 'shape.move', 'shape.create', 'shape.delete', 'connection.create', 'connection.move', 'connection.delete' ], ifBpmn(reverseUpdateParent)); /* * ## Updating Parent * * When morphing a Process into a Collaboration or vice-versa, * make sure that both the *semantic* and *di* parent of each element * is updated. * */ function updateRoot(event) { var context = event.context, oldRoot = context.oldRoot, children = oldRoot.children; forEach(children, function(child) { if (is(child, 'bpmn:BaseElement')) { self.updateParent(child); } }); } this.executed([ 'canvas.updateRoot' ], updateRoot); this.reverted([ 'canvas.updateRoot' ], updateRoot); // update bounds function updateBounds(e) { var shape = e.context.shape; if (!is(shape, 'bpmn:BaseElement')) { return; } self.updateBounds(shape); } this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) { // exclude labels because they're handled separately during shape.changed if (event.context.shape.type === 'label') { return; } updateBounds(event); })); this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) { // exclude labels because they're handled separately during shape.changed if (event.context.shape.type === 'label') { return; } updateBounds(event); })); // Handle labels separately. This is necessary, because the label bounds have to be updated // every time its shape changes, not only on move, create and resize. eventBus.on('shape.changed', function(event) { if (event.element.type === 'label') { updateBounds({ context: { shape: event.element } }); } }); // attach / detach connection function updateConnection(e) { self.updateConnection(e.context); } this.executed([ 'connection.create', 'connection.move', 'connection.delete', 'connection.reconnect' ], ifBpmn(updateConnection)); this.reverted([ 'connection.create', 'connection.move', 'connection.delete', 'connection.reconnect' ], ifBpmn(updateConnection)); // update waypoints function updateConnectionWaypoints(e) { self.updateConnectionWaypoints(e.context.connection); } this.executed([ 'connection.layout', 'connection.move', 'connection.updateWaypoints', ], ifBpmn(updateConnectionWaypoints)); this.reverted([ 'connection.layout', 'connection.move', 'connection.updateWaypoints', ], ifBpmn(updateConnectionWaypoints)); // update conditional/default flows this.executed('connection.reconnect', ifBpmn(function(event) { var context = event.context, connection = context.connection, oldSource = context.oldSource, newSource = context.newSource, connectionBo = getBusinessObject(connection), oldSourceBo = getBusinessObject(oldSource), newSourceBo = getBusinessObject(newSource); // remove condition from connection on reconnect to new source // if new source can NOT have condional sequence flow if (connectionBo.conditionExpression && !isAny(newSourceBo, [ 'bpmn:Activity', 'bpmn:ExclusiveGateway', 'bpmn:InclusiveGateway' ])) { context.oldConditionExpression = connectionBo.conditionExpression; delete connectionBo.conditionExpression; } // remove default from old source flow on reconnect to new source // if source changed if (oldSource !== newSource && oldSourceBo.default === connectionBo) { context.oldDefault = oldSourceBo.default; delete oldSourceBo.default; } })); this.reverted('connection.reconnect', ifBpmn(function(event) { var context = event.context, connection = context.connection, oldSource = context.oldSource, newSource = context.newSource, connectionBo = getBusinessObject(connection), oldSourceBo = getBusinessObject(oldSource), newSourceBo = getBusinessObject(newSource); // add condition to connection on revert reconnect to new source if (context.oldConditionExpression) { connectionBo.conditionExpression = context.oldConditionExpression; } // add default to old source on revert reconnect to new source if (context.oldDefault) { oldSourceBo.default = context.oldDefault; delete newSourceBo.default; } })); // update attachments function updateAttachment(e) { self.updateAttachment(e.context); } this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment)); this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment)); // update BPMNLabel this.executed('element.updateLabel', ifBpmn(updateBPMNLabel)); this.reverted('element.updateLabel', ifBpmn(updateBPMNLabel)); function updateBPMNLabel(event) { const { element } = event.context, label = getLabel(element); const di = getDi(element), diLabel = di && di.get('label'); if (isLabelExternal(element) || isPlane(element)) { return; } if (label && !diLabel) { di.set('label', bpmnFactory.create('bpmndi:BPMNLabel')); } else if (!label && diLabel) { di.set('label', undefined); } } } inherits(BpmnUpdater, CommandInterceptor); BpmnUpdater.$inject = [ 'eventBus', 'bpmnFactory', 'connectionDocking' ]; // implementation ////////////////////// /** * @param { { * shape: Shape; * host: Shape; * } } context */ BpmnUpdater.prototype.updateAttachment = function(context) { var shape = context.shape, businessObject = shape.businessObject, host = shape.host; businessObject.attachedToRef = host && host.businessObject; }; /** * @param {Element} element * @param {Parent} oldParent */ BpmnUpdater.prototype.updateParent = function(element, oldParent) { // do not update BPMN 2.0 label parent if (isLabel(element)) { return; } // data stores in collaborations are handled separately by DataStoreBehavior if (is(element, 'bpmn:DataStoreReference') && element.parent && is(element.parent, 'bpmn:Collaboration')) { return; } var parentShape = element.parent; var businessObject = element.businessObject, di = getDi(element), parentBusinessObject = parentShape && parentShape.businessObject, parentDi = getDi(parentShape); if (is(element, 'bpmn:FlowNode')) { this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject); } if (is(element, 'bpmn:DataOutputAssociation')) { if (element.source) { parentBusinessObject = element.source.businessObject; } else { parentBusinessObject = null; } } if (is(element, 'bpmn:DataInputAssociation')) { if (element.target) { parentBusinessObject = element.target.businessObject; } else { parentBusinessObject = null; } } this.updateSemanticParent(businessObject, parentBusinessObject); if (is(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) { this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject); } this.updateDiParent(di, parentDi); }; /** * @param {Shape} shape */ BpmnUpdater.prototype.updateBounds = function(shape) { var di = getDi(shape), embeddedLabelBounds = getEmbeddedLabelBounds(shape); // update embedded label bounds if possible if (embeddedLabelBounds) { var embeddedLabelBoundsDelta = delta(embeddedLabelBounds, di.get('bounds')); assign(embeddedLabelBounds, { x: shape.x + embeddedLabelBoundsDelta.x, y: shape.y + embeddedLabelBoundsDelta.y }); } var target = isLabel(shape) ? this._getLabel(di) : di; var bounds = target.bounds; if (!bounds) { bounds = this._bpmnFactory.createDiBounds(); target.set('bounds', bounds); } assign(bounds, { x: shape.x, y: shape.y, width: shape.width, height: shape.height }); }; /** * @param {ModdleElement} businessObject * @param {ModdleElement} newContainment * @param {ModdleElement} oldContainment */ BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) { if (oldContainment === newContainment) { return; } var oldRefs, newRefs; if (is (oldContainment, 'bpmn:Lane')) { oldRefs = oldContainment.get('flowNodeRef'); collectionRemove(oldRefs, businessObject); } if (is(newContainment, 'bpmn:Lane')) { newRefs = newContainment.get('flowNodeRef'); collectionAdd(newRefs, businessObject); } }; /** * @param {Connection} connection * @param {Element} newSource * @param {Element} newTarget */ BpmnUpdater.prototype.updateDiConnection = function(connection, newSource, newTarget) { var connectionDi = getDi(connection), newSourceDi = getDi(newSource), newTargetDi = getDi(newTarget); if (connectionDi.sourceElement && connectionDi.sourceElement.bpmnElement !== getBusinessObject(newSource)) { connectionDi.sourceElement = newSource && newSourceDi; } if (connectionDi.targetElement && connectionDi.targetElement.bpmnElement !== getBusinessObject(newTarget)) { connectionDi.targetElement = newTarget && newTargetDi; } }; /** * @param {ModdleElement} di * @param {ModdleElement} parentDi */ BpmnUpdater.prototype.updateDiParent = function(di, parentDi) { if (parentDi && !is(parentDi, 'bpmndi:BPMNPlane')) { parentDi = parentDi.$parent; } if (di.$parent === parentDi) { return; } var planeElements = (parentDi || di.$parent).get('planeElement'); if (parentDi) { planeElements.push(di); di.$parent = parentDi; } else { collectionRemove(planeElements, di); di.$parent = null; } }; /** * @param {ModdleElement} element * * @return {ModdleElement} */ function getDefinitions(element) { while (element && !is(element, 'bpmn:Definitions')) { element = element.$parent; } return element; } /** * @param {ModdleElement} container * * @return {ModdleElement} */ BpmnUpdater.prototype.getLaneSet = function(container) { var laneSet, laneSets; // bpmn:Lane if (is(container, 'bpmn:Lane')) { laneSet = container.childLaneSet; if (!laneSet) { laneSet = this._bpmnFactory.create('bpmn:LaneSet'); container.childLaneSet = laneSet; laneSet.$parent = container; } return laneSet; } // bpmn:Participant if (is(container, 'bpmn:Participant')) { container = container.processRef; } // bpmn:FlowElementsContainer laneSets = container.get('laneSets'); laneSet = laneSets[0]; if (!laneSet) { laneSet = this._bpmnFactory.create('bpmn:LaneSet'); laneSet.$parent = container; laneSets.push(laneSet); } return laneSet; }; /** * @param {ModdleElement} businessObject * @param {ModdleElement} newParent * @param {ModdleElement} visualParent */ BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) { var containment; if (businessObject.$parent === newParent) { return; } if (is(businessObject, 'bpmn:DataInput') || is(businessObject, 'bpmn:DataOutput')) { if (is(newParent, 'bpmn:Participant') && 'processRef' in newParent) { newParent = newParent.processRef; } // already in correct ioSpecification if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) { return; } } if (is(businessObject, 'bpmn:Lane')) { if (newParent) { newParent = this.getLaneSet(newParent); } containment = 'lanes'; } else if (is(businessObject, 'bpmn:FlowElement')) { if (newParent) { if (is(newParent, 'bpmn:Participant')) { newParent = newParent.processRef; } else if (is(newParent, 'bpmn:Lane')) { do { // unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer) newParent = newParent.$parent.$parent; } while (is(newParent, 'bpmn:Lane')); } } containment = 'flowElements'; } else if (is(businessObject, 'bpmn:Artifact')) { while (newParent && !is(newParent, 'bpmn:Process') && !is(newParent, 'bpmn:SubProcess') && !is(newParent, 'bpmn:Collaboration')) { if (is(newParent, 'bpmn:Participant')) { newParent = newParent.processRef; break; } else { newParent = newParent.$parent; } } containment = 'artifacts'; } else if (is(businessObject, 'bpmn:MessageFlow')) { containment = 'messageFlows'; } else if (is(businessObject, 'bpmn:Participant')) { containment = 'participants'; // make sure the participants process is properly attached / detached // from the XML document var process = businessObject.processRef, definitions; if (process) { definitions = getDefinitions(businessObject.$parent || newParent); if (businessObject.$parent) { collectionRemove(definitions.get('rootElements'), process); process.$parent = null; } if (newParent) { collectionAdd(definitions.get('rootElements'), process); process.$parent = definitions; } } } else if (is(businessObject, 'bpmn:DataOutputAssociation')) { containment = 'dataOutputAssociations'; } else if (is(businessObject, 'bpmn:DataInputAssociation')) { containment = 'dataInputAssociations'; } if (!containment) { throw new Error(`no parent for <${ businessObject.id }> in <${ newParent.id }>`); } var children; if (businessObject.$parent) { // remove from old parent children = businessObject.$parent.get(containment); collectionRemove(children, businessObject); } if (!newParent) { businessObject.$parent = null; } else { // add to new parent children = newParent.get(containment); children.push(businessObject); businessObject.$parent = newParent; } if (visualParent) { var diChildren = visualParent.get(containment); collectionRemove(children, businessObject); if (newParent) { if (!diChildren) { diChildren = []; newParent.set(containment, diChildren); } diChildren.push(businessObject); } } }; /** * @param {Connection} connection */ BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) { var di = getDi(connection); di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints)); }; /** * @param { { * connection: Connection; * parent: Parent; * newParent: Parent; * } } context */ BpmnUpdater.prototype.updateConnection = function(context) { var connection = context.connection, businessObject = getBusinessObject(connection), newSource = connection.source, newSourceBo = getBusinessObject(newSource), newTarget = connection.target, newTargetBo = getBusinessObject(connection.target), visualParent; if (!is(businessObject, 'bpmn:DataAssociation')) { var inverseSet = is(businessObject, 'bpmn:SequenceFlow'); if (businessObject.sourceRef !== newSourceBo) { if (inverseSet) { collectionRemove(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject); if (newSourceBo && newSourceBo.get('outgoing')) { newSourceBo.get('outgoing').push(businessObject); } } businessObject.sourceRef = newSourceBo; } if (businessObject.targetRef !== newTargetBo) { if (inverseSet) { collectionRemove(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject); if (newTargetBo && newTargetBo.get('incoming')) { newTargetBo.get('incoming').push(businessObject); } } businessObject.targetRef = newTargetBo; } } else if (is(businessObject, 'bpmn:DataInputAssociation')) { // handle obnoxious isMsome sourceRef businessObject.get('sourceRef')[0] = newSourceBo; visualParent = context.parent || context.newParent || newTargetBo; this.updateSemanticParent(businessObject, newTargetBo, visualParent); } else if (is(businessObject, 'bpmn:DataOutputAssociation')) { visualParent = context.parent || context.newParent || newSourceBo; this.updateSemanticParent(businessObject, newSourceBo, visualParent); // targetRef = new target businessObject.targetRef = newTargetBo; } this.updateConnectionWaypoints(connection); this.updateDiConnection(connection, newSource, newTarget); }; // helpers ////////////////////// BpmnUpdater.prototype._getLabel = function(di) { if (!di.label) { di.label = this._bpmnFactory.createDiLabel(); } return di.label; }; /** * Call function if shape or connection is BPMN element. * * @param {Function} fn * * @return {Function} */ function ifBpmn(fn) { return function(event) { var context = event.context, element = context.shape || context.connection || context.element; if (is(element, 'bpmn:BaseElement')) { fn(event); } }; } /** * Return dc:Bounds of bpmndi:BPMNLabel if exists. * * @param {Shape} shape * * @return {ModdleElement|undefined} */ function getEmbeddedLabelBounds(shape) { if (!is(shape, 'bpmn:Activity')) { return; } var di = getDi(shape); if (!di) { return; } var label = di.get('label'); if (!label) { return; } return label.get('bounds'); } ================================================ FILE: lib/features/modeling/ElementFactory.js ================================================ import { assign, forEach, has, isDefined, isObject, omit } from 'min-dash'; import inherits from 'inherits-browser'; import { getBusinessObject, getDi, is } from '../../util/ModelUtil'; import { isAny } from '../modeling/util/ModelingUtil'; import { isExpanded } from '../../util/DiUtil'; import BaseElementFactory from 'diagram-js/lib/core/ElementFactory'; import { DEFAULT_LABEL_SIZE } from '../../util/LabelUtil'; import { ensureCompatDiRef } from '../../util/CompatibilityUtil'; /** * @typedef {import('diagram-js/lib/util/Types').Dimensions} Dimensions * * @typedef {import('./BpmnFactory').default} BpmnFactory * * @typedef {import('../../model/Types').BpmnAttributes} BpmnAttributes * @typedef {import('../../model/Types').Connection} Connection * @typedef {import('../../model/Types').Element} Element * @typedef {import('../../model/Types').Label} Label * @typedef {import('../../model/Types').Root} Root * @typedef {import('../../model/Types').Shape} Shape * @typedef {import('../../model/Types').Moddle} Moddle * @typedef {import('../../model/Types').ModdleElement} ModdleElement */ /** * A BPMN-specific element factory. * * @template {Connection} [T=Connection] * @template {Label} [U=Label] * @template {Root} [V=Root] * @template {Shape} [W=Shape] * * @extends {BaseElementFactory} * * @param {BpmnFactory} bpmnFactory * @param {Moddle} moddle */ export default function ElementFactory(bpmnFactory, moddle) { BaseElementFactory.call(this); this._bpmnFactory = bpmnFactory; this._moddle = moddle; } inherits(ElementFactory, BaseElementFactory); ElementFactory.$inject = [ 'bpmnFactory', 'moddle' ]; ElementFactory.prototype._baseCreate = BaseElementFactory.prototype.create; /** * Create a root element. * * @overlord * @param {'root'} elementType * @param {Partial & Partial} [attrs] * @return {V} */ /** * Create a shape. * * @overlord * @param {'shape'} elementType * @param {Partial & Partial} [attrs] * @return {W} */ /** * Create a connection. * * @overlord * @param {'connection'} elementType * @param {Partial & Partial} [attrs] * @return {T} */ /** * Create a label. * * @param {'label'} elementType * @param {Partial