-
+
+
+ Definition: {definitionJson.length} bytes
+ Selected step: {selectedStepId}
+ Is readonly: {yesOrNo(isReadonly)}
+ Is valid: {definition.isValid === undefined ? '?' : yesOrNo(definition.isValid)}
+ Is toolbox collapsed: {yesOrNo(isToolboxCollapsed)}
+ Is editor collapsed: {yesOrNo(isEditorCollapsed)}
+
+
+
+ Toggle visibility
+ Reload definition
+ Toggle selection
+ Toggle readonly
+ Toggle toolbox
+ Toggle editor
+ Move viewport to first step
+ Append step
+
+
+
+
+
>
);
diff --git a/demos/react-app/src/playground/RootEditor.tsx b/demos/react-app/src/playground/RootEditor.tsx
index 1f3405e8..b88f6c79 100644
--- a/demos/react-app/src/playground/RootEditor.tsx
+++ b/demos/react-app/src/playground/RootEditor.tsx
@@ -11,7 +11,9 @@ export function RootEditor() {
return (
<>
-
Root editor
+
π Playground Demo
+
+
This demo showcases how several features of the Sequential Workflow Designer can be used within a React application.
Alfa
diff --git a/demos/react-app/src/playground/StepEditor.tsx b/demos/react-app/src/playground/StepEditor.tsx
index 3ce85424..d054869a 100644
--- a/demos/react-app/src/playground/StepEditor.tsx
+++ b/demos/react-app/src/playground/StepEditor.tsx
@@ -31,7 +31,7 @@ export function StepEditor() {
return (
<>
-
Step Editor {type}
+
Step Editor - {type}
Name
diff --git a/demos/react-app/src/saveRequiredEditor/ChangeController.tsx b/demos/react-app/src/saveRequiredEditor/ChangeController.tsx
new file mode 100644
index 00000000..66d102c1
--- /dev/null
+++ b/demos/react-app/src/saveRequiredEditor/ChangeController.tsx
@@ -0,0 +1,42 @@
+import { useEffect, useState } from 'react';
+import { SimpleEvent } from 'sequential-workflow-designer';
+
+export class ChangeController {
+ public readonly onIsChangedChanged = new SimpleEvent
();
+ public isChanged = false;
+
+ public set(isChanged: boolean) {
+ if (this.isChanged !== isChanged) {
+ this.isChanged = isChanged;
+ this.onIsChangedChanged.forward(isChanged);
+ }
+ }
+}
+
+export interface ChangeControllerWrapper {
+ controller: ChangeController;
+ isChanged: boolean;
+}
+
+export function useChangeControllerWrapper(controller: ChangeController): ChangeControllerWrapper {
+ const [wrapper, setWrapper] = useState(() => ({
+ controller,
+ isChanged: controller.isChanged
+ }));
+
+ useEffect(() => {
+ function onIsDirtyChanged(isChanged: boolean) {
+ setWrapper({
+ ...wrapper,
+ isChanged
+ });
+ }
+
+ wrapper.controller.onIsChangedChanged.subscribe(onIsDirtyChanged);
+ return () => {
+ wrapper.controller.onIsChangedChanged.unsubscribe(onIsDirtyChanged);
+ };
+ }, [wrapper]);
+
+ return wrapper;
+}
diff --git a/demos/react-app/src/saveRequiredEditor/RootEditor.tsx b/demos/react-app/src/saveRequiredEditor/RootEditor.tsx
new file mode 100644
index 00000000..24e72ec6
--- /dev/null
+++ b/demos/react-app/src/saveRequiredEditor/RootEditor.tsx
@@ -0,0 +1,16 @@
+export function RootEditor() {
+ return (
+ <>
+ π΄ Save Required Editor Demo
+
+
+ This demo shows how to implement an editor that requires saving changes before theyβre applied to the workflow definition.
+
+
+
+ It also demonstrates how to prevent users from navigating away from the editor when there are unsaved changes. To test this
+ feature, edit the name of any step and then try to unselect that step on the canvas.
+
+ >
+ );
+}
diff --git a/demos/react-app/src/saveRequiredEditor/SaveRequiredEditor.tsx b/demos/react-app/src/saveRequiredEditor/SaveRequiredEditor.tsx
new file mode 100644
index 00000000..0056ec25
--- /dev/null
+++ b/demos/react-app/src/saveRequiredEditor/SaveRequiredEditor.tsx
@@ -0,0 +1,104 @@
+import { useEffect, useMemo, useState } from 'react';
+import { Definition, Step, StepsConfiguration, Uid } from 'sequential-workflow-designer';
+import { SequentialWorkflowDesigner, wrapDefinition, WrappedDefinition } from 'sequential-workflow-designer-react';
+import { StepEditor } from './StepEditor';
+import { RootEditor } from './RootEditor';
+import { ChangeController, useChangeControllerWrapper } from './ChangeController';
+
+import './style.css';
+
+function createStep(name: string): Step {
+ return {
+ id: Uid.next(),
+ name,
+ componentType: 'task',
+ type: 'task',
+ properties: {}
+ };
+}
+
+function createDefinition(): Definition {
+ return {
+ sequence: [createStep('Alfa'), createStep('Beta'), createStep('Gamma')],
+ properties: {}
+ };
+}
+
+export function SaveRequiredEditor() {
+ const changeController = useMemo(() => new ChangeController(), []);
+ const changeControllerWrapper = useChangeControllerWrapper(changeController);
+
+ const [definition, setDefinition] = useState(() => wrapDefinition(createDefinition()));
+ const [isUnselectionBlocked, setIsUnselectionBlocked] = useState(false);
+
+ const stepsConfiguration: StepsConfiguration = useMemo(
+ () => ({
+ canUnselectStep: () => !changeController.isChanged
+ }),
+ [changeController]
+ );
+
+ useEffect(() => {
+ if (isUnselectionBlocked) {
+ let to: ReturnType | null = setTimeout(() => {
+ setIsUnselectionBlocked(false);
+ to = null;
+ }, 2000);
+ return () => {
+ if (to) {
+ clearTimeout(to);
+ }
+ };
+ }
+ }, [isUnselectionBlocked]);
+
+ function onDefinitionChange(definition: WrappedDefinition) {
+ setDefinition(definition);
+ if (changeController.isChanged) {
+ changeController.set(false);
+ }
+ }
+
+ function onStepUnselectionBlocked() {
+ if (!isUnselectionBlocked) {
+ setIsUnselectionBlocked(true);
+ }
+ }
+
+ function onSelectedStepIdChanged() {
+ if (changeController.isChanged) {
+ // We need to reset the change controller when the selected step id is changed,
+ // for example this happens when a step is deleted.
+ changeController.set(false);
+ }
+ }
+
+ return (
+ <>
+ {isUnselectionBlocked && Please save or cancel changes before unselecting the step.
}
+
+
+ }
+ stepEditor={ }
+ />
+
+
+
+
+ Unsaved changes: {changeControllerWrapper.isChanged ? 'β
Yes' : 'β No'}
+
+
+ >
+ );
+}
diff --git a/demos/react-app/src/saveRequiredEditor/StepEditor.tsx b/demos/react-app/src/saveRequiredEditor/StepEditor.tsx
new file mode 100644
index 00000000..ace4816a
--- /dev/null
+++ b/demos/react-app/src/saveRequiredEditor/StepEditor.tsx
@@ -0,0 +1,45 @@
+import { ChangeEvent, useState } from 'react';
+import { Step } from 'sequential-workflow-designer';
+import { useStepEditor } from 'sequential-workflow-designer-react';
+import { ChangeController } from './ChangeController';
+
+export function StepEditor(props: { changeController: ChangeController }) {
+ const { name, setName } = useStepEditor();
+ const [currentName, setCurrentName] = useState(() => name);
+ const isDirty = name !== currentName;
+
+ function onNameChanged(e: ChangeEvent) {
+ const newName = (e.target as HTMLInputElement).value;
+ setCurrentName(newName);
+ props.changeController.set(name !== newName);
+ }
+
+ function onSave() {
+ setName(currentName);
+ // No need to reset the change controller here, it's already reset
+ // in the parent component when the definition changes.
+ }
+
+ function onCancel() {
+ setCurrentName(name);
+ props.changeController.set(false);
+ }
+
+ return (
+ <>
+ Step Editor
+
+ Name
+
+
+
+
+ Save
+ {' '}
+
+ Cancel
+
+
+ >
+ );
+}
diff --git a/demos/react-app/src/saveRequiredEditor/style.css b/demos/react-app/src/saveRequiredEditor/style.css
new file mode 100644
index 00000000..8d042707
--- /dev/null
+++ b/demos/react-app/src/saveRequiredEditor/style.css
@@ -0,0 +1,11 @@
+.unselectionBlocked {
+ position: fixed;
+ z-index: 9999;
+ left: 0;
+ top: 0;
+ right: 0;
+ background: #ed4800;
+ color: white;
+ text-align: center;
+ padding: 20px;
+}
diff --git a/demos/svelte-app/package.json b/demos/svelte-app/package.json
index 5f677b38..9c2de47a 100644
--- a/demos/svelte-app/package.json
+++ b/demos/svelte-app/package.json
@@ -16,8 +16,8 @@
"eslint": "eslint ./src --ext .ts"
},
"dependencies": {
- "sequential-workflow-designer": "^0.32.0",
- "sequential-workflow-designer-svelte": "^0.32.0"
+ "sequential-workflow-designer": "^0.33.0",
+ "sequential-workflow-designer-svelte": "^0.33.0"
},
"devDependencies": {
"@sveltejs/adapter-static": "^2.0.3",
diff --git a/designer/package.json b/designer/package.json
index 202ce65e..8517d21b 100644
--- a/designer/package.json
+++ b/designer/package.json
@@ -1,7 +1,7 @@
{
"name": "sequential-workflow-designer",
"description": "Customizable no-code component for building flow-based programming applications.",
- "version": "0.32.0",
+ "version": "0.33.0",
"type": "module",
"main": "./lib/esm/index.js",
"types": "./lib/index.d.ts",
diff --git a/designer/src/api/control-bar-api.ts b/designer/src/api/control-bar-api.ts
index 7582ee29..1088e6fc 100644
--- a/designer/src/api/control-bar-api.ts
+++ b/designer/src/api/control-bar-api.ts
@@ -71,8 +71,7 @@ export class ControlBarApi {
public tryDelete(): boolean {
if (this.canDelete() && this.state.selectedStepId) {
- this.stateModifier.tryDelete(this.state.selectedStepId);
- return true;
+ return this.stateModifier.tryDeleteById(this.state.selectedStepId);
}
return false;
}
@@ -82,7 +81,7 @@ export class ControlBarApi {
!!this.state.selectedStepId &&
!this.state.isReadonly &&
!this.state.isDragging &&
- this.stateModifier.isDeletable(this.state.selectedStepId)
+ this.stateModifier.isDeletableById(this.state.selectedStepId)
);
}
}
diff --git a/designer/src/behaviors/move-viewport-behavior.ts b/designer/src/behaviors/move-viewport-behavior.ts
index 743039bc..bbf5f728 100644
--- a/designer/src/behaviors/move-viewport-behavior.ts
+++ b/designer/src/behaviors/move-viewport-behavior.ts
@@ -9,6 +9,8 @@ export class MoveViewportBehavior implements Behavior {
return new MoveViewportBehavior(context.state.viewport.position, resetSelectedStep, context.state, context.stateModifier);
}
+ private lastDelta?: Vector;
+
private constructor(
private readonly startPosition: Vector,
private readonly resetSelectedStep: boolean,
@@ -17,17 +19,12 @@ export class MoveViewportBehavior implements Behavior {
) {}
public onStart() {
- if (this.resetSelectedStep) {
- const stepIdOrNull = this.state.tryGetLastStepIdFromFolderPath();
- if (stepIdOrNull) {
- this.stateModifier.trySelectStepById(stepIdOrNull);
- } else {
- this.state.setSelectedStepId(null);
- }
- }
+ // Nothing to do.
}
public onMove(delta: Vector) {
+ this.lastDelta = delta;
+
this.state.setViewport({
position: this.startPosition.subtract(delta),
scale: this.state.viewport.scale
@@ -35,6 +32,12 @@ export class MoveViewportBehavior implements Behavior {
}
public onEnd() {
- // Nothing to do.
+ if (this.resetSelectedStep) {
+ const distance = this.lastDelta ? this.lastDelta.distance() : 0;
+ if (distance > 2) {
+ return;
+ }
+ this.stateModifier.tryResetSelectedStep();
+ }
}
}
diff --git a/designer/src/behaviors/select-step-behavior.ts b/designer/src/behaviors/select-step-behavior.ts
index 9a3bb675..91b1605b 100644
--- a/designer/src/behaviors/select-step-behavior.ts
+++ b/designer/src/behaviors/select-step-behavior.ts
@@ -33,7 +33,7 @@ export class SelectStepBehavior implements Behavior {
if (delta.distance() > 2) {
const canDrag = !this.state.isReadonly && !this.isDragDisabled;
if (canDrag) {
- this.state.setSelectedStepId(null);
+ this.stateModifier.tryResetSelectedStep();
return DragStepBehavior.create(this.context, this.pressedStepComponent.step, this.pressedStepComponent);
} else {
return MoveViewportBehavior.create(false, this.context);
@@ -46,9 +46,11 @@ export class SelectStepBehavior implements Behavior {
return;
}
- if (!this.stateModifier.trySelectStep(this.pressedStepComponent.step, this.pressedStepComponent.parentSequence)) {
- // If we cannot select the step, we clear the selection.
- this.state.setSelectedStepId(null);
+ if (this.stateModifier.isSelectable(this.pressedStepComponent.step, this.pressedStepComponent.parentSequence)) {
+ this.stateModifier.trySelectStepById(this.pressedStepComponent.step.id);
+ } else {
+ // If the step is not selectable, we try to reset the selection.
+ this.stateModifier.tryResetSelectedStep();
}
return new SelectStepBehaviorEndToken(this.pressedStepComponent.step.id, Date.now());
}
diff --git a/designer/src/designer-configuration.ts b/designer/src/designer-configuration.ts
index fe325a4b..027ae8af 100644
--- a/designer/src/designer-configuration.ts
+++ b/designer/src/designer-configuration.ts
@@ -166,13 +166,15 @@ export interface PreferenceStorage {
export interface StepsConfiguration {
isSelectable?: (step: Step, parentSequence: Sequence) => boolean;
- canInsertStep?: (step: Step, targetSequence: Sequence, targetIndex: number) => boolean;
isDraggable?: (step: Step, parentSequence: Sequence) => boolean;
- canMoveStep?: (sourceSequence: Sequence, step: Step, targetSequence: Sequence, targetIndex: number) => boolean;
isDeletable?: (step: Step, parentSequence: Sequence) => boolean;
- canDeleteStep?: (step: Step, parentSequence: Sequence) => boolean;
isDuplicable?: (step: Step, parentSequence: Sequence) => boolean;
+ canUnselectStep?: (step: Step, parentSequence: Sequence) => boolean;
+ canInsertStep?: (step: Step, targetSequence: Sequence, targetIndex: number) => boolean;
+ canMoveStep?: (sourceSequence: Sequence, step: Step, targetSequence: Sequence, targetIndex: number) => boolean;
+ canDeleteStep?: (step: Step, parentSequence: Sequence) => boolean;
+
/**
* @description The designer automatically selects the step after it is dropped. If true, the step will not be selected.
*/
diff --git a/designer/src/designer-state.ts b/designer/src/designer-state.ts
index 6051f82f..98e63631 100644
--- a/designer/src/designer-state.ts
+++ b/designer/src/designer-state.ts
@@ -12,6 +12,7 @@ export interface DefinitionChangedEvent {
export class DesignerState {
public readonly onViewportChanged = new SimpleEvent();
public readonly onSelectedStepIdChanged = new SimpleEvent();
+ public readonly onStepUnselectionBlocked = new SimpleEvent();
public readonly onFolderPathChanged = new SimpleEvent();
public readonly onIsReadonlyChanged = new SimpleEvent();
public readonly onIsDraggingChanged = new SimpleEvent();
@@ -66,6 +67,10 @@ export class DesignerState {
this.onDefinitionChanged.forward({ changeType, stepId });
}
+ public notifyStepUnselectionBlocked(stepId: string | null) {
+ this.onStepUnselectionBlocked.forward(stepId);
+ }
+
public setViewport(viewport: Viewport) {
this.viewport = viewport;
this.onViewportChanged.forward(viewport);
diff --git a/designer/src/designer.ts b/designer/src/designer.ts
index 86df6e17..ce83a951 100644
--- a/designer/src/designer.ts
+++ b/designer/src/designer.ts
@@ -69,6 +69,7 @@ export class Designer {
designerContext.state.onViewportChanged.subscribe(designer.onViewportChanged.forward);
designerContext.state.onIsToolboxCollapsedChanged.subscribe(designer.onIsToolboxCollapsedChanged.forward);
designerContext.state.onIsEditorCollapsedChanged.subscribe(designer.onIsEditorCollapsedChanged.forward);
+ designerContext.state.onStepUnselectionBlocked.subscribe(designer.onStepUnselectionBlocked.forward);
return designer;
}
@@ -101,6 +102,11 @@ export class Designer {
*/
public readonly onSelectedStepIdChanged = new SimpleEvent();
+ /**
+ * @description Fires when the designer could not unselect the currently selected step due to restrictions.
+ */
+ public readonly onStepUnselectionBlocked = new SimpleEvent();
+
/**
* @description Fires when the toolbox is collapsed or expanded.
*/
@@ -150,7 +156,14 @@ export class Designer {
* @description Selects a step by the id.
*/
public selectStepById(stepId: string) {
- this.stateModifier.trySelectStepById(stepId);
+ this.state.setSelectedStepId(stepId);
+ }
+
+ /**
+ * @description Unselects the selected step.
+ */
+ public clearSelectedStep() {
+ this.state.setSelectedStepId(null);
}
/**
@@ -175,13 +188,6 @@ export class Designer {
this.api.viewport.resetViewport();
}
- /**
- * @description Unselects the selected step.
- */
- public clearSelectedStep() {
- this.state.setSelectedStepId(null);
- }
-
/**
* @description Moves the viewport to the step with the animation.
*/
diff --git a/designer/src/modifier/state-modifier.ts b/designer/src/modifier/state-modifier.ts
index 97b82cad..938efa49 100644
--- a/designer/src/modifier/state-modifier.ts
+++ b/designer/src/modifier/state-modifier.ts
@@ -36,24 +36,51 @@ export class StateModifier {
return this.configuration.isSelectable ? this.configuration.isSelectable(step, parentSequence) : true;
}
- public trySelectStep(step: Step, parentSequence: Sequence): boolean {
- if (this.isSelectable(step, parentSequence)) {
- this.state.setSelectedStepId(step.id);
+ public isSelectableById(stepId: string): boolean {
+ if (this.configuration.isSelectable) {
+ const result = this.definitionWalker.getParentSequence(this.state.definition, stepId);
+ return this.configuration.isSelectable(result.step, result.parentSequence);
+ }
+ return true;
+ }
+
+ private canUnselectSelectedStep(): boolean | null {
+ if (this.state.selectedStepId) {
+ if (this.configuration.canUnselectStep) {
+ const result = this.definitionWalker.getParentSequence(this.state.definition, this.state.selectedStepId);
+ return this.configuration.canUnselectStep(result.step, result.parentSequence);
+ }
return true;
}
+ return null;
+ }
+
+ /**
+ * @description Check the `isSelectable` callback before calling this method.
+ */
+ public trySelectStepById(stepIdOrNull: string | null): boolean {
+ const can = this.canUnselectSelectedStep();
+ if (can === true || can === null) {
+ this.state.setSelectedStepId(stepIdOrNull);
+ return true;
+ }
+ this.state.notifyStepUnselectionBlocked(stepIdOrNull);
return false;
}
- public trySelectStepById(stepId: string) {
- if (this.configuration.isSelectable) {
- const result = this.definitionWalker.getParentSequence(this.state.definition, stepId);
- this.trySelectStep(result.step, result.parentSequence);
- } else {
- this.state.setSelectedStepId(stepId);
+ public tryResetSelectedStep() {
+ let stepIdOrNull = this.state.tryGetLastStepIdFromFolderPath();
+ if (stepIdOrNull && !this.isSelectableById(stepIdOrNull)) {
+ stepIdOrNull = null;
}
+ this.trySelectStepById(stepIdOrNull);
}
- public isDeletable(stepId: string): boolean {
+ public isDeletable(step: Step, parentSequence: Sequence): boolean {
+ return this.configuration.isDeletable ? this.configuration.isDeletable(step, parentSequence) : true;
+ }
+
+ public isDeletableById(stepId: string): boolean {
if (this.configuration.isDeletable) {
const result = this.definitionWalker.getParentSequence(this.state.definition, stepId);
return this.configuration.isDeletable(result.step, result.parentSequence);
@@ -61,7 +88,10 @@ export class StateModifier {
return true;
}
- public tryDelete(stepId: string): boolean {
+ /**
+ * @description Check the `isDeletable` callback before calling this method.
+ */
+ public tryDeleteById(stepId: string): boolean {
const result = this.definitionWalker.getParentSequence(this.state.definition, stepId);
const canDeleteStep = this.configuration.canDeleteStep
@@ -87,7 +117,7 @@ export class StateModifier {
SequenceModifier.insertStep(step, targetSequence, targetIndex);
this.state.notifyDefinitionChanged(DefinitionChangeType.stepInserted, step.id);
- if (!this.configuration.isAutoSelectDisabled) {
+ if (!this.configuration.isAutoSelectDisabled && this.isSelectable(step, targetSequence)) {
this.trySelectStepById(step.id);
}
return true;
@@ -113,8 +143,8 @@ export class StateModifier {
apply();
this.state.notifyDefinitionChanged(DefinitionChangeType.stepMoved, step.id);
- if (!this.configuration.isAutoSelectDisabled) {
- this.trySelectStep(step, targetSequence);
+ if (!this.configuration.isAutoSelectDisabled && this.isSelectable(step, targetSequence)) {
+ this.trySelectStepById(step.id);
}
return true;
}
@@ -123,6 +153,9 @@ export class StateModifier {
return this.configuration.isDuplicable ? this.configuration.isDuplicable(step, parentSequence) : false;
}
+ /**
+ * @description Check the `isDuplicable` callback before calling this method.
+ */
public tryDuplicate(step: Step, parentSequence: Sequence): boolean {
const duplicator = new StepDuplicator(this.uidGenerator, this.definitionWalker);
diff --git a/designer/src/workspace/context-menu/context-menu-items-builder.ts b/designer/src/workspace/context-menu/context-menu-items-builder.ts
index acaff7b5..71a6cf1d 100644
--- a/designer/src/workspace/context-menu/context-menu-items-builder.ts
+++ b/designer/src/workspace/context-menu/context-menu-items-builder.ts
@@ -38,7 +38,7 @@ export class ContextMenuItemsBuilder {
label: this.i18n('contextMenu.unselect', 'Unselect'),
order: 10,
callback: () => {
- this.state.setSelectedStepId(null);
+ this.stateModifier.tryResetSelectedStep();
}
});
} else {
@@ -53,12 +53,12 @@ export class ContextMenuItemsBuilder {
}
if (!this.state.isReadonly) {
- if (this.stateModifier.isDeletable(step.id)) {
+ if (this.stateModifier.isDeletable(step, parentSequence)) {
items.push({
label: this.i18n('contextMenu.delete', 'Delete'),
order: 30,
callback: () => {
- this.stateModifier.tryDelete(step.id);
+ this.stateModifier.tryDeleteById(step.id);
}
});
}
diff --git a/examples/assets/lib.js b/examples/assets/lib.js
index 9b63e6af..45cd76e6 100644
--- a/examples/assets/lib.js
+++ b/examples/assets/lib.js
@@ -13,7 +13,7 @@ function embedStylesheet(url) {
document.write(` `);
}
-const baseUrl = isTestEnv() ? '../designer' : '//cdn.jsdelivr.net/npm/sequential-workflow-designer@0.32.0';
+const baseUrl = isTestEnv() ? '../designer' : '//cdn.jsdelivr.net/npm/sequential-workflow-designer@0.33.0';
embedScript(`${baseUrl}/dist/index.umd.js`);
embedStylesheet(`${baseUrl}/css/designer.css`);
diff --git a/examples/assets/triggers.js b/examples/assets/triggers.js
index 20e9baab..d1297067 100644
--- a/examples/assets/triggers.js
+++ b/examples/assets/triggers.js
@@ -127,13 +127,6 @@ const configuration = {
view: {
start: null
}
- }),
- sequentialWorkflowDesigner.RectPlaceholderDesignerExtension.create({
- gapWidth: 88,
- gapHeight: 24,
- radius: 6,
- iconD: 'M21.2 27.04H5.6v-5.19h15.6V6.29h5.2v15.56H42v5.19H26.4V42.6h-5.2V27.04z',
- iconSize: 16
})
]
};
diff --git a/react/package.json b/react/package.json
index e7fcea22..08f9ef67 100644
--- a/react/package.json
+++ b/react/package.json
@@ -1,7 +1,7 @@
{
"name": "sequential-workflow-designer-react",
"description": "React wrapper for Sequential Workflow Designer component.",
- "version": "0.32.0",
+ "version": "0.33.0",
"type": "module",
"main": "./lib/esm/index.js",
"types": "./lib/index.d.ts",
@@ -47,7 +47,7 @@
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0",
- "sequential-workflow-designer": "^0.32.0"
+ "sequential-workflow-designer": "^0.33.0"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.1",
@@ -63,7 +63,7 @@
"prettier": "^3.2.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "sequential-workflow-designer": "^0.32.0",
+ "sequential-workflow-designer": "^0.33.0",
"rollup": "^4.40.0",
"rollup-plugin-dts": "^6.2.1",
"rollup-plugin-typescript2": "^0.36.0",
diff --git a/react/src/SequentialWorkflowDesigner.tsx b/react/src/SequentialWorkflowDesigner.tsx
index d33aff23..e8142ee3 100644
--- a/react/src/SequentialWorkflowDesigner.tsx
+++ b/react/src/SequentialWorkflowDesigner.tsx
@@ -36,6 +36,7 @@ export interface SequentialWorkflowDesignerProps
onDefinitionChange: (state: WrappedDefinition) => void;
selectedStepId?: string | null;
onSelectedStepIdChanged?: (stepId: string | null) => void;
+ onStepUnselectionBlocked?: (targetStepId: string | null) => void;
isReadonly?: boolean;
rootEditor: false | JSX.Element | RootEditorProvider;
@@ -69,6 +70,7 @@ export function SequentialWorkflowDesigner(props
const onDefinitionChangeRef = useRef(props.onDefinitionChange);
const onSelectedStepIdChangedRef = useRef(props.onSelectedStepIdChanged);
+ const onStepUnselectionBlockedRef = useRef(props.onStepUnselectionBlocked);
const onIsEditorCollapsedChangedRef = useRef(props.onIsEditorCollapsedChanged);
const onIsToolboxCollapsedChangedRef = useRef(props.onIsToolboxCollapsedChanged);
const rootEditorRef = useRef(props.rootEditor);
@@ -163,6 +165,10 @@ export function SequentialWorkflowDesigner(props
onSelectedStepIdChangedRef.current = props.onSelectedStepIdChanged;
}, [props.onSelectedStepIdChanged]);
+ useEffect(() => {
+ onStepUnselectionBlockedRef.current = props.onStepUnselectionBlocked;
+ }, [props.onStepUnselectionBlocked]);
+
useEffect(() => {
onIsEditorCollapsedChangedRef.current = props.onIsEditorCollapsedChanged;
}, [props.onIsEditorCollapsedChanged]);
@@ -263,6 +269,11 @@ export function SequentialWorkflowDesigner(props
onSelectedStepIdChangedRef.current(stepId);
}
});
+ designer.onStepUnselectionBlocked.subscribe(targetStepId => {
+ if (onStepUnselectionBlockedRef.current) {
+ onStepUnselectionBlockedRef.current(targetStepId);
+ }
+ });
designer.onIsToolboxCollapsedChanged.subscribe(isCollapsed => {
if (onIsToolboxCollapsedChangedRef.current) {
onIsToolboxCollapsedChangedRef.current(isCollapsed);
diff --git a/svelte/package.json b/svelte/package.json
index eb352923..76228ffe 100644
--- a/svelte/package.json
+++ b/svelte/package.json
@@ -1,7 +1,7 @@
{
"name": "sequential-workflow-designer-svelte",
"description": "Svelte wrapper for Sequential Workflow Designer component.",
- "version": "0.32.0",
+ "version": "0.33.0",
"license": "MIT",
"scripts": {
"prepare": "cp ../LICENSE LICENSE",
@@ -28,10 +28,10 @@
],
"peerDependencies": {
"svelte": "^4.0.0",
- "sequential-workflow-designer": "^0.32.0"
+ "sequential-workflow-designer": "^0.33.0"
},
"devDependencies": {
- "sequential-workflow-designer": "^0.32.0",
+ "sequential-workflow-designer": "^0.33.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.20.4",
"@sveltejs/package": "^2.0.0",
diff --git a/svelte/src/lib/SequentialWorkflowDesigner.svelte b/svelte/src/lib/SequentialWorkflowDesigner.svelte
index 55dacfd4..1310154f 100644
--- a/svelte/src/lib/SequentialWorkflowDesigner.svelte
+++ b/svelte/src/lib/SequentialWorkflowDesigner.svelte
@@ -31,6 +31,9 @@
selectedStepIdChanged: {
stepId: string | null;
};
+ stepUnselectionBlocked: {
+ targetStepId: string | null;
+ };
isToolboxCollapsedChanged: {
isCollapsed: boolean;
};
@@ -158,6 +161,7 @@
})
);
d.onSelectedStepIdChanged.subscribe(stepId => dispatch('selectedStepIdChanged', { stepId }));
+ d.onStepUnselectionBlocked.subscribe(targetStepId => dispatch('stepUnselectionBlocked', { targetStepId }));
d.onIsToolboxCollapsedChanged.subscribe(isCollapsed => dispatch('isToolboxCollapsedChanged', { isCollapsed }));
d.onIsEditorCollapsedChanged.subscribe(isCollapsed => dispatch('isEditorCollapsedChanged', { isCollapsed }));