@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,535 @@
|
|
|
1
|
+
const TERMINAL_PROCESS_STATUSES = new Set(['completed', 'terminated']);
|
|
2
|
+
const ABNORMAL_TRANSITION_TYPES = new Set(['reject', 'return']);
|
|
3
|
+
const APPROVAL_ACTION_LABELS = {
|
|
4
|
+
submit: '提交', approve: '通过', reject: '驳回', return: '退回',
|
|
5
|
+
'add-sign': '加签', transfer: '转办', delegate: '委派', withdraw: '撤回', comment: '备注', skip: '跳过',
|
|
6
|
+
};
|
|
7
|
+
const OUTCOME_ACTION_TYPES = {
|
|
8
|
+
submitted: 'submit', approved: 'approve', rejected: 'reject', returned: 'return', skipped: 'skip',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function normalizeParticipant(record = {}) {
|
|
12
|
+
if (record.participant?.name) return { ...record.participant };
|
|
13
|
+
if (record.assignee) return { id: record.assigneeId || undefined, name: String(record.assignee) };
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function recordOrder(record, index) {
|
|
18
|
+
const stamp = record.startTime || record.endTime;
|
|
19
|
+
const parsed = stamp ? Date.parse(stamp.replace(' ', 'T')) : Number.NaN;
|
|
20
|
+
return Number.isFinite(parsed) ? parsed : index;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runtimeOrder(value, index) {
|
|
24
|
+
const stamp = value?.occurredAt || value?.time || value?.endTime || value?.startTime;
|
|
25
|
+
const parsed = stamp ? Date.parse(String(stamp).replace(' ', 'T')) : Number.NaN;
|
|
26
|
+
return Number.isFinite(parsed) ? parsed : index;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeRuntimeAsset(asset) {
|
|
30
|
+
if (!asset?.id) return null;
|
|
31
|
+
return {
|
|
32
|
+
id: String(asset.id),
|
|
33
|
+
name: String(asset.name || asset.id),
|
|
34
|
+
mediaType: String(asset.mediaType || 'application/octet-stream'),
|
|
35
|
+
...(Number.isFinite(Number(asset.size)) ? { size: Number(asset.size) } : {}),
|
|
36
|
+
...(Number.isFinite(Number(asset.width)) ? { width: Number(asset.width) } : {}),
|
|
37
|
+
...(Number.isFinite(Number(asset.height)) ? { height: Number(asset.height) } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeApprovalContent(content, fallbackText = '') {
|
|
42
|
+
const source = typeof content === 'string' ? { plainText: content } : (content || {});
|
|
43
|
+
const assets = Array.isArray(source.assets) ? source.assets.map(normalizeRuntimeAsset).filter(Boolean) : [];
|
|
44
|
+
const blocks = Array.isArray(source.blocks)
|
|
45
|
+
? source.blocks.filter((block) => block && typeof block === 'object').map((block) => {
|
|
46
|
+
if (block.type === 'paragraph') return { type: 'paragraph', text: String(block.text || '') };
|
|
47
|
+
if (block.type === 'image') return { type: 'image', assetId: String(block.assetId || ''), ...(block.alt ? { alt: String(block.alt) } : {}) };
|
|
48
|
+
if (block.type === 'file') return { type: 'file', assetId: String(block.assetId || '') };
|
|
49
|
+
return { type: String(block.type || 'unknown') };
|
|
50
|
+
})
|
|
51
|
+
: [];
|
|
52
|
+
const paragraphText = blocks.filter((block) => block.type === 'paragraph').map((block) => block.text.trim()).filter(Boolean).join('\n');
|
|
53
|
+
const plainText = String(source.plainText ?? fallbackText ?? paragraphText).trim() || paragraphText;
|
|
54
|
+
if (!blocks.length && plainText) blocks.push({ type: 'paragraph', text: plainText });
|
|
55
|
+
const referencedAssets = new Set(blocks.filter((block) => ['image', 'file'].includes(block.type)).map((block) => block.assetId));
|
|
56
|
+
assets.forEach((asset) => {
|
|
57
|
+
if (referencedAssets.has(asset.id)) return;
|
|
58
|
+
blocks.push({ type: asset.mediaType.startsWith('image/') ? 'image' : 'file', assetId: asset.id });
|
|
59
|
+
});
|
|
60
|
+
return { plainText, blocks, assets };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeApprovalAction(action, index) {
|
|
64
|
+
const type = String(action?.type || 'comment');
|
|
65
|
+
const actor = action?.actor?.name ? { ...action.actor, name: String(action.actor.name) } : null;
|
|
66
|
+
const targets = Array.isArray(action?.targets)
|
|
67
|
+
? action.targets.filter((target) => target?.name).map((target) => ({ ...target, name: String(target.name) }))
|
|
68
|
+
: [];
|
|
69
|
+
return {
|
|
70
|
+
id: String(action?.id || `runtime-action-${index + 1}`),
|
|
71
|
+
type,
|
|
72
|
+
...(action?.label ? { label: String(action.label) } : {}),
|
|
73
|
+
elementId: String(action?.elementId || ''),
|
|
74
|
+
visitId: String(action?.visitId || action?.activityId || action?.id || `runtime-action-${index + 1}`),
|
|
75
|
+
...(action?.activityId ? { activityId: String(action.activityId) } : {}),
|
|
76
|
+
...(actor ? { actor } : {}),
|
|
77
|
+
occurredAt: String(action?.occurredAt || action?.time || ''),
|
|
78
|
+
...(targets.length ? { targets } : {}),
|
|
79
|
+
...(action?.targetElementId ? { targetElementId: String(action.targetElementId) } : {}),
|
|
80
|
+
content: normalizeApprovalContent(action?.content),
|
|
81
|
+
...(action?.metadata && typeof action.metadata === 'object' ? { metadata: { ...action.metadata } } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function legacyActionForRecord(record, index) {
|
|
86
|
+
const comment = String(record.comment || '').trim();
|
|
87
|
+
const type = OUTCOME_ACTION_TYPES[record.outcome] || (record.outcome && record.outcome !== 'pending' ? String(record.outcome) : 'comment');
|
|
88
|
+
if (!comment && (!record.outcome || record.outcome === 'pending')) return null;
|
|
89
|
+
return normalizeApprovalAction({
|
|
90
|
+
id: `legacy-activity-action:${record.id || index + 1}`,
|
|
91
|
+
type,
|
|
92
|
+
activityId: record.id || undefined,
|
|
93
|
+
elementId: record.elementId,
|
|
94
|
+
visitId: record.visitId || record.multiInstanceId || record.id || `record-${index + 1}`,
|
|
95
|
+
actor: normalizeParticipant(record) || undefined,
|
|
96
|
+
occurredAt: record.endTime || record.startTime || '',
|
|
97
|
+
content: comment ? { plainText: comment } : undefined,
|
|
98
|
+
}, index);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function actionText(action) {
|
|
102
|
+
return String(action?.content?.plainText || '').trim();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function approvalActionLabel(action) {
|
|
106
|
+
return action?.label || APPROVAL_ACTION_LABELS[action?.type] || action?.type || '审批操作';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function summarizeApprovalActions(actions = []) {
|
|
110
|
+
const latestAction = [...actions].reverse().find((action) => action.plainText || action.assetCount) || actions.at(-1) || null;
|
|
111
|
+
const assetSummary = latestAction
|
|
112
|
+
? [latestAction.imageCount ? `${latestAction.imageCount} 张图片` : '', latestAction.fileCount ? `${latestAction.fileCount} 个附件` : ''].filter(Boolean).join(' · ')
|
|
113
|
+
: '';
|
|
114
|
+
return {
|
|
115
|
+
latestAction,
|
|
116
|
+
actionText: latestAction?.plainText || '',
|
|
117
|
+
actionSummary: latestAction ? [latestAction.label, latestAction.plainText, assetSummary].filter(Boolean).join(' · ') : '',
|
|
118
|
+
imageCount: actions.reduce((sum, action) => sum + action.imageCount, 0),
|
|
119
|
+
fileCount: actions.reduce((sum, action) => sum + action.fileCount, 0),
|
|
120
|
+
assetCount: actions.reduce((sum, action) => sum + action.assetCount, 0),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function groupVisits(records = []) {
|
|
125
|
+
const groups = new Map();
|
|
126
|
+
records.forEach((record, index) => {
|
|
127
|
+
const key = record.visitId
|
|
128
|
+
? `visit:${record.visitId}`
|
|
129
|
+
: record.multiInstanceId
|
|
130
|
+
? `multi:${record.multiInstanceId}`
|
|
131
|
+
: `record:${record.id || index}`;
|
|
132
|
+
if (!groups.has(key)) groups.set(key, { id: record.visitId || record.multiInstanceId || record.id || String(index), records: [], order: recordOrder(record, index) });
|
|
133
|
+
const visit = groups.get(key);
|
|
134
|
+
visit.records.push(record);
|
|
135
|
+
visit.order = Math.max(visit.order, recordOrder(record, index));
|
|
136
|
+
});
|
|
137
|
+
return [...groups.values()].sort((a, b) => a.order - b.order).map((visit, index) => ({ ...visit, round: index + 1 }));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function resolveVisitStatus(records = []) {
|
|
141
|
+
if (!records.length) return 'idle';
|
|
142
|
+
if (records.some((item) => item.status === 'failed')) return 'failed';
|
|
143
|
+
if (records.some((item) => item.status === 'active')) return 'active';
|
|
144
|
+
if (records.every((item) => item.status === 'skipped')) return 'skipped';
|
|
145
|
+
if (records.some((item) => item.status === 'cancelled') && !records.some((item) => item.status === 'completed')) return 'cancelled';
|
|
146
|
+
return 'completed';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function uniqueParticipants(records = []) {
|
|
150
|
+
const seen = new Set();
|
|
151
|
+
const result = [];
|
|
152
|
+
for (const record of records) {
|
|
153
|
+
const participant = normalizeParticipant(record);
|
|
154
|
+
if (!participant?.name) continue;
|
|
155
|
+
const key = participant.id || participant.name;
|
|
156
|
+
if (seen.has(key)) continue;
|
|
157
|
+
seen.add(key);
|
|
158
|
+
result.push(participant);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function participantSummary(participants) {
|
|
164
|
+
if (!participants.length) return '';
|
|
165
|
+
const visible = participants.slice(0, 2).map((item) => item.name).join('、');
|
|
166
|
+
return participants.length > 2 ? `${visible} +${participants.length - 2}` : visible;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function candidateSummary(node) {
|
|
170
|
+
const properties = node?.properties || {};
|
|
171
|
+
const candidate = properties.candidateGroups || properties.candidateUsers;
|
|
172
|
+
if (!candidate) return '';
|
|
173
|
+
const value = Array.isArray(candidate) ? candidate.join('、') : String(candidate);
|
|
174
|
+
return value ? `待领取 · ${value}` : '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function baseStatusLabel(status) {
|
|
178
|
+
return {
|
|
179
|
+
completed: '已完成', active: '处理中', failed: '失败', cancelled: '已取消', skipped: '未经过', idle: '未到达', rejected: '已驳回',
|
|
180
|
+
}[status] || status;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function transitionOrder(transition, index) {
|
|
184
|
+
const stamp = transition.occurredAt || transition.time;
|
|
185
|
+
const parsed = stamp ? Date.parse(stamp.replace(' ', 'T')) : Number.NaN;
|
|
186
|
+
return Number.isFinite(parsed) ? parsed : index;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function edgeVisitOrder(visit, index) {
|
|
190
|
+
const stamp = visit.occurredAt || visit.time;
|
|
191
|
+
const parsed = stamp ? Date.parse(stamp.replace(' ', 'T')) : Number.NaN;
|
|
192
|
+
return Number.isFinite(parsed) ? parsed : index;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function inferInvalidatedPath(model, runtime, transition) {
|
|
196
|
+
const source = model?.nodes?.find((node) => node.id === transition.sourceElementId);
|
|
197
|
+
const target = model?.nodes?.find((node) => node.id === transition.targetElementId);
|
|
198
|
+
if (!source || !target) return { activityIds: [], edgeIds: [], issues: ['驳回来源或目标节点不存在,无法推断失效路径。'] };
|
|
199
|
+
const rootScope = model.id;
|
|
200
|
+
if ((source.scopeId || rootScope) !== (target.scopeId || rootScope)) return { activityIds: [], edgeIds: [], issues: ['驳回跨越流程作用域,未自动重置路径。'] };
|
|
201
|
+
const visited = new Set(runtime.visitedEdges);
|
|
202
|
+
const edges = (model.edges || []).filter((edge) => visited.has(edge.id) && (edge.scopeId || rootScope) === (source.scopeId || rootScope));
|
|
203
|
+
const outgoing = new Map();
|
|
204
|
+
for (const edge of edges) {
|
|
205
|
+
if (!outgoing.has(edge.source)) outgoing.set(edge.source, []);
|
|
206
|
+
outgoing.get(edge.source).push(edge);
|
|
207
|
+
}
|
|
208
|
+
const paths = [];
|
|
209
|
+
const walk = (nodeId, nodeIds, edgeIds) => {
|
|
210
|
+
if (paths.length > 1 || nodeIds.length > (model.nodes?.length || 0) + 1) return;
|
|
211
|
+
if (nodeId === source.id) {
|
|
212
|
+
paths.push({ nodeIds, edgeIds });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for (const edge of outgoing.get(nodeId) || []) {
|
|
216
|
+
if (nodeIds.includes(edge.target)) continue;
|
|
217
|
+
walk(edge.target, [...nodeIds, edge.target], [...edgeIds, edge.id]);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
walk(target.id, [target.id], []);
|
|
221
|
+
if (paths.length !== 1) {
|
|
222
|
+
return {
|
|
223
|
+
activityIds: [], edgeIds: [],
|
|
224
|
+
issues: [paths.length ? '驳回路径存在多个候选分支,未自动重置路径。' : '未找到驳回目标到来源的已访问路径。'],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return { activityIds: paths[0].nodeIds.slice(1), edgeIds: paths[0].edgeIds, issues: [] };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function normalizeRuntime(snapshot = {}) {
|
|
231
|
+
snapshot = snapshot || {};
|
|
232
|
+
const activities = Array.isArray(snapshot.activities)
|
|
233
|
+
? snapshot.activities.map((record) => ({ ...record, participant: normalizeParticipant(record) || undefined }))
|
|
234
|
+
: [];
|
|
235
|
+
const explicitActions = Array.isArray(snapshot.actions)
|
|
236
|
+
? snapshot.actions.map(normalizeApprovalAction)
|
|
237
|
+
: [];
|
|
238
|
+
const actions = [...explicitActions];
|
|
239
|
+
const explicitActivityIds = new Set(explicitActions.map((action) => action.activityId).filter(Boolean));
|
|
240
|
+
activities.forEach((record, index) => {
|
|
241
|
+
if (record.id && explicitActivityIds.has(record.id)) return;
|
|
242
|
+
const action = legacyActionForRecord(record, index);
|
|
243
|
+
if (action) actions.push(action);
|
|
244
|
+
});
|
|
245
|
+
const transitions = Array.isArray(snapshot.transitions)
|
|
246
|
+
? snapshot.transitions.map((transition, index) => ({ id: transition.id || `runtime-transition-${index + 1}`, type: transition.type || 'forward', ...transition }))
|
|
247
|
+
: [];
|
|
248
|
+
transitions.forEach((transition, index) => {
|
|
249
|
+
if (transition.actionId && actions.some((action) => action.id === transition.actionId)) return;
|
|
250
|
+
if (!['reject', 'return', 'skip'].includes(transition.type)) return;
|
|
251
|
+
const comment = String(transition.comment || '').trim();
|
|
252
|
+
const sourceRecord = activities.find((record) => record.id === transition.sourceActivityId);
|
|
253
|
+
const reusable = actions.find((action) => action.activityId && action.activityId === transition.sourceActivityId
|
|
254
|
+
&& action.type === transition.type && (!comment || actionText(action) === comment));
|
|
255
|
+
if (reusable) {
|
|
256
|
+
transition.actionId = reusable.id;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const action = normalizeApprovalAction({
|
|
260
|
+
id: `legacy-transition-action:${transition.id}`,
|
|
261
|
+
type: transition.type,
|
|
262
|
+
activityId: transition.sourceActivityId,
|
|
263
|
+
elementId: transition.sourceElementId,
|
|
264
|
+
visitId: sourceRecord?.visitId || sourceRecord?.multiInstanceId || sourceRecord?.id || `transition:${transition.id}`,
|
|
265
|
+
actor: transition.operator ? { name: String(transition.operator) } : undefined,
|
|
266
|
+
occurredAt: transition.occurredAt || transition.time || '',
|
|
267
|
+
targetElementId: transition.targetElementId,
|
|
268
|
+
content: comment ? { plainText: comment } : undefined,
|
|
269
|
+
}, activities.length + actions.length + index);
|
|
270
|
+
actions.push(action);
|
|
271
|
+
transition.actionId = action.id;
|
|
272
|
+
});
|
|
273
|
+
const edgeVisits = Array.isArray(snapshot.edgeVisits)
|
|
274
|
+
? snapshot.edgeVisits.map((visit, index) => ({ id: visit.id || `runtime-edge-visit-${index + 1}`, status: visit.status || 'effective', ...visit }))
|
|
275
|
+
: [];
|
|
276
|
+
const orderedActions = actions
|
|
277
|
+
.map((action, index) => ({ action, order: runtimeOrder(action, index), index }))
|
|
278
|
+
.sort((a, b) => (a.order - b.order) || (a.index - b.index))
|
|
279
|
+
.map(({ action }) => action);
|
|
280
|
+
return {
|
|
281
|
+
...snapshot,
|
|
282
|
+
processInstanceId: snapshot.processInstanceId || '',
|
|
283
|
+
status: snapshot.status || 'running',
|
|
284
|
+
activities,
|
|
285
|
+
actions: orderedActions,
|
|
286
|
+
visitedEdges: Array.isArray(snapshot.visitedEdges) ? [...snapshot.visitedEdges] : [],
|
|
287
|
+
transitions,
|
|
288
|
+
edgeVisits,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function activityState(runtime, elementId) {
|
|
293
|
+
if (!runtime) return { status: 'idle', records: [], latestRecords: [], visits: [] };
|
|
294
|
+
const records = runtime.activities.filter((item) => item.elementId === elementId);
|
|
295
|
+
const visits = groupVisits(records);
|
|
296
|
+
const latestRecords = visits.at(-1)?.records || [];
|
|
297
|
+
return { status: resolveVisitStatus(latestRecords), records, latestRecords, visits };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function createRuntimePresentation({ model, runtime, appearance = null } = {}) {
|
|
301
|
+
const normalized = normalizeRuntime(runtime);
|
|
302
|
+
const nodes = new Map((model?.nodes || []).map((node) => [node.id, node]));
|
|
303
|
+
const activityStates = new Map((model?.nodes || []).map((node) => [node.id, activityState(normalized, node.id)]));
|
|
304
|
+
const nodePresentations = new Map();
|
|
305
|
+
const actionPresentations = normalized.actions.map((action, index) => {
|
|
306
|
+
const assets = action.content.assets || [];
|
|
307
|
+
const referenced = (action.content.blocks || []).filter((block) => ['image', 'file'].includes(block.type));
|
|
308
|
+
const imageIds = new Set(referenced.filter((block) => block.type === 'image').map((block) => block.assetId));
|
|
309
|
+
const fileIds = new Set(referenced.filter((block) => block.type === 'file').map((block) => block.assetId));
|
|
310
|
+
const images = assets.filter((asset) => imageIds.has(asset.id) || (!referenced.length && asset.mediaType.startsWith('image/')));
|
|
311
|
+
const files = assets.filter((asset) => fileIds.has(asset.id) || (!referenced.length && !asset.mediaType.startsWith('image/')));
|
|
312
|
+
const baseAction = {
|
|
313
|
+
...action,
|
|
314
|
+
explicitLabel: action.label || null,
|
|
315
|
+
label: approvalActionLabel(action),
|
|
316
|
+
plainText: action.content.plainText || '',
|
|
317
|
+
images,
|
|
318
|
+
files,
|
|
319
|
+
imageCount: images.length,
|
|
320
|
+
fileCount: files.length,
|
|
321
|
+
assetCount: images.length + files.length,
|
|
322
|
+
order: runtimeOrder(action, index),
|
|
323
|
+
};
|
|
324
|
+
const resolvedAppearance = appearance?.resolveAction?.(baseAction);
|
|
325
|
+
return resolvedAppearance
|
|
326
|
+
? { ...baseAction, label: resolvedAppearance.label, iconId: resolvedAppearance.iconId, tone: resolvedAppearance.tone }
|
|
327
|
+
: baseAction;
|
|
328
|
+
});
|
|
329
|
+
const actionMap = new Map(actionPresentations.map((action) => [action.id, action]));
|
|
330
|
+
const rawTransitions = normalized.transitions
|
|
331
|
+
.map((transition, index) => ({ ...transition, order: transitionOrder(transition, index) }))
|
|
332
|
+
.sort((a, b) => a.order - b.order);
|
|
333
|
+
const transitionPresentations = rawTransitions.map((transition) => {
|
|
334
|
+
const targetName = nodes.get(transition.targetElementId)?.name || transition.targetElementId || '';
|
|
335
|
+
const labelPrefix = transition.type === 'reject' ? '驳回至' : transition.type === 'return' ? '退回至' : transition.type === 'skip' ? '跳过' : '流转至';
|
|
336
|
+
const targetState = activityStates.get(transition.targetElementId);
|
|
337
|
+
const targetRecordsAfter = (targetState?.records || []).filter((record, index) => recordOrder(record, index) > transition.order);
|
|
338
|
+
const targetStatusAfter = resolveVisitStatus(groupVisits(targetRecordsAfter).at(-1)?.records || []);
|
|
339
|
+
const state = transition.state || (ABNORMAL_TRANSITION_TYPES.has(transition.type)
|
|
340
|
+
? (targetRecordsAfter.length && targetStatusAfter !== 'active' ? 'resolved' : 'active')
|
|
341
|
+
: 'resolved');
|
|
342
|
+
const hasExplicitInvalidation = Array.isArray(transition.invalidatedActivityIds) || Array.isArray(transition.invalidatedEdgeIds);
|
|
343
|
+
const invalidation = hasExplicitInvalidation
|
|
344
|
+
? { activityIds: [...(transition.invalidatedActivityIds || [])], edgeIds: [...(transition.invalidatedEdgeIds || [])], issues: [] }
|
|
345
|
+
: ABNORMAL_TRANSITION_TYPES.has(transition.type)
|
|
346
|
+
? inferInvalidatedPath(model, normalized, transition)
|
|
347
|
+
: { activityIds: [], edgeIds: [], issues: [] };
|
|
348
|
+
return {
|
|
349
|
+
...transition,
|
|
350
|
+
action: actionMap.get(transition.actionId) || null,
|
|
351
|
+
sourceName: nodes.get(transition.sourceElementId)?.name || transition.sourceElementId || '',
|
|
352
|
+
targetName,
|
|
353
|
+
label: targetName ? `${labelPrefix}:${targetName}` : labelPrefix,
|
|
354
|
+
state,
|
|
355
|
+
visible: ABNORMAL_TRANSITION_TYPES.has(transition.type) && state === 'active',
|
|
356
|
+
resolvedAt: transition.resolvedAt || (state === 'resolved' ? targetRecordsAfter.map((record) => record.endTime).filter(Boolean).at(-1) : undefined),
|
|
357
|
+
invalidatedActivityIds: invalidation.activityIds,
|
|
358
|
+
invalidatedEdgeIds: invalidation.edgeIds,
|
|
359
|
+
issues: invalidation.issues,
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
const latestAbnormal = [...transitionPresentations].reverse().find((item) => ABNORMAL_TRANSITION_TYPES.has(item.type));
|
|
363
|
+
transitionPresentations.forEach((transition) => { transition.latest = transition === latestAbnormal; });
|
|
364
|
+
|
|
365
|
+
const invalidatedNodes = new Map();
|
|
366
|
+
const invalidatedEdges = new Map();
|
|
367
|
+
const hasActivityAfter = (elementId, transition) => (activityStates.get(elementId)?.records || [])
|
|
368
|
+
.some((record, index) => recordOrder(record, index) > transition.order);
|
|
369
|
+
const hasEdgeVisitAfter = (edgeId, transition) => {
|
|
370
|
+
const edgeVisits = normalized.edgeVisits.filter((visit) => visit.edgeId === edgeId);
|
|
371
|
+
if (edgeVisits.length) return edgeVisits.some((visit, index) => visit.status !== 'superseded' && edgeVisitOrder(visit, index) > transition.order);
|
|
372
|
+
const edge = model?.edges?.find((item) => item.id === edgeId);
|
|
373
|
+
return edge ? hasActivityAfter(edge.target, transition) : false;
|
|
374
|
+
};
|
|
375
|
+
for (const transition of transitionPresentations.filter((item) => ABNORMAL_TRANSITION_TYPES.has(item.type))) {
|
|
376
|
+
for (const elementId of transition.invalidatedActivityIds) if (!hasActivityAfter(elementId, transition)) invalidatedNodes.set(elementId, transition);
|
|
377
|
+
for (const edgeId of transition.invalidatedEdgeIds) if (!hasEdgeVisitAfter(edgeId, transition)) invalidatedEdges.set(edgeId, transition);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for (const node of model?.nodes || []) {
|
|
381
|
+
const state = activityStates.get(node.id);
|
|
382
|
+
const actions = actionPresentations.filter((action) => action.elementId === node.id);
|
|
383
|
+
const visits = state.visits.map((visit) => {
|
|
384
|
+
const visitActions = actions.filter((action) => action.visitId === visit.id);
|
|
385
|
+
return { ...visit, actions: visitActions, ...summarizeApprovalActions(visitActions) };
|
|
386
|
+
});
|
|
387
|
+
const latestVisit = visits.at(-1) || null;
|
|
388
|
+
const latestRecords = latestVisit?.records || [];
|
|
389
|
+
const participants = uniqueParticipants(latestRecords);
|
|
390
|
+
const approvalMode = latestRecords.find((item) => item.approvalMode)?.approvalMode || null;
|
|
391
|
+
const multiInstanceMode = latestRecords.find((item) => item.multiInstanceMode)?.multiInstanceMode || null;
|
|
392
|
+
const declaredTotal = latestRecords.reduce((max, item) => Math.max(max, Number(item.totalInstances) || 0), 0);
|
|
393
|
+
const total = Math.max(declaredTotal, latestRecords.length, participants.length);
|
|
394
|
+
const completed = latestRecords.filter((item) => item.status === 'completed').length;
|
|
395
|
+
const required = latestRecords.reduce((max, item) => Math.max(max, Number(item.requiredInstances) || 0), 0) || (approvalMode === 'any' ? 1 : total);
|
|
396
|
+
const inboundReentry = transitionPresentations.some((item) => ABNORMAL_TRANSITION_TYPES.has(item.type) && item.targetElementId === node.id);
|
|
397
|
+
const latestSourceRejection = [...transitionPresentations].reverse().find((item) => item.type === 'reject' && item.sourceElementId === node.id);
|
|
398
|
+
const rejectionSource = latestRecords.some((item) => item.outcome === 'rejected')
|
|
399
|
+
|| (latestSourceRejection && (!latestVisit || latestSourceRejection.order >= latestVisit.order));
|
|
400
|
+
const skipped = !state.records.length && transitionPresentations.some((item) => item.type === 'skip' && item.targetElementId === node.id);
|
|
401
|
+
let status = skipped ? 'skipped' : state.status;
|
|
402
|
+
if (rejectionSource && status === 'completed') status = 'rejected';
|
|
403
|
+
const isReentry = inboundReentry && Boolean(latestVisit) && (latestVisit.round > 1 || status === 'active');
|
|
404
|
+
const superseded = invalidatedNodes.has(node.id);
|
|
405
|
+
const pathStatus = superseded || status === 'rejected' ? 'idle' : status;
|
|
406
|
+
|
|
407
|
+
let statusLabel = baseStatusLabel(status);
|
|
408
|
+
if (isReentry && status === 'active') statusLabel = '重新审批';
|
|
409
|
+
else if (status === 'active' || status === 'completed') {
|
|
410
|
+
if (approvalMode === 'all') statusLabel = `${multiInstanceMode === 'sequential' ? '顺序会签' : '会签'} ${completed}/${total || required}`;
|
|
411
|
+
else if (approvalMode === 'any') statusLabel = `或签 ${completed}/${total || required}`;
|
|
412
|
+
else if (participants.length > 1) statusLabel = `多人审批 ${completed}/${total}`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
let summary = participantSummary(participants);
|
|
416
|
+
const isApprovalTask = ['userTask', 'callActivity'].includes(node.type);
|
|
417
|
+
const isAutomatedTask = ['serviceTask', 'scriptTask', 'businessRuleTask', 'sendTask', 'receiveTask'].includes(node.type);
|
|
418
|
+
if (!summary && isApprovalTask) {
|
|
419
|
+
if (status === 'active') summary = candidateSummary(node) || '待分配审批人';
|
|
420
|
+
else if (status === 'skipped') summary = '分支未经过';
|
|
421
|
+
else if (!state.records.length) summary = TERMINAL_PROCESS_STATUSES.has(normalized.status) ? '流程未经过' : '尚未流转';
|
|
422
|
+
} else if (!summary && isAutomatedTask) {
|
|
423
|
+
summary = status === 'active' ? '执行中' : status === 'completed' ? '执行完成' : status === 'failed' ? '执行失败' : status === 'skipped' ? '分支未经过' : '等待执行';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const relatedTransitions = transitionPresentations.filter((item) => item.sourceElementId === node.id || item.targetElementId === node.id);
|
|
427
|
+
const actionSummary = summarizeApprovalActions(actions);
|
|
428
|
+
nodePresentations.set(node.id, {
|
|
429
|
+
elementId: node.id, status, pathStatus, superseded, statusLabel, summary, fullSummary: participants.map((item) => item.name).join('、') || summary,
|
|
430
|
+
participants, approvalMode, multiInstanceMode, completed, total, required, round: latestVisit?.round || 0,
|
|
431
|
+
records: state.records, latestRecords, visits, transitions: relatedTransitions, actions, ...actionSummary,
|
|
432
|
+
isReentry,
|
|
433
|
+
hasDetails: Boolean(state.records.length || actions.length || relatedTransitions.length),
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
runtime: normalized,
|
|
439
|
+
getNode(elementId) {
|
|
440
|
+
return nodePresentations.get(elementId) || {
|
|
441
|
+
elementId, status: 'idle', pathStatus: 'idle', superseded: false, statusLabel: '未到达', summary: '', fullSummary: '', participants: [], approvalMode: null,
|
|
442
|
+
multiInstanceMode: null, completed: 0, total: 0, required: 0, round: 0, records: [], latestRecords: [], visits: [], transitions: [], actions: [], latestAction: null,
|
|
443
|
+
actionText: '', actionSummary: '', imageCount: 0, fileCount: 0, assetCount: 0, isReentry: false, hasDetails: false,
|
|
444
|
+
};
|
|
445
|
+
},
|
|
446
|
+
getEdge(edgeId) {
|
|
447
|
+
const edgeVisits = normalized.edgeVisits.filter((visit) => visit.edgeId === edgeId);
|
|
448
|
+
const latestVisit = edgeVisits
|
|
449
|
+
.map((visit, index) => ({ visit, order: edgeVisitOrder(visit, index) }))
|
|
450
|
+
.sort((a, b) => a.order - b.order)
|
|
451
|
+
.at(-1)?.visit;
|
|
452
|
+
const visited = normalized.visitedEdges.includes(edgeId) || edgeVisits.length > 0;
|
|
453
|
+
const superseded = invalidatedEdges.has(edgeId) || latestVisit?.status === 'superseded';
|
|
454
|
+
const status = !visited || superseded ? 'idle' : 'completed';
|
|
455
|
+
return { edgeId, status, visited, historicallyVisited: visited, superseded };
|
|
456
|
+
},
|
|
457
|
+
getAction(actionId) { return actionMap.get(actionId) || null; },
|
|
458
|
+
getActions(elementId = null) { return elementId ? actionPresentations.filter((action) => action.elementId === elementId) : [...actionPresentations]; },
|
|
459
|
+
getTransition(transitionId) { return transitionPresentations.find((item) => item.id === transitionId) || null; },
|
|
460
|
+
getTransitions() { return transitionPresentations; },
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function demoRuntime() {
|
|
465
|
+
return normalizeRuntime({
|
|
466
|
+
processInstanceId: 'PI-DEMO-20260824-001',
|
|
467
|
+
status: 'running',
|
|
468
|
+
visitedEdges: ['Flow_Start_Submit', 'Flow_Submit_Manager', 'Flow_Manager_Gateway', 'Flow_To_Director'],
|
|
469
|
+
edgeVisits: [
|
|
470
|
+
{ id: 'edge-start-submit-1', edgeId: 'Flow_Start_Submit', occurredAt: '2026-08-24 09:00' },
|
|
471
|
+
{ id: 'edge-submit-manager-1', edgeId: 'Flow_Submit_Manager', occurredAt: '2026-08-24 09:08' },
|
|
472
|
+
{ id: 'edge-manager-gateway-1', edgeId: 'Flow_Manager_Gateway', occurredAt: '2026-08-24 10:12' },
|
|
473
|
+
{ id: 'edge-gateway-director-1', edgeId: 'Flow_To_Director', occurredAt: '2026-08-24 10:12' },
|
|
474
|
+
],
|
|
475
|
+
activities: [
|
|
476
|
+
{ id: 'act-start-1', elementId: 'StartEvent_1', visitId: 'visit-start-1', status: 'completed', startTime: '2026-08-24 09:00', endTime: '2026-08-24 09:00' },
|
|
477
|
+
{ id: 'act-submit-1', elementId: 'UserTask_Submit', visitId: 'visit-submit-1', status: 'completed', assignee: '张三', startTime: '2026-08-24 09:00', endTime: '2026-08-24 09:08', outcome: 'submitted', comment: '采购申请已提交。' },
|
|
478
|
+
{ id: 'act-manager-1', elementId: 'UserTask_Manager', visitId: 'visit-manager-1', status: 'completed', assignee: '李经理', startTime: '2026-08-24 09:08', endTime: '2026-08-24 10:12', outcome: 'approved', comment: '资料完整,同意提交总经理审批。' },
|
|
479
|
+
{ id: 'act-gateway-1', elementId: 'Gateway_Amount', visitId: 'visit-gateway-1', status: 'completed', startTime: '2026-08-24 10:12', endTime: '2026-08-24 10:12' },
|
|
480
|
+
{ id: 'act-director-1', elementId: 'UserTask_Director', visitId: 'visit-director-1', status: 'completed', assignee: '王总', startTime: '2026-08-24 10:12', endTime: '2026-08-24 10:30', outcome: 'rejected', comment: '采购金额说明不完整,请补充后重新提交。' },
|
|
481
|
+
{ id: 'act-manager-2', elementId: 'UserTask_Manager', visitId: 'visit-manager-2', status: 'active', assignee: '李经理', startTime: '2026-08-24 10:31', outcome: 'pending' },
|
|
482
|
+
],
|
|
483
|
+
actions: [
|
|
484
|
+
{
|
|
485
|
+
id: 'action-submit-1', type: 'submit', activityId: 'act-submit-1', elementId: 'UserTask_Submit', visitId: 'visit-submit-1',
|
|
486
|
+
actor: { id: 'user-zhang', name: '张三' }, occurredAt: '2026-08-24 09:08',
|
|
487
|
+
content: { plainText: '采购申请已提交。' },
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
id: 'action-manager-add-sign-1', type: 'add-sign', activityId: 'act-manager-1', elementId: 'UserTask_Manager', visitId: 'visit-manager-1',
|
|
491
|
+
actor: { id: 'user-manager-li', name: '李经理' }, targets: [{ id: 'user-supervisor-wang', name: '王主管' }], occurredAt: '2026-08-24 09:36',
|
|
492
|
+
content: { plainText: '采购金额较大,请协同核验报价材料。' },
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
id: 'action-manager-approve-1', type: 'approve', activityId: 'act-manager-1', elementId: 'UserTask_Manager', visitId: 'visit-manager-1',
|
|
496
|
+
actor: { id: 'user-manager-li', name: '李经理' }, occurredAt: '2026-08-24 10:12',
|
|
497
|
+
content: {
|
|
498
|
+
plainText: '资料完整,同意提交总经理审批。',
|
|
499
|
+
blocks: [
|
|
500
|
+
{ type: 'paragraph', text: '资料完整,同意提交总经理审批。现场报价单和采购核验清单见附件。' },
|
|
501
|
+
{ type: 'image', assetId: 'asset-quotation-image', alt: '现场报价单预览' },
|
|
502
|
+
{ type: 'file', assetId: 'asset-purchase-checklist' },
|
|
503
|
+
],
|
|
504
|
+
assets: [
|
|
505
|
+
{ id: 'asset-quotation-image', name: '现场报价单.svg', mediaType: 'image/svg+xml', size: 2861, width: 960, height: 600 },
|
|
506
|
+
{ id: 'asset-purchase-checklist', name: '采购核验清单.txt', mediaType: 'text/plain', size: 248 },
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
id: 'action-director-reject-1', type: 'reject', activityId: 'act-director-1', elementId: 'UserTask_Director', visitId: 'visit-director-1',
|
|
512
|
+
actor: { id: 'user-director-wang', name: '王总' }, occurredAt: '2026-08-24 10:30', targetElementId: 'UserTask_Manager',
|
|
513
|
+
content: { plainText: '采购金额说明不完整,请补充后重新提交。' },
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
id: 'action-manager-transfer-2', type: 'transfer', activityId: 'act-manager-2', elementId: 'UserTask_Manager', visitId: 'visit-manager-2',
|
|
517
|
+
actor: { id: 'user-manager-li', name: '李经理' }, targets: [{ id: 'user-supervisor-wang', name: '王主管' }], occurredAt: '2026-08-24 10:34',
|
|
518
|
+
content: { plainText: '材料补充由王主管继续跟进。' },
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
transitions: [{
|
|
522
|
+
id: 'transition-reject-director-manager',
|
|
523
|
+
type: 'reject',
|
|
524
|
+
sourceActivityId: 'act-director-1',
|
|
525
|
+
sourceElementId: 'UserTask_Director',
|
|
526
|
+
targetElementId: 'UserTask_Manager',
|
|
527
|
+
operator: '王总',
|
|
528
|
+
occurredAt: '2026-08-24 10:30',
|
|
529
|
+
actionId: 'action-director-reject-1',
|
|
530
|
+
comment: '采购金额说明不完整,请补充后重新提交。',
|
|
531
|
+
invalidatedActivityIds: ['Gateway_Amount', 'UserTask_Director'],
|
|
532
|
+
invalidatedEdgeIds: ['Flow_Manager_Gateway', 'Flow_To_Director'],
|
|
533
|
+
}],
|
|
534
|
+
});
|
|
535
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type NovaThemeMode = 'light' | 'dark' | 'auto'
|
|
2
|
+
export type NovaResolvedTheme = 'light' | 'dark'
|
|
3
|
+
|
|
4
|
+
export interface NovaThemeState {
|
|
5
|
+
mode: NovaThemeMode
|
|
6
|
+
resolvedTheme: NovaResolvedTheme
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface NovaThemeSnapshot {
|
|
10
|
+
resolvedTheme: NovaResolvedTheme
|
|
11
|
+
colors: Readonly<Record<NovaSemanticColorToken, string>>
|
|
12
|
+
tones: Readonly<Record<string, Readonly<Required<NovaThemeTone>>>>
|
|
13
|
+
fontFamily: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type NovaSemanticColorToken =
|
|
17
|
+
| 'canvas' | 'surface' | 'surfaceRaised' | 'surfaceSubtle' | 'surfaceMuted'
|
|
18
|
+
| 'text' | 'textSecondary' | 'textMuted' | 'textInverse'
|
|
19
|
+
| 'border' | 'borderStrong' | 'divider'
|
|
20
|
+
| 'primary' | 'primaryHover' | 'primarySoft' | 'focusRing'
|
|
21
|
+
| 'edge' | 'gridDot' | 'backdrop'
|
|
22
|
+
|
|
23
|
+
export interface NovaThemeTone {
|
|
24
|
+
foreground?: string
|
|
25
|
+
background?: string
|
|
26
|
+
border?: string
|
|
27
|
+
strong?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface NovaThemePalette {
|
|
31
|
+
colors?: Partial<Record<NovaSemanticColorToken, string>>
|
|
32
|
+
shadows?: Partial<Record<'sm' | 'md' | 'lg', string>>
|
|
33
|
+
tones?: Record<string, NovaThemeTone>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface NovaThemeOptions {
|
|
37
|
+
mode?: NovaThemeMode
|
|
38
|
+
light?: NovaThemePalette | null
|
|
39
|
+
dark?: NovaThemePalette | null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type NovaThemeInput = NovaThemeMode | NovaThemeOptions
|
|
43
|
+
|
|
44
|
+
export interface RuntimeActionAppearance {
|
|
45
|
+
label?: string
|
|
46
|
+
iconId?: string
|
|
47
|
+
tone?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RuntimeAppearanceOptions {
|
|
51
|
+
statuses?: Record<string, string>
|
|
52
|
+
actions?: Record<string, RuntimeActionAppearance>
|
|
53
|
+
transitions?: Record<string, { tone?: string }>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface RuntimeAppearanceActionInput {
|
|
57
|
+
type?: string
|
|
58
|
+
explicitLabel?: string
|
|
59
|
+
label?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RuntimeAppearanceTransitionInput {
|
|
63
|
+
type?: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RuntimeAppearanceResolver {
|
|
67
|
+
resolveStatus(status: string): string
|
|
68
|
+
resolveAction(action?: RuntimeAppearanceActionInput): Required<RuntimeActionAppearance>
|
|
69
|
+
resolveTransition(transition?: RuntimeAppearanceTransitionInput): { tone: string }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class ThemeController {
|
|
73
|
+
constructor(options: {
|
|
74
|
+
root: HTMLElement
|
|
75
|
+
theme?: NovaThemeInput | null
|
|
76
|
+
onChange?: (state: NovaThemeState) => void
|
|
77
|
+
})
|
|
78
|
+
setTheme(theme: NovaThemeInput | null): NovaThemeState
|
|
79
|
+
setMode(mode: NovaThemeMode): NovaThemeState
|
|
80
|
+
getState(): NovaThemeState
|
|
81
|
+
getSnapshot(theme?: 'current' | NovaResolvedTheme): NovaThemeSnapshot
|
|
82
|
+
subscribe(listener: (state: NovaThemeState) => void): () => void
|
|
83
|
+
destroy(): void
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createThemeController(options: ConstructorParameters<typeof ThemeController>[0]): ThemeController
|
|
87
|
+
export function createRuntimeAppearance(options?: RuntimeAppearanceOptions): RuntimeAppearanceResolver
|
|
88
|
+
export function applyRuntimeTone(element: HTMLElement | SVGElement, tone?: string | null): string
|
|
89
|
+
|
|
90
|
+
export const NOVA_THEME_MODES: readonly NovaThemeMode[]
|
|
91
|
+
export const DEFAULT_RUNTIME_APPEARANCE: Readonly<{
|
|
92
|
+
statuses: Readonly<Record<string, string>>
|
|
93
|
+
actions: Readonly<Record<string, Readonly<Required<RuntimeActionAppearance>>>>
|
|
94
|
+
transitions: Readonly<Record<string, Readonly<{ tone: string }>>>
|
|
95
|
+
}>
|