@bpmn-nova/react 0.3.2-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 CHANGED
@@ -59,6 +59,8 @@ Choose the smallest Interface that owns the required experience:
59
59
 
60
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
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
+
62
64
  ## 4. Mount a minimal integration
63
65
 
64
66
  Every branch must import its own public `styles.css` exactly once and provide a height through the complete parent layout chain.
@@ -88,7 +90,7 @@ function handleChange(model, reason, xml) {
88
90
  engine="flowable"
89
91
  mode="design"
90
92
  :allowed-edge-types="['sequenceFlow']"
91
- :allowed-modes="['design']"
93
+ :allowed-modes="['design', 'viewer']"
92
94
  theme="auto"
93
95
  @change="handleChange"
94
96
  />
@@ -113,7 +115,7 @@ export function WorkflowEditor({ initialXml, saveDraft }) {
113
115
  engine="flowable"
114
116
  mode="design"
115
117
  allowedEdgeTypes={['sequenceFlow']}
116
- allowedModes={['design']}
118
+ allowedModes={['design', 'viewer']}
117
119
  theme="auto"
118
120
  onChange={(model, reason, xml) => saveDraft(xml)}
119
121
  />
@@ -140,7 +142,7 @@ const shell = createStudioShell({
140
142
  container: document.querySelector('#workflow-studio'),
141
143
  studio,
142
144
  mode: 'design',
143
- allowedModes: ['design'],
145
+ allowedModes: ['design', 'viewer'],
144
146
  theme: 'auto',
145
147
  })
146
148
 
@@ -180,17 +182,25 @@ This step is complete when the workbench is visible, the initial XML is rendered
180
182
  <template #header-start="{ state, actions }">
181
183
  <!-- Host back action, business icon, process name, and type -->
182
184
  </template>
185
+ <template #header-actions="{ actions, mode }">
186
+ <!-- Host Validate / Save / Publish actions. Validation calls actions.validate(). -->
187
+ </template>
183
188
  </BpmnStudio>
184
189
  ```
185
190
 
186
- - Vue uses native `#header-start` / `#header` slots. React uses `headerStart` / `header` render props. Core uses `slots.headerStart` / `slots.header`.
187
- - Header Start replaces only the Nova Brand. A complete Header replacement calls the public Actions Interface for undo, redo, layout, fit, validation, BPMN import/export, and SVG export.
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()`.
188
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.
189
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`.
190
198
  - Join host business configuration by stable BPMN element ID. Saving, publishing, authorization, upload, and server transactions remain host responsibilities.
191
199
  - `theme="auto"` explicitly follows the system theme. The compatibility default remains `light`.
192
200
  - Vue and React both export standalone `BpmnPalettePanel` for a fully custom layout. Pass the same external Studio Controller to Canvas, Palette, and Properties.
193
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
+
194
204
  ## 5. Keep model ownership deterministic
195
205
 
196
206
  - Treat `xml` and `model` as external replacement inputs. Use the emitted/exported XML as the draft output.
@@ -203,9 +213,9 @@ This step is complete when edit, undo, redo, save, reload, and intentional exter
203
213
 
204
214
  ## 6. Add host constraints before business use
205
215
 
206
- 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. Configure matching Palette and Properties registries when the host needs custom labels or business fields.
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.
207
217
 
208
- Use `allowedModes` to expose only host-authorized workbench modes. Pass real Runtime data for instance mode; absence of Runtime data means no approval trace rather than demo business data.
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.
209
219
 
210
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.
211
221
 
@@ -220,10 +230,11 @@ Run the target application's existing non-destructive quality commands, includin
220
230
  3. Create, connect, edit, delete, Undo, and Redo work in design mode.
221
231
  4. Change output is persisted and reloads to the same process.
222
232
  5. Intentional external XML replacement loads once and resets history once.
223
- 6. Disallowed node types are unavailable and rejected by import/creation commands; disallowed modes are not rendered and `setMode()` leaves the current mode unchanged.
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.
224
234
  7. Theme changes update the existing instance.
225
235
  8. Unmount/remount leaves no duplicate listeners, overlays, or framework instances.
226
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.
227
238
 
228
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.
229
240
 
@@ -244,7 +255,7 @@ The installation is complete only when the package is present, the production bu
244
255
  BPMN Nova 提供可嵌入的流程设计器、只读 Viewer、运行态审批轨迹、实例级主题、纯 SVG 导出,以及 React / Vue 适配。项目使用独立的 DOM / SVG 渲染实现,不依赖 `bpmn-js`,并为 Flowable 与 Activiti 提供 XML Profile 和属性扩展。
245
256
 
246
257
  > [!IMPORTANT]
247
- > 当前版本为 `0.3.2-preview`。它适合 SDK 评估、产品集成验证和企业审批原型;公开 Interface、BPMN XML round-trip 与引擎兼容能力仍在持续稳定中,建议使用 `@preview` 安装并在生产接入前完成目标流程验证。
258
+ > 当前版本为 `0.3.3-preview`。它适合 SDK 评估、产品集成验证和企业审批原型;公开 Interface、BPMN XML round-trip 与引擎兼容能力仍在持续稳定中,建议使用 `@preview` 安装并在生产接入前完成目标流程验证。
248
259
 
249
260
  AI 或代码生成工具接入必须从 [`llms.txt`](llms.txt) 的完整安装流程开始;需要全部接口与定制上下文时再读取 [`llms-full.txt`](llms-full.txt)。这两份文件也会随三个公开 npm 包发布。
250
261
 
@@ -476,7 +487,7 @@ function onChange(model, reason, nextXml) {
476
487
  </template>
477
488
  ```
478
489
 
479
- Vue Expose 提供与 Vanilla 实例对应的 ActionsXML、视图、主题和 SVG 导出方法。
490
+ Vue Expose 提供与 Vanilla 实例对应的 Actions、校验、XML、视图、主题和 SVG 导出方法。
480
491
 
481
492
  ### 嵌入现有业务设计工作台
482
493
 
@@ -495,19 +506,66 @@ Vue Expose 提供与 Vanilla 实例对应的 Actions、XML、视图、主题和
495
506
  theme="auto"
496
507
  @change="handleChange"
497
508
  @selection-change="handleSelectionChange"
509
+ @validation="handleValidation"
498
510
  >
499
511
  <template #header-start="{ state, actions }">
500
512
  <!-- 返回、业务图标、流程名称和类型 -->
501
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>
502
519
  </BpmnStudio>
503
520
  ```
504
521
 
505
- `header-start` 只替换 Nova Brand,`header` 可替换整个 Header;完整替换时通过 `actions` 调用编辑能力。`regions` 隐藏区域后不会保留 Grid 空白,也不会重建 Canvas。React 提供等价的 `headerStart` / `header` Render Prop,Core 提供 `slots.headerStart` / `slots.header`。
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
+ ```
506
535
 
507
536
  宿主右侧属性面板应位于 Nova 外部,并以 `selection-change` / `onSelectionChange` 为状态来源、以稳定 BPMN Element ID 关联业务配置。`element-click` 不能替代选择事件。Nova 不拥有宿主的保存、发布、权限或服务端事务。
508
537
 
509
538
  `theme="auto"` 是显式启用系统主题适配;为了兼容已有接入,未传 Theme 时仍默认为 `light`。Vue 包同时提供独立的 `BpmnPalettePanel`,用于完全自定义布局。
510
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
+
511
569
  ## 审批动作、图文与附件
512
570
 
513
571
  Runtime Snapshot 使用动作描述提交、通过、驳回、加签、转办等事实。内容只保存安全文本和资源引用,不保存 URL、Base64、`Blob`、Markdown 或 HTML。
@@ -661,11 +719,13 @@ shell.openSvgExportPreview()
661
719
  | Properties | 注册 Provider、Group、Entry、Data Provider、校验和自定义组件 |
662
720
  | Icons | 覆盖内置图标或注册业务图标 |
663
721
  | Node Renderer | 自定义 Canvas / Viewer 节点内容和运行态摘要 |
722
+ | Node Subtitle Resolver | 轻量覆盖 Design / Viewer 标准节点副标题并无历史刷新 |
664
723
  | Runtime Details Renderer | 自定义 Popover / Bottom Sheet 审批详情 |
665
724
  | Runtime Timeline Renderer | 自定义移动时间线条目、动作和资源布局 |
666
725
  | Runtime Transition Renderer | 自定义驳回、退回和异常线路 |
667
726
  | SVG Renderer | 为自定义节点或时间线提供纯 SVG 导出视觉 |
668
727
  | Context Menu / Slot | 扩展设计器操作、工具区和宿主内容 |
728
+ | Studio Mode Interface | 观察并运行时控制设计、展示与审批轨迹模式 |
669
729
  | React / Vue Bridge | 通过 Props、Ref / Expose 与组合式接口接入上述能力 |
670
730
 
671
731
  完整接口和示例见[自定义指南](docs/CUSTOMIZATION.md)与[组件参数](docs/COMPONENTS.md)。
@@ -808,7 +868,7 @@ const shell = createStudioShell({
808
868
  container: document.querySelector('#studio'),
809
869
  studio,
810
870
  mode: 'design',
811
- allowedModes: ['design'],
871
+ allowedModes: ['design', 'viewer'],
812
872
  theme: 'auto',
813
873
  })
814
874
 
@@ -827,6 +887,10 @@ export function disposeWorkflow() {
827
887
 
828
888
  Shell 负责界面组合,Controller 负责模型、命令、选择和历史。外部传入的 Controller 不会因 Shell 销毁而自动销毁。
829
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
+
830
894
  ### 2.2 独立 Designer 或 Viewer
831
895
 
832
896
  独立能力仍由 Studio 提供,不要安装旧包:
@@ -870,12 +934,13 @@ npm install @bpmn-nova/react@preview
870
934
  ```
871
935
 
872
936
  ```jsx
873
- import { useRef } from 'react'
937
+ import { useRef, useState } from 'react'
874
938
  import { BpmnStudio } from '@bpmn-nova/react'
875
939
  import '@bpmn-nova/react/styles.css'
876
940
 
877
941
  export function WorkflowEditor({ initialXml }) {
878
942
  const studioRef = useRef(null)
943
+ const [mode, setMode] = useState('design')
879
944
 
880
945
  return (
881
946
  <div style={{ height: 720 }}>
@@ -883,18 +948,19 @@ export function WorkflowEditor({ initialXml }) {
883
948
  ref={studioRef}
884
949
  xml={initialXml}
885
950
  engine="flowable"
886
- mode="design"
887
- allowedModes={['design']}
951
+ mode={mode}
952
+ allowedModes={['design', 'viewer']}
888
953
  theme="auto"
889
954
  propertiesProfile="business"
890
955
  onChange={(model, reason, nextXml) => saveDraft(nextXml)}
956
+ onModeChange={(event) => setMode(event.mode)}
891
957
  />
892
958
  </div>
893
959
  )
894
960
  }
895
961
  ```
896
962
 
897
- 需要只读或审批轨迹时使用 React 包导出的 `BpmnViewer`,传入 `model`、`runtime`、`projection`、`runtimeAssetResolver` 和 `runtimeAppearance`。Ref 可访问 `exportXml()`、`exportSvg()`、`openSvgExportPreview()`、`fitView()` 和 `setTheme()`;实际可用方法以类型声明为准。
963
+ 需要只读或审批轨迹时使用 React 包导出的 `BpmnViewer`,传入 `model`、`runtime`、`projection`、`runtimeAssetResolver` 和 `runtimeAppearance`。Ref 可访问实际 `mode`、`setMode()`、`refreshPresentation()`、`exportXml()`、`exportSvg()`、`openSvgExportPreview()`、`fitView()` 和 `setTheme()`;Header Render Context 同步提供 `mode`。实际可用方法以类型声明为准。
898
964
 
899
965
  不要在 React effect 中每次渲染都重新创建 Vanilla 实例。优先使用 Adapter 组件,让属性变化更新现有实例。
900
966
 
@@ -912,6 +978,7 @@ import '@bpmn-nova/vue/styles.css'
912
978
 
913
979
  const props = defineProps({ initialXml: String })
914
980
  const studioRef = ref(null)
981
+ const mode = ref('design')
915
982
  const onChange = (model, reason, nextXml) => saveDraft(nextXml)
916
983
  </script>
917
984
 
@@ -921,23 +988,28 @@ const onChange = (model, reason, nextXml) => saveDraft(nextXml)
921
988
  ref="studioRef"
922
989
  :xml="props.initialXml"
923
990
  engine="flowable"
924
- mode="design"
925
- :allowed-modes="['design']"
991
+ v-model:mode="mode"
992
+ :allowed-modes="['design', 'viewer']"
926
993
  theme="auto"
927
994
  properties-profile="business"
928
995
  @change="onChange"
996
+ @mode-change="event => console.log(event.source)"
929
997
  />
930
998
  </div>
931
999
  </template>
932
1000
  ```
933
1001
 
934
- 需要只读或审批轨迹时使用 Vue 包导出的 `BpmnViewer`。事件使用 kebab-caseExpose 提供的实例能力以包内类型声明为准。不要在 watcher 中重复挂载组件,主题、Runtime、Projection 和 Regions 应通过响应式属性更新。
1002
+ 需要只读或审批轨迹时使用 Vue 包导出的 `BpmnViewer`。事件使用 kebab-case,`v-model:mode` 由 `update:mode` 驱动,`mode-change` 提供来源与前后值;Expose 包含 `getMode()`、`setMode()` 与 `refreshPresentation()`。不要在 watcher 中重复挂载组件,Mode、Theme、Runtime、Projection、RegionsResolver 应通过响应式属性或轻量刷新更新。
935
1003
 
936
1004
  ### 4.1 宿主工作台组合规则
937
1005
 
938
1006
  默认 `BpmnStudio` 是完整工作台。只有宿主明确拥有自己的区域时才配置 `regions`;例如 DX 复用自己的业务属性面板时使用 `regions: { right: 'hidden' }`,并把该面板放在 Nova 外部。隐藏区域不保留 Grid 轨道。
939
1007
 
940
- Vue 使用原生 `#header-start` / `#header`,React 使用 `headerStart` / `header` Render Prop。Header Start 只替换 Brand,完整 Header 通过公开 `StudioShellActions` 重建所需编辑操作。不要创建第二个 Vue App 或 React Root 挂载 Header。
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` 会原地更新默认模式按钮,移除当前模式时只回退一次。
941
1013
 
942
1014
  外部业务属性面板必须以 `selection-change` / `onSelectionChange` 为状态来源,以稳定 BPMN Element ID 关联业务配置。该事件覆盖节点、连线、多选、清空和键盘选择;`element-click` / `onElementClick` 只是点击观察事件。保存草稿、发布、权限和服务端事务属于宿主,不要写入 Nova Actions。
943
1015
 
@@ -949,7 +1021,7 @@ Vue 使用原生 `#header-start` / `#header`,React 使用 `headerStart` / `hea
949
1021
 
950
1022
  React/Vue 包重新导出创建外部 Controller、Palette、Properties、Context Menu、Icon 和 Template Registry 所需的公开函数,因此高级接入仍只需要直接安装对应 Adapter 包。
951
1023
 
952
- 生产工作流通常只支持 BPMN 子集。通过 Controller 或 `BpmnStudio` 的 `allowedNodeTypes` 与 `allowedEdgeTypes` 声明节点、连线白名单;该限制会校验初始/外部模型,并约束创建、快捷新增、模板和节点类型转换。通过 `allowedModes` 限制工作台可切换模式。实例模式没有 Runtime 时保持空运行事实,不会自动加载演示数据。
1024
+ 生产工作流通常只支持 BPMN 子集。通过 Controller 或 `BpmnStudio` 的 `allowedNodeTypes` 与 `allowedEdgeTypes` 声明节点、连线白名单;该限制会校验初始/外部模型,并约束创建、快捷新增、模板和节点类型转换。Palette 会服从节点白名单,但同一节点类型可以有多个 Preset。通过 `allowedModes` 限制工作台可切换模式。实例模式没有 Runtime 时保持空运行事实,不会自动加载演示数据。
953
1025
 
954
1026
  ## 5. Runtime Snapshot 与审批动作
955
1027
 
@@ -1136,7 +1208,7 @@ BPMN Nova 负责 BPMN 建模、XML 导入导出、只读展示、运行态审批
1136
1208
 
1137
1209
  # BPMN Nova 快速开始
1138
1210
 
1139
- 本文面向通过 NPM 集成 BPMN Nova 的应用开发者。版本 `0.3.2-preview` 要求现代浏览器;Node.js 18+ 用于构建、SSR 和开发工具。
1211
+ 本文面向通过 NPM 集成 BPMN Nova 的应用开发者。版本 `0.3.3-preview` 要求现代浏览器;Node.js 18+ 用于构建、SSR 和开发工具。
1140
1212
 
1141
1213
  ## 1. 按语言与框架选择入口
1142
1214
 
@@ -1205,7 +1277,7 @@ const shell = createStudioShell({
1205
1277
  container: document.querySelector('#studio'),
1206
1278
  studio,
1207
1279
  mode: 'design',
1208
- allowedModes: ['design'],
1280
+ allowedModes: ['design', 'viewer'],
1209
1281
  theme: 'auto',
1210
1282
  })
1211
1283
 
@@ -1226,6 +1298,38 @@ function dispose() {
1226
1298
 
1227
1299
  Controller 持有模型、命令、选择、历史与作用域;Shell 只负责组合 Canvas、Palette、Properties 和 Viewer。销毁 Shell 不会自动销毁外部传入的 Controller。
1228
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
+
1229
1333
  ## 4. XML 导入与导出
1230
1334
 
1231
1335
  ```js
@@ -1244,7 +1348,7 @@ const activitiXml = studio.exportXml('activiti')
1244
1348
  const issues = studio.model.nodes.length ? [] : ['流程没有节点']
1245
1349
  ```
1246
1350
 
1247
- 独立 `BpmnDesigner` 提供内置 `validate()`;Studio 当前通过属性规则和命令约束模型,宿主也可以添加自己的发布校验。
1351
+ 独立 `BpmnDesigner` 提供纯校验 `validate()`。完整 Studio 则通过 `actions.validate()` 或 Shell/框架公开的 `validate()` 执行 Nova 校验、更新内部状态摘要、发送校验事件并返回 issues;宿主仍可在发布前追加自己的业务规则。
1248
1352
 
1249
1353
  `allowedNodeTypes` 与 `allowedEdgeTypes` 是完整模型约束:初始模型和后续导入包含白名单外节点或连线时会拒绝,Palette、连接、快捷新增、模板及节点类型转换也使用同一约束。只有确实支持全部 Nova 图元的宿主才省略它们。
1250
1354
 
@@ -1325,12 +1429,14 @@ npm install @bpmn-nova/react@preview
1325
1429
  ```
1326
1430
 
1327
1431
  ```jsx
1328
- import { useRef } from 'react'
1432
+ import { useCallback, useRef, useState } from 'react'
1329
1433
  import { BpmnStudio } from '@bpmn-nova/react'
1330
1434
  import '@bpmn-nova/react/styles.css'
1331
1435
 
1332
1436
  export function WorkflowEditor({ initialXml }) {
1333
1437
  const ref = useRef(null)
1438
+ const [mode, setMode] = useState('design')
1439
+ const subtitleResolver = useCallback(({ node, defaultSubtitle }) => defaultSubtitle, [])
1334
1440
 
1335
1441
  return (
1336
1442
  <div style={{ height: 720 }}>
@@ -1338,12 +1444,14 @@ export function WorkflowEditor({ initialXml }) {
1338
1444
  ref={ref}
1339
1445
  xml={initialXml}
1340
1446
  engine="flowable"
1341
- mode="design"
1342
- allowedModes={['design']}
1447
+ mode={mode}
1448
+ allowedModes={['design', 'viewer']}
1449
+ nodeSubtitleResolver={subtitleResolver}
1343
1450
  propertiesProfile="business"
1344
1451
  theme="auto"
1345
1452
  onChange={(model, reason, nextXml) => saveDraft(nextXml)}
1346
1453
  onSelectionChange={(selection, element) => console.log(element)}
1454
+ onModeChange={(event) => setMode(event.mode)}
1347
1455
  />
1348
1456
  </div>
1349
1457
  )
@@ -1357,7 +1465,7 @@ const { studio, state, commands } = useBpmnStudio({ model })
1357
1465
  return <BpmnStudio studio={studio} />
1358
1466
  ```
1359
1467
 
1360
- Ref 可访问 `studio`、`shell`、`actions`、`exportXml()`、`fitView()` 和 `setTheme()`。
1468
+ Ref 可访问 `studio`、`shell`、`actions`、实际 `mode`、`setMode()`、`validate()`、`refreshPresentation()`、`exportXml()`、`fitView()` 和 `setTheme()`。`onValidation` 在 Nova 内部状态展示完成后收到校验结果;Header Render Context 同样提供响应式实际 `mode`。
1361
1469
 
1362
1470
  ## 8. Vue 3.3+
1363
1471
 
@@ -1375,6 +1483,7 @@ import '@bpmn-nova/vue/styles.css'
1375
1483
 
1376
1484
  const props = defineProps({ initialXml: String })
1377
1485
  const studioRef = ref(null)
1486
+ const mode = ref('design')
1378
1487
  const onChange = (model, reason, nextXml) => saveDraft(nextXml)
1379
1488
  </script>
1380
1489
 
@@ -1384,17 +1493,19 @@ const onChange = (model, reason, nextXml) => saveDraft(nextXml)
1384
1493
  ref="studioRef"
1385
1494
  :xml="props.initialXml"
1386
1495
  engine="flowable"
1387
- mode="design"
1388
- :allowed-modes="['design']"
1496
+ v-model:mode="mode"
1497
+ :allowed-modes="['design', 'viewer']"
1389
1498
  properties-profile="business"
1390
1499
  theme="auto"
1391
1500
  @change="onChange"
1501
+ @mode-change="event => console.log(event.source)"
1502
+ @validation="event => console.log(event.valid, event.issues)"
1392
1503
  />
1393
1504
  </div>
1394
1505
  </template>
1395
1506
  ```
1396
1507
 
1397
- Vue 事件使用 kebab-case:`change`、`selection-change`、`scope-change`、`element-click` 和 `trace-click`。Expose 提供 `getStudio()`、`getShell()`、`getActions()`、`exportXml()`、`fitView()` 与 `setTheme()`。
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` 为响应式实际状态。
1398
1509
 
1399
1510
  框架组件把 `xml`/`model` 作为外部替换输入,把 `change`/`onChange` 的 XML 作为草稿输出。完全相同的导出 XML 回写会被忽略并保留 Undo/Redo;不同 XML 表示宿主有意加载另一流程或服务器 revision,会替换模型并重置历史。React/Vue 包也重新导出了外部 Controller 和各类 Registry 的创建函数,高级接入无需直接安装 Studio。
1400
1511
 
@@ -1415,14 +1526,28 @@ Vue 事件使用 kebab-case:`change`、`selection-change`、`scope-change`、`
1415
1526
  theme="auto"
1416
1527
  @change="handleChange"
1417
1528
  @selection-change="handleSelectionChange"
1529
+ @validation="handleValidation"
1418
1530
  >
1419
1531
  <template #header-start="{ state, actions }">
1420
1532
  <!-- 返回、业务图标、流程名称、类型 -->
1421
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>
1422
1539
  </BpmnStudio>
1423
1540
  ```
1424
1541
 
1425
- Vue 的 `#header-start` 只替换 Brand,`#header` 替换完整 Header;React 使用等价的 `headerStart` / `header` Render Prop。完整 Header 中使用公开 Actions 执行编辑操作。`regions` 属性变化会原地更新区域,不重建 Canvas。
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
+ ```
1426
1551
 
1427
1552
  把宿主属性面板放在 Nova 外部,以 `selection-change` / `onSelectionChange` 作为状态来源,并用稳定 BPMN Element ID 关联业务配置。该事件覆盖节点、连线、多选、清空和键盘选择;`element-click` 不能替代选择状态。Nova 不负责保存、发布、权限或服务端事务。
1428
1553
 
@@ -1485,7 +1610,7 @@ BPMN Nova 主题应挂在组件根容器。自定义 Renderer 不要修改 `docu
1485
1610
 
1486
1611
  # BPMN Nova 组件参数
1487
1612
 
1488
- 本手册记录 `0.3.2-preview` 的公开组件参数、回调和实例方法。所有视觉组件都需要具有实际尺寸的容器,并显式导入所属包的 `styles.css`。
1613
+ 本手册记录 `0.3.3-preview` 的公开组件参数、回调和实例方法。所有视觉组件都需要具有实际尺寸的容器,并显式导入所属包的 `styles.css`。
1489
1614
 
1490
1615
  ## 按项目环境阅读
1491
1616
 
@@ -1561,16 +1686,19 @@ Controller 不创建 DOM,负责模型、命令、历史、选择与子流程
1561
1686
  | `templateRegistry` | `TemplateRegistry` | 默认 Registry | 企业模板入口 |
1562
1687
  | `contextMenuRegistry` | `ContextMenuRegistry` | 默认 Registry | 右键动作入口 |
1563
1688
  | `rendererOptions` | `DiagramRendererOptions` | `{}` | 节点与 Runtime Renderer 配置 |
1689
+ | `nodeSubtitleResolver` | `NodeSubtitleResolver` | 无 | 覆盖 Design/Viewer 标准节点副标题;不影响 Instance |
1564
1690
  | `slots` | `StudioShellSlots` | `{}` | 局部 UI 替换 |
1565
1691
  | `regions` | `StudioShellRegions` | 全部 `default` | 原地隐藏 Header、Palette、Properties 或 Statusbar;隐藏后不保留轨道 |
1566
1692
  | `layout` | `Function` | 默认三栏布局 | 完整布局替换 |
1567
1693
  | `onThemeChange` | `(state) => void` | 无 | 主题模式解析变化回调 |
1568
1694
 
1569
- 实例方法包括 `getRegions()`、`setRegions()`、`setMode()`、`setRuntime()`、`setProjection()`、`setTheme()`、`setRuntimeAppearance()`、`fitView()`、`zoomBy()`、`exportSvg()`、`openSvgExportPreview()` 和 `destroy()`。
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`。
1570
1696
 
1571
1697
  `shell.actions` 是默认 Header 与宿主自定义 Header 共用的稳定 Interface,提供 `undo()`、`redo()`、`beautify()`、`rerouteEdges()`、`fitView()`、`validate()`、`importXml()`、`exportXml()`、`exportSvg()` 和 `openSvgExportPreview()`。它不包含保存、发布、权限或文件上传等宿主业务动作。
1572
1698
 
1573
- `slots.headerStart` 只替换 Nova Brand,保留默认模式区和工具区;`slots.header` 完整替换 Header,且优先级更高。Slot Context 提供 `studio`、`shell`、`canvas`、`actions`、`getState()` 和 `subscribe()`。`regions.header = 'hidden'` 的优先级最高。`layout()` 与 `regions` 不能同时使用。
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 不默认阻止发布。
1574
1702
 
1575
1703
  ## `BpmnDesigner`
1576
1704
 
@@ -1611,13 +1739,14 @@ new BpmnDesigner(options)
1611
1739
  | `runtimeDetailsRenderer` | `RuntimeDetailsRenderer \| null` | 默认 Renderer | 替换或关闭节点详情 |
1612
1740
  | `runtimeTransitionDetailsRenderer` | `RuntimeTransitionDetailsRenderer \| null` | 默认 Renderer | 替换或关闭异常转移详情 |
1613
1741
  | `nodeRenderers` | `Record<string, NodeRenderer>` | `{}` | 按节点类型替换内部内容 |
1742
+ | `nodeSubtitleResolver` | `NodeSubtitleResolver` | 无 | 定义态标准节点副标题视觉投影 |
1614
1743
  | `onTraceClick` | `(event) => void` | 无 | 统一节点、线路、Visit、Transition 点击 |
1615
1744
  | `onElementClick` | `(payload) => void` | 无 | 基础元素点击 |
1616
1745
  | `onProjectionChange` | `({ requested, active }) => void` | 无 | 实际投影变化 |
1617
1746
  | `theme` | `NovaThemeInput` | `light` | 实例主题 |
1618
1747
  | `runtimeAppearance` | `RuntimeAppearanceOptions` | 默认映射 | Runtime 语义外观 |
1619
1748
 
1620
- 实例方法:`setModel()`、`setRuntime()`、`setProjection()`、`setDisplayOptions()`、`setTheme()`、`setRuntimeAppearance()`、`fitView()`、`openRuntimeDetails()`、`openRuntimeTransitionDetails()`、`closeRuntimeDetails()` 和 `destroy()`。
1749
+ 实例方法:`setModel()`、`setRuntime()`、`setProjection()`、`setDisplayOptions()`、`refreshPresentation()`、`setTheme()`、`setRuntimeAppearance()`、`fitView()`、`openRuntimeDetails()`、`openRuntimeTransitionDetails()`、`closeRuntimeDetails()` 和 `destroy()`。Compact Runtime Timeline 中 `refreshPresentation()` 安全 No-op。
1621
1750
 
1622
1751
  ## `BpmnCanvas`
1623
1752
 
@@ -1626,6 +1755,7 @@ new BpmnDesigner(options)
1626
1755
  | `container` | `HTMLElement` | 必填 | 画布容器 |
1627
1756
  | `studio` | `BpmnStudioController` | 必填 | Controller |
1628
1757
  | `rendererOptions` | `DiagramRendererOptions` | `{}` | Renderer 配置 |
1758
+ | `nodeSubtitleResolver` | `NodeSubtitleResolver` | 无 | 标准节点副标题 Resolver |
1629
1759
  | `interactions` | `InteractionController \| null` | `null` | Palette 拖放协议 |
1630
1760
  | `selectionToolbar` | `HTMLElement \| SelectionToolbarSlot \| null` | 默认工具栏 | 框选工具栏替换 |
1631
1761
  | `contextMenu` | `HTMLElement \| ContextMenuSlot \| null` | 默认菜单 | 右键菜单替换 |
@@ -1633,7 +1763,7 @@ new BpmnDesigner(options)
1633
1763
  | `pointerMode` | `'select' \| 'marquee' \| 'pan'` | `'select'` | 指针模式 |
1634
1764
  | `theme` | `NovaThemeInput` | `light` | 实例主题 |
1635
1765
 
1636
- 常用方法:`clientToWorld()`、`fitView()`、`fitSelection()`、`setPointerMode()`、`zoomBy()`、`actualSize()` 和 `destroy()`。
1766
+ 常用方法:`clientToWorld()`、`fitView()`、`fitSelection()`、`setPointerMode()`、`zoomBy()`、`actualSize()`、`refreshPresentation()` 和 `destroy()`。
1637
1767
 
1638
1768
  ## `PalettePanel`
1639
1769
 
@@ -1678,14 +1808,17 @@ new BpmnDesigner(options)
1678
1808
  | `allowedNodeTypes` | 创建内部 Controller 时使用的完整模型节点白名单 |
1679
1809
  | `allowedEdgeTypes` | 创建内部 Controller 时使用的完整模型连线白名单 |
1680
1810
  | `allowedModes` | Shell 显示并允许切换的工作台模式 |
1811
+ | `mode` / `onModeChange` | 受控输入与实际 Mode 事件;Header Render Context 同步提供 `mode` |
1812
+ | `nodeSubtitleResolver` | Design/Viewer 标准节点副标题 Resolver |
1681
1813
  | `contextMenuRegistry` | 自定义右键动作 Registry |
1682
1814
  | `regions` | 默认 Shell 区域显隐;属性变化原地调用 `setRegions()` |
1683
- | `headerStart` / `header` | React Node 或 Render Prop;使用 Portal 保留宿主 Context |
1815
+ | `headerStart` / `headerActions` / `header` | React Node 或 Render Prop;使用 Portal 保留宿主 Context |
1816
+ | `onValidation` | Nova 内部校验展示完成后的只读结果事件 |
1684
1817
  | `onChange(model, reason, xml)` | 模型变化 |
1685
1818
  | `onSelectionChange(selection, element)` | 选择变化 |
1686
1819
  | `onScopeChange(activeScopeId, scopePath, state)` | 子流程作用域变化 |
1687
1820
 
1688
- Ref:`studio`、`shell`、`actions`、`exportXml()`、`fitView()`、`setTheme()`。
1821
+ Ref:`studio`、`shell`、`actions`、`mode`、`getMode()`、`setMode()`、`setAllowedModes()`、`validate()`、`refreshPresentation()`、`exportXml()`、`fitView()`、`setTheme()`。
1689
1822
 
1690
1823
  `xml`/`model` 是外部替换输入。回写与最近导出完全相同的 XML 不会重复导入或清空 Undo/Redo;不同 XML 会替换模型和历史。React/Vue 包均重新导出创建外部 Controller 与 Palette、Properties、Context Menu、Icon、Template Registry 所需的公开函数。
1691
1824
 
@@ -1695,7 +1828,7 @@ Props:`model`、`xml`、`engine`、`theme`、`onChange`、`onSelectionChange`
1695
1828
 
1696
1829
  ### `BpmnViewer`
1697
1830
 
1698
- 接受除 `container/model` 外的 `ViewerOptions`,并通过 `model` 或 `xml` 输入流程。Ref:`instance`、`fitView()`、`setProjection()`、`setRuntime()`、`setDisplayOptions()`、`setTheme()`。
1831
+ 接受除 `container/model` 外的 `ViewerOptions`,并通过 `model` 或 `xml` 输入流程。Ref:`instance`、`fitView()`、`refreshPresentation()`、`setProjection()`、`setRuntime()`、`setDisplayOptions()`、`setTheme()`。
1699
1832
 
1700
1833
  ### `BpmnCanvas` / `BpmnPalettePanel` / `BpmnPropertiesPanel`
1701
1834
 
@@ -1705,11 +1838,11 @@ Props:`model`、`xml`、`engine`、`theme`、`onChange`、`onSelectionChange`
1705
1838
 
1706
1839
  Vue 组件与 React 使用同一底层接口,主要差异为:
1707
1840
 
1708
- - Studio 提供原生 `#header-start` 和 `#header` Slot,通过 Teleport 保留宿主 provide/inject、响应式状态和生命周期。
1709
- - `slotsConfig` 继续保留,作为高级 Core DOM Slot 入口;原生完整 Header、Core 完整 Header、原生 Header Start、Core Header Start 依次降级。
1710
- - 事件为 `change`、`selection-change`、`scope-change`、`element-click`、`trace-click`。
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`。
1711
1844
  - `BpmnDesigner` 发出 `change`、`selection-change`。
1712
- - Ref Expose 使用 `getStudio()`、`getShell()`、`getActions()` 或 `getInstance()`,不直接暴露可替换字段。
1845
+ - Header Slot Context 的 `mode` 是响应式实际状态。Ref Expose 使用 `getStudio()`、`getShell()`、`getActions()`、`getMode()`、`setMode()`、`setAllowedModes()`、`validate()`、`refreshPresentation()` 或 `getInstance()`,不直接暴露可替换字段。
1713
1846
 
1714
1847
  Vue 提供 `BpmnStudio`、`BpmnDesigner`、`BpmnViewer`、`BpmnCanvas`、`BpmnPalettePanel`、`BpmnPropertiesPanel` 和 `useBpmnStudio()`。独立 `BpmnPalettePanel` 复用外部 Studio;传入 Registry 时不会隐式注册 Providers,未传 Registry 时才用 Providers 创建默认 Registry。
1715
1848
 
@@ -1845,6 +1978,69 @@ const shell = createStudioShell({
1845
1978
 
1846
1979
  React/Vue Adapter 的 `BpmnStudio` 接受同名 `allowedNodeTypes`、`allowedEdgeTypes` 和 `allowedModes`。框架 `xml`/`model` 是外部替换输入;回写与当前导出完全相同的 XML 不会重新导入或清空历史,不同 XML 会替换模型并重置历史。
1847
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
+
1848
2044
  ## Studio Shell 区域、Actions 与 Header Slot
1849
2045
 
1850
2046
  默认 Shell 是完整工作台。`regions` 只控制默认布局中的区域,`hidden` 不保留行列轨道:
@@ -1866,9 +2062,13 @@ const shell = createStudioShell({
1866
2062
  studio,
1867
2063
  regions: { right: 'hidden' },
1868
2064
  slots: {
1869
- headerStart({ container, studio, shell, canvas, actions, getState, subscribe }) {
2065
+ headerStart({ container, studio, shell, canvas, actions, getState, subscribe, getMode, getAllowedModes, subscribeMode, subscribeValidation }) {
1870
2066
  // 只替换 Brand;默认模式区和编辑工具继续保留。
1871
2067
  },
2068
+ headerActions({ container, actions, getMode, subscribeMode }) {
2069
+ // 替换默认“校验 / 导入 / 导出”,挂载宿主“校验 / 保存 / 发布”。
2070
+ // 校验调用 actions.validate();保存与发布调用宿主服务。
2071
+ },
1872
2072
  },
1873
2073
  })
1874
2074
 
@@ -1876,7 +2076,7 @@ shell.getRegions()
1876
2076
  shell.setRegions({ right: 'default' })
1877
2077
  ```
1878
2078
 
1879
- `slots.header` 完整替换 Header,并高于 `slots.headerStart`;`regions.header = 'hidden'` 的优先级最高。`layout()` 与 `regions` 互斥。
2079
+ `slots.headerActions` 只替换 Header 最右侧默认动作组,可与 `slots.headerStart` 同时使用;`slots.header` 完整替换 Header,并高于两个局部 Slot;`regions.header = 'hidden'` 的优先级最高。未提供 `headerActions` 时仍显示“校验 / 导入 / 导出”。顶部不再重复渲染最佳视图,底部缩放区与 `fitView()` Interface 保持不变。`layout()` 与 `regions` 互斥。
1880
2080
 
1881
2081
  ```ts
1882
2082
  interface StudioShellActions {
@@ -1893,9 +2093,9 @@ interface StudioShellActions {
1893
2093
  }
1894
2094
  ```
1895
2095
 
1896
- 默认 Header 与宿主 Header 都调用 `shell.actions`。Actions 保留 Controller 的返回值与异常语义,但不处理文件选择、保存草稿、发布、权限或宿主 API。
2096
+ 默认 Header 与宿主 Header 都调用 `shell.actions`。保存草稿、发布、权限和宿主服务不进入 Actions;宿主通过 `headerActions` 闭包调用自己的业务方法,并使用 `actions.exportXml()` 获取草稿。
1897
2097
 
1898
- Vue `BpmnStudio` 提供 `#header-start` / `#header` 与 `getActions()`;React 提供 `headerStart` / `header` Render Prop 与 Ref `actions`。框架插槽的 `state` 会随 Controller 事件更新,Undo/Redo 禁用状态可以直接绑定 `state.canUndo/canRedo`。
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`。
1899
2099
 
1900
2100
  ## Group 与 Pool / Lane
1901
2101
 
@@ -2458,7 +2658,7 @@ shell.setRegions({ right: 'default' })
2458
2658
 
2459
2659
  `header`、`left`、`right`、`footer` 均接受 `default | hidden`。隐藏区域不占 Grid 轨道、不显示边框,也不保留可聚焦控件。Viewer/Instance 的只读布局仍会隐藏 Palette;区域配置只能进一步隐藏。
2460
2660
 
2461
- 局部内容替换使用 `slots.left`、`slots.right`、`slots.headerStart`、`slots.header` 和 `slots.footer`。`headerStart` 只替换默认 Brand,保留模式区和工具区;`header` 替换完整 Header,优先级高于 `headerStart`。回调收到 `studio`、`shell`、`actions`、`getState()`、`subscribe()`、注册表、交互控制器和 Canvas。
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。
2462
2662
 
2463
2663
  ```js
2464
2664
  createStudioShell({
@@ -2468,6 +2668,9 @@ createStudioShell({
2468
2668
  headerStart({ container, actions, getState, subscribe }) {
2469
2669
  // 挂载返回入口、业务图标、流程名称和类型;返回卸载函数。
2470
2670
  },
2671
+ headerActions({ container, actions, getMode, subscribeMode }) {
2672
+ // 挂载校验 / 保存 / 发布;校验调用 actions.validate(),其余调用宿主服务。
2673
+ },
2471
2674
  left({ container, studio, interactions, canvas }) {
2472
2675
  // 挂载任意 Vanilla / React / Vue UI,返回卸载函数。
2473
2676
  },
@@ -2478,9 +2681,19 @@ createStudioShell({
2478
2681
  });
2479
2682
  ```
2480
2683
 
2481
- 默认 Header 和完整自定义 Header 共用 `shell.actions`:撤销/重做、布局/布线、最佳视图、结构校验、BPMN XML 导入导出及 SVG 导出。Actions 不拥有宿主的保存草稿、发布、权限、文件选择或服务端事务。
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
+ ```
2482
2695
 
2483
- React 使用 `headerStart` / `header` Render Prop 和 Portal;Vue 使用 `#header-start` / `#header` Named Slot 和 Teleport。两者都在宿主应用树内渲染,不创建独立 React Root 或 Vue App,因此 Context、provide/inject、响应式状态和生命周期继续有效。Vue 原有 `slotsConfig`、React 原有 `slots` 仍作为 Core DOM 接口保留。
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 接口保留。
2484
2697
 
2485
2698
  若整个三栏结构都要自定义,使用 `layout({ container, mount, ...services })`。`mount.canvas()`、`mount.palette()`、`mount.properties()` 可分别挂载官方实现,也可只使用 Controller 自行实现。`layout()` 与 `regions` 是互斥 Interface,同时传入会抛出配置错误。
2486
2699
 
@@ -2570,6 +2783,32 @@ const canvasRef = ref(null)
2570
2783
 
2571
2784
  创建外部 Controller 的一方负责在页面卸载时销毁它;Canvas 和 Palette Adapter 不销毁外部 Controller。
2572
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
+
2573
2812
  HTML 节点内容不会被复制到导出的 SVG。需要保持品牌视觉时,应为同一节点类型提供纯 SVG 适配;Renderer 接收 `SVGGElement`、具体主题快照与 Icon Registry,只能创建 SVG Primitive,不能使用 `foreignObject`:
2574
2813
 
2575
2814
  ```js
@@ -2745,7 +2984,7 @@ createStudioShell({
2745
2984
 
2746
2985
  # BPMN Nova NPM 包与发布
2747
2986
 
2748
- 版本:`0.3.2-preview`。BPMN Nova 只发布三个 `@bpmn-nova` 公共包,均使用 ESM、附带 TypeScript 声明、采用 Apache-2.0 License,并通过 `preview` dist-tag 发布。
2987
+ 版本:`0.3.3-preview`。BPMN Nova 只发布三个 `@bpmn-nova` 公共包,均使用 ESM、附带 TypeScript 声明、采用 Apache-2.0 License,并通过 `preview` dist-tag 发布。
2749
2988
 
2750
2989
  ## 公共包
2751
2990
 
@@ -2757,7 +2996,7 @@ createStudioShell({
2757
2996
  | React 18+ | `npm install @bpmn-nova/react@preview` | React 组件、Hook 与 Ref;依赖 Studio |
2758
2997
  | Vue 3.3+ | `npm install @bpmn-nova/vue@preview` | Vue 组件、Composable 与 Expose;依赖 Studio |
2759
2998
 
2760
- Core、Model、Renderer、Runtime、Theme、Properties 和引擎 Profile 是源码内部 Module,不再是可独立安装或独立发布的产品。React/Vue 各自只依赖完全相同版本的 `@bpmn-nova/studio`,并通过 peer dependency 使用宿主框架。
2999
+ Core、Model、Renderer、Runtime、Theme、Properties、Node Presentation 和引擎 Profile 是源码内部 Module,不再是可独立安装或独立发布的产品。`NodeSubtitleResolver` 类型从 Studio 根入口公开,但 `node-presentation` 没有 npm 子路径或独立版本。React/Vue 各自只依赖完全相同版本的 `@bpmn-nova/studio`,并通过 peer dependency 使用宿主框架。
2761
3000
 
2762
3001
  ## Studio 子路径
2763
3002
 
@@ -2780,7 +3019,7 @@ JavaScript 不会隐式插入 CSS。Studio 项目导入 `@bpmn-nova/studio/style
2780
3019
 
2781
3020
  源码目录保留当前 Module 边界和相对引用,以便 Playground 无需安装 workspace 包即可运行。发布构建执行以下操作:
2782
3021
 
2783
- 1. 将 Core、Model、Renderer、Runtime、Properties、Theme、引擎 Profile 和 SVG Export 复制到 `packages/studio/dist/modules`。
3022
+ 1. 将 Core、Model、Renderer、Runtime、Properties、Node Presentation、Theme、引擎 Profile 和 SVG Export 复制到 `packages/studio/dist/modules`。
2784
3023
  2. 将 Studio 中跨 Module 的源码引用改写为 `dist` 内部相对路径。
2785
3024
  3. 生成 Studio 根入口和受支持子路径所需的 JavaScript 与声明文件。
2786
3025
  4. 将 React/Vue 产物的运行时和类型导入统一改写到 `@bpmn-nova/studio`。
@@ -2828,6 +3067,7 @@ git diff --check
2828
3067
  - Studio、React、Vue 样式子路径存在。
2829
3068
  - 三个包都能直接读取 `llms.txt` 和 `llms-full.txt`,不需要访问源码仓库。
2830
3069
  - TypeScript 能解析 Options、Runtime、Theme、Ref/Expose 等声明。
3070
+ - TypeScript 能从三个公开入口解析 Mode Event、`NodeSubtitleResolver` 与 `refreshPresentation()`,无需导入内部 Module。
2831
3071
  - React/Vue Adapter 可以复用 Studio,不会安装历史内部包。
2832
3072
 
2833
3073
  ## 发布顺序
@@ -2845,14 +3085,14 @@ git diff --check
2845
3085
  发布后核对:
2846
3086
 
2847
3087
  ```bash
2848
- npm view @bpmn-nova/studio@0.3.2-preview version
2849
- npm view @bpmn-nova/react@0.3.2-preview version
2850
- npm view @bpmn-nova/vue@0.3.2-preview version
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
2851
3091
  ```
2852
3092
 
2853
3093
  ## Registry 清理说明
2854
3094
 
2855
- Registry 清理是不可逆的外部操作,不由构建脚本自动执行。只有在三个 `0.3.2-preview` 新产物发布、安装和子路径验证均成功后,才能在获得单独明确授权的情况下执行:
3095
+ Registry 清理是不可逆的外部操作,不由构建脚本自动执行。只有在三个 `0.3.3-preview` 新产物发布、安装和子路径验证均成功后,才能在获得单独明确授权的情况下执行:
2856
3096
 
2857
3097
  1. 删除 Studio、React、Vue 的旧 `0.3.0-preview` 版本。
2858
3098
  2. 按反向依赖顺序删除旧的内部包:Designer/Viewer/Properties 聚合层,Renderer/Provider/Model 层,最后 Runtime/Theme/Core 等基础层。