@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.
Files changed (63) hide show
  1. package/README.md +245 -20
  2. package/dist/canvas.js +7 -4
  3. package/dist/context-menu.js +2 -2
  4. package/dist/controller.js +84 -6
  5. package/dist/index.d.ts +138 -38
  6. package/dist/index.js +12 -11
  7. package/dist/interactions.js +1 -1
  8. package/dist/modules/bpmn-model/index.d.ts +11 -0
  9. package/dist/modules/bpmn-model/index.js +703 -0
  10. package/dist/modules/core/containment.js +590 -0
  11. package/dist/modules/core/gateway.js +72 -0
  12. package/dist/modules/core/history.js +32 -0
  13. package/dist/modules/core/index.d.ts +268 -0
  14. package/dist/modules/core/index.js +7 -0
  15. package/dist/modules/core/layout.js +644 -0
  16. package/dist/modules/core/model.js +287 -0
  17. package/dist/modules/core/runtime-transition-route.js +360 -0
  18. package/dist/modules/core/scope.js +99 -0
  19. package/dist/modules/designer/index.d.ts +79 -0
  20. package/dist/modules/designer/index.js +607 -0
  21. package/dist/modules/engine-activiti/index.d.ts +19 -0
  22. package/dist/modules/engine-activiti/index.js +160 -0
  23. package/dist/modules/engine-flowable/index.d.ts +19 -0
  24. package/dist/modules/engine-flowable/index.js +160 -0
  25. package/dist/modules/export-svg/index.d.ts +112 -0
  26. package/dist/modules/export-svg/index.js +2 -0
  27. package/dist/modules/export-svg/preview.js +327 -0
  28. package/dist/modules/export-svg/render.js +718 -0
  29. package/dist/modules/icons/index.d.ts +24 -0
  30. package/dist/modules/icons/index.js +264 -0
  31. package/dist/modules/palette/index.d.ts +74 -0
  32. package/dist/modules/palette/index.js +99 -0
  33. package/dist/modules/palette/panel.js +99 -0
  34. package/dist/modules/properties/index.d.ts +20 -0
  35. package/dist/modules/properties/index.js +19 -0
  36. package/dist/modules/properties-activiti/index.d.ts +3 -0
  37. package/dist/modules/properties-activiti/index.js +97 -0
  38. package/dist/modules/properties-bpmn/index.d.ts +3 -0
  39. package/dist/modules/properties-bpmn/index.js +518 -0
  40. package/dist/modules/properties-core/index.d.ts +124 -0
  41. package/dist/modules/properties-core/index.js +312 -0
  42. package/dist/modules/properties-flowable/index.d.ts +3 -0
  43. package/dist/modules/properties-flowable/index.js +114 -0
  44. package/dist/modules/properties-renderer/index.d.ts +25 -0
  45. package/dist/modules/properties-renderer/index.js +491 -0
  46. package/dist/modules/renderer-svg/index.d.ts +118 -0
  47. package/dist/modules/renderer-svg/index.js +1460 -0
  48. package/dist/modules/runtime/index.d.ts +169 -0
  49. package/dist/modules/runtime/index.js +535 -0
  50. package/dist/modules/theme/index.d.ts +95 -0
  51. package/dist/modules/theme/index.js +368 -0
  52. package/dist/modules/viewer/index.d.ts +265 -0
  53. package/dist/modules/viewer/index.js +1011 -0
  54. package/dist/modules/viewer/runtime-content.js +123 -0
  55. package/dist/modules/viewer/runtime-details-motion.js +228 -0
  56. package/dist/modules/viewer/runtime-trace.js +574 -0
  57. package/dist/modules/viewer/timeline.js +276 -0
  58. package/dist/selection-layout.js +1 -1
  59. package/dist/shell.js +210 -26
  60. package/dist/styles.css +116 -7
  61. package/llms-full.txt +3082 -0
  62. package/llms.txt +225 -0
  63. package/package.json +39 -16
