@bpmn-nova/react 0.3.1-preview → 0.3.2-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 +44 -6
- package/dist/index.d.ts +50 -5
- package/dist/index.js +78 -9
- package/dist/styles.css +8 -0
- package/llms-full.txt +3082 -0
- package/llms.txt +225 -0
- package/package.json +5 -3
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.2-preview`。React 与 React DOM 由宿主工程提供,本包不会替应用选择或升级框架版本。
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -20,6 +20,8 @@ import '@bpmn-nova/react/styles.css'
|
|
|
20
20
|
|
|
21
21
|
只安装本包;`@bpmn-nova/studio` 会作为内部依赖自动解析。容器必须有明确高度。
|
|
22
22
|
|
|
23
|
+
使用代码生成助手或 IDE Agent 接入时,从随包的 `llms.txt` 开始;需要完整接口和定制资料时再读取 `llms-full.txt`。两份文件都包含在 npm tarball 中,不依赖源码仓库可见性。
|
|
24
|
+
|
|
23
25
|
## 流程设计
|
|
24
26
|
|
|
25
27
|
```jsx
|
|
@@ -27,16 +29,17 @@ import { useRef } from 'react'
|
|
|
27
29
|
import { BpmnStudio } from '@bpmn-nova/react'
|
|
28
30
|
import '@bpmn-nova/react/styles.css'
|
|
29
31
|
|
|
30
|
-
export function WorkflowEditor({
|
|
32
|
+
export function WorkflowEditor({ initialXml }) {
|
|
31
33
|
const studioRef = useRef(null)
|
|
32
34
|
|
|
33
35
|
return (
|
|
34
36
|
<div style={{ height: 720 }}>
|
|
35
37
|
<BpmnStudio
|
|
36
38
|
ref={studioRef}
|
|
37
|
-
xml={
|
|
39
|
+
xml={initialXml}
|
|
38
40
|
engine="flowable"
|
|
39
41
|
mode="design"
|
|
42
|
+
allowedModes={['design']}
|
|
40
43
|
theme="auto"
|
|
41
44
|
onChange={(model, reason, nextXml) => {
|
|
42
45
|
console.log(reason, nextXml)
|
|
@@ -47,7 +50,38 @@ export function WorkflowEditor({ xml }) {
|
|
|
47
50
|
}
|
|
48
51
|
```
|
|
49
52
|
|
|
50
|
-
Ref 提供 `exportXml()`、`fitView()`、`setTheme()`、`exportSvg()` 和 `openSvgExportPreview()`。
|
|
53
|
+
Ref 提供 `actions`、`exportXml()`、`fitView()`、`setTheme()`、`exportSvg()` 和 `openSvgExportPreview()`。
|
|
54
|
+
|
|
55
|
+
`xml`/`model` 是外部替换输入,`onChange` 的 XML 是草稿输出。宿主回写完全相同的导出 XML 不会重新导入或清空撤销历史;切换服务器 revision 或流程时传入不同 XML 会替换模型。Activiti 宿主应显式使用 `engine="activiti"`。
|
|
56
|
+
|
|
57
|
+
## 组合宿主头部与业务属性面板
|
|
58
|
+
|
|
59
|
+
默认 `BpmnStudio` 是完整工作台。通过 `regions={{ right: 'hidden' }}` 隐藏 Nova Properties 后,Canvas 会获得该轨道的全部宽度;宿主属性面板应作为外部兄弟区域渲染。
|
|
60
|
+
|
|
61
|
+
```jsx
|
|
62
|
+
<BpmnStudio
|
|
63
|
+
ref={studioRef}
|
|
64
|
+
xml={xml}
|
|
65
|
+
engine="activiti"
|
|
66
|
+
mode="design"
|
|
67
|
+
allowedModes={['design']}
|
|
68
|
+
allowedNodeTypes={supportedNodeTypes}
|
|
69
|
+
allowedEdgeTypes={['sequenceFlow']}
|
|
70
|
+
regions={{ right: 'hidden' }}
|
|
71
|
+
theme="auto"
|
|
72
|
+
headerStart={({ state, actions }) => (
|
|
73
|
+
<HostProcessIdentity onBack={back} />
|
|
74
|
+
)}
|
|
75
|
+
onChange={handleChange}
|
|
76
|
+
onSelectionChange={(selection, element) => {
|
|
77
|
+
setSelectedElement(selection ? element : null)
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`headerStart` 只替换 Nova Brand,默认模式区与工具区保留;`header` 可完整替换 Header。两者都通过 React Portal 在当前组件树内渲染,保留 Context 和生命周期。完整替换时使用 Render Context 的 `state.canUndo/canRedo` 和 `actions` 重建所需编辑按钮。
|
|
83
|
+
|
|
84
|
+
宿主属性面板以 `onSelectionChange` 为选择状态来源;它覆盖节点、连线、多选、清空与键盘选择。`onElementClick` 只是点击观察事件,不能代替选择状态。宿主通过稳定 BPMN Element ID 关联业务配置,并自行负责保存、发布、权限与服务端事务。
|
|
51
85
|
|
|
52
86
|
## 只读展示与审批轨迹
|
|
53
87
|
|
|
@@ -161,6 +195,10 @@ studioRef.current?.openSvgExportPreview({
|
|
|
161
195
|
- `createReactRuntimeDetailsComponent`
|
|
162
196
|
- `createReactRuntimeTransitionDetailsComponent`
|
|
163
197
|
- `createReactRuntimeTimelineComponent`
|
|
198
|
+
- `createStudioController`、`createEmptyProcess`、`importBpmn`、`exportBpmn`
|
|
199
|
+
- Palette、Properties、Context Menu、Icon 和 Template Registry 的公开创建函数
|
|
200
|
+
|
|
201
|
+
完整工作台可通过 `allowedNodeTypes` 与 `allowedEdgeTypes` 限制宿主支持的图元类型,通过 `allowedModes` 限制可切换的工作台模式。图元限制同时作用于 XML 导入、Palette、连接、快捷新增、模板和节点类型转换。
|
|
164
202
|
|
|
165
203
|
## 常见问题
|
|
166
204
|
|
|
@@ -172,11 +210,11 @@ studioRef.current?.openSvgExportPreview({
|
|
|
172
210
|
|
|
173
211
|
## 文档与 AI
|
|
174
212
|
|
|
213
|
+
- npm 包内 `llms.txt`:可执行安装入口
|
|
214
|
+
- npm 包内 `llms-full.txt`:完整 AI 上下文
|
|
175
215
|
- [完整项目能力](https://github.com/daxiangme/bpmn-nova)
|
|
176
216
|
- [React/Vue 组件说明](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/COMPONENTS.md)
|
|
177
217
|
- [公开 Interface](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/API.md)
|
|
178
|
-
- [AI 接入入口](https://raw.githubusercontent.com/daxiangme/bpmn-nova/dev/llms.txt)
|
|
179
|
-
- [AI 完整上下文](https://raw.githubusercontent.com/daxiangme/bpmn-nova/dev/llms-full.txt)
|
|
180
218
|
|
|
181
219
|
## License
|
|
182
220
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,19 @@ import type {
|
|
|
2
2
|
ComponentType,
|
|
3
3
|
CSSProperties,
|
|
4
4
|
ForwardRefExoticComponent,
|
|
5
|
+
ReactNode,
|
|
5
6
|
RefAttributes,
|
|
6
7
|
} from 'react'
|
|
7
|
-
import type { BpmnEdge, BpmnNode, ElementSelection, EngineId, LayoutOptions, ProcessModel } from '@bpmn-nova/studio'
|
|
8
|
+
import type { BpmnEdge, BpmnNode, EdgeType, ElementSelection, EngineId, LayoutOptions, NodeType, ProcessModel } from '@bpmn-nova/studio'
|
|
8
9
|
import type { BpmnDesigner as DesignerInstance } from '@bpmn-nova/studio/designer'
|
|
9
10
|
import type { IconRegistry } from '@bpmn-nova/studio'
|
|
10
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
PaletteItemRenderer,
|
|
13
|
+
PalettePanel as PalettePanelInstance,
|
|
14
|
+
PaletteProvider,
|
|
15
|
+
PaletteRegistry,
|
|
16
|
+
PaletteSectionRenderer,
|
|
17
|
+
} from '@bpmn-nova/studio'
|
|
11
18
|
import type {
|
|
12
19
|
PropertiesContext,
|
|
13
20
|
PropertiesProvider,
|
|
@@ -23,7 +30,9 @@ import type {
|
|
|
23
30
|
ContextMenuRegistry,
|
|
24
31
|
InteractionController,
|
|
25
32
|
StudioCommands,
|
|
33
|
+
StudioShellActions,
|
|
26
34
|
StudioShellOptions,
|
|
35
|
+
StudioShellRegions,
|
|
27
36
|
StudioShellSlots,
|
|
28
37
|
StudioState,
|
|
29
38
|
TemplateRegistry,
|
|
@@ -39,6 +48,26 @@ import type {
|
|
|
39
48
|
ViewerProjection,
|
|
40
49
|
} from '@bpmn-nova/studio/viewer'
|
|
41
50
|
|
|
51
|
+
export {
|
|
52
|
+
createContextMenuRegistry,
|
|
53
|
+
createDefaultContextMenuRegistry,
|
|
54
|
+
createDefaultIconRegistry,
|
|
55
|
+
createDefaultPaletteRegistry,
|
|
56
|
+
createDefaultPropertiesRegistry,
|
|
57
|
+
createEmptyProcess,
|
|
58
|
+
createPaletteRegistry,
|
|
59
|
+
createPropertiesRegistry,
|
|
60
|
+
createStudioController,
|
|
61
|
+
createTemplateRegistry,
|
|
62
|
+
exportBpmn,
|
|
63
|
+
importBpmn,
|
|
64
|
+
propertyEntry,
|
|
65
|
+
propertyGroup,
|
|
66
|
+
registerActivitiProperties,
|
|
67
|
+
registerBpmnProperties,
|
|
68
|
+
registerFlowableProperties,
|
|
69
|
+
} from '@bpmn-nova/studio'
|
|
70
|
+
|
|
42
71
|
export interface BpmnNovaVisualProps {
|
|
43
72
|
theme?: NovaThemeInput
|
|
44
73
|
runtimeAppearance?: RuntimeAppearanceOptions
|
|
@@ -54,6 +83,8 @@ export interface BpmnModelProps {
|
|
|
54
83
|
export interface UseBpmnStudioOptions extends BpmnModelProps {
|
|
55
84
|
studio?: BpmnStudioController
|
|
56
85
|
propertiesProfile?: 'business' | 'developer'
|
|
86
|
+
allowedNodeTypes?: NodeType[]
|
|
87
|
+
allowedEdgeTypes?: EdgeType[]
|
|
57
88
|
}
|
|
58
89
|
export interface UseBpmnStudioResult { studio: BpmnStudioController; state: StudioState; commands: StudioCommands }
|
|
59
90
|
export function useBpmnStudio(options?: UseBpmnStudioOptions): UseBpmnStudioResult
|
|
@@ -85,11 +116,11 @@ export interface BpmnPalettePanelProps extends BpmnNovaVisualProps {
|
|
|
85
116
|
providers?: PaletteProvider[]
|
|
86
117
|
canvas?: CanvasInstance | null
|
|
87
118
|
iconRegistry?: IconRegistry
|
|
88
|
-
renderItem?:
|
|
89
|
-
renderSection?:
|
|
119
|
+
renderItem?: PaletteItemRenderer
|
|
120
|
+
renderSection?: PaletteSectionRenderer
|
|
90
121
|
}
|
|
91
122
|
export interface BpmnPalettePanelHandle {
|
|
92
|
-
readonly panel:
|
|
123
|
+
readonly panel: PalettePanelInstance | null
|
|
93
124
|
render(): void
|
|
94
125
|
setTheme(theme: NovaThemeInput): NovaThemeState | undefined
|
|
95
126
|
}
|
|
@@ -98,6 +129,8 @@ export const BpmnPalettePanel: ForwardRefExoticComponent<BpmnPalettePanelProps &
|
|
|
98
129
|
export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
99
130
|
studio?: BpmnStudioController
|
|
100
131
|
propertiesProfile?: 'business' | 'developer'
|
|
132
|
+
allowedNodeTypes?: NodeType[]
|
|
133
|
+
allowedEdgeTypes?: EdgeType[]
|
|
101
134
|
iconRegistry?: IconRegistry
|
|
102
135
|
paletteRegistry?: PaletteRegistry
|
|
103
136
|
propertiesRegistry?: PropertiesRegistry
|
|
@@ -107,8 +140,12 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
107
140
|
nodeRenderer?: Function
|
|
108
141
|
slots?: StudioShellSlots
|
|
109
142
|
layout?: StudioShellOptions['layout']
|
|
143
|
+
regions?: StudioShellRegions
|
|
144
|
+
headerStart?: BpmnStudioRenderSlot
|
|
145
|
+
header?: BpmnStudioRenderSlot
|
|
110
146
|
runtime?: ProcessInstanceSnapshot | null
|
|
111
147
|
mode?: 'design' | 'viewer' | 'instance'
|
|
148
|
+
allowedModes?: Array<'design' | 'viewer' | 'instance'>
|
|
112
149
|
projection?: ViewerProjection
|
|
113
150
|
responsive?: boolean
|
|
114
151
|
projectionOptions?: Array<{ value: ViewerProjection; label: string }>
|
|
@@ -133,9 +170,17 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
133
170
|
onSelectionChange?: (selection: ElementSelection | null, element: StudioState['selectedElement']) => void
|
|
134
171
|
onScopeChange?: (activeScopeId: string, scopePath: StudioState['scopePath'], state: StudioState) => void
|
|
135
172
|
}
|
|
173
|
+
export interface BpmnStudioRenderContext {
|
|
174
|
+
readonly studio: BpmnStudioController
|
|
175
|
+
readonly shell: BpmnStudioShell
|
|
176
|
+
readonly actions: StudioShellActions
|
|
177
|
+
readonly state: Readonly<StudioState>
|
|
178
|
+
}
|
|
179
|
+
export type BpmnStudioRenderSlot = ReactNode | ((context: BpmnStudioRenderContext) => ReactNode)
|
|
136
180
|
export interface BpmnStudioHandle {
|
|
137
181
|
readonly studio: BpmnStudioController
|
|
138
182
|
readonly shell: BpmnStudioShell | null
|
|
183
|
+
readonly actions: StudioShellActions | null
|
|
139
184
|
exportXml(engine?: EngineId): string
|
|
140
185
|
fitView(): void
|
|
141
186
|
setTheme(theme: NovaThemeInput): NovaThemeState | undefined
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
2
3
|
import { BpmnDesigner as CoreDesigner } from '@bpmn-nova/studio/designer';
|
|
3
4
|
import { BpmnViewer as CoreViewer } from '@bpmn-nova/studio/viewer';
|
|
4
5
|
import { importBpmn, exportBpmn } from '@bpmn-nova/studio';
|
|
@@ -6,6 +7,26 @@ import { createEmptyProcess } from '@bpmn-nova/studio';
|
|
|
6
7
|
import { BpmnCanvas as CoreCanvas, BpmnStudioShell as CoreStudioShell, createInteractionController, createStudioController, createTemplateRegistry } from '@bpmn-nova/studio';
|
|
7
8
|
import { createDefaultPaletteRegistry, PalettePanel as CorePalettePanel } from '@bpmn-nova/studio';
|
|
8
9
|
|
|
10
|
+
export {
|
|
11
|
+
createContextMenuRegistry,
|
|
12
|
+
createDefaultContextMenuRegistry,
|
|
13
|
+
createDefaultIconRegistry,
|
|
14
|
+
createDefaultPaletteRegistry,
|
|
15
|
+
createDefaultPropertiesRegistry,
|
|
16
|
+
createEmptyProcess,
|
|
17
|
+
createPaletteRegistry,
|
|
18
|
+
createPropertiesRegistry,
|
|
19
|
+
createStudioController,
|
|
20
|
+
createTemplateRegistry,
|
|
21
|
+
exportBpmn,
|
|
22
|
+
importBpmn,
|
|
23
|
+
propertyEntry,
|
|
24
|
+
propertyGroup,
|
|
25
|
+
registerActivitiProperties,
|
|
26
|
+
registerBpmnProperties,
|
|
27
|
+
registerFlowableProperties,
|
|
28
|
+
} from '@bpmn-nova/studio';
|
|
29
|
+
|
|
9
30
|
export function createReactRuntimeDetailsComponent(Component) {
|
|
10
31
|
return ({ container, ...context }) => {
|
|
11
32
|
const root = createRoot(container);
|
|
@@ -30,7 +51,7 @@ function resolveModel({ model, xml, engine = 'flowable' }) {
|
|
|
30
51
|
|
|
31
52
|
export function useBpmnStudio(options = {}) {
|
|
32
53
|
const ownedRef = useRef(null);
|
|
33
|
-
if (!options.studio && !ownedRef.current) ownedRef.current = createStudioController({ model: resolveModel(options), propertiesProfile: options.propertiesProfile });
|
|
54
|
+
if (!options.studio && !ownedRef.current) ownedRef.current = createStudioController({ model: resolveModel(options), propertiesProfile: options.propertiesProfile, allowedNodeTypes: options.allowedNodeTypes, allowedEdgeTypes: options.allowedEdgeTypes });
|
|
34
55
|
const studio = options.studio || ownedRef.current;
|
|
35
56
|
const [state, setState] = useState(() => studio.getState());
|
|
36
57
|
useEffect(() => studio.subscribe((event) => setState(event.state)), [studio]);
|
|
@@ -72,7 +93,7 @@ export const BpmnPalettePanel = forwardRef(function BpmnPalettePanel(props, ref)
|
|
|
72
93
|
const panel = new CorePalettePanel({ container: hostRef.current, registry, studio: props.studio, interactions, canvas: props.canvas, iconRegistry: props.iconRegistry, renderItem: props.renderItem, renderSection: props.renderSection, theme: props.theme, onThemeChange: props.onThemeChange });
|
|
73
94
|
panelRef.current = panel;
|
|
74
95
|
return () => { panel.destroy(); panelRef.current = null; };
|
|
75
|
-
}, [props.studio, props.registry, props.interactions, props.canvas]);
|
|
96
|
+
}, [props.studio, props.registry, props.interactions, props.templates, props.canvas, props.providers, props.iconRegistry, props.renderItem, props.renderSection, props.onThemeChange]);
|
|
76
97
|
useEffect(() => { if (props.theme !== undefined) panelRef.current?.setTheme(props.theme); }, [props.theme]);
|
|
77
98
|
return React.createElement('div', { ref: hostRef, className: props.className, style: { width: '100%', height: '100%', overflow: 'auto', ...props.style } });
|
|
78
99
|
});
|
|
@@ -81,21 +102,37 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
81
102
|
const hostRef = useRef(null);
|
|
82
103
|
const ownedStudioRef = useRef(null);
|
|
83
104
|
const shellRef = useRef(null);
|
|
105
|
+
const mountedModelSyncRef = useRef(false);
|
|
106
|
+
const [headerPortal, setHeaderPortal] = useState(null);
|
|
107
|
+
const [headerStartPortal, setHeaderStartPortal] = useState(null);
|
|
84
108
|
const callbacksRef = useRef({});
|
|
85
109
|
callbacksRef.current = props;
|
|
86
|
-
if (!props.studio && !ownedStudioRef.current) ownedStudioRef.current = createStudioController({ model: resolveModel(props), propertiesProfile: props.propertiesProfile });
|
|
110
|
+
if (!props.studio && !ownedStudioRef.current) ownedStudioRef.current = createStudioController({ model: resolveModel(props), propertiesProfile: props.propertiesProfile, allowedNodeTypes: props.allowedNodeTypes, allowedEdgeTypes: props.allowedEdgeTypes });
|
|
87
111
|
const studio = props.studio || ownedStudioRef.current;
|
|
112
|
+
const [studioState, setStudioState] = useState(() => studio.getState());
|
|
88
113
|
useImperativeHandle(ref, () => ({
|
|
89
114
|
studio,
|
|
90
115
|
get shell() { return shellRef.current; },
|
|
116
|
+
get actions() { return shellRef.current?.actions || null; },
|
|
91
117
|
exportXml: (engine) => studio.exportXml(engine),
|
|
92
118
|
exportSvg: (options) => shellRef.current?.exportSvg(options),
|
|
93
119
|
openSvgExportPreview: (options) => shellRef.current?.openSvgExportPreview(options),
|
|
94
|
-
fitView: () => shellRef.current?.
|
|
120
|
+
fitView: () => shellRef.current?.fitView(),
|
|
95
121
|
setTheme: (theme) => shellRef.current?.setTheme(theme),
|
|
96
122
|
}), [studio]);
|
|
123
|
+
const hasNativeHeader = props.header !== undefined && props.header !== null;
|
|
124
|
+
const hasNativeHeaderStart = props.headerStart !== undefined && props.headerStart !== null;
|
|
97
125
|
useEffect(() => {
|
|
98
126
|
if (!hostRef.current) return undefined;
|
|
127
|
+
setStudioState(studio.getState());
|
|
128
|
+
const createPortalSlot = (setPortal) => (context) => {
|
|
129
|
+
const portal = { target: context.container, context };
|
|
130
|
+
setPortal(portal);
|
|
131
|
+
return () => setPortal((current) => current?.target === context.container ? null : current);
|
|
132
|
+
};
|
|
133
|
+
const shellSlots = { ...(props.slots || {}) };
|
|
134
|
+
if (hasNativeHeader) shellSlots.header = createPortalSlot(setHeaderPortal);
|
|
135
|
+
else if (!shellSlots.header && hasNativeHeaderStart) shellSlots.headerStart = createPortalSlot(setHeaderStartPortal);
|
|
99
136
|
const shell = new CoreStudioShell({
|
|
100
137
|
container: hostRef.current,
|
|
101
138
|
studio,
|
|
@@ -103,6 +140,7 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
103
140
|
paletteRegistry: props.paletteRegistry,
|
|
104
141
|
propertiesRegistry: props.propertiesRegistry,
|
|
105
142
|
templateRegistry: props.templateRegistry,
|
|
143
|
+
contextMenuRegistry: props.contextMenuRegistry,
|
|
106
144
|
rendererOptions: {
|
|
107
145
|
nodeRenderers: props.nodeRenderers,
|
|
108
146
|
nodeRenderer: props.nodeRenderer,
|
|
@@ -121,10 +159,12 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
121
159
|
onElementClick: (payload) => callbacksRef.current.onElementClick?.(payload),
|
|
122
160
|
onTraceClick: (payload) => callbacksRef.current.onTraceClick?.(payload),
|
|
123
161
|
},
|
|
124
|
-
slots:
|
|
162
|
+
slots: shellSlots,
|
|
125
163
|
layout: props.layout,
|
|
164
|
+
regions: props.regions,
|
|
126
165
|
runtime: props.runtime,
|
|
127
166
|
mode: props.mode,
|
|
167
|
+
allowedModes: props.allowedModes,
|
|
128
168
|
projection: props.projection,
|
|
129
169
|
responsive: props.responsive,
|
|
130
170
|
projectionOptions: props.projectionOptions,
|
|
@@ -137,16 +177,31 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
137
177
|
});
|
|
138
178
|
shellRef.current = shell;
|
|
139
179
|
const off = studio.subscribe((event) => {
|
|
180
|
+
setStudioState(event.state);
|
|
140
181
|
if (event.type === 'modelChanged') callbacksRef.current.onChange?.(studio.model, event.reason, studio.exportXml());
|
|
141
182
|
if (event.type === 'selectionChanged') callbacksRef.current.onSelectionChange?.(studio.selection, studio.getSelectedElement());
|
|
142
183
|
if (event.type === 'scopeChanged') callbacksRef.current.onScopeChange?.(event.activeScopeId, event.scopePath, studio.getState());
|
|
143
184
|
});
|
|
144
|
-
return () => { off(); shell.destroy(); shellRef.current = null;
|
|
145
|
-
}, [studio]);
|
|
146
|
-
useEffect(() =>
|
|
185
|
+
return () => { off(); shell.destroy(); shellRef.current = null; };
|
|
186
|
+
}, [studio, hasNativeHeader, hasNativeHeaderStart]);
|
|
187
|
+
useEffect(() => () => {
|
|
188
|
+
if (!props.studio && ownedStudioRef.current === studio) {
|
|
189
|
+
studio.destroy();
|
|
190
|
+
ownedStudioRef.current = null;
|
|
191
|
+
}
|
|
192
|
+
}, [studio, Boolean(props.studio)]);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (!mountedModelSyncRef.current) {
|
|
195
|
+
mountedModelSyncRef.current = true;
|
|
196
|
+
if (!props.studio) return;
|
|
197
|
+
}
|
|
198
|
+
if (props.model && props.model !== studio.model) studio.setModel(props.model);
|
|
199
|
+
else if (props.xml && props.xml !== studio.exportXml(props.engine)) studio.importXml(props.xml, props.engine);
|
|
200
|
+
}, [studio, props.model, props.xml, props.engine]);
|
|
147
201
|
useEffect(() => { if (props.runtime !== undefined) shellRef.current?.setRuntime(props.runtime); }, [props.runtime]);
|
|
148
202
|
useEffect(() => { if (props.mode) shellRef.current?.setMode(props.mode); }, [props.mode]);
|
|
149
203
|
useEffect(() => { if (props.projection) shellRef.current?.setProjection(props.projection); }, [props.projection]);
|
|
204
|
+
useEffect(() => { if (props.regions !== undefined) shellRef.current?.setRegions(props.regions); }, [props.regions]);
|
|
150
205
|
useEffect(() => { if (props.theme !== undefined) shellRef.current?.setTheme(props.theme); }, [props.theme]);
|
|
151
206
|
useEffect(() => { if (props.runtimeAppearance !== undefined) shellRef.current?.setRuntimeAppearance(props.runtimeAppearance); }, [props.runtimeAppearance]);
|
|
152
207
|
useEffect(() => {
|
|
@@ -157,7 +212,21 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
157
212
|
shell.rendererOptions.runtimeTraceOptions = props.runtimeTraceOptions;
|
|
158
213
|
shell.viewer?.setDisplayOptions({ timeline: props.timeline, runtimeDetails: props.runtimeDetails, runtimeTraceOptions: props.runtimeTraceOptions });
|
|
159
214
|
}, [props.timeline, props.runtimeDetails, props.runtimeTraceOptions]);
|
|
160
|
-
|
|
215
|
+
const renderSlot = (slot, portal, key) => {
|
|
216
|
+
if (slot === undefined || slot === null || !portal) return null;
|
|
217
|
+
const context = {
|
|
218
|
+
studio: portal.context.studio,
|
|
219
|
+
shell: portal.context.shell,
|
|
220
|
+
actions: portal.context.actions,
|
|
221
|
+
state: studioState,
|
|
222
|
+
};
|
|
223
|
+
return createPortal(typeof slot === 'function' ? slot(context) : slot, portal.target, key);
|
|
224
|
+
};
|
|
225
|
+
return React.createElement(React.Fragment, null,
|
|
226
|
+
React.createElement('div', { ref: hostRef, className: props.className, style: { width: '100%', height: '100%', minHeight: 520, ...props.style } }),
|
|
227
|
+
renderSlot(props.header, headerPortal, 'bpmn-nova-header'),
|
|
228
|
+
renderSlot(props.headerStart, headerStartPortal, 'bpmn-nova-header-start'),
|
|
229
|
+
);
|
|
161
230
|
});
|
|
162
231
|
|
|
163
232
|
export const BpmnDesigner = forwardRef(function BpmnDesigner(props, ref) {
|
package/dist/styles.css
CHANGED
|
@@ -1141,6 +1141,8 @@ textarea.property-control { resize: vertical; }
|
|
|
1141
1141
|
|
|
1142
1142
|
/* @bpmn-nova/internal/studio */
|
|
1143
1143
|
.nova-studio-shell { --nova-left-width: 244px; --nova-right-width: 360px; --nova-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; --nova-font-size-xs: 10px; --nova-font-size-sm: 12px; --nova-font-size-md: 14px; --nova-font-size-lg: 16px; --nova-font-weight-regular: 400; --nova-font-weight-medium: 500; --nova-font-weight-semibold: 600; --nova-font-weight-bold: 700; position: relative; isolation: isolate; width: 100%; height: 100%; min-height: 520px; overflow: hidden; display: grid; grid-template-rows: 58px minmax(0, 1fr); color: var(--nova-color-text, #263148); background: var(--nova-color-canvas, #f6f7fb); font-family: var(--nova-font-family); }
|
|
1144
|
+
.nova-studio-shell.is-header-hidden { grid-template-rows: minmax(0, 1fr); }
|
|
1145
|
+
.nova-studio-header[hidden], .nova-studio-left[hidden], .nova-studio-right[hidden], .nova-studio-statusbar[hidden] { display: none !important; }
|
|
1144
1146
|
.nova-studio-header { min-width: 0; border-bottom: 1px solid var(--nova-color-divider, #e4e8f0); background: var(--nova-color-surface, #fff); display: grid; grid-template-columns: minmax(180px, auto) auto minmax(0, 1fr); align-items: center; gap: 18px; padding: 0 12px 0 14px; }
|
|
1145
1147
|
.nova-studio-brand { min-width: 0; display: flex; align-items: center; gap: 10px; }
|
|
1146
1148
|
.nova-studio-brand-mark { flex: none; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; color: var(--nova-color-text-inverse, #fff); background: linear-gradient(145deg, var(--nova-color-primary, #5362da), var(--nova-color-primary-hover)); font-size: var(--nova-font-size-md); font-weight: var(--nova-font-weight-bold); box-shadow: var(--nova-shadow-sm); }
|
|
@@ -1194,7 +1196,11 @@ textarea.property-control { resize: vertical; }
|
|
|
1194
1196
|
.nova-studio-beautify-action-icon > .nova-icon-svg { width: 14px; height: 14px; }
|
|
1195
1197
|
.nova-studio-beautify-action strong { font-size: var(--nova-font-size-sm); font-weight: var(--nova-font-weight-semibold); line-height: 18px; }.nova-studio-beautify-action small { color: #778195; color: var(--nova-color-text-muted, #778195); font-size: var(--nova-font-size-xs); line-height: 14px; }
|
|
1196
1198
|
.nova-studio-body { min-width: 0; min-height: 0; display: grid; grid-template-columns: var(--nova-left-width) minmax(320px, 1fr) var(--nova-right-width); }
|
|
1199
|
+
.nova-studio-body.is-left-hidden { grid-template-columns: minmax(320px, 1fr) var(--nova-right-width); }
|
|
1200
|
+
.nova-studio-body.is-right-hidden { grid-template-columns: var(--nova-left-width) minmax(320px, 1fr); }
|
|
1201
|
+
.nova-studio-body.is-left-hidden.is-right-hidden { grid-template-columns: minmax(320px, 1fr); }
|
|
1197
1202
|
.nova-studio-body.is-readonly { grid-template-columns: minmax(320px, 1fr) var(--nova-right-width); }
|
|
1203
|
+
.nova-studio-body.is-readonly.is-right-hidden { grid-template-columns: minmax(320px, 1fr); }
|
|
1198
1204
|
.nova-studio-body.is-readonly .nova-studio-left { display: none; }
|
|
1199
1205
|
.nova-studio-left, .nova-studio-right, .nova-studio-canvas-column, .nova-studio-canvas { min-width: 0; min-height: 0; overflow: hidden; }
|
|
1200
1206
|
.nova-studio-left { border-right: 1px solid var(--nova-color-divider, #e4e8f0); background: var(--nova-color-surface, #fff); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
|
|
@@ -1206,6 +1212,7 @@ textarea.property-control { resize: vertical; }
|
|
|
1206
1212
|
.nova-studio-left::-webkit-scrollbar-thumb:hover, .nova-studio-right::-webkit-scrollbar-thumb:hover { background: #c1c9d5; background: var(--nova-color-text-muted); }
|
|
1207
1213
|
.nova-studio-left::-webkit-scrollbar-button, .nova-studio-right::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
|
|
1208
1214
|
.nova-studio-canvas-column { display: grid; grid-template-rows: minmax(0, 1fr) 35px; background: var(--nova-color-canvas, #f7f8fc); }
|
|
1215
|
+
.nova-studio-canvas-column.is-footer-hidden { grid-template-rows: minmax(0, 1fr); }
|
|
1209
1216
|
.nova-studio-canvas-stage { position: relative; min-width: 0; min-height: 0; overflow: hidden; }
|
|
1210
1217
|
.nova-studio-canvas { position: relative; }
|
|
1211
1218
|
.nova-studio-canvas-stage > .nova-studio-canvas { width: 100%; height: 100%; }
|
|
@@ -1422,6 +1429,7 @@ textarea.property-control { resize: vertical; }
|
|
|
1422
1429
|
}
|
|
1423
1430
|
@media (max-width: 720px) {
|
|
1424
1431
|
.nova-studio-shell { grid-template-rows: 54px minmax(0,1fr); }
|
|
1432
|
+
.nova-studio-shell.is-header-hidden { grid-template-rows: minmax(0, 1fr); }
|
|
1425
1433
|
.nova-studio-header { grid-template-columns: auto minmax(0,1fr); padding-left: 9px; }
|
|
1426
1434
|
.nova-studio-brand-copy { display: none; }
|
|
1427
1435
|
.nova-studio-body { grid-template-columns: 1fr; }
|