@bpmn-nova/vue 0.3.4-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 +54 -5
- package/dist/index.d.ts +75 -1
- package/dist/index.js +64 -9
- package/dist/styles.css +72 -12
- package/llms-full.txt +1545 -92
- package/llms.txt +62 -15
- 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.5-preview`。Vue 由宿主工程提供,本包不会替应用选择或升级框架版本。
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -46,6 +46,7 @@ import '@bpmn-nova/vue/styles.css'
|
|
|
46
46
|
|
|
47
47
|
const props = defineProps({ initialXml: String })
|
|
48
48
|
const studioRef = ref(null)
|
|
49
|
+
const studioConfig = { ui: { controlSize: 'medium' } }
|
|
49
50
|
|
|
50
51
|
function handleChange(model, reason, nextXml) {
|
|
51
52
|
console.log(reason, nextXml)
|
|
@@ -58,6 +59,7 @@ function handleChange(model, reason, nextXml) {
|
|
|
58
59
|
ref="studioRef"
|
|
59
60
|
:xml="props.initialXml"
|
|
60
61
|
engine="flowable"
|
|
62
|
+
:config="studioConfig"
|
|
61
63
|
mode="design"
|
|
62
64
|
:allowed-modes="['design']"
|
|
63
65
|
theme="auto"
|
|
@@ -98,9 +100,10 @@ function handleSelectionChange(selection, element) {
|
|
|
98
100
|
engine="activiti"
|
|
99
101
|
mode="design"
|
|
100
102
|
:allowed-modes="['design']"
|
|
101
|
-
:
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
:config="{
|
|
104
|
+
modeling: { allowedNodeTypes: supportedNodeTypes, allowedEdgeTypes: ['sequenceFlow'] },
|
|
105
|
+
ui: { controlSize: 'medium', regions: { right: 'hidden' } },
|
|
106
|
+
}"
|
|
104
107
|
theme="auto"
|
|
105
108
|
@change="handleChange"
|
|
106
109
|
@selection-change="handleSelectionChange"
|
|
@@ -112,7 +115,7 @@ function handleSelectionChange(selection, element) {
|
|
|
112
115
|
<strong>{{ processName }}</strong>
|
|
113
116
|
<span>{{ processType }}</span>
|
|
114
117
|
</template>
|
|
115
|
-
<template #header-actions="{ actions, mode }">
|
|
118
|
+
<template #header-actions="{ actions, mode, ui }">
|
|
116
119
|
<button type="button" :disabled="mode !== 'design'" @click="actions.validate()">校验</button>
|
|
117
120
|
<button type="button" :disabled="mode !== 'design'" @click="saveDraft(actions.exportXml())">保存</button>
|
|
118
121
|
<button type="button" :disabled="mode !== 'design'" @click="publishProcess(actions)">发布</button>
|
|
@@ -141,6 +144,49 @@ async function publishProcess(actions) {
|
|
|
141
144
|
|
|
142
145
|
编辑器属性面板应订阅 `selection-change`,该事件覆盖节点、连线、多选、清空和键盘选择。`element-click` 是点击观察事件,不能代替选择状态。宿主使用稳定 BPMN Element ID 关联业务配置,并自行拥有保存、发布、权限和服务端事务。
|
|
143
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
|
+
|
|
144
190
|
## Mode 双向同步与副标题刷新
|
|
145
191
|
|
|
146
192
|
```vue
|
|
@@ -179,6 +225,8 @@ Expose 的 `getMode()` / `setMode()` 使用 Shell 实际状态。Resolver 返回
|
|
|
179
225
|
|
|
180
226
|
## 只读展示与审批轨迹
|
|
181
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
|
+
|
|
182
230
|
```vue
|
|
183
231
|
<script setup>
|
|
184
232
|
import { BpmnViewer } from '@bpmn-nova/vue'
|
|
@@ -315,6 +363,7 @@ studioRef.value?.openSvgExportPreview({
|
|
|
315
363
|
- [完整项目能力](https://github.com/daxiangme/bpmn-nova)
|
|
316
364
|
- [React/Vue 组件说明](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/COMPONENTS.md)
|
|
317
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)
|
|
318
367
|
|
|
319
368
|
## License
|
|
320
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'
|
|
@@ -53,13 +59,47 @@ export type {
|
|
|
53
59
|
ProcessModel,
|
|
54
60
|
} from '@bpmn-nova/studio'
|
|
55
61
|
export type {
|
|
62
|
+
BpmnStudioConfig,
|
|
56
63
|
NodeSubtitleResolver,
|
|
57
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,
|
|
58
77
|
StudioValidationEvent,
|
|
59
78
|
StudioValidationIssue,
|
|
60
79
|
StudioValidationSource,
|
|
80
|
+
StudioViewerConfig,
|
|
61
81
|
} from '@bpmn-nova/studio'
|
|
62
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
|
+
|
|
63
103
|
export {
|
|
64
104
|
createContextMenuRegistry,
|
|
65
105
|
createDefaultContextMenuRegistry,
|
|
@@ -86,7 +126,16 @@ export interface BpmnNovaVisualProps {
|
|
|
86
126
|
onThemeChange?: (state: NovaThemeState) => void
|
|
87
127
|
}
|
|
88
128
|
export interface BpmnModelProps { model?: ProcessModel; xml?: string; engine?: EngineId }
|
|
89
|
-
export interface UseBpmnStudioOptions extends BpmnModelProps {
|
|
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
|
+
}
|
|
90
139
|
export interface UseBpmnStudioResult { studio: BpmnStudioController; state: StudioState; commands: StudioCommands }
|
|
91
140
|
export function useBpmnStudio(options?: UseBpmnStudioOptions): UseBpmnStudioResult
|
|
92
141
|
|
|
@@ -130,8 +179,12 @@ export const BpmnPalettePanel: DefineComponent<BpmnPalettePanelProps>
|
|
|
130
179
|
|
|
131
180
|
export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
132
181
|
studio?: BpmnStudioController
|
|
182
|
+
config?: BpmnStudioConfig
|
|
183
|
+
/** @deprecated Use config.modeling.propertiesProfile. */
|
|
133
184
|
propertiesProfile?: 'business' | 'developer'
|
|
185
|
+
/** @deprecated Use config.modeling.allowedNodeTypes. */
|
|
134
186
|
allowedNodeTypes?: NodeType[]
|
|
187
|
+
/** @deprecated Use config.modeling.allowedEdgeTypes. */
|
|
135
188
|
allowedEdgeTypes?: EdgeType[]
|
|
136
189
|
iconRegistry?: IconRegistry
|
|
137
190
|
paletteRegistry?: PaletteRegistry
|
|
@@ -143,39 +196,56 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
143
196
|
nodeSubtitleResolver?: NodeSubtitleResolver
|
|
144
197
|
slotsConfig?: StudioShellSlots
|
|
145
198
|
layout?: StudioShellOptions['layout']
|
|
199
|
+
/** @deprecated Use config.ui.regions. */
|
|
146
200
|
regions?: StudioShellRegions
|
|
201
|
+
/** @deprecated Use config.ui.sidebarWidth.left. */
|
|
147
202
|
leftWidth?: number
|
|
203
|
+
/** @deprecated Use config.ui.sidebarWidth.right. */
|
|
148
204
|
rightWidth?: number
|
|
149
205
|
runtime?: ProcessInstanceSnapshot | null
|
|
150
206
|
runtimePresenter?: ViewerOptions['runtimePresenter']
|
|
151
207
|
runtimeTraceProjector?: ViewerOptions['runtimeTraceProjector']
|
|
152
208
|
runtimeAssetResolver?: ViewerOptions['runtimeAssetResolver']
|
|
153
209
|
runtimeTimelineRenderer?: RuntimeTimelineRenderer
|
|
210
|
+
/** @deprecated Use config.export. */
|
|
154
211
|
svgExport?: ViewerOptions['svgExport']
|
|
155
212
|
onRuntimeTraceItemClick?: ViewerOptions['onRuntimeTraceItemClick']
|
|
156
213
|
runtimeDetailsRenderer?: RuntimeDetailsRenderer | null
|
|
157
214
|
onRuntimeDetailsOpen?: ViewerOptions['onRuntimeDetailsOpen']
|
|
158
215
|
runtimeTransitionDetailsRenderer?: RuntimeTransitionDetailsRenderer | null
|
|
159
216
|
onRuntimeTransitionDetailsOpen?: ViewerOptions['onRuntimeTransitionDetailsOpen']
|
|
217
|
+
/** @deprecated Use config.viewer.timeline. */
|
|
160
218
|
timeline?: ViewerOptions['timeline']
|
|
219
|
+
/** @deprecated Use config.viewer.runtimeDetails. */
|
|
161
220
|
runtimeDetails?: ViewerOptions['runtimeDetails']
|
|
221
|
+
/** @deprecated Use config.viewer.runtimeTraceOptions. */
|
|
162
222
|
runtimeTraceOptions?: ViewerOptions['runtimeTraceOptions']
|
|
163
223
|
onTraceClick?: (event: RuntimeTraceClickEvent) => void
|
|
164
224
|
onElementClick?: ViewerOptions['onElementClick']
|
|
165
225
|
mode?: StudioMode
|
|
166
226
|
allowedModes?: readonly StudioMode[]
|
|
167
227
|
projection?: ViewerProjection
|
|
228
|
+
/** @deprecated Use config.viewer.responsive. */
|
|
168
229
|
responsive?: boolean
|
|
230
|
+
/** @deprecated Use config.viewer.projectionOptions. */
|
|
169
231
|
projectionOptions?: Array<{ value: ViewerProjection; label: string }>
|
|
232
|
+
onSidebarChange?: (event: StudioSidebarChangeEvent) => void
|
|
170
233
|
}
|
|
171
234
|
export interface BpmnStudioExposed {
|
|
172
235
|
getStudio(): BpmnStudioController
|
|
173
236
|
getShell(): BpmnStudioShell | null
|
|
174
237
|
getActions(): StudioShellActions | null
|
|
238
|
+
getConfig(): Readonly<BpmnStudioConfig> | undefined
|
|
239
|
+
setConfig(config?: BpmnStudioConfig): Readonly<BpmnStudioConfig> | undefined
|
|
175
240
|
getMode(): StudioMode | undefined
|
|
176
241
|
getAllowedModes(): readonly StudioMode[]
|
|
177
242
|
setMode(mode: StudioMode): boolean
|
|
178
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
|
|
179
249
|
refreshPresentation(): void
|
|
180
250
|
validate(): StudioValidationIssue[]
|
|
181
251
|
exportXml(engine?: EngineId): string
|
|
@@ -190,11 +260,14 @@ export interface BpmnStudioSlotContext {
|
|
|
190
260
|
readonly actions: StudioShellActions
|
|
191
261
|
readonly state: Readonly<StudioState>
|
|
192
262
|
readonly mode: StudioMode
|
|
263
|
+
readonly ui: StudioUiContext
|
|
264
|
+
readonly panelSelection: StudioPanelSelection
|
|
193
265
|
}
|
|
194
266
|
export interface BpmnStudioSlots {
|
|
195
267
|
'header-start'?(context: BpmnStudioSlotContext): VNodeChild
|
|
196
268
|
'header-actions'?(context: BpmnStudioSlotContext): VNodeChild
|
|
197
269
|
header?(context: BpmnStudioSlotContext): VNodeChild
|
|
270
|
+
right?(context: BpmnStudioSlotContext): VNodeChild
|
|
198
271
|
}
|
|
199
272
|
export const BpmnStudio: DefineComponent<BpmnStudioProps> & {
|
|
200
273
|
new(): { $slots: BpmnStudioSlots }
|
|
@@ -263,6 +336,7 @@ export interface BpmnStudioEmits {
|
|
|
263
336
|
'update:mode': [mode: StudioMode]
|
|
264
337
|
'mode-change': [event: StudioModeChangeEvent]
|
|
265
338
|
validation: [event: StudioValidationEvent]
|
|
339
|
+
'sidebar-change': [event: StudioSidebarChangeEvent]
|
|
266
340
|
}
|
|
267
341
|
export interface BpmnDesignerEmits {
|
|
268
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:
|
|
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
|
-
|
|
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({
|
|
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
|
});
|
package/dist/styles.css
CHANGED
|
@@ -536,6 +536,14 @@
|
|
|
536
536
|
|
|
537
537
|
/* Gateways */
|
|
538
538
|
.mb-node-gateway { display: grid; place-items: center; z-index: 7; }
|
|
539
|
+
.mb-node .mb-node-scaled-shape {
|
|
540
|
+
position: absolute;
|
|
541
|
+
left: 50%;
|
|
542
|
+
top: 50%;
|
|
543
|
+
box-sizing: border-box;
|
|
544
|
+
transform: translate(-50%, -50%) rotate(var(--mb-node-shape-rotation, 0rad)) scale(var(--mb-node-shape-scale, 1));
|
|
545
|
+
transform-origin: center;
|
|
546
|
+
}
|
|
539
547
|
.mb-gateway-shape {
|
|
540
548
|
width: 49px;
|
|
541
549
|
height: 49px;
|
|
@@ -699,6 +707,8 @@
|
|
|
699
707
|
.mb-port-right:hover { transform: translateY(-50%) scale(1.2); }
|
|
700
708
|
.mb-port-top:hover,
|
|
701
709
|
.mb-port-bottom:hover { transform: translateX(-50%) scale(1.2); }
|
|
710
|
+
.mb-port.mb-port-geometry { transform: translate(-50%, -50%); }
|
|
711
|
+
.mb-port.mb-port-geometry:hover { transform: translate(-50%, -50%) scale(1.2); }
|
|
702
712
|
.mb-node.is-connecting-source .mb-port-right { opacity: 1; border-color: var(--mb-primary); background: var(--mb-primary); box-shadow: 0 0 0 4px color-mix(in srgb, var(--nova-color-primary) 12%, transparent); }
|
|
703
713
|
.mb-port-target {
|
|
704
714
|
width: 12px;
|
|
@@ -1140,7 +1150,7 @@ textarea.property-control { resize: vertical; }
|
|
|
1140
1150
|
.property-branch-target small { color: var(--nova-color-text-muted); font-size: var(--nova-font-size-xs, 10px); line-height: 14px; }.property-branch-target strong { overflow: hidden; color: var(--nova-color-text-secondary); font-size: var(--nova-font-size-sm, 12px); line-height: 18px; font-weight: var(--nova-font-weight-semibold, 600); text-overflow: ellipsis; white-space: nowrap; }.property-branch-target > span { color: var(--nova-color-primary); font-size: var(--nova-font-size-sm, 12px); }
|
|
1141
1151
|
|
|
1142
1152
|
/* @bpmn-nova/internal/studio */
|
|
1143
|
-
.nova-studio-shell { --nova-left-width: 244px; --nova-right-width: 360px; --nova-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; --nova-font-size-xs: 10px; --nova-font-size-sm: 12px; --nova-font-size-md: 14px; --nova-font-size-lg: 16px; --nova-font-weight-regular: 400; --nova-font-weight-medium: 500; --nova-font-weight-semibold: 600; --nova-font-weight-bold: 700; position: relative; isolation: isolate; width: 100%; height: 100%; min-height: 520px; overflow: hidden; display: grid; grid-template-rows:
|
|
1153
|
+
.nova-studio-shell { --nova-left-width: 244px; --nova-right-width: 360px; --nova-control-height: 32px; --nova-header-height: 58px; --nova-control-font-size: 12px; --nova-control-padding-inline: 10px; --nova-mode-control-padding-inline: 12px; --nova-control-icon-size: 14px; --nova-control-radius: 7px; --nova-mode-control-height: 36px; --nova-mode-button-height: 28px; --nova-projection-button-height: 26px; --nova-icon-button-size: 31px; --nova-split-caret-width: 27px; --nova-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; --nova-font-size-xs: 10px; --nova-font-size-sm: 12px; --nova-font-size-md: 14px; --nova-font-size-lg: 16px; --nova-font-weight-regular: 400; --nova-font-weight-medium: 500; --nova-font-weight-semibold: 600; --nova-font-weight-bold: 700; position: relative; isolation: isolate; width: 100%; height: 100%; min-height: 520px; overflow: hidden; display: grid; grid-template-rows: var(--nova-header-height) minmax(0, 1fr); color: var(--nova-color-text, #263148); background: var(--nova-color-canvas, #f6f7fb); font-family: var(--nova-font-family); }
|
|
1144
1154
|
.nova-studio-shell.is-header-hidden { grid-template-rows: minmax(0, 1fr); }
|
|
1145
1155
|
.nova-studio-header[hidden], .nova-studio-left[hidden], .nova-studio-right[hidden], .nova-studio-statusbar[hidden] { display: none !important; }
|
|
1146
1156
|
.nova-studio-header { min-width: 0; border-bottom: 1px solid var(--nova-color-divider, #e4e8f0); background: var(--nova-color-surface, #fff); display: grid; grid-template-columns: minmax(180px, auto) auto minmax(0, 1fr); align-items: center; gap: 18px; padding: 0 12px 0 14px; }
|
|
@@ -1150,20 +1160,22 @@ textarea.property-control { resize: vertical; }
|
|
|
1150
1160
|
.nova-studio-brand-copy strong { color: var(--nova-tone-primary-foreground, #3443b8); font-size: var(--nova-font-size-md); line-height: 20px; font-weight: var(--nova-font-weight-bold); letter-spacing: -.01em; }
|
|
1151
1161
|
.nova-studio-brand-copy span { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--nova-color-text-muted, #7c8799); font-size: var(--nova-font-size-xs); line-height: 14px; }
|
|
1152
1162
|
.nova-studio-center-controls { min-width: 0; justify-self: center; display: flex; align-items: center; gap: 8px; }
|
|
1153
|
-
.nova-studio-mode-switch { height:
|
|
1154
|
-
.nova-studio-mode-switch button { height:
|
|
1163
|
+
.nova-studio-mode-switch { height: var(--nova-mode-control-height); border: 1px solid var(--nova-color-divider, #e6e8ef); border-radius: calc(var(--nova-control-radius) + 3px); padding: 3px; display: flex; align-items: center; gap: 2px; background: var(--nova-color-surface-muted, #f4f5f8); box-shadow: inset 0 1px 2px rgba(35,43,68,.03); }
|
|
1164
|
+
.nova-studio-mode-switch button { height: var(--nova-mode-button-height); border: 0; border-radius: var(--nova-control-radius); padding: 0 var(--nova-mode-control-padding-inline); display: flex; align-items: center; gap: 5px; color: var(--nova-color-text-muted, #8a93a4); background: transparent; font-size: var(--nova-control-font-size); line-height: 18px; white-space: nowrap; cursor: pointer; }
|
|
1155
1165
|
.nova-studio-mode-switch button.is-active { color: var(--nova-tone-primary-foreground, #4f5dd1); background: var(--nova-color-surface, #fff); font-weight: var(--nova-font-weight-semibold); box-shadow: var(--nova-shadow-sm); }
|
|
1156
|
-
.nova-studio-projection-switch { height:
|
|
1166
|
+
.nova-studio-projection-switch { height: var(--nova-control-height); border: 1px solid var(--nova-color-border, #e2e6ef); border-radius: calc(var(--nova-control-radius) + 1px); padding: 2px; display: flex; align-items: center; gap: 2px; background: var(--nova-color-surface, #fff); }
|
|
1157
1167
|
.nova-studio-projection-switch.is-hidden { display: none; }
|
|
1158
|
-
.nova-studio-projection-switch button { height:
|
|
1168
|
+
.nova-studio-projection-switch button { height: var(--nova-projection-button-height); border: 0; border-radius: max(0px, calc(var(--nova-control-radius) - 1px)); padding: 0 max(0px, calc(var(--nova-control-padding-inline) - 1px)); color: var(--nova-color-text-muted, #7c8799); background: transparent; font-size: var(--nova-control-font-size); line-height: 18px; white-space: nowrap; cursor: pointer; }
|
|
1159
1169
|
.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
1170
|
.nova-studio-tools { min-width: 0; overflow: visible; display: flex; align-items: center; justify-content: flex-end; gap: 5px; }
|
|
1161
1171
|
.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; }
|
|
1163
|
-
.nova-studio-
|
|
1164
|
-
.nova-studio-
|
|
1172
|
+
.nova-studio-header-actions { min-width: 0; min-height: var(--nova-control-height); display: flex; align-items: center; justify-content: flex-end; gap: 5px; }
|
|
1173
|
+
.nova-studio-header .nova-icon { width: var(--nova-control-icon-size); height: var(--nova-control-icon-size); }
|
|
1174
|
+
.nova-studio-header .nova-icon.nova-icon-xs { width: max(12px, calc(var(--nova-control-icon-size) - 2px)); height: max(12px, calc(var(--nova-control-icon-size) - 2px)); }
|
|
1175
|
+
.nova-studio-tool-divider { flex: none; width: 1px; height: calc(var(--nova-control-height) - 12px); margin: 0 2px; background: var(--nova-color-divider, #e4e8f0); }
|
|
1176
|
+
.nova-studio-tool { flex: none; height: var(--nova-control-height); min-height: var(--nova-control-height); border: 1px solid var(--nova-color-border, #e2e6ef); border-radius: var(--nova-control-radius); padding: 0 var(--nova-control-padding-inline); 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-control-font-size); line-height: 18px; white-space: nowrap; cursor: pointer; }
|
|
1165
1177
|
.nova-studio-tool:focus { outline: none; }.nova-studio-tool:focus-visible { outline: 2px solid var(--nova-color-focus-ring, #aeb7ff); outline-offset: 1px; }
|
|
1166
|
-
.nova-studio-tool.is-icon { width:
|
|
1178
|
+
.nova-studio-tool.is-icon { width: var(--nova-icon-button-size); padding: 0; font-size: calc(var(--nova-control-font-size) + 2px); }
|
|
1167
1179
|
.nova-studio-tool:hover, .nova-studio-tool.is-active { border-color: var(--nova-tone-primary-border, #bec6ff); color: var(--nova-tone-primary-foreground, #3948c5); background: var(--nova-color-primary-soft, #f2f4ff); }
|
|
1168
1180
|
.nova-studio-tool.is-magic { border-color: var(--nova-tone-primary-border, #d9ddff); color: var(--nova-tone-primary-foreground, #4654c8); background: var(--nova-color-primary-soft, #f4f5ff); font-weight: var(--nova-font-weight-semibold); }
|
|
1169
1181
|
.nova-studio-tool.is-primary { border-color: var(--nova-color-primary); color: var(--nova-color-text-inverse, #fff); background: var(--nova-color-primary, #5361d6); }
|
|
@@ -1182,8 +1194,8 @@ textarea.property-control { resize: vertical; }
|
|
|
1182
1194
|
.nova-studio-export-option.is-hidden { display: none; }
|
|
1183
1195
|
.nova-studio-import-input { display: none; }
|
|
1184
1196
|
.nova-studio-beautify-split { position: relative; flex: none; display: flex; }
|
|
1185
|
-
.nova-studio-beautify-split .nova-studio-beautify { border-radius:
|
|
1186
|
-
.nova-studio-beautify-split .nova-studio-beautify-caret { width:
|
|
1197
|
+
.nova-studio-beautify-split .nova-studio-beautify { border-radius: var(--nova-control-radius) 0 0 var(--nova-control-radius); }
|
|
1198
|
+
.nova-studio-beautify-split .nova-studio-beautify-caret { width: var(--nova-split-caret-width); border-left: 0; border-radius: 0 var(--nova-control-radius) var(--nova-control-radius) 0; padding: 0; }
|
|
1187
1199
|
.nova-studio-beautify-menu { position: absolute; z-index: 30; top: calc(100% + 8px); right: 0; width: 224px; border: 1px solid var(--nova-color-border, #e5e7ee); border-radius: 12px; padding: 12px; color: var(--nova-color-text-secondary, #3d4659); background: var(--nova-color-surface, #fff); box-shadow: var(--nova-shadow-lg); }
|
|
1188
1200
|
.nova-studio-beautify-menu.is-hidden, .nova-studio-canvas-floating-head.is-hidden, .is-hidden-by-mode { display: none !important; }
|
|
1189
1201
|
.nova-studio-beautify-title { margin-bottom: 9px; font-size: 12px; font-weight: 700; line-height: 18px; }
|
|
@@ -1429,7 +1441,7 @@ textarea.property-control { resize: vertical; }
|
|
|
1429
1441
|
.nova-studio-brand-copy span { display: none; }
|
|
1430
1442
|
}
|
|
1431
1443
|
@media (max-width: 720px) {
|
|
1432
|
-
.nova-studio-shell { grid-template-rows:
|
|
1444
|
+
.nova-studio-shell { grid-template-rows: var(--nova-header-height) minmax(0,1fr); }
|
|
1433
1445
|
.nova-studio-shell.is-header-hidden { grid-template-rows: minmax(0, 1fr); }
|
|
1434
1446
|
.nova-studio-header { grid-template-columns: auto minmax(0,1fr); padding-left: 9px; }
|
|
1435
1447
|
.nova-studio-brand-copy { display: none; }
|
|
@@ -1442,3 +1454,51 @@ textarea.property-control { resize: vertical; }
|
|
|
1442
1454
|
.nova-studio-body.is-timeline .nova-studio-statusbar { display: none; }
|
|
1443
1455
|
}
|
|
1444
1456
|
|
|
1457
|
+
/* The managed Shell keeps three tracks, including zero-width collapsed tracks.
|
|
1458
|
+
Legacy base layout above remains usable without the sidebar controller. */
|
|
1459
|
+
.nova-studio-shell .nova-studio-body.is-managed-sidebars {
|
|
1460
|
+
position: relative;
|
|
1461
|
+
grid-template-columns: var(--nova-sidebar-left-track, 0px) minmax(0, 1fr) var(--nova-sidebar-right-track, 0px);
|
|
1462
|
+
}
|
|
1463
|
+
.nova-studio-body.is-managed-sidebars.is-sidebar-animated {
|
|
1464
|
+
transition: grid-template-columns 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
1465
|
+
}
|
|
1466
|
+
.nova-studio-shell .is-managed-sidebars > .nova-studio-left, .nova-studio-shell .is-managed-sidebars > .nova-studio-right {
|
|
1467
|
+
display: block; min-width: 0; min-height: 0; overflow: hidden; border: 0;
|
|
1468
|
+
}
|
|
1469
|
+
.is-managed-sidebars > .nova-studio-left { grid-column: 1; grid-row: 1; }
|
|
1470
|
+
.is-managed-sidebars > .nova-studio-canvas-column { grid-column: 2; grid-row: 1; }
|
|
1471
|
+
.is-managed-sidebars > .nova-studio-right { grid-column: 3; grid-row: 1; }
|
|
1472
|
+
.nova-studio-sidebar-content {
|
|
1473
|
+
box-sizing: border-box; height: 100%; min-width: 0; min-height: 0;
|
|
1474
|
+
scrollbar-width: thin; scrollbar-color: var(--nova-color-border-strong, #d9dee7) transparent;
|
|
1475
|
+
}
|
|
1476
|
+
.nova-studio-left-content { overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; border-right: 1px solid var(--nova-color-divider); }
|
|
1477
|
+
.nova-studio-right-content { border-left: 1px solid var(--nova-color-divider); }
|
|
1478
|
+
.nova-studio-right-content[data-layout="flex"] { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
|
|
1479
|
+
.nova-studio-right-content[data-layout="scroll"] { display: block; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }
|
|
1480
|
+
.nova-studio-right-content[data-layout="flex"].nova-properties > .properties-head { flex: none; position: relative; }
|
|
1481
|
+
.nova-studio-right-content[data-layout="flex"].nova-properties > .properties-body { flex: 1; min-height: 0; overflow: auto; overscroll-behavior: contain; }
|
|
1482
|
+
.nova-studio-right-content[data-layout="scroll"].nova-properties > .properties-head { position: relative; }
|
|
1483
|
+
.nova-studio-right-content[data-layout="flex"] > .nova-studio-readonly-panel { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
1484
|
+
.nova-studio-right-content[data-layout="flex"] .nova-studio-readonly-header { flex: none; }
|
|
1485
|
+
.nova-studio-right-content[data-layout="flex"] .nova-studio-readonly-body { flex: 1; min-height: 0; overflow: auto; align-content: start; overscroll-behavior: contain; }
|
|
1486
|
+
.nova-studio-sidebar-content::-webkit-scrollbar, .nova-studio-right-content .properties-body::-webkit-scrollbar, .nova-studio-readonly-body::-webkit-scrollbar { width: 4px; height: 4px; }
|
|
1487
|
+
.nova-studio-sidebar-content::-webkit-scrollbar-thumb, .nova-studio-right-content .properties-body::-webkit-scrollbar-thumb, .nova-studio-readonly-body::-webkit-scrollbar-thumb { border-radius: 999px; background: var(--nova-color-border-strong, #d9dee7); }
|
|
1488
|
+
.nova-studio-sidebar-toggle {
|
|
1489
|
+
position: absolute; z-index: 8; top: 50%; transform: translateY(-50%);
|
|
1490
|
+
box-sizing: border-box; width: 22px; height: 44px; padding: 0;
|
|
1491
|
+
display: grid; place-items: center; border: 1px solid var(--nova-color-border);
|
|
1492
|
+
border-radius: 7px; background: var(--nova-color-surface); color: var(--nova-color-text-muted);
|
|
1493
|
+
box-shadow: var(--nova-shadow-sm); font: 22px/1 sans-serif; cursor: pointer;
|
|
1494
|
+
}
|
|
1495
|
+
.nova-studio-sidebar-toggle[hidden] { display: none; }
|
|
1496
|
+
.nova-studio-sidebar-toggle.is-left { left: max(0px, calc(var(--nova-sidebar-left-track, 0px) - 11px)); }
|
|
1497
|
+
.nova-studio-sidebar-toggle.is-right { right: max(0px, calc(var(--nova-sidebar-right-track, 0px) - 11px)); }
|
|
1498
|
+
.is-sidebar-animated > .nova-studio-sidebar-toggle { transition: left 200ms cubic-bezier(0.2, 0, 0, 1), right 200ms cubic-bezier(0.2, 0, 0, 1); }
|
|
1499
|
+
.nova-studio-sidebar-toggle:hover { color: var(--nova-color-primary); background: var(--nova-color-primary-soft); }
|
|
1500
|
+
.nova-studio-sidebar-toggle:focus-visible { outline: 2px solid var(--nova-color-focus-ring); outline-offset: 2px; }
|
|
1501
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1502
|
+
.nova-studio-body.is-managed-sidebars.is-sidebar-animated, .is-sidebar-animated > .nova-studio-sidebar-toggle { transition: none; }
|
|
1503
|
+
}
|
|
1504
|
+
|