@bpmn-nova/vue 0.3.2-preview → 0.3.4-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 +69 -3
- package/dist/index.d.ts +40 -2
- package/dist/index.js +40 -11
- package/dist/styles.css +4 -3
- package/llms-full.txt +367 -59
- package/llms.txt +33 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ BPMN Nova 的 Vue 3.3+ Adapter,提供 Studio、Designer、Viewer、审批轨
|
|
|
4
4
|
|
|
5
5
|
> **English summary:** Vue 3.3+ components and exposed instance methods for BPMN Nova process design, viewing, approval traces, properties, themes, and SVG export.
|
|
6
6
|
|
|
7
|
-
> 当前版本为 `0.3.
|
|
7
|
+
> 当前版本为 `0.3.4-preview`。Vue 由宿主工程提供,本包不会替应用选择或升级框架版本。
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -20,6 +20,20 @@ import '@bpmn-nova/vue/styles.css'
|
|
|
20
20
|
|
|
21
21
|
只安装本包;`@bpmn-nova/studio` 会作为内部依赖自动解析。容器必须有明确高度。
|
|
22
22
|
|
|
23
|
+
TypeScript 类型也从 Vue 入口导入,不要为了模型类型额外声明 Studio 直依赖:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import type {
|
|
27
|
+
BpmnNode,
|
|
28
|
+
EdgeType,
|
|
29
|
+
ElementSelection,
|
|
30
|
+
NodeType,
|
|
31
|
+
ProcessModel,
|
|
32
|
+
} from '@bpmn-nova/vue'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
公共类型门面还包含 `BpmnEdge`、`EngineId` 和 `LayoutOptions`。
|
|
36
|
+
|
|
23
37
|
使用代码生成助手或 IDE Agent 接入时,从随包的 `llms.txt` 开始;需要完整接口和定制资料时再读取 `llms-full.txt`。两份文件都包含在 npm tarball 中,不依赖源码仓库可见性。
|
|
24
38
|
|
|
25
39
|
## 流程设计
|
|
@@ -53,7 +67,7 @@ function handleChange(model, reason, nextXml) {
|
|
|
53
67
|
</template>
|
|
54
68
|
```
|
|
55
69
|
|
|
56
|
-
Expose 提供 `getActions()`、`exportXml()`、`fitView()`、`setTheme()`、`exportSvg()` 和 `openSvgExportPreview()`。
|
|
70
|
+
Expose 提供 `getActions()`、`validate()`、`exportXml()`、`fitView()`、`setTheme()`、`exportSvg()` 和 `openSvgExportPreview()`。
|
|
57
71
|
|
|
58
72
|
`xml`/`model` 是外部替换输入,`change` 的 XML 是草稿输出。宿主回写完全相同的导出 XML 不会重新导入或清空撤销历史;切换服务器 revision 或流程时传入不同 XML 会替换模型。Activiti 宿主应显式使用 `engine="activiti"`。
|
|
59
73
|
|
|
@@ -90,6 +104,7 @@ function handleSelectionChange(selection, element) {
|
|
|
90
104
|
theme="auto"
|
|
91
105
|
@change="handleChange"
|
|
92
106
|
@selection-change="handleSelectionChange"
|
|
107
|
+
@validation="handleValidation"
|
|
93
108
|
>
|
|
94
109
|
<template #header-start="{ state, actions }">
|
|
95
110
|
<button type="button" @click="back">← 返回审批列表</button>
|
|
@@ -97,6 +112,11 @@ function handleSelectionChange(selection, element) {
|
|
|
97
112
|
<strong>{{ processName }}</strong>
|
|
98
113
|
<span>{{ processType }}</span>
|
|
99
114
|
</template>
|
|
115
|
+
<template #header-actions="{ actions, mode }">
|
|
116
|
+
<button type="button" :disabled="mode !== 'design'" @click="actions.validate()">校验</button>
|
|
117
|
+
<button type="button" :disabled="mode !== 'design'" @click="saveDraft(actions.exportXml())">保存</button>
|
|
118
|
+
<button type="button" :disabled="mode !== 'design'" @click="publishProcess(actions)">发布</button>
|
|
119
|
+
</template>
|
|
100
120
|
</BpmnStudio>
|
|
101
121
|
|
|
102
122
|
<DxProcessProperties
|
|
@@ -107,10 +127,56 @@ function handleSelectionChange(selection, element) {
|
|
|
107
127
|
</template>
|
|
108
128
|
```
|
|
109
129
|
|
|
110
|
-
`header-start` 使用 Vue Teleport 在当前应用内渲染,因此保留 provide/inject
|
|
130
|
+
`header-start` 与 `header-actions` 使用 Vue Teleport 在当前应用内渲染,因此保留 provide/inject、响应式状态与组件生命周期。`header-actions` 只替换默认“校验 / 导入 / 导出”动作组,完整 `header` 的优先级更高。默认 Header 的最佳视图只保留在底部缩放区;若整个 Header 都不需要,配置 `regions.header = 'hidden'`。
|
|
131
|
+
|
|
132
|
+
`actions.validate()` 以 `toolbar` 来源先更新 Nova 默认状态栏,再发送 `validation` 事件并返回 issues;Expose `validate()` 使用 `api` 来源。事件的 `valid` 仅在存在 error 时为 `false`,warning 不默认阻止发布。保存与发布仍由宿主负责。
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
async function publishProcess(actions) {
|
|
136
|
+
const issues = actions.validate()
|
|
137
|
+
if (issues.some((issue) => issue.level === 'error')) return
|
|
138
|
+
await publishXml(actions.exportXml())
|
|
139
|
+
}
|
|
140
|
+
```
|
|
111
141
|
|
|
112
142
|
编辑器属性面板应订阅 `selection-change`,该事件覆盖节点、连线、多选、清空和键盘选择。`element-click` 是点击观察事件,不能代替选择状态。宿主使用稳定 BPMN Element ID 关联业务配置,并自行拥有保存、发布、权限和服务端事务。
|
|
113
143
|
|
|
144
|
+
## Mode 双向同步与副标题刷新
|
|
145
|
+
|
|
146
|
+
```vue
|
|
147
|
+
<script setup>
|
|
148
|
+
import { ref } from 'vue'
|
|
149
|
+
|
|
150
|
+
const studioRef = ref(null)
|
|
151
|
+
const mode = ref('design')
|
|
152
|
+
const summaries = new Map()
|
|
153
|
+
const resolveSubtitle = ({ node }) => summaries.has(node.id)
|
|
154
|
+
? summaries.get(node.id)
|
|
155
|
+
: undefined
|
|
156
|
+
|
|
157
|
+
function updateSummary() {
|
|
158
|
+
summaries.set('ServiceTask_Archive', '归档到采购系统')
|
|
159
|
+
studioRef.value?.refreshPresentation()
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
162
|
+
|
|
163
|
+
<template>
|
|
164
|
+
<BpmnStudio
|
|
165
|
+
ref="studioRef"
|
|
166
|
+
v-model:mode="mode"
|
|
167
|
+
:allowed-modes="['design', 'viewer']"
|
|
168
|
+
:node-subtitle-resolver="resolveSubtitle"
|
|
169
|
+
@mode-change="event => console.log(event.source)"
|
|
170
|
+
>
|
|
171
|
+
<template #header-start="{ mode: actualMode }">
|
|
172
|
+
<HostHeader :mode="actualMode" />
|
|
173
|
+
</template>
|
|
174
|
+
</BpmnStudio>
|
|
175
|
+
</template>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Expose 的 `getMode()` / `setMode()` 使用 Shell 实际状态。Resolver 返回 `undefined` 保留默认值、`null` 移除副标题行、字符串替换显示;函数引用变化会自动刷新,稳定闭包内部数据变化需显式调用 `refreshPresentation()`。Resolver 只影响 Design/Viewer 标准卡片和 SVG,不覆盖 Instance Runtime Presentation,也不修改 XML、History、Selection 或 Viewport。
|
|
179
|
+
|
|
114
180
|
## 只读展示与审批轨迹
|
|
115
181
|
|
|
116
182
|
```vue
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,12 @@ import type {
|
|
|
18
18
|
BpmnStudioShell,
|
|
19
19
|
ContextMenuRegistry,
|
|
20
20
|
InteractionController,
|
|
21
|
+
NodeSubtitleResolver,
|
|
21
22
|
StudioCommands,
|
|
23
|
+
StudioMode,
|
|
24
|
+
StudioModeChangeEvent,
|
|
25
|
+
StudioValidationEvent,
|
|
26
|
+
StudioValidationIssue,
|
|
22
27
|
StudioShellActions,
|
|
23
28
|
StudioShellOptions,
|
|
24
29
|
StudioShellRegions,
|
|
@@ -37,6 +42,24 @@ import type {
|
|
|
37
42
|
ViewerProjection,
|
|
38
43
|
} from '@bpmn-nova/studio/viewer'
|
|
39
44
|
|
|
45
|
+
export type {
|
|
46
|
+
BpmnEdge,
|
|
47
|
+
BpmnNode,
|
|
48
|
+
EdgeType,
|
|
49
|
+
ElementSelection,
|
|
50
|
+
EngineId,
|
|
51
|
+
LayoutOptions,
|
|
52
|
+
NodeType,
|
|
53
|
+
ProcessModel,
|
|
54
|
+
} from '@bpmn-nova/studio'
|
|
55
|
+
export type {
|
|
56
|
+
NodeSubtitleResolver,
|
|
57
|
+
NodeSubtitleResolverContext,
|
|
58
|
+
StudioValidationEvent,
|
|
59
|
+
StudioValidationIssue,
|
|
60
|
+
StudioValidationSource,
|
|
61
|
+
} from '@bpmn-nova/studio'
|
|
62
|
+
|
|
40
63
|
export {
|
|
41
64
|
createContextMenuRegistry,
|
|
42
65
|
createDefaultContextMenuRegistry,
|
|
@@ -74,11 +97,13 @@ export interface BpmnCanvasProps extends BpmnNovaVisualProps {
|
|
|
74
97
|
iconRegistry?: IconRegistry
|
|
75
98
|
nodeRenderers?: Record<string, Function>
|
|
76
99
|
nodeRenderer?: Function
|
|
100
|
+
nodeSubtitleResolver?: NodeSubtitleResolver
|
|
77
101
|
svgExport?: ViewerOptions['svgExport']
|
|
78
102
|
}
|
|
79
103
|
export interface BpmnCanvasExposed {
|
|
80
104
|
getCanvas(): CanvasInstance | null
|
|
81
105
|
fitView(): void
|
|
106
|
+
refreshPresentation(): void
|
|
82
107
|
setTheme(theme: NovaThemeInput): NovaThemeState | undefined
|
|
83
108
|
exportSvg(options?: SvgExportOptions): Promise<SvgExportArtifact> | undefined
|
|
84
109
|
openSvgExportPreview(options?: SvgExportOptions & { previewTitle?: string; onDownload?: (artifact: SvgExportArtifact) => void }): SvgExportPreviewController | undefined
|
|
@@ -115,6 +140,7 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
115
140
|
contextMenuRegistry?: ContextMenuRegistry
|
|
116
141
|
nodeRenderers?: Record<string, Function>
|
|
117
142
|
nodeRenderer?: Function
|
|
143
|
+
nodeSubtitleResolver?: NodeSubtitleResolver
|
|
118
144
|
slotsConfig?: StudioShellSlots
|
|
119
145
|
layout?: StudioShellOptions['layout']
|
|
120
146
|
regions?: StudioShellRegions
|
|
@@ -136,8 +162,8 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
136
162
|
runtimeTraceOptions?: ViewerOptions['runtimeTraceOptions']
|
|
137
163
|
onTraceClick?: (event: RuntimeTraceClickEvent) => void
|
|
138
164
|
onElementClick?: ViewerOptions['onElementClick']
|
|
139
|
-
mode?:
|
|
140
|
-
allowedModes?:
|
|
165
|
+
mode?: StudioMode
|
|
166
|
+
allowedModes?: readonly StudioMode[]
|
|
141
167
|
projection?: ViewerProjection
|
|
142
168
|
responsive?: boolean
|
|
143
169
|
projectionOptions?: Array<{ value: ViewerProjection; label: string }>
|
|
@@ -146,6 +172,12 @@ export interface BpmnStudioExposed {
|
|
|
146
172
|
getStudio(): BpmnStudioController
|
|
147
173
|
getShell(): BpmnStudioShell | null
|
|
148
174
|
getActions(): StudioShellActions | null
|
|
175
|
+
getMode(): StudioMode | undefined
|
|
176
|
+
getAllowedModes(): readonly StudioMode[]
|
|
177
|
+
setMode(mode: StudioMode): boolean
|
|
178
|
+
setAllowedModes(modes: readonly StudioMode[]): readonly StudioMode[]
|
|
179
|
+
refreshPresentation(): void
|
|
180
|
+
validate(): StudioValidationIssue[]
|
|
149
181
|
exportXml(engine?: EngineId): string
|
|
150
182
|
fitView(): void
|
|
151
183
|
setTheme(theme: NovaThemeInput): NovaThemeState | undefined
|
|
@@ -157,9 +189,11 @@ export interface BpmnStudioSlotContext {
|
|
|
157
189
|
readonly shell: BpmnStudioShell
|
|
158
190
|
readonly actions: StudioShellActions
|
|
159
191
|
readonly state: Readonly<StudioState>
|
|
192
|
+
readonly mode: StudioMode
|
|
160
193
|
}
|
|
161
194
|
export interface BpmnStudioSlots {
|
|
162
195
|
'header-start'?(context: BpmnStudioSlotContext): VNodeChild
|
|
196
|
+
'header-actions'?(context: BpmnStudioSlotContext): VNodeChild
|
|
163
197
|
header?(context: BpmnStudioSlotContext): VNodeChild
|
|
164
198
|
}
|
|
165
199
|
export const BpmnStudio: DefineComponent<BpmnStudioProps> & {
|
|
@@ -184,6 +218,7 @@ export interface BpmnViewerProps extends BpmnNovaVisualProps, BpmnModelProps, Om
|
|
|
184
218
|
export interface BpmnViewerExposed {
|
|
185
219
|
getInstance(): ViewerInstance | null
|
|
186
220
|
fitView(): void
|
|
221
|
+
refreshPresentation(): void
|
|
187
222
|
setProjection(projection: ViewerProjection): void
|
|
188
223
|
setDisplayOptions(options: Parameters<ViewerInstance['setDisplayOptions']>[0]): void
|
|
189
224
|
setTheme(theme: NovaThemeInput): NovaThemeState | undefined
|
|
@@ -225,6 +260,9 @@ export interface BpmnStudioEmits {
|
|
|
225
260
|
'scope-change': [activeScopeId: string, scopePath: StudioState['scopePath'], state: StudioState]
|
|
226
261
|
'element-click': [payload: Parameters<NonNullable<ViewerOptions['onElementClick']>>[0]]
|
|
227
262
|
'trace-click': [payload: RuntimeTraceClickEvent]
|
|
263
|
+
'update:mode': [mode: StudioMode]
|
|
264
|
+
'mode-change': [event: StudioModeChangeEvent]
|
|
265
|
+
validation: [event: StudioValidationEvent]
|
|
228
266
|
}
|
|
229
267
|
export interface BpmnDesignerEmits {
|
|
230
268
|
change: [model: ProcessModel, reason: string, xml: string]
|
package/dist/index.js
CHANGED
|
@@ -58,18 +58,20 @@ export function useBpmnStudio(options = {}) {
|
|
|
58
58
|
|
|
59
59
|
export const BpmnCanvas = defineComponent({
|
|
60
60
|
name: 'BpmnNovaCanvas',
|
|
61
|
-
props: { studio: { type: Object, required: true }, interactions: Object, templates: Object, iconRegistry: Object, nodeRenderers: Object, nodeRenderer: Function, svgExport: Object, theme: [Object, String], onThemeChange: Function },
|
|
61
|
+
props: { studio: { type: Object, required: true }, interactions: Object, templates: Object, iconRegistry: Object, nodeRenderers: Object, nodeRenderer: Function, nodeSubtitleResolver: Function, svgExport: Object, theme: [Object, String], onThemeChange: Function },
|
|
62
62
|
setup(props, { expose, attrs }) {
|
|
63
63
|
const host = ref(null);
|
|
64
64
|
let canvas = null;
|
|
65
|
+
const nodeSubtitleResolver = (context) => props.nodeSubtitleResolver?.(context);
|
|
65
66
|
onMounted(() => {
|
|
66
67
|
const interactions = props.interactions || createInteractionController({ studio: props.studio, templates: props.templates || createTemplateRegistry() });
|
|
67
|
-
canvas = new CoreCanvas({ container: host.value, studio: props.studio, interactions, theme: props.theme, onThemeChange: props.onThemeChange, rendererOptions: { iconRegistry: props.iconRegistry, nodeRenderers: props.nodeRenderers, nodeRenderer: props.nodeRenderer, svgExport: props.svgExport } });
|
|
68
|
+
canvas = new CoreCanvas({ container: host.value, studio: props.studio, interactions, theme: props.theme, onThemeChange: props.onThemeChange, nodeSubtitleResolver, rendererOptions: { iconRegistry: props.iconRegistry, nodeRenderers: props.nodeRenderers, nodeRenderer: props.nodeRenderer, svgExport: props.svgExport } });
|
|
68
69
|
requestAnimationFrame(() => canvas.fitView());
|
|
69
70
|
});
|
|
70
71
|
onBeforeUnmount(() => canvas?.destroy());
|
|
71
72
|
watch(() => props.theme, (value) => { if (value !== undefined) canvas?.setTheme(value); });
|
|
72
|
-
|
|
73
|
+
watch(() => props.nodeSubtitleResolver, () => canvas?.refreshPresentation());
|
|
74
|
+
expose({ getCanvas: () => canvas, fitView: () => canvas?.fitView(), refreshPresentation: () => canvas?.refreshPresentation(), setTheme: (value) => canvas?.setTheme(value), exportSvg: (options) => canvas?.exportSvg(options), openSvgExportPreview: (options) => canvas?.openSvgExportPreview(options) });
|
|
73
75
|
return () => h('div', { ...attrs, ref: host, style: { width: '100%', height: '100%', minHeight: '320px', ...(attrs.style || {}) } });
|
|
74
76
|
},
|
|
75
77
|
});
|
|
@@ -126,25 +128,31 @@ export const BpmnStudio = defineComponent({
|
|
|
126
128
|
props: {
|
|
127
129
|
studio: Object, model: Object, xml: String, engine: { type: String, default: 'flowable' }, propertiesProfile: String, allowedNodeTypes: Array, allowedEdgeTypes: Array,
|
|
128
130
|
iconRegistry: Object, paletteRegistry: Object, propertiesRegistry: Object, templateRegistry: Object, contextMenuRegistry: Object,
|
|
129
|
-
nodeRenderers: Object, nodeRenderer: Function, slotsConfig: Object, layout: Function, regions: Object, leftWidth: Number, rightWidth: Number,
|
|
131
|
+
nodeRenderers: Object, nodeRenderer: Function, nodeSubtitleResolver: Function, slotsConfig: Object, layout: Function, regions: Object, leftWidth: Number, rightWidth: Number,
|
|
130
132
|
runtime: Object, runtimePresenter: Function, runtimeTraceProjector: Function, runtimeAssetResolver: Function, runtimeTimelineRenderer: Function, onRuntimeTraceItemClick: Function, runtimeDetailsRenderer: Function, onRuntimeDetailsOpen: Function,
|
|
131
133
|
runtimeTransitionDetailsRenderer: Function, onRuntimeTransitionDetailsOpen: Function,
|
|
132
134
|
timeline: Object, runtimeDetails: Object, runtimeTraceOptions: Object, onTraceClick: Function, onElementClick: Function,
|
|
133
135
|
mode: String, allowedModes: Array, projection: String, responsive: { type: Boolean, default: false }, projectionOptions: Array,
|
|
134
136
|
theme: [Object, String], runtimeAppearance: Object, svgExport: Object, onThemeChange: Function,
|
|
135
137
|
},
|
|
136
|
-
emits: ['change', 'selection-change', 'scope-change', 'element-click', 'trace-click'],
|
|
138
|
+
emits: ['change', 'selection-change', 'scope-change', 'element-click', 'trace-click', 'update:mode', 'mode-change', 'validation'],
|
|
137
139
|
setup(props, { emit, expose, attrs, slots }) {
|
|
138
140
|
const host = ref(null);
|
|
139
141
|
const headerTarget = ref(null);
|
|
140
142
|
const headerStartTarget = ref(null);
|
|
143
|
+
const headerActionsTarget = ref(null);
|
|
141
144
|
const headerContext = ref(null);
|
|
142
145
|
const headerStartContext = ref(null);
|
|
146
|
+
const headerActionsContext = ref(null);
|
|
143
147
|
const owned = !props.studio;
|
|
144
148
|
const studio = props.studio || createStudioController({ model: resolveModel(props), propertiesProfile: props.propertiesProfile, allowedNodeTypes: props.allowedNodeTypes, allowedEdgeTypes: props.allowedEdgeTypes });
|
|
145
149
|
const state = reactive(studio.getState());
|
|
150
|
+
const actualMode = ref(props.mode || props.allowedModes?.[0] || 'design');
|
|
151
|
+
const nodeSubtitleResolver = (context) => props.nodeSubtitleResolver?.(context);
|
|
146
152
|
let shell = null;
|
|
147
153
|
let off = null;
|
|
154
|
+
let offMode = null;
|
|
155
|
+
let offValidation = null;
|
|
148
156
|
|
|
149
157
|
const createTeleportSlot = (target, contextRef) => (context) => {
|
|
150
158
|
target.value = context.container;
|
|
@@ -158,8 +166,18 @@ export const BpmnStudio = defineComponent({
|
|
|
158
166
|
onMounted(() => {
|
|
159
167
|
const shellSlots = { ...(props.slotsConfig || {}) };
|
|
160
168
|
if (slots.header) shellSlots.header = createTeleportSlot(headerTarget, headerContext);
|
|
161
|
-
else if (!shellSlots.header
|
|
162
|
-
|
|
169
|
+
else if (!shellSlots.header) {
|
|
170
|
+
if (slots['header-start']) shellSlots.headerStart = createTeleportSlot(headerStartTarget, headerStartContext);
|
|
171
|
+
if (slots['header-actions']) shellSlots.headerActions = createTeleportSlot(headerActionsTarget, headerActionsContext);
|
|
172
|
+
}
|
|
173
|
+
shell = new CoreStudioShell({ container: host.value, studio, iconRegistry: props.iconRegistry, paletteRegistry: props.paletteRegistry, propertiesRegistry: props.propertiesRegistry, templateRegistry: props.templateRegistry, contextMenuRegistry: props.contextMenuRegistry, rendererOptions: { nodeRenderers: props.nodeRenderers, nodeRenderer: props.nodeRenderer, nodeSubtitleResolver, runtimePresenter: props.runtimePresenter, runtimeTraceProjector: props.runtimeTraceProjector, runtimeAssetResolver: props.runtimeAssetResolver, runtimeTraceOptions: props.runtimeTraceOptions, runtimeTimelineRenderer: props.runtimeTimelineRenderer, timeline: props.timeline, runtimeDetails: props.runtimeDetails, onRuntimeTraceItemClick: (payload) => props.onRuntimeTraceItemClick?.(payload), runtimeDetailsRenderer: props.runtimeDetailsRenderer, onRuntimeDetailsOpen: (payload) => props.onRuntimeDetailsOpen?.(payload), runtimeTransitionDetailsRenderer: props.runtimeTransitionDetailsRenderer, onRuntimeTransitionDetailsOpen: (payload) => props.onRuntimeTransitionDetailsOpen?.(payload), onElementClick: (payload) => emit('element-click', payload), onTraceClick: (payload) => emit('trace-click', payload) }, slots: shellSlots, layout: props.layout, regions: props.regions, runtime: props.runtime, mode: props.mode, allowedModes: props.allowedModes, projection: props.projection, responsive: props.responsive, projectionOptions: props.projectionOptions, leftWidth: props.leftWidth, rightWidth: props.rightWidth, theme: props.theme, runtimeAppearance: props.runtimeAppearance, svgExport: props.svgExport, onThemeChange: props.onThemeChange });
|
|
174
|
+
actualMode.value = shell.getMode();
|
|
175
|
+
offMode = shell.subscribeMode((event) => {
|
|
176
|
+
actualMode.value = event.mode;
|
|
177
|
+
emit('update:mode', event.mode);
|
|
178
|
+
emit('mode-change', event);
|
|
179
|
+
});
|
|
180
|
+
offValidation = shell.subscribeValidation((event) => emit('validation', event));
|
|
163
181
|
off = studio.subscribe((event) => {
|
|
164
182
|
Object.assign(state, event.state);
|
|
165
183
|
if (event.type === 'modelChanged') emit('change', studio.model, event.reason, studio.exportXml());
|
|
@@ -172,11 +190,16 @@ export const BpmnStudio = defineComponent({
|
|
|
172
190
|
if (value && value !== studio.exportXml(props.engine)) studio.importXml(value, props.engine);
|
|
173
191
|
});
|
|
174
192
|
watch(() => props.runtime, (value) => { if (value !== undefined) shell?.setRuntime(value); });
|
|
175
|
-
watch(() => props.mode, (
|
|
193
|
+
watch(() => [props.allowedModes, props.mode], ([allowedModes, mode]) => {
|
|
194
|
+
if (!shell) return;
|
|
195
|
+
if (allowedModes !== undefined) shell.setAllowedModes(allowedModes);
|
|
196
|
+
if (mode) shell.setMode(mode);
|
|
197
|
+
}, { deep: true });
|
|
176
198
|
watch(() => props.projection, (value) => { if (value) shell?.setProjection(value); });
|
|
177
199
|
watch(() => props.regions, (value) => { if (value !== undefined) shell?.setRegions(value); }, { deep: true });
|
|
178
200
|
watch(() => props.theme, (value) => { if (value !== undefined) shell?.setTheme(value); });
|
|
179
201
|
watch(() => props.runtimeAppearance, (value) => { if (value !== undefined) shell?.setRuntimeAppearance(value); });
|
|
202
|
+
watch(() => props.nodeSubtitleResolver, () => shell?.refreshPresentation());
|
|
180
203
|
watch(() => [props.timeline, props.runtimeDetails, props.runtimeTraceOptions], ([timeline, runtimeDetails, runtimeTraceOptions]) => {
|
|
181
204
|
if (!shell) return;
|
|
182
205
|
shell.rendererOptions.timeline = timeline;
|
|
@@ -184,8 +207,8 @@ export const BpmnStudio = defineComponent({
|
|
|
184
207
|
shell.rendererOptions.runtimeTraceOptions = runtimeTraceOptions;
|
|
185
208
|
shell.viewer?.setDisplayOptions({ timeline, runtimeDetails, runtimeTraceOptions });
|
|
186
209
|
});
|
|
187
|
-
onBeforeUnmount(() => { off?.(); shell?.destroy(); if (owned) studio.destroy(); });
|
|
188
|
-
expose({ getStudio: () => studio, getShell: () => shell, getActions: () => shell?.actions || null, exportXml: (engine) => studio.exportXml(engine), exportSvg: (options) => shell?.exportSvg(options), openSvgExportPreview: (options) => shell?.openSvgExportPreview(options), fitView: () => shell?.fitView(), setTheme: (value) => shell?.setTheme(value) });
|
|
210
|
+
onBeforeUnmount(() => { offMode?.(); offValidation?.(); off?.(); shell?.destroy(); if (owned) studio.destroy(); });
|
|
211
|
+
expose({ getStudio: () => studio, getShell: () => shell, getActions: () => shell?.actions || null, getMode: () => shell?.getMode(), getAllowedModes: () => shell?.getAllowedModes() || [], setMode: (value) => shell?.setMode(value) || false, setAllowedModes: (value) => shell?.setAllowedModes(value) || [], refreshPresentation: () => shell?.refreshPresentation(), validate: () => shell?.validate() || [], exportXml: (engine) => studio.exportXml(engine), exportSvg: (options) => shell?.exportSvg(options), openSvgExportPreview: (options) => shell?.openSvgExportPreview(options), fitView: () => shell?.fitView(), setTheme: (value) => shell?.setTheme(value) });
|
|
189
212
|
const renderSlot = (name, target, contextRef) => {
|
|
190
213
|
if (!slots[name] || !target.value || !contextRef.value) return null;
|
|
191
214
|
return h(Teleport, { to: target.value }, slots[name]({
|
|
@@ -193,12 +216,14 @@ export const BpmnStudio = defineComponent({
|
|
|
193
216
|
shell: contextRef.value.shell,
|
|
194
217
|
actions: contextRef.value.actions,
|
|
195
218
|
state,
|
|
219
|
+
mode: actualMode.value,
|
|
196
220
|
}));
|
|
197
221
|
};
|
|
198
222
|
return () => h(Fragment, null, [
|
|
199
223
|
h('div', { ...attrs, ref: host, style: { width: '100%', height: '100%', minHeight: '520px', ...(attrs.style || {}) } }),
|
|
200
224
|
renderSlot('header', headerTarget, headerContext),
|
|
201
225
|
renderSlot('header-start', headerStartTarget, headerStartContext),
|
|
226
|
+
renderSlot('header-actions', headerActionsTarget, headerActionsContext),
|
|
202
227
|
]);
|
|
203
228
|
},
|
|
204
229
|
});
|
|
@@ -265,6 +290,7 @@ export const BpmnViewer = defineComponent({
|
|
|
265
290
|
iconRegistry: Object,
|
|
266
291
|
nodeRenderers: Object,
|
|
267
292
|
nodeRenderer: Function,
|
|
293
|
+
nodeSubtitleResolver: Function,
|
|
268
294
|
timeline: Object,
|
|
269
295
|
runtimeDetails: Object,
|
|
270
296
|
runtimePresenter: Function,
|
|
@@ -290,6 +316,7 @@ export const BpmnViewer = defineComponent({
|
|
|
290
316
|
setup(props, { emit, expose, attrs }) {
|
|
291
317
|
const host = ref(null);
|
|
292
318
|
let instance = null;
|
|
319
|
+
const nodeSubtitleResolver = (context) => props.nodeSubtitleResolver?.(context);
|
|
293
320
|
onMounted(() => {
|
|
294
321
|
instance = new CoreViewer({
|
|
295
322
|
container: host.value,
|
|
@@ -298,6 +325,7 @@ export const BpmnViewer = defineComponent({
|
|
|
298
325
|
iconRegistry: props.iconRegistry,
|
|
299
326
|
nodeRenderers: props.nodeRenderers,
|
|
300
327
|
nodeRenderer: props.nodeRenderer,
|
|
328
|
+
nodeSubtitleResolver,
|
|
301
329
|
timeline: props.timeline,
|
|
302
330
|
runtimeDetails: props.runtimeDetails,
|
|
303
331
|
runtimePresenter: props.runtimePresenter,
|
|
@@ -327,10 +355,11 @@ export const BpmnViewer = defineComponent({
|
|
|
327
355
|
watch(() => props.projection, (value) => instance?.setProjection(value));
|
|
328
356
|
watch(() => props.theme, (value) => { if (value !== undefined) instance?.setTheme(value); });
|
|
329
357
|
watch(() => props.runtimeAppearance, (value) => { if (value !== undefined) instance?.setRuntimeAppearance(value); });
|
|
358
|
+
watch(() => props.nodeSubtitleResolver, () => instance?.refreshPresentation());
|
|
330
359
|
watch(() => [props.timeline, props.runtimeDetails, props.runtimeTraceOptions, props.runtimeAssetResolver], ([timeline, runtimeDetails, runtimeTraceOptions, runtimeAssetResolver]) => instance?.setDisplayOptions({ timeline, runtimeDetails, runtimeTraceOptions, runtimeAssetResolver }));
|
|
331
360
|
watch(() => props.model, (value) => { if (value) instance?.setModel(value); });
|
|
332
361
|
watch(() => props.xml, (value) => { if (value) instance?.setModel(importBpmn(value, props.engine)); });
|
|
333
|
-
expose({ getInstance: () => instance, fitView: () => instance?.fitView(), setProjection: (p) => instance?.setProjection(p), setDisplayOptions: (value) => instance?.setDisplayOptions(value), setTheme: (value) => instance?.setTheme(value), exportSvg: (options) => instance?.exportSvg(options), openSvgExportPreview: (options) => instance?.openSvgExportPreview(options) });
|
|
362
|
+
expose({ getInstance: () => instance, fitView: () => instance?.fitView(), refreshPresentation: () => instance?.refreshPresentation(), setProjection: (p) => instance?.setProjection(p), setDisplayOptions: (value) => instance?.setDisplayOptions(value), setTheme: (value) => instance?.setTheme(value), exportSvg: (options) => instance?.exportSvg(options), openSvgExportPreview: (options) => instance?.openSvgExportPreview(options) });
|
|
334
363
|
return () => h('div', { ...attrs, ref: host, style: { width: '100%', height: '100%', minHeight: '260px', ...(attrs.style || {}) } });
|
|
335
364
|
},
|
|
336
365
|
});
|
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; }
|