@componyx/ui 0.0.0-stage → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/AppSkin/AppSkin.css +1268 -0
  2. package/AppSkin/AppSkin.js +2748 -0
  3. package/AppSkin/Themes/Default.css +241 -0
  4. package/Base/Base.js +3407 -0
  5. package/Base/ComponentIcons.css +66 -0
  6. package/Base/Font/ComponentIcons.woff +0 -0
  7. package/Base/Font/UI.woff +0 -0
  8. package/Base/Library.js +8432 -0
  9. package/Base/NavigationManager.js +462 -0
  10. package/Base/ResponsiveTemplates.js +371 -0
  11. package/Base/Sanitizer.js +288 -0
  12. package/Base/Themes/Default.css +255 -0
  13. package/Base/UI.css +618 -0
  14. package/Base/UIResourcePath.js +6 -0
  15. package/Bindary/Bindary.js +2011 -0
  16. package/Bindary/Modules/Connection.js +216 -0
  17. package/Bindary/Modules/Core.js +854 -0
  18. package/Bindary/Modules/DataUpdater.js +351 -0
  19. package/Bindary/Modules/ExpressionEngine.js +347 -0
  20. package/Bindary/Modules/ProxyManager.js +482 -0
  21. package/Bindary/Modules/Renderer.js +605 -0
  22. package/Bindary/Modules/Router.js +291 -0
  23. package/Bindary/Modules/Scroller.js +89 -0
  24. package/Bindary/Modules/Template.js +1273 -0
  25. package/BookmarkSpy/BookmarkSpy.js +430 -0
  26. package/Box/Box.css +36 -0
  27. package/Box/Box.js +1685 -0
  28. package/Button/Button.css +470 -0
  29. package/Button/Button.js +1398 -0
  30. package/Button/Themes/Default.css +200 -0
  31. package/Carousel/Carousel.css +273 -0
  32. package/Carousel/Carousel.js +607 -0
  33. package/ColorButton/ColorButton.css +39 -0
  34. package/ColorButton/ColorButton.js +336 -0
  35. package/ColorPicker/ColorPicker.css +359 -0
  36. package/ColorPicker/ColorPicker.js +972 -0
  37. package/ComboBox/ComboBox.css +357 -0
  38. package/ComboBox/ComboBox.js +2342 -0
  39. package/ComboBox/Themes/Default.css +23 -0
  40. package/DataPager/DataPager.css +72 -0
  41. package/DataPager/DataPager.js +674 -0
  42. package/DatePicker/DatePicker.css +227 -0
  43. package/DatePicker/DatePicker.js +1531 -0
  44. package/DatePicker/Themes/Default.css +26 -0
  45. package/Dialog/Dialog.css +106 -0
  46. package/Dialog/Dialog.js +572 -0
  47. package/Dialog/Themes/Default.css +10 -0
  48. package/Editor/Data/Emoji.json +8285 -0
  49. package/Editor/Data/Special.json +1042 -0
  50. package/Editor/Editor.css +1562 -0
  51. package/Editor/Editor.js +4058 -0
  52. package/Editor/Font/Editor.woff +0 -0
  53. package/Editor/Modules/ComponentFactory.js +565 -0
  54. package/Editor/Modules/ContentManager.js +748 -0
  55. package/Editor/Modules/DialogManager.js +1234 -0
  56. package/Editor/Modules/EventManager.js +458 -0
  57. package/Editor/Modules/Format.js +950 -0
  58. package/Editor/Modules/History.js +191 -0
  59. package/Editor/Modules/LayoutState.js +379 -0
  60. package/Editor/Modules/ListManager.js +296 -0
  61. package/Editor/Modules/MenuManager.js +388 -0
  62. package/Editor/Modules/NodeManager.js +681 -0
  63. package/Editor/Modules/ParagraphButtons.js +122 -0
  64. package/Editor/Modules/Resizer.js +176 -0
  65. package/Editor/Modules/SelectionRange.js +564 -0
  66. package/Editor/Modules/SourceViewBuilder.js +212 -0
  67. package/Editor/Modules/SymbolPicker.js +208 -0
  68. package/Editor/Modules/Table.js +2327 -0
  69. package/Editor/Modules/TableDialog.js +757 -0
  70. package/Editor/Modules/Toolbar.js +375 -0
  71. package/Editor/Modules/TooltipMenu.js +125 -0
  72. package/Editor/Modules/Utility.js +131 -0
  73. package/Editor/Themes/Default.css +204 -0
  74. package/FileUpload/FileUpload.css +156 -0
  75. package/FileUpload/FileUpload.js +898 -0
  76. package/FileUpload/Themes/Default.css +17 -0
  77. package/Form/Form.css +1934 -0
  78. package/Form/Form.js +4943 -0
  79. package/Form/Modules/ActionManager.js +368 -0
  80. package/Form/Modules/BuildPanelManager.js +319 -0
  81. package/Form/Modules/ComponentFactory.js +468 -0
  82. package/Form/Modules/ComponentPanel.js +265 -0
  83. package/Form/Modules/ConfigPanelManager.js +685 -0
  84. package/Form/Modules/DataBinder.js +602 -0
  85. package/Form/Modules/DataObserver.js +717 -0
  86. package/Form/Modules/Draggable.js +756 -0
  87. package/Form/Modules/OptionsPanel.js +342 -0
  88. package/Form/Modules/Renderer.js +1889 -0
  89. package/Form/Modules/RuleEngine.js +585 -0
  90. package/Form/Modules/RulesPanel.js +1158 -0
  91. package/Form/Modules/Types.js +1709 -0
  92. package/Form/Modules/ValidationManager.js +194 -0
  93. package/Form/Themes/Default.css +216 -0
  94. package/FormField/FormField.css +468 -0
  95. package/FormField/FormField.js +576 -0
  96. package/FormField/Themes/Default.css +99 -0
  97. package/Grid/Grid.css +544 -0
  98. package/Grid/Grid.js +3761 -0
  99. package/Grid/Themes/Default.css +43 -0
  100. package/LICENSE +126 -0
  101. package/MaskedTextBox/MaskedTextBox.css +28 -0
  102. package/MaskedTextBox/MaskedTextBox.js +642 -0
  103. package/MaskedTextBox/Themes/Default.css +6 -0
  104. package/Menu/Menu.css +328 -0
  105. package/Menu/Menu.js +2386 -0
  106. package/Menu/Themes/Default.css +24 -0
  107. package/NumericBox/NumericBox.css +120 -0
  108. package/NumericBox/NumericBox.js +984 -0
  109. package/PanelBar/PanelBar.css +123 -0
  110. package/PanelBar/PanelBar.js +505 -0
  111. package/PanelBar/Themes/Default.css +51 -0
  112. package/PieChart/PieChart.css +95 -0
  113. package/PieChart/PieChart.js +820 -0
  114. package/PieChart/Themes/Default.css +9 -0
  115. package/README.md +178 -3
  116. package/Slider/Slider.css +348 -0
  117. package/Slider/Slider.js +1306 -0
  118. package/Slider/Themes/Default.css +44 -0
  119. package/TabStrip/TabStrip.css +52 -0
  120. package/TabStrip/TabStrip.js +362 -0
  121. package/TabStrip/Themes/Default.css +35 -0
  122. package/TimePicker/Themes/Default.css +30 -0
  123. package/TimePicker/TimePicker.css +253 -0
  124. package/TimePicker/TimePicker.js +1485 -0
  125. package/TooltipManager/Themes/Default.css +21 -0
  126. package/TooltipManager/TooltipManager.css +227 -0
  127. package/TooltipManager/TooltipManager.js +791 -0
  128. package/Validator/Validator.css +54 -0
  129. package/Validator/Validator.js +2508 -0
  130. package/componyx.html-data.json +619 -0
  131. package/index.js +27 -0
  132. package/package.json +43 -4
  133. package/types/AppSkin.d.ts +842 -0
  134. package/types/Base.d.ts +1034 -0
  135. package/types/Bindary.d.ts +1196 -0
  136. package/types/BookmarkSpy.d.ts +112 -0
  137. package/types/Box.d.ts +374 -0
  138. package/types/Button.d.ts +455 -0
  139. package/types/Carousel.d.ts +136 -0
  140. package/types/ColorButton.d.ts +120 -0
  141. package/types/ColorPicker.d.ts +225 -0
  142. package/types/ComboBox.d.ts +507 -0
  143. package/types/DataPager.d.ts +152 -0
  144. package/types/DatePicker.d.ts +354 -0
  145. package/types/Dialog.d.ts +160 -0
  146. package/types/Editor.d.ts +1289 -0
  147. package/types/FileUpload.d.ts +206 -0
  148. package/types/Form.d.ts +3078 -0
  149. package/types/FormField.d.ts +204 -0
  150. package/types/Grid.d.ts +911 -0
  151. package/types/Library.d.ts +1899 -0
  152. package/types/MaskedTextBox.d.ts +91 -0
  153. package/types/Menu.d.ts +577 -0
  154. package/types/NumericBox.d.ts +310 -0
  155. package/types/PanelBar.d.ts +223 -0
  156. package/types/PieChart.d.ts +224 -0
  157. package/types/ResponsiveTemplates.d.ts +108 -0
  158. package/types/Slider.d.ts +227 -0
  159. package/types/TabStrip.d.ts +185 -0
  160. package/types/TimePicker.d.ts +265 -0
  161. package/types/TooltipManager.d.ts +178 -0
  162. package/types/UI.d.ts +29 -0
  163. package/types/Validator.d.ts +653 -0
  164. package/types/index.d.ts +59 -0
  165. package/ui.css +11783 -0
  166. package/ui.js +79477 -0
  167. package/ui.min.css +138 -0
  168. package/ui.min.js +264 -0
  169. package/ui.min.js.map +1 -0
