@bpmn-nova/studio 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 +75 -6
- package/dist/config.js +285 -0
- package/dist/controller.js +19 -4
- package/dist/index.d.ts +101 -2
- package/dist/modules/export-svg/render.js +82 -62
- package/dist/modules/node-geometry/index.d.ts +19 -0
- package/dist/modules/node-geometry/index.js +147 -0
- package/dist/modules/renderer-svg/index.js +63 -25
- package/dist/modules/viewer/index.d.ts +1 -1
- package/dist/modules/viewer/index.js +86 -35
- package/dist/panel-selection.js +60 -0
- package/dist/shell.js +315 -87
- package/dist/sidebars.js +195 -0
- package/dist/styles.css +72 -12
- package/llms-full.txt +1614 -93
- package/llms.txt +76 -15
- package/package.json +1 -1
package/dist/sidebars.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const SIDES = ['left', 'right'];
|
|
2
|
+
let sidebarId = 0;
|
|
3
|
+
|
|
4
|
+
/** Default-Shell layout only. No model, history, routing or viewport mutations. */
|
|
5
|
+
export class StudioSidebars {
|
|
6
|
+
constructor(shell, elements) {
|
|
7
|
+
this.shell = shell;
|
|
8
|
+
this.elements = elements;
|
|
9
|
+
this.width = shell.container.getBoundingClientRect().width;
|
|
10
|
+
this.band = this.width <= 720 ? 'narrow' : 'wide';
|
|
11
|
+
this.collapsed = {
|
|
12
|
+
wide: Object.fromEntries(SIDES.map((side) => [side, this.config(side).collapsible && this.config(side).defaultCollapsed])),
|
|
13
|
+
narrow: Object.fromEntries(SIDES.map((side) => [side, this.config(side).collapsible])),
|
|
14
|
+
};
|
|
15
|
+
this.buttons = {};
|
|
16
|
+
this.inactive = {};
|
|
17
|
+
this.scrollPositions = {};
|
|
18
|
+
this.listeners = new Set();
|
|
19
|
+
this.destroyed = false;
|
|
20
|
+
this.motion = shell.container.ownerDocument.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)');
|
|
21
|
+
this.onMotionChange = () => { if (this.motion.matches) this.finishMotion(); };
|
|
22
|
+
this.motion?.addEventListener('change', this.onMotionChange);
|
|
23
|
+
elements.body.classList.add('is-managed-sidebars');
|
|
24
|
+
for (const side of SIDES) {
|
|
25
|
+
const panel = elements[side];
|
|
26
|
+
panel.id ||= `nova-sidebar-${++sidebarId}`;
|
|
27
|
+
const button = shell.container.ownerDocument.createElement('button');
|
|
28
|
+
button.type = 'button';
|
|
29
|
+
button.className = `nova-studio-sidebar-toggle is-${side}`;
|
|
30
|
+
button.dataset.sidebar = side;
|
|
31
|
+
button.setAttribute('aria-controls', panel.id);
|
|
32
|
+
button.addEventListener('click', () => this.setCollapsed(side, !this.getState()[side].collapsed, 'button'));
|
|
33
|
+
elements.body.appendChild(button);
|
|
34
|
+
this.buttons[side] = button;
|
|
35
|
+
}
|
|
36
|
+
this.onTransitionEnd = (event) => {
|
|
37
|
+
if (event.target === elements.body && event.propertyName === 'grid-template-columns') this.finishMotion();
|
|
38
|
+
};
|
|
39
|
+
elements.body.addEventListener('transitionend', this.onTransitionEnd);
|
|
40
|
+
this.apply({ animate: false });
|
|
41
|
+
this.observer = typeof ResizeObserver === 'function' ? new ResizeObserver((entries) => {
|
|
42
|
+
const nextWidth = entries[0]?.contentRect.width ?? shell.container.getBoundingClientRect().width;
|
|
43
|
+
if (this.destroyed || Math.abs(nextWidth - this.width) < 0.01) return;
|
|
44
|
+
const previous = this.getState();
|
|
45
|
+
this.width = nextWidth;
|
|
46
|
+
this.band = nextWidth <= 720 ? 'narrow' : 'wide';
|
|
47
|
+
this.apply({ animate: false });
|
|
48
|
+
this.notifyChanges(previous, 'responsive');
|
|
49
|
+
}) : null;
|
|
50
|
+
this.observer?.observe(shell.container);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
config(side) { return this.shell._config.ui[`${side}Panel`]; }
|
|
54
|
+
|
|
55
|
+
getState() {
|
|
56
|
+
return Object.freeze(Object.fromEntries(SIDES.map((side) => [side, Object.freeze({
|
|
57
|
+
collapsed: this.config(side).collapsible && this.collapsed[this.band][side],
|
|
58
|
+
hidden: this.shell._isSidebarHidden(side),
|
|
59
|
+
collapsible: this.config(side).collapsible,
|
|
60
|
+
})])));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
subscribe(listener) {
|
|
64
|
+
if (typeof listener !== 'function') throw new TypeError('subscribeSidebarChange() requires a listener function.');
|
|
65
|
+
if (this.destroyed) return () => {};
|
|
66
|
+
this.listeners.add(listener);
|
|
67
|
+
return () => this.listeners.delete(listener);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setCollapsed(side, collapsed, source = 'api') {
|
|
71
|
+
if (!SIDES.includes(side)) throw new TypeError('Sidebar side must be left or right.');
|
|
72
|
+
if (typeof collapsed !== 'boolean') throw new TypeError('Sidebar collapsed must be a boolean.');
|
|
73
|
+
if (this.destroyed || this.shell._destroyed) return false;
|
|
74
|
+
const previous = this.getState();
|
|
75
|
+
const state = previous[side];
|
|
76
|
+
if (state.hidden || !state.collapsible || state.collapsed === collapsed) return false;
|
|
77
|
+
this.collapsed[this.band][side] = collapsed;
|
|
78
|
+
this.apply();
|
|
79
|
+
this.notifyChanges(previous, source);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
updateConfig(previous) {
|
|
84
|
+
for (const side of SIDES) {
|
|
85
|
+
if (!this.config(side).collapsible) {
|
|
86
|
+
this.collapsed.wide[side] = false;
|
|
87
|
+
this.collapsed.narrow[side] = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
this.apply();
|
|
91
|
+
this.notifyChanges(previous, 'config');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
notifyChanges(previous, source) {
|
|
95
|
+
const next = this.getState();
|
|
96
|
+
for (const side of SIDES) {
|
|
97
|
+
if (next[side].collapsed === previous[side].collapsed) continue;
|
|
98
|
+
const event = Object.freeze({ side, collapsed: next[side].collapsed, previousCollapsed: previous[side].collapsed, mode: this.shell.mode, source });
|
|
99
|
+
for (const listener of [...this.listeners]) {
|
|
100
|
+
try { listener(event); } catch (error) { console.error('BpmnStudioShell sidebar listener failed.', error); }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
finishMotion({ resume = true } = {}) {
|
|
106
|
+
clearTimeout(this.motionTimeout);
|
|
107
|
+
this.motionTimeout = null;
|
|
108
|
+
if (resume) this.motionViewer?._setSidebarTransitionActive(false);
|
|
109
|
+
this.motionViewer = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
apply({ animate = true } = {}) {
|
|
113
|
+
if (this.destroyed) return;
|
|
114
|
+
const { body } = this.elements;
|
|
115
|
+
const state = this.getState();
|
|
116
|
+
const desired = Object.fromEntries(SIDES.map((side) => [side,
|
|
117
|
+
state[side].hidden || state[side].collapsed ? 0 : this.shell._config.ui.sidebarWidth[side],
|
|
118
|
+
]));
|
|
119
|
+
const total = desired.left + desired.right;
|
|
120
|
+
const available = Math.max(0, this.width - Math.min(96, this.width));
|
|
121
|
+
const scale = total > available ? available / total : 1;
|
|
122
|
+
const widths = { left: desired.left * scale, right: desired.right * scale };
|
|
123
|
+
const changed = !this.appliedWidths || SIDES.some((side) => widths[side] !== this.appliedWidths[side]);
|
|
124
|
+
const moving = animate && !this.motion?.matches
|
|
125
|
+
&& (changed ? Boolean(this.appliedWidths) : Boolean(this.motionTimeout));
|
|
126
|
+
if (moving && changed) {
|
|
127
|
+
clearTimeout(this.motionTimeout);
|
|
128
|
+
if (this.motionViewer !== this.shell.viewer) this.finishMotion();
|
|
129
|
+
this.motionViewer = this.shell.viewer;
|
|
130
|
+
this.motionViewer?._setSidebarTransitionActive(true);
|
|
131
|
+
// Fallback also covers transition cancellation, reduced motion and detached hosts.
|
|
132
|
+
this.motionTimeout = setTimeout(() => this.finishMotion(), 240);
|
|
133
|
+
}
|
|
134
|
+
body.classList.toggle('is-sidebar-animated', Boolean(moving));
|
|
135
|
+
body.classList.toggle('is-sidebar-narrow', this.band === 'narrow');
|
|
136
|
+
for (const side of SIDES) {
|
|
137
|
+
const panel = this.elements[side];
|
|
138
|
+
const content = this.elements[`${side}Content`];
|
|
139
|
+
const button = this.buttons[side];
|
|
140
|
+
const inactive = state[side].hidden || state[side].collapsed;
|
|
141
|
+
const buttonHidden = state[side].hidden || !state[side].collapsible;
|
|
142
|
+
if (inactive && this.inactive[side] === false) {
|
|
143
|
+
this.scrollPositions[side] = [content, ...content.querySelectorAll('*')]
|
|
144
|
+
.filter((element) => element.scrollTop || element.scrollLeft)
|
|
145
|
+
.map((element) => ({ element, top: element.scrollTop, left: element.scrollLeft }));
|
|
146
|
+
}
|
|
147
|
+
const activeElement = panel.ownerDocument.activeElement;
|
|
148
|
+
if ((inactive && panel.contains(activeElement)) || (buttonHidden && activeElement === button)) {
|
|
149
|
+
if (!buttonHidden) {
|
|
150
|
+
button.hidden = false;
|
|
151
|
+
button.focus({ preventScroll: true });
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.shell._canvasHost?.setAttribute('tabindex', '-1');
|
|
155
|
+
this.shell._canvasHost?.focus({ preventScroll: true });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
panel.inert = inactive;
|
|
159
|
+
panel.setAttribute('aria-hidden', String(inactive));
|
|
160
|
+
panel.hidden = state[side].hidden;
|
|
161
|
+
button.hidden = buttonHidden;
|
|
162
|
+
button.setAttribute('aria-expanded', String(!state[side].collapsed));
|
|
163
|
+
const label = `${state[side].collapsed ? '展开' : '收起'}${side === 'left' ? '左侧节点面板' : '右侧面板'}`;
|
|
164
|
+
button.setAttribute('aria-label', label);
|
|
165
|
+
button.title = label;
|
|
166
|
+
button.textContent = (side === 'left') !== state[side].collapsed ? '‹' : '›';
|
|
167
|
+
body.classList.toggle(`is-${side}-collapsed`, state[side].collapsed);
|
|
168
|
+
body.style.setProperty(`--nova-sidebar-${side}-track`, `${widths[side]}px`);
|
|
169
|
+
if (!inactive || !content.style.width) content.style.width = `${inactive ? this.shell._config.ui.sidebarWidth[side] : widths[side]}px`;
|
|
170
|
+
if (!inactive && this.inactive[side]) {
|
|
171
|
+
for (const { element, top, left } of this.scrollPositions[side] || []) {
|
|
172
|
+
if (!content.contains(element) && element !== content) continue;
|
|
173
|
+
element.scrollTop = top;
|
|
174
|
+
element.scrollLeft = left;
|
|
175
|
+
}
|
|
176
|
+
delete this.scrollPositions[side];
|
|
177
|
+
}
|
|
178
|
+
this.inactive[side] = inactive;
|
|
179
|
+
}
|
|
180
|
+
this.elements.rightContent.dataset.layout = this.config('right').layout;
|
|
181
|
+
this.appliedWidths = widths;
|
|
182
|
+
// Resolve auto projection against the final layout, not an intermediate width.
|
|
183
|
+
if (!moving) this.finishMotion();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
destroy() {
|
|
187
|
+
this.destroyed = true;
|
|
188
|
+
this.observer?.disconnect();
|
|
189
|
+
this.motion?.removeEventListener('change', this.onMotionChange);
|
|
190
|
+
this.elements.body.removeEventListener('transitionend', this.onTransitionEnd);
|
|
191
|
+
this.listeners.clear();
|
|
192
|
+
clearTimeout(this.motionTimeout);
|
|
193
|
+
this.motionViewer = null;
|
|
194
|
+
}
|
|
195
|
+
}
|
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
|
+
|