@bpmn-nova/vue 0.3.1-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/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 BPMN Nova contributors
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # @bpmn-nova/vue
2
+
3
+ BPMN Nova 的 Vue 3.3+ Adapter,提供 Studio、Designer、Viewer、审批轨迹、Properties 和 Expose 实例方法。
4
+
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
+
7
+ > 当前版本为 `0.3.1-preview`。Vue 由宿主工程提供,本包不会替应用选择或升级框架版本。
8
+
9
+ ![BPMN Nova Vue 流程工作台](https://raw.githubusercontent.com/daxiangme/bpmn-nova/dev/docs/assets/bpmn-nova-designer.jpg)
10
+
11
+ ## 安装
12
+
13
+ ```bash
14
+ npm install @bpmn-nova/vue@preview
15
+ ```
16
+
17
+ ```js
18
+ import '@bpmn-nova/vue/styles.css'
19
+ ```
20
+
21
+ 只安装本包;`@bpmn-nova/studio` 会作为内部依赖自动解析。容器必须有明确高度。
22
+
23
+ ## 流程设计
24
+
25
+ ```vue
26
+ <script setup>
27
+ import { ref } from 'vue'
28
+ import { BpmnStudio } from '@bpmn-nova/vue'
29
+ import '@bpmn-nova/vue/styles.css'
30
+
31
+ defineProps({ xml: String })
32
+ const studioRef = ref(null)
33
+
34
+ function handleChange(model, reason, nextXml) {
35
+ console.log(reason, nextXml)
36
+ }
37
+ </script>
38
+
39
+ <template>
40
+ <div style="height: 720px">
41
+ <BpmnStudio
42
+ ref="studioRef"
43
+ :xml="xml"
44
+ engine="flowable"
45
+ mode="design"
46
+ theme="auto"
47
+ @change="handleChange"
48
+ />
49
+ </div>
50
+ </template>
51
+ ```
52
+
53
+ Expose 提供 `exportXml()`、`fitView()`、`setTheme()`、`exportSvg()` 和 `openSvgExportPreview()`。
54
+
55
+ ## 只读展示与审批轨迹
56
+
57
+ ```vue
58
+ <script setup>
59
+ import { BpmnViewer } from '@bpmn-nova/vue'
60
+ import '@bpmn-nova/vue/styles.css'
61
+
62
+ defineProps({
63
+ xml: String,
64
+ runtime: Object,
65
+ runtimeAssetResolver: Function,
66
+ })
67
+ </script>
68
+
69
+ <template>
70
+ <div style="height: 720px">
71
+ <BpmnViewer
72
+ :xml="xml"
73
+ engine="flowable"
74
+ :runtime="runtime"
75
+ projection="compact"
76
+ responsive
77
+ theme="auto"
78
+ :runtime-asset-resolver="runtimeAssetResolver"
79
+ @trace-click="event => console.log(event)"
80
+ />
81
+ </div>
82
+ </template>
83
+ ```
84
+
85
+ - `approval`:实际发生的有效路径。
86
+ - `compact`:移动时间线、审批动作、图片和附件。
87
+ - `standard`:完整 BPMN 叠加运行状态。
88
+
89
+ ![BPMN Nova Vue 移动审批时间线](https://raw.githubusercontent.com/daxiangme/bpmn-nova/dev/docs/assets/bpmn-nova-approval-mobile-timeline.jpg)
90
+
91
+ ## Runtime Action 与附件
92
+
93
+ ```js
94
+ const runtime = {
95
+ processInstanceId: 'purchase-20260824',
96
+ status: 'running',
97
+ activities: [{
98
+ id: 'activity-manager-1',
99
+ elementId: 'UserTask_Manager',
100
+ visitId: 'visit-manager-1',
101
+ status: 'completed',
102
+ assignee: '李经理',
103
+ }],
104
+ actions: [{
105
+ id: 'approve-manager-1',
106
+ type: 'approve',
107
+ elementId: 'UserTask_Manager',
108
+ visitId: 'visit-manager-1',
109
+ occurredAt: '2026-08-24T10:12:00+08:00',
110
+ actor: { id: 'manager-li', name: '李经理' },
111
+ content: {
112
+ plainText: '资料完整,同意提交总经理审批。',
113
+ blocks: [{ type: 'file', assetId: 'purchase-checklist' }],
114
+ assets: [{
115
+ id: 'purchase-checklist',
116
+ name: '采购核验清单.txt',
117
+ mediaType: 'text/plain',
118
+ size: 248,
119
+ }],
120
+ },
121
+ }],
122
+ visitedEdges: [],
123
+ }
124
+
125
+ const runtimeAssetResolver = async (asset, { purpose, signal }) => {
126
+ const response = await fetch(
127
+ `/api/runtime-assets/${encodeURIComponent(asset.id)}?purpose=${purpose}`,
128
+ { signal },
129
+ )
130
+ return response.ok ? response.url : null
131
+ }
132
+ ```
133
+
134
+ Runtime Snapshot 只保存资源引用;上传、存储和权限签发由宿主负责。
135
+
136
+ ## 主题和 SVG 导出
137
+
138
+ ```vue
139
+ <BpmnStudio
140
+ ref="studioRef"
141
+ :xml="xml"
142
+ :theme="{
143
+ mode: 'auto',
144
+ dark: {
145
+ colors: {
146
+ canvas: '#0b1018',
147
+ surface: '#18202b',
148
+ },
149
+ },
150
+ }"
151
+ />
152
+ ```
153
+
154
+ ```js
155
+ studioRef.value?.openSvgExportPreview({
156
+ theme: 'current',
157
+ filename: '采购申请审批流程.svg',
158
+ })
159
+ ```
160
+
161
+ 主题属性变化会更新现有实例,不会重新挂载画布、清除选择或关闭详情。
162
+
163
+ ## Properties 与自定义 Renderer
164
+
165
+ 本包还导出:
166
+
167
+ - `BpmnDesigner`、`BpmnCanvas`、`BpmnPropertiesPanel`
168
+ - `useBpmnStudio`
169
+ - `createVuePropertyComponent`
170
+ - `createVueRuntimeDetailsComponent`
171
+ - `createVueRuntimeTransitionDetailsComponent`
172
+ - `createVueRuntimeTimelineComponent`
173
+
174
+ ## 常见问题
175
+
176
+ - **无高度:** 为外层容器设置明确高度。
177
+ - **无样式:** 导入一次 `@bpmn-nova/vue/styles.css`。
178
+ - **Vue 重复:** Vue 应由宿主提供并保持单实例。
179
+ - **SSR:** 将可视组件放到客户端挂载阶段。
180
+ - **包选择:** Vue 项目只直接安装 `@bpmn-nova/vue`。
181
+
182
+ ## 文档与 AI
183
+
184
+ - [完整项目能力](https://github.com/daxiangme/bpmn-nova)
185
+ - [React/Vue 组件说明](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/COMPONENTS.md)
186
+ - [公开 Interface](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/API.md)
187
+ - [AI 接入入口](https://raw.githubusercontent.com/daxiangme/bpmn-nova/dev/llms.txt)
188
+ - [AI 完整上下文](https://raw.githubusercontent.com/daxiangme/bpmn-nova/dev/llms-full.txt)
189
+
190
+ ## License
191
+
192
+ Apache-2.0. See [LICENSE](LICENSE).
@@ -0,0 +1,167 @@
1
+ import type { Component, DefineComponent } from 'vue'
2
+ import type { BpmnEdge, BpmnNode, ElementSelection, EngineId, LayoutOptions, ProcessModel } from '@bpmn-nova/studio'
3
+ import type { BpmnDesigner as DesignerInstance } from '@bpmn-nova/studio/designer'
4
+ import type { IconRegistry } from '@bpmn-nova/studio'
5
+ import type { PaletteRegistry } from '@bpmn-nova/studio'
6
+ import type { PropertiesContext, PropertiesProvider, PropertiesRegistry, PropertyEntry } from '@bpmn-nova/studio'
7
+ import type { ProcessInstanceSnapshot } from '@bpmn-nova/studio/runtime'
8
+ import type { SvgExportArtifact, SvgExportOptions, SvgExportPreviewController } from '@bpmn-nova/studio/export-svg'
9
+ import type {
10
+ BpmnCanvas as CanvasInstance,
11
+ BpmnStudioController,
12
+ BpmnStudioShell,
13
+ InteractionController,
14
+ StudioCommands,
15
+ StudioShellOptions,
16
+ StudioShellSlots,
17
+ StudioState,
18
+ TemplateRegistry,
19
+ } from '@bpmn-nova/studio'
20
+ import type { NovaThemeInput, NovaThemeState, RuntimeAppearanceOptions } from '@bpmn-nova/studio/theme'
21
+ import type {
22
+ BpmnViewer as ViewerInstance,
23
+ RuntimeDetailsRenderer,
24
+ RuntimeTimelineRenderer,
25
+ RuntimeTraceClickEvent,
26
+ RuntimeTransitionDetailsRenderer,
27
+ ViewerOptions,
28
+ ViewerProjection,
29
+ } from '@bpmn-nova/studio/viewer'
30
+
31
+ export interface BpmnNovaVisualProps {
32
+ theme?: NovaThemeInput
33
+ runtimeAppearance?: RuntimeAppearanceOptions
34
+ onThemeChange?: (state: NovaThemeState) => void
35
+ }
36
+ export interface BpmnModelProps { model?: ProcessModel; xml?: string; engine?: EngineId }
37
+ export interface UseBpmnStudioOptions extends BpmnModelProps { studio?: BpmnStudioController; propertiesProfile?: 'business' | 'developer' }
38
+ export interface UseBpmnStudioResult { studio: BpmnStudioController; state: StudioState; commands: StudioCommands }
39
+ export function useBpmnStudio(options?: UseBpmnStudioOptions): UseBpmnStudioResult
40
+
41
+ export interface BpmnCanvasProps extends BpmnNovaVisualProps {
42
+ studio: BpmnStudioController
43
+ interactions?: InteractionController
44
+ templates?: TemplateRegistry
45
+ iconRegistry?: IconRegistry
46
+ nodeRenderers?: Record<string, Function>
47
+ nodeRenderer?: Function
48
+ svgExport?: ViewerOptions['svgExport']
49
+ }
50
+ export interface BpmnCanvasExposed {
51
+ getCanvas(): CanvasInstance | null
52
+ fitView(): void
53
+ setTheme(theme: NovaThemeInput): NovaThemeState | undefined
54
+ exportSvg(options?: SvgExportOptions): Promise<SvgExportArtifact> | undefined
55
+ openSvgExportPreview(options?: SvgExportOptions & { previewTitle?: string; onDownload?: (artifact: SvgExportArtifact) => void }): SvgExportPreviewController | undefined
56
+ }
57
+ export const BpmnCanvas: DefineComponent<BpmnCanvasProps>
58
+
59
+ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
60
+ studio?: BpmnStudioController
61
+ propertiesProfile?: 'business' | 'developer'
62
+ iconRegistry?: IconRegistry
63
+ paletteRegistry?: PaletteRegistry
64
+ propertiesRegistry?: PropertiesRegistry
65
+ templateRegistry?: TemplateRegistry
66
+ nodeRenderers?: Record<string, Function>
67
+ nodeRenderer?: Function
68
+ slotsConfig?: StudioShellSlots
69
+ layout?: StudioShellOptions['layout']
70
+ leftWidth?: number
71
+ rightWidth?: number
72
+ runtime?: ProcessInstanceSnapshot | null
73
+ runtimePresenter?: ViewerOptions['runtimePresenter']
74
+ runtimeTraceProjector?: ViewerOptions['runtimeTraceProjector']
75
+ runtimeAssetResolver?: ViewerOptions['runtimeAssetResolver']
76
+ runtimeTimelineRenderer?: RuntimeTimelineRenderer
77
+ svgExport?: ViewerOptions['svgExport']
78
+ onRuntimeTraceItemClick?: ViewerOptions['onRuntimeTraceItemClick']
79
+ runtimeDetailsRenderer?: RuntimeDetailsRenderer | null
80
+ onRuntimeDetailsOpen?: ViewerOptions['onRuntimeDetailsOpen']
81
+ runtimeTransitionDetailsRenderer?: RuntimeTransitionDetailsRenderer | null
82
+ onRuntimeTransitionDetailsOpen?: ViewerOptions['onRuntimeTransitionDetailsOpen']
83
+ timeline?: ViewerOptions['timeline']
84
+ runtimeDetails?: ViewerOptions['runtimeDetails']
85
+ runtimeTraceOptions?: ViewerOptions['runtimeTraceOptions']
86
+ onTraceClick?: (event: RuntimeTraceClickEvent) => void
87
+ onElementClick?: ViewerOptions['onElementClick']
88
+ mode?: 'design' | 'viewer' | 'instance'
89
+ projection?: ViewerProjection
90
+ responsive?: boolean
91
+ projectionOptions?: Array<{ value: ViewerProjection; label: string }>
92
+ }
93
+ export interface BpmnStudioExposed {
94
+ getStudio(): BpmnStudioController
95
+ getShell(): BpmnStudioShell | null
96
+ exportXml(engine?: EngineId): string
97
+ fitView(): void
98
+ setTheme(theme: NovaThemeInput): NovaThemeState | undefined
99
+ exportSvg(options?: SvgExportOptions): Promise<SvgExportArtifact> | undefined
100
+ openSvgExportPreview(options?: SvgExportOptions & { previewTitle?: string; onDownload?: (artifact: SvgExportArtifact) => void }): SvgExportPreviewController | null | undefined
101
+ }
102
+ export const BpmnStudio: DefineComponent<BpmnStudioProps>
103
+
104
+ export interface BpmnDesignerProps extends BpmnNovaVisualProps, BpmnModelProps { svgExport?: ViewerOptions['svgExport'] }
105
+ export interface BpmnDesignerExposed {
106
+ getInstance(): DesignerInstance | null
107
+ exportXml(engine?: EngineId): string
108
+ fitView(): void
109
+ beautify(options?: LayoutOptions): void
110
+ rerouteEdges(options?: LayoutOptions): void
111
+ validate(): Array<{ level: 'error' | 'warning'; elementId?: string; message: string }>
112
+ setTheme(theme: NovaThemeInput): NovaThemeState | undefined
113
+ exportSvg(options?: SvgExportOptions): Promise<SvgExportArtifact> | undefined
114
+ openSvgExportPreview(options?: SvgExportOptions & { previewTitle?: string; onDownload?: (artifact: SvgExportArtifact) => void }): SvgExportPreviewController | undefined
115
+ }
116
+ export const BpmnDesigner: DefineComponent<BpmnDesignerProps>
117
+
118
+ export interface BpmnViewerProps extends BpmnNovaVisualProps, BpmnModelProps, Omit<ViewerOptions, 'container' | 'model' | 'theme' | 'runtimeAppearance' | 'onThemeChange'> {}
119
+ export interface BpmnViewerExposed {
120
+ getInstance(): ViewerInstance | null
121
+ fitView(): void
122
+ setProjection(projection: ViewerProjection): void
123
+ setDisplayOptions(options: Parameters<ViewerInstance['setDisplayOptions']>[0]): void
124
+ setTheme(theme: NovaThemeInput): NovaThemeState | undefined
125
+ exportSvg(options?: SvgExportOptions): Promise<SvgExportArtifact> | undefined
126
+ openSvgExportPreview(options?: SvgExportOptions & { previewTitle?: string; onDownload?: (artifact: SvgExportArtifact) => void }): SvgExportPreviewController | undefined
127
+ }
128
+ export const BpmnViewer: DefineComponent<BpmnViewerProps>
129
+
130
+ export interface VuePropertyComponentProps {
131
+ entry: PropertyEntry
132
+ context: PropertiesContext
133
+ value: unknown
134
+ onChange(value: unknown): void
135
+ }
136
+ export function createVuePropertyComponent(component: Component<VuePropertyComponentProps>): (context: Record<string, unknown>) => void | (() => void)
137
+ export interface BpmnPropertiesPanelProps extends BpmnNovaVisualProps {
138
+ studio?: BpmnStudioController
139
+ designer?: BpmnStudioController | DesignerInstance
140
+ registry?: PropertiesRegistry
141
+ providers?: PropertiesProvider[]
142
+ dataProviders?: Record<string, unknown>
143
+ components?: Record<string, Component<VuePropertyComponentProps>>
144
+ }
145
+ export interface BpmnPropertiesPanelExposed {
146
+ getPanel(): unknown
147
+ getRegistry(): PropertiesRegistry | null
148
+ render(): void
149
+ setTheme(theme: NovaThemeInput): NovaThemeState | undefined
150
+ }
151
+ export const BpmnPropertiesPanel: DefineComponent<BpmnPropertiesPanelProps>
152
+
153
+ export function createVueRuntimeDetailsComponent(component: Component<Omit<Parameters<RuntimeDetailsRenderer>[0], 'container'>>): RuntimeDetailsRenderer
154
+ export function createVueRuntimeTransitionDetailsComponent(component: Component<Omit<Parameters<RuntimeTransitionDetailsRenderer>[0], 'container'>>): RuntimeTransitionDetailsRenderer
155
+ export function createVueRuntimeTimelineComponent(component: Component<Omit<Parameters<RuntimeTimelineRenderer>[0], 'container'>>): RuntimeTimelineRenderer
156
+
157
+ export interface BpmnStudioEmits {
158
+ change: [model: ProcessModel, reason: string, xml: string]
159
+ 'selection-change': [selection: ElementSelection | null, element: StudioState['selectedElement']]
160
+ 'scope-change': [activeScopeId: string, scopePath: StudioState['scopePath'], state: StudioState]
161
+ 'element-click': [payload: Parameters<NonNullable<ViewerOptions['onElementClick']>>[0]]
162
+ 'trace-click': [payload: RuntimeTraceClickEvent]
163
+ }
164
+ export interface BpmnDesignerEmits {
165
+ change: [model: ProcessModel, reason: string, xml: string]
166
+ 'selection-change': [selection: ElementSelection | null, element: BpmnNode | BpmnEdge | null]
167
+ }
package/dist/index.js ADDED
@@ -0,0 +1,287 @@
1
+ import { createApp, defineComponent, h, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
2
+ import { BpmnDesigner as CoreDesigner } from '@bpmn-nova/studio/designer';
3
+ import { BpmnViewer as CoreViewer } from '@bpmn-nova/studio/viewer';
4
+ import { importBpmn, exportBpmn } from '@bpmn-nova/studio';
5
+ import { createEmptyProcess } from '@bpmn-nova/studio';
6
+ import { BpmnCanvas as CoreCanvas, BpmnStudioShell as CoreStudioShell, createInteractionController, createStudioController, createTemplateRegistry } from '@bpmn-nova/studio';
7
+
8
+ export function createVueRuntimeDetailsComponent(Component) {
9
+ return ({ container, ...context }) => {
10
+ const app = createApp(Component, context);
11
+ app.mount(container);
12
+ return () => app.unmount();
13
+ };
14
+ }
15
+
16
+ export function createVueRuntimeTransitionDetailsComponent(Component) {
17
+ return createVueRuntimeDetailsComponent(Component);
18
+ }
19
+
20
+ export function createVueRuntimeTimelineComponent(Component) {
21
+ return createVueRuntimeDetailsComponent(Component);
22
+ }
23
+
24
+ function resolveModel(props) {
25
+ if (props.model) return props.model;
26
+ if (props.xml) return importBpmn(props.xml, props.engine);
27
+ return createEmptyProcess(props.engine || 'flowable');
28
+ }
29
+
30
+ export function useBpmnStudio(options = {}) {
31
+ const studio = options.studio || createStudioController({ model: resolveModel(options), propertiesProfile: options.propertiesProfile });
32
+ const state = reactive(studio.getState());
33
+ const off = studio.subscribe((event) => Object.assign(state, event.state));
34
+ onBeforeUnmount(() => { off(); if (!options.studio) studio.destroy(); });
35
+ return { studio, state, commands: studio.commands };
36
+ }
37
+
38
+ export const BpmnCanvas = defineComponent({
39
+ name: 'BpmnNovaCanvas',
40
+ props: { studio: { type: Object, required: true }, interactions: Object, templates: Object, iconRegistry: Object, nodeRenderers: Object, nodeRenderer: Function, svgExport: Object, theme: [Object, String], onThemeChange: Function },
41
+ setup(props, { expose, attrs }) {
42
+ const host = ref(null);
43
+ let canvas = null;
44
+ onMounted(() => {
45
+ const interactions = props.interactions || createInteractionController({ studio: props.studio, templates: props.templates || createTemplateRegistry() });
46
+ 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 } });
47
+ requestAnimationFrame(() => canvas.fitView());
48
+ });
49
+ onBeforeUnmount(() => canvas?.destroy());
50
+ watch(() => props.theme, (value) => { if (value !== undefined) canvas?.setTheme(value); });
51
+ expose({ getCanvas: () => canvas, fitView: () => canvas?.fitView(), setTheme: (value) => canvas?.setTheme(value), exportSvg: (options) => canvas?.exportSvg(options), openSvgExportPreview: (options) => canvas?.openSvgExportPreview(options) });
52
+ return () => h('div', { ...attrs, ref: host, style: { width: '100%', height: '100%', minHeight: '320px', ...(attrs.style || {}) } });
53
+ },
54
+ });
55
+
56
+ export const BpmnStudio = defineComponent({
57
+ name: 'BpmnNovaStudio',
58
+ props: {
59
+ studio: Object, model: Object, xml: String, engine: { type: String, default: 'flowable' }, propertiesProfile: String,
60
+ iconRegistry: Object, paletteRegistry: Object, propertiesRegistry: Object, templateRegistry: Object,
61
+ nodeRenderers: Object, nodeRenderer: Function, slotsConfig: Object, layout: Function, leftWidth: Number, rightWidth: Number,
62
+ runtime: Object, runtimePresenter: Function, runtimeTraceProjector: Function, runtimeAssetResolver: Function, runtimeTimelineRenderer: Function, onRuntimeTraceItemClick: Function, runtimeDetailsRenderer: Function, onRuntimeDetailsOpen: Function,
63
+ runtimeTransitionDetailsRenderer: Function, onRuntimeTransitionDetailsOpen: Function,
64
+ timeline: Object, runtimeDetails: Object, runtimeTraceOptions: Object, onTraceClick: Function, onElementClick: Function,
65
+ mode: String, projection: String, responsive: { type: Boolean, default: false }, projectionOptions: Array,
66
+ theme: [Object, String], runtimeAppearance: Object, svgExport: Object, onThemeChange: Function,
67
+ },
68
+ emits: ['change', 'selection-change', 'scope-change', 'element-click', 'trace-click'],
69
+ setup(props, { emit, expose, attrs }) {
70
+ const host = ref(null);
71
+ const owned = !props.studio;
72
+ const studio = props.studio || createStudioController({ model: resolveModel(props), propertiesProfile: props.propertiesProfile });
73
+ let shell = null;
74
+ let off = null;
75
+ onMounted(() => {
76
+ shell = new CoreStudioShell({ container: host.value, studio, iconRegistry: props.iconRegistry, paletteRegistry: props.paletteRegistry, propertiesRegistry: props.propertiesRegistry, templateRegistry: props.templateRegistry, rendererOptions: { nodeRenderers: props.nodeRenderers, nodeRenderer: props.nodeRenderer, 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: props.slotsConfig || {}, layout: props.layout, runtime: props.runtime, mode: props.mode, 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 });
77
+ off = studio.subscribe((event) => {
78
+ if (event.type === 'modelChanged') emit('change', studio.model, event.reason, studio.exportXml());
79
+ if (event.type === 'selectionChanged') emit('selection-change', studio.selection, studio.getSelectedElement());
80
+ if (event.type === 'scopeChanged') emit('scope-change', event.activeScopeId, event.scopePath, studio.getState());
81
+ });
82
+ });
83
+ watch(() => props.model, (value) => { if (value && value !== studio.model) studio.setModel(value); });
84
+ watch(() => props.xml, (value) => { if (value) studio.importXml(value, props.engine); });
85
+ watch(() => props.runtime, (value) => { if (value !== undefined) shell?.setRuntime(value); });
86
+ watch(() => props.mode, (value) => { if (value) shell?.setMode(value); });
87
+ watch(() => props.projection, (value) => { if (value) shell?.setProjection(value); });
88
+ watch(() => props.theme, (value) => { if (value !== undefined) shell?.setTheme(value); });
89
+ watch(() => props.runtimeAppearance, (value) => { if (value !== undefined) shell?.setRuntimeAppearance(value); });
90
+ watch(() => [props.timeline, props.runtimeDetails, props.runtimeTraceOptions], ([timeline, runtimeDetails, runtimeTraceOptions]) => {
91
+ if (!shell) return;
92
+ shell.rendererOptions.timeline = timeline;
93
+ shell.rendererOptions.runtimeDetails = runtimeDetails;
94
+ shell.rendererOptions.runtimeTraceOptions = runtimeTraceOptions;
95
+ shell.viewer?.setDisplayOptions({ timeline, runtimeDetails, runtimeTraceOptions });
96
+ });
97
+ onBeforeUnmount(() => { off?.(); shell?.destroy(); if (owned) studio.destroy(); });
98
+ expose({ getStudio: () => studio, getShell: () => shell, exportXml: (engine) => studio.exportXml(engine), exportSvg: (options) => shell?.exportSvg(options), openSvgExportPreview: (options) => shell?.openSvgExportPreview(options), fitView: () => shell?.canvas?.fitView(), setTheme: (value) => shell?.setTheme(value) });
99
+ return () => h('div', { ...attrs, ref: host, style: { width: '100%', height: '100%', minHeight: '520px', ...(attrs.style || {}) } });
100
+ },
101
+ });
102
+
103
+ export const BpmnDesigner = defineComponent({
104
+ name: 'ModernBpmnDesigner',
105
+ props: {
106
+ xml: String,
107
+ model: Object,
108
+ engine: { type: String, default: 'flowable' },
109
+ theme: [Object, String],
110
+ svgExport: Object,
111
+ onThemeChange: Function,
112
+ },
113
+ emits: ['change', 'selection-change'],
114
+ setup(props, { emit, expose, attrs }) {
115
+ const host = ref(null);
116
+ let instance = null;
117
+
118
+ onMounted(() => {
119
+ instance = new CoreDesigner({
120
+ container: host.value,
121
+ model: resolveModel(props),
122
+ onChange(model, reason) { emit('change', model, reason, exportBpmn(model, model.engine)); },
123
+ onSelectionChange(selection, element) { emit('selection-change', selection, element); },
124
+ theme: props.theme,
125
+ svgExport: props.svgExport,
126
+ onThemeChange: props.onThemeChange,
127
+ });
128
+ requestAnimationFrame(() => instance.renderer.fitView());
129
+ });
130
+ onBeforeUnmount(() => instance?.destroy());
131
+
132
+ watch(() => props.engine, (value) => {
133
+ if (instance && value && value !== instance.model.engine) instance.setEngine(value);
134
+ });
135
+ watch(() => props.model, (value) => { if (instance && value && value !== instance.model) instance.setModel(value); });
136
+ watch(() => props.xml, (value) => { if (instance && value) instance.setModel(importBpmn(value, props.engine)); });
137
+ watch(() => props.theme, (value) => { if (value !== undefined) instance?.setTheme(value); });
138
+
139
+ expose({
140
+ getInstance: () => instance,
141
+ exportXml: (engine) => instance?.exportXml(engine) || '',
142
+ fitView: () => instance?.renderer.fitView(),
143
+ beautify: (options) => instance?.beautify(options),
144
+ rerouteEdges: (options) => instance?.rerouteEdges(options),
145
+ validate: () => instance?.validate() || [],
146
+ setTheme: (value) => instance?.setTheme(value),
147
+ exportSvg: (options) => instance?.exportSvg(options),
148
+ openSvgExportPreview: (options) => instance?.openSvgExportPreview(options),
149
+ });
150
+
151
+ return () => h('div', { ...attrs, ref: host, style: { width: '100%', height: '100%', minHeight: '320px', ...(attrs.style || {}) } });
152
+ },
153
+ });
154
+
155
+ export const BpmnViewer = defineComponent({
156
+ name: 'ModernBpmnViewer',
157
+ props: {
158
+ xml: String,
159
+ model: Object,
160
+ engine: { type: String, default: 'flowable' },
161
+ runtime: Object,
162
+ iconRegistry: Object,
163
+ nodeRenderers: Object,
164
+ nodeRenderer: Function,
165
+ timeline: Object,
166
+ runtimeDetails: Object,
167
+ runtimePresenter: Function,
168
+ runtimeTraceProjector: Function,
169
+ runtimeAssetResolver: Function,
170
+ runtimeTraceOptions: Object,
171
+ runtimeTimelineRenderer: Function,
172
+ onRuntimeTraceItemClick: Function,
173
+ onProjectionChange: Function,
174
+ runtimeDetailsRenderer: Function,
175
+ onRuntimeDetailsOpen: Function,
176
+ runtimeTransitionDetailsRenderer: Function,
177
+ onRuntimeTransitionDetailsOpen: Function,
178
+ onTraceClick: Function,
179
+ responsive: { type: Boolean, default: false },
180
+ projection: { type: String, default: undefined },
181
+ theme: [Object, String],
182
+ runtimeAppearance: Object,
183
+ svgExport: Object,
184
+ onThemeChange: Function,
185
+ },
186
+ emits: ['element-click', 'trace-click'],
187
+ setup(props, { emit, expose, attrs }) {
188
+ const host = ref(null);
189
+ let instance = null;
190
+ onMounted(() => {
191
+ instance = new CoreViewer({
192
+ container: host.value,
193
+ model: resolveModel(props),
194
+ runtime: props.runtime,
195
+ iconRegistry: props.iconRegistry,
196
+ nodeRenderers: props.nodeRenderers,
197
+ nodeRenderer: props.nodeRenderer,
198
+ timeline: props.timeline,
199
+ runtimeDetails: props.runtimeDetails,
200
+ runtimePresenter: props.runtimePresenter,
201
+ runtimeTraceProjector: props.runtimeTraceProjector,
202
+ runtimeAssetResolver: props.runtimeAssetResolver,
203
+ runtimeTraceOptions: props.runtimeTraceOptions,
204
+ runtimeTimelineRenderer: props.runtimeTimelineRenderer,
205
+ onRuntimeTraceItemClick: (payload) => props.onRuntimeTraceItemClick?.(payload),
206
+ onProjectionChange: (payload) => props.onProjectionChange?.(payload),
207
+ runtimeDetailsRenderer: props.runtimeDetailsRenderer,
208
+ onRuntimeDetailsOpen: (payload) => props.onRuntimeDetailsOpen?.(payload),
209
+ runtimeTransitionDetailsRenderer: props.runtimeTransitionDetailsRenderer,
210
+ onRuntimeTransitionDetailsOpen: (payload) => props.onRuntimeTransitionDetailsOpen?.(payload),
211
+ projection: props.projection,
212
+ responsive: props.responsive,
213
+ onElementClick(payload) { emit('element-click', payload); },
214
+ onTraceClick(payload) { emit('trace-click', payload); },
215
+ theme: props.theme,
216
+ runtimeAppearance: props.runtimeAppearance,
217
+ svgExport: props.svgExport,
218
+ onThemeChange: props.onThemeChange,
219
+ });
220
+ requestAnimationFrame(() => instance.fitView());
221
+ });
222
+ onBeforeUnmount(() => instance?.destroy());
223
+ watch(() => props.runtime, (value) => instance?.setRuntime(value));
224
+ watch(() => props.projection, (value) => instance?.setProjection(value));
225
+ watch(() => props.theme, (value) => { if (value !== undefined) instance?.setTheme(value); });
226
+ watch(() => props.runtimeAppearance, (value) => { if (value !== undefined) instance?.setRuntimeAppearance(value); });
227
+ watch(() => [props.timeline, props.runtimeDetails, props.runtimeTraceOptions, props.runtimeAssetResolver], ([timeline, runtimeDetails, runtimeTraceOptions, runtimeAssetResolver]) => instance?.setDisplayOptions({ timeline, runtimeDetails, runtimeTraceOptions, runtimeAssetResolver }));
228
+ watch(() => props.model, (value) => { if (value) instance?.setModel(value); });
229
+ watch(() => props.xml, (value) => { if (value) instance?.setModel(importBpmn(value, props.engine)); });
230
+ 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) });
231
+ return () => h('div', { ...attrs, ref: host, style: { width: '100%', height: '100%', minHeight: '260px', ...(attrs.style || {}) } });
232
+ },
233
+ });
234
+
235
+
236
+ // Properties Panel -----------------------------------------------------------
237
+ import { createDefaultPropertiesRegistry, PropertiesPanel as CorePropertiesPanel } from '@bpmn-nova/studio';
238
+
239
+ export function createVuePropertyComponent(Component) {
240
+ return ({ container, entry, context, value, commit }) => {
241
+ const app = createApp(Component, { entry, context, value, onChange: commit });
242
+ app.mount(container);
243
+ return () => app.unmount();
244
+ };
245
+ }
246
+
247
+ export const BpmnPropertiesPanel = defineComponent({
248
+ name: 'BpmnNovaPropertiesPanel',
249
+ props: {
250
+ studio: Object,
251
+ designer: Object,
252
+ registry: Object,
253
+ providers: Array,
254
+ dataProviders: Object,
255
+ components: Object,
256
+ theme: [Object, String],
257
+ onThemeChange: Function,
258
+ },
259
+ setup(props, { expose, attrs }) {
260
+ const host = ref(null);
261
+ let panel = null;
262
+ let registry = null;
263
+ let offSelection = null;
264
+ let offChange = null;
265
+
266
+ const mountPanel = () => {
267
+ const studio = props.studio || props.designer;
268
+ if (!host.value || !studio) return;
269
+ panel?.destroy();
270
+ offSelection?.();
271
+ offChange?.();
272
+ registry = props.registry || createDefaultPropertiesRegistry({ studio, dataProviders: props.dataProviders || {} });
273
+ for (const provider of props.providers || []) registry.registerProvider(provider.priority ?? 500, provider);
274
+ for (const [name, Component] of Object.entries(props.components || {})) registry.registerComponent(name, createVuePropertyComponent(Component));
275
+ panel = new CorePropertiesPanel({ container: host.value, registry, studio, theme: props.theme, onThemeChange: props.onThemeChange });
276
+ panel.render();
277
+ };
278
+
279
+ onMounted(mountPanel);
280
+ watch(() => props.designer, mountPanel);
281
+ watch(() => props.studio, mountPanel);
282
+ watch(() => props.theme, (value) => { if (value !== undefined) panel?.setTheme(value); });
283
+ onBeforeUnmount(() => { offSelection?.(); offChange?.(); panel?.destroy(); });
284
+ expose({ getPanel: () => panel, getRegistry: () => registry, render: () => panel?.render(), setTheme: (value) => panel?.setTheme(value) });
285
+ return () => h('div', { ...attrs, ref: host, style: { width: '100%', height: '100%', overflow: 'auto', ...(attrs.style || {}) } });
286
+ },
287
+ });