@@ -0,0 +1,124 @@
1
+ import type { BpmnEdge, BpmnNode, ElementSelection, EngineId, ProcessModel } from '../core/index.js'
2
+
3
+ export type PropertyEntryType =
4
+ | 'text' | 'textarea' | 'code' | 'expression' | 'number' | 'select' | 'switch' | 'range' | 'readonly' | 'action' | 'tags'
5
+ | 'table' | 'branchList' | 'flow-list' | 'node-list' | 'user-select' | 'form-select' | 'process-select' | 'resource-select' | 'node-select'
6
+ | (string & {})
7
+ export interface PropertiesStudio {
8
+ model: ProcessModel
9
+ selection: ElementSelection | null
10
+ propertiesProfile?: 'business' | 'developer'
11
+ getSelectedElement(): ProcessModel | BpmnNode | BpmnEdge | Array<BpmnNode | BpmnEdge> | null
12
+ updateProcess?(patch: Partial<ProcessModel>): unknown
13
+ updateNode?(id: string, patch: Partial<BpmnNode>): unknown
14
+ updateEdge?(id: string, patch: Partial<BpmnEdge>): unknown
15
+ commands?: Record<string, Function>
16
+ }
17
+ export interface PropertiesContext {
18
+ registry: PropertiesRegistry
19
+ studio: PropertiesStudio
20
+ designer: PropertiesStudio
21
+ model: ProcessModel
22
+ selection: ElementSelection | null
23
+ element: ProcessModel | BpmnNode | BpmnEdge
24
+ kind: 'none' | 'process' | 'node' | 'edge' | 'multi'
25
+ profile: 'business' | 'developer'
26
+ definition?: { bpmnType: string; label: string; kind: string; [key: string]: unknown } | null
27
+ engine: EngineId
28
+ is(value: string | ((context: PropertiesContext) => boolean)): boolean
29
+ get(path: string, fallback?: unknown): unknown
30
+ set(path: string, value: unknown): void
31
+ extensions: { get(name: string, fallback?: unknown): unknown; set(name: string, value: unknown): void }
32
+ data(name: string): unknown
33
+ }
34
+ export interface PropertyOption { value: unknown; label: string; disabled?: boolean }
35
+ export interface PropertyEntry {
36
+ id: string
37
+ type?: PropertyEntryType
38
+ label?: string
39
+ iconId?: string
40
+ fields?: Array<'name' | 'condition' | 'default'>
41
+ relation?: 'source' | 'target'
42
+ emptyText?: string
43
+ path?: string
44
+ component?: string
45
+ placeholder?: string
46
+ note?: string
47
+ required?: boolean
48
+ readonly?: boolean
49
+ level?: 'business' | 'developer'
50
+ visible?: (context: PropertiesContext) => boolean
51
+ appliesTo?: AppliesTo
52
+ getValue?: (context: PropertiesContext) => unknown
53
+ setValue?: (context: PropertiesContext, value: unknown) => void
54
+ validate?: (value: unknown, context: PropertiesContext) => string | null
55
+ parse?: (value: unknown, context: PropertiesContext) => unknown
56
+ options?: Array<PropertyOption | [unknown, string] | string> | ((context: PropertiesContext) => Array<PropertyOption | [unknown, string] | string>)
57
+ [key: string]: unknown
58
+ }
59
+ export interface PropertyPosition { prepend?: boolean; append?: boolean; before?: string; after?: string; replace?: string; remove?: string }
60
+ export interface PropertyGroup {
61
+ id: string
62
+ label: string
63
+ entries: PropertyEntry[]
64
+ description?: string
65
+ collapsed?: boolean
66
+ collapsible?: boolean
67
+ level?: 'business' | 'developer'
68
+ position?: PropertyPosition
69
+ appliesTo?: AppliesTo
70
+ [key: string]: unknown
71
+ }
72
+ export interface AppliesTo {
73
+ kind?: 'none' | 'process' | 'node' | 'edge' | 'multi' | Array<'none' | 'process' | 'node' | 'edge' | 'multi'>
74
+ nodeType?: string | string[]
75
+ bpmnType?: string | string[]
76
+ engine?: EngineId
77
+ engines?: EngineId[]
78
+ predicate?: (context: PropertiesContext) => boolean
79
+ [key: string]: unknown
80
+ }
81
+ export interface PropertiesProvider {
82
+ id: string
83
+ priority?: number
84
+ appliesTo?: AppliesTo | ((context: PropertiesContext) => boolean)
85
+ getGroups?: (context: PropertiesContext) => PropertyGroup[] | ((groups: PropertyGroup[], context: PropertiesContext) => PropertyGroup[])
86
+ groups?: PropertyGroup[] | ((context: PropertiesContext) => PropertyGroup[])
87
+ }
88
+ export type PropertyComponentRenderer = (context: {
89
+ container: HTMLElement
90
+ entry: PropertyEntry
91
+ context: PropertiesContext
92
+ value: unknown
93
+ commit(value: unknown): void
94
+ }) => void | (() => void)
95
+ export interface PropertiesRegistryOptions {
96
+ studio?: PropertiesStudio | null
97
+ designer?: PropertiesStudio | null
98
+ components?: Record<string, PropertyComponentRenderer>
99
+ dataProviders?: Record<string, unknown>
100
+ }
101
+
102
+ export function getPath(object: unknown, path: string, fallback?: unknown): unknown
103
+ export function setPath<T extends object>(object: T, path: string, value: unknown): T
104
+ export function normalizeOptions(options?: Array<PropertyOption | [unknown, string] | string>): PropertyOption[]
105
+ export function matchesAppliesTo(appliesTo: AppliesTo | ((context: PropertiesContext) => boolean) | undefined, context: PropertiesContext): boolean
106
+ export function propertyEntry(config: PropertyEntry): PropertyEntry
107
+ export function propertyGroup(config: PropertyGroup): PropertyGroup
108
+ export class PropertiesRegistry {
109
+ constructor(options?: PropertiesRegistryOptions)
110
+ studio: PropertiesStudio | null
111
+ designer: PropertiesStudio | null
112
+ setStudio(studio: PropertiesStudio): this
113
+ setDesigner(designer: PropertiesStudio): this
114
+ registerProvider(priority: number, provider: PropertiesProvider): () => void
115
+ registerProvider(provider: PropertiesProvider): () => void
116
+ registerComponent(name: string, renderer: PropertyComponentRenderer): () => void
117
+ registerDataProvider(name: string, provider: unknown): () => void
118
+ getDataProvider(name: string): unknown
119
+ createContext(selection?: ElementSelection | null, element?: BpmnNode | BpmnEdge | null): PropertiesContext
120
+ getGroups(selection?: ElementSelection | null, element?: BpmnNode | BpmnEdge | null): { groups: PropertyGroup[]; context: PropertiesContext }
121
+ getValue(entry: PropertyEntry, context: PropertiesContext): unknown
122
+ setValue(entry: PropertyEntry, value: unknown, context: PropertiesContext): unknown
123
+ }
124
+ export function createPropertiesRegistry(options?: PropertiesRegistryOptions): PropertiesRegistry
@@ -0,0 +1,312 @@
1
+ import { NODE_DEFINITIONS } from '../core/index.js';
2
+
3
+ function isBlank(value) {
4
+ if (value === undefined || value === null || value === '') return true;
5
+ if (Array.isArray(value)) return value.length === 0;
6
+ if (typeof value === 'object') return Object.keys(value).length === 0;
7
+ return false;
8
+ }
9
+
10
+ export function getPath(object, path, fallback) {
11
+ if (!path) return object ?? fallback;
12
+ const value = String(path).split('.').reduce((current, part) => current == null ? undefined : current[part], object);
13
+ return value === undefined ? fallback : value;
14
+ }
15
+
16
+ export function setPath(object, path, value) {
17
+ const parts = String(path).split('.');
18
+ const root = { ...(object || {}) };
19
+ let target = root;
20
+ let source = object || {};
21
+ parts.forEach((part, index) => {
22
+ if (index === parts.length - 1) {
23
+ target[part] = value;
24
+ return;
25
+ }
26
+ const sourceValue = source?.[part];
27
+ target[part] = Array.isArray(sourceValue) ? [...sourceValue] : { ...(sourceValue || {}) };
28
+ target = target[part];
29
+ source = sourceValue || {};
30
+ });
31
+ return root;
32
+ }
33
+
34
+ export function normalizeOptions(options = []) {
35
+ return (options || []).map((option) => {
36
+ if (Array.isArray(option)) return { value: option[0], label: option[1] };
37
+ if (typeof option === 'string') return { value: option, label: option };
38
+ return option;
39
+ });
40
+ }
41
+
42
+ function matchList(value, list) {
43
+ if (!list || !list.length) return true;
44
+ return list.includes(value);
45
+ }
46
+
47
+ export function matchesAppliesTo(appliesTo, context) {
48
+ if (!appliesTo) return true;
49
+ if (typeof appliesTo === 'function') return !!appliesTo(context);
50
+ const { selection, element, definition, model } = context;
51
+ if (appliesTo.kind && appliesTo.kind !== context.kind) return false;
52
+ if (appliesTo.kinds && !matchList(context.kind, appliesTo.kinds)) return false;
53
+ if (appliesTo.engine && appliesTo.engine !== model.engine) return false;
54
+ if (appliesTo.engines && !matchList(model.engine, appliesTo.engines)) return false;
55
+ if (appliesTo.nodeType && element?.type !== appliesTo.nodeType) return false;
56
+ if (appliesTo.nodeTypes && !matchList(element?.type, appliesTo.nodeTypes)) return false;
57
+ if (appliesTo.bpmnType && element?.bpmnType !== appliesTo.bpmnType) return false;
58
+ if (appliesTo.bpmnTypes && !matchList(element?.bpmnType, appliesTo.bpmnTypes)) return false;
59
+ if (appliesTo.elementKind && definition?.kind !== appliesTo.elementKind) return false;
60
+ if (appliesTo.elementKinds && !matchList(definition?.kind, appliesTo.elementKinds)) return false;
61
+ if (appliesTo.eventDefinition && definition?.eventDefinition !== appliesTo.eventDefinition) return false;
62
+ if (appliesTo.eventDefinitions && !matchList(definition?.eventDefinition, appliesTo.eventDefinitions)) return false;
63
+ if (appliesTo.selectionKind && selection?.kind !== appliesTo.selectionKind) return false;
64
+ return true;
65
+ }
66
+
67
+ function insertGroup(groups, group) {
68
+ if (!group || !group.id) return groups;
69
+ const position = group.position || {};
70
+ if (position.remove) return groups.filter((item) => item.id !== position.remove && item.id !== group.id);
71
+
72
+ let next = [...groups];
73
+ let index = next.length;
74
+ const existingIndex = next.findIndex((item) => item.id === group.id);
75
+ if (existingIndex >= 0) {
76
+ index = existingIndex;
77
+ next.splice(existingIndex, 1);
78
+ }
79
+
80
+ if (position.replace) {
81
+ const found = next.findIndex((item) => item.id === position.replace);
82
+ if (found >= 0) {
83
+ index = found;
84
+ next.splice(found, 1);
85
+ }
86
+ } else if (position.prepend) index = 0;
87
+ else if (position.before) {
88
+ const found = next.findIndex((item) => item.id === position.before);
89
+ if (found >= 0) index = found;
90
+ } else if (position.after) {
91
+ const found = next.findIndex((item) => item.id === position.after);
92
+ if (found >= 0) index = found + 1;
93
+ } else if (position.append) index = next.length;
94
+
95
+ index = Math.max(0, Math.min(index, next.length));
96
+ next.splice(index, 0, group);
97
+ return next;
98
+ }
99
+
100
+ export function propertyEntry(config) {
101
+ return {
102
+ type: 'text',
103
+ ...config,
104
+ };
105
+ }
106
+
107
+ export function propertyGroup(config) {
108
+ return {
109
+ entries: [],
110
+ collapsible: true,
111
+ ...config,
112
+ };
113
+ }
114
+
115
+ const DEVELOPER_GROUPS = new Set([
116
+ 'namespaces', 'extension-elements', 'extension-attributes', 'flowable-process-execution', 'activiti-process-execution',
117
+ 'engine-execution', 'execution-listeners', 'task-listeners', 'field-injection', 'edge-listeners',
118
+ ]);
119
+
120
+ function profileAllows(item, profile) {
121
+ const level = item.level || (DEVELOPER_GROUPS.has(item.id) ? 'developer' : 'business');
122
+ if (profile === 'developer') return true;
123
+ return level !== 'developer';
124
+ }
125
+
126
+ export class PropertiesRegistry {
127
+ constructor({ studio = null, designer = null, components = {}, dataProviders = {} } = {}) {
128
+ this.studio = studio || designer;
129
+ this.designer = this.studio;
130
+ this._providers = [];
131
+ this.components = new Map(Object.entries(components));
132
+ this.dataProviders = new Map(Object.entries(dataProviders));
133
+ }
134
+
135
+ setDesigner(designer) {
136
+ this.studio = designer;
137
+ this.designer = designer;
138
+ return this;
139
+ }
140
+
141
+ setStudio(studio) {
142
+ this.studio = studio;
143
+ this.designer = studio;
144
+ return this;
145
+ }
146
+
147
+ registerProvider(priority = 500, provider) {
148
+ if (typeof priority === 'object' && provider === undefined) {
149
+ provider = priority;
150
+ priority = provider.priority ?? 500;
151
+ }
152
+ const record = { priority, provider, order: this._providers.length };
153
+ this._providers.push(record);
154
+ this._providers.sort((a, b) => b.priority - a.priority || a.order - b.order);
155
+ return () => { this._providers = this._providers.filter((item) => item !== record); };
156
+ }
157
+
158
+ registerComponent(name, renderer) {
159
+ this.components.set(name, renderer);
160
+ return () => this.components.delete(name);
161
+ }
162
+
163
+ registerDataProvider(name, provider) {
164
+ this.dataProviders.set(name, provider);
165
+ return () => this.dataProviders.delete(name);
166
+ }
167
+
168
+ getDataProvider(name) {
169
+ return this.dataProviders.get(name) || null;
170
+ }
171
+
172
+ createContext(selection, element) {
173
+ const studio = this.studio;
174
+ if (selection === undefined) selection = studio?.selection ?? null;
175
+ if (element === undefined) element = selection ? studio?.getSelectedElement?.() : null;
176
+ const designer = studio;
177
+ const model = studio?.model || null;
178
+ let kind = selection?.kind || 'none';
179
+ if (kind !== 'process' && !element) kind = 'none';
180
+ const definition = kind === 'node' ? (NODE_DEFINITIONS[element.type] || NODE_DEFINITIONS.generic) : null;
181
+ const context = {
182
+ registry: this,
183
+ studio,
184
+ designer,
185
+ model,
186
+ selection,
187
+ element: kind === 'process' ? model : kind === 'none' ? null : element,
188
+ kind,
189
+ definition,
190
+ engine: model?.engine,
191
+ profile: studio?.propertiesProfile || 'business',
192
+ is(value) {
193
+ if (typeof value === 'function') return !!value(context);
194
+ if (value === kind) return true;
195
+ if (kind === 'node') return value === element?.type || value === element?.bpmnType || value === definition?.kind;
196
+ return false;
197
+ },
198
+ get(path, fallback) {
199
+ const root = kind === 'process' ? model : element;
200
+ return getPath(root, path, fallback);
201
+ },
202
+ set(path, value) {
203
+ if (!studio || !model || kind === 'none') return;
204
+ const parts = String(path).split('.');
205
+ if (kind === 'process') {
206
+ if (path === 'id') { studio.commands?.changeElementId('process', model.id, value); return; }
207
+ if (parts[0] === 'properties') {
208
+ const next = setPath(model.properties || {}, parts.slice(1).join('.'), value);
209
+ studio.commands?.updateProcess({ properties: next });
210
+ } else if (parts[0] === 'settings') {
211
+ const next = setPath(model.settings || {}, parts.slice(1).join('.'), value);
212
+ studio.commands?.updateProcess({ settings: next });
213
+ } else if (parts[0] === 'resources') {
214
+ const next = setPath(model.resources || {}, parts.slice(1).join('.'), value);
215
+ studio.commands?.updateProcess({ resources: next });
216
+ } else {
217
+ studio.commands?.updateProcess(setPath({}, path, value));
218
+ }
219
+ return;
220
+ }
221
+ if (kind === 'node') {
222
+ if (path === 'id') studio.commands?.changeElementId('node', element.id, value);
223
+ else if (parts[0] === 'properties') {
224
+ const next = setPath(element.properties || {}, parts.slice(1).join('.'), value);
225
+ studio.commands?.updateNode(element.id, { properties: next });
226
+ } else studio.commands?.updateNode(element.id, setPath({}, path, value));
227
+ return;
228
+ }
229
+ if (kind === 'edge') {
230
+ if (path === 'id') studio.commands?.changeElementId('edge', element.id, value);
231
+ else if (parts[0] === 'properties') {
232
+ const next = setPath(element.properties || {}, parts.slice(1).join('.'), value);
233
+ studio.commands?.updateEdge(element.id, { properties: next });
234
+ } else studio.commands?.updateEdge(element.id, setPath({}, path, value));
235
+ }
236
+ },
237
+ extensions: {
238
+ get(name, fallback = '') {
239
+ return getPath(kind === 'process' ? model?.properties?.extensionAttributes : element?.properties?.extensionAttributes, name, fallback);
240
+ },
241
+ set(name, value) {
242
+ const current = kind === 'process' ? (model?.properties?.extensionAttributes || {}) : (element?.properties?.extensionAttributes || {});
243
+ const next = { ...current, [name]: value };
244
+ context.set('properties.extensionAttributes', next);
245
+ },
246
+ },
247
+ data(name) { return context.registry.getDataProvider(name); },
248
+ };
249
+ return context;
250
+ }
251
+
252
+ getGroups(selection, element) {
253
+ const context = this.createContext(selection, element);
254
+ if (context.kind === 'none' || context.kind === 'multi') return { groups: [], context };
255
+ let groups = [];
256
+ for (const { provider } of this._providers) {
257
+ if (!provider || !matchesAppliesTo(provider.appliesTo, context)) continue;
258
+ let output;
259
+ if (typeof provider.getGroups === 'function') output = provider.getGroups(context);
260
+ else if (typeof provider.groups === 'function') output = provider.groups(context);
261
+ else output = provider.groups;
262
+ if (typeof output === 'function') {
263
+ const result = output(groups, context);
264
+ if (Array.isArray(result)) groups = result;
265
+ continue;
266
+ }
267
+ const providerGroups = Array.isArray(output) ? output : output ? [output] : [];
268
+ for (const group of providerGroups) {
269
+ if (!group || !matchesAppliesTo(group.appliesTo, context) || !profileAllows(group, context.profile)) continue;
270
+ const resolved = {
271
+ ...group,
272
+ entries: (group.entries || []).filter((entry) => entry && profileAllows(entry, context.profile) && matchesAppliesTo(entry.appliesTo, context) && (typeof entry.visible !== 'function' || entry.visible(context))),
273
+ };
274
+ if (!resolved.entries.length && !resolved.alwaysShow) continue;
275
+ groups = insertGroup(groups, resolved);
276
+ }
277
+ }
278
+ return { groups, context };
279
+ }
280
+
281
+ getValue(entry, context) {
282
+ if (typeof entry.getValue === 'function') return entry.getValue(context);
283
+ if (entry.path) return context.get(entry.path, entry.defaultValue);
284
+ return entry.value ?? entry.defaultValue;
285
+ }
286
+
287
+ setValue(entry, value, context) {
288
+ if (typeof entry.parse === 'function') value = entry.parse(value, context);
289
+ if (typeof entry.setValue === 'function') return entry.setValue(context, value);
290
+ if (entry.path) return context.set(entry.path, value);
291
+ }
292
+
293
+ validateEntry(entry, context) {
294
+ if (typeof entry.validate !== 'function') return null;
295
+ return entry.validate(this.getValue(entry, context), context);
296
+ }
297
+
298
+ configuredCount(group, context) {
299
+ return (group.entries || []).reduce((count, entry) => {
300
+ if (['readonly', 'action'].includes(entry.type)) return count;
301
+ const value = this.getValue(entry, context);
302
+ if (typeof entry.isConfigured === 'function') return count + (entry.isConfigured(value, context) ? 1 : 0);
303
+ if (Object.prototype.hasOwnProperty.call(entry, 'defaultValue') && value === entry.defaultValue) return count;
304
+ if (value === false || isBlank(value)) return count;
305
+ return count + 1;
306
+ }, 0);
307
+ }
308
+ }
309
+
310
+ export function createPropertiesRegistry(options) {
311
+ return new PropertiesRegistry(options);
312
+ }
@@ -0,0 +1,3 @@
1
+ import type { PropertiesProvider, PropertiesRegistry } from '../properties-core/index.js'
2
+ export const flowablePropertiesProviders: PropertiesProvider[]
3
+ export function registerFlowableProperties(registry: PropertiesRegistry): () => void
@@ -0,0 +1,114 @@
1
+ import { propertyEntry, propertyGroup } from '../properties-core/index.js';
2
+
3
+ const activityTypes = new Set(['task', 'userTask', 'manualTask', 'serviceTask', 'scriptTask', 'businessRuleTask', 'sendTask', 'receiveTask', 'callActivity', 'subProcess', 'eventSubProcess', 'transaction', 'adHocSubProcess']);
4
+ const implementationTypes = [['class', 'Java Class'], ['delegateExpression', 'Delegate Expression'], ['expression', 'Expression']];
5
+ const implementationColumns = [
6
+ { key: 'event', label: 'Event', type: 'select', options: [['start', 'start'], ['end', 'end'], ['take', 'take']] },
7
+ { key: 'implementationType', label: '类型', type: 'select', options: implementationTypes },
8
+ { key: 'implementation', label: '实现' },
9
+ ];
10
+ const taskListenerColumns = [
11
+ { key: 'event', label: 'Event', type: 'select', options: [['create', 'create'], ['assignment', 'assignment'], ['complete', 'complete'], ['delete', 'delete'], ['all', 'all']] },
12
+ { key: 'implementationType', label: '类型', type: 'select', options: implementationTypes },
13
+ { key: 'implementation', label: '实现' },
14
+ ];
15
+
16
+ const flowableProcessProvider = {
17
+ id: 'flowable-process', priority: 640, appliesTo: { kind: 'process', engine: 'flowable' },
18
+ getGroups() {
19
+ return [propertyGroup({
20
+ id: 'flowable-process-execution', label: 'Flowable 执行监听器', collapsed: true, position: { after: 'global-resources' },
21
+ entries: [propertyEntry({ id: 'process-listeners', type: 'table', label: 'Execution Listeners', path: 'properties.executionListeners', rowLabel: '监听器', defaultRow: () => ({ event: 'start', implementationType: 'class', implementation: '' }), columns: implementationColumns.filter((c) => c.key !== 'event' || true) })],
22
+ })];
23
+ },
24
+ };
25
+
26
+ const flowableUserTaskProvider = {
27
+ id: 'flowable-user-task', priority: 660, appliesTo: { kind: 'node', nodeType: 'userTask', engine: 'flowable' },
28
+ getGroups() {
29
+ return [
30
+ propertyGroup({
31
+ id: 'assignment', label: '人员分配 · Flowable', position: { after: 'basic' },
32
+ entries: [
33
+ propertyEntry({ id: 'assignee', type: 'user-select', label: '负责人 Assignee', path: 'properties.assignee', dataProvider: 'identity', allowExpression: true, placeholder: '${manager}' }),
34
+ propertyEntry({ id: 'owner', type: 'user-select', label: 'Owner', path: 'properties.owner', dataProvider: 'identity', allowExpression: true }),
35
+ propertyEntry({ id: 'candidate-users', type: 'tags', label: '候选用户', path: 'properties.candidateUsers', dataProvider: 'identity', placeholder: 'user1,user2' }),
36
+ propertyEntry({ id: 'candidate-groups', type: 'tags', label: '候选组', path: 'properties.candidateGroups', dataProvider: 'identity', placeholder: 'manager,finance' }),
37
+ ],
38
+ }),
39
+ propertyGroup({
40
+ id: 'task-config', label: '任务属性', position: { after: 'assignment' },
41
+ entries: [
42
+ propertyEntry({ id: 'form-key', type: 'form-select', label: 'Form Key', path: 'properties.formKey', dataProvider: 'forms', allowCustom: true }),
43
+ propertyEntry({ id: 'due-date', type: 'expression', label: 'Due Date', path: 'properties.dueDate', placeholder: '${now().plusDays(1)}' }),
44
+ propertyEntry({ id: 'priority', type: 'number', label: 'Priority', path: 'properties.priority', min: 0, max: 100 }),
45
+ propertyEntry({ id: 'category', label: 'Category', path: 'properties.category' }),
46
+ propertyEntry({ id: 'skip-expression', type: 'expression', label: 'Skip Expression', path: 'properties.skipExpression', placeholder: '${skipApproval}' }),
47
+ ],
48
+ }),
49
+ propertyGroup({
50
+ id: 'task-listeners', label: 'Task Listeners', collapsed: true, position: { after: 'multi-instance' },
51
+ entries: [propertyEntry({ id: 'task-listener-table', type: 'table', label: '任务监听器', path: 'properties.taskListeners', rowLabel: 'Task Listener', defaultRow: () => ({ event: 'create', implementationType: 'class', implementation: '' }), columns: taskListenerColumns })],
52
+ }),
53
+ ];
54
+ },
55
+ };
56
+
57
+ const flowableImplementationProvider = {
58
+ id: 'flowable-implementation', priority: 660,
59
+ appliesTo: (ctx) => ctx.kind === 'node' && ctx.model.engine === 'flowable' && ['serviceTask', 'sendTask', 'businessRuleTask'].includes(ctx.element.type),
60
+ getGroups(context) {
61
+ const label = context.element.type === 'businessRuleTask' ? '业务规则实现 · Flowable' : context.element.type === 'sendTask' ? '发送实现 · Flowable' : '服务实现 · Flowable';
62
+ return [
63
+ propertyGroup({ id: 'engine-implementation', label, position: { after: 'basic' }, entries: [
64
+ propertyEntry({ id: 'implementation-type', type: 'select', label: '实现方式', path: 'properties.implementationType', options: implementationTypes }),
65
+ propertyEntry({ id: 'implementation', type: 'expression', label: '实现值', path: 'properties.implementation', placeholder: 'com.example.MyDelegate / ${delegateBean}' }),
66
+ propertyEntry({ id: 'result-variable', label: 'Result Variable', path: 'properties.resultVariable', placeholder: 'result' }),
67
+ ] }),
68
+ propertyGroup({ id: 'field-injection', label: 'Field Injection', collapsed: true, position: { after: 'engine-implementation' }, entries: [
69
+ propertyEntry({ id: 'fields', type: 'table', label: '字段注入', path: 'properties.fields', rowLabel: 'Field', defaultRow: () => ({ name: '', type: 'string', value: '' }), columns: [{ key: 'name', label: 'Name' }, { key: 'type', label: '类型', type: 'select', options: [['string', 'String'], ['expression', 'Expression']] }, { key: 'value', label: 'Value' }] }),
70
+ ] }),
71
+ ];
72
+ },
73
+ };
74
+
75
+ const flowableActivityExecutionProvider = {
76
+ id: 'flowable-activity-execution', priority: 610,
77
+ appliesTo: (ctx) => ctx.kind === 'node' && ctx.model.engine === 'flowable' && activityTypes.has(ctx.element.type),
78
+ getGroups() {
79
+ return [
80
+ propertyGroup({ id: 'engine-execution', label: '执行配置 · Flowable', collapsed: true, position: { after: 'multi-instance' }, entries: [
81
+ propertyEntry({ id: 'async', type: 'switch', label: 'Async', path: 'properties.async' }),
82
+ propertyEntry({ id: 'exclusive', type: 'switch', label: 'Exclusive Job', path: 'properties.exclusive' }),
83
+ propertyEntry({ id: 'async-leave', type: 'switch', label: 'Async Leave', path: 'properties.asyncLeave' }),
84
+ propertyEntry({ id: 'failed-retry', type: 'expression', label: 'Failed Job Retry Cycle', path: 'properties.failedJobRetryTimeCycle', placeholder: 'R3/PT5M' }),
85
+ ] }),
86
+ propertyGroup({ id: 'execution-listeners', label: 'Execution Listeners', collapsed: true, position: { after: 'engine-execution' }, entries: [
87
+ propertyEntry({ id: 'execution-listener-table', type: 'table', label: '执行监听器', path: 'properties.executionListeners', rowLabel: 'Execution Listener', defaultRow: () => ({ event: 'start', implementationType: 'class', implementation: '' }), columns: implementationColumns.filter((column) => column.key !== 'event' || true).map((column) => column.key === 'event' ? { ...column, options: [['start', 'start'], ['end', 'end']] } : column) }),
88
+ ] }),
89
+ ];
90
+ },
91
+ };
92
+
93
+ const flowableCallActivityProvider = {
94
+ id: 'flowable-call-activity', priority: 650, appliesTo: { kind: 'node', nodeType: 'callActivity', engine: 'flowable' },
95
+ getGroups() { return [propertyGroup({ id: 'flowable-call', label: 'Flowable 调用选项', collapsed: true, position: { after: 'call-activity' }, entries: [
96
+ propertyEntry({ id: 'same-deployment', type: 'switch', label: 'Same Deployment', path: 'properties.sameDeployment' }),
97
+ propertyEntry({ id: 'process-instance-name', type: 'expression', label: 'Process Instance Name', path: 'properties.processInstanceName' }),
98
+ ] })]; },
99
+ };
100
+
101
+ const flowableEdgeProvider = {
102
+ id: 'flowable-edge-listeners', priority: 620,
103
+ appliesTo: (ctx) => ctx.kind === 'edge' && ctx.model.engine === 'flowable' && (ctx.element.type || 'sequenceFlow') === 'sequenceFlow',
104
+ getGroups() { return [propertyGroup({ id: 'edge-listeners', label: 'Take Listener · Flowable', collapsed: true, position: { after: 'condition' }, entries: [
105
+ propertyEntry({ id: 'edge-execution-listeners', type: 'table', label: 'Execution Listeners', path: 'properties.executionListeners', rowLabel: 'Take Listener', defaultRow: () => ({ event: 'take', implementationType: 'class', implementation: '' }), columns: implementationColumns.map((column) => column.key === 'event' ? { ...column, options: [['take', 'take']] } : column) }),
106
+ ] })]; },
107
+ };
108
+
109
+ export const flowablePropertiesProviders = [flowableProcessProvider, flowableUserTaskProvider, flowableImplementationProvider, flowableActivityExecutionProvider, flowableCallActivityProvider, flowableEdgeProvider];
110
+
111
+ export function registerFlowableProperties(registry) {
112
+ const disposers = flowablePropertiesProviders.map((provider) => registry.registerProvider(provider.priority ?? 500, provider));
113
+ return () => disposers.forEach((dispose) => dispose());
114
+ }
@@ -0,0 +1,25 @@
1
+ import type { BpmnEdge, BpmnNode, ElementSelection } from '../core/index.js'
2
+ import type { IconRegistry } from '../icons/index.js'
3
+ import type { PropertiesContext, PropertiesRegistry, PropertiesStudio, PropertyGroup } from '../properties-core/index.js'
4
+ import type { NovaThemeInput, NovaThemeMode, NovaThemeState, ThemeController } from '../theme/index.js'
5
+
6
+ export interface PropertiesPanelOptions {
7
+ container: HTMLElement
8
+ registry: PropertiesRegistry
9
+ studio?: PropertiesStudio | null
10
+ designer?: PropertiesStudio | null
11
+ canvas?: unknown
12
+ iconRegistry?: IconRegistry
13
+ emptyText?: string
14
+ themeController?: ThemeController | null
15
+ theme?: NovaThemeInput | null
16
+ onThemeChange?: ((state: NovaThemeState) => void) | null
17
+ }
18
+ export class PropertiesPanel {
19
+ constructor(options: PropertiesPanelOptions)
20
+ render(selection?: ElementSelection | null, element?: BpmnNode | BpmnEdge | null): { groups: PropertyGroup[]; context: PropertiesContext }
21
+ setTheme(theme: NovaThemeInput): NovaThemeState
22
+ setThemeMode(mode: NovaThemeMode): NovaThemeState
23
+ getThemeState(): NovaThemeState
24
+ destroy(): void
25
+ }