@bpmn-nova/react 0.3.1-preview → 0.3.3-preview

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/llms-full.txt ADDED
@@ -0,0 +1,3322 @@
1
+ # BPMN Nova — Complete AI Context
2
+
3
+ > Generated by `npm run docs:ai`. Do not edit this file directly.
4
+ > Prefer public interfaces documented here; source-internal Modules are not installable NPM packages.
5
+
6
+
7
+ <!-- SOURCE: llms.txt -->
8
+
9
+ # BPMN Nova integration runbook
10
+
11
+ > BPMN Nova is a framework-neutral BPMN 2.0 design, viewing, approval-trace, theme, and pure SVG export toolkit for Flowable and Activiti applications. The current release is Preview.
12
+
13
+ Use this file as the executable starting point for installing BPMN Nova. Complete the steps in order. Read the co-located `llms-full.txt` only when the selected branch needs advanced customization or complete Interface reference.
14
+
15
+ ## 1. Resolve the target application
16
+
17
+ Before installing or editing code:
18
+
19
+ 1. Locate the target application's `package.json`, source entry, and build configuration.
20
+ 2. In a monorepo, select the concrete Web application instead of the workspace root.
21
+ 3. Determine whether that application uses Vue, React, or another browser JavaScript/TypeScript stack.
22
+ 4. Determine its existing package manager from `packageManager` and lockfiles. Preserve that package manager.
23
+ 5. Inspect the application lifecycle and layout so the visual component is mounted only in the browser and receives a computable height.
24
+
25
+ This step is complete only when one application root, one framework branch, and one package manager are unambiguous. If any is ambiguous, stop before installing and ask the user to identify the target application and framework. Missing framework evidence is not evidence of a Vanilla application.
26
+
27
+ ## 2. Select exactly one public package
28
+
29
+ | Target application | Public package | Minimum host version |
30
+ | --- | --- | --- |
31
+ | Vue or Nuxt | `@bpmn-nova/vue` | Vue 3.3 |
32
+ | React, Next.js, or Remix | `@bpmn-nova/react` | React 18 |
33
+ | Another confirmed browser JavaScript/TypeScript stack | `@bpmn-nova/studio` | Node 18 for installation/build |
34
+
35
+ Preserve an already installed public package when it matches the target stack. Do not install historical internal packages such as Core, Designer, Viewer, Runtime, Theme, Export SVG, Properties, or engine profiles. Studio exposes supported `/designer`, `/viewer`, `/runtime`, `/theme`, `/export-svg`, `/flowable`, and `/activiti` subpaths.
36
+
37
+ Install with the application's existing package manager:
38
+
39
+ ```bash
40
+ npm install @bpmn-nova/vue@preview
41
+ pnpm add @bpmn-nova/vue@preview
42
+ yarn add @bpmn-nova/vue@preview
43
+ ```
44
+
45
+ Replace `vue` with `react` or `studio` only when the selected framework branch requires it. Run one installation command, not all three.
46
+
47
+ This step is complete when the application manifest contains exactly the selected direct BPMN Nova dependency and the package manager finishes without peer-dependency errors.
48
+
49
+ ## 3. Select the visual Interface
50
+
51
+ Choose the smallest Interface that owns the required experience:
52
+
53
+ | Requirement | Interface |
54
+ | --- | --- |
55
+ | Complete workbench with Palette, Canvas, Properties, history, import/export, and optional viewing modes | `BpmnStudio` or `createStudioShell()` |
56
+ | Embeddable design canvas without the complete workbench | `BpmnDesigner` |
57
+ | Read-only BPMN, approval path, or mobile timeline | `BpmnViewer` |
58
+ | Custom host layout with official Controller, Canvas, Palette, and Properties modules | external Studio Controller plus adapter components/registries |
59
+
60
+ Use `engine="flowable"` or `engine="activiti"` explicitly from the host workflow engine. Do not infer the engine from BPMN XML namespace declarations alone.
61
+
62
+ Studio modes are presentation state: `design` means editable process design, `viewer` means read-only process viewing, and `instance` means approval trace. Observe the actual Shell state through `subscribeMode()` or framework mode events; never store it in the BPMN model.
63
+
64
+ ## 4. Mount a minimal integration
65
+
66
+ Every branch must import its own public `styles.css` exactly once and provide a height through the complete parent layout chain.
67
+
68
+ ### Vue
69
+
70
+ ```vue
71
+ <script setup>
72
+ import { ref } from 'vue'
73
+ import { BpmnStudio } from '@bpmn-nova/vue'
74
+ import '@bpmn-nova/vue/styles.css'
75
+
76
+ const props = defineProps({ initialXml: String })
77
+ const emit = defineEmits(['change'])
78
+ const studioRef = ref(null)
79
+
80
+ function handleChange(model, reason, xml) {
81
+ emit('change', xml)
82
+ }
83
+ </script>
84
+
85
+ <template>
86
+ <div style="height: min(760px, calc(100vh - 96px)); min-height: 520px">
87
+ <BpmnStudio
88
+ ref="studioRef"
89
+ :xml="props.initialXml"
90
+ engine="flowable"
91
+ mode="design"
92
+ :allowed-edge-types="['sequenceFlow']"
93
+ :allowed-modes="['design', 'viewer']"
94
+ theme="auto"
95
+ @change="handleChange"
96
+ />
97
+ </div>
98
+ </template>
99
+ ```
100
+
101
+ ### React
102
+
103
+ ```jsx
104
+ import { useRef } from 'react'
105
+ import { BpmnStudio } from '@bpmn-nova/react'
106
+ import '@bpmn-nova/react/styles.css'
107
+
108
+ export function WorkflowEditor({ initialXml, saveDraft }) {
109
+ const studioRef = useRef(null)
110
+ return (
111
+ <div style={{ height: 'min(760px, calc(100vh - 96px))', minHeight: 520 }}>
112
+ <BpmnStudio
113
+ ref={studioRef}
114
+ xml={initialXml}
115
+ engine="flowable"
116
+ mode="design"
117
+ allowedEdgeTypes={['sequenceFlow']}
118
+ allowedModes={['design', 'viewer']}
119
+ theme="auto"
120
+ onChange={(model, reason, xml) => saveDraft(xml)}
121
+ />
122
+ </div>
123
+ )
124
+ }
125
+ ```
126
+
127
+ ### Vanilla JavaScript/TypeScript
128
+
129
+ ```js
130
+ import {
131
+ createEmptyProcess,
132
+ createStudioController,
133
+ createStudioShell,
134
+ } from '@bpmn-nova/studio'
135
+ import '@bpmn-nova/studio/styles.css'
136
+
137
+ const studio = createStudioController({
138
+ model: createEmptyProcess('flowable'),
139
+ allowedEdgeTypes: ['sequenceFlow'],
140
+ })
141
+ const shell = createStudioShell({
142
+ container: document.querySelector('#workflow-studio'),
143
+ studio,
144
+ mode: 'design',
145
+ allowedModes: ['design', 'viewer'],
146
+ theme: 'auto',
147
+ })
148
+
149
+ const unsubscribe = studio.subscribe((event) => {
150
+ if (event.type === 'modelChanged') saveDraft(studio.exportXml())
151
+ })
152
+
153
+ export function disposeWorkflowStudio() {
154
+ unsubscribe()
155
+ shell.destroy()
156
+ studio.destroy()
157
+ }
158
+ ```
159
+
160
+ The Vanilla container needs the same explicit height as the framework examples.
161
+
162
+ This step is complete when the workbench is visible, the initial XML is rendered, and one edit produces a non-empty exported XML value.
163
+
164
+ ### 4.1 Embed the complete Studio in an existing business workbench
165
+
166
+ `BpmnStudio` is a complete Header + Palette + Canvas + Properties + Statusbar workbench by default. Preserve that default unless the host already owns a region. To keep the Nova Palette and editing tools, replace only the Header start area and explicitly hide Nova Properties:
167
+
168
+ ```vue
169
+ <BpmnStudio
170
+ ref="studioRef"
171
+ :xml="xml"
172
+ engine="activiti"
173
+ mode="design"
174
+ :allowed-modes="['design']"
175
+ :allowed-node-types="supportedNodeTypes"
176
+ :allowed-edge-types="['sequenceFlow']"
177
+ :regions="{ right: 'hidden' }"
178
+ theme="auto"
179
+ @change="handleChange"
180
+ @selection-change="handleSelectionChange"
181
+ >
182
+ <template #header-start="{ state, actions }">
183
+ <!-- Host back action, business icon, process name, and type -->
184
+ </template>
185
+ <template #header-actions="{ actions, mode }">
186
+ <!-- Host Validate / Save / Publish actions. Validation calls actions.validate(). -->
187
+ </template>
188
+ </BpmnStudio>
189
+ ```
190
+
191
+ - Vue uses native `#header-start` / `#header-actions` / `#header` slots. React uses `headerStart` / `headerActions` / `header` render props. Core uses matching DOM Slots.
192
+ - Vue supports `v-model:mode` and `mode-change`; React supports `mode` and `onModeChange`. Framework Header contexts expose the actual reactive `mode`. Core Slots use `getMode()` / `subscribeMode()`.
193
+ - Header Start replaces only the Nova Brand. Header Actions replaces only the default Validate / Import / Export group, so a host can compose Validate / Save / Publish without rebuilding the editing tools. A complete Header replacement calls the public Actions Interface.
194
+ - `actions.validate()` updates Nova's default status summary before emitting a read-only validation result with `source: 'toolbar'`; `shell.validate()` and framework Handle/Expose methods use `source: 'api'`. Vue receives `validation`, React receives `onValidation`, and Core uses `subscribeValidation()`. `valid` means there are no errors; warnings do not block publishing by default.
195
+ - The default Header does not duplicate Fit; use the footer control or `fitView()`.
196
+ - `regions` can hide `header`, `left`, `right`, or `footer` without leaving an empty layout track. Prop changes update the existing Shell; do not rebuild the Canvas.
197
+ - Put a host-owned properties panel beside the Nova root. Use `selection-change` / `onSelectionChange` as its source of truth because it covers nodes, edges, multi-selection, clearing, and keyboard selection. Do not substitute `element-click` / `onElementClick`.
198
+ - Join host business configuration by stable BPMN element ID. Saving, publishing, authorization, upload, and server transactions remain host responsibilities.
199
+ - `theme="auto"` explicitly follows the system theme. The compatibility default remains `light`.
200
+ - Vue and React both export standalone `BpmnPalettePanel` for a fully custom layout. Pass the same external Studio Controller to Canvas, Palette, and Properties.
201
+
202
+ For a host-owned definition-time subtitle, pass `nodeSubtitleResolver` to Studio, Canvas, or Viewer. Return `undefined` for Nova's default subtitle, `null` to remove the row, or a string (including an empty string) as the override. After data inside a stable closure or Map changes, call `refreshPresentation()` on the component handle or Shell. This refresh must not be implemented by mutating a node, re-importing XML, or remounting the component. The resolver applies only to standard Design/Viewer task and container cards and standard SVG fallback; a complete custom renderer wins, and Instance runtime summaries are never overridden.
203
+
204
+ ## 5. Keep model ownership deterministic
205
+
206
+ - Treat `xml` and `model` as external replacement inputs. Use the emitted/exported XML as the draft output.
207
+ - A local edit followed by the host echoing the same exported XML must not re-import the model or clear Undo/Redo history.
208
+ - Replace `xml` when the host intentionally loads another server revision or another process.
209
+ - For a custom host layout, create one external Studio Controller and pass the same instance to Canvas, Palette, and Properties. The creator owns that Controller and destroys it.
210
+ - Use stable BPMN element IDs as the join key for host business configuration. BPMN Nova does not own the host's persistence transaction.
211
+
212
+ This step is complete when edit, undo, redo, save, reload, and intentional external XML replacement all produce the expected model without duplicate remounts.
213
+
214
+ ## 6. Add host constraints before business use
215
+
216
+ Production workflow applications usually support a subset of BPMN. Configure `allowedNodeTypes` and `allowedEdgeTypes` on the Studio Controller or framework `BpmnStudio`; these reject unsupported imported models and block node/edge creation outside the host contract. The node allowlist filters Palette entries, but one allowed node type may still have multiple Palette presets. Configure matching Palette and Properties registries when the host needs custom labels or business fields.
217
+
218
+ Use `allowedModes` to expose only host-authorized workbench modes. Runtime changes use `setAllowedModes()` rather than rebuilding the Studio; invalid, empty, or duplicate lists are errors. Pass real Runtime data for instance mode; absence of Runtime data means no approval trace rather than demo business data.
219
+
220
+ Runtime snapshots store asset references only. The host supplies `runtimeAssetResolver`. BPMN Nova does not execute workflow engines, submit approvals, upload/store attachments, issue permissions, or export PNG/PDF.
221
+
222
+ Read `llms-full.txt` before implementing custom Shell regions, Header composition, Actions, Palette, Properties, Context Menu, Renderer, Runtime, theme, or SVG export behavior. Use only public exports from the one selected package or supported Studio subpaths. `llms-full.txt` is generated by the Nova repository and must not be edited by hand.
223
+
224
+ ## 7. Verify the integration
225
+
226
+ Run the target application's existing non-destructive quality commands, including its type check and production build. Then verify in a real browser:
227
+
228
+ 1. The component fills its intended container and has complete styles.
229
+ 2. Existing BPMN XML imports without losing IDs, names, topology, or DI positions.
230
+ 3. Create, connect, edit, delete, Undo, and Redo work in design mode.
231
+ 4. Change output is persisted and reloads to the same process.
232
+ 5. Intentional external XML replacement loads once and resets history once.
233
+ 6. Disallowed node types are unavailable and rejected by import/creation commands; disallowed modes are not rendered and `setMode()` returns `false` without changing state.
234
+ 7. Theme changes update the existing instance.
235
+ 8. Unmount/remount leaves no duplicate listeners, overlays, or framework instances.
236
+ 9. Browser Console has no errors.
237
+ 10. If a subtitle resolver is used, verify override, `undefined`, `null`, SVG parity, and that `refreshPresentation()` preserves XML, history, selection, scope, zoom, pan, theme, and mode. Verify Instance summaries are unchanged.
238
+
239
+ The installation is complete only when the package is present, the production build succeeds, and the relevant browser checks pass. Report any unverified branch explicitly.
240
+
241
+ ## Reference
242
+
243
+ - `README.md`: human-facing product overview and quick start.
244
+ - `llms-full.txt`: complete generated AI context containing setup, component, Interface, customization, publishing, and architecture documentation.
245
+
246
+
247
+ <!-- SOURCE: README.md -->
248
+
249
+ # BPMN Nova
250
+
251
+ **简体中文(默认)** · [English](README.en.md)
252
+
253
+ 面向现代 Web 应用的独立 BPMN 2.0 流程设计、展示与审批轨迹组件库。
254
+
255
+ BPMN Nova 提供可嵌入的流程设计器、只读 Viewer、运行态审批轨迹、实例级主题、纯 SVG 导出,以及 React / Vue 适配。项目使用独立的 DOM / SVG 渲染实现,不依赖 `bpmn-js`,并为 Flowable 与 Activiti 提供 XML Profile 和属性扩展。
256
+
257
+ > [!IMPORTANT]
258
+ > 当前版本为 `0.3.3-preview`。它适合 SDK 评估、产品集成验证和企业审批原型;公开 Interface、BPMN XML round-trip 与引擎兼容能力仍在持续稳定中,建议使用 `@preview` 安装并在生产接入前完成目标流程验证。
259
+
260
+ AI 或代码生成工具接入必须从 [`llms.txt`](llms.txt) 的完整安装流程开始;需要全部接口与定制上下文时再读取 [`llms-full.txt`](llms-full.txt)。这两份文件也会随三个公开 npm 包发布。
261
+
262
+ ## 视觉总览
263
+
264
+ ### 设计与展示
265
+
266
+ | 流程设计器与属性面板 | 只读流程展示 |
267
+ | --- | --- |
268
+ | ![采购申请审批流程的设计器、Palette、画布与属性面板](docs/assets/bpmn-nova-designer.jpg) | ![采购申请审批流程的只读 Viewer 与流程摘要](docs/assets/bpmn-nova-viewer.jpg) |
269
+
270
+ ### 审批轨迹的三种展示形式
271
+
272
+ #### 实际路径
273
+
274
+ 只展示流程实例实际发生的节点和有效线路,聚焦当前审批上下文、处理人、意见摘要以及驳回 / 退回关系,适合桌面端追踪本次实例为什么走到当前状态。
275
+
276
+ ![采购申请审批流程的实际审批路径](docs/assets/bpmn-nova-approval-effective-path.jpg)
277
+
278
+ #### 移动时间线
279
+
280
+ 按 Activity Visit 和实际处理顺序纵向展示,直接呈现审批意见、图片、附件、多轮处理和转办等动作,适合窄屏、移动端和以记录阅读为主的场景。
281
+
282
+ ![采购申请审批流程的移动审批时间线](docs/assets/bpmn-nova-approval-mobile-timeline.jpg)
283
+
284
+ #### 完整 BPMN
285
+
286
+ 保留完整流程定义,在全部节点和连线上叠加运行状态、当前节点、历史摘要与驳回线路,适合需要同时理解流程设计和实例执行位置的场景。
287
+
288
+ ![采购申请审批流程的完整 BPMN 运行态](docs/assets/bpmn-nova-approval-full-bpmn.jpg)
289
+
290
+ ### 主题与导出
291
+
292
+ | 深色主题运行态 | SVG 导出确认预览 |
293
+ | --- | --- |
294
+ | ![深色主题下的采购申请审批流程运行态](docs/assets/bpmn-nova-dark-runtime.jpg) | ![采购申请审批流程的 SVG 导出确认预览](docs/assets/bpmn-nova-svg-export-preview.jpg) |
295
+
296
+ ## 项目定位
297
+
298
+ BPMN Nova 是一个引擎中立、实例级可嵌入的 BPMN UI SDK。它把“流程定义编辑”“流程图展示”“流程实例轨迹”与“宿主业务系统”分开:组件负责建模、可视化、交互和导出,宿主负责流程引擎执行、审批提交、人员权限和资源存储。
299
+
300
+ 主题、Renderer、Palette、Properties Provider、运行态动作和 SVG 导出都通过公开接口扩展;Runtime Snapshot 只描述业务事实,不保存颜色、图标、HTML 或临时资源地址。
301
+
302
+ ## 能力矩阵
303
+
304
+ | 能力 | 当前支持 |
305
+ | --- | --- |
306
+ | BPMN 建模 | 当前目录覆盖 59 种 BPMN 可视节点,以及 Sequence Flow、Message Flow、Association、Group、Pool 和 Lane |
307
+ | 设计交互 | Palette 拖放、快捷创建、连线、框选、多选、对齐、自动布局、Undo / Redo、上下文菜单、Scope 导航 |
308
+ | XML 与引擎 Profile | BPMN XML / BPMN DI 导入导出,Flowable / Activiti 扩展字段与独立 Profile |
309
+ | 属性面板 | Provider 驱动的 Group / Entry、数据源、验证、业务字段、人员组织、表单和引擎配置 |
310
+ | Viewer 与 Runtime | 只读 BPMN、实际审批路径、移动时间线、完整 BPMN 运行态叠加、状态节点与驳回 / 退回线路 |
311
+ | 审批动作 | 提交、通过、驳回、退回、加签、转办、委派、撤回、备注、跳过和自定义动作 |
312
+ | 图文与附件 | 安全文本段落、图片缩略图与预览、文件摘要与下载、资源 Resolver、加载失败降级 |
313
+ | 主题 | `light` / `dark` / `auto`、实例级 CSS Variables、语义 Tone、自定义状态与动作配色、运行时无重挂载切换 |
314
+ | SVG 导出 | 设计、只读展示、实际路径、移动时间线和完整 BPMN;确认预览、主题切换、透明背景和资源警告 |
315
+ | 自定义扩展 | Palette、Properties、图标、节点内容、详情、时间线、线路、上下文菜单、Slot 与 SVG Renderer |
316
+ | 框架适配 | Vanilla JavaScript / TypeScript、React 18+、Vue 3.3+,完整类型声明与实例方法透传 |
317
+
318
+ ## 五类使用场景
319
+
320
+ | 场景 | 推荐入口 | 展示语义 |
321
+ | --- | --- | --- |
322
+ | 流程设计 | Studio / Designer | 编辑完整流程定义、属性和 XML |
323
+ | 只读展示 | Viewer `standard` | 展示完整 BPMN 结构,不叠加实例轨迹 |
324
+ | 实际路径 | Viewer `approval` | 按实例已发生的有效路径展示状态、意见和异常线路 |
325
+ | 移动时间线 | Viewer `compact` | 按审批访问轮次纵向展示动作、图文、附件和 Bottom Sheet 详情 |
326
+ | 完整 BPMN | Viewer `standard` + Runtime | 保留完整 BPMN,并叠加节点状态、动作摘要和回退线路 |
327
+
328
+ `projection: 'auto'` 会根据容器与 Runtime 场景选择合适投影;需要稳定布局语义时,应显式传入投影。
329
+
330
+ ## 安装与包选择
331
+
332
+ 完整流程工作台推荐安装 Studio:
333
+
334
+ ```bash
335
+ npm install @bpmn-nova/studio@preview
336
+ ```
337
+
338
+ JavaScript 入口不会自动注入 CSS。使用视觉组件时,请显式导入对应包的 `styles.css`。所有公开包都是 ESM,包含 TypeScript 声明,要求 Node.js 18+。
339
+
340
+ ### 三个公开入口
341
+
342
+ | 包 | 用途 |
343
+ | --- | --- |
344
+ | [`@bpmn-nova/studio`](https://www.npmjs.com/package/@bpmn-nova/studio) | Vanilla JavaScript / TypeScript:设计、展示、Runtime、Properties、主题和 SVG 导出 |
345
+ | [`@bpmn-nova/react`](https://www.npmjs.com/package/@bpmn-nova/react) | React 18+ 组件、Hook 与 Ref |
346
+ | [`@bpmn-nova/vue`](https://www.npmjs.com/package/@bpmn-nova/vue) | Vue 3.3+ 组件、Composable 与 Expose |
347
+
348
+ 设计、Viewer、Runtime、Theme、SVG Export、Flowable 和 Activiti 继续保持独立内部 Module,但统一由 Studio 根入口或受支持子路径暴露,不再单独发布。一个业务项目只直接安装上述三个包之一。
349
+
350
+ ## 快速开始
351
+
352
+ 视觉组件依赖容器尺寸,请先提供明确高度:
353
+
354
+ ```html
355
+ <div id="bpmn-studio" style="height: 720px"></div>
356
+ ```
357
+
358
+ ### Studio
359
+
360
+ ```js
361
+ import {
362
+ createEmptyProcess,
363
+ createStudioController,
364
+ createStudioShell,
365
+ } from '@bpmn-nova/studio'
366
+ import '@bpmn-nova/studio/styles.css'
367
+
368
+ const model = createEmptyProcess('flowable')
369
+ model.id = 'Process_PurchaseApproval'
370
+ model.name = '采购申请审批流程'
371
+
372
+ const studio = createStudioController({
373
+ model,
374
+ propertiesProfile: 'business',
375
+ allowedNodeTypes: ['startEvent', 'userTask', 'exclusiveGateway', 'endEvent'],
376
+ allowedEdgeTypes: ['sequenceFlow'],
377
+ })
378
+
379
+ const shell = createStudioShell({
380
+ container: document.querySelector('#bpmn-studio'),
381
+ studio,
382
+ mode: 'design',
383
+ allowedModes: ['design'],
384
+ theme: 'auto',
385
+ })
386
+
387
+ const xml = studio.exportXml('flowable')
388
+
389
+ // 页面卸载时释放事件和观察器
390
+ shell.destroy()
391
+ studio.destroy()
392
+ ```
393
+
394
+ 导入已有 BPMN XML:
395
+
396
+ ```js
397
+ import { importBpmn } from '@bpmn-nova/studio'
398
+
399
+ const model = importBpmn(xmlText, 'flowable')
400
+ const studio = createStudioController({ model })
401
+ ```
402
+
403
+ ### Viewer 与 Runtime
404
+
405
+ ```js
406
+ import { BpmnViewer } from '@bpmn-nova/studio/viewer'
407
+ import '@bpmn-nova/studio/styles.css'
408
+
409
+ const viewer = new BpmnViewer({
410
+ container: document.querySelector('#viewer'),
411
+ model,
412
+ runtime,
413
+ projection: 'approval',
414
+ onTraceClick(event) {
415
+ console.log(event.elementId, event.actions)
416
+ },
417
+ })
418
+
419
+ viewer.fitView()
420
+ ```
421
+
422
+ ### React
423
+
424
+ ```bash
425
+ npm install @bpmn-nova/react@preview
426
+ ```
427
+
428
+ ```jsx
429
+ import { BpmnStudio } from '@bpmn-nova/react'
430
+ import '@bpmn-nova/react/styles.css'
431
+
432
+ export function WorkflowEditor({ initialXml }) {
433
+ return (
434
+ <div style={{ height: 720 }}>
435
+ <BpmnStudio
436
+ xml={initialXml}
437
+ engine="flowable"
438
+ propertiesProfile="business"
439
+ allowedNodeTypes={['startEvent', 'userTask', 'exclusiveGateway', 'endEvent']}
440
+ allowedEdgeTypes={['sequenceFlow']}
441
+ mode="design"
442
+ allowedModes={['design']}
443
+ theme="auto"
444
+ onChange={(model, reason, nextXml) => {
445
+ console.log(reason, nextXml)
446
+ }}
447
+ />
448
+ </div>
449
+ )
450
+ }
451
+ ```
452
+
453
+ React Ref 透传 `exportXml()`、`fitView()`、`setTheme()`、`exportSvg()` 和 `openSvgExportPreview()` 等实例能力。
454
+
455
+ ### Vue 3
456
+
457
+ ```bash
458
+ npm install @bpmn-nova/vue@preview
459
+ ```
460
+
461
+ ```vue
462
+ <script setup>
463
+ import { BpmnStudio } from '@bpmn-nova/vue'
464
+ import '@bpmn-nova/vue/styles.css'
465
+
466
+ const props = defineProps({ initialXml: String })
467
+
468
+ function onChange(model, reason, nextXml) {
469
+ console.log(reason, nextXml)
470
+ }
471
+ </script>
472
+
473
+ <template>
474
+ <div style="height: 720px">
475
+ <BpmnStudio
476
+ :xml="props.initialXml"
477
+ engine="flowable"
478
+ properties-profile="business"
479
+ :allowed-node-types="['startEvent', 'userTask', 'exclusiveGateway', 'endEvent']"
480
+ :allowed-edge-types="['sequenceFlow']"
481
+ mode="design"
482
+ :allowed-modes="['design']"
483
+ theme="auto"
484
+ @change="onChange"
485
+ />
486
+ </div>
487
+ </template>
488
+ ```
489
+
490
+ Vue Expose 提供与 Vanilla 实例对应的 Actions、校验、XML、视图、主题和 SVG 导出方法。
491
+
492
+ ### 嵌入现有业务设计工作台
493
+
494
+ 默认 `BpmnStudio` 仍是完整工作台。宿主已有业务头部和属性面板时,可以保留 Nova Palette/Canvas/编辑工具,只替换 Header 左侧并隐藏 Nova Properties:
495
+
496
+ ```vue
497
+ <BpmnStudio
498
+ ref="studioRef"
499
+ :xml="xml"
500
+ engine="activiti"
501
+ mode="design"
502
+ :allowed-modes="['design']"
503
+ :allowed-node-types="supportedNodeTypes"
504
+ :allowed-edge-types="['sequenceFlow']"
505
+ :regions="{ right: 'hidden' }"
506
+ theme="auto"
507
+ @change="handleChange"
508
+ @selection-change="handleSelectionChange"
509
+ @validation="handleValidation"
510
+ >
511
+ <template #header-start="{ state, actions }">
512
+ <!-- 返回、业务图标、流程名称和类型 -->
513
+ </template>
514
+ <template #header-actions="{ actions, mode }">
515
+ <button type="button" :disabled="mode !== 'design'" @click="actions.validate()">校验</button>
516
+ <button type="button" :disabled="mode !== 'design'" @click="saveDraft(actions.exportXml())">保存</button>
517
+ <button type="button" :disabled="mode !== 'design'" @click="publishProcess(actions)">发布</button>
518
+ </template>
519
+ </BpmnStudio>
520
+ ```
521
+
522
+ `header-start` 只替换 Nova Brand;`header-actions` 只替换默认的“校验 / 导入 / 导出”动作组,适合宿主组合“校验 / 保存 / 发布”;`header` 可替换整个 Header。`regions` 隐藏区域后不会保留 Grid 空白,也不会重建 Canvas。React 提供等价的 `headerStart` / `headerActions` / `header` Render Prop,Core 提供同名 DOM Slots。默认 Header 的最佳视图只保留在底部缩放区。
523
+
524
+ `actions.validate()` 会先更新 Nova 默认状态栏,再发送 `validation` 事件并返回 issues;公开 `validate()` 使用相同流程。`valid` 只在存在 error 时为 `false`,warning 会展示和上报但不默认阻止发布。
525
+
526
+ 发布事务由宿主实现,并可以直接复用同一个 Header Action 完成“校验后发布”:
527
+
528
+ ```js
529
+ async function publishProcess(actions) {
530
+ const issues = actions.validate()
531
+ if (issues.some((issue) => issue.level === 'error')) return
532
+ await publishXml(actions.exportXml())
533
+ }
534
+ ```
535
+
536
+ 宿主右侧属性面板应位于 Nova 外部,并以 `selection-change` / `onSelectionChange` 为状态来源、以稳定 BPMN Element ID 关联业务配置。`element-click` 不能替代选择事件。Nova 不拥有宿主的保存、发布、权限或服务端事务。
537
+
538
+ `theme="auto"` 是显式启用系统主题适配;为了兼容已有接入,未传 Theme 时仍默认为 `light`。Vue 包同时提供独立的 `BpmnPalettePanel`,用于完全自定义布局。
539
+
540
+ Shell 的 `design`、`viewer`、`instance` 分别表示流程设计、流程展示和审批轨迹。宿主可以观察并在运行时调整可用模式,而不会写入 BPMN Model 或 Undo/Redo History:
541
+
542
+ ```js
543
+ const offMode = shell.subscribeMode(({ mode, previousMode, source }) => {
544
+ console.log(previousMode, mode, source)
545
+ })
546
+
547
+ shell.setMode('viewer') // 成功切换返回 true
548
+ shell.setAllowedModes(['design', 'viewer'])
549
+ ```
550
+
551
+ 业务系统只需要覆盖标准节点卡片的定义态副标题时,可以提供轻量 Resolver。`undefined` 保留 Nova 默认值,`null` 隐藏副标题行,字符串替换显示值;外部 Map 等数据变化后显式调用 `refreshPresentation()`。该刷新不改 XML、历史、选择或视口,且不会覆盖 `instance` 的运行事实。
552
+
553
+ ```js
554
+ const subtitles = new Map()
555
+ const shell = createStudioShell({
556
+ container,
557
+ studio,
558
+ nodeSubtitleResolver: ({ node }) => subtitles.has(node.id)
559
+ ? subtitles.get(node.id)
560
+ : undefined,
561
+ })
562
+
563
+ subtitles.set('ServiceTask_Archive', '归档到采购系统')
564
+ shell.refreshPresentation()
565
+ ```
566
+
567
+ `allowedNodeTypes` 会过滤 Palette 和所有建模入口;一个节点类型仍可能由多个 Palette Preset 创建。副标题 Resolver 只是视觉投影,不能替代标准 BPMN Candidate 配置或 Runtime Snapshot。
568
+
569
+ ## 审批动作、图文与附件
570
+
571
+ Runtime Snapshot 使用动作描述提交、通过、驳回、加签、转办等事实。内容只保存安全文本和资源引用,不保存 URL、Base64、`Blob`、Markdown 或 HTML。
572
+
573
+ ```js
574
+ const runtime = {
575
+ processInstanceId: 'purchase-2026-0824',
576
+ status: 'running',
577
+ activities: [
578
+ {
579
+ id: 'activity-manager-1',
580
+ elementId: 'UserTask_Manager',
581
+ visitId: 'visit-manager-1',
582
+ status: 'completed',
583
+ },
584
+ ],
585
+ actions: [
586
+ {
587
+ id: 'action-manager-approve-1',
588
+ type: 'approve',
589
+ elementId: 'UserTask_Manager',
590
+ visitId: 'visit-manager-1',
591
+ activityId: 'activity-manager-1',
592
+ actor: { id: 'user-li', name: '李经理' },
593
+ occurredAt: '2026-08-24T10:12:00+08:00',
594
+ content: {
595
+ plainText: '资料完整,同意提交总经理审批。',
596
+ blocks: [
597
+ { type: 'paragraph', text: '现场报价单和采购核验清单见附件。' },
598
+ { type: 'image', assetId: 'quotation-image', alt: '现场报价单' },
599
+ { type: 'file', assetId: 'checklist-file' },
600
+ ],
601
+ assets: [
602
+ {
603
+ id: 'quotation-image',
604
+ name: '现场报价单.png',
605
+ mediaType: 'image/png',
606
+ size: 82410,
607
+ width: 1280,
608
+ height: 720,
609
+ },
610
+ {
611
+ id: 'checklist-file',
612
+ name: '采购核验清单.txt',
613
+ mediaType: 'text/plain',
614
+ size: 248,
615
+ },
616
+ ],
617
+ },
618
+ },
619
+ ],
620
+ visitedEdges: [],
621
+ }
622
+ ```
623
+
624
+ 资源地址由宿主按用途和权限动态解析:
625
+
626
+ ```js
627
+ const viewer = new BpmnViewer({
628
+ container,
629
+ model,
630
+ runtime,
631
+ runtimeAssetResolver(asset, { purpose, action, signal }) {
632
+ return getSignedAssetUrl({
633
+ assetId: asset.id,
634
+ purpose, // thumbnail | preview | download | export
635
+ actionId: action.id,
636
+ signal,
637
+ })
638
+ },
639
+ })
640
+ ```
641
+
642
+ 图片可在 Viewer 容器内预览,普通文件提供摘要和下载入口;资源拒绝、过期、MIME 不匹配或 Resolver 缺失时,会局部显示“资源不可用”,不会破坏其他轨迹内容。
643
+
644
+ ## 主题与运行态外观
645
+
646
+ 主题作用于组件实例根节点,不修改宿主 `<html>`。`auto` 使用实例所属 Window 的 `prefers-color-scheme` 并监听运行中变化;宿主有独立主题状态时,应直接传入 `light` 或 `dark`。
647
+
648
+ ```js
649
+ const shell = createStudioShell({
650
+ container,
651
+ studio,
652
+ theme: {
653
+ mode: 'auto',
654
+ dark: {
655
+ colors: {
656
+ canvas: '#0b1018',
657
+ surface: '#18202b',
658
+ primary: '#8b86ff',
659
+ },
660
+ tones: {
661
+ audit: {
662
+ foreground: '#d7c7ff',
663
+ background: '#2b2441',
664
+ border: '#6d5d96',
665
+ strong: '#b69cff',
666
+ },
667
+ },
668
+ },
669
+ },
670
+ runtimeAppearance: {
671
+ statuses: { active: 'primary', completed: 'success' },
672
+ actions: {
673
+ approve: { label: '通过', tone: 'success' },
674
+ reject: { label: '驳回', tone: 'danger' },
675
+ audit: { label: '复核', tone: 'audit' },
676
+ },
677
+ transitions: { reject: { tone: 'danger' }, return: { tone: 'warning' } },
678
+ },
679
+ })
680
+
681
+ shell.setThemeMode('dark')
682
+ shell.setTheme({ mode: 'light', light: { colors: { primary: '#4f46e5' } } })
683
+ console.log(shell.getThemeState())
684
+ ```
685
+
686
+ 内置 Tone 包括 `primary`、`success`、`danger`、`warning`、`info`、`neutral` 和节点类型强调色。无效颜色、非法 Tone 或未知配置会安全回退,不进入 Runtime Snapshot。
687
+
688
+ ## SVG 导出与确认预览
689
+
690
+ 设计器、Viewer、审批轨迹和 Studio 都可以生成纯 SVG Artifact:
691
+
692
+ ```js
693
+ const artifact = await shell.exportSvg({
694
+ theme: 'current', // current | light | dark
695
+ transparentBackground: false,
696
+ padding: 32,
697
+ filename: '采购申请审批流程-完整-BPMN.svg',
698
+ })
699
+
700
+ console.log(artifact.svg, artifact.width, artifact.height, artifact.warnings)
701
+
702
+ // 打开内置确认预览;确认下载复用当前预览的同一份 Artifact
703
+ shell.openSvgExportPreview()
704
+ ```
705
+
706
+ - 导出完整业务内容,不受当前缩放、平移、滚动或移动时间线折叠状态影响。
707
+ - 预览支持最佳视图、1:1、缩放、拖动画布、当前 / 浅色 / 深色主题和透明背景。
708
+ - SVG 不依赖 `foreignObject`、页面 DOM 或外部 CSS;文本被安全转义。
709
+ - 审批图片通过 `runtimeAssetResolver` 的 `export` 用途解析后嵌入;普通文件保留名称、MIME 和大小。
710
+ - 自定义 HTML Renderer 没有 SVG 适配时回退到标准视觉并产生非阻断警告。
711
+
712
+ 第一版只提供 SVG,不提供 PNG 或 PDF 导出。
713
+
714
+ ## 自定义能力索引
715
+
716
+ | 扩展点 | 用途 |
717
+ | --- | --- |
718
+ | Palette | 添加、替换、分组或隐藏建模条目 |
719
+ | Properties | 注册 Provider、Group、Entry、Data Provider、校验和自定义组件 |
720
+ | Icons | 覆盖内置图标或注册业务图标 |
721
+ | Node Renderer | 自定义 Canvas / Viewer 节点内容和运行态摘要 |
722
+ | Node Subtitle Resolver | 轻量覆盖 Design / Viewer 标准节点副标题并无历史刷新 |
723
+ | Runtime Details Renderer | 自定义 Popover / Bottom Sheet 审批详情 |
724
+ | Runtime Timeline Renderer | 自定义移动时间线条目、动作和资源布局 |
725
+ | Runtime Transition Renderer | 自定义驳回、退回和异常线路 |
726
+ | SVG Renderer | 为自定义节点或时间线提供纯 SVG 导出视觉 |
727
+ | Context Menu / Slot | 扩展设计器操作、工具区和宿主内容 |
728
+ | Studio Mode Interface | 观察并运行时控制设计、展示与审批轨迹模式 |
729
+ | React / Vue Bridge | 通过 Props、Ref / Expose 与组合式接口接入上述能力 |
730
+
731
+ 完整接口和示例见[自定义指南](docs/CUSTOMIZATION.md)与[组件参数](docs/COMPONENTS.md)。
732
+
733
+ ## 浏览器接入注意事项
734
+
735
+ - 为 Canvas、Studio 和 Viewer 容器设置明确高度;隐藏容器恢复显示后可调用 `fitView()`。
736
+ - CSS 必须由宿主显式导入;Studio、React、Vue 各自提供一个自包含样式入口。
737
+ - 使用 ESM 与现代浏览器;Node.js 18+ 用于构建和包消费工具链。
738
+ - `runtimeAssetResolver` 应按用户、动作和用途执行授权,并支持 `AbortSignal`;不要把永久签名 URL 写入 Snapshot。
739
+ - 同页可创建不同主题的多个实例,主题变量不会写入 `document.documentElement`。
740
+ - 自定义 HTML 不会自动进入 SVG;需要同步注册 SVG Renderer。
741
+
742
+ ## 文档导航
743
+
744
+ - [快速开始](docs/GETTING-STARTED.md):Studio、Designer、Viewer、React、Vue 与 XML。
745
+ - [组件参数](docs/COMPONENTS.md):Options、Props、事件、Ref / Expose 和实例方法。
746
+ - [API](docs/API.md):模型、命令、XML、Runtime Snapshot、主题与导出类型。
747
+ - [自定义指南](docs/CUSTOMIZATION.md):主题、Palette、图标、Renderer、Slot 和运行态扩展。
748
+ - [Properties Panel](docs/PROPERTIES-PANEL.md):Provider、Group、Entry、Data Provider 和自定义字段。
749
+ - [NPM Packages](docs/NPM-PACKAGES.md):三个公开包、Studio 子路径、构建验证与 Registry 清理说明。
750
+ - [AI 接入指南](docs/AI-INTEGRATION.md):面向 AI 的包选择、完整接入步骤、生命周期与排错。
751
+ - [`llms.txt`](llms.txt) / [`llms-full.txt`](llms-full.txt):可执行安装流程与生成式完整上下文。
752
+ - [Roadmap](docs/ROADMAP.md):Preview 边界与后续计划。
753
+
754
+ `docs/` 当前以中文为主;英文 README 只提供完整产品入口,不重复翻译底层 API 文档。更新公开能力时,请在同一个变更中同步维护 `README.md` 与 `README.en.md`。
755
+
756
+ ## Preview 边界
757
+
758
+ BPMN Nova 不负责:
759
+
760
+ - 流程引擎执行、任务领取、审批提交、加签或转办业务写入。
761
+ - 附件上传、删除、存储、病毒扫描、权限判断或下载签名。
762
+ - PNG / PDF 导出,或对宿主图片、PDF 和自定义资源做反色。
763
+ - BPMN 2.0 Schema 100% 覆盖。
764
+
765
+ 完整 Collaboration 规则、更多 IO / Data Association、完全无损的 XML round-trip,以及真实 Flowable / Activiti 部署验证仍在持续完善。生产接入前请针对目标引擎、流程模型、权限与资源策略完成兼容性验证。
766
+
767
+ ## 本地开发
768
+
769
+ 要求 Node.js 18+:
770
+
771
+ ```bash
772
+ npm run demo
773
+ ```
774
+
775
+ 打开 `http://127.0.0.1:4173/apps/playground/`。
776
+
777
+ 构建全部发布包并运行仓库现有检查:
778
+
779
+ ```bash
780
+ npm run build:packages
781
+ npm test
782
+ ```
783
+
784
+ ## License
785
+
786
+ [Apache-2.0](LICENSE)
787
+
788
+
789
+ <!-- SOURCE: docs/AI-INTEGRATION.md -->
790
+
791
+ # BPMN Nova AI 接入指南
792
+
793
+ 本文供代码生成助手、IDE Agent 和自动化集成工具使用。目标是让 AI 只依赖公开 Interface,正确选择 NPM 包,并完成设计、展示、审批轨迹、主题和 SVG 导出的接入。
794
+
795
+ ## 1. 先确认项目目录,再选择公开包
796
+
797
+ ### 1.1 项目目录门禁
798
+
799
+ 开始安装或修改代码前,AI 必须先确认当前目录就是目标 Web 应用:
800
+
801
+ 1. 查找当前项目的 `package.json`,并结合 `src`、应用入口和构建配置判断技术栈。
802
+ 2. Monorepo 必须定位到用户要接入 BPMN Nova 的具体应用包,不能只依据 Workspace 根目录依赖。
803
+ 3. 如果找不到 `package.json`、应用源码或可识别的 Web 技术栈,停止安装和代码修改,询问用户是否选错项目目录,并请求正确目录及 React、Vue 或其他 JavaScript/TypeScript 技术栈信息。
804
+ 4. 如果同时发现 React 和 Vue,或者存在多个候选应用且目标不明确,询问用户本次需要修改哪一个应用。
805
+
806
+ 推荐询问:
807
+
808
+ > 当前目录没有足够信息确认目标项目和技术栈。请确认正确的项目目录,并说明目标应用使用 React、Vue,还是其他 JavaScript/TypeScript 技术栈。
809
+
810
+ 完成门禁的标准是:目标应用目录明确,并且有项目文件或用户说明能够支持唯一的包选择。检测不到技术栈不能被解释为 Vanilla 项目。
811
+
812
+ ### 1.2 包选择规则
813
+
814
+ 根据宿主技术栈只安装一个包:
815
+
816
+ | 宿主 | 安装命令 | 选择规则 |
817
+ | --- | --- | --- |
818
+ | React 18+、Next.js、Remix 等 React 应用 | `npm install @bpmn-nova/react@preview` | 使用 React 组件、Hook 和 Ref |
819
+ | Vue 3.3+、Nuxt 等 Vue 应用 | `npm install @bpmn-nova/vue@preview` | 使用 Vue 组件、Composable 和 Expose |
820
+ | 已确认的其他 JavaScript / TypeScript Web 项目 | `npm install @bpmn-nova/studio@preview` | 使用框架无关 Interface |
821
+
822
+ 已经安装三个公开包之一且与目标技术栈匹配时,沿用现有入口。React 和 Vue 同时存在但无法确定目标应用时先询问用户。`@bpmn-nova/core`、`designer`、`viewer`、`runtime`、`theme`、`export-svg`、`properties` 等历史入口不参与选择;这些能力已经收进 Studio,并通过根入口或受支持子路径提供。
823
+
824
+ 所有示例都必须满足:
825
+
826
+ - 导入对应公开包的 `styles.css`。
827
+ - 为组件容器及其父级提供可计算高度。
828
+ - 只在浏览器生命周期创建视觉实例;SSR 阶段只准备 XML 和数据。
829
+ - 组件卸载时销毁实例和取消订阅。
830
+ - Runtime Snapshot 不保存资源 URL、Base64、Blob 或 HTML。
831
+
832
+ ## 2. Vanilla JavaScript / TypeScript
833
+
834
+ ### 2.1 完整 Studio
835
+
836
+ ```html
837
+ <div id="studio" class="workflow-studio"></div>
838
+ ```
839
+
840
+ ```css
841
+ .workflow-studio {
842
+ width: 100%;
843
+ height: min(760px, calc(100vh - 96px));
844
+ min-height: 520px;
845
+ }
846
+ ```
847
+
848
+ ```js
849
+ import {
850
+ createEmptyProcess,
851
+ createStudioController,
852
+ createStudioShell,
853
+ } from '@bpmn-nova/studio'
854
+ import '@bpmn-nova/studio/styles.css'
855
+
856
+ const model = createEmptyProcess('flowable')
857
+ model.id = 'Process_Approval'
858
+ model.name = '审批流程'
859
+
860
+ const studio = createStudioController({
861
+ model,
862
+ propertiesProfile: 'business',
863
+ allowedNodeTypes: ['startEvent', 'userTask', 'exclusiveGateway', 'endEvent'],
864
+ allowedEdgeTypes: ['sequenceFlow'],
865
+ })
866
+
867
+ const shell = createStudioShell({
868
+ container: document.querySelector('#studio'),
869
+ studio,
870
+ mode: 'design',
871
+ allowedModes: ['design', 'viewer'],
872
+ theme: 'auto',
873
+ })
874
+
875
+ const unsubscribe = studio.subscribe((event) => {
876
+ if (event.type === 'modelChanged') {
877
+ saveDraft(studio.exportXml())
878
+ }
879
+ })
880
+
881
+ export function disposeWorkflow() {
882
+ unsubscribe()
883
+ shell.destroy()
884
+ studio.destroy()
885
+ }
886
+ ```
887
+
888
+ Shell 负责界面组合,Controller 负责模型、命令、选择和历史。外部传入的 Controller 不会因 Shell 销毁而自动销毁。
889
+
890
+ Mode 是 Shell 展示状态。`design`、`viewer`、`instance` 分别表示流程设计、流程展示和审批轨迹;使用 `getMode()` / `setMode()` / `setAllowedModes()` / `subscribeMode()` 与宿主状态同步,不要把 Mode 写进 BPMN Model 或 Studio State。
891
+
892
+ 定义态业务摘要使用同步 `nodeSubtitleResolver`,按稳定 `node.id` 查询宿主数据。返回 `undefined` 保留 Nova 默认副标题,`null` 删除该行,字符串覆盖显示。外部 Map 更新后调用 `refreshPresentation()`;不要调用 `updateNode()`、重新导入 XML 或重建组件来刷新纯视觉摘要。Instance 模式继续完全使用 Runtime Presentation。
893
+
894
+ ### 2.2 独立 Designer 或 Viewer
895
+
896
+ 独立能力仍由 Studio 提供,不要安装旧包:
897
+
898
+ ```js
899
+ import { createEmptyProcess, importBpmn } from '@bpmn-nova/studio'
900
+ import { BpmnDesigner } from '@bpmn-nova/studio/designer'
901
+ import { BpmnViewer } from '@bpmn-nova/studio/viewer'
902
+ import '@bpmn-nova/studio/styles.css'
903
+ ```
904
+
905
+ Viewer 的常用投影:
906
+
907
+ | projection | 结果 |
908
+ | --- | --- |
909
+ | `standard` | 完整 BPMN;传入 Runtime 时叠加运行状态 |
910
+ | `approval` | 实际发生的有效审批路径 |
911
+ | `compact` | 纵向移动审批时间线 |
912
+ | `auto` | 按 Viewer 容器宽度切换 `approval` / `compact` |
913
+
914
+ ```js
915
+ const viewer = new BpmnViewer({
916
+ container: document.querySelector('#viewer'),
917
+ model: importBpmn(xml, 'flowable'),
918
+ runtime,
919
+ projection: 'approval',
920
+ theme: 'auto',
921
+ runtimeAssetResolver,
922
+ })
923
+
924
+ viewer.fitView()
925
+ viewer.setProjection('compact')
926
+ viewer.setRuntime(nextRuntime)
927
+ viewer.destroy()
928
+ ```
929
+
930
+ ## 3. React 18+
931
+
932
+ ```bash
933
+ npm install @bpmn-nova/react@preview
934
+ ```
935
+
936
+ ```jsx
937
+ import { useRef, useState } from 'react'
938
+ import { BpmnStudio } from '@bpmn-nova/react'
939
+ import '@bpmn-nova/react/styles.css'
940
+
941
+ export function WorkflowEditor({ initialXml }) {
942
+ const studioRef = useRef(null)
943
+ const [mode, setMode] = useState('design')
944
+
945
+ return (
946
+ <div style={{ height: 720 }}>
947
+ <BpmnStudio
948
+ ref={studioRef}
949
+ xml={initialXml}
950
+ engine="flowable"
951
+ mode={mode}
952
+ allowedModes={['design', 'viewer']}
953
+ theme="auto"
954
+ propertiesProfile="business"
955
+ onChange={(model, reason, nextXml) => saveDraft(nextXml)}
956
+ onModeChange={(event) => setMode(event.mode)}
957
+ />
958
+ </div>
959
+ )
960
+ }
961
+ ```
962
+
963
+ 需要只读或审批轨迹时使用 React 包导出的 `BpmnViewer`,传入 `model`、`runtime`、`projection`、`runtimeAssetResolver` 和 `runtimeAppearance`。Ref 可访问实际 `mode`、`setMode()`、`refreshPresentation()`、`exportXml()`、`exportSvg()`、`openSvgExportPreview()`、`fitView()` 和 `setTheme()`;Header Render Context 同步提供 `mode`。实际可用方法以类型声明为准。
964
+
965
+ 不要在 React effect 中每次渲染都重新创建 Vanilla 实例。优先使用 Adapter 组件,让属性变化更新现有实例。
966
+
967
+ ## 4. Vue 3.3+
968
+
969
+ ```bash
970
+ npm install @bpmn-nova/vue@preview
971
+ ```
972
+
973
+ ```vue
974
+ <script setup>
975
+ import { ref } from 'vue'
976
+ import { BpmnStudio } from '@bpmn-nova/vue'
977
+ import '@bpmn-nova/vue/styles.css'
978
+
979
+ const props = defineProps({ initialXml: String })
980
+ const studioRef = ref(null)
981
+ const mode = ref('design')
982
+ const onChange = (model, reason, nextXml) => saveDraft(nextXml)
983
+ </script>
984
+
985
+ <template>
986
+ <div style="height: 720px">
987
+ <BpmnStudio
988
+ ref="studioRef"
989
+ :xml="props.initialXml"
990
+ engine="flowable"
991
+ v-model:mode="mode"
992
+ :allowed-modes="['design', 'viewer']"
993
+ theme="auto"
994
+ properties-profile="business"
995
+ @change="onChange"
996
+ @mode-change="event => console.log(event.source)"
997
+ />
998
+ </div>
999
+ </template>
1000
+ ```
1001
+
1002
+ 需要只读或审批轨迹时使用 Vue 包导出的 `BpmnViewer`。事件使用 kebab-case,`v-model:mode` 由 `update:mode` 驱动,`mode-change` 提供来源与前后值;Expose 包含 `getMode()`、`setMode()` 与 `refreshPresentation()`。不要在 watcher 中重复挂载组件,Mode、Theme、Runtime、Projection、Regions 和 Resolver 应通过响应式属性或轻量刷新更新。
1003
+
1004
+ ### 4.1 宿主工作台组合规则
1005
+
1006
+ 默认 `BpmnStudio` 是完整工作台。只有宿主明确拥有自己的区域时才配置 `regions`;例如 DX 复用自己的业务属性面板时使用 `regions: { right: 'hidden' }`,并把该面板放在 Nova 外部。隐藏区域不保留 Grid 轨道。
1007
+
1008
+ Vue 使用原生 `#header-start` / `#header-actions` / `#header`,React 使用 `headerStart` / `headerActions` / `header` Render Prop。Header Start 只替换 Brand,Header Actions 只替换默认“校验 / 导入 / 导出”动作组,完整 Header 通过公开 `StudioShellActions` 重建所需编辑操作。不要创建第二个 Vue App 或 React Root 挂载 Header。
1009
+
1010
+ 宿主通常在 Header Actions 中组合“校验 / 保存 / 发布”。校验按钮调用 `actions.validate()`:Nova 先更新默认状态栏,再通过 Vue `validation`、React `onValidation` 或 Core `subscribeValidation()` 发送只读结果。程序化 `validate()` 的来源为 `api`,Header Action 的来源为 `toolbar`;`valid` 仅在存在 error 时为 `false`。保存、发布、权限和服务端事务始终留在宿主。顶部不再重复渲染最佳视图,底部缩放区及 `fitView()` Interface 继续可用。
1011
+
1012
+ Header Slot/Render Context 的 `mode` 是当前实际模式。宿主用它显隐保存、发布按钮或外部属性面板;不要从 Nova Header DOM 读取选中按钮。运行时改变 `allowedModes` 会原地更新默认模式按钮,移除当前模式时只回退一次。
1013
+
1014
+ 外部业务属性面板必须以 `selection-change` / `onSelectionChange` 为状态来源,以稳定 BPMN Element ID 关联业务配置。该事件覆盖节点、连线、多选、清空和键盘选择;`element-click` / `onElementClick` 只是点击观察事件。保存草稿、发布、权限和服务端事务属于宿主,不要写入 Nova Actions。
1015
+
1016
+ 显式 `theme="auto"` 才表示跟随系统主题;默认 Theme 为兼容性的 `light`。Vue 与 React 都公开独立 `BpmnPalettePanel`,完整自定义布局时必须让 Canvas、Palette 和 Properties 复用同一个外部 Controller。
1017
+
1018
+ ### 4.1 模型所有权
1019
+
1020
+ 框架组件的 `xml`/`model` 是外部替换输入,`change`/`onChange` 的 XML 是草稿输出。宿主可以保存该输出;如果响应式状态回写的是完全相同的导出 XML,Adapter 会忽略这次回声并保留 Undo/Redo 历史。只有加载另一流程或服务器 revision 时才传入不同 XML,此时模型和历史会替换一次。
1021
+
1022
+ React/Vue 包重新导出创建外部 Controller、Palette、Properties、Context Menu、Icon 和 Template Registry 所需的公开函数,因此高级接入仍只需要直接安装对应 Adapter 包。
1023
+
1024
+ 生产工作流通常只支持 BPMN 子集。通过 Controller 或 `BpmnStudio` 的 `allowedNodeTypes` 与 `allowedEdgeTypes` 声明节点、连线白名单;该限制会校验初始/外部模型,并约束创建、快捷新增、模板和节点类型转换。Palette 会服从节点白名单,但同一节点类型可以有多个 Preset。通过 `allowedModes` 限制工作台可切换模式。实例模式没有 Runtime 时保持空运行事实,不会自动加载演示数据。
1025
+
1026
+ ## 5. Runtime Snapshot 与审批动作
1027
+
1028
+ Runtime Snapshot 描述已经发生的运行事实,不负责发起审批或执行流程引擎。新动作必须关联 `elementId` 与 `visitId`;`activityId` 用于精确关联工作项。
1029
+
1030
+ ```js
1031
+ const runtime = {
1032
+ processInstanceId: 'purchase-20260824',
1033
+ status: 'running',
1034
+ activities: [{
1035
+ id: 'activity-manager-1',
1036
+ elementId: 'UserTask_Manager',
1037
+ visitId: 'visit-manager-1',
1038
+ status: 'completed',
1039
+ assignee: '李经理',
1040
+ endTime: '2026-08-24T10:12:00+08:00',
1041
+ }],
1042
+ actions: [{
1043
+ id: 'action-manager-approve',
1044
+ type: 'approve',
1045
+ elementId: 'UserTask_Manager',
1046
+ visitId: 'visit-manager-1',
1047
+ activityId: 'activity-manager-1',
1048
+ actor: { id: 'manager-li', name: '李经理' },
1049
+ occurredAt: '2026-08-24T10:12:00+08:00',
1050
+ content: {
1051
+ plainText: '资料完整,同意提交总经理审批。',
1052
+ blocks: [
1053
+ { type: 'paragraph', text: '资料完整,同意提交总经理审批。' },
1054
+ { type: 'image', assetId: 'quotation-preview' },
1055
+ { type: 'file', assetId: 'purchase-checklist' },
1056
+ ],
1057
+ assets: [
1058
+ {
1059
+ id: 'quotation-preview',
1060
+ name: '现场报价单.png',
1061
+ mediaType: 'image/png',
1062
+ width: 1280,
1063
+ height: 720,
1064
+ },
1065
+ {
1066
+ id: 'purchase-checklist',
1067
+ name: '采购核验清单.txt',
1068
+ mediaType: 'text/plain',
1069
+ size: 248,
1070
+ },
1071
+ ],
1072
+ },
1073
+ }],
1074
+ visitedEdges: [],
1075
+ }
1076
+ ```
1077
+
1078
+ 旧的 `ActivityInstance.comment/outcome` 和 `RuntimeTransition.comment` 仍会兼容归一化,但新接入应使用 `actions`。
1079
+
1080
+ ## 6. 图片和附件 Resolver
1081
+
1082
+ Snapshot 只存资源 ID、名称、MIME、大小和图片尺寸。宿主通过 Resolver 按用途返回短期资源地址:
1083
+
1084
+ ```js
1085
+ const runtimeAssetResolver = async (asset, { purpose, signal }) => {
1086
+ const response = await fetch(
1087
+ `/api/runtime-assets/${encodeURIComponent(asset.id)}?purpose=${purpose}`,
1088
+ { signal },
1089
+ )
1090
+ if (!response.ok) return null
1091
+ return response.url
1092
+ }
1093
+ ```
1094
+
1095
+ `purpose` 可能是 `thumbnail`、`preview`、`download` 或 `export`。必须传递 `signal` 支持取消。Resolver 缺失、拒绝、过期或 MIME 不匹配时,Viewer 会显示资源不可用,不应让整条轨迹失败。
1096
+
1097
+ BPMN Nova 不负责上传、删除、存储、鉴权或签发下载地址。
1098
+
1099
+ ## 7. 主题和运行态外观
1100
+
1101
+ `theme` 接受 `light`、`dark`、`auto` 或主题对象。`auto` 跟随实例根节点所属 Window 的 `prefers-color-scheme` 并持续监听,不修改 `document.documentElement`。
1102
+
1103
+ ```js
1104
+ const theme = {
1105
+ mode: 'auto',
1106
+ dark: {
1107
+ colors: {
1108
+ canvas: '#0b1018',
1109
+ surface: '#18202b',
1110
+ primary: '#8f88ff',
1111
+ },
1112
+ },
1113
+ }
1114
+
1115
+ viewer.setTheme(theme)
1116
+ viewer.setThemeMode('dark')
1117
+ console.log(viewer.getThemeState())
1118
+ ```
1119
+
1120
+ 审批状态和动作使用语义 Tone,不要把颜色写入 Runtime Snapshot:
1121
+
1122
+ ```js
1123
+ const runtimeAppearance = {
1124
+ statuses: { completed: 'success', active: 'primary' },
1125
+ actions: {
1126
+ approve: { label: '通过', tone: 'success' },
1127
+ reject: { label: '驳回', tone: 'danger' },
1128
+ },
1129
+ transitions: {
1130
+ reject: { tone: 'danger' },
1131
+ return: { tone: 'warning' },
1132
+ },
1133
+ }
1134
+ ```
1135
+
1136
+ ## 8. SVG 导出
1137
+
1138
+ 高层视觉实例提供两类能力:
1139
+
1140
+ ```js
1141
+ // 先打开确认预览,用户确认后下载当前 Artifact。
1142
+ viewer.openSvgExportPreview({
1143
+ theme: 'current',
1144
+ transparentBackground: false,
1145
+ filename: '采购申请审批流程-实际路径.svg',
1146
+ })
1147
+
1148
+ // 只生成 Artifact,不自动下载。
1149
+ const artifact = await viewer.exportSvg({
1150
+ theme: 'dark',
1151
+ transparentBackground: false,
1152
+ padding: 36,
1153
+ })
1154
+ console.log(artifact.svg, artifact.width, artifact.height, artifact.warnings)
1155
+ ```
1156
+
1157
+ 导出内容不受当前缩放、平移或滚动影响。移动时间线会展开全部历史动作。审批图片通过 `purpose: 'export'` 解析并嵌入 Data URL;普通附件只输出名称、MIME 和大小。第一版不导出 PNG/PDF。
1158
+
1159
+ ## 9. 清理与生命周期
1160
+
1161
+ - Vanilla:调用实例 `destroy()`,并取消 Controller 订阅。
1162
+ - React:让组件卸载;不要手动销毁 Adapter 内部创建的实例。
1163
+ - Vue:让组件卸载;不要在 `onUpdated` 中创建新实例。
1164
+ - 外部传入的 Controller、Resolver 缓存或业务订阅由创建者负责清理。
1165
+ - 关闭 SVG 预览会中止未完成资源解析;销毁 Viewer 时也应清理详情层和预览层。
1166
+
1167
+ ## 10. 故障排查
1168
+
1169
+ ### 页面空白
1170
+
1171
+ 检查组件自身与所有父级的高度。仅设置 `width: 100%` 不够。
1172
+
1173
+ ### 样式缺失
1174
+
1175
+ 确认导入当前公开包的 `styles.css`,并避免重复导入 Studio 内部样式。
1176
+
1177
+ ### SSR 报 `window` 或 DOM 不存在
1178
+
1179
+ 在客户端挂载阶段加载视觉组件;服务端不要构造 Designer、Viewer 或 Shell。
1180
+
1181
+ ### 附件或导出图片不可用
1182
+
1183
+ 提供 `runtimeAssetResolver`,处理正确 `purpose`,返回同源或允许读取的资源,并转发 `AbortSignal`。
1184
+
1185
+ ### 自动主题没有跟随宿主应用
1186
+
1187
+ `auto` 只跟随浏览器/操作系统。如果宿主有独立主题状态,应把明确的 `light` 或 `dark` 传给组件并随宿主状态更新。
1188
+
1189
+ ### AI 生成旧包导入
1190
+
1191
+ 把 [llms.txt](../llms.txt) 或 [llms-full.txt](../llms-full.txt) 作为上下文重新执行,并要求只使用三个公开包和 Studio 受支持子路径。
1192
+
1193
+ ## 11. 能力边界
1194
+
1195
+ BPMN Nova 负责 BPMN 建模、XML 导入导出、只读展示、运行态审批轨迹、主题和纯 SVG 导出。它不负责流程引擎执行、审批提交、任务权限、附件上传存储、资源签名、PNG/PDF 导出,也不宣称 Preview 阶段覆盖 100% BPMN Schema。
1196
+
1197
+ ## 12. 权威文档
1198
+
1199
+ - [快速开始](GETTING-STARTED.md)
1200
+ - [组件参数](COMPONENTS.md)
1201
+ - [公开 API](API.md)
1202
+ - [自定义指南](CUSTOMIZATION.md)
1203
+ - [NPM 发布说明](NPM-PACKAGES.md)
1204
+ - [项目架构](ARCHITECTURE.md)
1205
+
1206
+
1207
+ <!-- SOURCE: docs/GETTING-STARTED.md -->
1208
+
1209
+ # BPMN Nova 快速开始
1210
+
1211
+ 本文面向通过 NPM 集成 BPMN Nova 的应用开发者。版本 `0.3.3-preview` 要求现代浏览器;Node.js 18+ 用于构建、SSR 和开发工具。
1212
+
1213
+ ## 1. 按语言与框架选择入口
1214
+
1215
+ 源码继续按 Core、Renderer、Runtime、Theme 等内部 Module 分层,但 NPM 只发布三个面向使用者的入口。普通项目按框架选择一个包即可:
1216
+
1217
+ | 项目环境 | 安装命令 | 后续章节 |
1218
+ | --- | --- | --- |
1219
+ | Vanilla JavaScript / TypeScript 完整 Studio | `npm install @bpmn-nova/studio@preview` | 第 3 节 |
1220
+ | 已有 React 18+ 工程 | `npm install @bpmn-nova/react@preview` | 第 7 节 |
1221
+ | 已有 Vue 3.3+ 工程 | `npm install @bpmn-nova/vue@preview` | 第 8 节 |
1222
+
1223
+ JavaScript 与 TypeScript 使用相同的包和导入方式。所有公开包都附带 `.d.ts`,TypeScript 项目无需额外安装类型包。React/Vue 的命令假定宿主工程已经由对应框架脚手架创建;适配包只声明框架 `peerDependencies`,不会替宿主选择、安装或升级框架版本。当前版本处于 Preview 阶段,因此文档统一显式安装 `@preview`,避免未来 `latest` 切换到稳定版后出现版本歧义。
1224
+
1225
+ Core、Viewer、Runtime、Theme 等目录不再是可直接安装的 NPM 包。Vanilla 项目从 Studio 根入口或受支持子路径导入;React/Vue 项目使用对应 Adapter。
1226
+
1227
+ 每个视觉包都有自己的完整样式入口。JavaScript 不会隐式插入 CSS:
1228
+
1229
+ ```js
1230
+ import '@bpmn-nova/studio/styles.css'
1231
+ // React 项目导入 @bpmn-nova/react/styles.css
1232
+ // Vue 项目导入 @bpmn-nova/vue/styles.css
1233
+ ```
1234
+
1235
+ 不要同时导入 Studio 和其内部 Renderer/Palette 样式,否则会重复加载规则。
1236
+
1237
+ ## 2. 容器尺寸
1238
+
1239
+ Canvas 依赖容器的实际宽高。父容器必须具有可计算高度:
1240
+
1241
+ ```css
1242
+ .workflow-page {
1243
+ width: 100%;
1244
+ height: min(760px, calc(100vh - 96px));
1245
+ min-height: 520px;
1246
+ }
1247
+ ```
1248
+
1249
+ ```html
1250
+ <div id="studio" class="workflow-page"></div>
1251
+ ```
1252
+
1253
+ 如果容器高度为 `0`,组件可以创建,但画布不会正常显示。
1254
+
1255
+ ## 3. Vanilla JavaScript / TypeScript Studio
1256
+
1257
+ ```js
1258
+ import {
1259
+ createEmptyProcess,
1260
+ createStudioController,
1261
+ createStudioShell,
1262
+ } from '@bpmn-nova/studio'
1263
+ import '@bpmn-nova/studio/styles.css'
1264
+
1265
+ const model = createEmptyProcess('flowable')
1266
+ model.id = 'Process_LeaveApproval'
1267
+ model.name = '请假审批'
1268
+
1269
+ const studio = createStudioController({
1270
+ model,
1271
+ propertiesProfile: 'business',
1272
+ allowedNodeTypes: ['startEvent', 'userTask', 'exclusiveGateway', 'endEvent'],
1273
+ allowedEdgeTypes: ['sequenceFlow'],
1274
+ })
1275
+
1276
+ const shell = createStudioShell({
1277
+ container: document.querySelector('#studio'),
1278
+ studio,
1279
+ mode: 'design',
1280
+ allowedModes: ['design', 'viewer'],
1281
+ theme: 'auto',
1282
+ })
1283
+
1284
+ const unsubscribe = studio.subscribe((event) => {
1285
+ if (event.type === 'modelChanged') {
1286
+ const xml = studio.exportXml()
1287
+ saveDraft(xml, event.reason)
1288
+ }
1289
+ })
1290
+
1291
+ // 页面卸载
1292
+ function dispose() {
1293
+ unsubscribe()
1294
+ shell.destroy()
1295
+ studio.destroy()
1296
+ }
1297
+ ```
1298
+
1299
+ Controller 持有模型、命令、选择、历史与作用域;Shell 只负责组合 Canvas、Palette、Properties 和 Viewer。销毁 Shell 不会自动销毁外部传入的 Controller。
1300
+
1301
+ 宿主可观察实际 Mode,并在运行时调整白名单:
1302
+
1303
+ ```js
1304
+ const offMode = shell.subscribeMode(({ mode, source }) => {
1305
+ externalProperties.hidden = mode !== 'design'
1306
+ console.log(source)
1307
+ })
1308
+
1309
+ shell.setMode('viewer')
1310
+ shell.setAllowedModes(['design', 'viewer'])
1311
+ ```
1312
+
1313
+ `design` 是流程设计,`viewer` 是流程展示,`instance` 是审批轨迹。Mode 只属于 Shell 展示状态,不会进入 BPMN Model 或撤销历史。
1314
+
1315
+ 业务数据需要显示在标准节点副标题时,传入同步 `nodeSubtitleResolver`;闭包中的 Map 更新后调用无历史刷新:
1316
+
1317
+ ```js
1318
+ const subtitles = new Map()
1319
+ const shell = createStudioShell({
1320
+ container,
1321
+ studio,
1322
+ nodeSubtitleResolver: ({ node }) => subtitles.has(node.id)
1323
+ ? subtitles.get(node.id)
1324
+ : undefined,
1325
+ })
1326
+
1327
+ subtitles.set('ServiceTask_Archive', '归档到采购系统')
1328
+ shell.refreshPresentation()
1329
+ ```
1330
+
1331
+ `undefined` 使用 Nova 默认值,`null` 删除该行。该 Resolver 只影响 Design/Viewer 与 SVG 标准视觉;Instance 仍由 Runtime Snapshot/Presentation 决定。`allowedNodeTypes` 会过滤 Palette,但同一节点类型可以继续提供多个业务 Preset。
1332
+
1333
+ ## 4. XML 导入与导出
1334
+
1335
+ ```js
1336
+ import { importBpmn } from '@bpmn-nova/studio'
1337
+
1338
+ const model = importBpmn(xmlText, 'flowable')
1339
+ const studio = createStudioController({ model })
1340
+
1341
+ const flowableXml = studio.exportXml('flowable')
1342
+ const activitiXml = studio.exportXml('activiti')
1343
+ ```
1344
+
1345
+ `engineHint` 用于 XML 中没有明确扩展命名空间时的回退。导出前可以运行:
1346
+
1347
+ ```js
1348
+ const issues = studio.model.nodes.length ? [] : ['流程没有节点']
1349
+ ```
1350
+
1351
+ 独立 `BpmnDesigner` 提供纯校验 `validate()`。完整 Studio 则通过 `actions.validate()` 或 Shell/框架公开的 `validate()` 执行 Nova 校验、更新内部状态摘要、发送校验事件并返回 issues;宿主仍可在发布前追加自己的业务规则。
1352
+
1353
+ `allowedNodeTypes` 与 `allowedEdgeTypes` 是完整模型约束:初始模型和后续导入包含白名单外节点或连线时会拒绝,Palette、连接、快捷新增、模板及节点类型转换也使用同一约束。只有确实支持全部 Nova 图元的宿主才省略它们。
1354
+
1355
+ ## 5. Studio Designer 子路径
1356
+
1357
+ ```js
1358
+ import { createEmptyProcess } from '@bpmn-nova/studio'
1359
+ import { BpmnDesigner } from '@bpmn-nova/studio/designer'
1360
+ import '@bpmn-nova/studio/styles.css'
1361
+
1362
+ const designer = new BpmnDesigner({
1363
+ container: document.querySelector('#designer'),
1364
+ model: createEmptyProcess('flowable'),
1365
+ onChange(model, reason) {
1366
+ console.log(reason, model)
1367
+ },
1368
+ onSelectionChange(selection, element) {
1369
+ console.log(selection, element)
1370
+ },
1371
+ })
1372
+
1373
+ designer.addNode('userTask', 320, 240, { name: '主管审批' })
1374
+ designer.beautify({ direction: 'horizontal', density: 'balanced' })
1375
+ designer.renderer.fitView()
1376
+
1377
+ designer.destroy()
1378
+ ```
1379
+
1380
+ Designer 子路径适合已有自定义工具栏和属性面板的宿主。需要完整 Studio Controller、框选工具栏、右键菜单和作用域导航时使用 Studio 根入口。
1381
+
1382
+ ## 6. Studio Viewer 子路径与审批轨迹
1383
+
1384
+ ```js
1385
+ import { BpmnViewer } from '@bpmn-nova/studio/viewer'
1386
+ import '@bpmn-nova/studio/styles.css'
1387
+
1388
+ const viewer = new BpmnViewer({
1389
+ container: document.querySelector('#viewer'),
1390
+ model,
1391
+ runtime,
1392
+ projection: 'approval',
1393
+ timeline: {
1394
+ title: ({ model }) => model.name,
1395
+ description: '按实际处理顺序展示',
1396
+ },
1397
+ runtimeDetails: {
1398
+ autoOpen: true,
1399
+ desktop: { placement: 'popover', width: 360 },
1400
+ mobile: { placement: 'bottom', width: '100%', dragToDismiss: true },
1401
+ },
1402
+ onTraceClick(event) {
1403
+ console.log(event.elementId, event.visitId, event.actions)
1404
+ },
1405
+ })
1406
+
1407
+ viewer.fitView()
1408
+ viewer.setRuntime(nextRuntime)
1409
+ viewer.destroy()
1410
+ ```
1411
+
1412
+ 投影模式:
1413
+
1414
+ | 值 | 行为 |
1415
+ | --- | --- |
1416
+ | `standard` | 完整 BPMN 流程图 |
1417
+ | `approval` | 桌面端实际有效路径图 |
1418
+ | `compact` | 适合移动端的纵向审批时间线 |
1419
+ | `auto` | 按 Viewer 容器宽度在 `approval` 与 `compact` 间切换 |
1420
+
1421
+ 只有显式设置 `responsive: true` 且没有固定投影,或直接设置 `projection: 'auto'` 时,才启用响应式切换。
1422
+
1423
+ ## 7. React 18+
1424
+
1425
+ 本节面向已有 React 18+ 与 React DOM 18+ 工程,只安装 BPMN Nova 适配包:
1426
+
1427
+ ```bash
1428
+ npm install @bpmn-nova/react@preview
1429
+ ```
1430
+
1431
+ ```jsx
1432
+ import { useCallback, useRef, useState } from 'react'
1433
+ import { BpmnStudio } from '@bpmn-nova/react'
1434
+ import '@bpmn-nova/react/styles.css'
1435
+
1436
+ export function WorkflowEditor({ initialXml }) {
1437
+ const ref = useRef(null)
1438
+ const [mode, setMode] = useState('design')
1439
+ const subtitleResolver = useCallback(({ node, defaultSubtitle }) => defaultSubtitle, [])
1440
+
1441
+ return (
1442
+ <div style={{ height: 720 }}>
1443
+ <BpmnStudio
1444
+ ref={ref}
1445
+ xml={initialXml}
1446
+ engine="flowable"
1447
+ mode={mode}
1448
+ allowedModes={['design', 'viewer']}
1449
+ nodeSubtitleResolver={subtitleResolver}
1450
+ propertiesProfile="business"
1451
+ theme="auto"
1452
+ onChange={(model, reason, nextXml) => saveDraft(nextXml)}
1453
+ onSelectionChange={(selection, element) => console.log(element)}
1454
+ onModeChange={(event) => setMode(event.mode)}
1455
+ />
1456
+ </div>
1457
+ )
1458
+ }
1459
+ ```
1460
+
1461
+ 外部创建 Controller 时可以传入 `studio`;组件不会销毁外部 Controller:
1462
+
1463
+ ```jsx
1464
+ const { studio, state, commands } = useBpmnStudio({ model })
1465
+ return <BpmnStudio studio={studio} />
1466
+ ```
1467
+
1468
+ Ref 可访问 `studio`、`shell`、`actions`、实际 `mode`、`setMode()`、`validate()`、`refreshPresentation()`、`exportXml()`、`fitView()` 和 `setTheme()`。`onValidation` 在 Nova 内部状态展示完成后收到校验结果;Header Render Context 同样提供响应式实际 `mode`。
1469
+
1470
+ ## 8. Vue 3.3+
1471
+
1472
+ 本节面向已有 Vue 3.3+ 工程,只安装 BPMN Nova 适配包:
1473
+
1474
+ ```bash
1475
+ npm install @bpmn-nova/vue@preview
1476
+ ```
1477
+
1478
+ ```vue
1479
+ <script setup>
1480
+ import { ref } from 'vue'
1481
+ import { BpmnStudio } from '@bpmn-nova/vue'
1482
+ import '@bpmn-nova/vue/styles.css'
1483
+
1484
+ const props = defineProps({ initialXml: String })
1485
+ const studioRef = ref(null)
1486
+ const mode = ref('design')
1487
+ const onChange = (model, reason, nextXml) => saveDraft(nextXml)
1488
+ </script>
1489
+
1490
+ <template>
1491
+ <div style="height: 720px">
1492
+ <BpmnStudio
1493
+ ref="studioRef"
1494
+ :xml="props.initialXml"
1495
+ engine="flowable"
1496
+ v-model:mode="mode"
1497
+ :allowed-modes="['design', 'viewer']"
1498
+ properties-profile="business"
1499
+ theme="auto"
1500
+ @change="onChange"
1501
+ @mode-change="event => console.log(event.source)"
1502
+ @validation="event => console.log(event.valid, event.issues)"
1503
+ />
1504
+ </div>
1505
+ </template>
1506
+ ```
1507
+
1508
+ Vue 事件使用 kebab-case:`change`、`selection-change`、`scope-change`、`element-click`、`trace-click`、`update:mode`、`mode-change` 和 `validation`。Expose 提供 `getStudio()`、`getShell()`、`getActions()`、`getMode()`、`setMode()`、`validate()`、`refreshPresentation()`、`exportXml()`、`fitView()` 与 `setTheme()`;Header Slot Context 的 `mode` 为响应式实际状态。
1509
+
1510
+ 框架组件把 `xml`/`model` 作为外部替换输入,把 `change`/`onChange` 的 XML 作为草稿输出。完全相同的导出 XML 回写会被忽略并保留 Undo/Redo;不同 XML 表示宿主有意加载另一流程或服务器 revision,会替换模型并重置历史。React/Vue 包也重新导出了外部 Controller 和各类 Registry 的创建函数,高级接入无需直接安装 Studio。
1511
+
1512
+ ### 8.1 嵌入现有设计工作台
1513
+
1514
+ 默认 `BpmnStudio` 包含 Header、Palette、Canvas、Properties 和 Statusbar。宿主已经拥有业务属性面板时,显式隐藏 Nova 右侧并使用 Header Start 插槽:
1515
+
1516
+ ```vue
1517
+ <BpmnStudio
1518
+ ref="studioRef"
1519
+ :xml="xml"
1520
+ engine="activiti"
1521
+ mode="design"
1522
+ :allowed-modes="['design']"
1523
+ :allowed-node-types="supportedNodeTypes"
1524
+ :allowed-edge-types="['sequenceFlow']"
1525
+ :regions="{ right: 'hidden' }"
1526
+ theme="auto"
1527
+ @change="handleChange"
1528
+ @selection-change="handleSelectionChange"
1529
+ @validation="handleValidation"
1530
+ >
1531
+ <template #header-start="{ state, actions }">
1532
+ <!-- 返回、业务图标、流程名称、类型 -->
1533
+ </template>
1534
+ <template #header-actions="{ actions, mode }">
1535
+ <button type="button" :disabled="mode !== 'design'" @click="actions.validate()">校验</button>
1536
+ <button type="button" :disabled="mode !== 'design'" @click="saveDraft(actions.exportXml())">保存</button>
1537
+ <button type="button" :disabled="mode !== 'design'" @click="publishProcess(actions)">发布</button>
1538
+ </template>
1539
+ </BpmnStudio>
1540
+ ```
1541
+
1542
+ Vue 的 `#header-start` 只替换 Brand,`#header-actions` 只替换默认“校验 / 导入 / 导出”动作组,`#header` 替换完整 Header;React 使用等价的 `headerStart` / `headerActions` / `header` Render Prop。顶部最佳视图已去重,底部入口与 `fitView()` 保留。`actions.validate()` 先展示 Nova 内部结果,再发送 `validation` / `onValidation`,最后返回 issues。`regions` 属性变化会原地更新区域,不重建 Canvas。
1543
+
1544
+ ```js
1545
+ async function publishProcess(actions) {
1546
+ const issues = actions.validate()
1547
+ if (issues.some((issue) => issue.level === 'error')) return
1548
+ await publishXml(actions.exportXml())
1549
+ }
1550
+ ```
1551
+
1552
+ 把宿主属性面板放在 Nova 外部,以 `selection-change` / `onSelectionChange` 作为状态来源,并用稳定 BPMN Element ID 关联业务配置。该事件覆盖节点、连线、多选、清空和键盘选择;`element-click` 不能替代选择状态。Nova 不负责保存、发布、权限或服务端事务。
1553
+
1554
+ `theme="auto"` 需要显式配置才会跟随系统主题;未传 Theme 时仍默认为 `light`。完全自定义布局时,React 与 Vue 均可组合 `BpmnCanvas`、`BpmnPalettePanel` 和 `BpmnPropertiesPanel`,并传入同一个外部 Controller。
1555
+
1556
+ ## 9. Group 与 Pool/Lane
1557
+
1558
+ Group 是纯视觉 Artifact,不保存成员关系:
1559
+
1560
+ ```js
1561
+ studio.commands.groupSelection()
1562
+ studio.commands.ungroup('Group_Review')
1563
+ ```
1564
+
1565
+ Lane 通过 `containerId` 表示画布中的 Pool containment,通过标准 `flowNodeRefs` 表示 BPMN 成员:
1566
+
1567
+ ```js
1568
+ studio.commands.attachLane('Lane_Finance', 'Participant_Main', 1)
1569
+ studio.commands.detachLane('Lane_Finance', { x: 320, y: 640 })
1570
+ studio.commands.updateNode('Participant_Main', {
1571
+ properties: { swimlaneLabelPlacement: 'top' },
1572
+ })
1573
+ ```
1574
+
1575
+ `containerId` 和 `swimlaneLabelPlacement` 是编辑器展示状态,不写入 Nova 私有 XML 属性;重新导入标准 XML 时会按 `processRef` 与 DI Bounds 恢复 Lane owner,标题方向回退为 `side`。
1576
+
1577
+ ## 10. 常见问题
1578
+
1579
+ ### 页面空白
1580
+
1581
+ 检查容器及全部父级是否有高度,并确认导入了当前视觉包的 `styles.css`。
1582
+
1583
+ ### 图标或属性面板没有样式
1584
+
1585
+ 不要只导入 JavaScript。Studio/React/Vue 的样式入口已经按 Theme → Icons → Renderer → Palette → Properties → Studio 的顺序打包。
1586
+
1587
+ ### SSR 报错
1588
+
1589
+ Designer、Viewer 和 Studio 依赖 DOM,应在客户端挂载。CSS 可以由框架构建器静态导入,组件实例应在浏览器生命周期内创建。
1590
+
1591
+ ### XML 导入失败
1592
+
1593
+ 先确认输入为完整 BPMN Definitions 文档,并为不明确的 XML 传入 `flowable` 或 `activiti` engine hint。
1594
+
1595
+ ### 主题影响了整个页面
1596
+
1597
+ BPMN Nova 主题应挂在组件根容器。自定义 Renderer 不要修改 `document.documentElement`,应读取传入的 `themeState` 或 CSS Variables。
1598
+
1599
+ ## 下一步
1600
+
1601
+ - [组件参数与事件](COMPONENTS.md)
1602
+ - [API](API.md)
1603
+ - [Properties Panel](PROPERTIES-PANEL.md)
1604
+ - [自定义指南](CUSTOMIZATION.md)
1605
+ - [NPM 包与发布](NPM-PACKAGES.md)
1606
+ - [AI 接入指南](AI-INTEGRATION.md)
1607
+
1608
+
1609
+ <!-- SOURCE: docs/COMPONENTS.md -->
1610
+
1611
+ # BPMN Nova 组件参数
1612
+
1613
+ 本手册记录 `0.3.3-preview` 的公开组件参数、回调和实例方法。所有视觉组件都需要具有实际尺寸的容器,并显式导入所属包的 `styles.css`。
1614
+
1615
+ ## 按项目环境阅读
1616
+
1617
+ | 项目环境 | 建议先看 | 对应入口 |
1618
+ | --- | --- | --- |
1619
+ | Vanilla JavaScript / TypeScript 完整工作台 | `createStudioController`、`createStudioShell` | `@bpmn-nova/studio` |
1620
+ | 框架无关的独立画布 | `BpmnDesigner` 或 `BpmnViewer` | `@bpmn-nova/studio/designer`、`@bpmn-nova/studio/viewer` |
1621
+ | React 18+ | “React 组件”章节 | `@bpmn-nova/react` |
1622
+ | Vue 3.3+ | “Vue 组件”章节 | `@bpmn-nova/vue` |
1623
+ | 自建 UI 与属性面板 | `BpmnCanvas`、`PalettePanel`、`PropertiesPanel` | Studio 或对应框架适配包 |
1624
+
1625
+ JavaScript 与 TypeScript 的运行接口一致,TypeScript 类型随包提供。React/Vue 项目应优先使用对应适配包,让组件生命周期、事件和 Ref/Expose 由框架管理,而不是在组件中手动创建 Vanilla 实例。
1626
+
1627
+ ## 公共类型
1628
+
1629
+ ```ts
1630
+ type EngineId = 'flowable' | 'activiti'
1631
+ type ViewerProjection = 'auto' | 'standard' | 'approval' | 'compact'
1632
+ type NovaThemeInput = 'light' | 'dark' | 'auto' | NovaThemeOptions
1633
+ type PropertiesProfile = 'business' | 'developer'
1634
+ ```
1635
+
1636
+ 完整 `ProcessModel`、`BpmnNode`、`BpmnEdge` 与 Runtime Snapshot 类型见对应包的 `.d.ts` 和 [API](API.md)。
1637
+
1638
+ ## `createStudioController(options)`
1639
+
1640
+ Controller 不创建 DOM,负责模型、命令、历史、选择与子流程作用域。
1641
+
1642
+ | 参数 | 类型 | 必填/默认 | 说明 |
1643
+ | --- | --- | --- | --- |
1644
+ | `model` | `ProcessModel` | 必填 | 初始流程模型 |
1645
+ | `historyLimit` | `number` | `80` | Undo/Redo 快照上限 |
1646
+ | `propertiesProfile` | `'business' \| 'developer'` | `'business'` | 属性面板可见级别 |
1647
+ | `extensions` | `StudioExtension[]` | `[]` | Controller 扩展,`setup()` 可返回清理函数 |
1648
+ | `allowedNodeTypes` | `Iterable<NodeType> \| null` | `null` | 完整模型节点白名单;同时约束导入与所有创建/转换命令 |
1649
+ | `allowedEdgeTypes` | `Iterable<EdgeType> \| null` | `null` | 完整模型连线白名单;同时约束导入、连接与模板命令 |
1650
+
1651
+ 常用方法:
1652
+
1653
+ | 方法 | 返回值 | 说明 |
1654
+ | --- | --- | --- |
1655
+ | `subscribe(listener)` | `() => void` | 订阅模型、选择、历史和作用域事件 |
1656
+ | `getState()` | `StudioState` | 获取不可直接编辑的状态快照 |
1657
+ | `setModel(model, options?)` | `void` | 替换模型,可用 `resetHistory: false` 保留历史 |
1658
+ | `exportXml(engine?)` | `string` | 导出 BPMN XML |
1659
+ | `importXml(xml, engine?)` | `ProcessModel` | 导入并替换当前模型 |
1660
+ | `validate()` | `StudioValidationIssue[]` | 返回结构化错误和警告,不触发宿主 UI |
1661
+ | `undo()` / `redo()` | `boolean` | 执行历史操作 |
1662
+ | `destroy()` | `void` | 清理扩展和订阅 |
1663
+
1664
+ 主要命令位于 `studio.commands`:创建节点/模板、连接、修改、删除、布局、分组、Lane containment、作用域导航和历史操作。
1665
+
1666
+ ## `createStudioShell(options)`
1667
+
1668
+ | 参数 | 类型 | 必填/默认 | 说明 |
1669
+ | --- | --- | --- | --- |
1670
+ | `container` | `HTMLElement` | 必填 | Studio 根容器 |
1671
+ | `studio` | `BpmnStudioController` | 必填 | 外部 Controller |
1672
+ | `mode` | `'design' \| 'viewer' \| 'instance'` | `'design'` | 当前展示模式 |
1673
+ | `allowedModes` | `('design' \| 'viewer' \| 'instance')[]` | 全部模式 | 默认工作台显示并允许切换的模式 |
1674
+ | `runtime` | `ProcessInstanceSnapshot \| null` | `null` | 实例模式数据;没有数据时保持空运行事实 |
1675
+ | `projection` | `ViewerProjection` | 实例模式默认 `approval` | 审批轨迹投影 |
1676
+ | `responsive` | `boolean` | `false` | 是否允许审批投影响应容器宽度 |
1677
+ | `projectionOptions` | `{ value, label }[]` | 默认两项 | Shell 轨迹切换选项 |
1678
+ | `leftWidth` | `number` | `244` | 左侧栏宽度,像素 |
1679
+ | `rightWidth` | `number` | `360` | 右侧栏宽度,像素 |
1680
+ | `theme` | `NovaThemeInput` | `light` | 组件实例主题 |
1681
+ | `runtimeAppearance` | `RuntimeAppearanceOptions` | 默认语义映射 | 状态、动作、转移的 Tone 映射 |
1682
+ | `svgExport` | `SvgExportOptions & Renderer Adapters` | `{}` | SVG 文件名、节点/时间线 SVG Renderer 等导出配置 |
1683
+ | `iconRegistry` | `IconRegistry` | 默认 Registry | 图标覆盖入口 |
1684
+ | `paletteRegistry` | `PaletteRegistry` | 默认 Registry | Palette Provider 入口 |
1685
+ | `propertiesRegistry` | `PropertiesRegistry` | 默认 Registry | Properties Provider 入口 |
1686
+ | `templateRegistry` | `TemplateRegistry` | 默认 Registry | 企业模板入口 |
1687
+ | `contextMenuRegistry` | `ContextMenuRegistry` | 默认 Registry | 右键动作入口 |
1688
+ | `rendererOptions` | `DiagramRendererOptions` | `{}` | 节点与 Runtime Renderer 配置 |
1689
+ | `nodeSubtitleResolver` | `NodeSubtitleResolver` | 无 | 覆盖 Design/Viewer 标准节点副标题;不影响 Instance |
1690
+ | `slots` | `StudioShellSlots` | `{}` | 局部 UI 替换 |
1691
+ | `regions` | `StudioShellRegions` | 全部 `default` | 原地隐藏 Header、Palette、Properties 或 Statusbar;隐藏后不保留轨道 |
1692
+ | `layout` | `Function` | 默认三栏布局 | 完整布局替换 |
1693
+ | `onThemeChange` | `(state) => void` | 无 | 主题模式解析变化回调 |
1694
+
1695
+ 实例方法包括 `getMode()`、`getAllowedModes()`、`setMode()`、`setAllowedModes()`、`subscribeMode()`、`validate()`、`subscribeValidation()`、`getRegions()`、`setRegions()`、`setRuntime()`、`setProjection()`、`refreshPresentation()`、`setTheme()`、`setRuntimeAppearance()`、`fitView()`、`zoomBy()`、`exportSvg()`、`openSvgExportPreview()` 和 `destroy()`。`setMode()` 仅在真实成功切换时返回 `true`;Mode Event 的 `source` 为 `toolbar | api | allowed-modes`。
1696
+
1697
+ `shell.actions` 是默认 Header 与宿主自定义 Header 共用的稳定 Interface,提供 `undo()`、`redo()`、`beautify()`、`rerouteEdges()`、`fitView()`、`validate()`、`importXml()`、`exportXml()`、`exportSvg()` 和 `openSvgExportPreview()`。它不包含保存、发布、权限或文件上传等宿主业务动作。
1698
+
1699
+ `slots.headerStart` 只替换 Nova Brand;`slots.headerActions` 只替换默认“校验 / 导入 / 导出”动作组;`slots.header` 完整替换 Header,且优先级更高。Slot Context 提供 `studio`、`shell`、`canvas`、`actions`、`getState()`、`subscribe()`、`getMode()`、`getAllowedModes()`、`subscribeMode()` 和 `subscribeValidation()`。默认 Header 的最佳视图只保留在底部缩放区。`regions.header = 'hidden'` 的优先级最高。`layout()` 与 `regions` 不能同时使用。
1700
+
1701
+ `actions.validate()` 先更新 Nova 默认状态栏,再发送一次来源为 `toolbar` 的 Validation Event 并返回 issues;实例 `validate()` 的来源为 `api`。事件 `valid` 只由 error 决定,warning 不默认阻止发布。
1702
+
1703
+ ## `BpmnDesigner`
1704
+
1705
+ ```js
1706
+ new BpmnDesigner(options)
1707
+ ```
1708
+
1709
+ | 参数 | 类型 | 必填/默认 | 说明 |
1710
+ | --- | --- | --- | --- |
1711
+ | `container` | `HTMLElement` | 必填 | 设计器容器 |
1712
+ | `model` | `ProcessModel` | 必填 | 初始模型 |
1713
+ | `historyLimit` | `number` | `80` | 历史上限 |
1714
+ | `onChange` | `(model, reason) => void` | 无 | 模型提交回调 |
1715
+ | `onSelectionChange` | `(selection, element) => void` | 无 | 选择变化回调 |
1716
+ | `onStateChange` | `(state) => void` | 无 | 历史、连接等状态变化 |
1717
+ | `onViewportChange` | `(viewport) => void` | 无 | 缩放/平移变化 |
1718
+ | `theme` | `NovaThemeInput` | `light` | 实例主题 |
1719
+ | `svgExport` | `SvgExportOptions` | `{}` | SVG 导出默认值及节点 SVG Renderer |
1720
+ | `onThemeChange` | `(state) => void` | 无 | 主题变化 |
1721
+
1722
+ 常用方法:`addNode()`、`quickAdd()`、`connect()`、`updateNode()`、`updateEdge()`、`changeNodeType()`、`beautify()`、`rerouteEdges()`、`validate()`、`importXml()`、`exportXml()`、`exportSvg()`、`openSvgExportPreview()`、`undo()`、`redo()`、`destroy()`。
1723
+
1724
+ ## `BpmnViewer`
1725
+
1726
+ | 参数 | 类型 | 必填/默认 | 说明 |
1727
+ | --- | --- | --- | --- |
1728
+ | `container` | `HTMLElement` | 必填 | Viewer 容器 |
1729
+ | `model` | `ProcessModel` | 必填 | BPMN 模型 |
1730
+ | `runtime` | `ProcessInstanceSnapshot \| null` | `null` | 存在时进入实例展示 |
1731
+ | `projection` | `ViewerProjection` | Runtime 时 `approval`,否则 `standard` | 展示投影 |
1732
+ | `responsive` | `boolean` | `false` | 未固定投影时启用容器响应式切换 |
1733
+ | `runtimeTraceOptions` | `RuntimeTraceProjectionOptions` | `{}` | 预测路径与首尾里程碑配置 |
1734
+ | `timeline` | `ViewerTimelineOptions` | 默认标题/描述 | 移动时间线标题与说明 |
1735
+ | `runtimeDetails` | `RuntimeDetailsOptions` | 默认桌面 Popover/移动 Bottom | 详情布局 |
1736
+ | `runtimeAssetResolver` | `RuntimeAssetResolver` | `null` | 图片与附件临时 URL 解析 |
1737
+ | `svgExport` | `SvgExportOptions` | `{}` | SVG 默认值、节点与时间线 SVG Renderer |
1738
+ | `runtimeTimelineRenderer` | `RuntimeTimelineRenderer` | 默认 Renderer | 替换移动时间线 |
1739
+ | `runtimeDetailsRenderer` | `RuntimeDetailsRenderer \| null` | 默认 Renderer | 替换或关闭节点详情 |
1740
+ | `runtimeTransitionDetailsRenderer` | `RuntimeTransitionDetailsRenderer \| null` | 默认 Renderer | 替换或关闭异常转移详情 |
1741
+ | `nodeRenderers` | `Record<string, NodeRenderer>` | `{}` | 按节点类型替换内部内容 |
1742
+ | `nodeSubtitleResolver` | `NodeSubtitleResolver` | 无 | 定义态标准节点副标题视觉投影 |
1743
+ | `onTraceClick` | `(event) => void` | 无 | 统一节点、线路、Visit、Transition 点击 |
1744
+ | `onElementClick` | `(payload) => void` | 无 | 基础元素点击 |
1745
+ | `onProjectionChange` | `({ requested, active }) => void` | 无 | 实际投影变化 |
1746
+ | `theme` | `NovaThemeInput` | `light` | 实例主题 |
1747
+ | `runtimeAppearance` | `RuntimeAppearanceOptions` | 默认映射 | Runtime 语义外观 |
1748
+
1749
+ 实例方法:`setModel()`、`setRuntime()`、`setProjection()`、`setDisplayOptions()`、`refreshPresentation()`、`setTheme()`、`setRuntimeAppearance()`、`fitView()`、`openRuntimeDetails()`、`openRuntimeTransitionDetails()`、`closeRuntimeDetails()` 和 `destroy()`。Compact Runtime Timeline 中 `refreshPresentation()` 安全 No-op。
1750
+
1751
+ ## `BpmnCanvas`
1752
+
1753
+ | 参数 | 类型 | 必填/默认 | 说明 |
1754
+ | --- | --- | --- | --- |
1755
+ | `container` | `HTMLElement` | 必填 | 画布容器 |
1756
+ | `studio` | `BpmnStudioController` | 必填 | Controller |
1757
+ | `rendererOptions` | `DiagramRendererOptions` | `{}` | Renderer 配置 |
1758
+ | `nodeSubtitleResolver` | `NodeSubtitleResolver` | 无 | 标准节点副标题 Resolver |
1759
+ | `interactions` | `InteractionController \| null` | `null` | Palette 拖放协议 |
1760
+ | `selectionToolbar` | `HTMLElement \| SelectionToolbarSlot \| null` | 默认工具栏 | 框选工具栏替换 |
1761
+ | `contextMenu` | `HTMLElement \| ContextMenuSlot \| null` | 默认菜单 | 右键菜单替换 |
1762
+ | `contextMenuRegistry` | `ContextMenuRegistry` | 默认 Registry | 右键动作来源 |
1763
+ | `pointerMode` | `'select' \| 'marquee' \| 'pan'` | `'select'` | 指针模式 |
1764
+ | `theme` | `NovaThemeInput` | `light` | 实例主题 |
1765
+
1766
+ 常用方法:`clientToWorld()`、`fitView()`、`fitSelection()`、`setPointerMode()`、`zoomBy()`、`actualSize()`、`refreshPresentation()` 和 `destroy()`。
1767
+
1768
+ ## `PalettePanel`
1769
+
1770
+ | 参数 | 必填 | 说明 |
1771
+ | --- | --- | --- |
1772
+ | `container` | 是 | Palette 容器 |
1773
+ | `registry` | 是 | `PaletteRegistry` |
1774
+ | `studio` | 是 | Studio Controller |
1775
+ | `interactions` | 是 | 拖放 InteractionController |
1776
+ | `canvas` | 否 | 用于定位放置点 |
1777
+ | `iconRegistry` | 否 | 自定义图标 |
1778
+ | `renderItem` / `renderSection` | 否 | 局部 DOM Renderer |
1779
+ | `theme` / `onThemeChange` | 否 | 实例主题 |
1780
+
1781
+ ## `PropertiesPanel`
1782
+
1783
+ | 参数 | 必填/默认 | 说明 |
1784
+ | --- | --- | --- |
1785
+ | `container` | 必填 | 属性面板容器 |
1786
+ | `registry` | 必填 | `PropertiesRegistry` |
1787
+ | `studio` | Registry 中的 Studio | Controller |
1788
+ | `canvas` | `null` | 定位节点等画布能力 |
1789
+ | `iconRegistry` | 默认 Registry | UI 与节点图标 |
1790
+ | `emptyText` | `'请选择元素查看属性'` | 空选择提示 |
1791
+ | `theme` / `onThemeChange` | 可选 | 实例主题 |
1792
+
1793
+ `render(selection?, element?)` 返回 `{ groups, context }`,便于调试 Provider 解析结果。
1794
+
1795
+ ## React 组件
1796
+
1797
+ 所有 React 视觉组件支持 `className`、`style`、`theme` 和 `onThemeChange`。
1798
+
1799
+ ### `BpmnStudio`
1800
+
1801
+ 在 `createStudioShell()` 参数基础上增加:
1802
+
1803
+ | Prop | 说明 |
1804
+ | --- | --- |
1805
+ | `studio` | 外部 Controller;不传时根据 `model/xml/engine` 创建并托管 |
1806
+ | `model` / `xml` / `engine` | 初始及后续受控模型输入 |
1807
+ | `propertiesProfile` | 创建内部 Controller 时的属性 Profile |
1808
+ | `allowedNodeTypes` | 创建内部 Controller 时使用的完整模型节点白名单 |
1809
+ | `allowedEdgeTypes` | 创建内部 Controller 时使用的完整模型连线白名单 |
1810
+ | `allowedModes` | Shell 显示并允许切换的工作台模式 |
1811
+ | `mode` / `onModeChange` | 受控输入与实际 Mode 事件;Header Render Context 同步提供 `mode` |
1812
+ | `nodeSubtitleResolver` | Design/Viewer 标准节点副标题 Resolver |
1813
+ | `contextMenuRegistry` | 自定义右键动作 Registry |
1814
+ | `regions` | 默认 Shell 区域显隐;属性变化原地调用 `setRegions()` |
1815
+ | `headerStart` / `headerActions` / `header` | React Node 或 Render Prop;使用 Portal 保留宿主 Context |
1816
+ | `onValidation` | Nova 内部校验展示完成后的只读结果事件 |
1817
+ | `onChange(model, reason, xml)` | 模型变化 |
1818
+ | `onSelectionChange(selection, element)` | 选择变化 |
1819
+ | `onScopeChange(activeScopeId, scopePath, state)` | 子流程作用域变化 |
1820
+
1821
+ Ref:`studio`、`shell`、`actions`、`mode`、`getMode()`、`setMode()`、`setAllowedModes()`、`validate()`、`refreshPresentation()`、`exportXml()`、`fitView()`、`setTheme()`。
1822
+
1823
+ `xml`/`model` 是外部替换输入。回写与最近导出完全相同的 XML 不会重复导入或清空 Undo/Redo;不同 XML 会替换模型和历史。React/Vue 包均重新导出创建外部 Controller 与 Palette、Properties、Context Menu、Icon、Template Registry 所需的公开函数。
1824
+
1825
+ ### `BpmnDesigner`
1826
+
1827
+ Props:`model`、`xml`、`engine`、`theme`、`onChange`、`onSelectionChange`。Ref:`instance`、`model`、`exportXml()`、`fitView()`、`beautify()`、`rerouteEdges()`、`validate()`、`setTheme()`。
1828
+
1829
+ ### `BpmnViewer`
1830
+
1831
+ 接受除 `container/model` 外的 `ViewerOptions`,并通过 `model` 或 `xml` 输入流程。Ref:`instance`、`fitView()`、`refreshPresentation()`、`setProjection()`、`setRuntime()`、`setDisplayOptions()`、`setTheme()`。
1832
+
1833
+ ### `BpmnCanvas` / `BpmnPalettePanel` / `BpmnPropertiesPanel`
1834
+
1835
+ 用于自行组合布局。三者都要求外部 Studio/Designer 实例;Properties 还支持 `providers`、`dataProviders` 和框架组件映射 `components`。
1836
+
1837
+ ## Vue 组件
1838
+
1839
+ Vue 组件与 React 使用同一底层接口,主要差异为:
1840
+
1841
+ - Studio 提供原生 `#header-start`、`#header-actions` 和 `#header` Slot,通过 Teleport 保留宿主 provide/inject、响应式状态和生命周期。
1842
+ - `slotsConfig` 继续保留,作为高级 Core DOM Slot 入口;完整 Header 高于局部 Header Slots,原生局部 Slot 高于对应 Core Slot。
1843
+ - 事件为 `change`、`selection-change`、`scope-change`、`element-click`、`trace-click`、`update:mode`、`mode-change`、`validation`;支持 `v-model:mode`。
1844
+ - `BpmnDesigner` 发出 `change`、`selection-change`。
1845
+ - Header Slot Context 的 `mode` 是响应式实际状态。Ref Expose 使用 `getStudio()`、`getShell()`、`getActions()`、`getMode()`、`setMode()`、`setAllowedModes()`、`validate()`、`refreshPresentation()` 或 `getInstance()`,不直接暴露可替换字段。
1846
+
1847
+ Vue 提供 `BpmnStudio`、`BpmnDesigner`、`BpmnViewer`、`BpmnCanvas`、`BpmnPalettePanel`、`BpmnPropertiesPanel` 和 `useBpmnStudio()`。独立 `BpmnPalettePanel` 复用外部 Studio;传入 Registry 时不会隐式注册 Providers,未传 Registry 时才用 Providers 创建默认 Registry。
1848
+
1849
+ 编辑器宿主属性面板应使用 `selection-change` / `onSelectionChange`,因为它覆盖节点、连线、多选、清空和键盘选择。`element-click` / `onElementClick` 只是点击观察接口,不能作为选择状态替代品。
1850
+
1851
+ ## 自定义框架字段
1852
+
1853
+ React/Vue Properties Component 都收到:
1854
+
1855
+ ```ts
1856
+ interface PropertyComponentProps {
1857
+ entry: PropertyEntry
1858
+ context: PropertiesContext
1859
+ value: unknown
1860
+ onChange(nextValue: unknown): void
1861
+ }
1862
+ ```
1863
+
1864
+ Runtime Details、Timeline 和 Transition Details 组件通过对应 `createReact*Component()` / `createVue*Component()` 适配器注册。完整上下文见类型声明和[自定义指南](CUSTOMIZATION.md)。
1865
+
1866
+
1867
+ <!-- SOURCE: docs/API.md -->
1868
+
1869
+ # BPMN Nova v0.3.2 Preview API
1870
+
1871
+ ## Designer
1872
+
1873
+ ```js
1874
+ import { BpmnDesigner, createSampleProcess } from '@bpmn-nova/studio'
1875
+ import '@bpmn-nova/studio/styles.css'
1876
+
1877
+ const designer = new BpmnDesigner({
1878
+ container: document.querySelector('#designer'),
1879
+ model: createSampleProcess('flowable'),
1880
+ onChange(model, reason) {
1881
+ console.log(reason, model)
1882
+ },
1883
+ })
1884
+ ```
1885
+
1886
+ 常用建模:
1887
+
1888
+ ```js
1889
+ designer.addNode('userTask', 320, 240)
1890
+ designer.quickAdd('Gateway_1', 'serviceTask')
1891
+ designer.startConnect('Task_A')
1892
+ designer.connect('Task_A', 'Task_B', { name: '同意' })
1893
+ designer.changeNodeType('Task_A', 'scriptTask')
1894
+ designer.updateNode('Task_A', { properties: { documentation: '说明' } })
1895
+ designer.updateEdge('Flow_1', { condition: '${approved}' })
1896
+ designer.updateProcess({ name: '采购审批' })
1897
+ designer.removeSelection()
1898
+ ```
1899
+
1900
+ 事件:
1901
+
1902
+ ```js
1903
+ const offSelection = designer.on('selection', ({ selection, element }) => {})
1904
+ const offChange = designer.on('change', ({ model, reason, state }) => {})
1905
+ ```
1906
+
1907
+ 布局:
1908
+
1909
+ ```js
1910
+ designer.beautify({
1911
+ direction: 'horizontal',
1912
+ density: 'balanced', // compact | balanced | spacious
1913
+ edgeStyle: 'rounded',
1914
+ })
1915
+
1916
+ designer.rerouteEdges({ edgeStyle: 'smooth' })
1917
+ designer.resetEdgeRoute('Flow_1')
1918
+ designer.renderer.fitReadable(72)
1919
+ designer.renderer.actualSize(72)
1920
+ ```
1921
+
1922
+ BPMN:
1923
+
1924
+ ```js
1925
+ const issues = designer.validate()
1926
+ const flowableXml = designer.exportXml('flowable')
1927
+ const activitiXml = designer.exportXml('activiti')
1928
+ designer.importXml(xml, 'flowable')
1929
+ ```
1930
+
1931
+ ## Studio 子流程作用域
1932
+
1933
+ 内嵌子流程使用扁平 `nodes / edges` 存储,`scopeId` 指向所属流程或子流程;旧模型缺少 `scopeId` 时默认属于根流程。
1934
+
1935
+ ```js
1936
+ const studio = createStudioController({ model })
1937
+
1938
+ studio.enterScope('SubProcess_Review')
1939
+ studio.getState().activeScopeId
1940
+ studio.getState().scopePath
1941
+ studio.getActiveGraph()
1942
+ studio.leaveScope()
1943
+ studio.navigateToScope(model.id)
1944
+
1945
+ const off = studio.subscribe((event) => {
1946
+ if (event.type === 'scopeChanged') {
1947
+ console.log(event.activeScopeId, event.scopePath)
1948
+ }
1949
+ })
1950
+ ```
1951
+
1952
+ `SubProcess`、`Event SubProcess`、`Transaction` 和 `Ad-hoc SubProcess` 支持作用域下钻;`Call Activity` 仍通过 `calledElement` 引用独立流程。连线不能跨作用域,删除子流程会级联删除其内部元素。
1953
+
1954
+ ## Studio 模型与模式约束
1955
+
1956
+ 生产宿主可以把支持的 BPMN 子集声明在 Controller 上:
1957
+
1958
+ ```js
1959
+ const allowedNodeTypes = [
1960
+ 'startEvent',
1961
+ 'endEvent',
1962
+ 'userTask',
1963
+ 'serviceTask',
1964
+ 'exclusiveGateway',
1965
+ ]
1966
+ const allowedEdgeTypes = ['sequenceFlow']
1967
+
1968
+ const studio = createStudioController({ model, allowedNodeTypes, allowedEdgeTypes })
1969
+ const shell = createStudioShell({
1970
+ container,
1971
+ studio,
1972
+ mode: 'design',
1973
+ allowedModes: ['design'],
1974
+ })
1975
+ ```
1976
+
1977
+ `allowedNodeTypes` 与 `allowedEdgeTypes` 是 Controller 的完整模型不变量:构造、`setModel()` 和 `importXml()` 会拒绝白名单外图元;创建、连接、快捷新增、模板和节点类型转换也查询同一约束。`studio.allowsNodeType(type)` 与 `studio.allowsEdgeType(type)` 供宿主 UI 和扩展复用。`allowedModes` 只决定 Shell 显示并允许切换的模式;不传时保留设计、展示和实例三个模式。Shell 默认 `runtime` 为 `null`,演示数据必须由示例应用显式传入。
1978
+
1979
+ React/Vue Adapter 的 `BpmnStudio` 接受同名 `allowedNodeTypes`、`allowedEdgeTypes` 和 `allowedModes`。框架 `xml`/`model` 是外部替换输入;回写与当前导出完全相同的 XML 不会重新导入或清空历史,不同 XML 会替换模型并重置历史。
1980
+
1981
+ ### Studio Mode Interface
1982
+
1983
+ ```ts
1984
+ type StudioMode = 'design' | 'viewer' | 'instance'
1985
+ type StudioModeChangeSource = 'toolbar' | 'api' | 'allowed-modes'
1986
+
1987
+ interface StudioModeChangeEvent {
1988
+ readonly mode: StudioMode
1989
+ readonly previousMode: StudioMode
1990
+ readonly source: StudioModeChangeSource
1991
+ readonly allowedModes: readonly StudioMode[]
1992
+ }
1993
+ ```
1994
+
1995
+ ```js
1996
+ shell.getMode()
1997
+ shell.getAllowedModes()
1998
+ shell.setMode('viewer')
1999
+ shell.setAllowedModes(['design', 'viewer'])
2000
+ const off = shell.subscribeMode((event) => console.log(event))
2001
+ ```
2002
+
2003
+ `setMode()` 只在完成一次真实切换时返回 `true`。同模式、不允许的模式或已销毁 Shell 返回 `false`,不重建也不发事件。`setAllowedModes()` 先完整校验非空、唯一且已知的 Mode;移除当前 Mode 时回退到新列表首项,只发送一次 `allowed-modes` 事件。事件在新视图挂载和 Shell UI 同步后发送,Mode 不进入 Controller State 或 History。
2004
+
2005
+ ### Studio Validation Interface
2006
+
2007
+ ```ts
2008
+ type StudioValidationSource = 'toolbar' | 'api'
2009
+
2010
+ interface StudioValidationEvent {
2011
+ readonly source: StudioValidationSource
2012
+ readonly valid: boolean
2013
+ readonly errorCount: number
2014
+ readonly warningCount: number
2015
+ readonly issues: readonly StudioValidationIssue[]
2016
+ }
2017
+ ```
2018
+
2019
+ `shell.actions.validate()` 用于默认或宿主 Header,来源为 `toolbar`;`shell.validate()` 用于程序调用,来源为 `api`。两者都会先运行纯 `studio.validate()`,同步更新 Nova 默认状态栏,再向 `subscribeValidation()` Listener 发送一次只读结果,最后返回兼容的 issues 数组。`valid` 等价于 `errorCount === 0`,warning 不默认阻止保存或发布。隐藏或替换默认 Footer 时事件与返回值不受影响,只是不再强制显示 Nova 状态摘要。
2020
+
2021
+ Vue 将事件透传为 `validation` 并 Expose `validate()`;React 使用 `onValidation` 和 Ref `validate()`。Listener 异常不会阻断其他 Listener、内部状态展示或返回值。
2022
+
2023
+ ### Node Subtitle Resolver 与刷新
2024
+
2025
+ ```ts
2026
+ interface NodeSubtitleResolverContext {
2027
+ readonly node: BpmnNode
2028
+ readonly definition: NodeDefinition
2029
+ readonly model: ProcessModel
2030
+ readonly mode: 'design' | 'viewer'
2031
+ readonly surface: 'canvas' | 'svg-export'
2032
+ readonly defaultSubtitle: string
2033
+ }
2034
+
2035
+ type NodeSubtitleResolver = (
2036
+ context: Readonly<NodeSubtitleResolverContext>,
2037
+ ) => string | null | undefined
2038
+ ```
2039
+
2040
+ `undefined` 保留默认值,`null` 删除副标题行,字符串替换视觉值;空字符串是显式字符串,不等同于隐藏。Resolver 必须同步,异常、Promise 或非法返回会抛出包含节点 ID 的错误。它只作用于 Design/Viewer 的标准任务和容器卡片;事件、网关、泳道等不会新增副标题槽,完整自定义 Renderer 优先,Instance 始终使用 Runtime Presentation。
2041
+
2042
+ `DiagramRenderer`、`BpmnCanvas`、`BpmnViewer` 与 `BpmnStudioShell` 均提供 `refreshPresentation()`。该方法先计算全部目标节点,再原位更新 DOM;失败时保持上一版视觉,不调用 Model Mutation、History、完整 Viewer Refresh 或 `fitView()`。SVG Export 使用同一默认规则和 Resolver,标准降级路径失败时直接 Reject。
2043
+
2044
+ ## Studio Shell 区域、Actions 与 Header Slot
2045
+
2046
+ 默认 Shell 是完整工作台。`regions` 只控制默认布局中的区域,`hidden` 不保留行列轨道:
2047
+
2048
+ ```ts
2049
+ type StudioShellRegionMode = 'default' | 'hidden'
2050
+
2051
+ interface StudioShellRegions {
2052
+ header?: StudioShellRegionMode
2053
+ left?: StudioShellRegionMode
2054
+ right?: StudioShellRegionMode
2055
+ footer?: StudioShellRegionMode
2056
+ }
2057
+ ```
2058
+
2059
+ ```js
2060
+ const shell = createStudioShell({
2061
+ container,
2062
+ studio,
2063
+ regions: { right: 'hidden' },
2064
+ slots: {
2065
+ headerStart({ container, studio, shell, canvas, actions, getState, subscribe, getMode, getAllowedModes, subscribeMode, subscribeValidation }) {
2066
+ // 只替换 Brand;默认模式区和编辑工具继续保留。
2067
+ },
2068
+ headerActions({ container, actions, getMode, subscribeMode }) {
2069
+ // 替换默认“校验 / 导入 / 导出”,挂载宿主“校验 / 保存 / 发布”。
2070
+ // 校验调用 actions.validate();保存与发布调用宿主服务。
2071
+ },
2072
+ },
2073
+ })
2074
+
2075
+ shell.getRegions()
2076
+ shell.setRegions({ right: 'default' })
2077
+ ```
2078
+
2079
+ `slots.headerActions` 只替换 Header 最右侧默认动作组,可与 `slots.headerStart` 同时使用;`slots.header` 完整替换 Header,并高于两个局部 Slot;`regions.header = 'hidden'` 的优先级最高。未提供 `headerActions` 时仍显示“校验 / 导入 / 导出”。顶部不再重复渲染最佳视图,底部缩放区与 `fitView()` Interface 保持不变。`layout()` 与 `regions` 互斥。
2080
+
2081
+ ```ts
2082
+ interface StudioShellActions {
2083
+ undo(): boolean
2084
+ redo(): boolean
2085
+ beautify(options?: LayoutOptions): void
2086
+ rerouteEdges(options?: LayoutOptions): void
2087
+ fitView(): void
2088
+ validate(): StudioValidationIssue[]
2089
+ importXml(xml: string, engine?: EngineId): ProcessModel
2090
+ exportXml(engine?: EngineId): string
2091
+ exportSvg(options?: SvgExportOptions): Promise<SvgExportArtifact>
2092
+ openSvgExportPreview(options?: SvgExportOptions): SvgExportPreviewController | null
2093
+ }
2094
+ ```
2095
+
2096
+ 默认 Header 与宿主 Header 都调用 `shell.actions`。保存草稿、发布、权限和宿主服务不进入 Actions;宿主通过 `headerActions` 闭包调用自己的业务方法,并使用 `actions.exportXml()` 获取草稿。
2097
+
2098
+ Vue `BpmnStudio` 提供 `#header-start` / `#header-actions` / `#header` 与 `getActions()`;React 提供 `headerStart` / `headerActions` / `header` Render Prop 与 Ref `actions`。Core Slot 可通过 `getMode()` / `subscribeMode()` / `subscribeValidation()` 观察 Shell,框架 Slot/Render Context 直接提供响应式实际 `mode`。框架插槽的 `state` 会随 Controller 事件更新,Undo/Redo 禁用状态可以直接绑定 `state.canUndo/canRedo`。
2099
+
2100
+ ## Group 与 Pool / Lane
2101
+
2102
+ `Group` 是不持有成员引用的 BPMN 视觉 Artifact;`Lane` 通过 `containerId` 表示画布上的 Participant containment,并继续使用标准 `flowNodeRefs` 表示 BPMN 成员。`containerId` 不会写入 XML,导入时会根据 Participant 的 `processRef` 与 BPMN DI Bounds 恢复。
2103
+
2104
+ ```js
2105
+ studio.commands.groupSelection()
2106
+ studio.commands.ungroup('Group_Review')
2107
+
2108
+ studio.commands.attachLane('Lane_Review', 'Participant_Main', 0)
2109
+ studio.commands.detachLane('Lane_Review', { x: 320, y: 640 })
2110
+
2111
+ const containment = resolveContainment(studio.model)
2112
+ containment.getParticipantLanes('Participant_Main')
2113
+ containment.getLaneForNode('Task_Approve')
2114
+
2115
+ resolveSwimlaneLabelPlacement(studio.model, lane)
2116
+ getSwimlaneContentInsets(studio.model, lane)
2117
+ getSwimlaneContentBounds(studio.model, lane)
2118
+ ```
2119
+
2120
+ 只有 `properties.processRef === model.id` 的 Participant 可以拥有 Lane。移动 Pool 会联动其 Lane、Lane 成员与相关边界事件;节点拖动结束后按中心点自动更新 Lane 的 `flowNodeRefs`。
2121
+
2122
+ Participant 与独立 Lane 可通过 `properties.swimlaneLabelPlacement` 选择 `side`(左侧竖排,默认)或 `top`(上方横排)。挂载 Lane 始终继承所属 Participant 的设置。Core 的内容 Bounds 会扣除 Participant 和 Lane 各自的 38px 标题栏,缩放与 Lane-aware 自动布局使用同一几何结果。该属性属于编辑器展示状态,不写入标准 BPMN XML;重新导入标准 XML 时回退为 `side`。
2123
+
2124
+ ## Properties Panel
2125
+
2126
+ ### 默认 Registry
2127
+
2128
+ ```js
2129
+ import {
2130
+ createDefaultPropertiesRegistry,
2131
+ PropertiesPanel,
2132
+ } from '@bpmn-nova/studio'
2133
+
2134
+ const registry = createDefaultPropertiesRegistry({ designer })
2135
+ designer.properties = registry
2136
+
2137
+ const panel = new PropertiesPanel({
2138
+ container: document.querySelector('#properties'),
2139
+ registry,
2140
+ designer,
2141
+ })
2142
+
2143
+ panel.render()
2144
+
2145
+ designer.on('selection', ({ selection, element }) => panel.render(selection, element))
2146
+ designer.on('change', () => panel.render(designer.selection, designer.getSelectedElement()))
2147
+ ```
2148
+
2149
+ ### 注册 Provider
2150
+
2151
+ ```js
2152
+ import { propertyEntry, propertyGroup } from '@bpmn-nova/studio'
2153
+
2154
+ registry.registerProvider(450, {
2155
+ id: 'company-approval',
2156
+ appliesTo: {
2157
+ kind: 'node',
2158
+ nodeType: 'userTask',
2159
+ engines: ['flowable', 'activiti'],
2160
+ },
2161
+ getGroups() {
2162
+ return [propertyGroup({
2163
+ id: 'company-approval',
2164
+ label: '公司审批配置',
2165
+ position: { after: 'task-config' },
2166
+ entries: [propertyEntry({
2167
+ id: 'approval-mode',
2168
+ type: 'select',
2169
+ label: '审批策略',
2170
+ getValue: (ctx) => ctx.extensions.get('company:approvalMode', 'single'),
2171
+ setValue: (ctx, value) => ctx.extensions.set('company:approvalMode', value),
2172
+ options: [['single', '单人'], ['all', '会签'], ['any', '或签']],
2173
+ })],
2174
+ })]
2175
+ },
2176
+ })
2177
+ ```
2178
+
2179
+ ### 修改已有 Groups(bpmn-js 风格)
2180
+
2181
+ ```js
2182
+ registry.registerProvider(900, {
2183
+ id: 'remove-documentation',
2184
+ getGroups() {
2185
+ return (groups) => groups.filter((group) => group.id !== 'documentation')
2186
+ },
2187
+ })
2188
+ ```
2189
+
2190
+ 也可通过 `position` 声明 `prepend / append / before / after / replace / remove`。
2191
+
2192
+ ### Data Provider
2193
+
2194
+ ```js
2195
+ registry.registerDataProvider('identity', {
2196
+ searchUsers(keyword) {
2197
+ return [{ value: '${manager}', label: '部门经理' }]
2198
+ },
2199
+ searchGroups(keyword) {
2200
+ return [{ value: 'finance', label: '财务部' }]
2201
+ },
2202
+ })
2203
+
2204
+ registry.registerDataProvider('forms', {
2205
+ listForms() {
2206
+ return [{ value: 'approval-form', label: '审批表单' }]
2207
+ },
2208
+ })
2209
+
2210
+ registry.registerDataProvider('processes', {
2211
+ listProcesses() {
2212
+ return [{ value: 'Process_Sub', label: '子流程' }]
2213
+ },
2214
+ })
2215
+ ```
2216
+
2217
+ ### Custom Component
2218
+
2219
+ ```js
2220
+ registry.registerComponent('company:user-picker', ({ container, value, commit }) => {
2221
+ // mount company picker
2222
+ // commit(nextValue)
2223
+ return () => {/* cleanup */}
2224
+ })
2225
+ ```
2226
+
2227
+ 完整说明见 `docs/PROPERTIES-PANEL.md`。
2228
+
2229
+ ## React
2230
+
2231
+ ```jsx
2232
+ import {
2233
+ BpmnDesigner,
2234
+ BpmnPropertiesPanel,
2235
+ } from '@bpmn-nova/react'
2236
+
2237
+ <BpmnDesigner ref={designerRef} xml={xml} engine="flowable" />
2238
+
2239
+ <BpmnPropertiesPanel
2240
+ designer={designer}
2241
+ providers={[companyProvider]}
2242
+ dataProviders={{ identity: companyIdentityProvider }}
2243
+ components={{ 'company:user-picker': CompanyUserPicker }}
2244
+ />
2245
+ ```
2246
+
2247
+ `components` 中的 React Component 接收:
2248
+
2249
+ ```ts
2250
+ {
2251
+ entry,
2252
+ context,
2253
+ value,
2254
+ onChange(nextValue)
2255
+ }
2256
+ ```
2257
+
2258
+ 也可单独调用:
2259
+
2260
+ ```js
2261
+ createReactPropertyComponent(MyReactField)
2262
+ ```
2263
+
2264
+ ## Vue 3
2265
+
2266
+ ```vue
2267
+ <BpmnDesigner ref="designerRef" :xml="xml" engine="flowable" />
2268
+
2269
+ <BpmnPalettePanel
2270
+ :studio="studio"
2271
+ :canvas="canvas"
2272
+ :providers="[companyPaletteProvider]"
2273
+ />
2274
+
2275
+ <BpmnPropertiesPanel
2276
+ :designer="designer"
2277
+ :providers="[companyProvider]"
2278
+ :data-providers="{ identity: companyIdentityProvider }"
2279
+ :components="{ 'company:user-picker': CompanyUserPicker }"
2280
+ />
2281
+ ```
2282
+
2283
+ `BpmnPalettePanel` 与 React 版本语义一致:传入的 `registry` 是权威 Registry;未传时才使用 `providers` 创建默认 Registry。没有 `canvas` 时拖拽仍可用,但点击不会伪造放置结果。复杂 Vue Entry Component 同样接收 `entry / context / value / onChange`。
2284
+
2285
+ ## Viewer / Instance Viewer
2286
+
2287
+ ```js
2288
+ const viewer = new BpmnViewer({
2289
+ container,
2290
+ model,
2291
+ runtime,
2292
+ projection: 'approval', // auto | standard | approval | compact
2293
+ responsive: false, // 默认不根据宽度自动切换
2294
+ runtimeTraceOptions: {
2295
+ showStartMilestone: false,
2296
+ showEndMilestone: false,
2297
+ },
2298
+ timeline: {
2299
+ title: ({ model }) => model.name,
2300
+ description: '按实际处理顺序展示',
2301
+ },
2302
+ runtimeDetails: {
2303
+ autoOpen: true,
2304
+ desktop: { placement: 'popover', width: 360, backdrop: false },
2305
+ mobile: { placement: 'bottom', width: '100%', maxHeight: '75%', backdrop: true, dragToDismiss: true },
2306
+ },
2307
+ onTraceClick(event) {
2308
+ console.log(event.targetType, event.element, event.visitId, event.activityInstances)
2309
+ },
2310
+ })
2311
+
2312
+ viewer.fitView()
2313
+ viewer.setProjection('compact')
2314
+ viewer.setRuntime(nextSnapshot)
2315
+ viewer.setDisplayOptions({ timeline: { description: null } })
2316
+ ```
2317
+
2318
+ `BpmnViewer` 本身只负责传入容器内的只读流程图或审批时间线,不创建 Studio 顶部导航与右侧属性面板;后两者属于 `BpmnStudioShell` 的默认示例布局。时间线 `title / description` 可传字符串、返回字符串的函数或 `null`,`null` 会隐藏对应内容。
2319
+
2320
+ Studio Shell 的轨迹切换项可通过 `projectionOptions` 配置。Playground 使用 `approval / compact / standard` 三项分别演示“实际路径 / 移动时间线 / 完整 BPMN”;组件默认仍只显示“实际路径 / 完整 BPMN”。Shell 会单独保存审批轨迹投影,`setProjection()` 只更新该偏好;“流程展示”始终使用 `standard` 完整 BPMN,再次进入审批轨迹时恢复上次选择。需要在没有 Runtime Snapshot 时显式使用 `approval / compact` 定义投影的场景,应直接使用独立 `BpmnViewer`。
2321
+
2322
+ 运行实例默认使用稳定的 `approval` 实际路径图,不根据容器宽度自动改变展示形态。传入 `responsive: true` 且未显式设置 `projection`,或直接设置 `projection: 'auto'`,才会启用响应式切换:Viewer 容器宽度 `>= 720px` 时渲染实际有效路径图,`< 720px` 时切换为原生纵向滚动的审批时间线。响应式判断使用 `ResizeObserver`,不依赖全局窗口宽度;显式指定 `standard / approval / compact` 会固定投影。无 Runtime Snapshot 时默认仍为 `standard`。Compact 时间线始终占满 Viewer 容器宽度,不保留桌面居中最大宽度。
2323
+
2324
+ `approval` 与 `compact` 都由 `createRuntimeTraceProjection()` 生成。普通 StartEvent / EndEvent 默认只在 `standard` 完整 BPMN 中显示,实际路径图和移动时间线从第一个真实 Activity Visit 开始;消息、定时、信号等特殊事件仍作为运行时里程碑保留。可通过 `runtimeTraceOptions.showStartMilestone / showEndMilestone` 恢复普通里程碑。投影优先按带时间的 `edgeVisits`、Activity Visit 和运行时转移还原实际路径,只在路径唯一可预测时追加后续人工任务。条件网关不会成为轨迹卡片,实际命中的 Sequence Flow 名称会作为步骤间条件标签,原始表达式不会进入轨迹 UI。需要核对流程定义时切换到 `standard` 查看完整 BPMN。
2325
+
2326
+ ```js
2327
+ const trace = createRuntimeTraceProjection({ model, runtime, presentation })
2328
+ console.log(trace.graphModel) // PC 有效路径图
2329
+ console.log(trace.items, trace.links, trace.groups) // 移动端时间线数据
2330
+ console.log(trace.mappings) // 回溯原始节点、Sequence Flow、Activity Visit 与 Edge Visit
2331
+ ```
2332
+
2333
+ Viewer 与审批轨迹模式可在空白画布上直接按住左键拖动视口;Studio 单选模式具有相同行为,框选模式的左键仍用于选择节点。
2334
+
2335
+ ### Runtime presentation
2336
+
2337
+ Runtime snapshots are engine-neutral. `visitId` separates repeated arrivals at the same BPMN element; records sharing a visit describe one approval round, while `multiInstanceId` and an explicit `approvalMode` describe multi-person approval. Multiple people are never assumed to be countersign unless the snapshot declares `approvalMode: 'all'`.
2338
+
2339
+ ```js
2340
+ const runtime = {
2341
+ processInstanceId: 'PI-1001',
2342
+ status: 'running',
2343
+ visitedEdges: ['Flow_Submit_Review'],
2344
+ activities: [
2345
+ { id: 'a-1', elementId: 'Task_Review', visitId: 'visit-1', multiInstanceId: 'mi-1', status: 'completed', participant: { id: 'u-1', name: '张三' }, approvalMode: 'all', totalInstances: 3 },
2346
+ { id: 'a-2', elementId: 'Task_Review', visitId: 'visit-1', multiInstanceId: 'mi-1', status: 'active', participant: { id: 'u-2', name: '李四' }, approvalMode: 'all', totalInstances: 3 },
2347
+ ],
2348
+ actions: [
2349
+ {
2350
+ id: 'approve-1',
2351
+ type: 'approve',
2352
+ activityId: 'a-1',
2353
+ elementId: 'Task_Review',
2354
+ visitId: 'visit-1',
2355
+ actor: { id: 'u-1', name: '张三' },
2356
+ occurredAt: '2026-08-27 09:18',
2357
+ content: {
2358
+ plainText: '资料完整,同意。',
2359
+ blocks: [
2360
+ { type: 'paragraph', text: '资料完整,同意。报价单见附件。' },
2361
+ { type: 'image', assetId: 'quotation-image', alt: '报价单' },
2362
+ { type: 'file', assetId: 'quotation-file' },
2363
+ ],
2364
+ assets: [
2365
+ { id: 'quotation-image', name: '报价单.jpg', mediaType: 'image/jpeg', size: 382144 },
2366
+ { id: 'quotation-file', name: '报价明细.pdf', mediaType: 'application/pdf', size: 1282048 },
2367
+ ],
2368
+ },
2369
+ },
2370
+ {
2371
+ id: 'reject-1-action',
2372
+ type: 'reject',
2373
+ elementId: 'Task_Final',
2374
+ visitId: 'visit-final-1',
2375
+ actor: { name: '王总' },
2376
+ occurredAt: '2026-08-27 10:30',
2377
+ targetElementId: 'Task_Review',
2378
+ content: { plainText: '请补充附件' },
2379
+ },
2380
+ ],
2381
+ edgeVisits: [
2382
+ { id: 'ev-1', edgeId: 'Flow_Submit_Review', occurredAt: '2026-08-27 09:20' },
2383
+ ],
2384
+ transitions: [{
2385
+ id: 'reject-1',
2386
+ type: 'reject',
2387
+ sourceElementId: 'Task_Final',
2388
+ targetElementId: 'Task_Review',
2389
+ operator: '王总',
2390
+ occurredAt: '2026-08-27 10:30',
2391
+ actionId: 'reject-1-action',
2392
+ comment: '请补充附件',
2393
+ invalidatedActivityIds: ['Gateway_Amount', 'Task_Final'],
2394
+ invalidatedEdgeIds: ['Flow_Review_Gateway', 'Flow_Gateway_Final'],
2395
+ }],
2396
+ }
2397
+
2398
+ const presentation = createRuntimePresentation({ model, runtime })
2399
+ console.log(presentation.getNode('Task_Review').statusLabel) // 会签 1/3
2400
+ console.log(presentation.getNode('Task_Review').actionSummary)
2401
+ ```
2402
+
2403
+ Approval Action 表示 Activity Visit 中发生的一次不可变审批操作,通过、驳回、加签、转办等动作都可以携带结构化 Action Content。第一版默认 Renderer 只解释 `paragraph / image / file` 三类内容块,不执行 HTML 或 Markdown。旧 `ActivityInstance.comment / outcome` 和 `RuntimeTransition.comment` 会被 `normalizeRuntime()` 转为兼容动作;显式 `actions` 始终优先。
2404
+
2405
+ Runtime Asset Reference 只保存资源 ID 和展示元数据,不保存二进制或访问地址。宿主通过 `runtimeAssetResolver` 按用途返回临时地址;图片支持缩略图和容器内预览,其他文件提供下载入口:
2406
+
2407
+ ```js
2408
+ const viewer = new BpmnViewer({
2409
+ container,
2410
+ model,
2411
+ runtime,
2412
+ runtimeAssetResolver(asset, { purpose, action, signal }) {
2413
+ return api.resolveRuntimeAsset({ assetId: asset.id, purpose, actionId: action.id, signal })
2414
+ },
2415
+ onTraceClick(event) {
2416
+ console.log(event.actions)
2417
+ },
2418
+ })
2419
+ ```
2420
+
2421
+ Resolver 可以返回相对或绝对 HTTP(S) 地址,也可以返回 Blob URL;未配置、解析失败、协议不安全或资源过期时,默认 UI 显示“资源不可用”,不会阻断审批轨迹。文件上传、删除、权限签发和生命周期管理属于宿主业务系统,不属于 Viewer。
2422
+
2423
+ ## SVG 导出与确认预览
2424
+
2425
+ Studio、Designer、Viewer 和底层 Canvas/Renderer 都提供异步 `exportSvg()`;高层视觉实例同时提供 `openSvgExportPreview()`。导出器按模型和 Runtime Presentation 重建纯 SVG,不读取当前页面节点,因此输出范围不受缩放、平移、滚动、选中框、网格或弹层影响。
2426
+
2427
+ ```js
2428
+ const artifact = await viewer.exportSvg({
2429
+ theme: 'dark',
2430
+ transparentBackground: false,
2431
+ padding: 36,
2432
+ filename: '采购申请审批流程-实际路径.svg',
2433
+ })
2434
+
2435
+ // 内置弹窗先生成 Artifact;确认按钮下载当前预览,不会再次生成
2436
+ viewer.openSvgExportPreview()
2437
+ ```
2438
+
2439
+ `SvgExportArtifact` 包含 `svg`、`blob`、文件名、尺寸、`viewBox` 与非阻断 `warnings`。移动时间线会展开全部历史动作;附件只输出名称、MIME 和大小,审批图片通过 `runtimeAssetResolver(asset, { purpose: 'export', ... })` 解析并转为 Data URL 内嵌。自定义 HTML Renderer 需要在 `svgExport.nodeRenderers` 或 `svgExport.runtimeTimelineRenderer` 提供纯 SVG 适配,否则导出器使用标准视觉并记录警告。导出不使用 `foreignObject`、页面 CSS、Blob URL 或临时附件地址。
2440
+
2441
+ ## 实例级主题与运行态外观
2442
+
2443
+ NPM 接入应使用 Studio、Designer、Viewer、React 或 Vue 自带的单一样式入口;这些入口已按正确顺序包含 Theme 和所需模块样式。组件库默认是 `light`,Playground 示例首次打开默认使用 `auto`。
2444
+
2445
+ ```js
2446
+ import { BpmnViewer } from '@bpmn-nova/studio/viewer'
2447
+ import '@bpmn-nova/studio/styles.css'
2448
+
2449
+ const viewer = new BpmnViewer({
2450
+ container,
2451
+ model,
2452
+ runtime,
2453
+ theme: {
2454
+ mode: 'auto',
2455
+ dark: {
2456
+ colors: {
2457
+ canvas: '#0b1018',
2458
+ surface: '#18202b',
2459
+ primary: '#8f88ff',
2460
+ },
2461
+ tones: {
2462
+ compliance: {
2463
+ foreground: '#a9d7ff',
2464
+ background: '#18324a',
2465
+ border: '#315d7f',
2466
+ strong: '#68b8f4',
2467
+ },
2468
+ },
2469
+ },
2470
+ },
2471
+ runtimeAppearance: {
2472
+ statuses: { active: 'primary', completed: 'success' },
2473
+ actions: {
2474
+ approve: { label: '同意', iconId: 'ui.statusCompleted', tone: 'success' },
2475
+ complianceReview: { label: '合规复核', tone: 'compliance' },
2476
+ },
2477
+ transitions: { reject: { tone: 'danger' }, return: { tone: 'warning' } },
2478
+ },
2479
+ onThemeChange(state) {
2480
+ console.log(state.mode, state.resolvedTheme)
2481
+ },
2482
+ })
2483
+
2484
+ viewer.setThemeMode('dark')
2485
+ viewer.setTheme({ mode: 'auto', light: { colors: { primary: '#4f46e5' } } })
2486
+ console.log(viewer.getThemeState())
2487
+ ```
2488
+
2489
+ `canvas` 是工作区和 BPMN 画布底层,`surface` 是顶部栏、面板和主要内容层。只覆盖这两个值时,`surfaceRaised/subtle/muted` 等辅助层继续使用深色预设;需要更大幅度的品牌调整时应显式覆盖相应语义令牌。NPM Studio 用户只需导入 `@bpmn-nova/studio/styles.css`,不需要了解内部 CSS 顺序。
2490
+
2491
+ `mode: 'auto'` 使用实例根节点所属 Window 的 `matchMedia('(prefers-color-scheme: dark)')` 并持续监听系统变化。Controller 只更新组件根节点的 `data-nova-theme`、`data-nova-theme-mode`、`color-scheme` 和 CSS Variables,不修改宿主 `<html>`,也不会刷新 BPMN、清除选择或关闭详情层。React/Vue 的 `theme` 与 `runtimeAppearance` 属性变化会更新已有实例,不会重新挂载。
2492
+
2493
+ 显式 `RuntimeApprovalAction.label` 的优先级最高,其次是 `runtimeAppearance.actions[type].label`、内置标签和原始类型。非法 Tone 名称、无效颜色和未知主题字段被忽略;未声明的 Tone 回退到 `neutral`。Runtime Snapshot 始终只描述运行事实,不保存主题、图标或配色。
2494
+
2495
+ `visitedEdges` 表示历史上曾经经过;`edgeVisits` 与驳回记录的失效范围共同决定当前有效路径。活动驳回仅在目标重新处理尚未完成时显示,目标本轮完成后转为 `resolved` 并从画布隐藏,但历史记录仍保留。接入流程引擎时应优先提供明确的 `invalidatedActivityIds`、`invalidatedEdgeIds` 和带时间的 `edgeVisits`;缺失时仅对唯一、同作用域的已访问路径做兼容推断。
2496
+
2497
+ Sequence Flow 是瞬时流转:一旦到达目标节点即显示为已完成,不会因为目标任务仍在处理中而显示为蓝色活动虚线。任务的“处理中”状态只体现在任务节点自身;被驳回失效的历史线路则恢复为默认样式。
2498
+
2499
+ `BpmnViewer` accepts `runtimePresenter` to replace aggregation and `runtimeDetailsRenderer` to replace the default node details content. `runtimeTraceProjector` can replace the effective-path calculation, while `runtimeTimelineRenderer` replaces only the compact timeline UI. The unified `onTraceClick` observes desktop nodes/edges, mobile Activity Visits, and runtime transitions; legacy callbacks remain available. Set `runtimeDetails.autoOpen: false` to emit clicks without opening built-in details. Desktop and mobile detail placement are configured independently, so a mobile bottom sheet or centered dialog never changes the desktop popover. `placement: 'bottom'` defaults `dragToDismiss` to `true`, allowing mouse, touch, or pen input to drag the Viewer-provided handle downward to dismiss; set it to `false` when a host or custom Renderer needs a fixed sheet. `runtimeTransitionDetailsRenderer` replaces rejection details content, while `onRuntimeTransitionDetailsOpen` observes its opening. `RuntimePresentation.getTransition(id)` returns the same derived transition state used by the line renderer and details UI.
2500
+
2501
+ 活动驳回线由公开的纯函数 `routeRuntimeTransition(model, transition, options?)` 计算。它会读取流程方向并优先使用与外侧通道一致的同侧锚点:纵向左/右通道分别采用 `Left → Left`、`Right → Right`,横向上/下通道分别采用 `Top → Top`、`Bottom → Bottom`。同侧锚点不可用时才降级到主流程轴锚点。路由会同时避让节点、Sequence Flow、已占用端口、标签和其他运行时路线;自定义 Renderer 可传入自己的占用路线和矩形障碍:
2502
+
2503
+ ```js
2504
+ import { routeRuntimeTransition } from '@bpmn-nova/studio'
2505
+
2506
+ const route = routeRuntimeTransition(model, transition, {
2507
+ occupiedRoutes: previousRoutes.map((item) => item.points),
2508
+ obstacles: [{ x: 420, y: 180, width: 120, height: 32 }],
2509
+ labelSize: { width: 144, height: 28 },
2510
+ })
2511
+
2512
+ console.log(route.points, route.labelPoint, route.laneSide, route.laneIndex)
2513
+ console.log(route.sourceSide, route.targetSide, route.anchorStrategy, route.portConflicts)
2514
+ ```
2515
+
2516
+ 碰撞计算基于正交折线骨架;默认 SVG Renderer 再根据 `model.settings.edgeStyle` 和 `cornerRadius` 输出直线、圆角折线或柔和曲线。该路线只属于运行时投影,不会写入 BPMN 模型或 XML。
2517
+
2518
+ ## 节点与 Properties Registry
2519
+
2520
+ ```js
2521
+ import { NODE_DEFINITIONS, PALETTE_GROUPS } from '@bpmn-nova/studio'
2522
+
2523
+ console.log(Object.keys(NODE_DEFINITIONS)) // 59 concrete visual types + generic fallback
2524
+ ```
2525
+
2526
+ Palette、Quick Add 和 Properties Panel 都以同一 Node Definition 为基础,不维护第二份节点类型列表。
2527
+
2528
+ ## 并行分支与汇聚
2529
+
2530
+ ```js
2531
+ import { PARALLEL_GATEWAY_PRESETS, resolveGatewayRole } from '@bpmn-nova/studio'
2532
+
2533
+ studio.quickAdd(
2534
+ 'Task_Review',
2535
+ PARALLEL_GATEWAY_PRESETS.converging.nodeType,
2536
+ PARALLEL_GATEWAY_PRESETS.converging.preset,
2537
+ )
2538
+
2539
+ const role = resolveGatewayRole(studio.model, studio.getSelectedElement())
2540
+ console.log(role.declared, role.inferred, role.effective)
2541
+ console.log(role.status, role.topologyComplete, role.issues)
2542
+ ```
2543
+
2544
+ “并行分支”和“并行汇聚”都是标准 `parallelGateway`,分别预设 `gatewayDirection="Diverging"` 与 `gatewayDirection="Converging"`。两者在 Palette 和快捷添加中使用可覆盖的 `ui.parallelDiverging` / `ui.parallelConverging` 编辑器图标,画布与 XML 仍使用标准并行网关符号。排他和包容网关不提供额外的汇聚快捷项。
2545
+
2546
+ `resolveGatewayRole()` 会同时返回声明角色、线路推断角色、结构完成度及 `valid` / `incomplete` / `conflict` / `ambiguous` 状态。业务属性面板据此分别显示并行出口或汇聚入口/后续出口;角色切换和校验不会自动删除、改线或清空已有 Sequence Flow 数据。
2547
+
2548
+
2549
+ <!-- SOURCE: docs/CUSTOMIZATION.md -->
2550
+
2551
+ # Studio 自定义实施指南
2552
+
2553
+ Stage 0 将编辑器拆成无 UI 的 `BpmnStudioController`,以及可组合的 Palette、Canvas、Properties、Shell。流程模型与 BPMN XML 导入导出格式保持不变。
2554
+
2555
+ ## 默认组合
2556
+
2557
+ ```js
2558
+ const studio = createStudioController({
2559
+ model: createSampleProcess('flowable'),
2560
+ propertiesProfile: 'business',
2561
+ });
2562
+
2563
+ const shell = createStudioShell({ container, studio });
2564
+ ```
2565
+
2566
+ NPM 接入只需要加载 Studio 的自包含样式入口。它已经按 Theme、Icons、Renderer、Palette、Properties Renderer、Studio 的顺序打包,避免宿主重复引入内部 CSS。
2567
+
2568
+ ```js
2569
+ import '@bpmn-nova/studio/styles.css'
2570
+ ```
2571
+
2572
+ ## 自定义左侧内容
2573
+
2574
+ Palette 由 Provider、Section、Item 三层组成。Provider 可用 `before`、`after`、`replace`、`remove` 调整默认内容,较高优先级最后应用。
2575
+
2576
+ ```js
2577
+ const palette = createDefaultPaletteRegistry({
2578
+ providers: [{
2579
+ id: 'company-components',
2580
+ priority: 900,
2581
+ getSections: () => [{
2582
+ id: 'company',
2583
+ label: '公司组件',
2584
+ position: { before: 'favorites' },
2585
+ items: [{
2586
+ id: 'department-approval',
2587
+ label: '部门审批',
2588
+ iconId: 'company.approval',
2589
+ create: {
2590
+ kind: 'node',
2591
+ nodeType: 'userTask',
2592
+ preset: { name: '部门审批', properties: { candidateGroups: 'department-manager' } },
2593
+ },
2594
+ }],
2595
+ }],
2596
+ }],
2597
+ });
2598
+ ```
2599
+
2600
+ `create.kind` 支持 `node` 和 `template`。模板可一次创建多个节点及连线,并作为一个历史记录撤销。
2601
+
2602
+ ## 自定义 UI 图标
2603
+
2604
+ 节点图标和编辑器 UI 图标共用 `IconRegistry`。默认 UI 图标使用稳定的 `ui.*` ID;注册同 ID 描述符即可覆盖所有使用该 registry 的 Shell、Palette、Properties、Designer 和 Viewer。
2605
+
2606
+ ```js
2607
+ const icons = createDefaultIconRegistry([{
2608
+ id: 'ui.chevron',
2609
+ viewBox: '0 0 24 24',
2610
+ paths: [{ d: 'M5 8l7 8 7-8', fill: 'none', stroke: 'currentColor' }],
2611
+ }]);
2612
+
2613
+ createStudioShell({ container, studio, iconRegistry: icons });
2614
+ ```
2615
+
2616
+ 自定义 DOM 布局可以输出 `<span class="nova-icon" data-icon="ui.chevron"></span>`,再调用 `hydrateIcons(root, icons)` 挂载 SVG。该操作可重复执行;未知 `ui.*` ID 保持空白,不会回退成 BPMN 节点图标。
2617
+
2618
+ ## 自定义右侧内容
2619
+
2620
+ 默认属性面板支持业务和开发者两个 Profile。业务视图隐藏 Element ID、Namespace、Raw XML、Listener、Field Injection 等技术字段;开发者视图显示完整配置。
2621
+
2622
+ ```js
2623
+ properties.registerProvider(850, {
2624
+ id: 'company-approval',
2625
+ appliesTo: { kind: 'node', nodeType: 'userTask' },
2626
+ getGroups: () => [propertyGroup({
2627
+ id: 'approval-policy',
2628
+ label: '公司审批规范',
2629
+ position: { after: 'assignment' },
2630
+ entries: [propertyEntry({
2631
+ id: 'policy',
2632
+ type: 'select',
2633
+ label: '审批策略',
2634
+ getValue: (ctx) => ctx.extensions.get('company:policy', 'single'),
2635
+ setValue: (ctx, value) => ctx.extensions.set('company:policy', value),
2636
+ options: [['single', '单人审批'], ['all', '会签']],
2637
+ })],
2638
+ })],
2639
+ });
2640
+ ```
2641
+
2642
+ 在 Group 或 Entry 上设置 `level: 'developer'`,可将自定义技术配置限制在开发者视图。
2643
+
2644
+ ## 组合 Shell 区域与宿主界面
2645
+
2646
+ 默认 Shell 包含 Header、Palette、Canvas、Properties 和 Statusbar。仅需要移除某个默认区域时使用 `regions`,不需要重写布局:
2647
+
2648
+ ```js
2649
+ const shell = createStudioShell({
2650
+ container,
2651
+ studio,
2652
+ regions: { right: 'hidden' },
2653
+ })
2654
+
2655
+ // 原地切换,不重建 Controller、Canvas、历史或视口。
2656
+ shell.setRegions({ right: 'default' })
2657
+ ```
2658
+
2659
+ `header`、`left`、`right`、`footer` 均接受 `default | hidden`。隐藏区域不占 Grid 轨道、不显示边框,也不保留可聚焦控件。Viewer/Instance 的只读布局仍会隐藏 Palette;区域配置只能进一步隐藏。
2660
+
2661
+ 局部内容替换使用 `slots.left`、`slots.right`、`slots.headerStart`、`slots.headerActions`、`slots.header` 和 `slots.footer`。`headerStart` 只替换默认 Brand;`headerActions` 只替换最右侧默认“校验 / 导入 / 导出”动作组;`header` 替换完整 Header,优先级高于两个局部 Slot。回调收到 `studio`、`shell`、`actions`、`getState()`、`subscribe()`、注册表、交互控制器和 Canvas。
2662
+
2663
+ ```js
2664
+ createStudioShell({
2665
+ container,
2666
+ studio,
2667
+ slots: {
2668
+ headerStart({ container, actions, getState, subscribe }) {
2669
+ // 挂载返回入口、业务图标、流程名称和类型;返回卸载函数。
2670
+ },
2671
+ headerActions({ container, actions, getMode, subscribeMode }) {
2672
+ // 挂载校验 / 保存 / 发布;校验调用 actions.validate(),其余调用宿主服务。
2673
+ },
2674
+ left({ container, studio, interactions, canvas }) {
2675
+ // 挂载任意 Vanilla / React / Vue UI,返回卸载函数。
2676
+ },
2677
+ right({ container, studio, propertiesRegistry }) {
2678
+ // 可忽略默认 PropertiesPanel,自行订阅 studio.subscribe()。
2679
+ },
2680
+ },
2681
+ });
2682
+ ```
2683
+
2684
+ 默认 Header 和宿主 Header Slots 共用 `shell.actions`:撤销/重做、布局/布线、最佳视图、结构校验、BPMN XML 导入导出及 SVG 导出。顶部不再重复显示最佳视图,底部缩放区和程序化 `fitView()` 继续可用。Actions 不拥有宿主的保存草稿、发布、权限、文件选择或服务端事务。
2685
+
2686
+ `actions.validate()` 会先更新 Nova 默认状态栏,再发送 `toolbar` 来源的 Validation Event,并返回 issues。程序化 `shell.validate()` 使用 `api` 来源;两者都可由 `shell.subscribeValidation()` 观察。`valid` 表示不存在 error,warning 仍会显示并随事件返回。
2687
+
2688
+ ```js
2689
+ async function publishProcess(actions) {
2690
+ const issues = actions.validate()
2691
+ if (issues.some((issue) => issue.level === 'error')) return
2692
+ await publishXml(actions.exportXml())
2693
+ }
2694
+ ```
2695
+
2696
+ React 使用 `headerStart` / `headerActions` / `header` Render Prop 和 Portal;Vue 使用 `#header-start` / `#header-actions` / `#header` Named Slot 和 Teleport。两者都在宿主应用树内渲染,不创建独立 React Root 或 Vue App,因此 Context、provide/inject、响应式状态和生命周期继续有效。Vue 原有 `slotsConfig`、React 原有 `slots` 仍作为 Core DOM 接口保留。
2697
+
2698
+ 若整个三栏结构都要自定义,使用 `layout({ container, mount, ...services })`。`mount.canvas()`、`mount.palette()`、`mount.properties()` 可分别挂载官方实现,也可只使用 Controller 自行实现。`layout()` 与 `regions` 是互斥 Interface,同时传入会抛出配置错误。
2699
+
2700
+ 宿主复用自己的业务属性面板时,把它放在 Nova 根节点外部的兄弟区域,并订阅 Controller 的 `selectionChanged`(框架中为 `selection-change` / `onSelectionChange`)。该事件覆盖节点、连线、多选、清空和键盘选择;`element-click` 只观察点击,不能替代选择状态。业务配置应使用稳定 BPMN Element ID 关联,不写入 Nova UI 状态。
2701
+
2702
+ ## 自定义画布右键菜单
2703
+
2704
+ 设计模式的右键菜单由 `ContextMenuRegistry` 解析。Provider 可以使用稳定 Action ID 添加、替换、移除或重新定位默认动作;动作仍通过传入的 `studio` 与 `canvas` 调用同一套编辑命令。
2705
+
2706
+ ```js
2707
+ const contextMenu = createDefaultContextMenuRegistry({
2708
+ providers: [{
2709
+ id: 'company-context-actions',
2710
+ priority: 900,
2711
+ getActions: () => [
2712
+ { id: 'remove-reroute', position: { remove: 'canvas.reroute' } },
2713
+ {
2714
+ id: 'company.audit',
2715
+ label: '查看审计信息',
2716
+ iconId: 'ui.locate',
2717
+ position: { after: 'canvas.fit' },
2718
+ execute({ target }) { openAuditPanel(target); },
2719
+ },
2720
+ ],
2721
+ }],
2722
+ });
2723
+
2724
+ createStudioShell({ container, studio, contextMenuRegistry: contextMenu });
2725
+ ```
2726
+
2727
+ 如果需要完全不同的视觉布局,使用 `slots.contextMenu`。回调接收命中目标、当前选择、屏幕位置、已解析动作以及 `execute(id)`、`close()`;自定义布局不需要重新实现动作可用性判断。
2728
+
2729
+ ```js
2730
+ createStudioShell({
2731
+ container,
2732
+ studio,
2733
+ slots: {
2734
+ contextMenu({ container, actions, execute }) {
2735
+ // 挂载自定义菜单,点击时调用 execute(action.id)。
2736
+ },
2737
+ },
2738
+ });
2739
+ ```
2740
+
2741
+ ## 自定义画布节点内容
2742
+
2743
+ `rendererOptions.nodeRenderers` 按节点类型或节点 kind 注册内部渲染器。外框、端口、选择、连接、拖动和删除仍由 Canvas 管理。
2744
+
2745
+ ```js
2746
+ createStudioShell({
2747
+ container,
2748
+ studio,
2749
+ rendererOptions: {
2750
+ nodeRenderers: {
2751
+ userTask({ container, node }) {
2752
+ container.textContent = `${node.name} · ${node.properties.candidateGroups || '待配置'}`;
2753
+ return () => {}; // React/Vue 等有挂载状态的实现应在此卸载。
2754
+ },
2755
+ },
2756
+ },
2757
+ });
2758
+ ```
2759
+
2760
+ React 与 Vue 包都提供 `BpmnStudio`、`BpmnCanvas`、`BpmnPalettePanel`、`BpmnPropertiesPanel` 和 `useBpmnStudio`。它们共享同一个 Controller 与注册表协议。独立 Palette 未传 `registry` 时使用 `providers` 创建默认 Registry;传入外部 Registry 后该 Registry 是权威来源,Adapter 不再隐式修改它。
2761
+
2762
+ ```vue
2763
+ <script setup>
2764
+ import { ref } from 'vue'
2765
+ import { BpmnCanvas, BpmnPalettePanel, createStudioController } from '@bpmn-nova/vue'
2766
+
2767
+ const studio = createStudioController({ model, allowedNodeTypes })
2768
+ const canvasRef = ref(null)
2769
+ </script>
2770
+
2771
+ <template>
2772
+ <div class="custom-workbench">
2773
+ <BpmnPalettePanel
2774
+ :studio="studio"
2775
+ :canvas="canvasRef?.getCanvas()"
2776
+ :providers="paletteProviders"
2777
+ theme="auto"
2778
+ />
2779
+ <BpmnCanvas ref="canvasRef" :studio="studio" theme="auto" />
2780
+ </div>
2781
+ </template>
2782
+ ```
2783
+
2784
+ 创建外部 Controller 的一方负责在页面卸载时销毁它;Canvas 和 Palette Adapter 不销毁外部 Controller。
2785
+
2786
+ ### 只覆盖标准节点副标题
2787
+
2788
+ 不需要复制整个节点 Renderer 时,优先使用 `nodeSubtitleResolver`。它复用 Nova 的默认标题、图标、端口、Tooltip、ARIA 与 SVG 布局,只替换标准任务和容器卡片的定义态副标题:
2789
+
2790
+ ```js
2791
+ const summaries = new Map()
2792
+ const shell = createStudioShell({
2793
+ container,
2794
+ studio,
2795
+ nodeSubtitleResolver({ node, definition, model, mode, surface, defaultSubtitle }) {
2796
+ if (!summaries.has(node.id)) return undefined
2797
+ return summaries.get(node.id)
2798
+ },
2799
+ })
2800
+
2801
+ summaries.set('ServiceTask_Archive', '1 条归档规则')
2802
+ shell.refreshPresentation()
2803
+
2804
+ summaries.set('ServiceTask_Archive', null) // 删除副标题行
2805
+ shell.refreshPresentation()
2806
+ ```
2807
+
2808
+ 返回 `undefined` 恢复权威默认值,返回 `null` 隐藏,字符串(包括 `''`)是显式覆盖。Resolver 必须同步且只读;异常、Promise 或非法值会带节点 ID 失败。刷新会先解析全部节点再更新,因此不会留下局部视觉,也不会改变 XML、Undo/Redo、Selection、Scope、Zoom/Pan 或 Mode。
2809
+
2810
+ 优先级为“成功的完整自定义 Renderer > Subtitle Resolver > Nova 默认副标题”。HTML Renderer 没有 SVG 适配而回退到标准导出视觉时,SVG 会应用 Resolver;Instance 不调用 Resolver,等待、会签、驳回和重新进入摘要继续来自 Runtime Presentation。业务候选人摘要不能写入标准 `candidateUsers/candidateGroups` 来冒充执行配置。
2811
+
2812
+ HTML 节点内容不会被复制到导出的 SVG。需要保持品牌视觉时,应为同一节点类型提供纯 SVG 适配;Renderer 接收 `SVGGElement`、具体主题快照与 Icon Registry,只能创建 SVG Primitive,不能使用 `foreignObject`:
2813
+
2814
+ ```js
2815
+ createStudioShell({
2816
+ container,
2817
+ studio,
2818
+ svgExport: {
2819
+ nodeRenderers: {
2820
+ userTask({ container, node, themeSnapshot }) {
2821
+ const label = container.ownerDocument.createElementNS(container.namespaceURI, 'text')
2822
+ label.setAttribute('x', String(node.x + 16))
2823
+ label.setAttribute('y', String(node.y + 28))
2824
+ label.setAttribute('fill', themeSnapshot.colors.text)
2825
+ label.textContent = node.name
2826
+ container.appendChild(label)
2827
+ },
2828
+ },
2829
+ async runtimeTimelineRenderer({ container, projection, themeSnapshot, resolveAsset, signal }) {
2830
+ // 可按统一 projection 生成自定义纵向 SVG,并返回最终内容高度。
2831
+ return { height: 960 }
2832
+ },
2833
+ },
2834
+ })
2835
+ ```
2836
+
2837
+ 未提供 SVG 适配时,导出器回退到标准 BPMN/审批时间线视觉,并把降级记录放入 `artifact.warnings` 与确认预览。预览主题切换不会切换宿主组件主题;确认下载直接复用当前预览 Artifact。
2838
+
2839
+ ## 主题与语义 Tone
2840
+
2841
+ Theme 模块是颜色与主题状态的唯一配置 Seam。Shell 为整套 Studio 创建一个 `ThemeController`,Canvas、Palette、Properties 和 Viewer 继承同一组 CSS Variables;独立组件则各自拥有 Controller。不要在自定义 Renderer 中修改 `document.documentElement`。
2842
+
2843
+ NPM Studio 接入使用一个自包含样式入口:
2844
+
2845
+ ```js
2846
+ import { createStudioShell } from '@bpmn-nova/studio'
2847
+ import '@bpmn-nova/studio/styles.css'
2848
+ ```
2849
+
2850
+ ```js
2851
+ createStudioShell({
2852
+ container,
2853
+ studio,
2854
+ theme: {
2855
+ mode: 'dark',
2856
+ dark: {
2857
+ colors: {
2858
+ canvas: '#0b1018',
2859
+ surface: '#18202b',
2860
+ surfaceRaised: '#222936',
2861
+ borderStrong: '#4c586d',
2862
+ focusRing: 'rgba(160, 155, 255, .74)',
2863
+ },
2864
+ shadows: { lg: '0 24px 70px rgba(0, 0, 0, .52)' },
2865
+ tones: {
2866
+ audit: {
2867
+ foreground: '#9dd7ff',
2868
+ background: '#17344a',
2869
+ border: '#2f607e',
2870
+ strong: '#58b8ef',
2871
+ },
2872
+ },
2873
+ },
2874
+ },
2875
+ runtimeAppearance: {
2876
+ actions: { audit: { label: '审计复核', tone: 'audit' } },
2877
+ },
2878
+ })
2879
+ ```
2880
+
2881
+ 主题颜色按语义配置:Canvas、Surface、分层 Surface、文本、边框、主色、焦点环、连线、网格、遮罩和 `sm/md/lg` 阴影。Tone 固定包含 `foreground / background / border / strong` 四个角色;自定义名称必须是安全的小写标识。审批状态、动作和异常线路只映射到 Tone,不把颜色写入 Runtime Snapshot。
2882
+
2883
+ `canvas` 与 `surface` 是最小的深色品牌覆盖组合:前者控制最底层工作区,后者控制顶部栏、Palette、Properties 和主要内容。ThemeController 不会自动从这两个值推导其他 Surface,因而未配置的 `surfaceRaised/subtle/muted` 会稳定保留默认值;这可以避免任意颜色混合造成不可控的对比度变化。
2884
+
2885
+ 自定义节点、详情和移动时间线 Renderer 会收到只读 `themeState` 与 `runtimeAppearance`。资源可以根据 `themeState.resolvedTheme` 选择深浅版本;颜色优先使用继承的 `--nova-color-*` 与 `--nova-tone-*`,从而在 `auto` 运行中切换时无需重新挂载。
2886
+
2887
+ 完整可运行示例见 `apps/playground/main.js`。
2888
+
2889
+ ## 自定义审批轨迹详情
2890
+
2891
+ 默认 Instance Viewer 会把运行记录归一化为 `RuntimePresentation`。自定义节点通过 `runtimePresentation` 读取状态和处理人摘要,通过 `openRuntimeDetails()` 复用默认详情弹层,不需要再次实现会签与多轮审批聚合。
2892
+
2893
+ ```js
2894
+ createStudioShell({
2895
+ container,
2896
+ studio,
2897
+ runtime,
2898
+ mode: 'instance',
2899
+ rendererOptions: {
2900
+ nodeRenderers: {
2901
+ userTask({ container, node, mode, runtimePresentation, openRuntimeDetails }) {
2902
+ if (mode !== 'instance') return
2903
+ const button = document.createElement('button')
2904
+ button.textContent = `${node.name} · ${runtimePresentation.summary}`
2905
+ button.onclick = () => openRuntimeDetails(button)
2906
+ container.appendChild(button)
2907
+ },
2908
+ },
2909
+ },
2910
+ })
2911
+ ```
2912
+
2913
+ 完整替换详情内容使用 `slots.runtimeDetails`;回调接收 `node`、`presentation`、`anchor`、已解析的 `layout` 和 `close()`,返回卸载函数。独立 `BpmnViewer` 使用等价的 `runtimeDetailsRenderer`。React 与 Vue 包分别提供 `createReactRuntimeDetailsComponent()` 和 `createVueRuntimeDetailsComponent()` 适配框架组件。内容 Renderer 不负责定位;`runtimeDetails.desktop / mobile` 分别控制 `popover / center / bottom`,移动端默认 Bottom 抽屉占满 Viewer 容器宽度。Viewer 会为启用 `dragToDismiss` 的 Bottom 抽屉注入标准拖动横条,并在退出动效完成后调用 Renderer 的卸载函数;自定义 Renderer 可配置 `dragToDismiss: false` 禁用该能力。
2914
+
2915
+ 默认详情会按 Activity Visit 展示 `presentation.actions` 中的 Approval Action,并渲染安全的文本、图片和附件块。自定义详情和时间线 Renderer 会收到同一个 `resolveAsset(asset, purpose, action)`,可复用宿主配置的 `runtimeAssetResolver`,无需知道文件存储或鉴权方式:
2916
+
2917
+ ```js
2918
+ createStudioShell({
2919
+ container,
2920
+ studio,
2921
+ runtime,
2922
+ rendererOptions: {
2923
+ runtimeAssetResolver(asset, { purpose, action, signal }) {
2924
+ return fetchTemporaryAssetUrl(asset.id, purpose, action.id, signal)
2925
+ },
2926
+ },
2927
+ slots: {
2928
+ runtimeDetails({ presentation, resolveAsset }) {
2929
+ console.log(presentation.latestAction, resolveAsset)
2930
+ },
2931
+ },
2932
+ })
2933
+ ```
2934
+
2935
+ 节点自定义 Renderer 只应读取 `runtimePresentation.actionSummary / latestAction / imageCount / fileCount` 做紧凑摘要,不应在 BPMN 节点内加载图片。完整媒体仍放在时间线或详情层;如需完全不同的富内容 UI,再替换已有 `runtimeDetails / runtimeTimeline` Renderer。
2936
+
2937
+ ```js
2938
+ createStudioShell({
2939
+ container,
2940
+ studio,
2941
+ slots: {
2942
+ runtimeDetails({ container, node, presentation, close }) {
2943
+ // 挂载任意 Vanilla / React / Vue 详情组件。
2944
+ return () => {}
2945
+ },
2946
+ },
2947
+ })
2948
+ ```
2949
+
2950
+ 移动端时间线也可以独立替换,而不需要重写轨迹计算。`slots.runtimeTimeline` 会收到解析后的 `title / description`、`projection.items / links / groups`、点击回调和详情打开动作;独立 Viewer 使用 `runtimeTimelineRenderer`。React 与 Vue 分别提供 `createReactRuntimeTimelineComponent()` 和 `createVueRuntimeTimelineComponent()`。如果连有效路径规则也需要替换,再传入 `runtimeTraceProjector`。
2951
+
2952
+ ```js
2953
+ createStudioShell({
2954
+ container,
2955
+ studio,
2956
+ slots: {
2957
+ runtimeTimeline({ container, projection, onItemClick, onDetailsRequest }) {
2958
+ // 使用统一投影数据挂载自己的移动端时间线。
2959
+ return () => {}
2960
+ },
2961
+ },
2962
+ })
2963
+ ```
2964
+
2965
+ 驳回/退回线路详情与节点详情是两个独立扩展点。使用 `slots.runtimeTransitionDetails`,或在独立 Viewer 中传入 `runtimeTransitionDetailsRenderer`;回调还会收到 `transition`、来源/目标节点及两个定位动作。React 与 Vue 分别提供 `createReactRuntimeTransitionDetailsComponent()` 和 `createVueRuntimeTransitionDetailsComponent()`。
2966
+
2967
+ 如果只需要替换驳回线的绘制方式,可以复用 `routeRuntimeTransition()` 的方向感知和避障结果。通过 `occupiedRoutes` 依次传入已采用的路线可避免多条驳回线共用同一通道,通过 `obstacles` 传入自定义浮层或标签矩形;自定义视觉层仍应使用返回的 `points` 做命中和动画路径,并把 `labelPoint` 作为标签锚点。`sourceSide`、`targetSide` 和 `anchorStrategy` 可用于保持自定义箭头与默认同侧锚点语义一致,`portConflicts` 可用于诊断因普通 Sequence Flow 占用端口而发生的换边或降级。
2968
+
2969
+ ```js
2970
+ createStudioShell({
2971
+ container,
2972
+ studio,
2973
+ slots: {
2974
+ runtimeTransitionDetails({ container, transition, locateSource, locateTarget, close }) {
2975
+ // 渲染引擎自己的驳回记录、意见和定位入口。
2976
+ return () => {}
2977
+ },
2978
+ },
2979
+ })
2980
+ ```
2981
+
2982
+
2983
+ <!-- SOURCE: docs/NPM-PACKAGES.md -->
2984
+
2985
+ # BPMN Nova NPM 包与发布
2986
+
2987
+ 版本:`0.3.3-preview`。BPMN Nova 只发布三个 `@bpmn-nova` 公共包,均使用 ESM、附带 TypeScript 声明、采用 Apache-2.0 License,并通过 `preview` dist-tag 发布。
2988
+
2989
+ ## 公共包
2990
+
2991
+ 一个业务项目只选择与技术栈对应的一个入口:
2992
+
2993
+ | 项目环境 | 安装命令 | 说明 |
2994
+ | --- | --- | --- |
2995
+ | Vanilla JavaScript / TypeScript | `npm install @bpmn-nova/studio@preview` | 设计、展示、审批轨迹、主题与 SVG 导出 |
2996
+ | React 18+ | `npm install @bpmn-nova/react@preview` | React 组件、Hook 与 Ref;依赖 Studio |
2997
+ | Vue 3.3+ | `npm install @bpmn-nova/vue@preview` | Vue 组件、Composable 与 Expose;依赖 Studio |
2998
+
2999
+ Core、Model、Renderer、Runtime、Theme、Properties、Node Presentation 和引擎 Profile 是源码内部 Module,不再是可独立安装或独立发布的产品。`NodeSubtitleResolver` 类型从 Studio 根入口公开,但 `node-presentation` 没有 npm 子路径或独立版本。React/Vue 各自只依赖完全相同版本的 `@bpmn-nova/studio`,并通过 peer dependency 使用宿主框架。
3000
+
3001
+ ## Studio 子路径
3002
+
3003
+ Studio 根入口重新导出稳定的完整能力;以下子路径用于让导入意图更清晰:
3004
+
3005
+ | 子路径 | 主要能力 |
3006
+ | --- | --- |
3007
+ | `@bpmn-nova/studio/designer` | 独立 `BpmnDesigner` |
3008
+ | `@bpmn-nova/studio/viewer` | `BpmnViewer` 与运行态投影 |
3009
+ | `@bpmn-nova/studio/runtime` | Runtime Snapshot 归一化与 Presentation |
3010
+ | `@bpmn-nova/studio/theme` | 实例级主题、令牌和 Tone |
3011
+ | `@bpmn-nova/studio/export-svg` | 纯 SVG Artifact 与下载能力 |
3012
+ | `@bpmn-nova/studio/flowable` | Flowable Profile |
3013
+ | `@bpmn-nova/studio/activiti` | Activiti Profile |
3014
+ | `@bpmn-nova/studio/styles.css` | Studio 自包含样式 |
3015
+
3016
+ JavaScript 不会隐式插入 CSS。Studio 项目导入 `@bpmn-nova/studio/styles.css`;React/Vue 项目分别导入适配包自己的 `styles.css`,不要重复导入内部样式。
3017
+
3018
+ ## 构建机制
3019
+
3020
+ 源码目录保留当前 Module 边界和相对引用,以便 Playground 无需安装 workspace 包即可运行。发布构建执行以下操作:
3021
+
3022
+ 1. 将 Core、Model、Renderer、Runtime、Properties、Node Presentation、Theme、引擎 Profile 和 SVG Export 复制到 `packages/studio/dist/modules`。
3023
+ 2. 将 Studio 中跨 Module 的源码引用改写为 `dist` 内部相对路径。
3024
+ 3. 生成 Studio 根入口和受支持子路径所需的 JavaScript 与声明文件。
3025
+ 4. 将 React/Vue 产物的运行时和类型导入统一改写到 `@bpmn-nova/studio`。
3026
+ 5. 按 Theme → Icons → SVG Export → Renderer → Palette → Properties → Studio 的顺序生成自包含样式。
3027
+
3028
+ `npm run docs:ai` 从根 `llms.txt` 和权威文档生成 `llms-full.txt`,并把两份 AI 文档同步到三个公开包。各包 `prepack` 先检查三个公共版本与 Adapter 的 Studio 精确依赖,再执行 `docs:ai:check`;版本漂移、文档副本缺失或过期时都会拒绝打包。
3029
+
3030
+ ```bash
3031
+ npm run build:packages
3032
+ ```
3033
+
3034
+ 只构建一个公开包时可以使用:
3035
+
3036
+ ```bash
3037
+ node scripts/build-packages.mjs --package studio
3038
+ node scripts/build-packages.mjs --package react
3039
+ node scripts/build-packages.mjs --package vue
3040
+ ```
3041
+
3042
+ `--package` 不接受内部 Module 名称。三个公开包之外的目录没有发布 manifest、NPM README 或独立版本生命周期。
3043
+
3044
+ ## 发布前验证
3045
+
3046
+ ```bash
3047
+ npm run release:check-versions
3048
+ npm run docs:ai:check
3049
+ npm run build:packages
3050
+ npm test
3051
+ git diff --check
3052
+ ```
3053
+
3054
+ 还应对源码和产物 JavaScript 执行 `node --check`,并分别检查三个 tarball:
3055
+
3056
+ ```bash
3057
+ (cd packages/studio && npm pack --dry-run --json)
3058
+ (cd packages/react && npm pack --dry-run --json)
3059
+ (cd packages/vue && npm pack --dry-run --json)
3060
+ ```
3061
+
3062
+ 每个 tarball 只应包含声明的 `dist`、类型、样式、README、`llms.txt`、`llms-full.txt`、LICENSE 和必要元数据。Studio 产物不得引用旧内部包,也不得保留 `../../*/src` 或 `.jsx` 路径;React/Vue 除 Studio 和框架 peer dependency 外不得请求其他 `@bpmn-nova/*` 包。
3063
+
3064
+ 正式发布前还应在空临时项目中同时安装三个本地 tarball,验证:
3065
+
3066
+ - Studio 根入口及全部公开子路径可以 ESM 导入。
3067
+ - Studio、React、Vue 样式子路径存在。
3068
+ - 三个包都能直接读取 `llms.txt` 和 `llms-full.txt`,不需要访问源码仓库。
3069
+ - TypeScript 能解析 Options、Runtime、Theme、Ref/Expose 等声明。
3070
+ - TypeScript 能从三个公开入口解析 Mode Event、`NodeSubtitleResolver` 与 `refreshPresentation()`,无需导入内部 Module。
3071
+ - React/Vue Adapter 可以复用 Studio,不会安装历史内部包。
3072
+
3073
+ ## 发布顺序
3074
+
3075
+ 先发布 Studio,确认 Registry 已可解析后,再发布 React 和 Vue:
3076
+
3077
+ ```bash
3078
+ (cd packages/studio && npm publish --access public --tag preview)
3079
+ (cd packages/react && npm publish --access public --tag preview)
3080
+ (cd packages/vue && npm publish --access public --tag preview)
3081
+ ```
3082
+
3083
+ 三个 manifest 都设置了 Preview 发布守卫。仍应显式使用 `--tag preview`,避免预览版本意外覆盖稳定版 `latest`。
3084
+
3085
+ 发布后核对:
3086
+
3087
+ ```bash
3088
+ npm view @bpmn-nova/studio@0.3.3-preview version
3089
+ npm view @bpmn-nova/react@0.3.3-preview version
3090
+ npm view @bpmn-nova/vue@0.3.3-preview version
3091
+ ```
3092
+
3093
+ ## Registry 清理说明
3094
+
3095
+ Registry 清理是不可逆的外部操作,不由构建脚本自动执行。只有在三个 `0.3.3-preview` 新产物发布、安装和子路径验证均成功后,才能在获得单独明确授权的情况下执行:
3096
+
3097
+ 1. 删除 Studio、React、Vue 的旧 `0.3.0-preview` 版本。
3098
+ 2. 按反向依赖顺序删除旧的内部包:Designer/Viewer/Properties 聚合层,Renderer/Provider/Model 层,最后 Runtime/Theme/Core 等基础层。
3099
+ 3. 清理后确认 NPM 搜索只保留 Studio、React、Vue。
3100
+
3101
+ 历史内部包包括 Core、BPMN Model、Runtime、Theme、SVG Export、Designer、Viewer、Flowable、Activiti、Icons、Palette、Properties Core/BPMN/Flowable/Activiti/Renderer/Aggregate,以及 SVG Renderer。准确目标必须在执行前通过 `npm view` 解析,不能依赖模糊匹配。
3102
+
3103
+ 若 NPM 因版本时间、依赖或策略拒绝删除,唯一降级方式是对整个旧包执行 deprecate,并在提示中指向三个新入口。NPM 当前的 unpublish 与 deprecate 限制以 [官方 Unpublish Policy](https://docs.npmjs.com/policies/unpublish/) 为准。
3104
+
3105
+ 本仓库只准备和验证发布、清理命令;不会在没有额外授权时执行 `npm publish`、`npm unpublish` 或 `npm deprecate`。
3106
+
3107
+ ## 部分发布失败
3108
+
3109
+ 不要覆盖已经发布的相同版本,也不要在排查阶段自动删除包。
3110
+
3111
+ 1. 使用 `npm view` 确认成功发布的包和版本。
3112
+ 2. 修复身份、网络或顺序问题。
3113
+ 3. 如果 Studio 正确而 Adapter 尚未发布,可以继续发布 React/Vue。
3114
+ 4. 如果已发布 tarball 内容错误,停止本轮发布,修复后提升 Preview 版本并重新构建三个包。
3115
+
3116
+ ## 文档
3117
+
3118
+ - [快速开始](GETTING-STARTED.md)
3119
+ - [组件参数](COMPONENTS.md)
3120
+ - [公开 API](API.md)
3121
+ - [AI 接入指南](AI-INTEGRATION.md)
3122
+ - [AI 安装执行入口](../llms.txt)
3123
+
3124
+
3125
+ <!-- SOURCE: docs/ARCHITECTURE.md -->
3126
+
3127
+ # BPMN Nova Architecture
3128
+
3129
+ **版本:v0.3.1 Preview**
3130
+
3131
+ ## 1. 总体架构
3132
+
3133
+ ```text
3134
+ BPMN Nova Studio
3135
+
3136
+ ┌────────────────────────┼────────────────────────┐
3137
+ │ │ │
3138
+ Designer Viewer Instance Viewer
3139
+ │ │ │
3140
+ └────────────────────────┴────────────────────────┘
3141
+
3142
+ Shared Renderer
3143
+
3144
+ ┌───────────────────┴───────────────────┐
3145
+ │ │
3146
+ BPMN Model Core Runtime Snapshot
3147
+
3148
+ ┌───────┴────────┐
3149
+ │ │
3150
+ BPMN XML / DI Properties System
3151
+ │ │
3152
+ │ ┌─────────┼──────────────┐
3153
+ │ │ │ │
3154
+ │ BPMN Flowable Activiti
3155
+ │ Provider Provider Provider
3156
+ │ │
3157
+ │ App Provider
3158
+
3159
+ Engine Profiles
3160
+ ```
3161
+
3162
+ ## 2. Model / Renderer 分离
3163
+
3164
+ BPMN Nova 不把 SVG/DOM Node 当成 BPMN Model。`ProcessModel / BpmnNode / BpmnEdge` 保存语义与 BPMN DI;Renderer 只根据 Model 构建 Scene。
3165
+
3166
+ 这保证 Designer / Viewer / Instance Viewer 共用同一套视觉,并允许 BPMN XML、布局、运行态分别演进。
3167
+
3168
+ 容器关系分为两类:`scopeId` 只描述 Process / SubProcess 语义作用域,`containerId` 只描述 Lane → Participant 的画布 containment。Pool/Lane 几何、成员归属、移动/缩放联动和引用清理由 Core `containment` 模块统一维护。`containerId` 是运行时画布状态,不扩展 BPMN XML;导入时由标准 `processRef` 和 DI Bounds 恢复。
3169
+
3170
+ Group 不参与 containment。它是标准 BPMN Artifact,只保存自身 Bounds 与 Category Value,不保存被框住节点的私有成员列表。
3171
+
3172
+ ## 3. Properties 子系统
3173
+
3174
+ 新增包:
3175
+
3176
+ ```text
3177
+ properties-core
3178
+ ├─ PropertiesRegistry
3179
+ ├─ Provider matching / priority
3180
+ ├─ Group positioning
3181
+ ├─ Entry read/write/validate
3182
+ └─ Data / Component registries
3183
+
3184
+ properties-bpmn
3185
+ ├─ Process
3186
+ ├─ BaseElement / Documentation
3187
+ ├─ Activity / Loop / Multi-instance
3188
+ ├─ Event definitions
3189
+ ├─ Gateway + Branch editor
3190
+ ├─ Sequence/Message/Association
3191
+ ├─ Artifact / Collaboration
3192
+ └─ XML Extension fallback
3193
+
3194
+ properties-flowable
3195
+ ├─ UserTask
3196
+ ├─ Service implementation
3197
+ ├─ Async / Retry
3198
+ ├─ Listener / Field Injection
3199
+ ├─ CallActivity
3200
+ └─ SequenceFlow Take Listener
3201
+
3202
+ properties-activiti
3203
+ └─ 与 Flowable 同层但由 Activiti namespace/能力映射
3204
+
3205
+ properties-renderer
3206
+ └─ framework-neutral DOM PropertiesPanel
3207
+
3208
+ properties
3209
+ └─ createDefaultPropertiesRegistry()
3210
+ ```
3211
+
3212
+ 依赖方向固定为:
3213
+
3214
+ ```text
3215
+ properties-core → core
3216
+ properties-bpmn → properties-core + core
3217
+ properties-flowable / activiti → properties-core
3218
+ properties-renderer → properties-core
3219
+ properties → 组合以上 Provider
3220
+ react / vue → properties + framework bridge
3221
+ ```
3222
+
3223
+ Properties Core 不依赖 React/Vue。
3224
+
3225
+ ## 3.1 SVG Export Module
3226
+
3227
+ `export-svg` 位于 Model/Runtime Presentation 与视觉宿主之间。它只接收模型、具体主题快照、Icon Registry、Runtime Appearance 和可选 SVG Renderer,生成自包含的纯 SVG Artifact;不读取 Renderer 的缩放/平移状态,也不克隆 HTML DOM。Designer、Viewer 与 Studio 只负责组装上下文和打开实例级确认预览。
3228
+
3229
+ 审批图片在导出会话中通过 `purpose: 'export'` 解析并内嵌 Data URL,普通附件只输出元数据。预览中的主题切换通过只读主题快照解析颜色,不修改宿主 ThemeController 状态。因而预览与最终下载共用同一 Artifact,同时保持 Runtime Snapshot 与展示配置解耦。
3230
+
3231
+ ## 4. 属性更新链路
3232
+
3233
+ ```text
3234
+ Entry UI change
3235
+
3236
+ registry.setValue(entry, value, context)
3237
+
3238
+ context.set(path, value)
3239
+
3240
+ Designer.updateProcess / updateNode / updateEdge / updateEdges
3241
+
3242
+ History.capture(model)
3243
+
3244
+ Model mutation + Renderer refresh
3245
+
3246
+ exportBpmn(model, engine)
3247
+ ```
3248
+
3249
+ 因此属性编辑与 Canvas modeling 使用同一 Command/History 边界,Undo/Redo 不会出现两套状态。
3250
+
3251
+ ## 5. Engine 分层
3252
+
3253
+ Engine 有两个相关但不同的概念:
3254
+
3255
+ - **Engine Profile**:负责 BPMN XML parse/export,例如 `flowable:assignee`。
3256
+ - **Engine Properties Provider**:负责 UI 上应该出现哪些 Engine 属性。
3257
+
3258
+ 例如 UserTask `负责人`:
3259
+
3260
+ ```text
3261
+ UI Entry: properties.assignee
3262
+
3263
+ Model: assignee = ${manager}
3264
+
3265
+ Flowable Profile → flowable:assignee
3266
+ Activiti Profile → activiti:assignee
3267
+ ```
3268
+
3269
+ Properties UI 不需要知道最终 Namespace 写法。
3270
+
3271
+ ## 6. Extension 模型
3272
+
3273
+ 简单企业扩展属性:
3274
+
3275
+ ```text
3276
+ ctx.extensions.set('company:approvalMode', 'all')
3277
+
3278
+ properties.extensionAttributes
3279
+
3280
+ XML attribute company:approvalMode="all"
3281
+ ```
3282
+
3283
+ 未知 `extensionElements` 在 Import 时保存 raw XML,Export 时原样保留;已被 Flowable/Activiti Profile 结构化解析的 Listener/Field/Retry 则由 Profile 重新生成,避免重复输出。
3284
+
3285
+ ## 7. Framework Adapter
3286
+
3287
+ React / Vue 均只做:
3288
+
3289
+ 1. Designer / Viewer 生命周期包装。
3290
+ 2. PropertiesPanel 生命周期包装。
3291
+ 3. 将 React/Vue Component 转换为 framework-neutral custom entry renderer。
3292
+
3293
+ Provider 本身保持纯 JS,可同时用于 Vanilla、React 和 Vue。
3294
+
3295
+ ## 8. Theme 与 Runtime Appearance
3296
+
3297
+ Theme 是独立的深 Module:`ThemeController` 对外只暴露请求模式、解析主题、局部 Palette 覆盖和状态订阅,内部负责实例根节点属性、CSS Variables、`matchMedia` 生命周期与销毁恢复。
3298
+
3299
+ ```text
3300
+ Host theme preference
3301
+
3302
+ ThemeController (one per top-level instance)
3303
+
3304
+ data-nova-theme + scoped CSS variables
3305
+
3306
+ Studio / Canvas / Viewer / Palette / Properties / overlays
3307
+ ```
3308
+
3309
+ Shell 内部组件共享 Controller,不重复注册系统主题监听。独立 Designer、Viewer、Canvas、Palette 和 Properties 各自管理 Controller。Theme 变化只修改根节点属性和变量,不触碰 Model、Runtime Presentation、选择或弹层生命周期。
3310
+
3311
+ Runtime Appearance 是运行事实到 Tone/标签/图标的展示 Adapter。Runtime Snapshot 不依赖 Theme,也不保存颜色;Renderer 仅消费解析后的安全 Tone 变量。这个 Seam 允许宿主自定义审批语义,同时保持运行数据可移植。
3312
+
3313
+ NPM 发布构建只生成 Studio、React 和 Vue 三个公开包。Studio 会把内部 Module 复制到自身 `dist/modules`,并把跨 Module 源码引用改写为包内相对路径,因此运行时不依赖任何旧的 `@bpmn-nova/*` 内部包;React/Vue 只依赖同版本 Studio。源码仍可直接运行且不依赖 workspace 安装。三个视觉入口在构建阶段按固定顺序合并样式,避免把内部 CSS 加载顺序变成调用方必须掌握的 Interface。
3314
+
3315
+ ## 9. 下一阶段架构重点
3316
+
3317
+ - Structured Moddle/Extension Descriptor API,而不仅是 raw extension fallback。
3318
+ - 真正 SubProcess containment model。
3319
+ - Boundary Event attachment model。
3320
+ - Data Input / Output / Association 完整语义。
3321
+ - Engine capability/version matrix。
3322
+ - Async Data Provider(debounce / cancellation / remote search)标准协议。