@pbinitiative/zenbpm-js-properties-panel 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,936 @@
1
+ 'use strict';
2
+
3
+ var propertiesPanel = require('@bpmn-io/properties-panel');
4
+ var preact = require('@bpmn-io/properties-panel/preact');
5
+ var bpmnJsPropertiesPanel = require('bpmn-js-properties-panel');
6
+
7
+ function ZenFormProps(element) {
8
+ if (element.type !== 'bpmn:UserTask') {
9
+ return [];
10
+ }
11
+ return [
12
+ {
13
+ id: 'zenFormDesignButton',
14
+ component: ZenFormDesignButtonEntry,
15
+ isEdited: () => false,
16
+ }
17
+ ];
18
+ }
19
+ function getZenFormValue(element) {
20
+ const bo = element.businessObject;
21
+ const extensionElements = bo.extensionElements;
22
+ if (!extensionElements)
23
+ return '';
24
+ const ioMapping = extensionElements.values?.find((e) => e.$type === 'zenbpm:IoMapping');
25
+ if (!ioMapping)
26
+ return '';
27
+ const input = (ioMapping.inputParameters || []).find((p) => p.target === 'ZEN_FORM');
28
+ if (!input?.source)
29
+ return '';
30
+ // Parse FEEL string literal: ="..." → raw JSON
31
+ const src = input.source;
32
+ if (src.startsWith('="') && src.endsWith('"')) {
33
+ return src.slice(2, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
34
+ }
35
+ return src;
36
+ }
37
+ function ZenFormDesignButtonEntry(props) {
38
+ const { element } = props;
39
+ const translate = bpmnJsPropertiesPanel.useService('translate');
40
+ const handleClick = () => {
41
+ const currentValue = getZenFormValue(element);
42
+ document.dispatchEvent(new CustomEvent('bpmn-open-form-designer', {
43
+ detail: { elementId: element.id, value: currentValue },
44
+ }));
45
+ };
46
+ return preact.createElement('div', { class: 'bio-properties-panel-entry', style: 'padding: 0 10px 6px' }, preact.createElement('button', {
47
+ type: 'button',
48
+ onClick: handleClick,
49
+ style: 'width: 100%; padding: 6px 12px; cursor: pointer; ' +
50
+ 'background: #4d90fe; color: white; border: none; border-radius: 3px; ' +
51
+ 'font-size: 13px; font-weight: 500;',
52
+ }, translate('Design Form')));
53
+ }
54
+
55
+ /**
56
+ * Return the first extension element of `type` from the given business object,
57
+ * or undefined if none exists.
58
+ */
59
+ function getExtensionElement(bo, type) {
60
+ const ext = bo.extensionElements;
61
+ if (!ext)
62
+ return undefined;
63
+ return (ext.values || []).find((e) => e.$instanceOf(type));
64
+ }
65
+ /**
66
+ * Update properties on an existing extension element, or create a new one
67
+ * inside bpmn:ExtensionElements if it does not yet exist.
68
+ *
69
+ * Uses `properties-panel.multi-command-executor` so all mutations land as
70
+ * a single undo-able step.
71
+ */
72
+ function updateExtensionElementProps(element, bo, type, props, bpmnFactory, commandStack) {
73
+ const commands = [];
74
+ let extensionElements = bo.extensionElements;
75
+ // (1) create bpmn:ExtensionElements container if missing
76
+ if (!extensionElements) {
77
+ extensionElements = bpmnFactory.create('bpmn:ExtensionElements', { values: [] });
78
+ extensionElements.$parent = bo;
79
+ commands.push({
80
+ cmd: 'element.updateModdleProperties',
81
+ context: { element, moddleElement: bo, properties: { extensionElements } },
82
+ });
83
+ }
84
+ const existing = (extensionElements.values || []).find((e) => e.$instanceOf(type));
85
+ if (existing) {
86
+ // (2a) update properties on the existing element
87
+ commands.push({
88
+ cmd: 'element.updateModdleProperties',
89
+ context: { element, moddleElement: existing, properties: props },
90
+ });
91
+ }
92
+ else {
93
+ // (2b) create and attach a new extension element
94
+ const created = bpmnFactory.create(type, props);
95
+ created.$parent = extensionElements;
96
+ commands.push({
97
+ cmd: 'element.updateModdleProperties',
98
+ context: {
99
+ element,
100
+ moddleElement: extensionElements,
101
+ properties: { values: [...(extensionElements.values || []), created] },
102
+ },
103
+ });
104
+ }
105
+ commandStack.execute('properties-panel.multi-command-executor', commands);
106
+ }
107
+ /**
108
+ * Atomically swap extension elements: remove all instances of `removeType` and
109
+ * ensure exactly one instance of `createType` exists. Both changes land as a
110
+ * single undoable step via `properties-panel.multi-command-executor`.
111
+ *
112
+ * Used when toggling mutually-exclusive extension elements (e.g. switching a
113
+ * BusinessRuleTask between a CalledDecision and a TaskDefinition).
114
+ */
115
+ function switchExtensionElement(element, bo, removeType, createType, bpmnFactory, commandStack) {
116
+ const commands = [];
117
+ let extensionElements = bo.extensionElements;
118
+ // (1) create bpmn:ExtensionElements container if missing
119
+ if (!extensionElements) {
120
+ extensionElements = bpmnFactory.create('bpmn:ExtensionElements', { values: [] });
121
+ extensionElements.$parent = bo;
122
+ commands.push({
123
+ cmd: 'element.updateModdleProperties',
124
+ context: { element, moddleElement: bo, properties: { extensionElements } },
125
+ });
126
+ }
127
+ const currentValues = extensionElements.values || [];
128
+ const hasRemoveType = currentValues.some((e) => e.$instanceOf(removeType));
129
+ const hasCreateType = currentValues.some((e) => e.$instanceOf(createType));
130
+ // Already in the desired state — nothing to do
131
+ if (!hasRemoveType && hasCreateType)
132
+ return;
133
+ let newValues = currentValues.filter((e) => !e.$instanceOf(removeType));
134
+ if (!hasCreateType) {
135
+ const created = bpmnFactory.create(createType, {});
136
+ created.$parent = extensionElements;
137
+ newValues = [...newValues, created];
138
+ }
139
+ commands.push({
140
+ cmd: 'element.updateModdleProperties',
141
+ context: { element, moddleElement: extensionElements, properties: { values: newValues } },
142
+ });
143
+ commandStack.execute('properties-panel.multi-command-executor', commands);
144
+ }
145
+
146
+ // bpmn:ServiceTask, bpmn:BusinessRuleTask, bpmn:ScriptTask, bpmn:SendTask all
147
+ // use zenbpm:TaskDefinition to declare the job worker type & retry count.
148
+ const SERVICE_TASK_TYPES = new Set([
149
+ 'bpmn:ServiceTask',
150
+ 'bpmn:BusinessRuleTask',
151
+ 'bpmn:ScriptTask',
152
+ 'bpmn:SendTask',
153
+ ]);
154
+ function isServiceTaskLike(element) {
155
+ return SERVICE_TASK_TYPES.has(element.type);
156
+ }
157
+ // ─── entry components ────────────────────────────────────────────────────────
158
+ function TypeEntry(props) {
159
+ const { element } = props;
160
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
161
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
162
+ const translate = bpmnJsPropertiesPanel.useService('translate');
163
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
164
+ const bo = element.businessObject;
165
+ const getValue = () => getExtensionElement(bo, 'zenbpm:TaskDefinition')?.type ?? '';
166
+ const setValue = (value) => updateExtensionElementProps(element, bo, 'zenbpm:TaskDefinition', { type: value }, bpmnFactory, commandStack);
167
+ return propertiesPanel.TextFieldEntry({ element, id: 'zenbpm-taskDef-type', label: translate('Type'), getValue, setValue, debounce });
168
+ }
169
+ function RetriesEntry(props) {
170
+ const { element } = props;
171
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
172
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
173
+ const translate = bpmnJsPropertiesPanel.useService('translate');
174
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
175
+ const bo = element.businessObject;
176
+ const getValue = () => getExtensionElement(bo, 'zenbpm:TaskDefinition')?.retries ?? '';
177
+ const setValue = (value) => updateExtensionElementProps(element, bo, 'zenbpm:TaskDefinition', { retries: value }, bpmnFactory, commandStack);
178
+ return propertiesPanel.TextFieldEntry({ element, id: 'zenbpm-taskDef-retries', label: translate('Retries'), getValue, setValue, debounce });
179
+ }
180
+ // ─── exported entry list ─────────────────────────────────────────────────────
181
+ function TaskDefinitionProps(element) {
182
+ if (!isServiceTaskLike(element))
183
+ return [];
184
+ return [
185
+ { id: 'zenbpm-taskDef-type', component: TypeEntry, isEdited: propertiesPanel.isTextFieldEntryEdited },
186
+ { id: 'zenbpm-taskDef-retries', component: RetriesEntry, isEdited: propertiesPanel.isTextFieldEntryEdited },
187
+ ];
188
+ }
189
+
190
+ /**
191
+ * Normalise a raw stored value for display inside a `FeelEntry` with
192
+ * `feel: 'required'`.
193
+ *
194
+ * `FeelEntry` expects values to carry the `=` prefix that marks them as FEEL
195
+ * expressions (e.g. `=myVariable`, `=[1,2,3]`). Older data saved without
196
+ * the prefix is transparently upgraded on read so the editor shows it
197
+ * correctly, and the next save will persist the `=`.
198
+ *
199
+ * @example
200
+ * // In a FeelEntry getValue:
201
+ * const getValue = () => getFeelValue(param.source);
202
+ */
203
+ function getFeelValue(stored) {
204
+ if (!stored)
205
+ return '';
206
+ return stored.startsWith('=') ? stored : '=' + stored;
207
+ }
208
+ /**
209
+ * Read the FEEL body from a `bpmn:FormalExpression` element.
210
+ * Returns an empty string when the expression does not exist yet.
211
+ */
212
+ function getFormalExpressionValue(expression) {
213
+ return expression?.body ?? '';
214
+ }
215
+ /**
216
+ * Create, update, or remove a `bpmn:FormalExpression` child property.
217
+ *
218
+ * - When `value` is empty the property is cleared (`undefined`).
219
+ * - When the expression already exists its `body` is updated in-place.
220
+ * - Otherwise a new `bpmn:FormalExpression` is created and attached.
221
+ *
222
+ * @param element The diagram element (needed by the command stack).
223
+ * @param moddleElement The parent moddle object that owns the expression.
224
+ * @param prop Property name on `moddleElement` (e.g. `'conditionExpression'`).
225
+ * @param value New FEEL body value coming from `FeelEntry`.
226
+ * @param bpmnFactory Injected bpmn factory.
227
+ * @param commandStack Injected command stack.
228
+ */
229
+ function setFormalExpression(element, moddleElement, prop, value, bpmnFactory, commandStack) {
230
+ if (!value) {
231
+ commandStack.execute('element.updateModdleProperties', {
232
+ element,
233
+ moddleElement,
234
+ properties: { [prop]: undefined },
235
+ });
236
+ }
237
+ else if (moddleElement[prop]) {
238
+ commandStack.execute('element.updateModdleProperties', {
239
+ element,
240
+ moddleElement: moddleElement[prop],
241
+ properties: { body: value },
242
+ });
243
+ }
244
+ else {
245
+ const expr = bpmnFactory.create('bpmn:FormalExpression', { body: value });
246
+ commandStack.execute('element.updateModdleProperties', {
247
+ element,
248
+ moddleElement,
249
+ properties: { [prop]: expr },
250
+ });
251
+ }
252
+ }
253
+
254
+ // ─── helpers ─────────────────────────────────────────────────────────────────
255
+ function makeFeelEntry(id, labelKey, extensionType, prop) {
256
+ return function Entry(props) {
257
+ const { element } = props;
258
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
259
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
260
+ const translate = bpmnJsPropertiesPanel.useService('translate');
261
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
262
+ const bo = element.businessObject;
263
+ const getValue = () => getFeelValue(getExtensionElement(bo, extensionType)?.[prop]);
264
+ const setValue = (value) => updateExtensionElementProps(element, bo, extensionType, { [prop]: value }, bpmnFactory, commandStack);
265
+ return propertiesPanel.FeelEntry({ element, id, label: translate(labelKey), feel: 'required', getValue, setValue, debounce });
266
+ };
267
+ }
268
+ // ─── entry components ────────────────────────────────────────────────────────
269
+ const AssigneeEntry = makeFeelEntry('zenbpm-assign-assignee', 'Assignee', 'zenbpm:AssignmentDefinition', 'assignee');
270
+ const CandidateGroupsEntry = makeFeelEntry('zenbpm-assign-candidateGroups', 'Candidate groups', 'zenbpm:AssignmentDefinition', 'candidateGroups');
271
+ const CandidateUsersEntry = makeFeelEntry('zenbpm-assign-candidateUsers', 'Candidate users', 'zenbpm:AssignmentDefinition', 'candidateUsers');
272
+ const DueDateEntry = makeFeelEntry('zenbpm-assign-dueDate', 'Due date', 'zenbpm:TaskSchedule', 'dueDate');
273
+ const FollowUpDateEntry = makeFeelEntry('zenbpm-assign-followUpDate', 'Follow-up date', 'zenbpm:TaskSchedule', 'followUpDate');
274
+ // ─── exported entry list ─────────────────────────────────────────────────────
275
+ function AssignmentDefinitionProps(element) {
276
+ if (element.type !== 'bpmn:UserTask')
277
+ return [];
278
+ return [
279
+ { id: 'zenbpm-assign-assignee', component: AssigneeEntry, isEdited: propertiesPanel.isFeelEntryEdited },
280
+ { id: 'zenbpm-assign-candidateGroups', component: CandidateGroupsEntry, isEdited: propertiesPanel.isFeelEntryEdited },
281
+ { id: 'zenbpm-assign-candidateUsers', component: CandidateUsersEntry, isEdited: propertiesPanel.isFeelEntryEdited },
282
+ { id: 'zenbpm-assign-dueDate', component: DueDateEntry, isEdited: propertiesPanel.isFeelEntryEdited },
283
+ { id: 'zenbpm-assign-followUpDate', component: FollowUpDateEntry, isEdited: propertiesPanel.isFeelEntryEdited },
284
+ ];
285
+ }
286
+
287
+ // ─── constants ───────────────────────────────────────────────────────────────
288
+ const BINDING_OPTIONS = [
289
+ { value: 'latest', label: 'Latest' },
290
+ { value: 'deployment', label: 'Deployment' },
291
+ { value: 'versionTag', label: 'Version tag' },
292
+ ];
293
+ // ─── entry component factories ───────────────────────────────────────────────
294
+ // Call these once at module level in the consumer file to get a stable function
295
+ // reference. Never call inside getGroups / a Props function — a new reference
296
+ // each render causes Preact to unmount and remount the entry (lost focus, etc.).
297
+ function makeBindingTypeEntry(idPrefix, extensionType) {
298
+ return function BindingTypeEntry(props) {
299
+ const { element } = props;
300
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
301
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
302
+ const translate = bpmnJsPropertiesPanel.useService('translate');
303
+ const bo = element.businessObject;
304
+ const getValue = () => getExtensionElement(bo, extensionType)?.bindingType ?? 'latest';
305
+ const setValue = (value) => updateExtensionElementProps(element, bo, extensionType, { bindingType: value }, bpmnFactory, commandStack);
306
+ const getOptions = () => BINDING_OPTIONS.map(({ value, label }) => ({ value, label: translate(label) }));
307
+ return propertiesPanel.SelectEntry({ element, id: `${idPrefix}-bindingType`, label: translate('Binding'), getValue, setValue, getOptions });
308
+ };
309
+ }
310
+ function makeVersionTagEntry(idPrefix, extensionType) {
311
+ return function VersionTagEntry(props) {
312
+ const { element } = props;
313
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
314
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
315
+ const translate = bpmnJsPropertiesPanel.useService('translate');
316
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
317
+ const bo = element.businessObject;
318
+ const getValue = () => getExtensionElement(bo, extensionType)?.versionTag ?? '';
319
+ const setValue = (value) => updateExtensionElementProps(element, bo, extensionType, { versionTag: value }, bpmnFactory, commandStack);
320
+ return propertiesPanel.TextFieldEntry({ element, id: `${idPrefix}-versionTag`, label: translate('Version tag'), getValue, setValue, debounce });
321
+ };
322
+ }
323
+ // ─── conditional entry list helper ───────────────────────────────────────────
324
+ // Pass the pre-created (module-level) component instances so references are stable.
325
+ function bindingEntries(idPrefix, bindingTypeComponent, versionTagComponent, element, extensionType) {
326
+ const currentBinding = getExtensionElement(element.businessObject, extensionType)?.bindingType ?? 'latest';
327
+ const entries = [
328
+ { id: `${idPrefix}-bindingType`, component: bindingTypeComponent, isEdited: propertiesPanel.isSelectEntryEdited },
329
+ ];
330
+ if (currentBinding === 'versionTag') {
331
+ entries.push({ id: `${idPrefix}-versionTag`, component: versionTagComponent, isEdited: propertiesPanel.isTextFieldEntryEdited });
332
+ }
333
+ return entries;
334
+ }
335
+
336
+ const TYPE$2 = 'zenbpm:CalledElement';
337
+ const ID$1 = 'zenbpm-calledEl';
338
+ // Module-level component instances — stable references, never recreated on render.
339
+ const BindingTypeEntry$1 = makeBindingTypeEntry(ID$1, TYPE$2);
340
+ const BindingVersionTagEntry$1 = makeVersionTagEntry(ID$1, TYPE$2);
341
+ // ─── entry components ────────────────────────────────────────────────────────
342
+ function ProcessIdEntry(props) {
343
+ const { element } = props;
344
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
345
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
346
+ const translate = bpmnJsPropertiesPanel.useService('translate');
347
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
348
+ const bo = element.businessObject;
349
+ const getValue = () => getExtensionElement(bo, TYPE$2)?.processId ?? '';
350
+ const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$2, { processId: value }, bpmnFactory, commandStack);
351
+ return propertiesPanel.TextFieldEntry({ element, id: `${ID$1}-processId`, label: translate('Process ID'), getValue, setValue, debounce });
352
+ }
353
+ function PropagateAllChildVarsEntry(props) {
354
+ const { element } = props;
355
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
356
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
357
+ const translate = bpmnJsPropertiesPanel.useService('translate');
358
+ const bo = element.businessObject;
359
+ const getValue = () => getExtensionElement(bo, TYPE$2)?.propagateAllChildVariables ?? false;
360
+ const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$2, { propagateAllChildVariables: value }, bpmnFactory, commandStack);
361
+ return propertiesPanel.ToggleSwitchEntry({ element, id: `${ID$1}-propagateAllChildVariables`, label: translate('Propagate all child variables'), getValue, setValue });
362
+ }
363
+ function PropagateAllParentVarsEntry(props) {
364
+ const { element } = props;
365
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
366
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
367
+ const translate = bpmnJsPropertiesPanel.useService('translate');
368
+ const bo = element.businessObject;
369
+ const getValue = () => getExtensionElement(bo, TYPE$2)?.propagateAllParentVariables ?? true;
370
+ const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$2, { propagateAllParentVariables: value }, bpmnFactory, commandStack);
371
+ return propertiesPanel.ToggleSwitchEntry({ element, id: `${ID$1}-propagateAllParentVariables`, label: translate('Propagate all parent variables'), getValue, setValue });
372
+ }
373
+ // ─── exported entry list ─────────────────────────────────────────────────────
374
+ function CalledElementProps(element) {
375
+ if (element.type !== 'bpmn:CallActivity')
376
+ return [];
377
+ return [
378
+ { id: `${ID$1}-processId`, component: ProcessIdEntry, isEdited: propertiesPanel.isTextFieldEntryEdited },
379
+ ...bindingEntries(ID$1, BindingTypeEntry$1, BindingVersionTagEntry$1, element, TYPE$2),
380
+ { id: `${ID$1}-propagateAllChildVariables`, component: PropagateAllChildVarsEntry, isEdited: propertiesPanel.isToggleSwitchEntryEdited },
381
+ { id: `${ID$1}-propagateAllParentVariables`, component: PropagateAllParentVarsEntry, isEdited: propertiesPanel.isToggleSwitchEntryEdited },
382
+ ];
383
+ }
384
+
385
+ const TYPE$1 = 'zenbpm:CalledDecision';
386
+ const ID = 'zenbpm-calledDecision';
387
+ // Module-level component instances — stable references, never recreated on render.
388
+ const BindingTypeEntry = makeBindingTypeEntry(ID, TYPE$1);
389
+ const BindingVersionTagEntry = makeVersionTagEntry(ID, TYPE$1);
390
+ // ─── entry components ────────────────────────────────────────────────────────
391
+ function DecisionIdEntry(props) {
392
+ const { element } = props;
393
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
394
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
395
+ const translate = bpmnJsPropertiesPanel.useService('translate');
396
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
397
+ const bo = element.businessObject;
398
+ const getValue = () => getExtensionElement(bo, TYPE$1)?.decisionId ?? '';
399
+ const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$1, { decisionId: value }, bpmnFactory, commandStack);
400
+ return propertiesPanel.TextFieldEntry({ element, id: `${ID}-decisionId`, label: translate('Decision ID'), getValue, setValue, debounce });
401
+ }
402
+ function ResultVariableEntry(props) {
403
+ const { element } = props;
404
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
405
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
406
+ const translate = bpmnJsPropertiesPanel.useService('translate');
407
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
408
+ const bo = element.businessObject;
409
+ const getValue = () => getExtensionElement(bo, TYPE$1)?.resultVariable ?? '';
410
+ const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$1, { resultVariable: value }, bpmnFactory, commandStack);
411
+ return propertiesPanel.TextFieldEntry({ element, id: `${ID}-resultVariable`, label: translate('Result variable'), getValue, setValue, debounce });
412
+ }
413
+ // ─── exported entry list ─────────────────────────────────────────────────────
414
+ function CalledDecisionProps(element) {
415
+ if (element.type !== 'bpmn:BusinessRuleTask')
416
+ return [];
417
+ return [
418
+ { id: `${ID}-decisionId`, component: DecisionIdEntry, isEdited: propertiesPanel.isTextFieldEntryEdited },
419
+ ...bindingEntries(ID, BindingTypeEntry, BindingVersionTagEntry, element, TYPE$1),
420
+ { id: `${ID}-resultVariable`, component: ResultVariableEntry, isEdited: propertiesPanel.isTextFieldEntryEdited },
421
+ ];
422
+ }
423
+
424
+ // ─── constants ───────────────────────────────────────────────────────────────
425
+ const IMPLEMENTATION_OPTIONS = [
426
+ { value: 'dmnDecision', label: 'DMN decision' },
427
+ { value: 'jobWorker', label: 'Job worker' },
428
+ ];
429
+ // ─── helpers ─────────────────────────────────────────────────────────────────
430
+ /**
431
+ * Infer the current implementation type from extension elements:
432
+ * - zenbpm:TaskDefinition present → 'jobWorker'
433
+ * - otherwise (zenbpm:CalledDecision or nothing) → 'dmnDecision'
434
+ */
435
+ function getImplementationType(element) {
436
+ return getExtensionElement(element.businessObject, 'zenbpm:TaskDefinition')
437
+ ? 'jobWorker'
438
+ : 'dmnDecision';
439
+ }
440
+ // ─── entry component ─────────────────────────────────────────────────────────
441
+ function ImplementationEntry(props) {
442
+ const { element } = props;
443
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
444
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
445
+ const translate = bpmnJsPropertiesPanel.useService('translate');
446
+ const bo = element.businessObject;
447
+ const getValue = () => getImplementationType(element);
448
+ const setValue = (value) => {
449
+ if (value === 'jobWorker') {
450
+ switchExtensionElement(element, bo, 'zenbpm:CalledDecision', 'zenbpm:TaskDefinition', bpmnFactory, commandStack);
451
+ }
452
+ else {
453
+ switchExtensionElement(element, bo, 'zenbpm:TaskDefinition', 'zenbpm:CalledDecision', bpmnFactory, commandStack);
454
+ }
455
+ };
456
+ const getOptions = () => IMPLEMENTATION_OPTIONS.map(({ value, label }) => ({ value, label: translate(label) }));
457
+ return propertiesPanel.SelectEntry({
458
+ element,
459
+ id: 'zenbpm-implementation-type',
460
+ label: translate('Implementation'),
461
+ getValue,
462
+ setValue,
463
+ getOptions,
464
+ });
465
+ }
466
+ // ─── exported entry list ─────────────────────────────────────────────────────
467
+ function ImplementationProps(_element) {
468
+ return [
469
+ { id: 'zenbpm-implementation-type', component: ImplementationEntry, isEdited: propertiesPanel.isSelectEntryEdited },
470
+ ];
471
+ }
472
+
473
+ // ─── entry component ─────────────────────────────────────────────────────────
474
+ function VersionTagEntry(props) {
475
+ const { element } = props;
476
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
477
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
478
+ const translate = bpmnJsPropertiesPanel.useService('translate');
479
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
480
+ // Version tag sits on the process business object.
481
+ // For the canvas root the bo IS the process; for a sub-process it is too.
482
+ const bo = element.businessObject;
483
+ const getValue = () => getExtensionElement(bo, 'zenbpm:VersionTag')?.value ?? '';
484
+ const setValue = (value) => updateExtensionElementProps(element, bo, 'zenbpm:VersionTag', { value }, bpmnFactory, commandStack);
485
+ return propertiesPanel.TextFieldEntry({
486
+ element,
487
+ id: 'zenbpm-versionTag-value',
488
+ label: translate('Version tag'),
489
+ getValue,
490
+ setValue,
491
+ debounce,
492
+ });
493
+ }
494
+ // ─── exported entry list ─────────────────────────────────────────────────────
495
+ function VersionTagProps(element) {
496
+ // Show only on the process root (bpmn:Process); sub-processes use their own version lifecycle
497
+ if (element.type !== 'bpmn:Process')
498
+ return [];
499
+ return [
500
+ { id: 'zenbpm-versionTag-value', component: VersionTagEntry, isEdited: propertiesPanel.isTextFieldEntryEdited },
501
+ ];
502
+ }
503
+
504
+ const TYPE = 'zenbpm:LoopCharacteristics';
505
+ // ─── helpers ─────────────────────────────────────────────────────────────────
506
+ /**
507
+ * Return the bpmn:MultiInstanceLoopCharacteristics of an element, or null.
508
+ */
509
+ function getMultiInstanceLoopCharacteristics(element) {
510
+ const lc = element.businessObject?.loopCharacteristics;
511
+ if (!lc || !lc.$instanceOf('bpmn:MultiInstanceLoopCharacteristics'))
512
+ return null;
513
+ return lc;
514
+ }
515
+ function getZenbpmLoopCharacteristics(element) {
516
+ const lc = getMultiInstanceLoopCharacteristics(element);
517
+ return lc ? getExtensionElement(lc, TYPE) : undefined;
518
+ }
519
+ // ─── entry components ────────────────────────────────────────────────────────
520
+ /**
521
+ * FEEL expression — the collection to iterate over (e.g. `= items`)
522
+ */
523
+ function InputCollectionEntry(props) {
524
+ const { element } = props;
525
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
526
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
527
+ const translate = bpmnJsPropertiesPanel.useService('translate');
528
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
529
+ const lc = getMultiInstanceLoopCharacteristics(element);
530
+ const getValue = () => getZenbpmLoopCharacteristics(element)?.inputCollection ?? '';
531
+ const setValue = (value) => updateExtensionElementProps(element, lc, TYPE, { inputCollection: value }, bpmnFactory, commandStack);
532
+ return propertiesPanel.FeelEntry({
533
+ element,
534
+ id: 'zenbpm-multiInstance-inputCollection',
535
+ label: translate('Input collection'),
536
+ feel: 'required',
537
+ getValue,
538
+ setValue,
539
+ debounce,
540
+ });
541
+ }
542
+ /**
543
+ * Plain variable name — what each iteration element is called (e.g. `item`)
544
+ */
545
+ function InputElementEntry(props) {
546
+ const { element } = props;
547
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
548
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
549
+ const translate = bpmnJsPropertiesPanel.useService('translate');
550
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
551
+ const lc = getMultiInstanceLoopCharacteristics(element);
552
+ const getValue = () => getZenbpmLoopCharacteristics(element)?.inputElement ?? '';
553
+ const setValue = (value) => updateExtensionElementProps(element, lc, TYPE, { inputElement: value }, bpmnFactory, commandStack);
554
+ return propertiesPanel.TextFieldEntry({
555
+ element,
556
+ id: 'zenbpm-multiInstance-inputElement',
557
+ label: translate('Input element'),
558
+ getValue,
559
+ setValue,
560
+ debounce,
561
+ });
562
+ }
563
+ /**
564
+ * Plain variable name — where to collect the results (e.g. `results`)
565
+ */
566
+ function OutputCollectionEntry(props) {
567
+ const { element } = props;
568
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
569
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
570
+ const translate = bpmnJsPropertiesPanel.useService('translate');
571
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
572
+ const lc = getMultiInstanceLoopCharacteristics(element);
573
+ const getValue = () => getZenbpmLoopCharacteristics(element)?.outputCollection ?? '';
574
+ const setValue = (value) => updateExtensionElementProps(element, lc, TYPE, { outputCollection: value }, bpmnFactory, commandStack);
575
+ return propertiesPanel.TextFieldEntry({
576
+ element,
577
+ id: 'zenbpm-multiInstance-outputCollection',
578
+ label: translate('Output collection'),
579
+ getValue,
580
+ setValue,
581
+ debounce,
582
+ });
583
+ }
584
+ /**
585
+ * FEEL expression — the value contributed to the output collection by each iteration
586
+ */
587
+ function OutputElementEntry(props) {
588
+ const { element } = props;
589
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
590
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
591
+ const translate = bpmnJsPropertiesPanel.useService('translate');
592
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
593
+ const lc = getMultiInstanceLoopCharacteristics(element);
594
+ const getValue = () => getZenbpmLoopCharacteristics(element)?.outputElement ?? '';
595
+ const setValue = (value) => updateExtensionElementProps(element, lc, TYPE, { outputElement: value }, bpmnFactory, commandStack);
596
+ return propertiesPanel.FeelEntry({
597
+ element,
598
+ id: 'zenbpm-multiInstance-outputElement',
599
+ label: translate('Output element'),
600
+ feel: 'required',
601
+ getValue,
602
+ setValue,
603
+ debounce,
604
+ });
605
+ }
606
+ function CompletionConditionEntry(props) {
607
+ const { element } = props;
608
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
609
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
610
+ const translate = bpmnJsPropertiesPanel.useService('translate');
611
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
612
+ const lc = getMultiInstanceLoopCharacteristics(element);
613
+ const getValue = () => getFormalExpressionValue(lc.completionCondition);
614
+ const setValue = (value) => setFormalExpression(element, lc, 'completionCondition', value, bpmnFactory, commandStack);
615
+ return propertiesPanel.FeelEntry({
616
+ element,
617
+ id: 'zenbpm-multiInstance-completionCondition',
618
+ label: translate('Completion condition'),
619
+ feel: 'required',
620
+ getValue,
621
+ setValue,
622
+ debounce,
623
+ });
624
+ }
625
+ // ─── exported entry list ─────────────────────────────────────────────────────
626
+ function MultiInstanceProps(element) {
627
+ if (!getMultiInstanceLoopCharacteristics(element))
628
+ return [];
629
+ return [
630
+ { id: 'zenbpm-multiInstance-inputCollection', component: InputCollectionEntry, isEdited: propertiesPanel.isFeelEntryEdited },
631
+ { id: 'zenbpm-multiInstance-inputElement', component: InputElementEntry, isEdited: propertiesPanel.isTextFieldEntryEdited },
632
+ { id: 'zenbpm-multiInstance-outputCollection', component: OutputCollectionEntry, isEdited: propertiesPanel.isTextFieldEntryEdited },
633
+ { id: 'zenbpm-multiInstance-outputElement', component: OutputElementEntry, isEdited: propertiesPanel.isFeelEntryEdited },
634
+ { id: 'zenbpm-multiInstance-completionCondition', component: CompletionConditionEntry, isEdited: propertiesPanel.isFeelEntryEdited },
635
+ ];
636
+ }
637
+
638
+ const IO_ELEMENTS = new Set([
639
+ 'bpmn:ServiceTask', 'bpmn:BusinessRuleTask', 'bpmn:SendTask', 'bpmn:ScriptTask',
640
+ 'bpmn:UserTask', 'bpmn:SubProcess', 'bpmn:CallActivity',
641
+ 'bpmn:EndEvent', 'bpmn:IntermediateCatchEvent', 'bpmn:IntermediateThrowEvent',
642
+ ]);
643
+ const OUTPUT_ONLY_ELEMENTS = new Set([
644
+ 'bpmn:StartEvent',
645
+ 'bpmn:BoundaryEvent',
646
+ ]);
647
+ function supportsInputMapping(element) {
648
+ return IO_ELEMENTS.has(element.type);
649
+ }
650
+ function supportsOutputMapping(element) {
651
+ return IO_ELEMENTS.has(element.type) || OUTPUT_ONLY_ELEMENTS.has(element.type);
652
+ }
653
+ function makeParamEntry(id, labelKey, prop, element, param) {
654
+ return function ParamEntry(_props) {
655
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
656
+ const translate = bpmnJsPropertiesPanel.useService('translate');
657
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
658
+ const getValue = () => prop === 'source' ? getFeelValue(param[prop]) : (param[prop] || '');
659
+ const setValue = (value) => commandStack.execute('element.updateModdleProperties', {
660
+ element,
661
+ moddleElement: param,
662
+ properties: { [prop]: value },
663
+ });
664
+ return prop === 'source'
665
+ ? propertiesPanel.FeelEntry({ element, id, label: translate(labelKey), feel: 'required', getValue, setValue, debounce })
666
+ : propertiesPanel.TextFieldEntry({ element, id, label: translate(labelKey), getValue, setValue, debounce });
667
+ };
668
+ }
669
+ function addParam(element, bo, bpmnFactory, commandStack, paramType, listProp) {
670
+ const commands = [];
671
+ let extensionElements = bo.extensionElements;
672
+ if (!extensionElements) {
673
+ extensionElements = bpmnFactory.create('bpmn:ExtensionElements', { values: [] });
674
+ extensionElements.$parent = bo;
675
+ commands.push({ cmd: 'element.updateModdleProperties', context: { element, moddleElement: bo, properties: { extensionElements } } });
676
+ }
677
+ let ioMapping = (extensionElements.values || []).find((e) => e.$instanceOf('zenbpm:IoMapping'));
678
+ if (!ioMapping) {
679
+ ioMapping = bpmnFactory.create('zenbpm:IoMapping', { inputParameters: [], outputParameters: [] });
680
+ ioMapping.$parent = extensionElements;
681
+ commands.push({ cmd: 'element.updateModdleProperties', context: { element, moddleElement: extensionElements, properties: { values: [...(extensionElements.values || []), ioMapping] } } });
682
+ }
683
+ const newParam = bpmnFactory.create(paramType, { source: '', target: '' });
684
+ newParam.$parent = ioMapping;
685
+ commands.push({ cmd: 'element.updateModdleProperties', context: { element, moddleElement: ioMapping, properties: { [listProp]: [...(ioMapping[listProp] || []), newParam] } } });
686
+ commandStack.execute('properties-panel.multi-command-executor', commands);
687
+ }
688
+ function removeParam(element, ioMapping, param, listProp, commandStack) {
689
+ commandStack.execute('element.updateModdleProperties', {
690
+ element,
691
+ moddleElement: ioMapping,
692
+ properties: { [listProp]: (ioMapping[listProp] || []).filter((p) => p !== param) },
693
+ });
694
+ }
695
+ function createInputMappingGroup(element, injector) {
696
+ if (!supportsInputMapping(element))
697
+ return null;
698
+ const commandStack = injector.get('commandStack');
699
+ const bpmnFactory = injector.get('bpmnFactory');
700
+ const translate = injector.get('translate');
701
+ const eventBus = injector.get('eventBus');
702
+ const bo = element.businessObject;
703
+ const ioMapping = getExtensionElement(bo, 'zenbpm:IoMapping');
704
+ const inputs = ioMapping?.inputParameters || [];
705
+ const items = inputs.map((input, index) => {
706
+ const id = `${element.id}-zenbpm-input-${index}`;
707
+ return {
708
+ id,
709
+ label: input.target || translate('<empty>'),
710
+ entries: [
711
+ { id: `${id}-source`, component: makeParamEntry(`${id}-source`, 'Source expression', 'source', element, input), isEdited: propertiesPanel.isFeelEntryEdited },
712
+ { id: `${id}-target`, component: makeParamEntry(`${id}-target`, 'Target variable', 'target', element, input), isEdited: propertiesPanel.isTextFieldEntryEdited },
713
+ ],
714
+ autoFocusEntry: `${id}-target`,
715
+ remove: () => removeParam(element, ioMapping, input, 'inputParameters', commandStack),
716
+ };
717
+ });
718
+ return {
719
+ id: 'zenbpm-ioMapping-inputs',
720
+ label: translate('Input mapping'),
721
+ component: propertiesPanel.ListGroup,
722
+ items,
723
+ add: () => {
724
+ addParam(element, bo, bpmnFactory, commandStack, 'zenbpm:Input', 'inputParameters');
725
+ const newId = `${element.id}-zenbpm-input-${inputs.length}`;
726
+ setTimeout(() => eventBus.fire('propertiesPanel.showEntry', { id: `${newId}-target` }), 0);
727
+ },
728
+ };
729
+ }
730
+ function createOutputMappingGroup(element, injector) {
731
+ if (!supportsOutputMapping(element))
732
+ return null;
733
+ const commandStack = injector.get('commandStack');
734
+ const bpmnFactory = injector.get('bpmnFactory');
735
+ const translate = injector.get('translate');
736
+ const eventBus = injector.get('eventBus');
737
+ const bo = element.businessObject;
738
+ const ioMapping = getExtensionElement(bo, 'zenbpm:IoMapping');
739
+ const outputs = ioMapping?.outputParameters || [];
740
+ const items = outputs.map((output, index) => {
741
+ const id = `${element.id}-zenbpm-output-${index}`;
742
+ return {
743
+ id,
744
+ label: output.target || translate('<empty>'),
745
+ entries: [
746
+ { id: `${id}-source`, component: makeParamEntry(`${id}-source`, 'Source expression', 'source', element, output), isEdited: propertiesPanel.isFeelEntryEdited },
747
+ { id: `${id}-target`, component: makeParamEntry(`${id}-target`, 'Target variable', 'target', element, output), isEdited: propertiesPanel.isTextFieldEntryEdited },
748
+ ],
749
+ autoFocusEntry: `${id}-target`,
750
+ remove: () => removeParam(element, ioMapping, output, 'outputParameters', commandStack),
751
+ };
752
+ });
753
+ return {
754
+ id: 'zenbpm-ioMapping-outputs',
755
+ label: translate('Output mapping'),
756
+ component: propertiesPanel.ListGroup,
757
+ items,
758
+ add: () => {
759
+ addParam(element, bo, bpmnFactory, commandStack, 'zenbpm:Output', 'outputParameters');
760
+ const newId = `${element.id}-zenbpm-output-${outputs.length}`;
761
+ setTimeout(() => eventBus.fire('propertiesPanel.showEntry', { id: `${newId}-target` }), 0);
762
+ },
763
+ };
764
+ }
765
+
766
+ function ConditionExpressionEntry(props) {
767
+ const { element } = props;
768
+ const commandStack = bpmnJsPropertiesPanel.useService('commandStack');
769
+ const bpmnFactory = bpmnJsPropertiesPanel.useService('bpmnFactory');
770
+ const translate = bpmnJsPropertiesPanel.useService('translate');
771
+ const debounce = bpmnJsPropertiesPanel.useService('debounceInput');
772
+ const bo = element.businessObject;
773
+ const getValue = () => getFormalExpressionValue(bo.conditionExpression);
774
+ const setValue = (value) => setFormalExpression(element, bo, 'conditionExpression', value, bpmnFactory, commandStack);
775
+ return propertiesPanel.FeelEntry({
776
+ element,
777
+ id: 'zenbpm-conditionExpression',
778
+ label: translate('Condition expression'),
779
+ feel: 'required',
780
+ getValue,
781
+ setValue,
782
+ debounce,
783
+ });
784
+ }
785
+ function ConditionExpressionProps(element) {
786
+ if (element.type !== 'bpmn:SequenceFlow')
787
+ return [];
788
+ return [
789
+ { id: 'zenbpm-conditionExpression', component: ConditionExpressionEntry, isEdited: propertiesPanel.isFeelEntryEdited },
790
+ ];
791
+ }
792
+
793
+ const PROVIDER_PRIORITY = 500;
794
+ class ZenBpmPropertiesProvider {
795
+ static $inject = ['propertiesPanel', 'injector'];
796
+ _injector;
797
+ constructor(propertiesPanel, injector) {
798
+ this._injector = injector;
799
+ propertiesPanel.registerProvider(PROVIDER_PRIORITY, this);
800
+ }
801
+ getGroups(element) {
802
+ return (groups) => {
803
+ const translate = this._injector.get('translate');
804
+ // ── Implementation (Business Rule Task only) ─────────────────────────
805
+ if (element.type === 'bpmn:BusinessRuleTask') {
806
+ groups.push({
807
+ id: 'zenbpm-implementation',
808
+ label: translate('Implementation'),
809
+ entries: ImplementationProps(),
810
+ component: propertiesPanel.Group,
811
+ });
812
+ }
813
+ // ── Task Definition ──────────────────────────────────────────────────
814
+ // Shown for all service-task-like types except BusinessRuleTask, where it
815
+ // is only shown when the implementation is set to Job worker.
816
+ const showTaskDefinition = (isServiceTaskLike(element) && element.type !== 'bpmn:BusinessRuleTask') ||
817
+ (element.type === 'bpmn:BusinessRuleTask' && getImplementationType(element) === 'jobWorker');
818
+ if (showTaskDefinition) {
819
+ groups.push({
820
+ id: 'zenbpm-taskDefinition',
821
+ label: translate('Task definition'),
822
+ entries: TaskDefinitionProps(element),
823
+ component: propertiesPanel.Group,
824
+ });
825
+ }
826
+ // ── Called Decision ──────────────────────────────────────────────────
827
+ if (element.type === 'bpmn:BusinessRuleTask' && getImplementationType(element) === 'dmnDecision') {
828
+ groups.push({
829
+ id: 'zenbpm-calledDecision',
830
+ label: translate('Called decision'),
831
+ entries: CalledDecisionProps(element),
832
+ component: propertiesPanel.Group,
833
+ });
834
+ }
835
+ // ── Called Element ───────────────────────────────────────────────────
836
+ if (element.type === 'bpmn:CallActivity') {
837
+ groups.push({
838
+ id: 'zenbpm-calledElement',
839
+ label: translate('Called element'),
840
+ entries: CalledElementProps(element),
841
+ component: propertiesPanel.Group,
842
+ });
843
+ }
844
+ // ── Assignment Definition ────────────────────────────────────────────
845
+ if (element.type === 'bpmn:UserTask') {
846
+ groups.push({
847
+ id: 'zenbpm-assignmentDefinition',
848
+ label: translate('Assignment'),
849
+ entries: AssignmentDefinitionProps(element),
850
+ component: propertiesPanel.Group,
851
+ });
852
+ }
853
+ // ── Input mapping ────────────────────────────────────────────────────
854
+ const inputGroup = createInputMappingGroup(element, this._injector);
855
+ if (inputGroup)
856
+ groups.push(inputGroup);
857
+ // ── Output mapping ───────────────────────────────────────────────────
858
+ const outputGroup = createOutputMappingGroup(element, this._injector);
859
+ if (outputGroup)
860
+ groups.push(outputGroup);
861
+ // ── Multi-Instance ───────────────────────────────────────────────────
862
+ // The standard bpmn-js-properties-panel adds zeebe:LoopCharacteristics
863
+ // entries to the 'multiInstance' group. We replace the entire group with
864
+ // our zenbpm:LoopCharacteristics entries to avoid duplicate fields.
865
+ const multiInstanceEntries = MultiInstanceProps(element);
866
+ if (multiInstanceEntries.length) {
867
+ const existingGroupIdx = groups.findIndex((g) => g.id === 'multiInstance');
868
+ if (existingGroupIdx !== -1) {
869
+ groups[existingGroupIdx].entries = multiInstanceEntries;
870
+ }
871
+ else {
872
+ groups.push({
873
+ id: 'multiInstance',
874
+ label: translate('Multi-instance'),
875
+ entries: multiInstanceEntries,
876
+ component: propertiesPanel.Group,
877
+ });
878
+ }
879
+ }
880
+ // ── Condition expression ─────────────────────────────────────────────
881
+ // The standard bpmn-js-properties-panel already adds a 'conditionExpression'
882
+ // entry to the 'condition' group. We replace the entire group so that only
883
+ // the FEEL-based ZenBPM entry is shown (avoids a duplicate field).
884
+ const conditionEntries = ConditionExpressionProps(element);
885
+ if (conditionEntries.length) {
886
+ const conditionGroupIdx = groups.findIndex((g) => g.id === 'condition');
887
+ if (conditionGroupIdx !== -1) {
888
+ // Replace the standard entries with our FEEL entry
889
+ groups[conditionGroupIdx].entries = conditionEntries;
890
+ }
891
+ else {
892
+ groups.push({
893
+ id: 'zenbpm-condition',
894
+ label: translate('Condition'),
895
+ entries: conditionEntries,
896
+ component: propertiesPanel.Group,
897
+ });
898
+ }
899
+ }
900
+ // ── Version Tag (appended to General) ───────────────────────────────
901
+ const versionTagEntries = VersionTagProps(element);
902
+ if (versionTagEntries.length) {
903
+ const generalGroup = groups.find((g) => g.id === 'general');
904
+ if (generalGroup) {
905
+ generalGroup.entries = [...generalGroup.entries, ...versionTagEntries];
906
+ }
907
+ else {
908
+ groups.push({
909
+ id: 'general',
910
+ label: translate('General'),
911
+ entries: versionTagEntries,
912
+ component: propertiesPanel.Group,
913
+ });
914
+ }
915
+ }
916
+ // ── Zen Form ─────────────────────────────────────────────────────────
917
+ if (element.type === 'bpmn:UserTask') {
918
+ groups.push({
919
+ id: 'zenbpm-form',
920
+ label: translate('Zen Form'),
921
+ entries: ZenFormProps(element),
922
+ component: propertiesPanel.Group,
923
+ });
924
+ }
925
+ return groups;
926
+ };
927
+ }
928
+ }
929
+
930
+ var index = {
931
+ __init__: ['zenbpmPropertiesProvider'],
932
+ zenbpmPropertiesProvider: ['type', ZenBpmPropertiesProvider]
933
+ };
934
+
935
+ exports.ZenBpmPropertiesProviderModule = index;
936
+ //# sourceMappingURL=index.cjs.map