@bpmn-nova/react 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 +59 -6
- package/dist/index.d.ts +68 -0
- package/dist/index.js +48 -3
- 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 的 React 18+ Adapter,提供 Studio、Designer、Viewer、审批轨
|
|
|
4
4
|
|
|
5
5
|
> **English summary:** React 18+ components and refs for BPMN Nova process design, viewing, approval traces, properties, themes, and SVG export.
|
|
6
6
|
|
|
7
|
-
> 当前版本为 `0.3.
|
|
7
|
+
> 当前版本为 `0.3.5-preview`。React 与 React DOM 由宿主工程提供,本包不会替应用选择或升级框架版本。
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -43,6 +43,8 @@ import { useRef } from 'react'
|
|
|
43
43
|
import { BpmnStudio } from '@bpmn-nova/react'
|
|
44
44
|
import '@bpmn-nova/react/styles.css'
|
|
45
45
|
|
|
46
|
+
const STUDIO_CONFIG = { ui: { controlSize: 'medium' } }
|
|
47
|
+
|
|
46
48
|
export function WorkflowEditor({ initialXml }) {
|
|
47
49
|
const studioRef = useRef(null)
|
|
48
50
|
|
|
@@ -52,6 +54,7 @@ export function WorkflowEditor({ initialXml }) {
|
|
|
52
54
|
ref={studioRef}
|
|
53
55
|
xml={initialXml}
|
|
54
56
|
engine="flowable"
|
|
57
|
+
config={STUDIO_CONFIG}
|
|
55
58
|
mode="design"
|
|
56
59
|
allowedModes={['design']}
|
|
57
60
|
theme="auto"
|
|
@@ -70,7 +73,7 @@ Ref 提供 `actions`、`validate()`、`exportXml()`、`fitView()`、`setTheme()`
|
|
|
70
73
|
|
|
71
74
|
## 组合宿主头部与业务属性面板
|
|
72
75
|
|
|
73
|
-
默认 `BpmnStudio`
|
|
76
|
+
默认 `BpmnStudio` 是完整工作台。下面展示宿主拥有外部面板的接法:通过 `config.ui.regions.right = 'hidden'` 隐藏 Nova Properties,Canvas 获得该轨道的全部宽度。若希望复用 Nova 侧栏布局,使用下文的 `right` Render Prop,不必放到外部。
|
|
74
77
|
|
|
75
78
|
```jsx
|
|
76
79
|
<BpmnStudio
|
|
@@ -79,14 +82,15 @@ Ref 提供 `actions`、`validate()`、`exportXml()`、`fitView()`、`setTheme()`
|
|
|
79
82
|
engine="activiti"
|
|
80
83
|
mode="design"
|
|
81
84
|
allowedModes={['design']}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
config={{
|
|
86
|
+
modeling: { allowedNodeTypes: supportedNodeTypes, allowedEdgeTypes: ['sequenceFlow'] },
|
|
87
|
+
ui: { controlSize: 'medium', regions: { right: 'hidden' } },
|
|
88
|
+
}}
|
|
85
89
|
theme="auto"
|
|
86
90
|
headerStart={({ state, actions }) => (
|
|
87
91
|
<HostProcessIdentity onBack={back} />
|
|
88
92
|
)}
|
|
89
|
-
headerActions={({ actions, mode }) => (
|
|
93
|
+
headerActions={({ actions, mode, ui }) => (
|
|
90
94
|
<HostWorkflowActions
|
|
91
95
|
disabled={mode !== 'design'}
|
|
92
96
|
onValidate={() => actions.validate()}
|
|
@@ -116,6 +120,52 @@ async function publishProcess(actions) {
|
|
|
116
120
|
|
|
117
121
|
宿主属性面板以 `onSelectionChange` 为选择状态来源;它覆盖节点、连线、多选、清空与键盘选择。`onElementClick` 只是点击观察事件,不能代替选择状态。宿主通过稳定 BPMN Element ID 关联业务配置,并自行负责保存、发布、权限与服务端事务。
|
|
118
122
|
|
|
123
|
+
`config` 收拢 `modeling/ui/viewer/export` 静态选项。顶部控件尺寸使用 `config.ui.controlSize`,默认 `medium`,支持 `small`、`medium`、`large` 和 `24px`–`48px`。Render Context 的只读 `ui.controlHeight` 可交给宿主按钮;Element Plus `default`、Ant Design `middle` 都映射到 Nova `medium`。自定义 px 时也可以在宿主 Header Actions CSS 中读取 `--nova-control-height` 等公开变量。
|
|
124
|
+
|
|
125
|
+
```jsx
|
|
126
|
+
<BpmnStudio
|
|
127
|
+
ref={studioRef}
|
|
128
|
+
config={{ ui: { controlSize: '36px' } }}
|
|
129
|
+
headerActions={({ actions, ui }) => (
|
|
130
|
+
<HostActions actions={actions} buttonHeight={ui.controlHeight} />
|
|
131
|
+
)}
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Ref 同步提供 `getConfig()` / `setConfig()`;运行时更新 UI、Viewer 或 Export 不重建 Canvas。旧顶层配置继续兼容并标记 Deprecated,同项冲突时显式旧值优先。`config.modeling` 只在内部 Controller 创建时建立节点和连线约束。
|
|
136
|
+
|
|
137
|
+
## 原生右侧内容与折叠
|
|
138
|
+
|
|
139
|
+
`right` Render Prop 仅替换侧栏内容,保留 Nova 的布局与折叠按钮;它高于 Core `slots.right`,通过 Portal 保留宿主 Context。不要同时配置 `regions.right: 'hidden'`,否则自定义内容也隐藏。
|
|
140
|
+
|
|
141
|
+
```jsx
|
|
142
|
+
<BpmnStudio
|
|
143
|
+
ref={studioRef}
|
|
144
|
+
xml={xml}
|
|
145
|
+
config={{ ui: {
|
|
146
|
+
leftPanel: { collapsible: true, defaultCollapsed: false },
|
|
147
|
+
rightPanel: { collapsible: true, defaultCollapsed: false, layout: 'flex' },
|
|
148
|
+
} }}
|
|
149
|
+
onSidebarChange={(event) => console.log(event.side, event.collapsed, event.source)}
|
|
150
|
+
right={({ panelSelection, mode }) => (
|
|
151
|
+
<>
|
|
152
|
+
<header style={{ flex: 'none', padding: 12 }}>业务详情 · {mode}</header>
|
|
153
|
+
<div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: 12 }}>
|
|
154
|
+
{panelSelection.selectedElement?.id ?? '未选择元素'}
|
|
155
|
+
{/* 填入宿主表单 */}
|
|
156
|
+
</div>
|
|
157
|
+
<footer style={{ flex: 'none', padding: 12 }}>宿主固定操作区</footer>
|
|
158
|
+
</>
|
|
159
|
+
)}
|
|
160
|
+
/>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
默认 `flex` 由宿主安排内部滚动;`scroll` 由 Nova 滚动整个内容,宿主只填普通内容。父容器仍需可计算高度。收起不卸载内容、不丢失表单/滚动状态、不自动 Fit;左右按钮位于分隔线中点,支持键盘和尊重减少动态效果的 200ms 动画。Shell 容器 ≤720px 时首次默认收起,保留按钮,展开挤压画布而非抽屉,宽窄屏分别记忆状态。
|
|
164
|
+
|
|
165
|
+
Ref 增加 `getSidebarState()`、`setSidebarCollapsed(side, collapsed)`、`subscribeSidebarChange()`、`getPanelSelection()`、`subscribePanelSelection()`;设置仅在实际切换时返回 `true`。Header/Right Render Context 共享响应式 `panelSelection`(`selection`、`selectedElement`、`trace`),只读轨迹选择不回写设计选择。
|
|
166
|
+
|
|
167
|
+
Instance 默认隐藏右侧且没有展开按钮;需要时显式设置 `config.ui.regions.right: 'default'`。新增能力自 `0.3.5-preview` 提供。完整示例见[侧栏指南](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/CUSTOMIZATION.md#侧栏布局与平滑折叠)或包内 `llms-full.txt`。
|
|
168
|
+
|
|
119
169
|
## Mode 双向同步与副标题刷新
|
|
120
170
|
|
|
121
171
|
```jsx
|
|
@@ -152,6 +202,8 @@ Ref 的 `mode` Getter 与 `setMode()` 始终反映 Shell 实际状态。Resolver
|
|
|
152
202
|
|
|
153
203
|
## 只读展示与审批轨迹
|
|
154
204
|
|
|
205
|
+
真实后端的数据格式与接入步骤见[审批轨迹接入指南](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/RUNTIME-INTEGRATION.md),完整正文也包含在包内 `llms-full.txt`。React 根入口的 `ProcessInstanceSnapshot`、Activity/Action/Transition、`RuntimeTraceClickEvent` 与资源 Resolver 等 14 个 Runtime 类型出口自 `0.3.5-preview` 提供;旧版可按指南从 `BpmnStudioProps['runtime']` 推导,无需增加 Studio 直依赖。
|
|
206
|
+
|
|
155
207
|
```jsx
|
|
156
208
|
import { BpmnViewer } from '@bpmn-nova/react'
|
|
157
209
|
import '@bpmn-nova/react/styles.css'
|
|
@@ -282,6 +334,7 @@ studioRef.current?.openSvgExportPreview({
|
|
|
282
334
|
- [完整项目能力](https://github.com/daxiangme/bpmn-nova)
|
|
283
335
|
- [React/Vue 组件说明](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/COMPONENTS.md)
|
|
284
336
|
- [公开 Interface](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/API.md)
|
|
337
|
+
- [审批轨迹接入指南](https://github.com/daxiangme/bpmn-nova/blob/dev/docs/RUNTIME-INTEGRATION.md)
|
|
285
338
|
|
|
286
339
|
## License
|
|
287
340
|
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type { ProcessInstanceSnapshot } from '@bpmn-nova/studio/runtime'
|
|
|
25
25
|
import type { SvgExportArtifact, SvgExportOptions, SvgExportPreviewController } from '@bpmn-nova/studio/export-svg'
|
|
26
26
|
import type {
|
|
27
27
|
BpmnCanvas as CanvasInstance,
|
|
28
|
+
BpmnStudioConfig,
|
|
28
29
|
BpmnStudioController,
|
|
29
30
|
BpmnStudioShell,
|
|
30
31
|
ContextMenuRegistry,
|
|
@@ -34,12 +35,17 @@ import type {
|
|
|
34
35
|
StudioShellActions,
|
|
35
36
|
StudioMode,
|
|
36
37
|
StudioModeChangeEvent,
|
|
38
|
+
StudioPanelSelection,
|
|
39
|
+
StudioSidebarChangeEvent,
|
|
40
|
+
StudioSidebarSide,
|
|
41
|
+
StudioSidebarState,
|
|
37
42
|
StudioValidationEvent,
|
|
38
43
|
StudioValidationIssue,
|
|
39
44
|
StudioShellOptions,
|
|
40
45
|
StudioShellRegions,
|
|
41
46
|
StudioShellSlots,
|
|
42
47
|
StudioState,
|
|
48
|
+
StudioUiContext,
|
|
43
49
|
TemplateRegistry,
|
|
44
50
|
} from '@bpmn-nova/studio'
|
|
45
51
|
import type { NovaThemeInput, NovaThemeState, RuntimeAppearanceOptions } from '@bpmn-nova/studio/theme'
|
|
@@ -83,13 +89,47 @@ export {
|
|
|
83
89
|
registerFlowableProperties,
|
|
84
90
|
} from '@bpmn-nova/studio'
|
|
85
91
|
export type {
|
|
92
|
+
BpmnStudioConfig,
|
|
86
93
|
NodeSubtitleResolver,
|
|
87
94
|
NodeSubtitleResolverContext,
|
|
95
|
+
StudioControlSize,
|
|
96
|
+
StudioModelingConfig,
|
|
97
|
+
StudioPanelSelection,
|
|
98
|
+
StudioRightPanelConfig,
|
|
99
|
+
StudioSidebarChangeEvent,
|
|
100
|
+
StudioSidebarChangeSource,
|
|
101
|
+
StudioSidebarConfig,
|
|
102
|
+
StudioSidebarSide,
|
|
103
|
+
StudioSidebarState,
|
|
104
|
+
StudioSvgExportOptions,
|
|
105
|
+
StudioUiConfig,
|
|
106
|
+
StudioUiContext,
|
|
88
107
|
StudioValidationEvent,
|
|
89
108
|
StudioValidationIssue,
|
|
90
109
|
StudioValidationSource,
|
|
110
|
+
StudioViewerConfig,
|
|
91
111
|
} from '@bpmn-nova/studio'
|
|
92
112
|
|
|
113
|
+
/** Runtime integration facade. Added in 0.3.5-preview; no runtime JavaScript exports. */
|
|
114
|
+
export type {
|
|
115
|
+
ActivityInstance,
|
|
116
|
+
ActivityStatus,
|
|
117
|
+
ProcessInstanceSnapshot,
|
|
118
|
+
RuntimeApprovalAction,
|
|
119
|
+
RuntimeApprovalActionType,
|
|
120
|
+
RuntimeApprovalContent,
|
|
121
|
+
RuntimeApprovalContentBlock,
|
|
122
|
+
RuntimeAssetRef,
|
|
123
|
+
RuntimeEdgeVisit,
|
|
124
|
+
RuntimeParticipant,
|
|
125
|
+
RuntimeTransition,
|
|
126
|
+
} from '@bpmn-nova/studio/runtime'
|
|
127
|
+
export type {
|
|
128
|
+
RuntimeAssetPurpose,
|
|
129
|
+
RuntimeAssetResolver,
|
|
130
|
+
RuntimeTraceClickEvent,
|
|
131
|
+
} from '@bpmn-nova/studio/viewer'
|
|
132
|
+
|
|
93
133
|
export interface BpmnNovaVisualProps {
|
|
94
134
|
theme?: NovaThemeInput
|
|
95
135
|
runtimeAppearance?: RuntimeAppearanceOptions
|
|
@@ -104,8 +144,12 @@ export interface BpmnModelProps {
|
|
|
104
144
|
}
|
|
105
145
|
export interface UseBpmnStudioOptions extends BpmnModelProps {
|
|
106
146
|
studio?: BpmnStudioController
|
|
147
|
+
config?: BpmnStudioConfig
|
|
148
|
+
/** @deprecated Use config.modeling.propertiesProfile. */
|
|
107
149
|
propertiesProfile?: 'business' | 'developer'
|
|
150
|
+
/** @deprecated Use config.modeling.allowedNodeTypes. */
|
|
108
151
|
allowedNodeTypes?: NodeType[]
|
|
152
|
+
/** @deprecated Use config.modeling.allowedEdgeTypes. */
|
|
109
153
|
allowedEdgeTypes?: EdgeType[]
|
|
110
154
|
}
|
|
111
155
|
export interface UseBpmnStudioResult { studio: BpmnStudioController; state: StudioState; commands: StudioCommands }
|
|
@@ -152,8 +196,12 @@ export const BpmnPalettePanel: ForwardRefExoticComponent<BpmnPalettePanelProps &
|
|
|
152
196
|
|
|
153
197
|
export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
154
198
|
studio?: BpmnStudioController
|
|
199
|
+
config?: BpmnStudioConfig
|
|
200
|
+
/** @deprecated Use config.modeling.propertiesProfile. */
|
|
155
201
|
propertiesProfile?: 'business' | 'developer'
|
|
202
|
+
/** @deprecated Use config.modeling.allowedNodeTypes. */
|
|
156
203
|
allowedNodeTypes?: NodeType[]
|
|
204
|
+
/** @deprecated Use config.modeling.allowedEdgeTypes. */
|
|
157
205
|
allowedEdgeTypes?: EdgeType[]
|
|
158
206
|
iconRegistry?: IconRegistry
|
|
159
207
|
paletteRegistry?: PaletteRegistry
|
|
@@ -165,25 +213,35 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
165
213
|
nodeSubtitleResolver?: NodeSubtitleResolver
|
|
166
214
|
slots?: StudioShellSlots
|
|
167
215
|
layout?: StudioShellOptions['layout']
|
|
216
|
+
/** @deprecated Use config.ui.regions. */
|
|
168
217
|
regions?: StudioShellRegions
|
|
169
218
|
headerStart?: BpmnStudioRenderSlot
|
|
170
219
|
headerActions?: BpmnStudioRenderSlot
|
|
171
220
|
header?: BpmnStudioRenderSlot
|
|
221
|
+
right?: BpmnStudioRenderSlot
|
|
172
222
|
runtime?: ProcessInstanceSnapshot | null
|
|
173
223
|
mode?: StudioMode
|
|
174
224
|
allowedModes?: readonly StudioMode[]
|
|
175
225
|
projection?: ViewerProjection
|
|
226
|
+
/** @deprecated Use config.viewer.responsive. */
|
|
176
227
|
responsive?: boolean
|
|
228
|
+
/** @deprecated Use config.viewer.projectionOptions. */
|
|
177
229
|
projectionOptions?: Array<{ value: ViewerProjection; label: string }>
|
|
230
|
+
/** @deprecated Use config.ui.sidebarWidth.left. */
|
|
178
231
|
leftWidth?: number
|
|
232
|
+
/** @deprecated Use config.ui.sidebarWidth.right. */
|
|
179
233
|
rightWidth?: number
|
|
234
|
+
/** @deprecated Use config.viewer.timeline. */
|
|
180
235
|
timeline?: ViewerOptions['timeline']
|
|
236
|
+
/** @deprecated Use config.viewer.runtimeDetails. */
|
|
181
237
|
runtimeDetails?: ViewerOptions['runtimeDetails']
|
|
238
|
+
/** @deprecated Use config.viewer.runtimeTraceOptions. */
|
|
182
239
|
runtimeTraceOptions?: ViewerOptions['runtimeTraceOptions']
|
|
183
240
|
runtimePresenter?: ViewerOptions['runtimePresenter']
|
|
184
241
|
runtimeTraceProjector?: ViewerOptions['runtimeTraceProjector']
|
|
185
242
|
runtimeAssetResolver?: ViewerOptions['runtimeAssetResolver']
|
|
186
243
|
runtimeTimelineRenderer?: RuntimeTimelineRenderer
|
|
244
|
+
/** @deprecated Use config.export. */
|
|
187
245
|
svgExport?: ViewerOptions['svgExport']
|
|
188
246
|
runtimeDetailsRenderer?: RuntimeDetailsRenderer | null
|
|
189
247
|
runtimeTransitionDetailsRenderer?: RuntimeTransitionDetailsRenderer | null
|
|
@@ -197,6 +255,7 @@ export interface BpmnStudioProps extends BpmnNovaVisualProps, BpmnModelProps {
|
|
|
197
255
|
onScopeChange?: (activeScopeId: string, scopePath: StudioState['scopePath'], state: StudioState) => void
|
|
198
256
|
onModeChange?: (event: StudioModeChangeEvent) => void
|
|
199
257
|
onValidation?: (event: StudioValidationEvent) => void
|
|
258
|
+
onSidebarChange?: (event: StudioSidebarChangeEvent) => void
|
|
200
259
|
}
|
|
201
260
|
export interface BpmnStudioRenderContext {
|
|
202
261
|
readonly studio: BpmnStudioController
|
|
@@ -204,6 +263,8 @@ export interface BpmnStudioRenderContext {
|
|
|
204
263
|
readonly actions: StudioShellActions
|
|
205
264
|
readonly state: Readonly<StudioState>
|
|
206
265
|
readonly mode: StudioMode
|
|
266
|
+
readonly ui: StudioUiContext
|
|
267
|
+
readonly panelSelection: StudioPanelSelection
|
|
207
268
|
}
|
|
208
269
|
export type BpmnStudioRenderSlot = ReactNode | ((context: BpmnStudioRenderContext) => ReactNode)
|
|
209
270
|
export interface BpmnStudioHandle {
|
|
@@ -211,10 +272,17 @@ export interface BpmnStudioHandle {
|
|
|
211
272
|
readonly shell: BpmnStudioShell | null
|
|
212
273
|
readonly actions: StudioShellActions | null
|
|
213
274
|
readonly mode: StudioMode | undefined
|
|
275
|
+
getConfig(): Readonly<BpmnStudioConfig> | undefined
|
|
276
|
+
setConfig(config?: BpmnStudioConfig): Readonly<BpmnStudioConfig> | undefined
|
|
214
277
|
getMode(): StudioMode | undefined
|
|
215
278
|
getAllowedModes(): readonly StudioMode[]
|
|
216
279
|
setMode(mode: StudioMode): boolean
|
|
217
280
|
setAllowedModes(modes: readonly StudioMode[]): readonly StudioMode[]
|
|
281
|
+
getSidebarState(): StudioSidebarState | undefined
|
|
282
|
+
setSidebarCollapsed(side: StudioSidebarSide, collapsed: boolean): boolean
|
|
283
|
+
subscribeSidebarChange(listener: (event: StudioSidebarChangeEvent) => void): () => void
|
|
284
|
+
getPanelSelection(): StudioPanelSelection | undefined
|
|
285
|
+
subscribePanelSelection(listener: (selection: StudioPanelSelection) => void): () => void
|
|
218
286
|
refreshPresentation(): void
|
|
219
287
|
validate(): StudioValidationIssue[]
|
|
220
288
|
exportXml(engine?: EngineId): string
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ function resolveModel({ model, xml, engine = 'flowable' }) {
|
|
|
51
51
|
|
|
52
52
|
export function useBpmnStudio(options = {}) {
|
|
53
53
|
const ownedRef = useRef(null);
|
|
54
|
-
if (!options.studio && !ownedRef.current) ownedRef.current = createStudioController({ model: resolveModel(options), propertiesProfile: options.propertiesProfile, allowedNodeTypes: options.allowedNodeTypes, allowedEdgeTypes: options.allowedEdgeTypes });
|
|
54
|
+
if (!options.studio && !ownedRef.current) ownedRef.current = createStudioController({ model: resolveModel(options), config: options.config, propertiesProfile: options.propertiesProfile, allowedNodeTypes: options.allowedNodeTypes, allowedEdgeTypes: options.allowedEdgeTypes });
|
|
55
55
|
const studio = options.studio || ownedRef.current;
|
|
56
56
|
const [state, setState] = useState(() => studio.getState());
|
|
57
57
|
useEffect(() => studio.subscribe((event) => setState(event.state)), [studio]);
|
|
@@ -111,12 +111,21 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
111
111
|
const [headerPortal, setHeaderPortal] = useState(null);
|
|
112
112
|
const [headerStartPortal, setHeaderStartPortal] = useState(null);
|
|
113
113
|
const [headerActionsPortal, setHeaderActionsPortal] = useState(null);
|
|
114
|
+
const [rightPortal, setRightPortal] = useState(null);
|
|
115
|
+
const nativeRightSlotRef = useRef(null);
|
|
116
|
+
if (!nativeRightSlotRef.current) nativeRightSlotRef.current = (context) => {
|
|
117
|
+
const portal = { target: context.container, context };
|
|
118
|
+
setRightPortal(portal);
|
|
119
|
+
return () => setRightPortal((current) => current === portal ? null : current);
|
|
120
|
+
};
|
|
114
121
|
const callbacksRef = useRef({});
|
|
115
122
|
callbacksRef.current = props;
|
|
116
|
-
if (!props.studio && !ownedStudioRef.current) ownedStudioRef.current = createStudioController({ model: resolveModel(props), propertiesProfile: props.propertiesProfile, allowedNodeTypes: props.allowedNodeTypes, allowedEdgeTypes: props.allowedEdgeTypes });
|
|
123
|
+
if (!props.studio && !ownedStudioRef.current) ownedStudioRef.current = createStudioController({ model: resolveModel(props), config: props.config, propertiesProfile: props.propertiesProfile, allowedNodeTypes: props.allowedNodeTypes, allowedEdgeTypes: props.allowedEdgeTypes });
|
|
117
124
|
const studio = props.studio || ownedStudioRef.current;
|
|
118
125
|
const [studioState, setStudioState] = useState(() => studio.getState());
|
|
119
126
|
const [actualMode, setActualMode] = useState(() => props.mode || props.allowedModes?.[0] || 'design');
|
|
127
|
+
const [actualUi, setActualUi] = useState(() => Object.freeze({ controlSize: 'medium', controlHeight: '32px' }));
|
|
128
|
+
const [panelSelection, setPanelSelection] = useState(() => Object.freeze({ selection: studio.selection, selectedElement: studio.getSelectedElement(), trace: null }));
|
|
120
129
|
const subtitleResolverRef = useRef(props.nodeSubtitleResolver);
|
|
121
130
|
subtitleResolverRef.current = props.nodeSubtitleResolver;
|
|
122
131
|
const stableSubtitleResolverRef = useRef((context) => subtitleResolverRef.current?.(context));
|
|
@@ -124,11 +133,22 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
124
133
|
studio,
|
|
125
134
|
get shell() { return shellRef.current; },
|
|
126
135
|
get actions() { return shellRef.current?.actions || null; },
|
|
136
|
+
getConfig: () => shellRef.current?.getConfig(),
|
|
137
|
+
setConfig: (config) => {
|
|
138
|
+
const result = shellRef.current?.setConfig(config);
|
|
139
|
+
if (shellRef.current) setActualUi(Object.freeze({ ...shellRef.current.ui }));
|
|
140
|
+
return result;
|
|
141
|
+
},
|
|
127
142
|
get mode() { return shellRef.current?.getMode(); },
|
|
128
143
|
getMode: () => shellRef.current?.getMode(),
|
|
129
144
|
getAllowedModes: () => shellRef.current?.getAllowedModes() || [],
|
|
130
145
|
setMode: (mode) => shellRef.current?.setMode(mode) || false,
|
|
131
146
|
setAllowedModes: (modes) => shellRef.current?.setAllowedModes(modes) || [],
|
|
147
|
+
getSidebarState: () => shellRef.current?.getSidebarState(),
|
|
148
|
+
setSidebarCollapsed: (side, collapsed) => shellRef.current?.setSidebarCollapsed(side, collapsed) || false,
|
|
149
|
+
subscribeSidebarChange: (listener) => shellRef.current?.subscribeSidebarChange(listener) || (() => {}),
|
|
150
|
+
getPanelSelection: () => shellRef.current?.getPanelSelection(),
|
|
151
|
+
subscribePanelSelection: (listener) => shellRef.current?.subscribePanelSelection(listener) || (() => {}),
|
|
132
152
|
refreshPresentation: () => shellRef.current?.refreshPresentation(),
|
|
133
153
|
validate: () => shellRef.current?.validate() || [],
|
|
134
154
|
exportXml: (engine) => studio.exportXml(engine),
|
|
@@ -140,6 +160,7 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
140
160
|
const hasNativeHeader = props.header !== undefined && props.header !== null;
|
|
141
161
|
const hasNativeHeaderStart = props.headerStart !== undefined && props.headerStart !== null;
|
|
142
162
|
const hasNativeHeaderActions = props.headerActions !== undefined && props.headerActions !== null;
|
|
163
|
+
const hasNativeRight = props.right !== undefined && props.right !== null;
|
|
143
164
|
useEffect(() => {
|
|
144
165
|
if (!hostRef.current) return undefined;
|
|
145
166
|
setStudioState(studio.getState());
|
|
@@ -154,9 +175,11 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
154
175
|
if (hasNativeHeaderStart) shellSlots.headerStart = createPortalSlot(setHeaderStartPortal);
|
|
155
176
|
if (hasNativeHeaderActions) shellSlots.headerActions = createPortalSlot(setHeaderActionsPortal);
|
|
156
177
|
}
|
|
178
|
+
if (hasNativeRight) shellSlots.right = nativeRightSlotRef.current;
|
|
157
179
|
const shell = new CoreStudioShell({
|
|
158
180
|
container: hostRef.current,
|
|
159
181
|
studio,
|
|
182
|
+
config: props.config,
|
|
160
183
|
iconRegistry: props.iconRegistry,
|
|
161
184
|
paletteRegistry: props.paletteRegistry,
|
|
162
185
|
propertiesRegistry: props.propertiesRegistry,
|
|
@@ -192,6 +215,9 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
192
215
|
projectionOptions: props.projectionOptions,
|
|
193
216
|
leftWidth: props.leftWidth,
|
|
194
217
|
rightWidth: props.rightWidth,
|
|
218
|
+
timeline: props.timeline,
|
|
219
|
+
runtimeDetails: props.runtimeDetails,
|
|
220
|
+
runtimeTraceOptions: props.runtimeTraceOptions,
|
|
195
221
|
theme: props.theme,
|
|
196
222
|
runtimeAppearance: props.runtimeAppearance,
|
|
197
223
|
svgExport: props.svgExport,
|
|
@@ -199,19 +225,28 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
199
225
|
});
|
|
200
226
|
shellRef.current = shell;
|
|
201
227
|
setActualMode(shell.getMode());
|
|
228
|
+
setActualUi(Object.freeze({ ...shell.ui }));
|
|
202
229
|
const offMode = shell.subscribeMode((event) => {
|
|
203
230
|
setActualMode(event.mode);
|
|
204
231
|
callbacksRef.current.onModeChange?.(event);
|
|
205
232
|
});
|
|
206
233
|
const offValidation = shell.subscribeValidation((event) => callbacksRef.current.onValidation?.(event));
|
|
234
|
+
setPanelSelection(shell.getPanelSelection());
|
|
235
|
+
const offPanelSelection = shell.subscribePanelSelection(setPanelSelection);
|
|
236
|
+
const offSidebar = shell.subscribeSidebarChange((event) => callbacksRef.current.onSidebarChange?.(event));
|
|
207
237
|
const off = studio.subscribe((event) => {
|
|
208
238
|
setStudioState(event.state);
|
|
209
239
|
if (event.type === 'modelChanged') callbacksRef.current.onChange?.(studio.model, event.reason, studio.exportXml());
|
|
210
240
|
if (event.type === 'selectionChanged') callbacksRef.current.onSelectionChange?.(studio.selection, studio.getSelectedElement());
|
|
211
241
|
if (event.type === 'scopeChanged') callbacksRef.current.onScopeChange?.(event.activeScopeId, event.scopePath, studio.getState());
|
|
212
242
|
});
|
|
213
|
-
return () => { offMode(); offValidation(); off(); shell.destroy(); shellRef.current = null; };
|
|
243
|
+
return () => { offMode(); offValidation(); offPanelSelection(); offSidebar(); off(); shell.destroy(); shellRef.current = null; };
|
|
214
244
|
}, [studio, hasNativeHeader, hasNativeHeaderStart, hasNativeHeaderActions]);
|
|
245
|
+
// The content render function may change each render; only slot ownership
|
|
246
|
+
// changes replace the right mount. Neither case reconstructs the Shell.
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
shellRef.current?._setRightSlot(hasNativeRight ? nativeRightSlotRef.current : props.slots?.right);
|
|
249
|
+
}, [studio, hasNativeRight, props.slots?.right, hasNativeHeader, hasNativeHeaderStart, hasNativeHeaderActions]);
|
|
215
250
|
useEffect(() => () => {
|
|
216
251
|
if (!props.studio && ownedStudioRef.current === studio) {
|
|
217
252
|
studio.destroy();
|
|
@@ -234,6 +269,12 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
234
269
|
if (props.mode) shell.setMode(props.mode);
|
|
235
270
|
}, [props.allowedModes, props.mode]);
|
|
236
271
|
useEffect(() => { if (props.projection) shellRef.current?.setProjection(props.projection); }, [props.projection]);
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
const shell = shellRef.current;
|
|
274
|
+
if (!shell) return;
|
|
275
|
+
shell.setConfig(props.config);
|
|
276
|
+
setActualUi(Object.freeze({ ...shell.ui }));
|
|
277
|
+
}, [props.config]);
|
|
237
278
|
useEffect(() => { if (props.regions !== undefined) shellRef.current?.setRegions(props.regions); }, [props.regions]);
|
|
238
279
|
useEffect(() => { if (props.theme !== undefined) shellRef.current?.setTheme(props.theme); }, [props.theme]);
|
|
239
280
|
useEffect(() => { if (props.runtimeAppearance !== undefined) shellRef.current?.setRuntimeAppearance(props.runtimeAppearance); }, [props.runtimeAppearance]);
|
|
@@ -241,6 +282,7 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
241
282
|
useEffect(() => {
|
|
242
283
|
const shell = shellRef.current;
|
|
243
284
|
if (!shell) return;
|
|
285
|
+
if (props.timeline === undefined && props.runtimeDetails === undefined && props.runtimeTraceOptions === undefined) return;
|
|
244
286
|
shell.rendererOptions.timeline = props.timeline;
|
|
245
287
|
shell.rendererOptions.runtimeDetails = props.runtimeDetails;
|
|
246
288
|
shell.rendererOptions.runtimeTraceOptions = props.runtimeTraceOptions;
|
|
@@ -254,6 +296,8 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
254
296
|
actions: portal.context.actions,
|
|
255
297
|
state: studioState,
|
|
256
298
|
mode: actualMode,
|
|
299
|
+
ui: actualUi,
|
|
300
|
+
panelSelection,
|
|
257
301
|
};
|
|
258
302
|
return createPortal(typeof slot === 'function' ? slot(context) : slot, portal.target, key);
|
|
259
303
|
};
|
|
@@ -262,6 +306,7 @@ export const BpmnStudio = forwardRef(function BpmnStudio(props, ref) {
|
|
|
262
306
|
renderSlot(props.header, headerPortal, 'bpmn-nova-header'),
|
|
263
307
|
renderSlot(props.headerStart, headerStartPortal, 'bpmn-nova-header-start'),
|
|
264
308
|
renderSlot(props.headerActions, headerActionsPortal, 'bpmn-nova-header-actions'),
|
|
309
|
+
renderSlot(props.right, rightPortal, 'bpmn-nova-right'),
|
|
265
310
|
);
|
|
266
311
|
});
|
|
267
312
|
|
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
|
+
|