@bpmn-nova/vue 0.3.3-preview → 0.3.5-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 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.3-preview`。Vue 由宿主工程提供,本包不会替应用选择或升级框架版本。
7
+ > 当前版本为 `0.3.5-preview`。Vue 由宿主工程提供,本包不会替应用选择或升级框架版本。
8
8
 
9
9
  ![BPMN Nova Vue 流程工作台](https://raw.githubusercontent.com/daxiangme/bpmn-nova/dev/docs/assets/bpmn-nova-designer.jpg)
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
  ## 流程设计
@@ -32,6 +46,7 @@ import '@bpmn-nova/vue/styles.css'
32
46
 
33
47
  const props = defineProps({ initialXml: String })
34
48
  const studioRef = ref(null)
49
+ const studioConfig = { ui: { controlSize: 'medium' } }
35
50
 
36
51
  function handleChange(model, reason, nextXml) {
37
52
  console.log(reason, nextXml)
@@ -44,6 +59,7 @@ function handleChange(model, reason, nextXml) {
44
59
  ref="studioRef"
45
60
  :xml="props.initialXml"
46
61
  engine="flowable"
62
+ :config="studioConfig"
47
63
  mode="design"
48
64
  :allowed-modes="['design']"
49
65
  theme="auto"
@@ -84,9 +100,10 @@ function handleSelectionChange(selection, element) {
84
100
  engine="activiti"
85
101
  mode="design"
86
102
  :allowed-modes="['design']"
87
- :allowed-node-types="supportedNodeTypes"
88
- :allowed-edge-types="['sequenceFlow']"
89
- :regions="{ right: 'hidden' }"
103
+ :config="{
104
+ modeling: { allowedNodeTypes: supportedNodeTypes, allowedEdgeTypes: ['sequenceFlow'] },
105
+ ui: { controlSize: 'medium', regions: { right: 'hidden' } },
106
+ }"
90
107
  theme="auto"
91
108
  @change="handleChange"
92
109
  @selection-change="handleSelectionChange"
@@ -98,7 +115,7 @@ function handleSelectionChange(selection, element) {
98
115
  <strong>{{ processName }}</strong>
99
116
  <span>{{ processType }}</span>
100
117
  </template>
101
- <template #header-actions="{ actions, mode }">
118
+ <template #header-actions="{ actions, mode, ui }">
102
119
  <button type="button" :disabled="mode !== 'design'" @click="actions.validate()">校验</button>
103
120
  <button type="button" :disabled="mode !== 'design'" @click="saveDraft(actions.exportXml())">保存</button>
104
121
  <button type="button" :disabled="mode !== 'design'" @click="publishProcess(actions)">发布</button>
@@ -127,6 +144,49 @@ async function publishProcess(actions) {
127
144
 
128
145
  编辑器属性面板应订阅 `selection-change`,该事件覆盖节点、连线、多选、清空和键盘选择。`element-click` 是点击观察事件,不能代替选择状态。宿主使用稳定 BPMN Element ID 关联业务配置,并自行拥有保存、发布、权限和服务端事务。
129
146
 
147
+ `config` 收拢 `modeling/ui/viewer/export` 静态选项。顶部控件尺寸使用 `config.ui.controlSize`,默认 `medium`,支持 `small`、`medium`、`large` 和 `24px`–`48px`。Slot Context 的只读 `ui.controlHeight` 可直接适配宿主按钮;Element Plus `default`、Ant Design `middle` 都映射到 Nova `medium`。自定义 px 时也可在 Header Actions 样式中读取 `--nova-control-height` 等公开变量。
148
+
149
+ ```vue
150
+ <BpmnStudio ref="studioRef" :config="{ ui: { controlSize: '36px' } }">
151
+ <template #header-actions="{ actions, ui }">
152
+ <HostActions :actions="actions" :button-height="ui.controlHeight" />
153
+ </template>
154
+ </BpmnStudio>
155
+ ```
156
+
157
+ Expose 同步提供 `getConfig()` / `setConfig()`;响应式更新 UI、Viewer 或 Export 不重建 Canvas。旧顶层配置继续兼容并标记 Deprecated,同项冲突时显式旧值优先。`config.modeling` 只在内部 Controller 创建时建立节点和连线约束。
158
+
159
+ ## 原生右侧 Slot 与折叠
160
+
161
+ 不需要把所有业务面板都放到 Nova 外部。使用 `#right` 可以只替换右侧内容,并保留 Nova 的布局与折叠按钮;它高于 `slotsConfig.right`,通过 Teleport 保留宿主上下文。不要同时设置 `regions.right: 'hidden'`,否则整个区域都隐藏。
162
+
163
+ ```vue
164
+ <BpmnStudio
165
+ ref="studioRef"
166
+ :xml="xml"
167
+ :config="{ ui: {
168
+ leftPanel: { collapsible: true, defaultCollapsed: false },
169
+ rightPanel: { collapsible: true, defaultCollapsed: false, layout: 'flex' },
170
+ } }"
171
+ @sidebar-change="event => console.log(event.side, event.collapsed, event.source)"
172
+ >
173
+ <template #right="{ panelSelection, mode }">
174
+ <header style="flex: none; padding: 12px">业务详情 · {{ mode }}</header>
175
+ <div style="flex: 1; min-height: 0; overflow: auto; padding: 12px">
176
+ {{ panelSelection.selectedElement?.id ?? '未选择元素' }}
177
+ <!-- 填入宿主表单 -->
178
+ </div>
179
+ <footer style="flex: none; padding: 12px">宿主固定操作区</footer>
180
+ </template>
181
+ </BpmnStudio>
182
+ ```
183
+
184
+ 默认 `flex` 由宿主安排内部滚动;`scroll` 由 Nova 滚动整个内容,宿主只填普通内容。父容器仍需可计算高度。收起不卸载 Slot,不丢失表单/滚动状态、不自动 Fit;左右分隔线中点按钮支持键盘,200ms 动画尊重减少动态效果。容器 ≤720px 时首次默认收起,按钮保留,展开挤压画布而非抽屉,宽窄屏分别记忆状态。
185
+
186
+ Expose 增加 `getSidebarState()`、`setSidebarCollapsed(side, collapsed)`、`subscribeSidebarChange()`、`getPanelSelection()`、`subscribePanelSelection()`;设置仅在实际切换时返回 `true`。Header/Right Slot 共享响应式 `panelSelection`(`selection`、`selectedElement`、`trace`),只读轨迹选择不回写设计选择。
187
+
188
+ Instance 默认隐藏右侧且没有展开按钮;需要时显式设置 `config.ui.regions.right: 'default'`。新增能力自 `0.3.5-preview` 提供。完整示例见[侧栏指南](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/CUSTOMIZATION.md#侧栏布局与平滑折叠)或包内 `llms-full.txt`。
189
+
130
190
  ## Mode 双向同步与副标题刷新
131
191
 
132
192
  ```vue
@@ -165,6 +225,8 @@ Expose 的 `getMode()` / `setMode()` 使用 Shell 实际状态。Resolver 返回
165
225
 
166
226
  ## 只读展示与审批轨迹
167
227
 
228
+ 真实后端的数据格式与接入步骤见[审批轨迹接入指南](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/RUNTIME-INTEGRATION.md),完整正文也包含在包内 `llms-full.txt`。Vue 根入口的 `ProcessInstanceSnapshot`、Activity/Action/Transition、`RuntimeTraceClickEvent` 与资源 Resolver 等 14 个 Runtime 类型出口自 `0.3.5-preview` 提供;旧版可按指南从 `BpmnStudioProps['runtime']` 推导,无需增加 Studio 直依赖。
229
+
168
230
  ```vue
169
231
  <script setup>
170
232
  import { BpmnViewer } from '@bpmn-nova/vue'
@@ -301,6 +363,7 @@ studioRef.value?.openSvgExportPreview({
301
363
  - [完整项目能力](https://github.com/daxiangme/bpmn-nova)
302
364
  - [React/Vue 组件说明](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/COMPONENTS.md)
303
365
  - [公开 Interface](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/API.md)
366
+ - [审批轨迹接入指南](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/RUNTIME-INTEGRATION.md)
304
367
 
305
368
  ## License
306
369
 
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ import type { ProcessInstanceSnapshot } from '@bpmn-nova/studio/runtime'
14
14
  import type { SvgExportArtifact, SvgExportOptions, SvgExportPreviewController } from '@bpmn-nova/studio/export-svg'
15
15
  import type {
16
16
  BpmnCanvas as CanvasInstance,
17
+ BpmnStudioConfig,
17
18
  BpmnStudioController,
18
19
  BpmnStudioShell,
19
20
  ContextMenuRegistry,
@@ -22,6 +23,10 @@ import type {
22
23
  StudioCommands,
23
24
  StudioMode,
24
25
  StudioModeChangeEvent,
26
+ StudioPanelSelection,
27
+ StudioSidebarChangeEvent,
28
+ StudioSidebarSide,
29
+ StudioSidebarState,
25
30
  StudioValidationEvent,
26
31
  StudioValidationIssue,
27
32
  StudioShellActions,
@@ -29,6 +34,7 @@ import type {
29
34
  StudioShellRegions,
30
35
  StudioShellSlots,
31
36
  StudioState,
37
+ StudioUiContext,
32
38
  TemplateRegistry,
33
39
  } from '@bpmn-nova/studio'
34
40
  import type { NovaThemeInput, NovaThemeState, RuntimeAppearanceOptions } from '@bpmn-nova/studio/theme'
@@ -43,13 +49,57 @@ import type {
43
49
  } from '@bpmn-nova/studio/viewer'
44
50
 
45
51
  export type {
52
+ BpmnEdge,
53
+ BpmnNode,
54
+ EdgeType,
55
+ ElementSelection,
56
+ EngineId,
57
+ LayoutOptions,
58
+ NodeType,
59
+ ProcessModel,
60
+ } from '@bpmn-nova/studio'
61
+ export type {
62
+ BpmnStudioConfig,
46
63
  NodeSubtitleResolver,
47
64
  NodeSubtitleResolverContext,
65
+ StudioControlSize,
66
+ StudioModelingConfig,
67
+ StudioPanelSelection,
68
+ StudioRightPanelConfig,
69
+ StudioSidebarChangeEvent,
70
+ StudioSidebarChangeSource,
71
+ StudioSidebarConfig,
72
+ StudioSidebarSide,
73
+ StudioSidebarState,
74
+ StudioSvgExportOptions,
75
+ StudioUiConfig,
76
+ StudioUiContext,
48
77
  StudioValidationEvent,
49
78
  StudioValidationIssue,
50
79
  StudioValidationSource,
80
+ StudioViewerConfig,
51
81
  } from '@bpmn-nova/studio'
52
82
 
83
+ /** Runtime integration facade. Added in 0.3.5-preview; no runtime JavaScript exports. */
84
+ export type {
85
+ ActivityInstance,
86
+ ActivityStatus,
87
+ ProcessInstanceSnapshot,
88
+ RuntimeApprovalAction,
89
+ RuntimeApprovalActionType,
90
+ RuntimeApprovalContent,
91
+ RuntimeApprovalContentBlock,
92
+ RuntimeAssetRef,
93
+ RuntimeEdgeVisit,
94
+ RuntimeParticipant,
95
+ RuntimeTransition,
96
+ } from '@bpmn-nova/studio/runtime'
97
+ export type {
98
+ RuntimeAssetPurpose,
99
+ RuntimeAssetResolver,
100
+ RuntimeTraceClickEvent,
101
+ } from '@bpmn-nova/studio/viewer'
102
+
53
103
  export {
54
104
  createContextMenuRegistry,
55
105
  createDefaultContextMenuRegistry,
@@ -76,7 +126,16 @@ export interface BpmnNovaVisualProps {
76
126
  onThemeChange?: (state: NovaThemeState) => void
77
127
  }
78
128
  export interface BpmnModelProps { model?: ProcessModel; xml?: string; engine?: EngineId }
79
- export interface UseBpmnStudioOptions extends BpmnModelProps { studio?: BpmnStudioController; propertiesProfile?: 'business' | 'developer'; allowedNodeTypes?: NodeType[]; allowedEdgeTypes?: EdgeType[] }
129
+ export interface UseBpmnStudioOptions extends BpmnModelProps {
130
+ studio?: BpmnStudioController
131
+ config?: BpmnStudioConfig
132
+ /** @deprecated Use config.modeling.propertiesProfile. */
133
+ propertiesProfile?: 'business' | 'developer'
134
+ /** @deprecated Use config.modeling.allowedNodeTypes. */
135
+ allowedNodeTypes?: NodeType[]
136
+ /** @deprecated Use config.modeling.allowedEdgeTypes. */
137
+ allowedEdgeTypes?: EdgeType[]
138
+ }
80
139
  export interface UseBpmnStudioResult { studio: BpmnStudioController; state: StudioState; commands: StudioCommands }
81
140
  export function useBpmnStudio(options?: UseBpmnStudioOptions): UseBpmnStudioResult
82
141
 
@@ -120,8 +179,12 @@ export const BpmnPalettePanel: DefineComponent<BpmnPalettePanelProps>
120
179
 
121
180
  export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
122
181
  studio?: BpmnStudioController
182
+ config?: BpmnStudioConfig
183
+ /** @deprecated Use config.modeling.propertiesProfile. */
123
184
  propertiesProfile?: 'business' | 'developer'
185
+ /** @deprecated Use config.modeling.allowedNodeTypes. */
124
186
  allowedNodeTypes?: NodeType[]
187
+ /** @deprecated Use config.modeling.allowedEdgeTypes. */
125
188
  allowedEdgeTypes?: EdgeType[]
126
189
  iconRegistry?: IconRegistry
127
190
  paletteRegistry?: PaletteRegistry
@@ -133,39 +196,56 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
133
196
  nodeSubtitleResolver?: NodeSubtitleResolver
134
197
  slotsConfig?: StudioShellSlots
135
198
  layout?: StudioShellOptions['layout']
199
+ /** @deprecated Use config.ui.regions. */
136
200
  regions?: StudioShellRegions
201
+ /** @deprecated Use config.ui.sidebarWidth.left. */
137
202
  leftWidth?: number
203
+ /** @deprecated Use config.ui.sidebarWidth.right. */
138
204
  rightWidth?: number
139
205
  runtime?: ProcessInstanceSnapshot | null
140
206
  runtimePresenter?: ViewerOptions['runtimePresenter']
141
207
  runtimeTraceProjector?: ViewerOptions['runtimeTraceProjector']
142
208
  runtimeAssetResolver?: ViewerOptions['runtimeAssetResolver']
143
209
  runtimeTimelineRenderer?: RuntimeTimelineRenderer
210
+ /** @deprecated Use config.export. */
144
211
  svgExport?: ViewerOptions['svgExport']
145
212
  onRuntimeTraceItemClick?: ViewerOptions['onRuntimeTraceItemClick']
146
213
  runtimeDetailsRenderer?: RuntimeDetailsRenderer | null
147
214
  onRuntimeDetailsOpen?: ViewerOptions['onRuntimeDetailsOpen']
148
215
  runtimeTransitionDetailsRenderer?: RuntimeTransitionDetailsRenderer | null
149
216
  onRuntimeTransitionDetailsOpen?: ViewerOptions['onRuntimeTransitionDetailsOpen']
217
+ /** @deprecated Use config.viewer.timeline. */
150
218
  timeline?: ViewerOptions['timeline']
219
+ /** @deprecated Use config.viewer.runtimeDetails. */
151
220
  runtimeDetails?: ViewerOptions['runtimeDetails']
221
+ /** @deprecated Use config.viewer.runtimeTraceOptions. */
152
222
  runtimeTraceOptions?: ViewerOptions['runtimeTraceOptions']
153
223
  onTraceClick?: (event: RuntimeTraceClickEvent) => void
154
224
  onElementClick?: ViewerOptions['onElementClick']
155
225
  mode?: StudioMode
156
226
  allowedModes?: readonly StudioMode[]
157
227
  projection?: ViewerProjection
228
+ /** @deprecated Use config.viewer.responsive. */
158
229
  responsive?: boolean
230
+ /** @deprecated Use config.viewer.projectionOptions. */
159
231
  projectionOptions?: Array<{ value: ViewerProjection; label: string }>
232
+ onSidebarChange?: (event: StudioSidebarChangeEvent) => void
160
233
  }
161
234
  export interface BpmnStudioExposed {
162
235
  getStudio(): BpmnStudioController
163
236
  getShell(): BpmnStudioShell | null
164
237
  getActions(): StudioShellActions | null
238
+ getConfig(): Readonly<BpmnStudioConfig> | undefined
239
+ setConfig(config?: BpmnStudioConfig): Readonly<BpmnStudioConfig> | undefined
165
240
  getMode(): StudioMode | undefined
166
241
  getAllowedModes(): readonly StudioMode[]
167
242
  setMode(mode: StudioMode): boolean
168
243
  setAllowedModes(modes: readonly StudioMode[]): readonly StudioMode[]
244
+ getSidebarState(): StudioSidebarState | undefined
245
+ setSidebarCollapsed(side: StudioSidebarSide, collapsed: boolean): boolean
246
+ subscribeSidebarChange(listener: (event: StudioSidebarChangeEvent) => void): () => void
247
+ getPanelSelection(): StudioPanelSelection | undefined
248
+ subscribePanelSelection(listener: (selection: StudioPanelSelection) => void): () => void
169
249
  refreshPresentation(): void
170
250
  validate(): StudioValidationIssue[]
171
251
  exportXml(engine?: EngineId): string
@@ -180,11 +260,14 @@ export interface BpmnStudioSlotContext {
180
260
  readonly actions: StudioShellActions
181
261
  readonly state: Readonly<StudioState>
182
262
  readonly mode: StudioMode
263
+ readonly ui: StudioUiContext
264
+ readonly panelSelection: StudioPanelSelection
183
265
  }
184
266
  export interface BpmnStudioSlots {
185
267
  'header-start'?(context: BpmnStudioSlotContext): VNodeChild
186
268
  'header-actions'?(context: BpmnStudioSlotContext): VNodeChild
187
269
  header?(context: BpmnStudioSlotContext): VNodeChild
270
+ right?(context: BpmnStudioSlotContext): VNodeChild
188
271
  }
189
272
  export const BpmnStudio: DefineComponent<BpmnStudioProps> & {
190
273
  new(): { $slots: BpmnStudioSlots }
@@ -253,6 +336,7 @@ export interface BpmnStudioEmits {
253
336
  'update:mode': [mode: StudioMode]
254
337
  'mode-change': [event: StudioModeChangeEvent]
255
338
  validation: [event: StudioValidationEvent]
339
+ 'sidebar-change': [event: StudioSidebarChangeEvent]
256
340
  }
257
341
  export interface BpmnDesignerEmits {
258
342
  change: [model: ProcessModel, reason: string, xml: string]
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Fragment, Teleport, createApp, defineComponent, h, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
1
+ import { Fragment, Teleport, createApp, defineComponent, h, onBeforeUnmount, onMounted, onUpdated, reactive, readonly, ref, shallowRef, watch } from 'vue';
2
2
  import { BpmnDesigner as CoreDesigner } from '@bpmn-nova/studio/designer';
3
3
  import { BpmnViewer as CoreViewer } from '@bpmn-nova/studio/viewer';
4
4
  import { importBpmn, exportBpmn } from '@bpmn-nova/studio';
@@ -49,7 +49,7 @@ function resolveModel(props) {
49
49
  }
50
50
 
51
51
  export function useBpmnStudio(options = {}) {
52
- const studio = options.studio || createStudioController({ model: resolveModel(options), propertiesProfile: options.propertiesProfile, allowedNodeTypes: options.allowedNodeTypes, allowedEdgeTypes: options.allowedEdgeTypes });
52
+ const studio = options.studio || createStudioController({ model: resolveModel(options), config: options.config, propertiesProfile: options.propertiesProfile, allowedNodeTypes: options.allowedNodeTypes, allowedEdgeTypes: options.allowedEdgeTypes });
53
53
  const state = reactive(studio.getState());
54
54
  const off = studio.subscribe((event) => Object.assign(state, event.state));
55
55
  onBeforeUnmount(() => { off(); if (!options.studio) studio.destroy(); });
@@ -126,33 +126,41 @@ export const BpmnPalettePanel = defineComponent({
126
126
  export const BpmnStudio = defineComponent({
127
127
  name: 'BpmnNovaStudio',
128
128
  props: {
129
- studio: Object, model: Object, xml: String, engine: { type: String, default: 'flowable' }, propertiesProfile: String, allowedNodeTypes: Array, allowedEdgeTypes: Array,
129
+ studio: Object, model: Object, xml: String, engine: { type: String, default: 'flowable' }, config: Object, propertiesProfile: String, allowedNodeTypes: Array, allowedEdgeTypes: Array,
130
130
  iconRegistry: Object, paletteRegistry: Object, propertiesRegistry: Object, templateRegistry: Object, contextMenuRegistry: Object,
131
131
  nodeRenderers: Object, nodeRenderer: Function, nodeSubtitleResolver: Function, slotsConfig: Object, layout: Function, regions: Object, leftWidth: Number, rightWidth: Number,
132
132
  runtime: Object, runtimePresenter: Function, runtimeTraceProjector: Function, runtimeAssetResolver: Function, runtimeTimelineRenderer: Function, onRuntimeTraceItemClick: Function, runtimeDetailsRenderer: Function, onRuntimeDetailsOpen: Function,
133
133
  runtimeTransitionDetailsRenderer: Function, onRuntimeTransitionDetailsOpen: Function,
134
134
  timeline: Object, runtimeDetails: Object, runtimeTraceOptions: Object, onTraceClick: Function, onElementClick: Function,
135
- mode: String, allowedModes: Array, projection: String, responsive: { type: Boolean, default: false }, projectionOptions: Array,
135
+ mode: String, allowedModes: Array, projection: String, responsive: { type: Boolean, default: undefined }, projectionOptions: Array,
136
136
  theme: [Object, String], runtimeAppearance: Object, svgExport: Object, onThemeChange: Function,
137
137
  },
138
- emits: ['change', 'selection-change', 'scope-change', 'element-click', 'trace-click', 'update:mode', 'mode-change', 'validation'],
138
+ emits: ['change', 'selection-change', 'scope-change', 'element-click', 'trace-click', 'update:mode', 'mode-change', 'validation', 'sidebar-change'],
139
139
  setup(props, { emit, expose, attrs, slots }) {
140
140
  const host = ref(null);
141
141
  const headerTarget = ref(null);
142
142
  const headerStartTarget = ref(null);
143
143
  const headerActionsTarget = ref(null);
144
+ const rightTarget = shallowRef(null);
144
145
  const headerContext = ref(null);
145
146
  const headerStartContext = ref(null);
146
147
  const headerActionsContext = ref(null);
148
+ const rightContext = shallowRef(null);
147
149
  const owned = !props.studio;
148
- const studio = props.studio || createStudioController({ model: resolveModel(props), propertiesProfile: props.propertiesProfile, allowedNodeTypes: props.allowedNodeTypes, allowedEdgeTypes: props.allowedEdgeTypes });
150
+ const studio = props.studio || createStudioController({ model: resolveModel(props), config: props.config, propertiesProfile: props.propertiesProfile, allowedNodeTypes: props.allowedNodeTypes, allowedEdgeTypes: props.allowedEdgeTypes });
149
151
  const state = reactive(studio.getState());
150
152
  const actualMode = ref(props.mode || props.allowedModes?.[0] || 'design');
153
+ const actualUi = reactive({ controlSize: 'medium', controlHeight: '32px' });
154
+ const readonlyUi = readonly(actualUi);
155
+ const panelSelection = shallowRef(Object.freeze({ selection: studio.selection, selectedElement: studio.getSelectedElement(), trace: null }));
151
156
  const nodeSubtitleResolver = (context) => props.nodeSubtitleResolver?.(context);
152
157
  let shell = null;
153
158
  let off = null;
154
159
  let offMode = null;
155
160
  let offValidation = null;
161
+ let offSidebar = null;
162
+ let offPanelSelection = null;
163
+ let mountedRightSlot = null;
156
164
 
157
165
  const createTeleportSlot = (target, contextRef) => (context) => {
158
166
  target.value = context.container;
@@ -162,6 +170,8 @@ export const BpmnStudio = defineComponent({
162
170
  if (contextRef.value === context) contextRef.value = null;
163
171
  };
164
172
  };
173
+ const nativeRightSlot = createTeleportSlot(rightTarget, rightContext);
174
+ const resolveRightSlot = () => slots.right ? nativeRightSlot : props.slotsConfig?.right;
165
175
 
166
176
  onMounted(() => {
167
177
  const shellSlots = { ...(props.slotsConfig || {}) };
@@ -170,14 +180,20 @@ export const BpmnStudio = defineComponent({
170
180
  if (slots['header-start']) shellSlots.headerStart = createTeleportSlot(headerStartTarget, headerStartContext);
171
181
  if (slots['header-actions']) shellSlots.headerActions = createTeleportSlot(headerActionsTarget, headerActionsContext);
172
182
  }
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 });
183
+ mountedRightSlot = resolveRightSlot();
184
+ shellSlots.right = mountedRightSlot;
185
+ shell = new CoreStudioShell({ container: host.value, studio, config: props.config, 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, timeline: props.timeline, runtimeDetails: props.runtimeDetails, runtimeTraceOptions: props.runtimeTraceOptions, theme: props.theme, runtimeAppearance: props.runtimeAppearance, svgExport: props.svgExport, onThemeChange: props.onThemeChange });
174
186
  actualMode.value = shell.getMode();
187
+ Object.assign(actualUi, shell.ui);
175
188
  offMode = shell.subscribeMode((event) => {
176
189
  actualMode.value = event.mode;
177
190
  emit('update:mode', event.mode);
178
191
  emit('mode-change', event);
179
192
  });
180
193
  offValidation = shell.subscribeValidation((event) => emit('validation', event));
194
+ panelSelection.value = shell.getPanelSelection();
195
+ offPanelSelection = shell.subscribePanelSelection((value) => { panelSelection.value = value; });
196
+ offSidebar = shell.subscribeSidebarChange((event) => emit('sidebar-change', event));
181
197
  off = studio.subscribe((event) => {
182
198
  Object.assign(state, event.state);
183
199
  if (event.type === 'modelChanged') emit('change', studio.model, event.reason, studio.exportXml());
@@ -185,6 +201,14 @@ export const BpmnStudio = defineComponent({
185
201
  if (event.type === 'scopeChanged') emit('scope-change', event.activeScopeId, event.scopePath, studio.getState());
186
202
  });
187
203
  });
204
+ // Vue slot membership is not reactive. Reconcile it after a parent update,
205
+ // keeping the same Teleport mount for ordinary slot-content changes.
206
+ onUpdated(() => {
207
+ const nextSlot = resolveRightSlot();
208
+ if (!shell || nextSlot === mountedRightSlot) return;
209
+ shell._setRightSlot(nextSlot);
210
+ mountedRightSlot = nextSlot;
211
+ });
188
212
  watch(() => props.model, (value) => { if (value && value !== studio.model) studio.setModel(value); });
189
213
  watch(() => props.xml, (value) => {
190
214
  if (value && value !== studio.exportXml(props.engine)) studio.importXml(value, props.engine);
@@ -196,19 +220,47 @@ export const BpmnStudio = defineComponent({
196
220
  if (mode) shell.setMode(mode);
197
221
  }, { deep: true });
198
222
  watch(() => props.projection, (value) => { if (value) shell?.setProjection(value); });
223
+ watch(() => props.config, (value) => {
224
+ if (!shell) return;
225
+ shell.setConfig(value);
226
+ Object.assign(actualUi, shell.ui);
227
+ }, { deep: true });
199
228
  watch(() => props.regions, (value) => { if (value !== undefined) shell?.setRegions(value); }, { deep: true });
200
229
  watch(() => props.theme, (value) => { if (value !== undefined) shell?.setTheme(value); });
201
230
  watch(() => props.runtimeAppearance, (value) => { if (value !== undefined) shell?.setRuntimeAppearance(value); });
202
231
  watch(() => props.nodeSubtitleResolver, () => shell?.refreshPresentation());
203
232
  watch(() => [props.timeline, props.runtimeDetails, props.runtimeTraceOptions], ([timeline, runtimeDetails, runtimeTraceOptions]) => {
204
233
  if (!shell) return;
234
+ if (timeline === undefined && runtimeDetails === undefined && runtimeTraceOptions === undefined) return;
205
235
  shell.rendererOptions.timeline = timeline;
206
236
  shell.rendererOptions.runtimeDetails = runtimeDetails;
207
237
  shell.rendererOptions.runtimeTraceOptions = runtimeTraceOptions;
208
238
  shell.viewer?.setDisplayOptions({ timeline, runtimeDetails, runtimeTraceOptions });
209
239
  });
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) });
240
+ onBeforeUnmount(() => { offMode?.(); offValidation?.(); offSidebar?.(); offPanelSelection?.(); off?.(); shell?.destroy(); if (owned) studio.destroy(); });
241
+ expose({
242
+ getStudio: () => studio,
243
+ getShell: () => shell,
244
+ getActions: () => shell?.actions || null,
245
+ getConfig: () => shell?.getConfig(),
246
+ setConfig: (value) => { const result = shell?.setConfig(value); if (shell) Object.assign(actualUi, shell.ui); return result; },
247
+ getMode: () => shell?.getMode(),
248
+ getAllowedModes: () => shell?.getAllowedModes() || [],
249
+ setMode: (value) => shell?.setMode(value) || false,
250
+ setAllowedModes: (value) => shell?.setAllowedModes(value) || [],
251
+ getSidebarState: () => shell?.getSidebarState(),
252
+ setSidebarCollapsed: (side, collapsed) => shell?.setSidebarCollapsed(side, collapsed) || false,
253
+ subscribeSidebarChange: (listener) => shell?.subscribeSidebarChange(listener) || (() => {}),
254
+ getPanelSelection: () => shell?.getPanelSelection(),
255
+ subscribePanelSelection: (listener) => shell?.subscribePanelSelection(listener) || (() => {}),
256
+ refreshPresentation: () => shell?.refreshPresentation(),
257
+ validate: () => shell?.validate() || [],
258
+ exportXml: (engine) => studio.exportXml(engine),
259
+ exportSvg: (options) => shell?.exportSvg(options),
260
+ openSvgExportPreview: (options) => shell?.openSvgExportPreview(options),
261
+ fitView: () => shell?.fitView(),
262
+ setTheme: (value) => shell?.setTheme(value),
263
+ });
212
264
  const renderSlot = (name, target, contextRef) => {
213
265
  if (!slots[name] || !target.value || !contextRef.value) return null;
214
266
  return h(Teleport, { to: target.value }, slots[name]({
@@ -217,6 +269,8 @@ export const BpmnStudio = defineComponent({
217
269
  actions: contextRef.value.actions,
218
270
  state,
219
271
  mode: actualMode.value,
272
+ ui: readonlyUi,
273
+ panelSelection: panelSelection.value,
220
274
  }));
221
275
  };
222
276
  return () => h(Fragment, null, [
@@ -224,6 +278,7 @@ export const BpmnStudio = defineComponent({
224
278
  renderSlot('header', headerTarget, headerContext),
225
279
  renderSlot('header-start', headerStartTarget, headerStartContext),
226
280
  renderSlot('header-actions', headerActionsTarget, headerActionsContext),
281
+ renderSlot('right', rightTarget, rightContext),
227
282
  ]);
228
283
  },
229
284
  });