@bpmnkit/plugins 0.0.8
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 +153 -0
- package/dist/ai-bridge/css.d.ts +2 -0
- package/dist/ai-bridge/css.js +366 -0
- package/dist/ai-bridge/index.d.ts +44 -0
- package/dist/ai-bridge/index.js +132 -0
- package/dist/ai-bridge/panel.d.ts +27 -0
- package/dist/ai-bridge/panel.js +715 -0
- package/dist/ascii-view/css.d.ts +4 -0
- package/dist/ascii-view/css.js +83 -0
- package/dist/ascii-view/index.d.ts +25 -0
- package/dist/ascii-view/index.js +92 -0
- package/dist/command-palette/css.d.ts +4 -0
- package/dist/command-palette/css.js +157 -0
- package/dist/command-palette/index.d.ts +53 -0
- package/dist/command-palette/index.js +378 -0
- package/dist/command-palette-editor/index.d.ts +35 -0
- package/dist/command-palette-editor/index.js +60 -0
- package/dist/config-panel/css.d.ts +4 -0
- package/dist/config-panel/css.js +719 -0
- package/dist/config-panel/index.d.ts +36 -0
- package/dist/config-panel/index.js +73 -0
- package/dist/config-panel/renderer.d.ts +127 -0
- package/dist/config-panel/renderer.js +1162 -0
- package/dist/config-panel/types.d.ts +118 -0
- package/dist/config-panel/types.js +2 -0
- package/dist/config-panel-bpmn/index.d.ts +58 -0
- package/dist/config-panel-bpmn/index.js +1320 -0
- package/dist/config-panel-bpmn/template-engine.d.ts +17 -0
- package/dist/config-panel-bpmn/template-engine.js +307 -0
- package/dist/config-panel-bpmn/template-to-service-task.d.ts +30 -0
- package/dist/config-panel-bpmn/template-to-service-task.js +92 -0
- package/dist/config-panel-bpmn/template-types.d.ts +126 -0
- package/dist/config-panel-bpmn/template-types.js +6 -0
- package/dist/config-panel-bpmn/templates/generated.d.ts +3 -0
- package/dist/config-panel-bpmn/templates/generated.js +57865 -0
- package/dist/config-panel-bpmn/util.d.ts +46 -0
- package/dist/config-panel-bpmn/util.js +195 -0
- package/dist/dmn-editor/css.d.ts +3 -0
- package/dist/dmn-editor/css.js +667 -0
- package/dist/dmn-editor/dmn-editor.d.ts +34 -0
- package/dist/dmn-editor/dmn-editor.js +646 -0
- package/dist/dmn-editor/drd-canvas.d.ts +85 -0
- package/dist/dmn-editor/drd-canvas.js +1087 -0
- package/dist/dmn-editor/index.d.ts +3 -0
- package/dist/dmn-editor/index.js +2 -0
- package/dist/dmn-viewer/css.d.ts +3 -0
- package/dist/dmn-viewer/css.js +151 -0
- package/dist/dmn-viewer/dmn-viewer.d.ts +30 -0
- package/dist/dmn-viewer/dmn-viewer.js +160 -0
- package/dist/dmn-viewer/feel.d.ts +3 -0
- package/dist/dmn-viewer/feel.js +2 -0
- package/dist/dmn-viewer/index.d.ts +14 -0
- package/dist/dmn-viewer/index.js +12 -0
- package/dist/element-docs/content.d.ts +20 -0
- package/dist/element-docs/content.js +1032 -0
- package/dist/element-docs/index.d.ts +7 -0
- package/dist/element-docs/index.js +643 -0
- package/dist/feel-playground/css.d.ts +4 -0
- package/dist/feel-playground/css.js +292 -0
- package/dist/feel-playground/index.d.ts +4 -0
- package/dist/feel-playground/index.js +3 -0
- package/dist/feel-playground/playground-plugin.d.ts +14 -0
- package/dist/feel-playground/playground-plugin.js +292 -0
- package/dist/form-editor/css.d.ts +3 -0
- package/dist/form-editor/css.js +703 -0
- package/dist/form-editor/form-editor.d.ts +48 -0
- package/dist/form-editor/form-editor.js +1039 -0
- package/dist/form-editor/index.d.ts +3 -0
- package/dist/form-editor/index.js +2 -0
- package/dist/form-viewer/css.d.ts +3 -0
- package/dist/form-viewer/css.js +303 -0
- package/dist/form-viewer/form-viewer.d.ts +29 -0
- package/dist/form-viewer/form-viewer.js +376 -0
- package/dist/form-viewer/index.d.ts +12 -0
- package/dist/form-viewer/index.js +11 -0
- package/dist/history/checkpoint.d.ts +10 -0
- package/dist/history/checkpoint.js +92 -0
- package/dist/history/css.d.ts +2 -0
- package/dist/history/css.js +130 -0
- package/dist/history/history-panel.d.ts +12 -0
- package/dist/history/history-panel.js +142 -0
- package/dist/history/index.d.ts +5 -0
- package/dist/history/index.js +3 -0
- package/dist/main-menu/css.d.ts +4 -0
- package/dist/main-menu/css.js +272 -0
- package/dist/main-menu/index.d.ts +59 -0
- package/dist/main-menu/index.js +285 -0
- package/dist/minimap/css.d.ts +10 -0
- package/dist/minimap/css.js +54 -0
- package/dist/minimap/index.d.ts +38 -0
- package/dist/minimap/index.js +69 -0
- package/dist/minimap/minimap.d.ts +35 -0
- package/dist/minimap/minimap.js +166 -0
- package/dist/optimize/index.d.ts +21 -0
- package/dist/optimize/index.js +304 -0
- package/dist/process-runner/css.d.ts +2 -0
- package/dist/process-runner/css.js +467 -0
- package/dist/process-runner/index.d.ts +56 -0
- package/dist/process-runner/index.js +549 -0
- package/dist/storage/auto-save.d.ts +11 -0
- package/dist/storage/auto-save.js +44 -0
- package/dist/storage/css.d.ts +2 -0
- package/dist/storage/css.js +2 -0
- package/dist/storage/db.d.ts +99 -0
- package/dist/storage/db.js +116 -0
- package/dist/storage/dialog.d.ts +15 -0
- package/dist/storage/dialog.js +201 -0
- package/dist/storage/export.d.ts +3 -0
- package/dist/storage/export.js +137 -0
- package/dist/storage/index.d.ts +44 -0
- package/dist/storage/index.js +201 -0
- package/dist/storage/sidebar.d.ts +2 -0
- package/dist/storage/sidebar.js +2 -0
- package/dist/storage/storage-api.d.ts +76 -0
- package/dist/storage/storage-api.js +342 -0
- package/dist/storage/types.d.ts +44 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage-tabs-bridge/index.d.ts +96 -0
- package/dist/storage-tabs-bridge/index.js +502 -0
- package/dist/tabs/css.d.ts +3 -0
- package/dist/tabs/css.js +642 -0
- package/dist/tabs/file-resolver.d.ts +71 -0
- package/dist/tabs/file-resolver.js +46 -0
- package/dist/tabs/index.d.ts +15 -0
- package/dist/tabs/index.js +13 -0
- package/dist/tabs/tabs-plugin.d.ts +250 -0
- package/dist/tabs/tabs-plugin.js +1119 -0
- package/dist/token-highlight/css.d.ts +2 -0
- package/dist/token-highlight/css.js +81 -0
- package/dist/token-highlight/index.d.ts +44 -0
- package/dist/token-highlight/index.js +164 -0
- package/dist/watermark/css.d.ts +10 -0
- package/dist/watermark/css.js +54 -0
- package/dist/watermark/index.d.ts +48 -0
- package/dist/watermark/index.js +64 -0
- package/dist/zoom-controls/css.d.ts +4 -0
- package/dist/zoom-controls/css.js +48 -0
- package/dist/zoom-controls/index.d.ts +36 -0
- package/dist/zoom-controls/index.js +111 -0
- package/package.json +121 -0
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
import { parseExpression } from "@bpmnkit/feel";
|
|
2
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
3
|
+
// Attribute set on the field wrapper div when the field has a condition, used
|
|
4
|
+
// by _refreshConditionals to toggle visibility without a full re-render.
|
|
5
|
+
const FIELD_WRAPPER_ATTR = "data-field-wrapper";
|
|
6
|
+
const STORAGE_KEY_WIDTH = "bpmnkit-cfg-panel-width";
|
|
7
|
+
/**
|
|
8
|
+
* Validates a `feel-expression` field value using the real FEEL parser.
|
|
9
|
+
* Returns null when the value is empty or is a valid FEEL expression (or a plain string).
|
|
10
|
+
* Returns an error message string when the FEEL body is syntactically invalid.
|
|
11
|
+
*/
|
|
12
|
+
function validateFeelExpression(val) {
|
|
13
|
+
if (typeof val !== "string" || !val.startsWith("="))
|
|
14
|
+
return null;
|
|
15
|
+
const body = val.slice(1).trim();
|
|
16
|
+
if (body === "")
|
|
17
|
+
return null; // empty body — required check handles this separately
|
|
18
|
+
const result = parseExpression(body);
|
|
19
|
+
if (result.errors.length > 0) {
|
|
20
|
+
return result.errors[0]?.message ?? "Invalid FEEL expression";
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export class ConfigPanelRenderer {
|
|
25
|
+
_schemas;
|
|
26
|
+
_getDefinitions;
|
|
27
|
+
_applyChange;
|
|
28
|
+
_getSvgViewport;
|
|
29
|
+
_getShapes;
|
|
30
|
+
_container;
|
|
31
|
+
_onPanelShow;
|
|
32
|
+
_onPanelHide;
|
|
33
|
+
_openInPlayground;
|
|
34
|
+
_readonly;
|
|
35
|
+
_panelEl = null;
|
|
36
|
+
/** SVG group appended to the canvas viewport; holds all validation badges. */
|
|
37
|
+
_badgeContainerEl = null;
|
|
38
|
+
_collapsed = false;
|
|
39
|
+
/** User-set panel width; persisted to localStorage. */
|
|
40
|
+
_panelWidth = 320;
|
|
41
|
+
/** Sub-element refs for layout toggling; reset on panel teardown. */
|
|
42
|
+
_tabsAreaEl = null;
|
|
43
|
+
_tabsScrollEl = null;
|
|
44
|
+
_tabsPrevBtn = null;
|
|
45
|
+
_tabsNextBtn = null;
|
|
46
|
+
_bodyEl = null;
|
|
47
|
+
_searchResultsEl = null;
|
|
48
|
+
_searchClearBtn = null;
|
|
49
|
+
_searchQuery = "";
|
|
50
|
+
/** Guide bar sub-element refs; reset on panel teardown. */
|
|
51
|
+
_guideBarEl = null;
|
|
52
|
+
_guideTextEl = null;
|
|
53
|
+
_guideBtnEl = null;
|
|
54
|
+
/** Key of the field the guide is currently pointing at. */
|
|
55
|
+
_guideCurrentKey = null;
|
|
56
|
+
_guideStarted = false;
|
|
57
|
+
_selectedId = null;
|
|
58
|
+
_selectedType = null;
|
|
59
|
+
_elementName = "";
|
|
60
|
+
_activeTabId = null;
|
|
61
|
+
_values = {};
|
|
62
|
+
/** The effective registration after optional `resolve?` override. */
|
|
63
|
+
_effectiveReg = null;
|
|
64
|
+
constructor(schemas, getDefinitions, applyChange, getSvgViewport, getShapes, opts) {
|
|
65
|
+
this._schemas = schemas;
|
|
66
|
+
this._getDefinitions = getDefinitions;
|
|
67
|
+
this._applyChange = applyChange;
|
|
68
|
+
this._getSvgViewport = getSvgViewport ?? null;
|
|
69
|
+
this._getShapes = getShapes ?? null;
|
|
70
|
+
this._container = opts?.container ?? null;
|
|
71
|
+
this._onPanelShow = opts?.onPanelShow ?? null;
|
|
72
|
+
this._onPanelHide = opts?.onPanelHide ?? null;
|
|
73
|
+
this._openInPlayground = opts?.openInPlayground ?? null;
|
|
74
|
+
this._readonly = opts?.readonly ?? false;
|
|
75
|
+
// Restore persisted panel width (only used in standalone mode)
|
|
76
|
+
if (!this._container) {
|
|
77
|
+
try {
|
|
78
|
+
const saved = Number(localStorage.getItem(STORAGE_KEY_WIDTH));
|
|
79
|
+
if (Number.isFinite(saved) && saved >= 240 && saved <= 600) {
|
|
80
|
+
this._panelWidth = saved;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// localStorage unavailable (e.g. sandboxed iframe) — use default
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
onSelect(ids, shapes, edges = []) {
|
|
89
|
+
if (ids.length !== 1) {
|
|
90
|
+
this._close();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const id = ids[0];
|
|
94
|
+
if (!id) {
|
|
95
|
+
this._close();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const shape = shapes.find((s) => s.id === id);
|
|
99
|
+
const elementType = shape?.flowElement?.type;
|
|
100
|
+
if (elementType) {
|
|
101
|
+
// Shape selection
|
|
102
|
+
const reg = this._schemas.get(elementType);
|
|
103
|
+
if (!reg) {
|
|
104
|
+
this._close();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this._selectedId = id;
|
|
108
|
+
this._selectedType = elementType;
|
|
109
|
+
this._elementName = shape?.flowElement?.name ?? "";
|
|
110
|
+
// Resolve optional template override
|
|
111
|
+
const defs = this._getDefinitions();
|
|
112
|
+
const resolved = defs ? (reg.adapter.resolve?.(defs, id) ?? null) : null;
|
|
113
|
+
this._effectiveReg = resolved ?? reg;
|
|
114
|
+
this._refreshValues(this._effectiveReg);
|
|
115
|
+
this._showPanel(this._effectiveReg);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Edge selection — check if it's a sequence flow
|
|
119
|
+
const isEdge = edges.some((e) => e.id === id);
|
|
120
|
+
if (!isEdge) {
|
|
121
|
+
this._close();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const defs = this._getDefinitions();
|
|
125
|
+
// Check top-level and subprocess sequence flows
|
|
126
|
+
const allFlows = defs?.processes.flatMap((p) => [
|
|
127
|
+
...p.sequenceFlows,
|
|
128
|
+
...p.flowElements.flatMap((el) => "sequenceFlows" in el && Array.isArray(el.sequenceFlows) ? el.sequenceFlows : []),
|
|
129
|
+
]);
|
|
130
|
+
const sf = allFlows?.find((f) => f.id === id);
|
|
131
|
+
if (!sf) {
|
|
132
|
+
this._close();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const reg = this._schemas.get("sequenceFlow");
|
|
136
|
+
if (!reg) {
|
|
137
|
+
this._close();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this._selectedId = id;
|
|
141
|
+
this._selectedType = "sequenceFlow";
|
|
142
|
+
this._elementName = sf.name ?? "";
|
|
143
|
+
this._effectiveReg = reg;
|
|
144
|
+
this._refreshValues(reg);
|
|
145
|
+
this._showPanel(reg);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
onDiagramChange(defs) {
|
|
149
|
+
// Update validation badges for all canvas shapes regardless of selection
|
|
150
|
+
this._updateBadges(defs);
|
|
151
|
+
if (!this._selectedId || !this._selectedType)
|
|
152
|
+
return;
|
|
153
|
+
const reg = this._schemas.get(this._selectedType);
|
|
154
|
+
if (!reg)
|
|
155
|
+
return;
|
|
156
|
+
// Re-resolve in case a template was applied or removed
|
|
157
|
+
const resolved = reg.adapter.resolve?.(defs, this._selectedId) ?? null;
|
|
158
|
+
const newEffective = resolved ?? reg;
|
|
159
|
+
this._values = newEffective.adapter.read(defs, this._selectedId);
|
|
160
|
+
// If the effective registration changed (e.g. template applied), re-render
|
|
161
|
+
if (newEffective !== this._effectiveReg) {
|
|
162
|
+
this._effectiveReg = newEffective;
|
|
163
|
+
this._showPanel(newEffective);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this._refreshInputs();
|
|
167
|
+
this._refreshConditionals(newEffective.schema);
|
|
168
|
+
this._refreshValidation(newEffective.schema);
|
|
169
|
+
}
|
|
170
|
+
destroy() {
|
|
171
|
+
this._badgeContainerEl?.remove();
|
|
172
|
+
this._badgeContainerEl = null;
|
|
173
|
+
this._close();
|
|
174
|
+
}
|
|
175
|
+
_refreshValues(reg) {
|
|
176
|
+
const defs = this._getDefinitions();
|
|
177
|
+
if (!defs || !this._selectedId)
|
|
178
|
+
return;
|
|
179
|
+
this._values = reg.adapter.read(defs, this._selectedId);
|
|
180
|
+
}
|
|
181
|
+
_applyField(key, value) {
|
|
182
|
+
const id = this._selectedId;
|
|
183
|
+
const type = this._selectedType;
|
|
184
|
+
if (!id || !type)
|
|
185
|
+
return;
|
|
186
|
+
const reg = this._schemas.get(type);
|
|
187
|
+
if (!reg)
|
|
188
|
+
return;
|
|
189
|
+
// Use the template-resolved adapter (if any) so template attributes are preserved on write
|
|
190
|
+
const effective = this._effectiveReg ?? reg;
|
|
191
|
+
this._values[key] = value;
|
|
192
|
+
// Refresh visibility and validation immediately without waiting for diagram:change
|
|
193
|
+
this._refreshConditionals(effective.schema);
|
|
194
|
+
this._refreshValidation(effective.schema);
|
|
195
|
+
const snapshot = { ...this._values };
|
|
196
|
+
this._applyChange((defs) => effective.adapter.write(defs, id, snapshot));
|
|
197
|
+
}
|
|
198
|
+
_close() {
|
|
199
|
+
this._hidePanel();
|
|
200
|
+
this._selectedId = null;
|
|
201
|
+
this._selectedType = null;
|
|
202
|
+
this._elementName = "";
|
|
203
|
+
this._activeTabId = null;
|
|
204
|
+
this._values = {};
|
|
205
|
+
this._effectiveReg = null;
|
|
206
|
+
this._searchQuery = "";
|
|
207
|
+
this._guideCurrentKey = null;
|
|
208
|
+
this._guideStarted = false;
|
|
209
|
+
}
|
|
210
|
+
_hidePanel() {
|
|
211
|
+
this._panelEl?.remove();
|
|
212
|
+
this._panelEl = null;
|
|
213
|
+
this._tabsAreaEl = null;
|
|
214
|
+
this._tabsScrollEl = null;
|
|
215
|
+
this._tabsPrevBtn = null;
|
|
216
|
+
this._tabsNextBtn = null;
|
|
217
|
+
this._bodyEl = null;
|
|
218
|
+
this._searchResultsEl = null;
|
|
219
|
+
this._searchClearBtn = null;
|
|
220
|
+
this._guideBarEl = null;
|
|
221
|
+
this._guideTextEl = null;
|
|
222
|
+
this._guideBtnEl = null;
|
|
223
|
+
this._onPanelHide?.();
|
|
224
|
+
}
|
|
225
|
+
/** Update all input values in-place without re-rendering (preserves focus). */
|
|
226
|
+
_refreshInputs() {
|
|
227
|
+
const container = this._panelEl;
|
|
228
|
+
if (!container)
|
|
229
|
+
return;
|
|
230
|
+
for (const [key, value] of Object.entries(this._values)) {
|
|
231
|
+
const els = container.querySelectorAll(`[data-field-key="${key}"]`);
|
|
232
|
+
for (const el of els) {
|
|
233
|
+
if (document.activeElement === el)
|
|
234
|
+
continue;
|
|
235
|
+
if (el instanceof HTMLInputElement && el.type === "checkbox") {
|
|
236
|
+
el.checked = value === true || value === "true";
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const rawVal = typeof value === "string" ? value : "";
|
|
240
|
+
// FEEL-expression textareas display the value without the leading "="
|
|
241
|
+
if (el instanceof HTMLTextAreaElement && el.getAttribute("data-feel-field") === "true") {
|
|
242
|
+
el.value = rawVal.startsWith("=") ? rawVal.slice(1) : rawVal;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
el.value = rawVal;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Keep FEEL mode toggle and playground button in sync with the current value
|
|
250
|
+
const isFeelMode = typeof value === "string" && value.startsWith("=");
|
|
251
|
+
const toggleBtn = container.querySelector(`[data-feel-toggle="${key}"]`);
|
|
252
|
+
if (toggleBtn) {
|
|
253
|
+
toggleBtn.textContent = isFeelMode ? "FEEL" : "string";
|
|
254
|
+
toggleBtn.classList.toggle("bpmnkit-cfg-feel-mode-btn--active", isFeelMode);
|
|
255
|
+
}
|
|
256
|
+
// Show playground button only when in FEEL mode (fixed-FEEL buttons are always visible)
|
|
257
|
+
const playBtn = container.querySelector(`[data-feel-playground="${key}"]`);
|
|
258
|
+
if (playBtn && !playBtn.hasAttribute("data-feel-playground-fixed")) {
|
|
259
|
+
playBtn.style.display = isFeelMode ? "" : "none";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Refresh both field-level and group-level conditional visibility.
|
|
265
|
+
* Called synchronously after any value change so the UI updates immediately.
|
|
266
|
+
*/
|
|
267
|
+
_refreshConditionals(schema) {
|
|
268
|
+
const container = this._panelEl;
|
|
269
|
+
if (!container)
|
|
270
|
+
return;
|
|
271
|
+
// Field-level conditions
|
|
272
|
+
const allFields = schema.groups.flatMap((g) => g.fields);
|
|
273
|
+
for (const field of allFields) {
|
|
274
|
+
if (!field.condition)
|
|
275
|
+
continue;
|
|
276
|
+
const wrapper = container.querySelector(`[${FIELD_WRAPPER_ATTR}="${field.key}"]`);
|
|
277
|
+
if (wrapper)
|
|
278
|
+
wrapper.style.display = field.condition(this._values) ? "" : "none";
|
|
279
|
+
}
|
|
280
|
+
this._refreshGroupVisibility(schema.groups);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Returns true when a field value is considered empty for validation purposes.
|
|
284
|
+
* For `feel-expression` fields, a value of just "=" (no expression body) is
|
|
285
|
+
* treated the same as empty.
|
|
286
|
+
*/
|
|
287
|
+
_isEffectivelyEmpty(field, val) {
|
|
288
|
+
if (val === "" || val === undefined)
|
|
289
|
+
return true;
|
|
290
|
+
if (field.type === "feel-expression" && typeof val === "string") {
|
|
291
|
+
return val.trim() === "=";
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Returns an error message string when the field has a validation error, or
|
|
297
|
+
* null when the value is valid. Checks (in order):
|
|
298
|
+
* 1. Required + empty → "Required"
|
|
299
|
+
* 2. feel-expression type with FEEL parse error → error from parser
|
|
300
|
+
* 3. Custom `field.validate` callback → its return value
|
|
301
|
+
*/
|
|
302
|
+
_fieldError(field, val) {
|
|
303
|
+
if (field.required && this._isEffectivelyEmpty(field, val))
|
|
304
|
+
return "Required";
|
|
305
|
+
if (field.type === "feel-expression") {
|
|
306
|
+
const err = validateFeelExpression(val);
|
|
307
|
+
if (err !== null)
|
|
308
|
+
return err;
|
|
309
|
+
}
|
|
310
|
+
if (field.validate)
|
|
311
|
+
return field.validate(val);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
/** Returns true when a field has any validation error. */
|
|
315
|
+
_fieldHasError(field, val) {
|
|
316
|
+
return this._fieldError(field, val) !== null;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Returns true when a group has at least one required, currently-visible field
|
|
320
|
+
* that is effectively empty.
|
|
321
|
+
*/
|
|
322
|
+
_groupHasErrors(group) {
|
|
323
|
+
return group.fields.some((f) => f.type !== "action" &&
|
|
324
|
+
(!f.condition || f.condition(this._values)) &&
|
|
325
|
+
this._fieldHasError(f, this._values[f.key]));
|
|
326
|
+
}
|
|
327
|
+
/** Update invalid state for all fields, tab error dots, and guide bar. */
|
|
328
|
+
_refreshValidation(schema) {
|
|
329
|
+
const container = this._panelEl;
|
|
330
|
+
if (!container)
|
|
331
|
+
return;
|
|
332
|
+
for (const group of schema.groups) {
|
|
333
|
+
for (const field of group.fields) {
|
|
334
|
+
if (field.type === "action")
|
|
335
|
+
continue;
|
|
336
|
+
const wrapper = container.querySelector(`[${FIELD_WRAPPER_ATTR}="${field.key}"]`);
|
|
337
|
+
if (!wrapper)
|
|
338
|
+
continue;
|
|
339
|
+
const err = this._fieldError(field, this._values[field.key]);
|
|
340
|
+
wrapper.classList.toggle("bpmnkit-cfg-field--invalid", err !== null);
|
|
341
|
+
// Update error message text
|
|
342
|
+
const errEl = wrapper.querySelector(".bpmnkit-cfg-field-error");
|
|
343
|
+
if (errEl) {
|
|
344
|
+
errEl.style.display = err !== null ? "" : "none";
|
|
345
|
+
if (err !== null)
|
|
346
|
+
errEl.textContent = err;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Update tab error dot
|
|
350
|
+
const tabBtn = container.querySelector(`[data-tab-id="${group.id}"]`);
|
|
351
|
+
if (tabBtn)
|
|
352
|
+
tabBtn.classList.toggle("has-error", this._groupHasErrors(group));
|
|
353
|
+
}
|
|
354
|
+
this._refreshGuide();
|
|
355
|
+
}
|
|
356
|
+
/** Show/hide tabs and groups based on their conditions. */
|
|
357
|
+
_refreshGroupVisibility(groups) {
|
|
358
|
+
const panel = this._panelEl;
|
|
359
|
+
if (!panel)
|
|
360
|
+
return;
|
|
361
|
+
let activeIsVisible = false;
|
|
362
|
+
for (const group of groups) {
|
|
363
|
+
const isVisible = !group.condition || group.condition(this._values);
|
|
364
|
+
const tabBtn = panel.querySelector(`[data-tab-id="${group.id}"]`);
|
|
365
|
+
if (tabBtn)
|
|
366
|
+
tabBtn.style.display = isVisible ? "" : "none";
|
|
367
|
+
if (group.id === this._activeTabId && isVisible)
|
|
368
|
+
activeIsVisible = true;
|
|
369
|
+
}
|
|
370
|
+
// If the active tab just became hidden, switch to the first visible one
|
|
371
|
+
if (!activeIsVisible) {
|
|
372
|
+
for (const group of groups) {
|
|
373
|
+
if (!group.condition || group.condition(this._values)) {
|
|
374
|
+
this._activateTab(panel, group.id);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
this._syncTabsAreaVisibility(groups);
|
|
380
|
+
requestAnimationFrame(() => this._updateTabScrollBtns());
|
|
381
|
+
}
|
|
382
|
+
_activateTab(panel, groupId) {
|
|
383
|
+
this._activeTabId = groupId;
|
|
384
|
+
for (const btn of panel.querySelectorAll(".bpmnkit-cfg-tab-btn")) {
|
|
385
|
+
btn.classList.remove("active");
|
|
386
|
+
}
|
|
387
|
+
for (const grp of panel.querySelectorAll(".bpmnkit-cfg-group")) {
|
|
388
|
+
grp.style.display = "none";
|
|
389
|
+
}
|
|
390
|
+
const tabBtn = panel.querySelector(`[data-tab-id="${groupId}"]`);
|
|
391
|
+
const groupEl = panel.querySelector(`[data-group-id="${groupId}"]`);
|
|
392
|
+
tabBtn?.classList.add("active");
|
|
393
|
+
if (groupEl)
|
|
394
|
+
groupEl.style.display = "";
|
|
395
|
+
requestAnimationFrame(() => this._updateTabScrollBtns());
|
|
396
|
+
}
|
|
397
|
+
/** Show/hide tab scroll arrow buttons based on current scroll position and overflow. */
|
|
398
|
+
_updateTabScrollBtns() {
|
|
399
|
+
const tabs = this._tabsScrollEl;
|
|
400
|
+
const prev = this._tabsPrevBtn;
|
|
401
|
+
const next = this._tabsNextBtn;
|
|
402
|
+
if (!tabs || !prev || !next)
|
|
403
|
+
return;
|
|
404
|
+
const hasOverflow = tabs.scrollWidth > tabs.clientWidth + 1;
|
|
405
|
+
const atStart = tabs.scrollLeft <= 0;
|
|
406
|
+
const atEnd = tabs.scrollLeft + tabs.clientWidth >= tabs.scrollWidth - 1;
|
|
407
|
+
prev.style.display = hasOverflow && !atStart ? "flex" : "none";
|
|
408
|
+
next.style.display = hasOverflow && !atEnd ? "flex" : "none";
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Hide the tabs area when there are 0 or 1 visible groups — a single tab
|
|
412
|
+
* provides no navigation value and adds visual noise.
|
|
413
|
+
*/
|
|
414
|
+
_syncTabsAreaVisibility(groups) {
|
|
415
|
+
const tabsArea = this._tabsAreaEl;
|
|
416
|
+
if (!tabsArea)
|
|
417
|
+
return;
|
|
418
|
+
const visibleCount = groups.filter((g) => g.fields.length > 0 && (!g.condition || g.condition(this._values))).length;
|
|
419
|
+
tabsArea.style.display = visibleCount <= 1 ? "none" : "";
|
|
420
|
+
}
|
|
421
|
+
// ── Validation badges ─────────────────────────────────────────────────────
|
|
422
|
+
/**
|
|
423
|
+
* Renders (or removes) a small red "!" badge on each canvas shape that has
|
|
424
|
+
* at least one required config field with a missing or empty value.
|
|
425
|
+
* Badges live in a `<g>` appended to the SVG viewport group so they
|
|
426
|
+
* automatically follow pan and zoom.
|
|
427
|
+
*/
|
|
428
|
+
_updateBadges(defs) {
|
|
429
|
+
if (!this._getSvgViewport || !this._getShapes)
|
|
430
|
+
return;
|
|
431
|
+
const viewport = this._getSvgViewport();
|
|
432
|
+
if (!viewport)
|
|
433
|
+
return;
|
|
434
|
+
// Create badge layer on first use
|
|
435
|
+
if (!this._badgeContainerEl) {
|
|
436
|
+
const g = document.createElementNS(SVG_NS, "g");
|
|
437
|
+
g.setAttribute("class", "bpmnkit-cfg-badge-layer");
|
|
438
|
+
viewport.appendChild(g);
|
|
439
|
+
this._badgeContainerEl = g;
|
|
440
|
+
}
|
|
441
|
+
// Clear previous badges
|
|
442
|
+
const container = this._badgeContainerEl;
|
|
443
|
+
while (container.lastChild) {
|
|
444
|
+
container.removeChild(container.lastChild);
|
|
445
|
+
}
|
|
446
|
+
for (const shape of this._getShapes()) {
|
|
447
|
+
const type = shape.flowElement?.type;
|
|
448
|
+
if (!type)
|
|
449
|
+
continue;
|
|
450
|
+
const baseReg = this._schemas.get(type);
|
|
451
|
+
if (!baseReg)
|
|
452
|
+
continue;
|
|
453
|
+
// Resolve template if stamped on the element
|
|
454
|
+
const reg = baseReg.adapter.resolve?.(defs, shape.id) ?? baseReg;
|
|
455
|
+
// Collect labels for all visible fields with errors (missing or invalid FEEL)
|
|
456
|
+
const values = reg.adapter.read(defs, shape.id);
|
|
457
|
+
const missingLabels = [];
|
|
458
|
+
for (const g of reg.schema.groups) {
|
|
459
|
+
for (const f of g.fields) {
|
|
460
|
+
if (f.type === "action")
|
|
461
|
+
continue;
|
|
462
|
+
if (f.condition && !f.condition(values))
|
|
463
|
+
continue;
|
|
464
|
+
if (this._fieldHasError(f, values[f.key]))
|
|
465
|
+
missingLabels.push(f.label);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (missingLabels.length === 0)
|
|
469
|
+
continue;
|
|
470
|
+
// Badge positioned at the top-right corner of the shape
|
|
471
|
+
const { x, y, width } = shape.shape.bounds;
|
|
472
|
+
const badge = document.createElementNS(SVG_NS, "g");
|
|
473
|
+
badge.setAttribute("class", "bpmnkit-cfg-badge");
|
|
474
|
+
badge.setAttribute("transform", `translate(${x + width}, ${y})`);
|
|
475
|
+
// Tooltip: lists the specific missing fields on hover
|
|
476
|
+
const title = document.createElementNS(SVG_NS, "title");
|
|
477
|
+
title.textContent = `Required: ${missingLabels.join(", ")}`;
|
|
478
|
+
badge.appendChild(title);
|
|
479
|
+
const circle = document.createElementNS(SVG_NS, "circle");
|
|
480
|
+
circle.setAttribute("r", "7");
|
|
481
|
+
circle.setAttribute("fill", "#f87171");
|
|
482
|
+
circle.setAttribute("stroke", "#fff");
|
|
483
|
+
circle.setAttribute("stroke-width", "1.5");
|
|
484
|
+
const text = document.createElementNS(SVG_NS, "text");
|
|
485
|
+
text.setAttribute("text-anchor", "middle");
|
|
486
|
+
text.setAttribute("dominant-baseline", "central");
|
|
487
|
+
text.setAttribute("fill", "#fff");
|
|
488
|
+
text.setAttribute("font-size", "9");
|
|
489
|
+
text.setAttribute("font-weight", "bold");
|
|
490
|
+
text.setAttribute("font-family", "system-ui, sans-serif");
|
|
491
|
+
text.setAttribute("pointer-events", "none");
|
|
492
|
+
text.textContent = "!";
|
|
493
|
+
badge.appendChild(circle);
|
|
494
|
+
badge.appendChild(text);
|
|
495
|
+
container.appendChild(badge);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// ── Setup assistant / field guide ───────────────────────────────────────
|
|
499
|
+
/** Returns all visible fields that have any error (missing required or invalid FEEL). */
|
|
500
|
+
_getMissingFields() {
|
|
501
|
+
const result = [];
|
|
502
|
+
const schema = this._effectiveReg?.schema;
|
|
503
|
+
if (!schema)
|
|
504
|
+
return result;
|
|
505
|
+
for (const group of schema.groups) {
|
|
506
|
+
for (const field of group.fields) {
|
|
507
|
+
if (field.type === "action")
|
|
508
|
+
continue;
|
|
509
|
+
if (field.condition && !field.condition(this._values))
|
|
510
|
+
continue;
|
|
511
|
+
if (this._fieldHasError(field, this._values[field.key])) {
|
|
512
|
+
result.push({ group, field });
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
/** Switches to the group's tab, scrolls the field into view, and focuses its input. */
|
|
519
|
+
_navigateToField(group, field) {
|
|
520
|
+
const panel = this._panelEl;
|
|
521
|
+
if (!panel)
|
|
522
|
+
return;
|
|
523
|
+
// Exit search mode so the field is visible
|
|
524
|
+
if (this._searchQuery.trim().length > 0) {
|
|
525
|
+
const searchInput = panel.querySelector(".bpmnkit-cfg-search-input");
|
|
526
|
+
if (searchInput) {
|
|
527
|
+
searchInput.value = "";
|
|
528
|
+
this._setSearch("");
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
this._activateTab(panel, group.id);
|
|
532
|
+
this._guideCurrentKey = field.key;
|
|
533
|
+
requestAnimationFrame(() => {
|
|
534
|
+
const wrapper = panel.querySelector(`[${FIELD_WRAPPER_ATTR}="${field.key}"]`);
|
|
535
|
+
if (!wrapper)
|
|
536
|
+
return;
|
|
537
|
+
wrapper.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
538
|
+
const input = wrapper.querySelector("input, select, textarea");
|
|
539
|
+
input?.focus();
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
/** Refreshes guide bar text and visibility; hides when no required fields are missing. */
|
|
543
|
+
_refreshGuide() {
|
|
544
|
+
const bar = this._guideBarEl;
|
|
545
|
+
const textEl = this._guideTextEl;
|
|
546
|
+
const btn = this._guideBtnEl;
|
|
547
|
+
if (!bar || !textEl || !btn)
|
|
548
|
+
return;
|
|
549
|
+
const missing = this._getMissingFields();
|
|
550
|
+
if (missing.length === 0) {
|
|
551
|
+
bar.style.display = "none";
|
|
552
|
+
this._guideCurrentKey = null;
|
|
553
|
+
this._guideStarted = false;
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
bar.style.display = "";
|
|
557
|
+
textEl.textContent = missing.length === 1 ? "1 field to fix" : `${missing.length} fields to fix`;
|
|
558
|
+
btn.textContent = this._guideStarted ? "Next \u203a" : "Start \u203a";
|
|
559
|
+
}
|
|
560
|
+
// ── Search ────────────────────────────────────────────────────────────────
|
|
561
|
+
_setSearch(query) {
|
|
562
|
+
this._searchQuery = query;
|
|
563
|
+
const searchResults = this._searchResultsEl;
|
|
564
|
+
const tabsArea = this._tabsAreaEl;
|
|
565
|
+
const body = this._bodyEl;
|
|
566
|
+
const clearBtn = this._searchClearBtn;
|
|
567
|
+
if (!searchResults || !body)
|
|
568
|
+
return;
|
|
569
|
+
const isSearching = query.trim().length > 0;
|
|
570
|
+
// Toggle clear button visibility
|
|
571
|
+
if (clearBtn)
|
|
572
|
+
clearBtn.style.display = isSearching ? "flex" : "none";
|
|
573
|
+
if (!isSearching) {
|
|
574
|
+
searchResults.style.display = "none";
|
|
575
|
+
searchResults.innerHTML = "";
|
|
576
|
+
if (tabsArea) {
|
|
577
|
+
const schema = this._effectiveReg?.schema;
|
|
578
|
+
if (schema)
|
|
579
|
+
this._syncTabsAreaVisibility(schema.groups);
|
|
580
|
+
}
|
|
581
|
+
body.style.display = "";
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
body.style.display = "none";
|
|
585
|
+
if (tabsArea)
|
|
586
|
+
tabsArea.style.display = "none";
|
|
587
|
+
searchResults.style.display = "";
|
|
588
|
+
this._buildSearchResults(query);
|
|
589
|
+
}
|
|
590
|
+
_buildSearchResults(query) {
|
|
591
|
+
const searchResults = this._searchResultsEl;
|
|
592
|
+
const schema = this._effectiveReg?.schema;
|
|
593
|
+
if (!searchResults || !schema)
|
|
594
|
+
return;
|
|
595
|
+
searchResults.innerHTML = "";
|
|
596
|
+
const q = query.trim().toLowerCase();
|
|
597
|
+
let totalMatches = 0;
|
|
598
|
+
for (const group of schema.groups) {
|
|
599
|
+
const matchingFields = group.fields.filter((f) => f.label.toLowerCase().includes(q));
|
|
600
|
+
if (matchingFields.length === 0)
|
|
601
|
+
continue;
|
|
602
|
+
totalMatches += matchingFields.length;
|
|
603
|
+
const groupHeader = document.createElement("div");
|
|
604
|
+
groupHeader.className = "bpmnkit-cfg-search-group-label";
|
|
605
|
+
groupHeader.textContent = group.label;
|
|
606
|
+
searchResults.appendChild(groupHeader);
|
|
607
|
+
for (const field of matchingFields) {
|
|
608
|
+
searchResults.appendChild(this._renderField(field));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (totalMatches === 0) {
|
|
612
|
+
const empty = document.createElement("div");
|
|
613
|
+
empty.className = "bpmnkit-cfg-search-empty";
|
|
614
|
+
empty.textContent = "No matching properties";
|
|
615
|
+
searchResults.appendChild(empty);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
// ── Inspector panel ───────────────────────────────────────────────────────
|
|
619
|
+
_showPanel(reg) {
|
|
620
|
+
this._hidePanel();
|
|
621
|
+
// Reset search when re-rendering (e.g., template changed)
|
|
622
|
+
this._searchQuery = "";
|
|
623
|
+
const panel = document.createElement("div");
|
|
624
|
+
panel.className = "bpmnkit-cfg-full";
|
|
625
|
+
if (this._container) {
|
|
626
|
+
panel.classList.add("bpmnkit-cfg-full--hosted");
|
|
627
|
+
}
|
|
628
|
+
else if (this._collapsed) {
|
|
629
|
+
panel.classList.add("bpmnkit-cfg-full--collapsed");
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
panel.style.width = `${this._panelWidth}px`;
|
|
633
|
+
}
|
|
634
|
+
// Resize handle — only in standalone mode; dock provides its own resize
|
|
635
|
+
if (!this._container) {
|
|
636
|
+
const resizeHandle = document.createElement("div");
|
|
637
|
+
resizeHandle.className = "bpmnkit-cfg-resize-handle";
|
|
638
|
+
resizeHandle.addEventListener("mousedown", (e) => {
|
|
639
|
+
if (this._collapsed)
|
|
640
|
+
return;
|
|
641
|
+
e.preventDefault();
|
|
642
|
+
const startX = e.clientX;
|
|
643
|
+
const startWidth = this._panelWidth;
|
|
644
|
+
const onMove = (ev) => {
|
|
645
|
+
const dx = startX - ev.clientX;
|
|
646
|
+
this._panelWidth = Math.max(240, Math.min(600, startWidth + dx));
|
|
647
|
+
panel.style.width = `${this._panelWidth}px`;
|
|
648
|
+
try {
|
|
649
|
+
localStorage.setItem(STORAGE_KEY_WIDTH, String(this._panelWidth));
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
// ignore
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
const onUp = () => {
|
|
656
|
+
document.removeEventListener("mousemove", onMove);
|
|
657
|
+
document.removeEventListener("mouseup", onUp);
|
|
658
|
+
};
|
|
659
|
+
document.addEventListener("mousemove", onMove);
|
|
660
|
+
document.addEventListener("mouseup", onUp);
|
|
661
|
+
});
|
|
662
|
+
panel.appendChild(resizeHandle);
|
|
663
|
+
}
|
|
664
|
+
// Header
|
|
665
|
+
const header = document.createElement("div");
|
|
666
|
+
header.className = "bpmnkit-cfg-full-header";
|
|
667
|
+
const info = document.createElement("div");
|
|
668
|
+
info.className = "bpmnkit-cfg-full-info";
|
|
669
|
+
const typeLabel = document.createElement("div");
|
|
670
|
+
typeLabel.className = "bpmnkit-cfg-full-type";
|
|
671
|
+
typeLabel.textContent = this._selectedType ?? "";
|
|
672
|
+
info.appendChild(typeLabel);
|
|
673
|
+
if (reg.schema.templateName) {
|
|
674
|
+
const templateLabel = document.createElement("div");
|
|
675
|
+
templateLabel.className = "bpmnkit-cfg-full-template";
|
|
676
|
+
templateLabel.textContent = reg.schema.templateName;
|
|
677
|
+
info.appendChild(templateLabel);
|
|
678
|
+
}
|
|
679
|
+
const nameLabel = document.createElement("div");
|
|
680
|
+
nameLabel.className = "bpmnkit-cfg-full-name";
|
|
681
|
+
nameLabel.textContent = this._elementName || "(unnamed)";
|
|
682
|
+
info.appendChild(nameLabel);
|
|
683
|
+
header.appendChild(info);
|
|
684
|
+
// Docs link — shown when the schema provides a documentation URL
|
|
685
|
+
if (reg.schema.docsUrl) {
|
|
686
|
+
const docsLink = document.createElement("a");
|
|
687
|
+
docsLink.className = "bpmnkit-cfg-docs-link";
|
|
688
|
+
docsLink.href = reg.schema.docsUrl;
|
|
689
|
+
docsLink.target = "_blank";
|
|
690
|
+
docsLink.rel = "noopener noreferrer";
|
|
691
|
+
docsLink.setAttribute("title", "Documentation");
|
|
692
|
+
docsLink.textContent = "?";
|
|
693
|
+
header.appendChild(docsLink);
|
|
694
|
+
}
|
|
695
|
+
// Collapse button — only in standalone mode; dock provides its own collapse
|
|
696
|
+
if (!this._container) {
|
|
697
|
+
const collapseBtn = document.createElement("button");
|
|
698
|
+
collapseBtn.className = "bpmnkit-cfg-collapse-btn";
|
|
699
|
+
collapseBtn.setAttribute("title", this._collapsed ? "Expand" : "Collapse");
|
|
700
|
+
collapseBtn.textContent = this._collapsed ? "›" : "‹";
|
|
701
|
+
collapseBtn.addEventListener("click", () => {
|
|
702
|
+
this._collapsed = !this._collapsed;
|
|
703
|
+
panel.classList.toggle("bpmnkit-cfg-full--collapsed", this._collapsed);
|
|
704
|
+
if (this._collapsed) {
|
|
705
|
+
panel.style.width = "";
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
panel.style.width = `${this._panelWidth}px`;
|
|
709
|
+
}
|
|
710
|
+
collapseBtn.textContent = this._collapsed ? "›" : "‹";
|
|
711
|
+
collapseBtn.setAttribute("title", this._collapsed ? "Expand" : "Collapse");
|
|
712
|
+
});
|
|
713
|
+
header.appendChild(collapseBtn);
|
|
714
|
+
}
|
|
715
|
+
const closeBtn = document.createElement("button");
|
|
716
|
+
closeBtn.className = "bpmnkit-cfg-full-close";
|
|
717
|
+
closeBtn.setAttribute("title", "Close");
|
|
718
|
+
closeBtn.textContent = "×";
|
|
719
|
+
closeBtn.addEventListener("click", () => this._close());
|
|
720
|
+
header.appendChild(closeBtn);
|
|
721
|
+
// Search bar
|
|
722
|
+
const searchBar = document.createElement("div");
|
|
723
|
+
searchBar.className = "bpmnkit-cfg-search-bar";
|
|
724
|
+
const searchInput = document.createElement("input");
|
|
725
|
+
searchInput.type = "text";
|
|
726
|
+
searchInput.className = "bpmnkit-cfg-search-input";
|
|
727
|
+
searchInput.placeholder = "Search properties…";
|
|
728
|
+
searchInput.value = this._searchQuery;
|
|
729
|
+
searchInput.setAttribute("spellcheck", "false");
|
|
730
|
+
searchInput.setAttribute("autocomplete", "off");
|
|
731
|
+
const clearBtn = document.createElement("button");
|
|
732
|
+
clearBtn.className = "bpmnkit-cfg-search-clear";
|
|
733
|
+
clearBtn.setAttribute("title", "Clear search");
|
|
734
|
+
clearBtn.textContent = "×";
|
|
735
|
+
clearBtn.style.display = this._searchQuery.trim().length > 0 ? "flex" : "none";
|
|
736
|
+
searchInput.addEventListener("input", () => this._setSearch(searchInput.value));
|
|
737
|
+
searchInput.addEventListener("keydown", (e) => {
|
|
738
|
+
if (e.key === "Escape") {
|
|
739
|
+
searchInput.value = "";
|
|
740
|
+
this._setSearch("");
|
|
741
|
+
searchInput.blur();
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
clearBtn.addEventListener("click", () => {
|
|
745
|
+
searchInput.value = "";
|
|
746
|
+
this._setSearch("");
|
|
747
|
+
searchInput.focus();
|
|
748
|
+
});
|
|
749
|
+
searchBar.appendChild(searchInput);
|
|
750
|
+
searchBar.appendChild(clearBtn);
|
|
751
|
+
this._searchClearBtn = clearBtn;
|
|
752
|
+
// Guide bar — omnipresent (between search and tabs), hidden when no errors
|
|
753
|
+
const guideBar = document.createElement("div");
|
|
754
|
+
guideBar.className = "bpmnkit-cfg-guide-bar";
|
|
755
|
+
const guideInfo = document.createElement("div");
|
|
756
|
+
guideInfo.className = "bpmnkit-cfg-guide-info";
|
|
757
|
+
const guideIcon = document.createElement("span");
|
|
758
|
+
guideIcon.className = "bpmnkit-cfg-guide-icon";
|
|
759
|
+
guideIcon.textContent = "!";
|
|
760
|
+
guideIcon.setAttribute("aria-hidden", "true");
|
|
761
|
+
const guideText = document.createElement("span");
|
|
762
|
+
guideText.className = "bpmnkit-cfg-guide-text";
|
|
763
|
+
guideInfo.appendChild(guideIcon);
|
|
764
|
+
guideInfo.appendChild(guideText);
|
|
765
|
+
const guideBtn = document.createElement("button");
|
|
766
|
+
guideBtn.className = "bpmnkit-cfg-guide-btn";
|
|
767
|
+
guideBtn.textContent = "Start ›";
|
|
768
|
+
guideBtn.addEventListener("click", () => {
|
|
769
|
+
const missing = this._getMissingFields();
|
|
770
|
+
if (missing.length === 0)
|
|
771
|
+
return;
|
|
772
|
+
let nextIdx = 0;
|
|
773
|
+
if (this._guideStarted && this._guideCurrentKey !== null) {
|
|
774
|
+
const curIdx = missing.findIndex((m) => m.field.key === this._guideCurrentKey);
|
|
775
|
+
nextIdx = curIdx === -1 ? 0 : (curIdx + 1) % missing.length;
|
|
776
|
+
}
|
|
777
|
+
this._guideStarted = true;
|
|
778
|
+
const target = missing[nextIdx];
|
|
779
|
+
if (target)
|
|
780
|
+
this._navigateToField(target.group, target.field);
|
|
781
|
+
this._refreshGuide();
|
|
782
|
+
});
|
|
783
|
+
guideBar.appendChild(guideInfo);
|
|
784
|
+
guideBar.appendChild(guideBtn);
|
|
785
|
+
this._guideBarEl = guideBar;
|
|
786
|
+
this._guideTextEl = guideText;
|
|
787
|
+
this._guideBtnEl = guideBtn;
|
|
788
|
+
// Tabs area: [prev arrow] [scrollable tabs] [next arrow]
|
|
789
|
+
const tabsArea = document.createElement("div");
|
|
790
|
+
tabsArea.className = "bpmnkit-cfg-tabs-area";
|
|
791
|
+
const prevBtn = document.createElement("button");
|
|
792
|
+
prevBtn.className = "bpmnkit-cfg-tabs-scroll-btn bpmnkit-cfg-tabs-scroll-btn--prev";
|
|
793
|
+
prevBtn.setAttribute("aria-label", "Scroll tabs left");
|
|
794
|
+
prevBtn.textContent = "‹";
|
|
795
|
+
prevBtn.style.display = "none";
|
|
796
|
+
const tabs = document.createElement("div");
|
|
797
|
+
tabs.className = "bpmnkit-cfg-tabs";
|
|
798
|
+
const nextBtn = document.createElement("button");
|
|
799
|
+
nextBtn.className = "bpmnkit-cfg-tabs-scroll-btn bpmnkit-cfg-tabs-scroll-btn--next";
|
|
800
|
+
nextBtn.setAttribute("aria-label", "Scroll tabs right");
|
|
801
|
+
nextBtn.textContent = "›";
|
|
802
|
+
nextBtn.style.display = "none";
|
|
803
|
+
prevBtn.addEventListener("click", () => {
|
|
804
|
+
tabs.scrollBy({ left: -100, behavior: "smooth" });
|
|
805
|
+
});
|
|
806
|
+
nextBtn.addEventListener("click", () => {
|
|
807
|
+
tabs.scrollBy({ left: 100, behavior: "smooth" });
|
|
808
|
+
});
|
|
809
|
+
tabs.addEventListener("scroll", () => this._updateTabScrollBtns());
|
|
810
|
+
tabsArea.appendChild(prevBtn);
|
|
811
|
+
tabsArea.appendChild(tabs);
|
|
812
|
+
tabsArea.appendChild(nextBtn);
|
|
813
|
+
this._tabsAreaEl = tabsArea;
|
|
814
|
+
this._tabsScrollEl = tabs;
|
|
815
|
+
this._tabsPrevBtn = prevBtn;
|
|
816
|
+
this._tabsNextBtn = nextBtn;
|
|
817
|
+
// Scrollable body
|
|
818
|
+
const body = document.createElement("div");
|
|
819
|
+
body.className = "bpmnkit-cfg-full-body";
|
|
820
|
+
this._bodyEl = body;
|
|
821
|
+
// Search results pane (initially hidden)
|
|
822
|
+
const searchResults = document.createElement("div");
|
|
823
|
+
searchResults.className = "bpmnkit-cfg-search-results";
|
|
824
|
+
searchResults.style.display = "none";
|
|
825
|
+
this._searchResultsEl = searchResults;
|
|
826
|
+
// Ensure the active tab is valid for the current values
|
|
827
|
+
const hasActive = reg.schema.groups.some((g) => g.id === this._activeTabId &&
|
|
828
|
+
g.fields.length > 0 &&
|
|
829
|
+
(!g.condition || g.condition(this._values)));
|
|
830
|
+
if (!hasActive) {
|
|
831
|
+
this._activeTabId =
|
|
832
|
+
reg.schema.groups.find((g) => g.fields.length > 0 && (!g.condition || g.condition(this._values)))?.id ?? null;
|
|
833
|
+
}
|
|
834
|
+
for (const group of reg.schema.groups) {
|
|
835
|
+
if (group.fields.length === 0)
|
|
836
|
+
continue;
|
|
837
|
+
const isVisible = !group.condition || group.condition(this._values);
|
|
838
|
+
const isActive = group.id === this._activeTabId;
|
|
839
|
+
// Tab button
|
|
840
|
+
const tabBtn = document.createElement("button");
|
|
841
|
+
tabBtn.className = "bpmnkit-cfg-tab-btn";
|
|
842
|
+
tabBtn.textContent = group.label;
|
|
843
|
+
tabBtn.setAttribute("data-tab-id", group.id);
|
|
844
|
+
if (!isVisible)
|
|
845
|
+
tabBtn.style.display = "none";
|
|
846
|
+
if (isActive)
|
|
847
|
+
tabBtn.classList.add("active");
|
|
848
|
+
if (this._groupHasErrors(group))
|
|
849
|
+
tabBtn.classList.add("has-error");
|
|
850
|
+
tabs.appendChild(tabBtn);
|
|
851
|
+
// Group content (visible only when this tab is active)
|
|
852
|
+
const groupEl = document.createElement("div");
|
|
853
|
+
groupEl.className = "bpmnkit-cfg-group";
|
|
854
|
+
groupEl.setAttribute("data-group-id", group.id);
|
|
855
|
+
if (!isActive)
|
|
856
|
+
groupEl.style.display = "none";
|
|
857
|
+
for (const field of group.fields) {
|
|
858
|
+
groupEl.appendChild(this._renderField(field));
|
|
859
|
+
}
|
|
860
|
+
body.appendChild(groupEl);
|
|
861
|
+
tabBtn.addEventListener("click", () => {
|
|
862
|
+
this._activateTab(panel, group.id);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
panel.appendChild(header);
|
|
866
|
+
panel.appendChild(searchBar);
|
|
867
|
+
panel.appendChild(guideBar);
|
|
868
|
+
panel.appendChild(tabsArea);
|
|
869
|
+
panel.appendChild(body);
|
|
870
|
+
panel.appendChild(searchResults);
|
|
871
|
+
(this._container ?? document.body).appendChild(panel);
|
|
872
|
+
this._panelEl = panel;
|
|
873
|
+
this._onPanelShow?.();
|
|
874
|
+
// Defer layout-dependent checks until the panel is in the DOM
|
|
875
|
+
requestAnimationFrame(() => {
|
|
876
|
+
this._syncTabsAreaVisibility(reg.schema.groups);
|
|
877
|
+
this._updateTabScrollBtns();
|
|
878
|
+
this._refreshGuide();
|
|
879
|
+
// Restore active search if panel was re-rendered while search was active
|
|
880
|
+
if (this._searchQuery.trim().length > 0) {
|
|
881
|
+
this._setSearch(this._searchQuery);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
// ── Field rendering ───────────────────────────────────────────────────────
|
|
886
|
+
_renderField(field) {
|
|
887
|
+
const wrapper = document.createElement("div");
|
|
888
|
+
wrapper.className = "bpmnkit-cfg-field";
|
|
889
|
+
// Always stamp the key so _refreshConditionals and _refreshValidation can find the wrapper
|
|
890
|
+
wrapper.setAttribute(FIELD_WRAPPER_ATTR, field.key);
|
|
891
|
+
if (field.condition && !field.condition(this._values))
|
|
892
|
+
wrapper.style.display = "none";
|
|
893
|
+
const value = this._values[field.key];
|
|
894
|
+
// Initial error state (required-empty OR invalid FEEL expression)
|
|
895
|
+
if (this._fieldHasError(field, value)) {
|
|
896
|
+
wrapper.classList.add("bpmnkit-cfg-field--invalid");
|
|
897
|
+
}
|
|
898
|
+
if (field.type === "action") {
|
|
899
|
+
wrapper.appendChild(this._renderActionButton(field));
|
|
900
|
+
}
|
|
901
|
+
else if (field.type === "toggle") {
|
|
902
|
+
wrapper.appendChild(this._renderToggle(field, value));
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
const labelRow = document.createElement("div");
|
|
906
|
+
labelRow.className = "bpmnkit-cfg-field-label";
|
|
907
|
+
labelRow.textContent = field.label;
|
|
908
|
+
if (field.tooltip)
|
|
909
|
+
labelRow.title = field.tooltip;
|
|
910
|
+
if (field.required) {
|
|
911
|
+
const star = document.createElement("span");
|
|
912
|
+
star.className = "bpmnkit-cfg-required-star";
|
|
913
|
+
star.textContent = "*";
|
|
914
|
+
star.setAttribute("aria-hidden", "true");
|
|
915
|
+
labelRow.appendChild(star);
|
|
916
|
+
}
|
|
917
|
+
if (field.docsUrl) {
|
|
918
|
+
const link = document.createElement("a");
|
|
919
|
+
link.className = "bpmnkit-cfg-field-docs";
|
|
920
|
+
link.textContent = "docs";
|
|
921
|
+
link.href = field.docsUrl;
|
|
922
|
+
link.target = "_blank";
|
|
923
|
+
link.rel = "noopener noreferrer";
|
|
924
|
+
labelRow.appendChild(link);
|
|
925
|
+
}
|
|
926
|
+
// FEEL/string mode indicator/toggle
|
|
927
|
+
if (field.type === "feel-expression") {
|
|
928
|
+
if (field.feelFixed) {
|
|
929
|
+
// Static "FEEL" badge — this field is always a FEEL expression
|
|
930
|
+
const badge = document.createElement("span");
|
|
931
|
+
badge.className = "bpmnkit-cfg-feel-mode-btn bpmnkit-cfg-feel-mode-btn--active";
|
|
932
|
+
badge.setAttribute("aria-label", "Always a FEEL expression");
|
|
933
|
+
badge.textContent = "FEEL";
|
|
934
|
+
labelRow.appendChild(badge);
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
// Clickable FEEL/string toggle
|
|
938
|
+
const isFeelMode = typeof value === "string" && value.startsWith("=");
|
|
939
|
+
const modeBtn = document.createElement("button");
|
|
940
|
+
modeBtn.type = "button";
|
|
941
|
+
modeBtn.className = "bpmnkit-cfg-feel-mode-btn";
|
|
942
|
+
if (isFeelMode)
|
|
943
|
+
modeBtn.classList.add("bpmnkit-cfg-feel-mode-btn--active");
|
|
944
|
+
modeBtn.setAttribute("data-feel-toggle", field.key);
|
|
945
|
+
modeBtn.setAttribute("title", isFeelMode ? "Switch to plain string" : "Switch to FEEL expression");
|
|
946
|
+
modeBtn.textContent = isFeelMode ? "FEEL" : "string";
|
|
947
|
+
if (this._readonly)
|
|
948
|
+
modeBtn.disabled = true;
|
|
949
|
+
modeBtn.addEventListener("click", () => {
|
|
950
|
+
const cur = this._values[field.key];
|
|
951
|
+
const curStr = typeof cur === "string" ? cur : "";
|
|
952
|
+
const newVal = curStr.startsWith("=") ? curStr.slice(1) : `=${curStr}`;
|
|
953
|
+
this._applyField(field.key, newVal);
|
|
954
|
+
// Update textarea display immediately (diagram:change arrives async)
|
|
955
|
+
const ta = this._panelEl?.querySelector(`[data-field-key="${field.key}"]`);
|
|
956
|
+
if (ta && document.activeElement !== ta) {
|
|
957
|
+
ta.value = newVal.startsWith("=") ? newVal.slice(1) : newVal;
|
|
958
|
+
// Sync placeholder with new mode
|
|
959
|
+
const rawPh = field.placeholder ?? "";
|
|
960
|
+
ta.placeholder =
|
|
961
|
+
newVal.startsWith("=") && rawPh.startsWith("=") ? rawPh.slice(1).trimStart() : rawPh;
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
labelRow.appendChild(modeBtn);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
wrapper.appendChild(labelRow);
|
|
968
|
+
if (field.type === "select") {
|
|
969
|
+
wrapper.appendChild(this._renderSelect(field, value));
|
|
970
|
+
}
|
|
971
|
+
else if (field.type === "textarea") {
|
|
972
|
+
wrapper.appendChild(this._renderTextarea(field, value));
|
|
973
|
+
}
|
|
974
|
+
else if (field.type === "feel-expression") {
|
|
975
|
+
wrapper.appendChild(this._renderFeelExpression(field, value));
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
wrapper.appendChild(this._renderTextInput(field, value));
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (field.hint) {
|
|
982
|
+
const hint = document.createElement("div");
|
|
983
|
+
hint.className = "bpmnkit-cfg-field-hint";
|
|
984
|
+
// hint may contain safe HTML from template descriptions (e.g. <a> links)
|
|
985
|
+
hint.innerHTML = field.hint;
|
|
986
|
+
wrapper.appendChild(hint);
|
|
987
|
+
}
|
|
988
|
+
// Error message — shown when field has a validation error
|
|
989
|
+
if (field.type !== "action" && field.type !== "toggle") {
|
|
990
|
+
const initialErr = this._fieldError(field, value);
|
|
991
|
+
const errDiv = document.createElement("div");
|
|
992
|
+
errDiv.className = "bpmnkit-cfg-field-error";
|
|
993
|
+
if (initialErr !== null) {
|
|
994
|
+
errDiv.textContent = initialErr;
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
errDiv.style.display = "none";
|
|
998
|
+
}
|
|
999
|
+
wrapper.appendChild(errDiv);
|
|
1000
|
+
}
|
|
1001
|
+
return wrapper;
|
|
1002
|
+
}
|
|
1003
|
+
_renderFeelExpression(field, value) {
|
|
1004
|
+
const text = typeof value === "string" ? value : "";
|
|
1005
|
+
// In FEEL mode (value starts with "=" or field is always FEEL), strip "=" for display.
|
|
1006
|
+
const isFeelMode = field.feelFixed === true || text.startsWith("=");
|
|
1007
|
+
const displayText = isFeelMode && text.startsWith("=") ? text.slice(1) : text;
|
|
1008
|
+
// Strip leading "= " from placeholder when in FEEL mode so it matches the display.
|
|
1009
|
+
const rawPlaceholder = field.placeholder ?? "";
|
|
1010
|
+
const displayPlaceholder = isFeelMode && rawPlaceholder.startsWith("=")
|
|
1011
|
+
? rawPlaceholder.slice(1).trimStart()
|
|
1012
|
+
: rawPlaceholder;
|
|
1013
|
+
const wrap = document.createElement("div");
|
|
1014
|
+
wrap.className = "bpmnkit-cfg-feel-wrap";
|
|
1015
|
+
const ta = document.createElement("textarea");
|
|
1016
|
+
ta.className = "bpmnkit-cfg-feel-ta bpmnkit-cfg-textarea";
|
|
1017
|
+
ta.placeholder = displayPlaceholder;
|
|
1018
|
+
ta.value = displayText;
|
|
1019
|
+
ta.setAttribute("data-field-key", field.key);
|
|
1020
|
+
ta.setAttribute("data-feel-field", "true");
|
|
1021
|
+
ta.setAttribute("spellcheck", "false");
|
|
1022
|
+
if (this._readonly) {
|
|
1023
|
+
ta.readOnly = true;
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
ta.addEventListener("change", () => {
|
|
1027
|
+
const raw = ta.value;
|
|
1028
|
+
const curVal = this._values[field.key];
|
|
1029
|
+
const inFeelMode = field.feelFixed === true || (typeof curVal === "string" && curVal.startsWith("="));
|
|
1030
|
+
if (inFeelMode) {
|
|
1031
|
+
// Strip any stray leading "=" the user might have typed, then prepend "=".
|
|
1032
|
+
const body = raw.startsWith("=") ? raw.slice(1) : raw;
|
|
1033
|
+
this._applyField(field.key, body !== "" ? `=${body}` : "");
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
this._applyField(field.key, raw);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
wrap.appendChild(ta);
|
|
1041
|
+
if (field.openInPlayground ?? this._openInPlayground) {
|
|
1042
|
+
const btn = document.createElement("button");
|
|
1043
|
+
btn.type = "button";
|
|
1044
|
+
btn.className = "bpmnkit-cfg-feel-playground-btn";
|
|
1045
|
+
btn.textContent = "Open in FEEL Playground ↗";
|
|
1046
|
+
btn.setAttribute("data-feel-playground", field.key);
|
|
1047
|
+
if (field.feelFixed)
|
|
1048
|
+
btn.setAttribute("data-feel-playground-fixed", "true");
|
|
1049
|
+
// Fixed-FEEL fields are always in FEEL mode — always show.
|
|
1050
|
+
// Togglable fields: show only when currently in FEEL mode.
|
|
1051
|
+
if (!field.feelFixed && !isFeelMode)
|
|
1052
|
+
btn.style.display = "none";
|
|
1053
|
+
btn.addEventListener("click", () => {
|
|
1054
|
+
if (field.openInPlayground) {
|
|
1055
|
+
field.openInPlayground(this._values);
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
const val = this._values[field.key];
|
|
1059
|
+
const expr = typeof val === "string" && val.startsWith("=") ? val.slice(1).trim() : "";
|
|
1060
|
+
this._openInPlayground?.(expr);
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
wrap.appendChild(btn);
|
|
1064
|
+
}
|
|
1065
|
+
return wrap;
|
|
1066
|
+
}
|
|
1067
|
+
_renderTextInput(field, value) {
|
|
1068
|
+
const input = document.createElement("input");
|
|
1069
|
+
input.type = field.secret === true ? "password" : "text";
|
|
1070
|
+
input.className = "bpmnkit-cfg-input";
|
|
1071
|
+
input.placeholder = field.placeholder ?? "";
|
|
1072
|
+
input.value = typeof value === "string" ? value : "";
|
|
1073
|
+
input.setAttribute("data-field-key", field.key);
|
|
1074
|
+
if (this._readonly) {
|
|
1075
|
+
input.readOnly = true;
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
input.addEventListener("change", () => this._applyField(field.key, input.value));
|
|
1079
|
+
}
|
|
1080
|
+
return input;
|
|
1081
|
+
}
|
|
1082
|
+
_renderTextarea(field, value) {
|
|
1083
|
+
const ta = document.createElement("textarea");
|
|
1084
|
+
ta.className = "bpmnkit-cfg-textarea";
|
|
1085
|
+
ta.placeholder = field.placeholder ?? "";
|
|
1086
|
+
ta.value = typeof value === "string" ? value : "";
|
|
1087
|
+
ta.setAttribute("data-field-key", field.key);
|
|
1088
|
+
if (this._readonly) {
|
|
1089
|
+
ta.readOnly = true;
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
ta.addEventListener("change", () => this._applyField(field.key, ta.value));
|
|
1093
|
+
}
|
|
1094
|
+
return ta;
|
|
1095
|
+
}
|
|
1096
|
+
_renderSelect(field, value) {
|
|
1097
|
+
const sel = document.createElement("select");
|
|
1098
|
+
sel.className = "bpmnkit-cfg-select";
|
|
1099
|
+
sel.setAttribute("data-field-key", field.key);
|
|
1100
|
+
for (const opt of field.options ?? []) {
|
|
1101
|
+
const option = document.createElement("option");
|
|
1102
|
+
option.value = opt.value;
|
|
1103
|
+
option.textContent = opt.label;
|
|
1104
|
+
sel.appendChild(option);
|
|
1105
|
+
}
|
|
1106
|
+
sel.value = typeof value === "string" ? value : (field.options?.[0]?.value ?? "");
|
|
1107
|
+
if (this._readonly) {
|
|
1108
|
+
sel.disabled = true;
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
sel.addEventListener("change", () => this._applyField(field.key, sel.value));
|
|
1112
|
+
}
|
|
1113
|
+
return sel;
|
|
1114
|
+
}
|
|
1115
|
+
_renderToggle(field, value) {
|
|
1116
|
+
const row = document.createElement("div");
|
|
1117
|
+
row.className = "bpmnkit-cfg-toggle-row";
|
|
1118
|
+
const lbl = document.createElement("label");
|
|
1119
|
+
lbl.className = "bpmnkit-cfg-toggle";
|
|
1120
|
+
const input = document.createElement("input");
|
|
1121
|
+
input.type = "checkbox";
|
|
1122
|
+
input.checked = value === true || value === "true";
|
|
1123
|
+
input.setAttribute("data-field-key", field.key);
|
|
1124
|
+
if (this._readonly) {
|
|
1125
|
+
input.disabled = true;
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
input.addEventListener("change", () => this._applyField(field.key, input.checked));
|
|
1129
|
+
}
|
|
1130
|
+
const track = document.createElement("span");
|
|
1131
|
+
track.className = "bpmnkit-cfg-toggle-track";
|
|
1132
|
+
const thumb = document.createElement("span");
|
|
1133
|
+
thumb.className = "bpmnkit-cfg-toggle-thumb";
|
|
1134
|
+
lbl.appendChild(input);
|
|
1135
|
+
lbl.appendChild(track);
|
|
1136
|
+
lbl.appendChild(thumb);
|
|
1137
|
+
const labelText = document.createElement("span");
|
|
1138
|
+
labelText.className = "bpmnkit-cfg-toggle-label";
|
|
1139
|
+
labelText.textContent = field.label;
|
|
1140
|
+
if (field.tooltip)
|
|
1141
|
+
labelText.title = field.tooltip;
|
|
1142
|
+
row.appendChild(lbl);
|
|
1143
|
+
row.appendChild(labelText);
|
|
1144
|
+
return row;
|
|
1145
|
+
}
|
|
1146
|
+
_renderActionButton(field) {
|
|
1147
|
+
const btn = document.createElement("button");
|
|
1148
|
+
btn.type = "button";
|
|
1149
|
+
btn.className = "bpmnkit-cfg-action-btn";
|
|
1150
|
+
btn.textContent = field.label;
|
|
1151
|
+
if (field.tooltip)
|
|
1152
|
+
btn.title = field.tooltip;
|
|
1153
|
+
if (this._readonly) {
|
|
1154
|
+
btn.disabled = true;
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
btn.addEventListener("click", () => field.onClick?.(this._values, (k, v) => this._applyField(k, v)));
|
|
1158
|
+
}
|
|
1159
|
+
return btn;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
//# sourceMappingURL=renderer.js.map
|