@bpmn-nova/studio 0.3.0-preview → 0.3.2-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 +245 -20
- package/dist/canvas.js +7 -4
- package/dist/context-menu.js +2 -2
- package/dist/controller.js +84 -6
- package/dist/index.d.ts +138 -38
- package/dist/index.js +12 -11
- package/dist/interactions.js +1 -1
- package/dist/modules/bpmn-model/index.d.ts +11 -0
- package/dist/modules/bpmn-model/index.js +703 -0
- package/dist/modules/core/containment.js +590 -0
- package/dist/modules/core/gateway.js +72 -0
- package/dist/modules/core/history.js +32 -0
- package/dist/modules/core/index.d.ts +268 -0
- package/dist/modules/core/index.js +7 -0
- package/dist/modules/core/layout.js +644 -0
- package/dist/modules/core/model.js +287 -0
- package/dist/modules/core/runtime-transition-route.js +360 -0
- package/dist/modules/core/scope.js +99 -0
- package/dist/modules/designer/index.d.ts +79 -0
- package/dist/modules/designer/index.js +607 -0
- package/dist/modules/engine-activiti/index.d.ts +19 -0
- package/dist/modules/engine-activiti/index.js +160 -0
- package/dist/modules/engine-flowable/index.d.ts +19 -0
- package/dist/modules/engine-flowable/index.js +160 -0
- package/dist/modules/export-svg/index.d.ts +112 -0
- package/dist/modules/export-svg/index.js +2 -0
- package/dist/modules/export-svg/preview.js +327 -0
- package/dist/modules/export-svg/render.js +718 -0
- package/dist/modules/icons/index.d.ts +24 -0
- package/dist/modules/icons/index.js +264 -0
- package/dist/modules/palette/index.d.ts +74 -0
- package/dist/modules/palette/index.js +99 -0
- package/dist/modules/palette/panel.js +99 -0
- package/dist/modules/properties/index.d.ts +20 -0
- package/dist/modules/properties/index.js +19 -0
- package/dist/modules/properties-activiti/index.d.ts +3 -0
- package/dist/modules/properties-activiti/index.js +97 -0
- package/dist/modules/properties-bpmn/index.d.ts +3 -0
- package/dist/modules/properties-bpmn/index.js +518 -0
- package/dist/modules/properties-core/index.d.ts +124 -0
- package/dist/modules/properties-core/index.js +312 -0
- package/dist/modules/properties-flowable/index.d.ts +3 -0
- package/dist/modules/properties-flowable/index.js +114 -0
- package/dist/modules/properties-renderer/index.d.ts +25 -0
- package/dist/modules/properties-renderer/index.js +491 -0
- package/dist/modules/renderer-svg/index.d.ts +118 -0
- package/dist/modules/renderer-svg/index.js +1460 -0
- package/dist/modules/runtime/index.d.ts +169 -0
- package/dist/modules/runtime/index.js +535 -0
- package/dist/modules/theme/index.d.ts +95 -0
- package/dist/modules/theme/index.js +368 -0
- package/dist/modules/viewer/index.d.ts +265 -0
- package/dist/modules/viewer/index.js +1011 -0
- package/dist/modules/viewer/runtime-content.js +123 -0
- package/dist/modules/viewer/runtime-details-motion.js +228 -0
- package/dist/modules/viewer/runtime-trace.js +574 -0
- package/dist/modules/viewer/timeline.js +276 -0
- package/dist/selection-layout.js +1 -1
- package/dist/shell.js +210 -26
- package/dist/styles.css +116 -7
- package/llms-full.txt +3082 -0
- package/llms.txt +225 -0
- package/package.json +39 -16
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
const THEME_MODES = new Set(['light', 'dark', 'auto']);
|
|
2
|
+
const TONE_NAME = /^[a-z][a-z0-9-]{0,47}$/;
|
|
3
|
+
|
|
4
|
+
const COLOR_VARIABLES = Object.freeze({
|
|
5
|
+
canvas: '--nova-color-canvas',
|
|
6
|
+
surface: '--nova-color-surface',
|
|
7
|
+
surfaceRaised: '--nova-color-surface-raised',
|
|
8
|
+
surfaceSubtle: '--nova-color-surface-subtle',
|
|
9
|
+
surfaceMuted: '--nova-color-surface-muted',
|
|
10
|
+
text: '--nova-color-text',
|
|
11
|
+
textSecondary: '--nova-color-text-secondary',
|
|
12
|
+
textMuted: '--nova-color-text-muted',
|
|
13
|
+
textInverse: '--nova-color-text-inverse',
|
|
14
|
+
border: '--nova-color-border',
|
|
15
|
+
borderStrong: '--nova-color-border-strong',
|
|
16
|
+
divider: '--nova-color-divider',
|
|
17
|
+
primary: '--nova-color-primary',
|
|
18
|
+
primaryHover: '--nova-color-primary-hover',
|
|
19
|
+
primarySoft: '--nova-color-primary-soft',
|
|
20
|
+
focusRing: '--nova-color-focus-ring',
|
|
21
|
+
edge: '--nova-color-edge',
|
|
22
|
+
gridDot: '--nova-color-grid-dot',
|
|
23
|
+
backdrop: '--nova-color-backdrop',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const SHADOW_VARIABLES = Object.freeze({
|
|
27
|
+
sm: '--nova-shadow-sm',
|
|
28
|
+
md: '--nova-shadow-md',
|
|
29
|
+
lg: '--nova-shadow-lg',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const TONE_FIELDS = Object.freeze({
|
|
33
|
+
foreground: 'foreground',
|
|
34
|
+
background: 'background',
|
|
35
|
+
border: 'border',
|
|
36
|
+
strong: 'strong',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const SNAPSHOT_TONES = Object.freeze([
|
|
40
|
+
'primary', 'success', 'danger', 'warning', 'info', 'neutral',
|
|
41
|
+
'user-task', 'service-task', 'script-task', 'rule-task', 'message-task', 'manual-task',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const DEFAULT_ACTIONS = Object.freeze({
|
|
45
|
+
submit: Object.freeze({ label: '提交', iconId: 'ui.statusCompleted', tone: 'success' }),
|
|
46
|
+
approve: Object.freeze({ label: '通过', iconId: 'ui.statusCompleted', tone: 'success' }),
|
|
47
|
+
reject: Object.freeze({ label: '驳回', iconId: 'ui.statusFailed', tone: 'danger' }),
|
|
48
|
+
return: Object.freeze({ label: '退回', iconId: 'ui.statusRejected', tone: 'warning' }),
|
|
49
|
+
'add-sign': Object.freeze({ label: '加签', iconId: 'ui.add', tone: 'info' }),
|
|
50
|
+
transfer: Object.freeze({ label: '转办', iconId: 'ui.locateTarget', tone: 'info' }),
|
|
51
|
+
delegate: Object.freeze({ label: '委派', iconId: 'node.userTask', tone: 'info' }),
|
|
52
|
+
withdraw: Object.freeze({ label: '撤回', iconId: 'ui.statusRejected', tone: 'warning' }),
|
|
53
|
+
comment: Object.freeze({ label: '备注', iconId: 'ui.edit', tone: 'neutral' }),
|
|
54
|
+
skip: Object.freeze({ label: '跳过', iconId: 'ui.statusSkipped', tone: 'neutral' }),
|
|
55
|
+
cancel: Object.freeze({ label: '取消', iconId: 'ui.statusSkipped', tone: 'neutral' }),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const DEFAULT_STATUSES = Object.freeze({
|
|
59
|
+
idle: 'neutral',
|
|
60
|
+
active: 'primary',
|
|
61
|
+
completed: 'success',
|
|
62
|
+
failed: 'danger',
|
|
63
|
+
rejected: 'danger',
|
|
64
|
+
cancelled: 'neutral',
|
|
65
|
+
skipped: 'neutral',
|
|
66
|
+
superseded: 'neutral',
|
|
67
|
+
effective: 'success',
|
|
68
|
+
resolved: 'neutral',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const DEFAULT_TRANSITIONS = Object.freeze({
|
|
72
|
+
reject: Object.freeze({ tone: 'danger' }),
|
|
73
|
+
return: Object.freeze({ tone: 'warning' }),
|
|
74
|
+
skip: Object.freeze({ tone: 'neutral' }),
|
|
75
|
+
forward: Object.freeze({ tone: 'success' }),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function normalizeMode(mode, fallback = 'light') {
|
|
79
|
+
return THEME_MODES.has(mode) ? mode : fallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeTone(tone, fallback = 'neutral') {
|
|
83
|
+
const value = String(tone || '').trim().toLowerCase();
|
|
84
|
+
return TONE_NAME.test(value) ? value : fallback;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function supports(ownerWindow, property, value) {
|
|
88
|
+
if (typeof value !== 'string' || !value.trim()) return false;
|
|
89
|
+
const css = ownerWindow?.CSS || globalThis.CSS;
|
|
90
|
+
if (typeof css?.supports === 'function') return css.supports(property, value.trim());
|
|
91
|
+
const style = ownerWindow?.document?.createElement?.('span')?.style;
|
|
92
|
+
if (style) {
|
|
93
|
+
style.setProperty(property, value.trim());
|
|
94
|
+
return Boolean(style.getPropertyValue(property));
|
|
95
|
+
}
|
|
96
|
+
if (property === 'color') return !/[;{}]/.test(value) && /^(?:#[\da-f]{3,8}|[a-z]+|(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color|color-mix|var)\(.+\))$/i.test(value.trim());
|
|
97
|
+
return property === 'box-shadow' && !/[;}]/.test(value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function compilePalette(palette, ownerWindow) {
|
|
101
|
+
if (!palette || typeof palette !== 'object') return new Map();
|
|
102
|
+
const variables = new Map();
|
|
103
|
+
for (const [key, variable] of Object.entries(COLOR_VARIABLES)) {
|
|
104
|
+
const value = palette.colors?.[key];
|
|
105
|
+
if (supports(ownerWindow, 'color', value)) variables.set(variable, value.trim());
|
|
106
|
+
}
|
|
107
|
+
for (const [key, variable] of Object.entries(SHADOW_VARIABLES)) {
|
|
108
|
+
const value = palette.shadows?.[key];
|
|
109
|
+
if (supports(ownerWindow, 'box-shadow', value)) variables.set(variable, value.trim());
|
|
110
|
+
}
|
|
111
|
+
for (const [rawName, tone] of Object.entries(palette.tones || {})) {
|
|
112
|
+
const name = normalizeTone(rawName, '');
|
|
113
|
+
if (!name || !tone || typeof tone !== 'object') continue;
|
|
114
|
+
for (const [key, suffix] of Object.entries(TONE_FIELDS)) {
|
|
115
|
+
const value = tone[key];
|
|
116
|
+
if (supports(ownerWindow, 'color', value)) variables.set(`--nova-tone-${name}-${suffix}`, value.trim());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return variables;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeThemeInput(input, current = null) {
|
|
123
|
+
if (typeof input === 'string') return { ...(current || {}), mode: normalizeMode(input, current?.mode || 'light') };
|
|
124
|
+
if (!input || typeof input !== 'object') return { mode: 'light', light: null, dark: null };
|
|
125
|
+
return {
|
|
126
|
+
mode: normalizeMode(input.mode, current?.mode || 'light'),
|
|
127
|
+
light: input.light === undefined ? (current?.light || null) : input.light,
|
|
128
|
+
dark: input.dark === undefined ? (current?.dark || null) : input.dark,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function mediaListener(media, handler, add) {
|
|
133
|
+
if (!media) return;
|
|
134
|
+
if (typeof media[add ? 'addEventListener' : 'removeEventListener'] === 'function') {
|
|
135
|
+
media[add ? 'addEventListener' : 'removeEventListener']('change', handler);
|
|
136
|
+
} else if (typeof media[add ? 'addListener' : 'removeListener'] === 'function') {
|
|
137
|
+
media[add ? 'addListener' : 'removeListener'](handler);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readAttribute(root, name) {
|
|
142
|
+
return typeof root?.getAttribute === 'function' ? root.getAttribute(name) : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function writeAttribute(root, name, value) {
|
|
146
|
+
if (typeof root?.setAttribute === 'function') root.setAttribute(name, value);
|
|
147
|
+
else if (root?.dataset) root.dataset[name === 'data-nova-theme' ? 'novaTheme' : 'novaThemeMode'] = value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function removeAttribute(root, name) {
|
|
151
|
+
if (typeof root?.removeAttribute === 'function') root.removeAttribute(name);
|
|
152
|
+
else if (root?.dataset) delete root.dataset[name === 'data-nova-theme' ? 'novaTheme' : 'novaThemeMode'];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function readStyle(root, name) {
|
|
156
|
+
return root?.style?.getPropertyValue?.(name) || '';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function writeStyle(root, name, value) {
|
|
160
|
+
root?.style?.setProperty?.(name, value);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function removeStyle(root, name) {
|
|
164
|
+
root?.style?.removeProperty?.(name);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Owns theme state for one top-level BPMN Nova root. Nested modules inherit the
|
|
169
|
+
* root tokens and share this controller instead of creating duplicate media listeners.
|
|
170
|
+
*/
|
|
171
|
+
export class ThemeController {
|
|
172
|
+
constructor({ root, theme = null, onChange = null } = {}) {
|
|
173
|
+
if (!root) throw new Error('ThemeController requires a root element.');
|
|
174
|
+
this.root = root;
|
|
175
|
+
this.ownerWindow = root.ownerDocument?.defaultView || (typeof window !== 'undefined' ? window : null);
|
|
176
|
+
this.options = normalizeThemeInput(theme);
|
|
177
|
+
this.onChange = typeof onChange === 'function' ? onChange : null;
|
|
178
|
+
this.listeners = new Set();
|
|
179
|
+
this.media = null;
|
|
180
|
+
this._managedVariables = new Set();
|
|
181
|
+
this._originalVariables = new Map();
|
|
182
|
+
this._originalTheme = readAttribute(root, 'data-nova-theme');
|
|
183
|
+
this._originalMode = readAttribute(root, 'data-nova-theme-mode');
|
|
184
|
+
this._originalColorScheme = root.style?.colorScheme || '';
|
|
185
|
+
this._mediaChange = () => this._apply(true);
|
|
186
|
+
this.state = Object.freeze({ mode: this.options.mode, resolvedTheme: 'light' });
|
|
187
|
+
this._syncMedia();
|
|
188
|
+
this._apply(false);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_resolve() {
|
|
192
|
+
if (this.options.mode !== 'auto') return this.options.mode;
|
|
193
|
+
return this.media?.matches ? 'dark' : 'light';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
_syncMedia() {
|
|
197
|
+
const needsMedia = this.options.mode === 'auto';
|
|
198
|
+
if (!needsMedia && this.media) {
|
|
199
|
+
mediaListener(this.media, this._mediaChange, false);
|
|
200
|
+
this.media = null;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (needsMedia && !this.media && typeof this.ownerWindow?.matchMedia === 'function') {
|
|
204
|
+
this.media = this.ownerWindow.matchMedia('(prefers-color-scheme: dark)');
|
|
205
|
+
mediaListener(this.media, this._mediaChange, true);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_apply(notify = true) {
|
|
210
|
+
const resolvedTheme = this._resolve();
|
|
211
|
+
const variables = compilePalette(this.options[resolvedTheme], this.ownerWindow);
|
|
212
|
+
for (const name of this._managedVariables) {
|
|
213
|
+
if (variables.has(name)) continue;
|
|
214
|
+
const original = this._originalVariables.get(name) || '';
|
|
215
|
+
if (original) writeStyle(this.root, name, original);
|
|
216
|
+
else removeStyle(this.root, name);
|
|
217
|
+
}
|
|
218
|
+
for (const [name, value] of variables) {
|
|
219
|
+
if (!this._originalVariables.has(name)) this._originalVariables.set(name, readStyle(this.root, name));
|
|
220
|
+
writeStyle(this.root, name, value);
|
|
221
|
+
}
|
|
222
|
+
this._managedVariables = new Set(variables.keys());
|
|
223
|
+
writeAttribute(this.root, 'data-nova-theme', resolvedTheme);
|
|
224
|
+
writeAttribute(this.root, 'data-nova-theme-mode', this.options.mode);
|
|
225
|
+
if (this.root.style) this.root.style.colorScheme = resolvedTheme;
|
|
226
|
+
this.state = Object.freeze({ mode: this.options.mode, resolvedTheme });
|
|
227
|
+
if (!notify) return;
|
|
228
|
+
this.onChange?.(this.state);
|
|
229
|
+
this.listeners.forEach((listener) => listener(this.state));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
setTheme(theme) {
|
|
233
|
+
this.options = normalizeThemeInput(theme, this.options);
|
|
234
|
+
this._syncMedia();
|
|
235
|
+
this._apply(true);
|
|
236
|
+
return this.state;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
setMode(mode) {
|
|
240
|
+
this.options = { ...this.options, mode: normalizeMode(mode, this.options.mode) };
|
|
241
|
+
this._syncMedia();
|
|
242
|
+
this._apply(true);
|
|
243
|
+
return this.state;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
getState() { return this.state; }
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Resolves the concrete color tokens used by standalone visual artifacts.
|
|
250
|
+
* A short-lived probe inherits the shipped theme stylesheet and receives the
|
|
251
|
+
* requested palette overrides without changing this controller's root state.
|
|
252
|
+
*/
|
|
253
|
+
getSnapshot(theme = 'current') {
|
|
254
|
+
const resolvedTheme = theme === 'current' || !['light', 'dark'].includes(theme)
|
|
255
|
+
? this.state.resolvedTheme
|
|
256
|
+
: theme;
|
|
257
|
+
const document = this.root.ownerDocument;
|
|
258
|
+
const ownerWindow = document?.defaultView || this.ownerWindow;
|
|
259
|
+
if (!document?.createElement || typeof ownerWindow?.getComputedStyle !== 'function') {
|
|
260
|
+
return Object.freeze({ resolvedTheme, colors: Object.freeze({}), tones: Object.freeze({}), fontFamily: 'sans-serif' });
|
|
261
|
+
}
|
|
262
|
+
const probe = document.createElement('span');
|
|
263
|
+
probe.setAttribute('data-nova-theme', resolvedTheme);
|
|
264
|
+
probe.setAttribute('aria-hidden', 'true');
|
|
265
|
+
Object.assign(probe.style, {
|
|
266
|
+
position: 'fixed',
|
|
267
|
+
left: '-100000px',
|
|
268
|
+
top: '-100000px',
|
|
269
|
+
width: '1px',
|
|
270
|
+
height: '1px',
|
|
271
|
+
visibility: 'hidden',
|
|
272
|
+
pointerEvents: 'none',
|
|
273
|
+
});
|
|
274
|
+
for (const [name, value] of compilePalette(this.options[resolvedTheme], ownerWindow)) probe.style.setProperty(name, value);
|
|
275
|
+
this.root.appendChild(probe);
|
|
276
|
+
const resolveColor = (variable, fallback = 'rgba(0, 0, 0, 0)') => {
|
|
277
|
+
probe.style.color = `var(${variable}, ${fallback})`;
|
|
278
|
+
return ownerWindow.getComputedStyle(probe).color || fallback;
|
|
279
|
+
};
|
|
280
|
+
const colors = {};
|
|
281
|
+
for (const [key, variable] of Object.entries(COLOR_VARIABLES)) colors[key] = resolveColor(variable);
|
|
282
|
+
const customToneNames = Object.keys(this.options[resolvedTheme]?.tones || {}).map((name) => normalizeTone(name, '')).filter(Boolean);
|
|
283
|
+
const tones = {};
|
|
284
|
+
for (const name of new Set([...SNAPSHOT_TONES, ...customToneNames])) {
|
|
285
|
+
tones[name] = {};
|
|
286
|
+
for (const suffix of Object.values(TONE_FIELDS)) tones[name][suffix] = resolveColor(`--nova-tone-${name}-${suffix}`);
|
|
287
|
+
tones[name] = Object.freeze(tones[name]);
|
|
288
|
+
}
|
|
289
|
+
probe.style.removeProperty('color');
|
|
290
|
+
const inherited = ownerWindow.getComputedStyle(this.root);
|
|
291
|
+
const fontFamily = inherited.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
292
|
+
probe.remove();
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
resolvedTheme,
|
|
295
|
+
colors: Object.freeze(colors),
|
|
296
|
+
tones: Object.freeze(tones),
|
|
297
|
+
fontFamily,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
subscribe(listener) {
|
|
302
|
+
if (typeof listener !== 'function') return () => {};
|
|
303
|
+
this.listeners.add(listener);
|
|
304
|
+
return () => this.listeners.delete(listener);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
destroy() {
|
|
308
|
+
if (this.media) mediaListener(this.media, this._mediaChange, false);
|
|
309
|
+
this.media = null;
|
|
310
|
+
this.listeners.clear();
|
|
311
|
+
for (const name of this._managedVariables) {
|
|
312
|
+
const original = this._originalVariables.get(name) || '';
|
|
313
|
+
if (original) writeStyle(this.root, name, original);
|
|
314
|
+
else removeStyle(this.root, name);
|
|
315
|
+
}
|
|
316
|
+
if (this._originalTheme == null) removeAttribute(this.root, 'data-nova-theme');
|
|
317
|
+
else writeAttribute(this.root, 'data-nova-theme', this._originalTheme);
|
|
318
|
+
if (this._originalMode == null) removeAttribute(this.root, 'data-nova-theme-mode');
|
|
319
|
+
else writeAttribute(this.root, 'data-nova-theme-mode', this._originalMode);
|
|
320
|
+
if (this.root.style) this.root.style.colorScheme = this._originalColorScheme;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function createThemeController(options) { return new ThemeController(options); }
|
|
325
|
+
|
|
326
|
+
export function createRuntimeAppearance(options = {}) {
|
|
327
|
+
const statuses = { ...DEFAULT_STATUSES, ...(options.statuses || {}) };
|
|
328
|
+
const actions = { ...DEFAULT_ACTIONS, ...(options.actions || {}) };
|
|
329
|
+
const transitions = { ...DEFAULT_TRANSITIONS, ...(options.transitions || {}) };
|
|
330
|
+
return Object.freeze({
|
|
331
|
+
resolveStatus(status) { return normalizeTone(statuses[status], DEFAULT_STATUSES[status] || 'neutral'); },
|
|
332
|
+
resolveAction(action = {}) {
|
|
333
|
+
const configured = actions[action.type] || {};
|
|
334
|
+
const fallback = DEFAULT_ACTIONS[action.type] || {};
|
|
335
|
+
return Object.freeze({
|
|
336
|
+
label: action.explicitLabel || configured.label || action.label || fallback.label || action.type || '审批操作',
|
|
337
|
+
iconId: configured.iconId || fallback.iconId || 'ui.edit',
|
|
338
|
+
tone: normalizeTone(configured.tone || fallback.tone, 'neutral'),
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
resolveTransition(transition = {}) {
|
|
342
|
+
const configured = transitions[transition.type] || {};
|
|
343
|
+
const fallback = DEFAULT_TRANSITIONS[transition.type] || {};
|
|
344
|
+
return Object.freeze({ tone: normalizeTone(configured.tone || fallback.tone, 'neutral') });
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function applyRuntimeTone(element, tone) {
|
|
350
|
+
if (!element?.style) return 'neutral';
|
|
351
|
+
const name = normalizeTone(tone, 'neutral');
|
|
352
|
+
element.dataset.tone = name;
|
|
353
|
+
for (const suffix of Object.values(TONE_FIELDS)) {
|
|
354
|
+
element.style.setProperty(
|
|
355
|
+
`--nova-runtime-tone-${suffix}`,
|
|
356
|
+
`var(--nova-tone-${name}-${suffix}, var(--nova-tone-neutral-${suffix}))`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return name;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export const DEFAULT_RUNTIME_APPEARANCE = Object.freeze({
|
|
363
|
+
statuses: DEFAULT_STATUSES,
|
|
364
|
+
actions: DEFAULT_ACTIONS,
|
|
365
|
+
transitions: DEFAULT_TRANSITIONS,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
export const NOVA_THEME_MODES = Object.freeze([...THEME_MODES]);
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import type { BpmnEdge, BpmnNode, NodeType, ProcessModel } from '../core/index.js'
|
|
2
|
+
import type { IconRegistry } from '../icons/index.js'
|
|
3
|
+
import type {
|
|
4
|
+
ActivityInstance,
|
|
5
|
+
ActivityStatus,
|
|
6
|
+
NodeRuntimePresentation,
|
|
7
|
+
ProcessInstanceSnapshot,
|
|
8
|
+
RuntimeApprovalActionPresentation,
|
|
9
|
+
RuntimeAssetRef,
|
|
10
|
+
RuntimeParticipant,
|
|
11
|
+
RuntimePresentation,
|
|
12
|
+
RuntimeTransition,
|
|
13
|
+
RuntimeTransitionPresentation,
|
|
14
|
+
} from '../runtime/index.js'
|
|
15
|
+
import type {
|
|
16
|
+
NovaThemeInput,
|
|
17
|
+
NovaThemeMode,
|
|
18
|
+
NovaThemeState,
|
|
19
|
+
RuntimeAppearanceOptions,
|
|
20
|
+
RuntimeAppearanceResolver,
|
|
21
|
+
} from '../theme/index.js'
|
|
22
|
+
import type { RuntimeTimelineSvgRenderer, SvgExportArtifact, SvgExportOptions, SvgExportPreviewController, SvgNodeRenderer } from '../export-svg/index.js'
|
|
23
|
+
|
|
24
|
+
export type ViewerProjection = 'auto' | 'standard' | 'approval' | 'compact'
|
|
25
|
+
export interface RuntimeTraceItem {
|
|
26
|
+
id: string
|
|
27
|
+
kind: 'activity' | 'milestone' | 'transition'
|
|
28
|
+
elementId?: string
|
|
29
|
+
visitId?: string | null
|
|
30
|
+
transitionId?: string
|
|
31
|
+
type?: RuntimeTransition['type']
|
|
32
|
+
name: string
|
|
33
|
+
nodeType?: NodeType
|
|
34
|
+
status: ActivityStatus | 'active' | 'resolved'
|
|
35
|
+
statusLabel: string
|
|
36
|
+
summary: string
|
|
37
|
+
time: string
|
|
38
|
+
startTime?: string
|
|
39
|
+
endTime?: string
|
|
40
|
+
outcome?: string
|
|
41
|
+
comment?: string
|
|
42
|
+
round?: number
|
|
43
|
+
participants?: RuntimeParticipant[]
|
|
44
|
+
records?: ActivityInstance[]
|
|
45
|
+
actions?: RuntimeApprovalActionPresentation[]
|
|
46
|
+
latestAction?: RuntimeApprovalActionPresentation | null
|
|
47
|
+
actionText?: string
|
|
48
|
+
actionSummary?: string
|
|
49
|
+
imageCount?: number
|
|
50
|
+
fileCount?: number
|
|
51
|
+
assetCount?: number
|
|
52
|
+
predicted: boolean
|
|
53
|
+
automated?: boolean
|
|
54
|
+
order: number
|
|
55
|
+
}
|
|
56
|
+
export interface RuntimeTraceLink {
|
|
57
|
+
id: string
|
|
58
|
+
sourceItemId: string
|
|
59
|
+
targetItemId: string
|
|
60
|
+
sourceElementId: string
|
|
61
|
+
targetElementId: string
|
|
62
|
+
sourceEdgeIds: string[]
|
|
63
|
+
label: string
|
|
64
|
+
predicted: boolean
|
|
65
|
+
}
|
|
66
|
+
export interface RuntimeTraceGroup {
|
|
67
|
+
id: string
|
|
68
|
+
kind: 'parallel'
|
|
69
|
+
gatewayId: string
|
|
70
|
+
label: string
|
|
71
|
+
itemIds: string[]
|
|
72
|
+
completed: number
|
|
73
|
+
total: number
|
|
74
|
+
status: ActivityStatus
|
|
75
|
+
order: number
|
|
76
|
+
}
|
|
77
|
+
export interface RuntimeTraceProjection {
|
|
78
|
+
graphModel: ProcessModel
|
|
79
|
+
graphRuntime: ProcessInstanceSnapshot
|
|
80
|
+
items: RuntimeTraceItem[]
|
|
81
|
+
links: RuntimeTraceLink[]
|
|
82
|
+
groups: RuntimeTraceGroup[]
|
|
83
|
+
warnings: string[]
|
|
84
|
+
mappings: {
|
|
85
|
+
nodes: Record<string, string[]>
|
|
86
|
+
edges: Record<string, string[]>
|
|
87
|
+
items: Record<string, { elementIds: string[]; activityIds: string[]; visitIds: string[] }>
|
|
88
|
+
edgeVisits: Record<string, string[]>
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export interface RuntimeTraceProjectionOptions {
|
|
92
|
+
includePredicted?: boolean
|
|
93
|
+
showStartMilestone?: boolean
|
|
94
|
+
showEndMilestone?: boolean
|
|
95
|
+
}
|
|
96
|
+
export function createRuntimeTraceProjection(context: {
|
|
97
|
+
model: ProcessModel
|
|
98
|
+
runtime?: ProcessInstanceSnapshot | null
|
|
99
|
+
presentation?: RuntimePresentation
|
|
100
|
+
options?: RuntimeTraceProjectionOptions
|
|
101
|
+
}): RuntimeTraceProjection
|
|
102
|
+
|
|
103
|
+
export interface ViewerTimelineContext {
|
|
104
|
+
model: ProcessModel
|
|
105
|
+
runtime: ProcessInstanceSnapshot | null
|
|
106
|
+
projection: Exclude<ViewerProjection, 'auto'>
|
|
107
|
+
traceProjection: RuntimeTraceProjection | null
|
|
108
|
+
viewer: BpmnViewer
|
|
109
|
+
}
|
|
110
|
+
export interface ViewerTimelineOptions {
|
|
111
|
+
title?: string | null | ((context: ViewerTimelineContext) => string | null)
|
|
112
|
+
description?: string | null | ((context: ViewerTimelineContext) => string | null)
|
|
113
|
+
}
|
|
114
|
+
export type RuntimeDetailsPlacement = 'popover' | 'center' | 'bottom'
|
|
115
|
+
export interface RuntimeDetailsLayoutOptions {
|
|
116
|
+
placement?: RuntimeDetailsPlacement
|
|
117
|
+
width?: number | string
|
|
118
|
+
maxHeight?: number | string
|
|
119
|
+
backdrop?: boolean
|
|
120
|
+
dragToDismiss?: boolean
|
|
121
|
+
}
|
|
122
|
+
export interface RuntimeDetailsOptions {
|
|
123
|
+
autoOpen?: boolean
|
|
124
|
+
desktop?: RuntimeDetailsLayoutOptions
|
|
125
|
+
mobile?: RuntimeDetailsLayoutOptions
|
|
126
|
+
}
|
|
127
|
+
export type RuntimeAssetPurpose = 'thumbnail' | 'preview' | 'download' | 'export'
|
|
128
|
+
export type RuntimeAssetResolver = (
|
|
129
|
+
asset: RuntimeAssetRef,
|
|
130
|
+
context: { purpose: RuntimeAssetPurpose; action: RuntimeApprovalActionPresentation; signal: AbortSignal },
|
|
131
|
+
) => string | null | Promise<string | null>
|
|
132
|
+
export type ResolveRuntimeAsset = (
|
|
133
|
+
asset: RuntimeAssetRef,
|
|
134
|
+
purpose: RuntimeAssetPurpose,
|
|
135
|
+
action: RuntimeApprovalActionPresentation,
|
|
136
|
+
) => Promise<string | null>
|
|
137
|
+
export type RuntimeDetailsRenderer = (context: {
|
|
138
|
+
container: HTMLElement
|
|
139
|
+
viewer: BpmnViewer
|
|
140
|
+
node: BpmnNode
|
|
141
|
+
presentation: NodeRuntimePresentation
|
|
142
|
+
anchor: Element | null
|
|
143
|
+
layout: Required<RuntimeDetailsLayoutOptions>
|
|
144
|
+
resolveAsset: ResolveRuntimeAsset
|
|
145
|
+
themeState: NovaThemeState
|
|
146
|
+
runtimeAppearance: RuntimeAppearanceResolver
|
|
147
|
+
close(): void
|
|
148
|
+
}) => void | (() => void)
|
|
149
|
+
export type RuntimeTransitionDetailsRenderer = (context: {
|
|
150
|
+
container: HTMLElement
|
|
151
|
+
viewer: BpmnViewer
|
|
152
|
+
transition: RuntimeTransitionPresentation
|
|
153
|
+
sourceNode: BpmnNode | null
|
|
154
|
+
targetNode: BpmnNode | null
|
|
155
|
+
anchor: Element | null
|
|
156
|
+
layout: Required<RuntimeDetailsLayoutOptions>
|
|
157
|
+
resolveAsset: ResolveRuntimeAsset
|
|
158
|
+
themeState: NovaThemeState
|
|
159
|
+
runtimeAppearance: RuntimeAppearanceResolver
|
|
160
|
+
close(): void
|
|
161
|
+
locateSource(): void
|
|
162
|
+
locateTarget(): void
|
|
163
|
+
}) => void | (() => void)
|
|
164
|
+
export type RuntimeTimelineRenderer = (context: {
|
|
165
|
+
container: HTMLElement
|
|
166
|
+
model: ProcessModel
|
|
167
|
+
runtime: ProcessInstanceSnapshot
|
|
168
|
+
presentation: RuntimePresentation
|
|
169
|
+
projection: RuntimeTraceProjection
|
|
170
|
+
title: string | null
|
|
171
|
+
description: string | null
|
|
172
|
+
iconRegistry: IconRegistry
|
|
173
|
+
themeState: NovaThemeState
|
|
174
|
+
appearance: RuntimeAppearanceResolver
|
|
175
|
+
resolveAsset: ResolveRuntimeAsset
|
|
176
|
+
onAssetPreview(asset: RuntimeAssetRef, action: RuntimeApprovalActionPresentation): void
|
|
177
|
+
onItemClick(item: RuntimeTraceItem, event: Event): void
|
|
178
|
+
onDetailsRequest(item: RuntimeTraceItem, anchor: Element, event: Event): void
|
|
179
|
+
onTransitionRequest(item: RuntimeTraceItem, anchor: Element, event: Event): void
|
|
180
|
+
}) => void | (() => void)
|
|
181
|
+
export interface RuntimeTraceClickEvent {
|
|
182
|
+
targetType: 'node' | 'edge' | 'visit' | 'transition'
|
|
183
|
+
elementId?: string
|
|
184
|
+
element?: BpmnNode | BpmnEdge | null
|
|
185
|
+
visitId?: string | null
|
|
186
|
+
traceItem?: RuntimeTraceItem | null
|
|
187
|
+
transition?: RuntimeTransitionPresentation | null
|
|
188
|
+
presentation?: NodeRuntimePresentation | null
|
|
189
|
+
activityInstances: ActivityInstance[]
|
|
190
|
+
actions: RuntimeApprovalActionPresentation[]
|
|
191
|
+
runtime: ProcessInstanceSnapshot | null
|
|
192
|
+
projection: Exclude<ViewerProjection, 'auto'>
|
|
193
|
+
originalEvent?: Event
|
|
194
|
+
viewer: BpmnViewer
|
|
195
|
+
}
|
|
196
|
+
export interface ViewerOptions {
|
|
197
|
+
container: HTMLElement
|
|
198
|
+
model: ProcessModel
|
|
199
|
+
runtime?: ProcessInstanceSnapshot | null
|
|
200
|
+
iconRegistry?: IconRegistry
|
|
201
|
+
nodeRenderers?: Record<string, Function>
|
|
202
|
+
nodeRenderer?: Function
|
|
203
|
+
projection?: ViewerProjection
|
|
204
|
+
responsive?: boolean
|
|
205
|
+
runtimeTraceOptions?: RuntimeTraceProjectionOptions
|
|
206
|
+
timeline?: ViewerTimelineOptions
|
|
207
|
+
runtimeDetails?: RuntimeDetailsOptions
|
|
208
|
+
onTraceClick?: (payload: RuntimeTraceClickEvent) => void
|
|
209
|
+
runtimePresenter?: (context: { model: ProcessModel; runtime: ProcessInstanceSnapshot | null; appearance: RuntimeAppearanceResolver }) => RuntimePresentation
|
|
210
|
+
runtimeTraceProjector?: (context: { model: ProcessModel; runtime: ProcessInstanceSnapshot; presentation: RuntimePresentation; options: RuntimeTraceProjectionOptions }) => RuntimeTraceProjection
|
|
211
|
+
runtimeAssetResolver?: RuntimeAssetResolver
|
|
212
|
+
theme?: NovaThemeInput
|
|
213
|
+
runtimeAppearance?: RuntimeAppearanceOptions
|
|
214
|
+
svgExport?: SvgExportOptions & { label?: string; nodeRenderers?: Record<string, SvgNodeRenderer>; runtimeTimelineRenderer?: RuntimeTimelineSvgRenderer }
|
|
215
|
+
onThemeChange?: (state: NovaThemeState) => void
|
|
216
|
+
runtimeTimelineRenderer?: RuntimeTimelineRenderer
|
|
217
|
+
onRuntimeTraceItemClick?: (payload: { item: RuntimeTraceItem; projection: RuntimeTraceProjection; viewer: BpmnViewer; event: Event }) => void
|
|
218
|
+
onProjectionChange?: (payload: { requested: ViewerProjection; active: Exclude<ViewerProjection, 'auto'> }) => void
|
|
219
|
+
runtimeDetailsRenderer?: RuntimeDetailsRenderer | null
|
|
220
|
+
onRuntimeDetailsOpen?: (payload: { node: BpmnNode; presentation: NodeRuntimePresentation; layout: Required<RuntimeDetailsLayoutOptions>; close(): void }) => void
|
|
221
|
+
runtimeTransitionDetailsRenderer?: RuntimeTransitionDetailsRenderer | null
|
|
222
|
+
onRuntimeTransitionDetailsOpen?: (payload: { transition: RuntimeTransitionPresentation; sourceNode: BpmnNode | null; targetNode: BpmnNode | null; layout: Required<RuntimeDetailsLayoutOptions>; close(): void }) => void
|
|
223
|
+
onElementClick?: (payload: { kind: 'node' | 'edge'; element: BpmnNode | BpmnEdge; runtime: ProcessInstanceSnapshot | null; presentation: NodeRuntimePresentation | null } | null) => void
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function renderDefaultRuntimeTimeline(context: Parameters<RuntimeTimelineRenderer>[0]): void | (() => void)
|
|
227
|
+
export function formatRuntimeAssetSize(value: number | null | undefined): string
|
|
228
|
+
export function renderRuntimeApprovalContent(context: {
|
|
229
|
+
container: HTMLElement
|
|
230
|
+
actions: RuntimeApprovalActionPresentation[]
|
|
231
|
+
resolveAsset?: ResolveRuntimeAsset
|
|
232
|
+
onAssetPreview?: (asset: RuntimeAssetRef, action: RuntimeApprovalActionPresentation) => void
|
|
233
|
+
}): void | (() => void)
|
|
234
|
+
export function renderDefaultRuntimeDetails(context: Parameters<RuntimeDetailsRenderer>[0]): void | (() => void)
|
|
235
|
+
export function renderDefaultRuntimeTransitionDetails(context: Parameters<RuntimeTransitionDetailsRenderer>[0]): void | (() => void)
|
|
236
|
+
export function projectModel(model: ProcessModel, projection?: ViewerProjection): ProcessModel
|
|
237
|
+
|
|
238
|
+
export class BpmnViewer {
|
|
239
|
+
constructor(options: ViewerOptions)
|
|
240
|
+
model: ProcessModel
|
|
241
|
+
runtime: ProcessInstanceSnapshot | null
|
|
242
|
+
projection: ViewerProjection
|
|
243
|
+
responsive: boolean
|
|
244
|
+
activeProjection: Exclude<ViewerProjection, 'auto'>
|
|
245
|
+
traceProjection: RuntimeTraceProjection | null
|
|
246
|
+
setModel(model: ProcessModel): void
|
|
247
|
+
setRuntime(runtime: ProcessInstanceSnapshot | null): void
|
|
248
|
+
setDisplayOptions(options: { timeline?: ViewerTimelineOptions; runtimeDetails?: RuntimeDetailsOptions; runtimeTraceOptions?: RuntimeTraceProjectionOptions; runtimeAssetResolver?: RuntimeAssetResolver | null }): void
|
|
249
|
+
setProjection(projection: ViewerProjection): void
|
|
250
|
+
setTheme(theme: NovaThemeInput): NovaThemeState
|
|
251
|
+
setThemeMode(mode: NovaThemeMode): NovaThemeState
|
|
252
|
+
getThemeState(): NovaThemeState
|
|
253
|
+
exportSvg(options?: SvgExportOptions): Promise<SvgExportArtifact>
|
|
254
|
+
openSvgExportPreview(options?: SvgExportOptions & { previewTitle?: string; onDownload?: (artifact: SvgExportArtifact) => void }): SvgExportPreviewController
|
|
255
|
+
setRuntimeAppearance(options: RuntimeAppearanceOptions | null): void
|
|
256
|
+
fitView(): void
|
|
257
|
+
zoomBy(delta: number): void
|
|
258
|
+
clearSelection(): void
|
|
259
|
+
openRuntimeDetails(node: BpmnNode, presentation?: NodeRuntimePresentation, anchor?: Element | null): boolean
|
|
260
|
+
openRuntimeTransitionDetails(transition: RuntimeTransitionPresentation | string, anchor?: Element | null): boolean
|
|
261
|
+
closeRuntimeAssetPreview(): void
|
|
262
|
+
closeRuntimeDetails(): void
|
|
263
|
+
refresh(): void
|
|
264
|
+
destroy(): void
|
|
265
|
+
}
|