@@ -0,0 +1,1158 @@
1
+ /*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
2
+ componyx.UI.form_modules = componyx.UI.form_modules || {};
3
+
4
+ componyx.UI.form_modules.RulesPanel = class RulesPanel
5
+ {
6
+ #formCtor;
7
+ #idPrefix;
8
+ #panelId;
9
+ #targetField;
10
+ constructor(form, configPanelManager)
11
+ {
12
+ this.#panelId = 'RulesPanel';
13
+ this.#idPrefix = `${form.id}_${this.#panelId}_`;
14
+
15
+ this.form = form;
16
+ this.expressionEngine = form.expressionEngine;
17
+ this.labels = form.labels;
18
+ this.cf = form.componentFactory;
19
+ this.cpm = configPanelManager;
20
+ this.#formCtor = this.form.constructor;
21
+ this.InputTypeOption = this.#formCtor.InputTypeOption;
22
+ this.LabelDisplayOption = componyx.UI.FormField.LabelDisplayOption;
23
+ this.RunVisibilityOption = this.#formCtor.RuleCaseRunVisibilityOption;
24
+ }
25
+
26
+ destroy()
27
+ {
28
+ if (this.#targetField)
29
+ this.#clearEmptyRules(this.#targetField);
30
+
31
+ this.#targetField = null;
32
+ }
33
+
34
+ render(target)
35
+ {
36
+ const panel = this.cpm.panelBar.panels.find(p => p.id === this.#panelId),
37
+ contentEl = panel.contentElement;
38
+
39
+ contentEl.innerHTML = '';
40
+
41
+ this.#targetField = this.form.getFields().find(f => f.id === target.id); // always make sure we have the proxied field
42
+
43
+ this.cf.createButton(contentEl, `${this.#idPrefix}add_case`, this.form.addRuleCaseButtonId,
44
+ {
45
+ transparent: false,
46
+ hasIcon: true,
47
+ cssClassIcon: 'ico-plus',
48
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.ADD),
49
+ text: `${this.labels.addItemPrefix}${this.labels.ruleCase}`,
50
+ command: this.#addRuleCase.bind(this)
51
+ });
52
+
53
+ (target.ruleCases || []).forEach(ruleCase =>
54
+ {
55
+ this.#createRuleCase(contentEl, ruleCase.id);
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Creates the value input for the rule or action.
61
+ * @param {HTMLElement} container The container element.
62
+ * @param {componyx.UI.Form.Field} field The Field instance.
63
+ * @param {componyx.UI.Form.Rule|componyx.UI.Form.Action} target The Rule or Action instance.
64
+ */
65
+ createValueInput(container, field, target)
66
+ {
67
+ this.#createValueInput(container, field, target);
68
+ }
69
+
70
+ /**
71
+ * Creates the expression value input for the action.
72
+ * @param {HTMLElement} container The container element.
73
+ * @param {componyx.UI.Form.Field} field The Field instance.
74
+ * @param {componyx.UI.Form.Action} action The Action instance.
75
+ */
76
+ createExpressionInput(container, field, action)
77
+ {
78
+ this.#createExpressionInput(container, field, action);
79
+ }
80
+
81
+ #getRuleCaseById(id)
82
+ {
83
+ return this.#targetField.ruleCases.find(rc => rc.id === id) || null;
84
+ }
85
+
86
+ #getRuleGroupById(id, ruleCase)
87
+ {
88
+ return ruleCase.ruleGroups?.find(rg => rg.id === id) || null;
89
+ }
90
+
91
+ #getRuleById(id, ruleGroup)
92
+ {
93
+
94
+ return ruleGroup.rules?.find(r => r.id === id) || null;
95
+ }
96
+
97
+ #getActionById(id, ruleCase)
98
+ {
99
+ return ruleCase.actions?.find(a => a.id === id) || null;
100
+ }
101
+
102
+ #createRuleCase(container, ruleCaseId)
103
+ {
104
+ const ruleCase = this.#getRuleCaseById(ruleCaseId),
105
+ id = `${this.#idPrefix}${ruleCase.id}`,
106
+ ruleCaseEl = this.#createContainer(container, this.cpm.classOption.RULE_CASE, id),
107
+ headerTitleEl = $lib.element({ tag: 'span', content: this.labels.ruleCaseHeader }),
108
+ headerEl = $lib.element({ container: ruleCaseEl, tag: 'header', content: headerTitleEl });
109
+
110
+ this.cf.createButton(headerEl, `${id}_remove`, this.form.removeRuleCaseButtonId,
111
+ {
112
+ hasIcon: true,
113
+ cssClassIcon: 'ico-bin',
114
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.REMOVE),
115
+ command: this.#removeRuleCase.bind(this, this.#targetField, ruleCase)
116
+ });
117
+
118
+ const ruleCaseContent = this.#createContainer(ruleCaseEl);
119
+ this.cf.createFormField(ruleCaseContent, `${id}_trigger`, '', this.labels.ruleCaseTrigger, this.#createTriggerCombo(ruleCaseContent, `${id}_trigger_input`, ruleCase), {
120
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.RULE_CASE_TRIGGER),
121
+ labelDisplay: this.LabelDisplayOption.BEFORE,
122
+ inline: true
123
+ });
124
+ this.cf.createFormField(ruleCaseContent, `${id}_run_visibility`, '', this.labels.ruleCaseRunVisibility, this.#createRunVisibilityCombo(ruleCaseContent, `${id}_run_visibility_input`, ruleCase), {
125
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.RULE_CASE_RUN_VISIBILITY),
126
+ labelDisplay: this.LabelDisplayOption.BEFORE,
127
+ inline: true
128
+ });
129
+
130
+ this.#createLineBreak(ruleCaseContent);
131
+ this.cf.createButton(ruleCaseContent, `${id}add_group`, this.form.addRuleGroupButtonId,
132
+ {
133
+ transparent: false,
134
+ hasIcon: true,
135
+ cssClassIcon: 'ico-plus',
136
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.ADD),
137
+ text: `${this.labels.addItemPrefix}${this.labels.ruleGroup}`,
138
+ command: this.#addRuleGroup.bind(this, ruleCase.id)
139
+ });
140
+
141
+ this.#createGroups(ruleCaseContent, ruleCase);
142
+ this.#createActions(ruleCaseContent, ruleCase);
143
+ }
144
+
145
+ #createLineBreak(container)
146
+ {
147
+ $lib.element({ container, tag: 'p', attrs: { class: this.cpm.getCssClass(this.cpm.classOption.LINE_BREAK) } });
148
+ }
149
+
150
+ #createGroups(container, ruleCase)
151
+ {
152
+ const ruleGroupsEl = this.#createContainer(container, this.cpm.classOption.RULE_GROUPS);
153
+
154
+ ruleGroupsEl.setAttribute('data-placeholder', this.labels.ruleGroupsEmptyHint);
155
+
156
+ (ruleCase.ruleGroups || []).forEach(ruleGroup =>
157
+ {
158
+ this.#createRuleGroup(ruleGroupsEl, ruleCase, ruleGroup.id);
159
+ });
160
+ }
161
+
162
+ #createActions(container, ruleCase)
163
+ {
164
+ const id = `${this.#idPrefix}${ruleCase.id}`,
165
+ actionsContainer = this.#createContainer(container, this.cpm.classOption.ACTIONS),
166
+ headerEl = $lib.element({ container: actionsContainer, tag: 'header', content: this.labels.actionsHeader });
167
+
168
+ this.cf.createButton(headerEl, `${id}add_action`, this.form.addActionButtonId,
169
+ {
170
+ hasIcon: true,
171
+ cssClassIcon: 'ico-plus',
172
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.ADD),
173
+ command: this.#addAction.bind(this, ruleCase)
174
+ });
175
+
176
+ let isFollowUpAction = false;
177
+ (ruleCase.actions || []).forEach(action =>
178
+ {
179
+ this.#createAction(actionsContainer, ruleCase, action.id, isFollowUpAction);
180
+
181
+ const runtime = this.form.runtimeActions[action.runtimeActionId];
182
+ if (runtime?.mustAwait)
183
+ isFollowUpAction = true;
184
+ });
185
+ }
186
+
187
+ #createRuleGroup(container, ruleCase, ruleGroupId)
188
+ {
189
+ const ruleGroup = this.#getRuleGroupById(ruleGroupId, ruleCase),
190
+ id = `${this.#idPrefix}${ruleGroup.id}`,
191
+ groupEl = this.#createContainer(container, this.cpm.classOption.RULE_GROUP, id);
192
+
193
+ ruleGroup.parent = ruleCase; // set parent reference
194
+
195
+ const rulesEl = this.#createContainer(groupEl, this.cpm.classOption.RULES);
196
+
197
+ (ruleGroup.rules || []).forEach(rule =>
198
+ {
199
+ this.#createRule(rulesEl, ruleGroup, rule.id);
200
+ });
201
+
202
+ this.cf.createFormField(groupEl, `${id}_logical_operator`, '', this.labels.ruleGroupConnector,
203
+ this.#createLogicalOperatorCombo(groupEl, `${id}_logical_operator_input`, ruleGroup,
204
+ {
205
+ onItemSelect: (combo, args) =>
206
+ {
207
+ if (args.item.id === 'X') // clear
208
+ {
209
+ ruleGroup.logicalOperator = null;
210
+
211
+ const index = ruleCase.ruleGroups.findIndex(rg => rg.id === ruleGroup.id);
212
+
213
+ if (index !== -1)
214
+ {
215
+ const groupsToRemove = ruleCase.ruleGroups.slice(index); // remove this and all following groups
216
+ groupsToRemove.forEach(g => this.#removeRuleGroup(ruleCase, g));
217
+ }
218
+ }
219
+ else
220
+ {
221
+ ruleGroup.logicalOperator = args.item.id;
222
+
223
+ const lastGroup = ruleCase.ruleGroups[ruleCase.ruleGroups.length - 1];
224
+ if (lastGroup.id === ruleGroup.id)
225
+ this.#addRuleGroup(ruleCase.id);
226
+ }
227
+ }
228
+ }), { labelDisplay: this.LabelDisplayOption.BEFORE });
229
+ }
230
+
231
+ #createRule(container, ruleGroup, ruleId)
232
+ {
233
+ const rule = this.#getRuleById(ruleId, ruleGroup),
234
+ id = `${this.#idPrefix}${rule.id}`,
235
+ ruleEl = this.#createContainer(container, this.cpm.classOption.RULE, id);
236
+
237
+ ruleEl._rule = rule;
238
+ rule.parent = ruleGroup;
239
+
240
+ // make sure value container is already available for possible field selection when rendering field combobox
241
+ const cssClass = `${this.cpm.getCssClass(this.cpm.classOption.RULE_VALUE)} ${this.cpm.getCssClass(this.cpm.classOption.DISABLED)}`,
242
+ valueContainer = $lib.element({ container: ruleEl, props: { className: cssClass, inert: true } });
243
+
244
+ this.#createFieldCombo(ruleEl, `${id}_field`, rule);
245
+ this.#createComparisonOperatorCombo(ruleEl, `${id}_comparison_operator`, rule);
246
+ const logicalOperatorEl = this.#createLogicalOperatorCombo(ruleEl, `${id}_logical_operator`, rule,
247
+ {
248
+ onItemSelect: (combo, args) =>
249
+ {
250
+ if (args.item.id === 'X') // clear
251
+ {
252
+ const index = ruleGroup.rules.findIndex(r => r.id === rule.id);
253
+
254
+ if (index !== -1)
255
+ {
256
+ const rulesToRemove = ruleGroup.rules.slice(index); // remove this and all following rules
257
+ rulesToRemove.forEach(r => this.#removeRule(ruleGroup, r));
258
+ }
259
+ }
260
+ else
261
+ {
262
+ rule.logicalOperator = args.item.id;
263
+
264
+ const lastRule = ruleGroup.rules[ruleGroup.rules.length - 1];
265
+ if (lastRule.id === rule.id)
266
+ this.#addRule(ruleGroup);
267
+ }
268
+ }
269
+ });
270
+
271
+ ruleEl.insertBefore(valueContainer, logicalOperatorEl); // correct placement of value container
272
+ }
273
+
274
+ #createAction(container, ruleCase, actionId, followUp)
275
+ {
276
+ const action = this.#getActionById(actionId, ruleCase),
277
+ id = `${this.#idPrefix}${action.id}`,
278
+ actionEl = this.#createContainer(container, this.cpm.classOption.ACTION, id);
279
+
280
+ if (followUp)
281
+ actionEl.classList.add(this.cpm.getCssClass(this.cpm.classOption.ACTION_FOLLOW_UP));
282
+
283
+ action.parent = ruleCase;
284
+
285
+ const valueContainer = $lib.element({
286
+ container: actionEl,
287
+ props:
288
+ {
289
+ className: `${this.cpm.getCssClass(this.cpm.classOption.ACTION_VALUE)} ${this.cpm.getCssClass(this.cpm.classOption.DISABLED)}`,
290
+ inert: true
291
+ }
292
+ });
293
+
294
+ this.#createActionCombo(actionEl, `${id}`, action);
295
+ this.#createActionTargetCombo(actionEl, `${id}`, action);
296
+ this.cf.createButton(actionEl, `${id}_remove`, this.form.removeActionButtonId,
297
+ {
298
+ hasIcon: true,
299
+ cssClassIcon: 'ico-bin',
300
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.REMOVE),
301
+ command: this.#removeAction.bind(this, ruleCase, action)
302
+ }).element;
303
+
304
+ this.#createLineBreak(actionEl);
305
+ actionEl.appendChild(valueContainer); // correct placement of value container
306
+ }
307
+
308
+ #addRuleCase()
309
+ {
310
+ const ruleCase = new this.#formCtor.RuleCase({ trigger: this.#formCtor.RuleCaseTriggerOption.ALWAYS, ruleGroups: [], actions: [] }),
311
+ target = this.#targetField;
312
+
313
+ ruleCase.id = this.form.guid();
314
+ target.ruleCases = target.ruleCases || [];
315
+ target.ruleCases.push(ruleCase);
316
+
317
+ const panel = this.cpm.panelBar.panels.find(p => p.id === this.#panelId);
318
+ this.#createRuleCase(panel.contentElement, ruleCase.id);
319
+ this.#addRuleGroup(ruleCase.id);
320
+ this.form.updateField(this.#targetField);
321
+ }
322
+
323
+ #removeRuleCase(target, ruleCase)
324
+ {
325
+ const index = target.ruleCases.findIndex(rc => rc.id === ruleCase.id);
326
+ if (index !== -1) target.ruleCases.splice(index, 1);
327
+
328
+ const el = $lib(`#${this.#idPrefix}${ruleCase.id}`);
329
+
330
+ if (el)
331
+ el.remove();
332
+
333
+ this.form.updateField(this.#targetField);
334
+ }
335
+
336
+ #addRuleGroup(ruleCaseId)
337
+ {
338
+ const ruleCase = this.#getRuleCaseById(ruleCaseId),
339
+ ruleGroup = new this.#formCtor.RuleGroup({ rules: [], logicalOperator: 'AND' }),
340
+ rule = new this.#formCtor.Rule();
341
+
342
+ ruleGroup.id = this.form.guid();
343
+ rule.id = this.form.guid();
344
+
345
+ ruleCase.ruleGroups = ruleCase.ruleGroups || [];
346
+ ruleCase.ruleGroups.push(ruleGroup);
347
+ ruleGroup.rules.push(rule);
348
+
349
+ const ruleGroupsEl = $lib(`#${this.#idPrefix}${ruleCase.id}`).querySelector(`.${this.cpm.getCssClass(this.cpm.classOption.RULE_GROUPS)}`);
350
+ this.#createRuleGroup(ruleGroupsEl, ruleCase, ruleGroup.id);
351
+ }
352
+
353
+ #removeRuleGroup(ruleCase, ruleGroup)
354
+ {
355
+ (ruleGroup.rules || []).forEach(rule =>
356
+ {
357
+ this.#removeRule(ruleGroup, rule);
358
+ });
359
+
360
+ const index = ruleCase.ruleGroups.findIndex(rg => rg.id === ruleGroup.id);
361
+ if (index !== -1) ruleCase.ruleGroups.splice(index, 1);
362
+
363
+ const el = $lib(`#${this.#idPrefix}${ruleGroup.id}`);
364
+
365
+ if (el)
366
+ el.remove();
367
+ }
368
+
369
+ #addRule(ruleGroup)
370
+ {
371
+ const rule = new this.#formCtor.Rule({ sourceId: null, property: null, comparisonOperator: null, value: null });
372
+
373
+ rule.id = this.form.guid();
374
+ ruleGroup.rules = ruleGroup.rules || [];
375
+ ruleGroup.rules.push(rule);
376
+
377
+ const groupEl = $lib(`#${this.#idPrefix}${ruleGroup.id}`).querySelector(`.${this.cpm.getCssClass(this.cpm.classOption.RULES)}`);
378
+ this.#createRule(groupEl, ruleGroup, rule.id);
379
+ }
380
+
381
+ #removeRule(ruleGroup, rule)
382
+ {
383
+ const index = ruleGroup.rules.findIndex(r => r.id === rule.id);
384
+ if (index !== -1) ruleGroup.rules.splice(index, 1);
385
+
386
+ const el = $lib(`#${this.#idPrefix}${rule.id}`);
387
+ el.remove();
388
+
389
+ if (!ruleGroup.rules || ruleGroup.rules.length === 0)
390
+ this.#removeRuleGroup(ruleGroup.parent, ruleGroup);
391
+ }
392
+
393
+ #addAction(ruleCase)
394
+ {
395
+ const action = new this.#formCtor.Action({ value: null });
396
+
397
+ action.id = this.form.guid();
398
+ ruleCase.actions = ruleCase.actions || [];
399
+ ruleCase.actions.push(action);
400
+
401
+ const actionsEl = $lib(`#${this.#idPrefix}${ruleCase.id}`).querySelector(`.${this.cpm.getCssClass(this.cpm.classOption.ACTIONS)}`);
402
+ this.#createAction(actionsEl, ruleCase, action.id, false);
403
+ this.#updateFollowUpActions(ruleCase);
404
+ }
405
+
406
+ #removeAction(ruleCase, action)
407
+ {
408
+ const index = ruleCase.actions.findIndex(a => a.id === action.id);
409
+ if (index !== -1) ruleCase.actions.splice(index, 1);
410
+
411
+ const el = $lib(`#${this.#idPrefix}${action.id}`);
412
+ el.remove();
413
+ this.#updateFollowUpActions(ruleCase);
414
+ }
415
+
416
+ #updateFollowUpActions(ruleCase)
417
+ {
418
+ const cssClassFollowUp = this.cpm.getCssClass(this.cpm.classOption.ACTION_FOLLOW_UP);
419
+ let isFollowUpAction = false;
420
+
421
+ (ruleCase.actions || []).forEach(action =>
422
+ {
423
+ const el = $lib(`#${this.#idPrefix}${action.id}`);
424
+ if (!el) return;
425
+
426
+ el.classList.toggle(cssClassFollowUp, isFollowUpAction);
427
+
428
+ const runtime = this.form.runtimeActions[action.runtimeActionId];
429
+ if (runtime?.mustAwait)
430
+ isFollowUpAction = true;
431
+ });
432
+ }
433
+
434
+ #clearEmptyRules(field)
435
+ {
436
+ (field.ruleCases || []).forEach(ruleCase =>
437
+ {
438
+ (ruleCase.ruleGroups || []).slice().forEach(ruleGroup =>
439
+ {
440
+ (ruleGroup.rules || []).slice().forEach(rule =>
441
+ {
442
+ if ($lib.isEmpty(rule.sourceId))
443
+ this.#removeRule(ruleGroup, rule);
444
+ });
445
+
446
+ if (!ruleGroup.rules || ruleGroup.rules.length === 0)
447
+ this.#removeRuleGroup(ruleCase, ruleGroup);
448
+ });
449
+ });
450
+ }
451
+
452
+ #createContainer(container, cssClass, id)
453
+ {
454
+ return $lib.element({ container: container, props: { id: (id) ? id : undefined, className: (cssClass) ? this.cpm.getCssClass(cssClass) : undefined } });
455
+ }
456
+
457
+ #createSectionsValueCombo(container, field, action)
458
+ {
459
+ const sections = this.form.fieldSets,
460
+ itemList = sections.map(s => ({ id: s.id, text: s.name, selected: s.id === action.value }));
461
+ this.cf.createComboBox(container, `${action.id}_input`, this.form.sectionsValueComboBoxId, {
462
+ placeholder: this.labels.comboBoxDefaultHint,
463
+ allowInput: false,
464
+ disableClearButton: true,
465
+ itemList,
466
+ events: {
467
+ onItemSelect: (combo, args) =>
468
+ {
469
+ action.value = combo.getValue();
470
+ }
471
+ }
472
+ });
473
+ }
474
+
475
+ /**
476
+ * Creates the value input for the rule or action.
477
+ * @param {HTMLElement} container The container element.
478
+ * @param {componyx.UI.Form.Field} field The Field instance.
479
+ * @param {componyx.UI.Form.Rule|componyx.UI.Form.Action} target The Rule or Action instance.
480
+ * @private
481
+ * @ignore
482
+ */
483
+ #createValueInput(container, field, target)
484
+ {
485
+ const formCtor = this.form.constructor,
486
+ customType = formCtor.getCustomType(field.inputTypeName),
487
+ InputTypeOption = this.InputTypeOption,
488
+ id = `${target.id}_input`,
489
+ defaultInput = this.#createDefaultValueInput.bind(this),
490
+ onChanged = (c, args) => { target.value = args.value; },
491
+ events = { onChanged },
492
+ createComponent = (cloneId, type, compSettings = {}) =>
493
+ {
494
+ return this.cf.createComponent(
495
+ container,
496
+ type || this.cf.getComponentType(field.inputType),
497
+ this.cf.createElement(id).id,
498
+ cloneId,
499
+ {
500
+ ...compSettings,
501
+ _disableDataBinding: true,
502
+ value: target.value,
503
+ events: {
504
+ ...(compSettings.events || {})
505
+ }
506
+ }
507
+ );
508
+ };
509
+
510
+ this.#clearValueContainer(container, target.id);
511
+
512
+ if (customType?.renderValueInput)
513
+ {
514
+ return customType.renderValueInput({
515
+ form: this.form,
516
+ field,
517
+ target,
518
+ container
519
+ });
520
+ }
521
+
522
+ const settings = field.componentSettings || {};
523
+
524
+ switch (field.inputType)
525
+ {
526
+ case InputTypeOption.TEXTBOX:
527
+ case InputTypeOption.TEXTAREA:
528
+ return defaultInput(container, target, id);
529
+
530
+ case InputTypeOption.MASKEDTEXTBOX:
531
+ return createComponent(this.form.valueMaskedTextBoxId, null, {
532
+ mask: settings.mask || '____-____',
533
+ events
534
+ });
535
+
536
+ case InputTypeOption.NUMERICBOX:
537
+ return createComponent(this.form.valueNumericBoxId, null, {
538
+ precision: (!$lib.isEmpty(settings.precision)) ? parseFloat(settings.precision) : 0,
539
+ minValue: settings.minValue,
540
+ maxValue: settings.maxValue,
541
+ events
542
+ });
543
+
544
+ case InputTypeOption.SLIDER:
545
+ return createComponent(this.form.valueNumericBoxId, this.cf.getComponentType(InputTypeOption.NUMERICBOX), {
546
+ minValue: settings.minValue,
547
+ maxValue: settings.maxValue,
548
+ events
549
+ });
550
+
551
+ case InputTypeOption.DATEPICKER:
552
+ return createComponent(this.form.valueDatePickerId, null, {
553
+ dateFormat: this.form.format.dateFormat,
554
+ events: {
555
+ onChanged: (c, args) => { target.value = $lib.formatDate(args.date, 'YYYY-MM-DD'); }
556
+ }
557
+ });
558
+
559
+ case InputTypeOption.TIMEPICKER:
560
+ return createComponent(this.form.valueTimePickerId, null, {
561
+ events
562
+ });
563
+
564
+ case InputTypeOption.CHECKBOX:
565
+ case InputTypeOption.SWITCH:
566
+ case InputTypeOption.RADIO:
567
+ case InputTypeOption.COMBOBOX:
568
+ return this.#createComboBoxValueInput(container, field, target);
569
+
570
+ default:
571
+ defaultInput(container, target, id);
572
+ }
573
+ }
574
+
575
+ #createDefaultValueInput(container, target, id)
576
+ {
577
+ return this.cf.createInput({
578
+ container,
579
+ id,
580
+ type: (target.type === 'Rule') ? 'text' : 'textarea',
581
+ value: target.value || '',
582
+ placeholder: this.labels.actionValueHint,
583
+ events: { input: (e) => { target.value = e.target.value; } }
584
+ });
585
+ }
586
+
587
+ #createComboBoxValueInput(container, field, target, showSelectAll = true, forceMultiSelect = false)
588
+ {
589
+ const id = `${this.#idPrefix}${target.type}_${target.id}_input`,
590
+ multiSelect = forceMultiSelect || (field.inputType === this.InputTypeOption.CHECKBOX || field.inputType === this.InputTypeOption.SWITCH || field.componentSettings?.multiSelect == true),
591
+ comboBox = this.cf.createComboBox(container, id, this.form.optionsValueComboBoxId,
592
+ {
593
+ allowInput: false,
594
+ _disableDataBinding: true,
595
+ _mustRender: false, // we render after clone
596
+ multiSelect,
597
+ events: {
598
+ onItemSelect: (combo, args) =>
599
+ {
600
+ target.value = combo.getValue().split(',');
601
+ },
602
+ onItemDeselect: (combo, args) =>
603
+ {
604
+ target.value = combo.getValue().split(',');
605
+ }
606
+ }
607
+ });
608
+
609
+ const config = this.cf.configureComboBox(field, {}, false);
610
+
611
+ (config.itemList || []).forEach((item) =>
612
+ {
613
+ if (target.value && target.value.includes(item.value))
614
+ item.selected = true;
615
+ });
616
+
617
+ $lib.clone(comboBox, config, true, true, true, true, true, null);
618
+
619
+ if (showSelectAll)
620
+ comboBox.setSelectAllTemplate(`{checkBox} ${this.labels.comboBoxSelectAll}`);
621
+
622
+ comboBox.render();
623
+
624
+ return comboBox.element;
625
+ }
626
+
627
+ /**
628
+ * Creates an expression input (textarea + two comboboxes: fields and math functions).
629
+ * Only supported for TEXTBOX, TEXTAREA and NUMERICBOX input types.
630
+ * @private
631
+ * @param {HTMLElement} container - The container where the expression UI will be placed (value container).
632
+ * @param {componyx.UI.Form.Field} field - The target field for which the expression will be built.
633
+ * @param {Object} action - The action object being edited (will get action.expression).
634
+ * @returns {HTMLElement} The expression UI container element.
635
+ */
636
+ #createExpressionInput(container, field, action)
637
+ {
638
+ this.#clearValueContainer(container, action.id);
639
+
640
+ const id = `${this.#idPrefix}${action.id}_expression`,
641
+ exprEl = this.#createContainer(container, this.cpm.classOption.EXPRESSION);
642
+
643
+ this.cf.createInput({
644
+ container: exprEl,
645
+ type: 'textarea',
646
+ id: `${id}_input`,
647
+ value: action.expression || '',
648
+ placeholder: this.labels.actionExpressionHint,
649
+ events: {
650
+ input: (e) =>
651
+ {
652
+ const textarea = e.target,
653
+ isNumericField = field.inputType === this.InputTypeOption.NUMERICBOX;
654
+
655
+ textarea.value = textarea.value.replace(/(\{\{[^}]*\}\})|./g, (match, placeholder) =>
656
+ {
657
+ if (placeholder) // sanitize inside placeholder: allow letters, numbers, underscore, dot
658
+ {
659
+ const disallowedCharsRegex = /[^0-9+\-*/().,\s<>=!?:@_\$[\]_A-Za-z0-9'"\\]/g;
660
+ const inner = placeholder.slice(2, -2).replace(disallowedCharsRegex, '');
661
+ return '{{' + inner + '}}';
662
+ }
663
+ else if (isNumericField) // for numeric fields, only allow numeric expression chars outside placeholders
664
+ {
665
+ if (/[\d+\-*/()., ]/.test(match)) return match;
666
+ return '';
667
+ }
668
+ else
669
+ return match; // for free-text fields, leave as-is
670
+ }
671
+ );
672
+
673
+ action.expression = e.target.value;
674
+ },
675
+ keydown: (e) =>
676
+ {
677
+ if (field.inputType === this.InputTypeOption.NUMERICBOX)
678
+ {
679
+ if (!/[0-9+\-*/()., ]/.test(e.key) && !['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key))
680
+ e.preventDefault();
681
+ }
682
+ },
683
+ }
684
+ });
685
+
686
+ const controls = this.#createContainer(exprEl, this.cpm.classOption.EXPRESSION_CONTROLS);
687
+ this.#createExprFieldCombo(controls, id, action);
688
+ this.#createExprMathCombo(controls, id, action);
689
+
690
+ return exprEl;
691
+ }
692
+
693
+ /**
694
+ * Checks if the cursor is inside an existing placeholder {{...}}.
695
+ * @private
696
+ * @param {HTMLTextAreaElement} textarea
697
+ * @returns {boolean}
698
+ */
699
+ #isCursorInsidePlaceholder(textarea)
700
+ {
701
+ const pos = textarea.selectionStart,
702
+ placeholders = this.#getPlaceholders(textarea.value);
703
+
704
+ for (const t of placeholders)
705
+ {
706
+ if (pos > t.start && pos < t.end)
707
+ return true;
708
+ }
709
+ return false;
710
+ }
711
+
712
+ /**
713
+ * Gets the placeholders from the textarea.
714
+ * @private
715
+ * @param {HTMLTextAreaElement} textarea
716
+ * @returns {string[]} The placeholders.
717
+ */
718
+ #getPlaceholders(value)
719
+ {
720
+ const regex = /\{\{[^{}]+\}\}/g;
721
+ let match, placeholders = [];
722
+ while ((match = regex.exec(value)) !== null)
723
+ {
724
+ placeholders.push({ start: match.index, end: match.index + match[0].length });
725
+ }
726
+ return placeholders;
727
+ }
728
+
729
+ /**
730
+ * Creates the Expression Field ComboBox.
731
+ * @private
732
+ * @param {any} container
733
+ * @param {any} id
734
+ * @param {any} action
735
+ */
736
+ #createExprFieldCombo(container, id, action)
737
+ {
738
+ let fields = (action.parent.runVisibility === this.RunVisibilityOption.SECTION_HIDDEN || !this.form.hasSections())
739
+ ? this.form.getFields()
740
+ : this.form.getFields().filter(f =>
741
+ {
742
+ const section = this.form.getParentSection(f);
743
+ return section.selected;
744
+ }),
745
+ itemList = fields.map(f => ({ id: f.name, text: f.name }));
746
+
747
+ this.cf.createComboBox(container, `${id}_fieldCombo`, this.form.compareFieldComboBoxId, {
748
+ name: 'exprField',
749
+ placeholder: this.labels.comboBoxDefaultHint,
750
+ allowInput: false,
751
+ disableClearButton: true,
752
+ itemList,
753
+ events: {
754
+ onItemSelect: (c, args) =>
755
+ {
756
+ const textarea = $lib(`#${id}_input`),
757
+ inPlaceholder = this.#isCursorInsidePlaceholder(textarea),
758
+ insertText = inPlaceholder ? `${args.item.id}` : `{{${args.item.id}}}`;
759
+
760
+ this.#insertAtCursor(textarea, insertText);
761
+ textarea.focus();
762
+ }
763
+ }
764
+ });
765
+ }
766
+
767
+ /**
768
+ * Creates the expression Math ComboBox.
769
+ * @private
770
+ * @param {any} container
771
+ * @param {any} id
772
+ * @param {any} action
773
+ */
774
+ #createExprMathCombo(container, id, action)
775
+ {
776
+ const mathFns = ['abs', 'ceil', 'floor', 'round', 'max', 'min', 'trunc', 'sqrt', 'pow'],
777
+ itemList = mathFns.map(fn => ({ id: fn, text: `Math.${fn}()` }));
778
+
779
+ this.cf.createComboBox(container, `${id}_mathCombo`, this.form.operatorComboBoxId, {
780
+ name: 'exprMath',
781
+ placeholder: this.labels.comboBoxDefaultHint,
782
+ allowInput: false,
783
+ disableClearButton: true,
784
+ itemList,
785
+ events: {
786
+ onItemSelect: (combo, args) =>
787
+ {
788
+ const textarea = $lib(`#${id}_input`),
789
+ inPlaceholder = this.#isCursorInsidePlaceholder(textarea),
790
+ insertText = inPlaceholder ? `Math.${args.item.id}()` : `{{Math.${args.item.id}()}}`,
791
+ cursorPos = inPlaceholder ? -1 : -3; // set cursor between parenthesis
792
+
793
+ this.#insertAtCursor(textarea, insertText, cursorPos);
794
+
795
+ textarea.focus();
796
+ }
797
+ }
798
+ });
799
+ }
800
+
801
+ /**
802
+ * Inserts text at the cursor of a textarea and optionally sets cursor offset (relative to insertion end).
803
+ * @private
804
+ * @param {HTMLTextAreaElement} textarea
805
+ * @param {string} text
806
+ * @param {number} cursorOffset - offset from end of inserted text where cursor should be placed (default 0).
807
+ */
808
+ #insertAtCursor(textarea, text, cursorOffset = 0)
809
+ {
810
+ const start = textarea.selectionStart ?? textarea.value.length,
811
+ end = textarea.selectionEnd ?? textarea.value.length,
812
+ before = textarea.value.substring(0, start),
813
+ after = textarea.value.substring(end);
814
+
815
+ textarea.value = before + text + after;
816
+
817
+ // position cursor: start + inserted length + offset
818
+ let cursorPos = start + text.length + cursorOffset;
819
+
820
+ if (cursorPos < 0)
821
+ cursorPos = 0;
822
+
823
+ if (cursorPos > textarea.value.length)
824
+ cursorPos = textarea.value.length;
825
+
826
+ textarea.selectionStart = textarea.selectionEnd = cursorPos;
827
+ textarea.focus();
828
+ textarea.dispatchEvent(new Event('input')); // trigger input so bindings pick it up
829
+ }
830
+
831
+ #createFieldCombo(container, id, rule)
832
+ {
833
+ let ruleGroup = rule.parent,
834
+ fields = (ruleGroup.parent.runVisibility === this.RunVisibilityOption.SECTION_HIDDEN || !this.form.hasSections())
835
+ ? this.form.getFields()
836
+ : this.form.getFields().filter(f =>
837
+ {
838
+ const section = this.form.getParentSection(f);
839
+ return section.selected;
840
+ }),
841
+ selectedValue = rule.sourceId,
842
+ itemList = fields
843
+ .flatMap(f =>
844
+ {
845
+ if (f.inputType === this.InputTypeOption.SLIDER && f.componentSettings?.range)
846
+ {
847
+ return [
848
+ { id: `${f.id}_start`, text: `${f.name} (${this.labels.start})`, selected: f.id === selectedValue && !rule.isRangeEnd },
849
+ { id: `${f.id}_end`, text: `${f.name} (${this.labels.end})`, selected: f.id === selectedValue && rule.isRangeEnd }
850
+ ];
851
+ }
852
+ return [{ id: f.id, text: f.name, selected: f.id === selectedValue }];
853
+ }),
854
+ events = {
855
+ onItemSelect: (c, args) =>
856
+ {
857
+ const id = args.item.id;
858
+
859
+ rule.isRangeEnd = id.endsWith("_end");
860
+ rule.sourceId = id.replace(/_start$|_end$/, '');
861
+
862
+ this.#createValueInput(this.#getValueContainer(container), this.form.getField(rule.sourceId), rule);
863
+ }
864
+ };
865
+
866
+ return this.cf.createComboBox(container, id, this.form.sourceFieldComboBoxId, {
867
+ name: `sourceId`,
868
+ placeholder: this.labels.ruleFieldHint,
869
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.COMBOBOX_RULE_FIELD),
870
+ allowInput: false,
871
+ disableClearButton: true,
872
+ itemList,
873
+ events
874
+ }).element;
875
+ }
876
+
877
+ #createComparisonOperatorCombo(container, id, rule)
878
+ {
879
+ const selectedValue = rule.comparisonOperator,
880
+ itemList = Object.values(this.#formCtor.RuleComparisonOperatorOption)
881
+ .map(value => ({
882
+ id: value,
883
+ text: this.labels[`operator_${value}`],
884
+ selected: value === selectedValue
885
+ })),
886
+ events = {
887
+ onItemSelect: (c, args) =>
888
+ {
889
+ const valueContainer = this.#getValueContainer(container);
890
+ rule.comparisonOperator = args.item.id;
891
+
892
+ if (['empty', 'not_empty', 'visible', 'hidden', 'enabled', 'disabled', 'required', 'optional'].includes(args.item.id))
893
+ {
894
+ this.#toggleContainer(valueContainer);
895
+ }
896
+ else
897
+ {
898
+ this.#toggleContainer(valueContainer, false);
899
+ }
900
+ }
901
+ };
902
+
903
+ return this.cf.createComboBox(container, id, this.form.comparisonComboBoxId, {
904
+ name: 'comparisonOperator',
905
+ placeholder: this.labels.ruleComparisonOperatorHint,
906
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.COMBOBOX_RULE_COMPARISON_OPERATOR),
907
+ allowInput: false,
908
+ disableClearButton: true,
909
+ itemList,
910
+ events
911
+ }).element;
912
+ }
913
+
914
+ #createLogicalOperatorCombo(container, id, target, events)
915
+ {
916
+ const itemList = [
917
+ { id: 'AND', text: this.labels.operator_and },
918
+ { id: 'OR', text: this.labels.operator_or },
919
+ { id: 'X', text: `${this.labels.removeItemPrefix.trim()}`, hasIcon: true, cssClassIcon: 'icon ico-bin' }];
920
+
921
+ itemList.forEach(item =>
922
+ {
923
+ item.selected = (item.id === target?.logicalOperator);
924
+ });
925
+
926
+ return this.cf.createComboBox(container, id, this.form.logicalOperatorComboBoxId, {
927
+ name: 'logicalOperator',
928
+ placeholder: this.labels.comboBoxDefaultHint,
929
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.COMBOBOX_LOGICAL_OPERATOR),
930
+ allowInput: false,
931
+ disableClearButton: true,
932
+ itemList,
933
+ events
934
+ }).element;
935
+ }
936
+
937
+ #createTriggerCombo(container, id, ruleCase)
938
+ {
939
+ const selectedValue = ruleCase.trigger || this.#formCtor.RuleCaseTriggerOption.ALWAYS,
940
+ itemList = Object.values(this.#formCtor.RuleCaseTriggerOption)
941
+ .map(value => ({
942
+ id: value,
943
+ text: this.labels[`trigger_${value}`],
944
+ selected: value === selectedValue
945
+ })),
946
+ events = {
947
+ onItemSelect: (c, args) =>
948
+ {
949
+ ruleCase.trigger = args.item.id;
950
+ }
951
+ };
952
+
953
+ return this.cf.createComboBox(container, id, this.form.triggerComboBoxId, {
954
+ name: 'trigger',
955
+ allowInput: false,
956
+ disableClearButton: true,
957
+ itemList,
958
+ events
959
+ }).element;
960
+ }
961
+
962
+ #createRunVisibilityCombo(container, id, ruleCase)
963
+ {
964
+ const selectedValue = ruleCase.runVisibility || this.RunVisibilityOption.VISIBLE,
965
+ itemList = Object.values(this.RunVisibilityOption)
966
+ .map(value => ({
967
+ id: value,
968
+ text: this.labels[`run_visibility_${value}`],
969
+ selected: value === selectedValue
970
+ })),
971
+ events = {
972
+ onItemSelect: (c, args) =>
973
+ {
974
+ ruleCase.runVisibility = args.item.id;
975
+ }
976
+ };
977
+
978
+ return this.cf.createComboBox(container, id, this.form.runVisibilityComboBoxId, {
979
+ name: 'runVisibility',
980
+ allowInput: false,
981
+ disableClearButton: true,
982
+ itemList,
983
+ events
984
+ }).element;
985
+ }
986
+
987
+ #createActionCombo(container, id, action)
988
+ {
989
+ const itemList = [],
990
+ selectedValue = action.runtimeActionId,
991
+ runtimeActions = this.form.runtimeActions,
992
+ target = this.#targetField,
993
+ InputTypeOption = this.InputTypeOption,
994
+ isOptionField = [InputTypeOption.CHECKBOX, InputTypeOption.SWITCH, InputTypeOption.RADIO, InputTypeOption.COMBOBOX].includes(target.inputType),
995
+ allowedExprTypes = [InputTypeOption.TEXTBOX, InputTypeOption.TEXTAREA, InputTypeOption.NUMERICBOX],
996
+ events = {
997
+ onItemSelect: (c, args) =>
998
+ {
999
+ action.runtimeActionId = args.item.id;
1000
+
1001
+ const ra = runtimeActions[action.runtimeActionId],
1002
+ valueContainer = this.#getValueContainer(container, this.cpm.classOption.ACTION_VALUE),
1003
+ targetCombo = $UI.store[`${id}_target_input`];
1004
+
1005
+ if (targetCombo)
1006
+ {
1007
+ if (targetCombo.renderState === $base.static.RenderState.RENDERED)
1008
+ {
1009
+ if (ra.supportsFieldSet)
1010
+ targetCombo.enable();
1011
+ else
1012
+ targetCombo.disable();
1013
+ }
1014
+ else
1015
+ {
1016
+ targetCombo.disabled = !ra.supportsFieldSet;
1017
+ }
1018
+ }
1019
+
1020
+ if (isOptionField && (action.runtimeActionId === 'disable' || action.runtimeActionId === 'enable'))
1021
+ {
1022
+ this.#toggleContainer(valueContainer, false);
1023
+ this.#clearValueContainer(valueContainer, target.id);
1024
+ this.#createComboBoxValueInput(valueContainer, target, action, true, true);
1025
+ }
1026
+ else if (ra.setValue)
1027
+ {
1028
+ this.#toggleContainer(valueContainer, false);
1029
+
1030
+ if (ra.id === 'show_section') // special case, show combo-box with sections
1031
+ this.#createSectionsValueCombo(valueContainer, target, action);
1032
+ else
1033
+ this.#createValueInput(valueContainer, target, action);
1034
+ }
1035
+ else if (ra.setExpression)
1036
+ {
1037
+ this.#toggleContainer(valueContainer, false);
1038
+ this.#createExpressionInput(valueContainer, target, action);
1039
+ }
1040
+ else
1041
+ this.#clearValueContainer(valueContainer, action.id);
1042
+ },
1043
+ onClear: () =>
1044
+ {
1045
+ action.runtimeActionId = null;
1046
+ this.#clearValueContainer(this.#getValueContainer(container, this.cpm.classOption.ACTION_VALUE), action.id);
1047
+ }
1048
+ };
1049
+
1050
+ Object.entries(runtimeActions).forEach(([id, a]) =>
1051
+ {
1052
+ if (((a.setValue || a.setExpression) && target.inputType === InputTypeOption.FILEUPLOAD) ||
1053
+ (a.setExpression && (!allowedExprTypes.includes(target.inputType))))
1054
+ return;
1055
+
1056
+ if (id === 'cancel_submit' && action.parent?.trigger !== this.#formCtor.RuleCaseTriggerOption.BEFORE_SUBMIT)
1057
+ return;
1058
+
1059
+ if (id === 'show_section' && !this.form.hasSections() && id !== selectedValue)
1060
+ return;
1061
+
1062
+ itemList.push({
1063
+ id: id,
1064
+ text: a.label,
1065
+ tooltip: a.description || undefined,
1066
+ selected: id === selectedValue
1067
+ });
1068
+ });
1069
+
1070
+ return this.cf.createComboBox(container, id + '_action_input', this.form.actionComboBoxId, {
1071
+ name: 'action',
1072
+ placeholder: this.labels.comboBoxDefaultHint,
1073
+ cssClass: this.cpm.getCssClass(this.cpm.classOption.COMBOBOX_ACTION),
1074
+ allowInput: false,
1075
+ tooltipManagerId: this.form.tooltipManager.id,
1076
+ itemList,
1077
+ events
1078
+ }).element;
1079
+ }
1080
+
1081
+ #createActionTargetCombo(container, id, action)
1082
+ {
1083
+ const field = this.#targetField,
1084
+ runtimeActions = this.form.runtimeActions,
1085
+ fieldSet = field.parent,
1086
+ selectedValue = action.targetFieldSetId || field.id,
1087
+ itemList = [
1088
+ {
1089
+ id: field.id,
1090
+ text: `${this.labels.applyToItemPrefix} ${this.labels.field} (${field.name})`,
1091
+ selected: selectedValue === field.id
1092
+ },
1093
+ {
1094
+ id: `fieldset_${fieldSet.id}`,
1095
+ text: `${this.labels.applyToItemPrefix} ${this.labels.fieldSet} (${fieldSet.id})`,
1096
+ selected: selectedValue === fieldSet.id
1097
+ }
1098
+ ];
1099
+
1100
+ const combo = this.cf.createComboBox(container, `${id}_target_input`, this.form.actionTargetComboBoxId, {
1101
+ name: 'actionTarget',
1102
+ placeholder: this.labels.comboBoxDefaultHint,
1103
+ allowInput: false,
1104
+ disableClearButton: true,
1105
+ disabled: (!action.runtimeActionId) ? true : !runtimeActions[action.runtimeActionId].supportsFieldSet,
1106
+ itemList,
1107
+ events: {
1108
+ onItemSelect: (c, args) =>
1109
+ {
1110
+ const selectedId = args.item.id,
1111
+ valueContainer = this.#getValueContainer(container, this.cpm.classOption.ACTION_VALUE);
1112
+
1113
+ if (selectedId.startsWith('fieldset_'))
1114
+ {
1115
+ action.targetFieldSetId = selectedId.replace('fieldset_', '');
1116
+ this.#toggleContainer(valueContainer, true);
1117
+ }
1118
+ else
1119
+ {
1120
+ action.targetFieldSetId = null;
1121
+ this.#toggleContainer(valueContainer, false);
1122
+ }
1123
+ }
1124
+ }
1125
+ });
1126
+
1127
+ return combo.element;
1128
+ }
1129
+
1130
+ #toggleContainer(container, disabled = true)
1131
+ {
1132
+ container.inert = disabled;
1133
+
1134
+ if (disabled)
1135
+ container.classList.add(this.cpm.getCssClass(this.cpm.classOption.DISABLED));
1136
+ else
1137
+ container.classList.remove(this.cpm.getCssClass(this.cpm.classOption.DISABLED));
1138
+ }
1139
+
1140
+ #getValueContainer(container, cssClass = this.cpm.classOption.RULE_VALUE)
1141
+ {
1142
+ return container.querySelector(`.${this.cpm.getCssClass(cssClass)}`);
1143
+ }
1144
+
1145
+ #clearValueContainer(container, id)
1146
+ {
1147
+ const inputId = `${this.#idPrefix}${id}_input`;
1148
+
1149
+ if ($UI.store[inputId]?.renderState > 0)
1150
+ $UI.store[inputId].destroy();
1151
+
1152
+ container.innerHTML = '';
1153
+ }
1154
+ };
1155
+
1156
+ var RulesPanel = componyx.UI.form_modules.RulesPanel;
1157
+
1158
+ export { RulesPanel as default };