aspnetzero-workflow-designer 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,220 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # @aspnetzero/workflow-designer
2
+
3
+ Framework-neutral custom elements for the ASP.NET Zero workflow & approval builder.
4
+ One implementation, wrapped by Angular, React and ASP.NET Core MVC — so the three UIs cannot drift apart.
5
+
6
+ ## Elements
7
+
8
+ | Tag | Purpose |
9
+ |---|---|
10
+ | `<workflow-designer>` | Graph editor for `WorkflowGraphDocument` (steps, transitions, layout) |
11
+ | `<workflow-form-builder>` | Form editor for `WorkflowFormDocument` (sections, fields, validation) |
12
+
13
+ Both are registered automatically on import.
14
+
15
+ ```ts
16
+ import '@aspnetzero/workflow-designer';
17
+ ```
18
+
19
+ ## The host contract
20
+
21
+ The component is **pure**: documents in, documents out. It never knows the host's auth token, tenant id
22
+ or API base URL, and it makes no HTTP calls of its own. Everything reaches it through properties, and
23
+ everything leaves it through events.
24
+
25
+ ### Properties (set as JS properties, not attributes)
26
+
27
+ | Property | Type | Purpose |
28
+ |---|---|---|
29
+ | `document` | `WorkflowGraphDocument` / `WorkflowFormDocument` | The canonical document to edit |
30
+ | `localization` | `Record<string, string>` | Host's localization dictionary |
31
+ | `theme` | `WorkflowThemeTokens` | Colour and font tokens; mapped to CSS custom properties |
32
+ | `permissions` | `{ canEdit?, canPublish?, canSimulate? }` | `canEdit: false` puts the editor in read-only mode |
33
+ | `capabilities` | `{ maxSteps?, maxFormFields?, publicFormEnabled?, ... }` | Edition limits and feature switches; see [Capabilities](#capabilities) |
34
+ | `lookups` | `{ users?, roles?, organizationUnits?, ... }` | Async callbacks the host provides for pickers |
35
+ | `validationIssues` | `WorkflowValidationIssue[]` | Server validation results; rendered inline on the offending node/field |
36
+ | `form` (designer only) | `WorkflowFormDocument` | The workflow's form, so conditions and mappings can pick its fields |
37
+ | `formBuilder` (designer only) | `WorkflowFormBuilderElement` | The `<workflow-form-builder>` editing the same form, so `focusValidationIssue` can hand field issues to it |
38
+
39
+ Hosts may re-assign any of these as often as they like (React effects, Angular change detection). A value
40
+ whose content equals the previous one is accepted without a re-render, and `document` is compared with the
41
+ document the element last emitted, so handing back the `document-change` detail is a no-op. When a real
42
+ change does re-render the element while the user is typing, the focused input keeps its focus, caret and
43
+ not yet committed text.
44
+
45
+ ### Capabilities
46
+
47
+ Every boolean switch except `publicFormEnabled` counts as available unless the host sets it to `false`.
48
+
49
+ | Capability | Meaning |
50
+ |---|---|
51
+ | `maxSteps`, `maxFormFields` | Edition limits; the editors refuse to add beyond them |
52
+ | `advancedApprovalsEnabled`, `externalActionsEnabled`, `eventWaitEnabled`, `slackEnabled`, `teamsEnabled` | Hide the steps and inputs that need the feature when `false` |
53
+ | `attachmentsEnabled` | `false` hides the file attachment field and the attachment inputs |
54
+ | `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. |
55
+
56
+ Assigning a new `capabilities` object to the live element re-renders the palettes.
57
+
58
+ ### Lookups and the assistant
59
+
60
+ The element never lets a host callback fail loudly: a rejected or throwing lookup yields an empty list, and
61
+ an identical lookup (same provider, filter and context) that is still in flight is shared rather than sent
62
+ again, so re-renders and quickly opened dropdowns do not flood the host. The assistant buttons
63
+ (`suggestCondition`, `summarizeWorkflow`) are disabled while their request runs. A result with
64
+ `succeeded: false` shows its `failureReason` inline as a soft warning (hosts should resolve with that
65
+ shape for "assistant not available" or rate limits rather than reject); a rejected request shows a generic
66
+ inline warning.
67
+
68
+ ### Theming and sizing
69
+
70
+ The canvas is SVG drawn from JavaScript, so node, port, label, execution and heat-map colours are derived
71
+ from the resolved theme tokens (`--wf-surface`, `--wf-text`, `--wf-primary`, `--wf-success`,
72
+ `--wf-warning`, `--wf-danger`, ...; by default the Bootstrap/Metronic `--bs-*` variables). Status colours
73
+ are tinted over the surface colour, so a dark theme gets dark nodes. Changing `theme` or the document's
74
+ `data-bs-theme`/`data-theme` attribute redraws the canvas.
75
+
76
+ The editor needs at least `--wf-designer-min-height` (default `560px`); read-only views (instance
77
+ timeline, template preview, heat map) need only `--wf-designer-readonly-min-height` (default `320px`), so
78
+ they fit a shorter host box. The minimap stays inside the canvas and is hidden when the canvas is too
79
+ small to hold it.
80
+
81
+ ### Events
82
+
83
+ | Event | Detail | Meaning |
84
+ |---|---|---|
85
+ | `document-change` | `{ document, isDirty }` | The user edited the document |
86
+ | `selection-change` | `{ nodeId? , fieldId? }` | Selection moved |
87
+ | `command` | `{ command: 'save' \| 'validate' \| 'simulate' \| 'publish' }` | The user asked the host to run a host-owned action |
88
+
89
+ | `issue-focus` | `WorkflowIssueFocusDetail` = `{ tab: 'flow' \| 'form' \| 'trigger', issue, nodeId?, edgeId?, branchId?, fieldId?, inputName? }` | `focusValidationIssue` resolved an issue; show this host tab |
90
+
91
+ The host owns save/validate/simulate/publish. The component only *asks*.
92
+
93
+ ### Methods
94
+
95
+ | Method | Element | Purpose |
96
+ |---|---|---|
97
+ | `focusValidationIssue(issue: WorkflowValidationIssue): boolean` | both | Shows what a validation issue is about (see below) |
98
+
99
+ `focusValidationIssue` takes one of the objects the host passes through `validationIssues` (`code`,
100
+ `nodeId`, `edgeId`, `fieldId`, `branchId`) and returns `true` when something was focused:
101
+
102
+ - On `<workflow-designer>`, a step, transition or branch issue selects it on the canvas, opens the
103
+ inspector tab holding the setting the issue's code names (an `SlaDurationInvalid` issue opens
104
+ *Service level*, an `ApprovalNodeTargetMissing` issue *Assignment*, and so on) and pans the canvas
105
+ until the step is visible. A field issue is handed to the element in `formBuilder` when the host set
106
+ one; a start-trigger issue (`ResponsibleUser...`, `StartTrigger...`, public form slug and embed origin
107
+ codes) is left to the host's trigger tab. In every case the element dispatches `issue-focus` whose
108
+ `tab` says which of the host's tabs (`flow`, `form`, `trigger`) holds the issue — the element has no
109
+ tabs of its own, so the host switches.
110
+ - On `<workflow-form-builder>`, a field issue selects the field, scrolls its row into view and
111
+ dispatches `issue-focus` with `tab: 'form'`.
112
+ - Ids neither document knows return `false` and dispatch nothing, so a stale issue list is harmless.
113
+ - Calling it before the element has rendered (not yet connected, or the document not yet set) is fine:
114
+ the focus is applied by the first render. Ids are still checked against the documents the element
115
+ holds at call time, so set `document` (and `form`) first.
116
+
117
+ The inline issue list in the designer's inspector uses the same method: every issue is a button
118
+ (keyboard reachable) that opens the tab holding the setting it is about.
119
+
120
+ A typical host wiring:
121
+
122
+ ```ts
123
+ designer.formBuilder = formBuilder;
124
+ designer.addEventListener('issue-focus', (event) => showTab(event.detail.tab));
125
+
126
+ issueRow.addEventListener('click', () => designer.focusValidationIssue(issue));
127
+ ```
128
+
129
+ ## Canonical schema
130
+
131
+ `src/schema/workflow-schema.ts` mirrors the product-owned C# contract
132
+ (`WorkflowGraphDocument`, `WorkflowFormDocument` and friends) — **not** any graph library's internal model.
133
+ Swapping the rendering library must never change the persisted JSON.
134
+
135
+ ## Example
136
+
137
+ ```html
138
+ <workflow-designer id="designer"></workflow-designer>
139
+ <script type="module">
140
+ import '@aspnetzero/workflow-designer';
141
+
142
+ const designer = document.getElementById('designer');
143
+
144
+ designer.localization = { WorkflowNodeType_Approval: 'Onay' };
145
+ designer.permissions = { canEdit: true };
146
+ designer.capabilities = { maxSteps: 40 };
147
+ designer.document = serverGraphDocument;
148
+
149
+ designer.addEventListener('document-change', (event) => {
150
+ draft.graph = event.detail.document;
151
+ });
152
+ </script>
153
+ ```
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ npm install
159
+ npm run dev # demo page at demo/index.html
160
+ npm run typecheck
161
+ npm test # vitest suite under test/
162
+ npm run build # ESM + UMD + rolled-up .d.ts into dist/, then syncs the hosts (see below)
163
+ ```
164
+
165
+ Rendering uses [AntV X6](https://x6.antv.antgroup.com/) — a vanilla-JS graph library, chosen precisely
166
+ because it carries no framework dependency and therefore works identically inside all three host UIs.
167
+
168
+ ### After every change: run `npm run build`
169
+
170
+ **Whenever you change anything in this package, run `npm run build` here before testing in a host or
171
+ committing.** The hosts never compile `src/`; they only consume the built output:
172
+
173
+ | Host | How it gets the designer |
174
+ |---|---|
175
+ | Angular (`angular/`) | `link:../workflow-designer` dependency, reads `dist/` |
176
+ | React (`react/`) | `link:../workflow-designer` dependency, reads `dist/` |
177
+ | ASP.NET Core MVC (`Web.Mvc`) | a copy of `dist/workflow-designer.umd.cjs`, committed under `wwwroot/view-resources/Areas/AppAreaName/Views/WorkflowDefinitions/lib/workflow-designer.umd.js` |
178
+
179
+ `npm run build` runs `scripts/sync-hosts.mjs` as its `postbuild` step, which copies the fresh UMD bundle
180
+ into the MVC host. Skipping the build leaves Angular and React on a stale `dist/` and MVC on a stale
181
+ committed bundle, so the three UIs drift apart. Commit the regenerated MVC bundle together with the
182
+ source change. (`npm run sync-hosts` re-runs only the copy step.)
183
+
184
+ ## Status
185
+
186
+ What the package implements today:
187
+
188
+ **Workflow designer**
189
+
190
+ - Node palette (click or drag onto the canvas), canvas with pan/zoom/grid, zoom to fit, minimap,
191
+ snaplines, auto layout and an outline panel.
192
+ - Node add/move/select/delete, multi-select, edge drawing with automatic edge-kind assignment per source
193
+ node type, parallel fan-out nodes paired with their join node.
194
+ - Undo/redo and copy/paste (toolbar and keyboard shortcuts).
195
+ - A descriptor-driven properties inspector, grouped into tabs by input category (for example
196
+ Assignment, Behaviour, Service level, Delivery), with editors resolved by `uiHint` through the input
197
+ editor registry.
198
+ - Condition branches: add, remove and reorder branches, and a condition builder over form fields,
199
+ variables and step outputs; optional assistant suggestions through `lookups.suggestCondition`.
200
+ - Assignee pickers (requester's manager, user, role, organization unit, fallback role) wired to the
201
+ host lookups (`lookups.optionsProvider`, or the older `users`/`roles`/`organizationUnits` callbacks).
202
+ - SLA editing (the "Service level" tab; "Escalate after" and "Escalate to" appear once escalation is
203
+ switched on), notification recipients, chat channels (Slack / Teams) with
204
+ adaptive card editing, secret pickers, action parameter and event payload mapping tables.
205
+ - Workflow variables panel, inline validation issues, and an optional assistant workflow summary
206
+ (`lookups.summarizeWorkflow`).
207
+
208
+ **Form builder**
209
+
210
+ - Field palette, field add/reorder/remove, sections (add, rename, remove; removing a section moves its
211
+ fields to the section above it), repeater fields with row fields and calculated fields.
212
+ - The file attachment field only on public forms (`capabilities.publicFormEnabled: true`).
213
+ - A descriptor-driven properties panel for label/key/required/validation, and a row-per-option editor
214
+ for single and multi select options.
215
+ - A new field's key is derived from its label (`Total amount` becomes `totalAmount`; accented and
216
+ Turkish letters are folded to ASCII, a key starts with a letter and is made unique with `2`, `3`, ...)
217
+ and keeps following the label until the user edits the key by hand. Keys that arrived with the
218
+ document are never changed.
219
+ - A palette note that signed-in requesters attach files from the request page, since the file
220
+ attachment field exists only for public forms.