aspnetzero-workflow-designer
v1.0.2
Published
Framework-neutral workflow designer and form builder custom elements for the ASP.NET Zero workflow & approval builder.
Readme
aspnetzero-workflow-designer
Framework-neutral custom elements for the ASP.NET Zero workflow & approval builder. One implementation, wrapped by Angular, React and ASP.NET Core MVC — so the three UIs cannot drift apart.
Elements
| Tag | Purpose |
|---|---|
| <workflow-designer> | Graph editor for WorkflowGraphDocument (steps, transitions, layout) |
| <workflow-form-builder> | Form editor for WorkflowFormDocument (sections, fields, validation) |
Both are registered automatically on import.
import 'aspnetzero-workflow-designer';The host contract
The component is pure: documents in, documents out. It never knows the host's auth token, tenant id or API base URL, and it makes no HTTP calls of its own. Everything reaches it through properties, and everything leaves it through events.
Properties (set as JS properties, not attributes)
| Property | Type | Purpose |
|---|---|---|
| document | WorkflowGraphDocument / WorkflowFormDocument | The canonical document to edit |
| localization | Record<string, string> | Host's localization dictionary |
| theme | WorkflowThemeTokens | Colour and font tokens; mapped to CSS custom properties |
| permissions | { canEdit?, canPublish?, canSimulate? } | canEdit: false puts the editor in read-only mode |
| capabilities | { maxSteps?, maxFormFields?, publicFormEnabled?, ... } | Edition limits and feature switches; see Capabilities |
| lookups | { users?, roles?, organizationUnits?, ... } | Async callbacks the host provides for pickers |
| validationIssues | WorkflowValidationIssue[] | Server validation results; rendered inline on the offending node/field |
| form (designer only) | WorkflowFormDocument | The workflow's form, so conditions and mappings can pick its fields |
| formBuilder (designer only) | WorkflowFormBuilderElement | The <workflow-form-builder> editing the same form, so focusValidationIssue can hand field issues to it |
Hosts may re-assign any of these as often as they like (React effects, Angular change detection). A value
whose content equals the previous one is accepted without a re-render, and document is compared with the
document the element last emitted, so handing back the document-change detail is a no-op. When a real
change does re-render the element while the user is typing, the focused input keeps its focus, caret and
not yet committed text.
Capabilities
Every boolean switch except publicFormEnabled counts as available unless the host sets it to false.
| Capability | Meaning |
|---|---|
| maxSteps, maxFormFields | Edition limits; the editors refuse to add beyond them |
| advancedApprovalsEnabled, externalActionsEnabled, eventWaitEnabled, slackEnabled, teamsEnabled | Hide the steps and inputs that need the feature when false |
| attachmentsEnabled | false hides the file attachment field and the attachment inputs |
| publicFormEnabled | Opt-in. Files can only be collected on a public form, so the form builder offers the file attachment field only when this is true (and attachmentsEnabled is not false). Leaving it out means "not a public form". A file attachment field already on such a form stays, with an inline warning on its card. |
Assigning a new capabilities object to the live element re-renders the palettes.
Lookups and the assistant
The element never lets a host callback fail loudly: a rejected or throwing lookup yields an empty list, and
an identical lookup (same provider, filter and context) that is still in flight is shared rather than sent
again, so re-renders and quickly opened dropdowns do not flood the host. The assistant buttons
(suggestCondition, summarizeWorkflow) are disabled while their request runs. A result with
succeeded: false shows its failureReason inline as a soft warning (hosts should resolve with that
shape for "assistant not available" or rate limits rather than reject); a rejected request shows a generic
inline warning.
Theming and sizing
The canvas is SVG drawn from JavaScript, so node, port, label, execution and heat-map colours are derived
from the resolved theme tokens (--wf-surface, --wf-text, --wf-primary, --wf-success,
--wf-warning, --wf-danger, ...; by default the Bootstrap/Metronic --bs-* variables). Status colours
are tinted over the surface colour, so a dark theme gets dark nodes. Changing theme or the document's
data-bs-theme/data-theme attribute redraws the canvas.
The editor needs at least --wf-designer-min-height (default 560px); read-only views (instance
timeline, template preview, heat map) need only --wf-designer-readonly-min-height (default 320px), so
they fit a shorter host box. The minimap stays inside the canvas and is hidden when the canvas is too
small to hold it.
Events
| Event | Detail | Meaning |
|---|---|---|
| document-change | { document, isDirty } | The user edited the document |
| selection-change | { nodeId? , fieldId? } | Selection moved |
| command | { command: 'save' \| 'validate' \| 'simulate' \| 'publish' } | The user asked the host to run a host-owned action |
| issue-focus | WorkflowIssueFocusDetail = { tab: 'flow' \| 'form' \| 'trigger', issue, nodeId?, edgeId?, branchId?, fieldId?, inputName? } | focusValidationIssue resolved an issue; show this host tab |
The host owns save/validate/simulate/publish. The component only asks.
Methods
| Method | Element | Purpose |
|---|---|---|
| focusValidationIssue(issue: WorkflowValidationIssue): boolean | both | Shows what a validation issue is about (see below) |
focusValidationIssue takes one of the objects the host passes through validationIssues (code,
nodeId, edgeId, fieldId, branchId) and returns true when something was focused:
- On
<workflow-designer>, a step, transition or branch issue selects it on the canvas, opens the inspector tab holding the setting the issue's code names (anSlaDurationInvalidissue opens Service level, anApprovalNodeTargetMissingissue Assignment, and so on) and pans the canvas until the step is visible. A field issue is handed to the element informBuilderwhen the host set one; a start-trigger issue (ResponsibleUser...,StartTrigger..., public form slug and embed origin codes) is left to the host's trigger tab. In every case the element dispatchesissue-focuswhosetabsays which of the host's tabs (flow,form,trigger) holds the issue — the element has no tabs of its own, so the host switches. - On
<workflow-form-builder>, a field issue selects the field, scrolls its row into view and dispatchesissue-focuswithtab: 'form'. - Ids neither document knows return
falseand dispatch nothing, so a stale issue list is harmless. - Calling it before the element has rendered (not yet connected, or the document not yet set) is fine:
the focus is applied by the first render. Ids are still checked against the documents the element
holds at call time, so set
document(andform) first.
The inline issue list in the designer's inspector uses the same method: every issue is a button (keyboard reachable) that opens the tab holding the setting it is about.
A typical host wiring:
designer.formBuilder = formBuilder;
designer.addEventListener('issue-focus', (event) => showTab(event.detail.tab));
issueRow.addEventListener('click', () => designer.focusValidationIssue(issue));Canonical schema
src/schema/workflow-schema.ts mirrors the product-owned C# contract
(WorkflowGraphDocument, WorkflowFormDocument and friends) — not any graph library's internal model.
Swapping the rendering library must never change the persisted JSON.
Example
<workflow-designer id="designer"></workflow-designer>
<script type="module">
import 'aspnetzero-workflow-designer';
const designer = document.getElementById('designer');
designer.localization = { WorkflowNodeType_Approval: 'Onay' };
designer.permissions = { canEdit: true };
designer.capabilities = { maxSteps: 40 };
designer.document = serverGraphDocument;
designer.addEventListener('document-change', (event) => {
draft.graph = event.detail.document;
});
</script>Development
npm install
npm run dev # demo page at demo/index.html
npm run typecheck
npm test # vitest suite under test/
npm run build # ESM + UMD + rolled-up .d.ts into dist/Rendering uses AntV X6 — a vanilla-JS graph library, chosen precisely because it carries no framework dependency and therefore works identically inside all three host UIs.
How the hosts get the designer
ASP.NET Zero lives in its own repository and never reads this one. All three of its UIs install the
published aspnetzero-workflow-designer package from npm, so they run the same build:
| Host | How it gets the designer |
|---|---|
| Angular (angular/) | aspnetzero-workflow-designer dependency, imports the ESM build |
| React (react/) | aspnetzero-workflow-designer dependency, imports the ESM build |
| ASP.NET Core MVC (Web.Mvc) | aspnetzero-workflow-designer dependency; bundles.json turns dist/workflow-designer.umd.cjs into _Bundles/workflow-designer.min.js |
The contract with the server
npm run build also writes dist/workflow-contract.json: the step kinds, input names, default keys,
vocabularies, schema versions and localization keys this version uses, taken from the same constants the
bundle is built from (see src/contract/workflow-contract.ts). ASP.NET Zero keeps a copy in its test
project. WorkflowDesignerContract_Tests and the other contract tests there compare the server with that
copy, and fail when the copy is not the version a host locks.
Releasing a change
- Change the package, then run
npm testandnpm run build. - Raise
versioninpackage.json(a new step kind or input is a minor version, a fix a patch). - Run
npm publish.prepublishOnlyruns the tests and the build again, so a broken package is not published. - In the ASP.NET Zero repository, in
angular/,react/andaspnet-core/src/MyCompanyName.AbpZeroTemplate.Web.Mvc/, runpnpm add aspnetzero-workflow-designer@~<version>. - At the root of that repository, run
npm run refresh-workflow-designer-contract, then commit the result and the lockfiles with the server change that needed the new version.
Trying a change in a host before publishing
With the two repositories checked out side by side, point a host at this folder for the time being:
cd angular # or react
pnpm link ../../workflow-designer
cd aspnet-core/src/MyCompanyName.AbpZeroTemplate.Web.Mvc
pnpm link ../../../../workflow-designerThe host now reads this folder's dist/, so run npm run build here after each change (MVC also needs
npm run create-bundles). pnpm link adds a link: override to the host's pnpm-workspace.yaml and
changes its pnpm-lock.yaml; do not commit those. To go back to the published package, revert both files
and run pnpm install.
Status
What the package implements today:
Workflow designer
- Node palette (click or drag onto the canvas), canvas with pan/zoom/grid, zoom to fit, minimap, snaplines, auto layout and an outline panel.
- Node add/move/select/delete, multi-select, edge drawing with automatic edge-kind assignment per source node type, parallel fan-out nodes paired with their join node.
- Undo/redo and copy/paste (toolbar and keyboard shortcuts).
- A descriptor-driven properties inspector, grouped into tabs by input category (for example
Assignment, Behaviour, Service level, Delivery), with editors resolved by
uiHintthrough the input editor registry. - Condition branches: add, remove and reorder branches, and a condition builder over form fields,
variables and step outputs; optional assistant suggestions through
lookups.suggestCondition. - Assignee pickers (requester's manager, user, role, organization unit, fallback role) wired to the
host lookups (
lookups.optionsProvider, or the olderusers/roles/organizationUnitscallbacks). - SLA editing (the "Service level" tab; "Escalate after" and "Escalate to" appear once escalation is switched on), notification recipients, chat channels (Slack / Teams) with adaptive card editing, secret pickers, action parameter and event payload mapping tables.
- Workflow variables panel, inline validation issues, and an optional assistant workflow summary
(
lookups.summarizeWorkflow).
Form builder
- Field palette, field add/reorder/remove, sections (add, rename, remove; removing a section moves its fields to the section above it), repeater fields with row fields and calculated fields.
- The file attachment field only on public forms (
capabilities.publicFormEnabled: true). - A descriptor-driven properties panel for label/key/required/validation, and a row-per-option editor for single and multi select options.
- A new field's key is derived from its label (
Total amountbecomestotalAmount; accented and Turkish letters are folded to ASCII, a key starts with a letter and is made unique with2,3, ...) and keeps following the label until the user edits the key by hand. Keys that arrived with the document are never changed. - A palette note that signed-in requesters attach files from the request page, since the file attachment field exists only for public forms.
