@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/README.md +56 -3
- package/dist/index.d.ts +29 -2
- package/dist/index.js +47 -5
- package/dist/styles.css +4 -3
- package/llms-full.txt +298 -58
- package/llms.txt +19 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ BPMN Nova 的 React 18+ Adapter,提供 Studio、Designer、Viewer、审批轨
|
|
|
4
4
|
|
|
5
5
|
> **English summary:** React 18+ components and refs for BPMN Nova process design, viewing, approval traces, properties, themes, and SVG export.
|
|
6
6
|
|
|
7
|
-
> 当前版本为 `0.3.
|
|
7
|
+
> 当前版本为 `0.3.3-preview`。React 与 React DOM 由宿主工程提供,本包不会替应用选择或升级框架版本。
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -50,7 +50,7 @@ export function WorkflowEditor({ initialXml }) {
|
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Ref 提供 `actions`、`exportXml()`、`fitView()`、`setTheme()`、`exportSvg()` 和 `openSvgExportPreview()`。
|
|
53
|
+
Ref 提供 `actions`、`validate()`、`exportXml()`、`fitView()`、`setTheme()`、`exportSvg()` 和 `openSvgExportPreview()`。
|
|
54
54
|
|
|
55
55
|
`xml`/`model` 是外部替换输入,`onChange` 的 XML 是草稿输出。宿主回写完全相同的导出 XML 不会重新导入或清空撤销历史;切换服务器 revision 或流程时传入不同 XML 会替换模型。Activiti 宿主应显式使用 `engine="activiti"`。
|
|
56
56
|
|
|
@@ -72,17 +72,70 @@ Ref 提供 `actions`、`exportXml()`、`fitView()`、`setTheme()`、`exportSvg()
|
|
|
72
72
|
headerStart={({ state, actions }) => (
|
|
73
73
|
<HostProcessIdentity onBack={back} />
|
|
74
74
|
)}
|
|
75
|
+
headerActions={({ actions, mode }) => (
|
|
76
|
+
<HostWorkflowActions
|
|
77
|
+
disabled={mode !== 'design'}
|
|
78
|
+
onValidate={() => actions.validate()}
|
|
79
|
+
onSave={() => saveDraft(actions.exportXml())}
|
|
80
|
+
onPublish={() => publishProcess(actions)}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
75
83
|
onChange={handleChange}
|
|
84
|
+
onValidation={(event) => handleValidation(event)}
|
|
76
85
|
onSelectionChange={(selection, element) => {
|
|
77
86
|
setSelectedElement(selection ? element : null)
|
|
78
87
|
}}
|
|
79
88
|
/>
|
|
80
89
|
```
|
|
81
90
|
|
|
82
|
-
`headerStart` 只替换 Nova Brand
|
|
91
|
+
`headerStart` 只替换 Nova Brand;`headerActions` 只替换默认“校验 / 导入 / 导出”动作组,适合组合“校验 / 保存 / 发布”;`header` 可完整替换 Header。它们都通过 React Portal 在当前组件树内渲染,保留 Context 和生命周期。默认 Header 的最佳视图只保留在底部缩放区。
|
|
92
|
+
|
|
93
|
+
`actions.validate()` 以 `toolbar` 来源先更新 Nova 默认状态栏,再调用 `onValidation` 并返回 issues;Ref `validate()` 使用 `api` 来源。事件的 `valid` 仅由 error 决定,warning 会保留在 `issues` 中但不默认阻止发布。保存与发布仍由宿主负责。
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
async function publishProcess(actions) {
|
|
97
|
+
const issues = actions.validate()
|
|
98
|
+
if (issues.some((issue) => issue.level === 'error')) return
|
|
99
|
+
await publishXml(actions.exportXml())
|
|
100
|
+
}
|
|
101
|
+
```
|
|
83
102
|
|
|
84
103
|
宿主属性面板以 `onSelectionChange` 为选择状态来源;它覆盖节点、连线、多选、清空与键盘选择。`onElementClick` 只是点击观察事件,不能代替选择状态。宿主通过稳定 BPMN Element ID 关联业务配置,并自行负责保存、发布、权限与服务端事务。
|
|
85
104
|
|
|
105
|
+
## Mode 双向同步与副标题刷新
|
|
106
|
+
|
|
107
|
+
```jsx
|
|
108
|
+
import { useCallback, useRef, useState } from 'react'
|
|
109
|
+
|
|
110
|
+
const [mode, setMode] = useState('design')
|
|
111
|
+
const summaries = useRef(new Map())
|
|
112
|
+
const resolveSubtitle = useCallback(
|
|
113
|
+
({ node }) => summaries.current.has(node.id)
|
|
114
|
+
? summaries.current.get(node.id)
|
|
115
|
+
: undefined,
|
|
116
|
+
[],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
<BpmnStudio
|
|
120
|
+
ref={studioRef}
|
|
121
|
+
xml={xml}
|
|
122
|
+
mode={mode}
|
|
123
|
+
allowedModes={['design', 'viewer']}
|
|
124
|
+
onModeChange={(event) => setMode(event.mode)}
|
|
125
|
+
nodeSubtitleResolver={resolveSubtitle}
|
|
126
|
+
headerStart={({ mode: actualMode }) => (
|
|
127
|
+
<HostHeader mode={actualMode} />
|
|
128
|
+
)}
|
|
129
|
+
/>
|
|
130
|
+
|
|
131
|
+
function updateArchiveSummary() {
|
|
132
|
+
summaries.current.set('ServiceTask_Archive', '归档到采购系统')
|
|
133
|
+
studioRef.current?.refreshPresentation()
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Ref 的 `mode` Getter 与 `setMode()` 始终反映 Shell 实际状态。Resolver 返回 `undefined` 保留默认值、`null` 移除副标题行、字符串替换显示;函数引用变化会自动刷新,稳定闭包内部数据变化需显式调用 `refreshPresentation()`。Resolver 只影响 Design/Viewer 标准卡片和 SVG,不覆盖 Instance Runtime Presentation,也不修改 XML、History、Selection 或 Viewport。
|
|
138
|
+
|
|
86
139
|
## 只读展示与审批轨迹
|
|
87
140
|
|
|
88
141
|
```jsx
|
package/dist/index.d.ts
CHANGED
|
@@ -29,8 +29,13 @@ import type {
|
|
|
29
29
|
BpmnStudioShell,
|
|
30
30
|
ContextMenuRegistry,
|
|
31
31
|
InteractionController,
|
|
32
|
+
NodeSubtitleResolver,
|
|
32
33
|
StudioCommands,
|
|
33
34
|
StudioShellActions,
|
|
35
|
+
StudioMode,
|
|
36
|
+
StudioModeChangeEvent,
|
|
37
|
+
StudioValidationEvent,
|
|
38
|
+
StudioValidationIssue,
|
|
34
39
|
StudioShellOptions,
|
|
35
40
|
StudioShellRegions,
|
|
36
41
|
StudioShellSlots,
|
|
@@ -67,6 +72,13 @@ export {
|
|
|
67
72
|
registerBpmnProperties,
|
|
68
73
|
registerFlowableProperties,
|
|
69
74
|
} from '@bpmn-nova/studio'
|
|
75
|
+
export type {
|
|
76
|
+
NodeSubtitleResolver,
|
|
77
|
+
NodeSubtitleResolverContext,
|
|
78
|
+
StudioValidationEvent,
|
|
79
|
+
StudioValidationIssue,
|
|
80
|
+
StudioValidationSource,
|
|
81
|
+
} from '@bpmn-nova/studio'
|
|
70
82
|
|
|
71
83
|
export interface BpmnNovaVisualProps {
|
|
72
84
|
theme?: NovaThemeInput
|
|
@@ -96,11 +108,13 @@ export interface BpmnCanvasProps extends BpmnNovaVisualProps {
|
|
|
96
108
|
iconRegistry?: IconRegistry
|
|
97
109
|
nodeRenderers?: Record<string, Function>
|
|
98
110
|
nodeRenderer?: Function
|
|
111
|
+
nodeSubtitleResolver?: NodeSubtitleResolver
|
|
99
112
|
svgExport?: ViewerOptions['svgExport']
|
|
100
113
|
}
|
|
101
114
|
export interface BpmnCanvasHandle {
|
|
102
115
|
readonly instance: CanvasInstance | null
|
|
103
116
|
fitView(padding?: number, options?: Record<string, unknown>): void
|
|
117
|
+
refreshPresentation(): void
|
|
104
118
|
clientToWorld(x: number, y: number): { x: number; y: number } | undefined
|
|
105
119
|
setTheme(theme: NovaThemeInput): NovaThemeState | undefined
|
|
106
120
|
exportSvg(options?: SvgExportOptions): Promise<SvgExportArtifact> | undefined
|
|
@@ -138,14 +152,16 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
138
152
|
contextMenuRegistry?: ContextMenuRegistry
|
|
139
153
|
nodeRenderers?: Record<string, Function>
|
|
140
154
|
nodeRenderer?: Function
|
|
155
|
+
nodeSubtitleResolver?: NodeSubtitleResolver
|
|
141
156
|
slots?: StudioShellSlots
|
|
142
157
|
layout?: StudioShellOptions['layout']
|
|
143
158
|
regions?: StudioShellRegions
|
|
144
159
|
headerStart?: BpmnStudioRenderSlot
|
|
160
|
+
headerActions?: BpmnStudioRenderSlot
|
|
145
161
|
header?: BpmnStudioRenderSlot
|
|
146
162
|
runtime?: ProcessInstanceSnapshot | null
|
|
147
|
-
mode?:
|
|
148
|
-
allowedModes?:
|
|
163
|
+
mode?: StudioMode
|
|
164
|
+
allowedModes?: readonly StudioMode[]
|
|
149
165
|
projection?: ViewerProjection
|
|
150
166
|
responsive?: boolean
|
|
151
167
|
projectionOptions?: Array<{ value: ViewerProjection; label: string }>
|
|
@@ -169,18 +185,28 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
169
185
|
onChange?: (model: ProcessModel, reason: string, xml: string) => void
|
|
170
186
|
onSelectionChange?: (selection: ElementSelection | null, element: StudioState['selectedElement']) => void
|
|
171
187
|
onScopeChange?: (activeScopeId: string, scopePath: StudioState['scopePath'], state: StudioState) => void
|
|
188
|
+
onModeChange?: (event: StudioModeChangeEvent) => void
|
|
189
|
+
onValidation?: (event: StudioValidationEvent) => void
|
|
172
190
|
}
|
|
173
191
|
export interface BpmnStudioRenderContext {
|
|
174
192
|
readonly studio: BpmnStudioController
|
|
175
193
|
readonly shell: BpmnStudioShell
|
|
176
194
|
readonly actions: StudioShellActions
|
|
177
195
|
readonly state: Readonly<StudioState>
|
|
196
|
+
readonly mode: StudioMode
|
|
178
197
|
}
|
|
179
198
|
export type BpmnStudioRenderSlot = ReactNode | ((context: BpmnStudioRenderContext) => ReactNode)
|
|
180
199
|
export interface BpmnStudioHandle {
|
|
181
200
|
readonly studio: BpmnStudioController
|
|
182
201
|
readonly shell: BpmnStudioShell | null
|
|
183
202
|
readonly actions: StudioShellActions | null
|
|
203
|
+
readonly mode: StudioMode | undefined
|
|
204
|
+
getMode(): StudioMode | undefined
|
|
205
|
+
getAllowedModes(): readonly StudioMode[]
|
|
206
|
+
setMode(mode: StudioMode): boolean
|
|
207
|
+
setAllowedModes(modes: readonly StudioMode[]): readonly StudioMode[]
|
|
208
|
+
refreshPresentation(): void
|
|
209
|
+
validate(): StudioValidationIssue[]
|
|
184
210
|
exportXml(engine?: EngineId): string
|
|
185
211
|
fitView(): void
|
|
186
212
|
setTheme(theme: NovaThemeInput): NovaThemeState | undefined
|
|
@@ -212,6 +238,7 @@ export interface BpmnViewerProps extends BpmnNovaVisualProps, BpmnModelProps, Om
|
|
|
212
238
|
export interface BpmnViewerHandle {
|
|
213
239
|
readonly instance: ViewerInstance | null
|
|
214
240
|
fitView(): void
|
|
241
|
+
refreshPresentation(): void
|
|
215
242
|
setProjection(projection: ViewerProjection): void
|
|
216
243
|
setRuntime(runtime: ProcessInstanceSnapshot | null): void
|
|
217
244
|
setDisplayOptions(options: Parameters<ViewerInstance['setDisplayOptions']>[0]): void
|
package/dist/index.js
CHANGED
|
@@ -62,9 +62,13 @@ export function useBpmnStudio(options = {}) {
|
|
|
62
62
|
export const BpmnCanvas = forwardRef(function BpmnCanvas(props, ref) {
|
|
63
63
|
const hostRef = useRef(null);
|
|
64
64
|
const instanceRef = useRef(null);
|
|
65
|
+
const subtitleResolverRef = useRef(props.nodeSubtitleResolver);
|
|
66
|
+
subtitleResolverRef.current = props.nodeSubtitleResolver;
|
|
67
|
+
const stableSubtitleResolverRef = useRef((context) => subtitleResolverRef.current?.(context));
|
|
65
68
|
useImperativeHandle(ref, () => ({
|
|
66
69
|
get instance() { return instanceRef.current; },
|
|
67
70
|
fitView(padding, options) { instanceRef.current?.fitView(padding, options); },
|
|
71
|
+
refreshPresentation() { instanceRef.current?.refreshPresentation(); },
|
|
68
72
|
clientToWorld(x, y) { return instanceRef.current?.clientToWorld(x, y); },
|
|
69
73
|
setTheme(theme) { return instanceRef.current?.setTheme(theme); },
|
|
70
74
|
exportSvg(options) { return instanceRef.current?.exportSvg(options); },
|
|
@@ -73,12 +77,13 @@ export const BpmnCanvas = forwardRef(function BpmnCanvas(props, ref) {
|
|
|
73
77
|
useEffect(() => {
|
|
74
78
|
if (!hostRef.current || !props.studio) return undefined;
|
|
75
79
|
const interactions = props.interactions || createInteractionController({ studio: props.studio, templates: props.templates || createTemplateRegistry() });
|
|
76
|
-
const instance = new CoreCanvas({ container: hostRef.current, studio: props.studio, interactions, theme: props.theme, onThemeChange: props.onThemeChange, rendererOptions: { iconRegistry: props.iconRegistry, nodeRenderers: props.nodeRenderers, nodeRenderer: props.nodeRenderer, svgExport: props.svgExport } });
|
|
80
|
+
const instance = new CoreCanvas({ container: hostRef.current, studio: props.studio, interactions, theme: props.theme, onThemeChange: props.onThemeChange, nodeSubtitleResolver: stableSubtitleResolverRef.current, rendererOptions: { iconRegistry: props.iconRegistry, nodeRenderers: props.nodeRenderers, nodeRenderer: props.nodeRenderer, svgExport: props.svgExport } });
|
|
77
81
|
instanceRef.current = instance;
|
|
78
82
|
requestAnimationFrame(() => instance.fitView());
|
|
79
83
|
return () => { instance.destroy(); instanceRef.current = null; };
|
|
80
84
|
}, [props.studio]);
|
|
81
85
|
useEffect(() => { if (props.theme !== undefined) instanceRef.current?.setTheme(props.theme); }, [props.theme]);
|
|
86
|
+
useEffect(() => { instanceRef.current?.refreshPresentation(); }, [props.nodeSubtitleResolver]);
|
|
82
87
|
return React.createElement('div', { ref: hostRef, className: props.className, style: { width: '100%', height: '100%', minHeight: 320, ...props.style } });
|
|
83
88
|
});
|
|
84
89
|
|
|
@@ -105,15 +110,27 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
105
110
|
const mountedModelSyncRef = useRef(false);
|
|
106
111
|
const [headerPortal, setHeaderPortal] = useState(null);
|
|
107
112
|
const [headerStartPortal, setHeaderStartPortal] = useState(null);
|
|
113
|
+
const [headerActionsPortal, setHeaderActionsPortal] = useState(null);
|
|
108
114
|
const callbacksRef = useRef({});
|
|
109
115
|
callbacksRef.current = props;
|
|
110
116
|
if (!props.studio && !ownedStudioRef.current) ownedStudioRef.current = createStudioController({ model: resolveModel(props), propertiesProfile: props.propertiesProfile, allowedNodeTypes: props.allowedNodeTypes, allowedEdgeTypes: props.allowedEdgeTypes });
|
|
111
117
|
const studio = props.studio || ownedStudioRef.current;
|
|
112
118
|
const [studioState, setStudioState] = useState(() => studio.getState());
|
|
119
|
+
const [actualMode, setActualMode] = useState(() => props.mode || props.allowedModes?.[0] || 'design');
|
|
120
|
+
const subtitleResolverRef = useRef(props.nodeSubtitleResolver);
|
|
121
|
+
subtitleResolverRef.current = props.nodeSubtitleResolver;
|
|
122
|
+
const stableSubtitleResolverRef = useRef((context) => subtitleResolverRef.current?.(context));
|
|
113
123
|
useImperativeHandle(ref, () => ({
|
|
114
124
|
studio,
|
|
115
125
|
get shell() { return shellRef.current; },
|
|
116
126
|
get actions() { return shellRef.current?.actions || null; },
|
|
127
|
+
get mode() { return shellRef.current?.getMode(); },
|
|
128
|
+
getMode: () => shellRef.current?.getMode(),
|
|
129
|
+
getAllowedModes: () => shellRef.current?.getAllowedModes() || [],
|
|
130
|
+
setMode: (mode) => shellRef.current?.setMode(mode) || false,
|
|
131
|
+
setAllowedModes: (modes) => shellRef.current?.setAllowedModes(modes) || [],
|
|
132
|
+
refreshPresentation: () => shellRef.current?.refreshPresentation(),
|
|
133
|
+
validate: () => shellRef.current?.validate() || [],
|
|
117
134
|
exportXml: (engine) => studio.exportXml(engine),
|
|
118
135
|
exportSvg: (options) => shellRef.current?.exportSvg(options),
|
|
119
136
|
openSvgExportPreview: (options) => shellRef.current?.openSvgExportPreview(options),
|
|
@@ -122,6 +139,7 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
122
139
|
}), [studio]);
|
|
123
140
|
const hasNativeHeader = props.header !== undefined && props.header !== null;
|
|
124
141
|
const hasNativeHeaderStart = props.headerStart !== undefined && props.headerStart !== null;
|
|
142
|
+
const hasNativeHeaderActions = props.headerActions !== undefined && props.headerActions !== null;
|
|
125
143
|
useEffect(() => {
|
|
126
144
|
if (!hostRef.current) return undefined;
|
|
127
145
|
setStudioState(studio.getState());
|
|
@@ -132,7 +150,10 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
132
150
|
};
|
|
133
151
|
const shellSlots = { ...(props.slots || {}) };
|
|
134
152
|
if (hasNativeHeader) shellSlots.header = createPortalSlot(setHeaderPortal);
|
|
135
|
-
else if (!shellSlots.header
|
|
153
|
+
else if (!shellSlots.header) {
|
|
154
|
+
if (hasNativeHeaderStart) shellSlots.headerStart = createPortalSlot(setHeaderStartPortal);
|
|
155
|
+
if (hasNativeHeaderActions) shellSlots.headerActions = createPortalSlot(setHeaderActionsPortal);
|
|
156
|
+
}
|
|
136
157
|
const shell = new CoreStudioShell({
|
|
137
158
|
container: hostRef.current,
|
|
138
159
|
studio,
|
|
@@ -144,6 +165,7 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
144
165
|
rendererOptions: {
|
|
145
166
|
nodeRenderers: props.nodeRenderers,
|
|
146
167
|
nodeRenderer: props.nodeRenderer,
|
|
168
|
+
nodeSubtitleResolver: stableSubtitleResolverRef.current,
|
|
147
169
|
runtimePresenter: props.runtimePresenter,
|
|
148
170
|
runtimeTraceProjector: props.runtimeTraceProjector,
|
|
149
171
|
runtimeAssetResolver: props.runtimeAssetResolver,
|
|
@@ -176,14 +198,20 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
176
198
|
onThemeChange: (state) => callbacksRef.current.onThemeChange?.(state),
|
|
177
199
|
});
|
|
178
200
|
shellRef.current = shell;
|
|
201
|
+
setActualMode(shell.getMode());
|
|
202
|
+
const offMode = shell.subscribeMode((event) => {
|
|
203
|
+
setActualMode(event.mode);
|
|
204
|
+
callbacksRef.current.onModeChange?.(event);
|
|
205
|
+
});
|
|
206
|
+
const offValidation = shell.subscribeValidation((event) => callbacksRef.current.onValidation?.(event));
|
|
179
207
|
const off = studio.subscribe((event) => {
|
|
180
208
|
setStudioState(event.state);
|
|
181
209
|
if (event.type === 'modelChanged') callbacksRef.current.onChange?.(studio.model, event.reason, studio.exportXml());
|
|
182
210
|
if (event.type === 'selectionChanged') callbacksRef.current.onSelectionChange?.(studio.selection, studio.getSelectedElement());
|
|
183
211
|
if (event.type === 'scopeChanged') callbacksRef.current.onScopeChange?.(event.activeScopeId, event.scopePath, studio.getState());
|
|
184
212
|
});
|
|
185
|
-
return () => { off(); shell.destroy(); shellRef.current = null; };
|
|
186
|
-
}, [studio, hasNativeHeader, hasNativeHeaderStart]);
|
|
213
|
+
return () => { offMode(); offValidation(); off(); shell.destroy(); shellRef.current = null; };
|
|
214
|
+
}, [studio, hasNativeHeader, hasNativeHeaderStart, hasNativeHeaderActions]);
|
|
187
215
|
useEffect(() => () => {
|
|
188
216
|
if (!props.studio && ownedStudioRef.current === studio) {
|
|
189
217
|
studio.destroy();
|
|
@@ -199,11 +227,17 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
199
227
|
else if (props.xml && props.xml !== studio.exportXml(props.engine)) studio.importXml(props.xml, props.engine);
|
|
200
228
|
}, [studio, props.model, props.xml, props.engine]);
|
|
201
229
|
useEffect(() => { if (props.runtime !== undefined) shellRef.current?.setRuntime(props.runtime); }, [props.runtime]);
|
|
202
|
-
useEffect(() => {
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
const shell = shellRef.current;
|
|
232
|
+
if (!shell) return;
|
|
233
|
+
if (props.allowedModes !== undefined) shell.setAllowedModes(props.allowedModes);
|
|
234
|
+
if (props.mode) shell.setMode(props.mode);
|
|
235
|
+
}, [props.allowedModes, props.mode]);
|
|
203
236
|
useEffect(() => { if (props.projection) shellRef.current?.setProjection(props.projection); }, [props.projection]);
|
|
204
237
|
useEffect(() => { if (props.regions !== undefined) shellRef.current?.setRegions(props.regions); }, [props.regions]);
|
|
205
238
|
useEffect(() => { if (props.theme !== undefined) shellRef.current?.setTheme(props.theme); }, [props.theme]);
|
|
206
239
|
useEffect(() => { if (props.runtimeAppearance !== undefined) shellRef.current?.setRuntimeAppearance(props.runtimeAppearance); }, [props.runtimeAppearance]);
|
|
240
|
+
useEffect(() => { shellRef.current?.refreshPresentation(); }, [props.nodeSubtitleResolver]);
|
|
207
241
|
useEffect(() => {
|
|
208
242
|
const shell = shellRef.current;
|
|
209
243
|
if (!shell) return;
|
|
@@ -219,6 +253,7 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
219
253
|
shell: portal.context.shell,
|
|
220
254
|
actions: portal.context.actions,
|
|
221
255
|
state: studioState,
|
|
256
|
+
mode: actualMode,
|
|
222
257
|
};
|
|
223
258
|
return createPortal(typeof slot === 'function' ? slot(context) : slot, portal.target, key);
|
|
224
259
|
};
|
|
@@ -226,6 +261,7 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
226
261
|
React.createElement('div', { ref: hostRef, className: props.className, style: { width: '100%', height: '100%', minHeight: 520, ...props.style } }),
|
|
227
262
|
renderSlot(props.header, headerPortal, 'bpmn-nova-header'),
|
|
228
263
|
renderSlot(props.headerStart, headerStartPortal, 'bpmn-nova-header-start'),
|
|
264
|
+
renderSlot(props.headerActions, headerActionsPortal, 'bpmn-nova-header-actions'),
|
|
229
265
|
);
|
|
230
266
|
});
|
|
231
267
|
|
|
@@ -290,6 +326,9 @@ export const BpmnViewer = forwardRef(function BpmnViewer(props, ref) {
|
|
|
290
326
|
const hostRef = useRef(null);
|
|
291
327
|
const instanceRef = useRef(null);
|
|
292
328
|
const callbacksRef = useRef({});
|
|
329
|
+
const subtitleResolverRef = useRef(props.nodeSubtitleResolver);
|
|
330
|
+
subtitleResolverRef.current = props.nodeSubtitleResolver;
|
|
331
|
+
const stableSubtitleResolverRef = useRef((context) => subtitleResolverRef.current?.(context));
|
|
293
332
|
callbacksRef.current = {
|
|
294
333
|
onTraceClick: props.onTraceClick,
|
|
295
334
|
onElementClick: props.onElementClick,
|
|
@@ -303,6 +342,7 @@ export const BpmnViewer = forwardRef(function BpmnViewer(props, ref) {
|
|
|
303
342
|
useImperativeHandle(ref, () => ({
|
|
304
343
|
get instance() { return instanceRef.current; },
|
|
305
344
|
fitView() { instanceRef.current?.fitView(); },
|
|
345
|
+
refreshPresentation() { instanceRef.current?.refreshPresentation(); },
|
|
306
346
|
setProjection(value) { instanceRef.current?.setProjection(value); },
|
|
307
347
|
setRuntime(value) { instanceRef.current?.setRuntime(value); },
|
|
308
348
|
setDisplayOptions(value) { instanceRef.current?.setDisplayOptions(value); },
|
|
@@ -325,6 +365,7 @@ export const BpmnViewer = forwardRef(function BpmnViewer(props, ref) {
|
|
|
325
365
|
iconRegistry: props.iconRegistry,
|
|
326
366
|
nodeRenderers: props.nodeRenderers,
|
|
327
367
|
nodeRenderer: props.nodeRenderer,
|
|
368
|
+
nodeSubtitleResolver: stableSubtitleResolverRef.current,
|
|
328
369
|
runtimePresenter: props.runtimePresenter,
|
|
329
370
|
runtimeTraceProjector: props.runtimeTraceProjector,
|
|
330
371
|
runtimeAssetResolver: props.runtimeAssetResolver,
|
|
@@ -354,6 +395,7 @@ export const BpmnViewer = forwardRef(function BpmnViewer(props, ref) {
|
|
|
354
395
|
useEffect(() => { if (instanceRef.current && props.projection) instanceRef.current.setProjection(props.projection); }, [props.projection]);
|
|
355
396
|
useEffect(() => { if (props.theme !== undefined) instanceRef.current?.setTheme(props.theme); }, [props.theme]);
|
|
356
397
|
useEffect(() => { if (props.runtimeAppearance !== undefined) instanceRef.current?.setRuntimeAppearance(props.runtimeAppearance); }, [props.runtimeAppearance]);
|
|
398
|
+
useEffect(() => { instanceRef.current?.refreshPresentation(); }, [props.nodeSubtitleResolver]);
|
|
357
399
|
useEffect(() => { instanceRef.current?.setDisplayOptions({ timeline: props.timeline, runtimeDetails: props.runtimeDetails, runtimeTraceOptions: props.runtimeTraceOptions, runtimeAssetResolver: props.runtimeAssetResolver }); }, [props.timeline, props.runtimeDetails, props.runtimeTraceOptions, props.runtimeAssetResolver]);
|
|
358
400
|
useEffect(() => {
|
|
359
401
|
if (!instanceRef.current) return;
|
package/dist/styles.css
CHANGED
|
@@ -511,8 +511,8 @@
|
|
|
511
511
|
.mb-node-event,
|
|
512
512
|
.mb-node-boundary { display: grid; place-items: center; z-index: 7; }
|
|
513
513
|
.mb-event-shape {
|
|
514
|
-
width: 46px;
|
|
515
|
-
height: 46px;
|
|
514
|
+
width: var(--mb-event-shape-size, 46px);
|
|
515
|
+
height: var(--mb-event-shape-size, 46px);
|
|
516
516
|
box-sizing: border-box;
|
|
517
517
|
display: grid;
|
|
518
518
|
place-items: center;
|
|
@@ -523,7 +523,7 @@
|
|
|
523
523
|
box-shadow: var(--nova-shadow-sm);
|
|
524
524
|
position: relative;
|
|
525
525
|
}
|
|
526
|
-
.mb-node-boundary .mb-event-shape { width: 40px; height: 40px; border-width: 2px; }
|
|
526
|
+
.mb-node-boundary .mb-event-shape { width: var(--mb-event-shape-size, 40px); height: var(--mb-event-shape-size, 40px); border-width: 2px; }
|
|
527
527
|
.mb-event-shape[data-stage="intermediate"],
|
|
528
528
|
.mb-node-boundary .mb-event-shape { box-shadow: inset 0 0 0 3px var(--nova-color-surface), inset 0 0 0 4.5px var(--nova-color-text-muted, #8796a9), var(--nova-shadow-sm); }
|
|
529
529
|
.mb-event-shape[data-stage="end"] { border-width: 4px; border-color: var(--nova-color-text-secondary); }
|
|
@@ -1159,6 +1159,7 @@ textarea.property-control { resize: vertical; }
|
|
|
1159
1159
|
.nova-studio-projection-switch button:hover { color: var(--nova-tone-primary-foreground, #4d59ca); background: var(--nova-color-primary-soft, #f4f5ff); }.nova-studio-projection-switch button.is-active { color: var(--nova-tone-primary-foreground, #4654c8); background: var(--nova-color-primary-soft, #eef0ff); font-weight: var(--nova-font-weight-semibold); }
|
|
1160
1160
|
.nova-studio-tools { min-width: 0; overflow: visible; display: flex; align-items: center; justify-content: flex-end; gap: 5px; }
|
|
1161
1161
|
.nova-studio-tools::-webkit-scrollbar { display: none; }
|
|
1162
|
+
.nova-studio-header-actions { min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 5px; }
|
|
1162
1163
|
.nova-studio-tool-divider { flex: none; width: 1px; height: 20px; margin: 0 2px; background: var(--nova-color-divider, #e4e8f0); }
|
|
1163
1164
|
.nova-studio-tool { flex: none; min-height: 32px; border: 1px solid var(--nova-color-border, #e2e6ef); border-radius: 7px; padding: 0 10px; display: inline-flex; align-items: center; justify-content: center; gap: 5px; color: var(--nova-color-text-secondary, #4c586d); background: var(--nova-color-surface, #fff); font-size: var(--nova-font-size-sm); line-height: 18px; white-space: nowrap; cursor: pointer; }
|
|
1164
1165
|
.nova-studio-tool:focus { outline: none; }.nova-studio-tool:focus-visible { outline: 2px solid var(--nova-color-focus-ring, #aeb7ff); outline-offset: 1px; }
|