@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.
- package/AppSkin/AppSkin.css +1268 -0
- package/AppSkin/AppSkin.js +2748 -0
- package/AppSkin/Themes/Default.css +241 -0
- package/Base/Base.js +3407 -0
- package/Base/ComponentIcons.css +66 -0
- package/Base/Font/ComponentIcons.woff +0 -0
- package/Base/Font/UI.woff +0 -0
- package/Base/Library.js +8432 -0
- package/Base/NavigationManager.js +462 -0
- package/Base/ResponsiveTemplates.js +371 -0
- package/Base/Sanitizer.js +288 -0
- package/Base/Themes/Default.css +255 -0
- package/Base/UI.css +618 -0
- package/Base/UIResourcePath.js +6 -0
- package/Bindary/Bindary.js +2011 -0
- package/Bindary/Modules/Connection.js +216 -0
- package/Bindary/Modules/Core.js +854 -0
- package/Bindary/Modules/DataUpdater.js +351 -0
- package/Bindary/Modules/ExpressionEngine.js +347 -0
- package/Bindary/Modules/ProxyManager.js +482 -0
- package/Bindary/Modules/Renderer.js +605 -0
- package/Bindary/Modules/Router.js +291 -0
- package/Bindary/Modules/Scroller.js +89 -0
- package/Bindary/Modules/Template.js +1273 -0
- package/BookmarkSpy/BookmarkSpy.js +430 -0
- package/Box/Box.css +36 -0
- package/Box/Box.js +1685 -0
- package/Button/Button.css +470 -0
- package/Button/Button.js +1398 -0
- package/Button/Themes/Default.css +200 -0
- package/Carousel/Carousel.css +273 -0
- package/Carousel/Carousel.js +607 -0
- package/ColorButton/ColorButton.css +39 -0
- package/ColorButton/ColorButton.js +336 -0
- package/ColorPicker/ColorPicker.css +359 -0
- package/ColorPicker/ColorPicker.js +972 -0
- package/ComboBox/ComboBox.css +357 -0
- package/ComboBox/ComboBox.js +2342 -0
- package/ComboBox/Themes/Default.css +23 -0
- package/DataPager/DataPager.css +72 -0
- package/DataPager/DataPager.js +674 -0
- package/DatePicker/DatePicker.css +227 -0
- package/DatePicker/DatePicker.js +1531 -0
- package/DatePicker/Themes/Default.css +26 -0
- package/Dialog/Dialog.css +106 -0
- package/Dialog/Dialog.js +572 -0
- package/Dialog/Themes/Default.css +10 -0
- package/Editor/Data/Emoji.json +8285 -0
- package/Editor/Data/Special.json +1042 -0
- package/Editor/Editor.css +1562 -0
- package/Editor/Editor.js +4058 -0
- package/Editor/Font/Editor.woff +0 -0
- package/Editor/Modules/ComponentFactory.js +565 -0
- package/Editor/Modules/ContentManager.js +748 -0
- package/Editor/Modules/DialogManager.js +1234 -0
- package/Editor/Modules/EventManager.js +458 -0
- package/Editor/Modules/Format.js +950 -0
- package/Editor/Modules/History.js +191 -0
- package/Editor/Modules/LayoutState.js +379 -0
- package/Editor/Modules/ListManager.js +296 -0
- package/Editor/Modules/MenuManager.js +388 -0
- package/Editor/Modules/NodeManager.js +681 -0
- package/Editor/Modules/ParagraphButtons.js +122 -0
- package/Editor/Modules/Resizer.js +176 -0
- package/Editor/Modules/SelectionRange.js +564 -0
- package/Editor/Modules/SourceViewBuilder.js +212 -0
- package/Editor/Modules/SymbolPicker.js +208 -0
- package/Editor/Modules/Table.js +2327 -0
- package/Editor/Modules/TableDialog.js +757 -0
- package/Editor/Modules/Toolbar.js +375 -0
- package/Editor/Modules/TooltipMenu.js +125 -0
- package/Editor/Modules/Utility.js +131 -0
- package/Editor/Themes/Default.css +204 -0
- package/FileUpload/FileUpload.css +156 -0
- package/FileUpload/FileUpload.js +898 -0
- package/FileUpload/Themes/Default.css +17 -0
- package/Form/Form.css +1934 -0
- package/Form/Form.js +4943 -0
- package/Form/Modules/ActionManager.js +368 -0
- package/Form/Modules/BuildPanelManager.js +319 -0
- package/Form/Modules/ComponentFactory.js +468 -0
- package/Form/Modules/ComponentPanel.js +265 -0
- package/Form/Modules/ConfigPanelManager.js +685 -0
- package/Form/Modules/DataBinder.js +602 -0
- package/Form/Modules/DataObserver.js +717 -0
- package/Form/Modules/Draggable.js +756 -0
- package/Form/Modules/OptionsPanel.js +342 -0
- package/Form/Modules/Renderer.js +1889 -0
- package/Form/Modules/RuleEngine.js +585 -0
- package/Form/Modules/RulesPanel.js +1158 -0
- package/Form/Modules/Types.js +1709 -0
- package/Form/Modules/ValidationManager.js +194 -0
- package/Form/Themes/Default.css +216 -0
- package/FormField/FormField.css +468 -0
- package/FormField/FormField.js +576 -0
- package/FormField/Themes/Default.css +99 -0
- package/Grid/Grid.css +544 -0
- package/Grid/Grid.js +3761 -0
- package/Grid/Themes/Default.css +43 -0
- package/LICENSE +126 -0
- package/MaskedTextBox/MaskedTextBox.css +28 -0
- package/MaskedTextBox/MaskedTextBox.js +642 -0
- package/MaskedTextBox/Themes/Default.css +6 -0
- package/Menu/Menu.css +328 -0
- package/Menu/Menu.js +2386 -0
- package/Menu/Themes/Default.css +24 -0
- package/NumericBox/NumericBox.css +120 -0
- package/NumericBox/NumericBox.js +984 -0
- package/PanelBar/PanelBar.css +123 -0
- package/PanelBar/PanelBar.js +505 -0
- package/PanelBar/Themes/Default.css +51 -0
- package/PieChart/PieChart.css +95 -0
- package/PieChart/PieChart.js +820 -0
- package/PieChart/Themes/Default.css +9 -0
- package/README.md +178 -3
- package/Slider/Slider.css +348 -0
- package/Slider/Slider.js +1306 -0
- package/Slider/Themes/Default.css +44 -0
- package/TabStrip/TabStrip.css +52 -0
- package/TabStrip/TabStrip.js +362 -0
- package/TabStrip/Themes/Default.css +35 -0
- package/TimePicker/Themes/Default.css +30 -0
- package/TimePicker/TimePicker.css +253 -0
- package/TimePicker/TimePicker.js +1485 -0
- package/TooltipManager/Themes/Default.css +21 -0
- package/TooltipManager/TooltipManager.css +227 -0
- package/TooltipManager/TooltipManager.js +791 -0
- package/Validator/Validator.css +54 -0
- package/Validator/Validator.js +2508 -0
- package/componyx.html-data.json +619 -0
- package/index.js +27 -0
- package/package.json +43 -4
- package/types/AppSkin.d.ts +842 -0
- package/types/Base.d.ts +1034 -0
- package/types/Bindary.d.ts +1196 -0
- package/types/BookmarkSpy.d.ts +112 -0
- package/types/Box.d.ts +374 -0
- package/types/Button.d.ts +455 -0
- package/types/Carousel.d.ts +136 -0
- package/types/ColorButton.d.ts +120 -0
- package/types/ColorPicker.d.ts +225 -0
- package/types/ComboBox.d.ts +507 -0
- package/types/DataPager.d.ts +152 -0
- package/types/DatePicker.d.ts +354 -0
- package/types/Dialog.d.ts +160 -0
- package/types/Editor.d.ts +1289 -0
- package/types/FileUpload.d.ts +206 -0
- package/types/Form.d.ts +3078 -0
- package/types/FormField.d.ts +204 -0
- package/types/Grid.d.ts +911 -0
- package/types/Library.d.ts +1899 -0
- package/types/MaskedTextBox.d.ts +91 -0
- package/types/Menu.d.ts +577 -0
- package/types/NumericBox.d.ts +310 -0
- package/types/PanelBar.d.ts +223 -0
- package/types/PieChart.d.ts +224 -0
- package/types/ResponsiveTemplates.d.ts +108 -0
- package/types/Slider.d.ts +227 -0
- package/types/TabStrip.d.ts +185 -0
- package/types/TimePicker.d.ts +265 -0
- package/types/TooltipManager.d.ts +178 -0
- package/types/UI.d.ts +29 -0
- package/types/Validator.d.ts +653 -0
- package/types/index.d.ts +59 -0
- package/ui.css +11783 -0
- package/ui.js +79477 -0
- package/ui.min.css +138 -0
- package/ui.min.js +264 -0
- package/ui.min.js.map +1 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
|
|
2
|
+
await (async function ()
|
|
3
|
+
{
|
|
4
|
+
await import(`${$UI.getScriptResourcePath('Base.Bindary.Core')}`);
|
|
5
|
+
const core = componyx.bindary_modules.core;
|
|
6
|
+
|
|
7
|
+
const dataUpdater = componyx.bindary_modules.dataUpdater =
|
|
8
|
+
{
|
|
9
|
+
initUpdateRules(rules, orgRules)
|
|
10
|
+
{
|
|
11
|
+
rules = (rules) ? ($lib.isArray(rules)) ? rules : [rules] : [{}];
|
|
12
|
+
|
|
13
|
+
if (orgRules && !$lib.isEmpty(orgRules[0]))
|
|
14
|
+
rules = orgRules.concat(rules);
|
|
15
|
+
|
|
16
|
+
return rules;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
update(dataMsg, mustUpdateView)
|
|
20
|
+
{
|
|
21
|
+
if (dataMsg)
|
|
22
|
+
{
|
|
23
|
+
if (core.fireEvent('preDataUpdate', [dataMsg]) === false) return;
|
|
24
|
+
|
|
25
|
+
let update = dataUpdater.updateData.bind(dataUpdater, dataMsg);
|
|
26
|
+
|
|
27
|
+
core.autoUpdateView = false;
|
|
28
|
+
|
|
29
|
+
// data update
|
|
30
|
+
if (core.dataLocks['#'])
|
|
31
|
+
{
|
|
32
|
+
core.dataLocks['#'].push(update); // store in cache to postpone data-update until unlock is called
|
|
33
|
+
}
|
|
34
|
+
else
|
|
35
|
+
{
|
|
36
|
+
update();
|
|
37
|
+
core.fireEvent('postDataUpdate');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
core.autoUpdateView = true;
|
|
41
|
+
|
|
42
|
+
// detect error and fire possible callbacks
|
|
43
|
+
if (!dataUpdater.advance(dataMsg))
|
|
44
|
+
{
|
|
45
|
+
if (core.routeCallbackId)
|
|
46
|
+
core.postRender(); // load route cancelled, but call postRender for possible event listeners
|
|
47
|
+
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (mustUpdateView !== false) $bindary.updateView(false);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
advance(dataMsg)
|
|
56
|
+
{
|
|
57
|
+
let advance = true, callbackResult, msg;
|
|
58
|
+
|
|
59
|
+
if (!$lib.isArray(dataMsg)) dataMsg = [dataMsg];
|
|
60
|
+
|
|
61
|
+
for (let index = 0; index < dataMsg.length; ++index)
|
|
62
|
+
{
|
|
63
|
+
msg = dataMsg[index];
|
|
64
|
+
|
|
65
|
+
if (msg.isError)
|
|
66
|
+
{
|
|
67
|
+
if (core.fireEvent('dataError', [msg]) === false)
|
|
68
|
+
advance = false;
|
|
69
|
+
|
|
70
|
+
core.clearPendingUpdate();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (msg.callbackId && msg.callbackId !== core.routeCallbackId)
|
|
74
|
+
{
|
|
75
|
+
if (core.callbacks[msg.callbackId])
|
|
76
|
+
{
|
|
77
|
+
callbackResult = core.callbacks[msg.callbackId](msg);
|
|
78
|
+
delete core.callbacks[msg.callbackId];
|
|
79
|
+
|
|
80
|
+
if (callbackResult === false) advance = false;
|
|
81
|
+
} else advance = false; // callback does not exist anymore
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (core.dataLocks['#']) advance = false;
|
|
86
|
+
|
|
87
|
+
return advance;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
updateData(dataMsg)
|
|
91
|
+
{
|
|
92
|
+
if (!$lib.isArray(dataMsg)) dataMsg = [dataMsg];
|
|
93
|
+
|
|
94
|
+
for (let index = 0; index < dataMsg.length; ++index)
|
|
95
|
+
{
|
|
96
|
+
let data = dataMsg[index].data;
|
|
97
|
+
|
|
98
|
+
if (data)
|
|
99
|
+
{
|
|
100
|
+
dataUpdater.prepareContainer(core.dataLabel[0], data);
|
|
101
|
+
|
|
102
|
+
if ($lib.isEmpty(dataMsg[index].routeIndex) || dataMsg[index].routeIndex === $bindary.routeIndex)
|
|
103
|
+
{
|
|
104
|
+
dataUpdater.prepareContainer(core.dataLabel[1], data);
|
|
105
|
+
core.cache.routeData[$bindary.routeIndex] = $bindary.routeData;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if ($lib.isEmpty(dataMsg[index].routePath) || dataMsg[index].routePath === $bindary.routePath)
|
|
109
|
+
{
|
|
110
|
+
dataUpdater.prepareContainer(core.dataLabel[2], data);
|
|
111
|
+
core.cache.viewData[$bindary.routePath] = $bindary.viewData;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
prepareContainer(name, data)
|
|
118
|
+
{
|
|
119
|
+
let container = data[name];
|
|
120
|
+
if (!container) return;
|
|
121
|
+
|
|
122
|
+
let update = dataUpdater.updateContainer.bind(dataUpdater, name, container, data[name + 'Rules']);
|
|
123
|
+
|
|
124
|
+
if (core.dataLocks[name])
|
|
125
|
+
{
|
|
126
|
+
core.dataLocks[name].push(update);
|
|
127
|
+
}
|
|
128
|
+
else
|
|
129
|
+
{
|
|
130
|
+
update();
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
updateContainer(name, sourceContainer, rulesContainer)
|
|
135
|
+
{
|
|
136
|
+
let target, source, rules, path, curTargetContainer, update,
|
|
137
|
+
targetContainer = $bindary[name];
|
|
138
|
+
|
|
139
|
+
for (let key in sourceContainer)
|
|
140
|
+
{
|
|
141
|
+
curTargetContainer = targetContainer;
|
|
142
|
+
source = sourceContainer[key];
|
|
143
|
+
rules = rulesContainer ? rulesContainer[key] : null;
|
|
144
|
+
path = rules ? rules.path : null;
|
|
145
|
+
|
|
146
|
+
if (rules)
|
|
147
|
+
{
|
|
148
|
+
if (!$lib.isEmpty(rules.routePath) && name === core.dataLabel[2])
|
|
149
|
+
{
|
|
150
|
+
curTargetContainer = core.cache.viewData[rules.routePath];
|
|
151
|
+
}
|
|
152
|
+
else if (!$lib.isEmpty(rules.routeIndex) && name === core.dataLabel[1])
|
|
153
|
+
{
|
|
154
|
+
curTargetContainer = core.cache.routeData[rules.routeIndex];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
target = path ? dataUpdater.getTargetModel(path, curTargetContainer) : { key, obj: curTargetContainer };
|
|
159
|
+
path = name + core.dksc + (path || key);
|
|
160
|
+
|
|
161
|
+
update = dataUpdater.updateModel.bind(dataUpdater, target.key, target.obj, source, rules, path);
|
|
162
|
+
|
|
163
|
+
if (core.dataLocks[path])
|
|
164
|
+
{
|
|
165
|
+
core.dataLocks[path].push(update);
|
|
166
|
+
}
|
|
167
|
+
else
|
|
168
|
+
{
|
|
169
|
+
update();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
updateModel(key, target, source, rules, path)
|
|
175
|
+
{
|
|
176
|
+
let isArray = $lib.isArray(source),
|
|
177
|
+
defaultUpdateRule = $bindary.defaultUpdateRule;
|
|
178
|
+
|
|
179
|
+
core.observed = false;
|
|
180
|
+
|
|
181
|
+
if (!(source == null && rules && rules.ignoreNullValues))
|
|
182
|
+
{
|
|
183
|
+
if (!rules)
|
|
184
|
+
rules = { update: defaultUpdateRule };
|
|
185
|
+
else if ($lib.isEmpty(rules.update))
|
|
186
|
+
rules.update = defaultUpdateRule;
|
|
187
|
+
|
|
188
|
+
const equalityCheck = (rules && typeof rules.equalityCheck === 'boolean') ? rules.equalityCheck : $bindary.equalityCheck;
|
|
189
|
+
|
|
190
|
+
if (equalityCheck && dataUpdater.deepEqual(target[key], source))
|
|
191
|
+
return;
|
|
192
|
+
|
|
193
|
+
if ($lib.isEmpty(target[key]) || rules.update === false || !(isArray || $lib.isPlainObject(source)))
|
|
194
|
+
{
|
|
195
|
+
target[key] = source;
|
|
196
|
+
}
|
|
197
|
+
else if (isArray)
|
|
198
|
+
{
|
|
199
|
+
dataUpdater.updateArray(target[key], source, rules);
|
|
200
|
+
}
|
|
201
|
+
else
|
|
202
|
+
{
|
|
203
|
+
$lib.clone(target[key], source, true, true, true, null, null, null, rules.omitKeys);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!core.observed)
|
|
207
|
+
dataUpdater.dataChange(path, rules, isArray);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
deepEqual(a, b, seen = new WeakMap())
|
|
212
|
+
{
|
|
213
|
+
if (a === b) return true;
|
|
214
|
+
|
|
215
|
+
if (a instanceof Date && b instanceof Date)
|
|
216
|
+
return a.getTime() === b.getTime();
|
|
217
|
+
|
|
218
|
+
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null)
|
|
219
|
+
return false;
|
|
220
|
+
|
|
221
|
+
if (seen.has(a))
|
|
222
|
+
{
|
|
223
|
+
if (seen.get(a).has(b))
|
|
224
|
+
{
|
|
225
|
+
return true; // already compared these objects
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else
|
|
229
|
+
{
|
|
230
|
+
seen.set(a, new WeakSet());
|
|
231
|
+
}
|
|
232
|
+
seen.get(a).add(b);
|
|
233
|
+
|
|
234
|
+
let keysA = Object.keys(a);
|
|
235
|
+
let keysB = Object.keys(b);
|
|
236
|
+
|
|
237
|
+
if (keysA.length !== keysB.length) return false;
|
|
238
|
+
|
|
239
|
+
for (let key of keysA)
|
|
240
|
+
{
|
|
241
|
+
if (!keysB.includes(key)) return false;
|
|
242
|
+
if (!dataUpdater.deepEqual(a[key], b[key], seen)) return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return true;
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
getTargetModel(path, target)
|
|
249
|
+
{
|
|
250
|
+
let pathKey, index;
|
|
251
|
+
|
|
252
|
+
while (path)
|
|
253
|
+
{
|
|
254
|
+
index = path.indexOf(core.dksc);
|
|
255
|
+
pathKey = index > -1 ? path.substr(0, index) : path;
|
|
256
|
+
|
|
257
|
+
if (index > -1)
|
|
258
|
+
{
|
|
259
|
+
target = target[pathKey] = target[pathKey] || {};
|
|
260
|
+
path = index > -1 ? path.substr(index + core.dksc.length) : null;
|
|
261
|
+
} else
|
|
262
|
+
{
|
|
263
|
+
path = null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return { key: pathKey, obj: target };
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
dataChange(path, rules, isArray)
|
|
271
|
+
{
|
|
272
|
+
if (!core.dataChanges[path]) core.dataChanges[path] = [];
|
|
273
|
+
if (rules && isArray) core.dataChanges[path].push(rules);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
updateArray(target, source, rules)
|
|
277
|
+
{
|
|
278
|
+
// the rules are required for updating repeat-elements within the view
|
|
279
|
+
// removeIndex, addIndex and addCount must be updated in the rules because an array can be updated multiple times (same target, different source array)
|
|
280
|
+
|
|
281
|
+
if (!rules)
|
|
282
|
+
rules = {};
|
|
283
|
+
|
|
284
|
+
if ($lib.isEmpty(rules.removeIndex) && rules.removeCount > 0)
|
|
285
|
+
rules.removeIndex = target.length - rules.removeCount; // set correct index when removeCount is specified
|
|
286
|
+
|
|
287
|
+
if (rules.removeCount > 0)
|
|
288
|
+
target.splice(rules.removeIndex, rules.removeCount);
|
|
289
|
+
|
|
290
|
+
if ($lib.isEmpty(rules.addCount))
|
|
291
|
+
rules.addCount = 0;
|
|
292
|
+
|
|
293
|
+
if (rules.updateKey)
|
|
294
|
+
{
|
|
295
|
+
let updateKey = rules.updateKey,
|
|
296
|
+
add = [];
|
|
297
|
+
|
|
298
|
+
rules.keys = [];
|
|
299
|
+
|
|
300
|
+
if (rules.removeByKeyValues)
|
|
301
|
+
{
|
|
302
|
+
$lib.each(rules.removeByKeyValues, function (value)
|
|
303
|
+
{
|
|
304
|
+
let index = $lib.indexOf(target, function (i) { return (i[updateKey] == value); });
|
|
305
|
+
|
|
306
|
+
if (index > -1)
|
|
307
|
+
{
|
|
308
|
+
rules.keys.push(index.toString());
|
|
309
|
+
target.splice(index, 1);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
$lib.each(source, function (srcItem)
|
|
315
|
+
{
|
|
316
|
+
let index = $lib.indexOf(target, function (i) { return (i[updateKey] === srcItem[updateKey]); });
|
|
317
|
+
|
|
318
|
+
if (index > -1)
|
|
319
|
+
{
|
|
320
|
+
rules.keys.push(index.toString());
|
|
321
|
+
target[index] = srcItem;
|
|
322
|
+
}
|
|
323
|
+
else
|
|
324
|
+
add.push(srcItem);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (add.length)
|
|
328
|
+
dataUpdater.extendTargetArray(target, add, rules);
|
|
329
|
+
}
|
|
330
|
+
else
|
|
331
|
+
dataUpdater.extendTargetArray(target, source, rules);
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
extendTargetArray(target, source, rules)
|
|
335
|
+
{
|
|
336
|
+
rules.addCount = source.length;
|
|
337
|
+
|
|
338
|
+
if (rules.addCount > 0 && $lib.isEmpty(rules.addIndex))
|
|
339
|
+
target.push.apply(target, source);
|
|
340
|
+
else if (rules.addCount > 0)
|
|
341
|
+
target.splice.apply(target, [rules.addIndex, 0].concat(source));
|
|
342
|
+
|
|
343
|
+
if ($lib.isEmpty(rules.addIndex) && rules.addCount > 0)
|
|
344
|
+
rules.addIndex = target.length - rules.addCount;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
})();
|
|
348
|
+
|
|
349
|
+
var DataUpdater = componyx.bindary_modules.dataUpdater;
|
|
350
|
+
|
|
351
|
+
export { DataUpdater as default };
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
|
|
2
|
+
await (async function ()
|
|
3
|
+
{
|
|
4
|
+
await import(`${$UI.getScriptResourcePath('Base.Bindary.Core')}`);
|
|
5
|
+
componyx.bindary_modules.core;
|
|
6
|
+
|
|
7
|
+
componyx.bindary_modules = componyx.bindary_modules || {};
|
|
8
|
+
componyx.bindary_modules.ExpressionEngine = class ExpressionEngine
|
|
9
|
+
{
|
|
10
|
+
constructor()
|
|
11
|
+
{
|
|
12
|
+
this.resolveRegex = /^[\$A-Za-z_][\w.@\[\]]*(\(\))?/;
|
|
13
|
+
this.allowedCharsRegex = /^[|&+\-*/().,\s<>=!?:@_\$[\]{}_A-Za-z0-9'".]+$/;
|
|
14
|
+
this.allowedMathFunctions = ['abs', 'ceil', 'floor', 'round', 'max', 'min', 'trunc', 'sqrt', 'pow', 'log', 'exp', 'random'];
|
|
15
|
+
this.allowedStringFunctions = ['toLowerCase', 'toUpperCase', 'trim', 'replace', 'includes', 'indexOf', 'startsWith', 'endsWith', 'slice', 'substring'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Compiles an expression string containing `{{ }}` placeholders into a function.
|
|
20
|
+
* Supports:
|
|
21
|
+
* - Multiple `{{ ... }}` placeholders within the string.
|
|
22
|
+
* - Expressions inside `{{ }}`, including basic operators, custom resolver tokens, and a whitelist of Math and String methods.
|
|
23
|
+
* - Literal text outside placeholders is preserved as string literals.
|
|
24
|
+
*
|
|
25
|
+
* Supported Math methods: 'abs', 'ceil', 'floor', 'round', 'max', 'min', 'trunc', 'sqrt', 'pow', 'log', 'exp', 'random'
|
|
26
|
+
* Supported String methods: 'toLowerCase', 'toUpperCase', 'trim', 'replace', 'includes', 'indexOf', 'startsWith', 'endsWith', 'slice', 'substring'
|
|
27
|
+
* @private
|
|
28
|
+
* @param {string} expr - The expression string to compile.
|
|
29
|
+
* @param {function(string, number): any} tokenHandler - Invoked at compile time for each custom token found.
|
|
30
|
+
* Receives the token string as first argument and the start index of the token in the source string as second argument.
|
|
31
|
+
* @returns {function(function(string): any): any} A function that takes a resolver callback to provide token values at runtime.
|
|
32
|
+
* @example
|
|
33
|
+
* const exprFn = evaluate("Hello {{ String.toUpperCase(name) + '!' }} Your high score is {{ Math.max(a, b) + ' points' }}.");
|
|
34
|
+
* const result = exprFn(token => {
|
|
35
|
+
* const values = { name: "Joe", a: 5, b: 10 };
|
|
36
|
+
* return values[token];
|
|
37
|
+
* });
|
|
38
|
+
* console.log(result); // "Hello JOE! Your high score is 10 points."
|
|
39
|
+
*/
|
|
40
|
+
evaluate(expr, tokenHandler)
|
|
41
|
+
{
|
|
42
|
+
const allowedCharsRegex = this.allowedCharsRegex,
|
|
43
|
+
allowedMathFunctions = this.allowedMathFunctions,
|
|
44
|
+
allowedStringFunctions = this.allowedStringFunctions;
|
|
45
|
+
|
|
46
|
+
// --------------------------------------------------------------
|
|
47
|
+
// Extract balanced parentheses content, used for Math/Strings
|
|
48
|
+
// --------------------------------------------------------------
|
|
49
|
+
const extractParentheses = (text, startIndex) =>
|
|
50
|
+
{
|
|
51
|
+
if (text[startIndex] !== '(') return null;
|
|
52
|
+
|
|
53
|
+
let depth = 1;
|
|
54
|
+
let index = startIndex + 1;
|
|
55
|
+
|
|
56
|
+
while (index < text.length && depth > 0)
|
|
57
|
+
{
|
|
58
|
+
if (text[index] === '(') depth++;
|
|
59
|
+
else if (text[index] === ')') depth--;
|
|
60
|
+
index++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (depth !== 0) return null;
|
|
64
|
+
return { content: text.slice(startIndex + 1, index - 1), endIndex: index - 1 };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// --------------------------------------------------------------
|
|
68
|
+
// Inner expression processor:
|
|
69
|
+
// Tokenizes Math/Strings/DataKeys/Text recursively.
|
|
70
|
+
// --------------------------------------------------------------
|
|
71
|
+
const processExpression = (text, exprStartIndex) =>
|
|
72
|
+
{
|
|
73
|
+
text = text.trim();
|
|
74
|
+
if (!text) return null;
|
|
75
|
+
|
|
76
|
+
// Full whitelist check
|
|
77
|
+
if (!allowedCharsRegex.test(text))
|
|
78
|
+
return null;
|
|
79
|
+
|
|
80
|
+
let index = 0;
|
|
81
|
+
let out = '';
|
|
82
|
+
|
|
83
|
+
while (index < text.length)
|
|
84
|
+
{
|
|
85
|
+
let ch = text[index];
|
|
86
|
+
|
|
87
|
+
// ----------------------------------------
|
|
88
|
+
// Whitespace - skip it
|
|
89
|
+
// ----------------------------------------
|
|
90
|
+
if (/\s/.test(ch))
|
|
91
|
+
{
|
|
92
|
+
index++;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ----------------------------------------
|
|
97
|
+
// Numeric literals
|
|
98
|
+
// ----------------------------------------
|
|
99
|
+
if (/\d/.test(ch))
|
|
100
|
+
{
|
|
101
|
+
out += ch;
|
|
102
|
+
index++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ----------------------------------------
|
|
107
|
+
// Quoted string literal
|
|
108
|
+
// ----------------------------------------
|
|
109
|
+
if (ch === '"' || ch === "'")
|
|
110
|
+
{
|
|
111
|
+
const quote = ch;
|
|
112
|
+
let start = index++;
|
|
113
|
+
|
|
114
|
+
while (index < text.length)
|
|
115
|
+
{
|
|
116
|
+
if (text[index] === '\\')
|
|
117
|
+
{
|
|
118
|
+
index += 2;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (text[index] === quote)
|
|
122
|
+
break;
|
|
123
|
+
index++;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (index >= text.length) return null;
|
|
127
|
+
|
|
128
|
+
out += text.slice(start, ++index);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ----------------------------------------
|
|
133
|
+
// Math or String function call
|
|
134
|
+
// ----------------------------------------
|
|
135
|
+
let fnMatch = text.slice(index).match(/^(Math|String)\.([A-Za-z_]\w*)/);
|
|
136
|
+
if (fnMatch)
|
|
137
|
+
{
|
|
138
|
+
const type = fnMatch[1],
|
|
139
|
+
fnName = fnMatch[2],
|
|
140
|
+
allowedFns = type === 'Math' ? allowedMathFunctions : allowedStringFunctions;
|
|
141
|
+
|
|
142
|
+
if (!allowedFns.includes(fnName)) return null;
|
|
143
|
+
|
|
144
|
+
out += (type === 'Math') ? `${type}.${fnName}` : `String.prototype.${fnName}.call`;
|
|
145
|
+
index += fnMatch[0].length;
|
|
146
|
+
|
|
147
|
+
if (text[index] !== '(') return null;
|
|
148
|
+
|
|
149
|
+
const paren = extractParentheses(text, index);
|
|
150
|
+
if (!paren) return null;
|
|
151
|
+
|
|
152
|
+
const inner = processExpression(paren.content, exprStartIndex + index + 1);
|
|
153
|
+
if (inner === null) return null;
|
|
154
|
+
|
|
155
|
+
if (type === 'Math')
|
|
156
|
+
out += `(${inner})`;
|
|
157
|
+
else
|
|
158
|
+
out += `( ${inner} )`;
|
|
159
|
+
|
|
160
|
+
index = paren.endIndex + 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ----------------------------------------
|
|
165
|
+
// Booleans
|
|
166
|
+
// ----------------------------------------
|
|
167
|
+
const afterTrue = text[index + 4];
|
|
168
|
+
|
|
169
|
+
if (text.startsWith("true", index) && (afterTrue === undefined || !/[\w]/.test(afterTrue)))
|
|
170
|
+
{
|
|
171
|
+
out += "true";
|
|
172
|
+
index += 4;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const afterFalse = text[index + 5];
|
|
177
|
+
|
|
178
|
+
if (text.startsWith("false", index) && (afterFalse === undefined || !/[\w]/.test(afterFalse)))
|
|
179
|
+
{
|
|
180
|
+
out += "false";
|
|
181
|
+
index += 5;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ----------------------------------------
|
|
186
|
+
// null
|
|
187
|
+
// ----------------------------------------
|
|
188
|
+
if (text.startsWith("null", index) && !/\w/.test(text[index + 4] ?? ''))
|
|
189
|
+
{
|
|
190
|
+
out += "null";
|
|
191
|
+
index += 4;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ----------------------------------------
|
|
196
|
+
// Empty Object
|
|
197
|
+
// ----------------------------------------
|
|
198
|
+
if (ch === '{')
|
|
199
|
+
{
|
|
200
|
+
if (text[index + 1] === '}')
|
|
201
|
+
{
|
|
202
|
+
out += '{}';
|
|
203
|
+
index += 2;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ----------------------------------------
|
|
210
|
+
// Empty Array
|
|
211
|
+
// ----------------------------------------
|
|
212
|
+
if (ch === '[')
|
|
213
|
+
{
|
|
214
|
+
if (text[index + 1] === ']')
|
|
215
|
+
{
|
|
216
|
+
out += '[]';
|
|
217
|
+
index += 2;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ----------------------------------------
|
|
224
|
+
// Custom Resolver match
|
|
225
|
+
// ----------------------------------------
|
|
226
|
+
let resolverMatch = text.slice(index).match(this.resolveRegex);
|
|
227
|
+
if (resolverMatch)
|
|
228
|
+
{
|
|
229
|
+
const token = resolverMatch[0];
|
|
230
|
+
|
|
231
|
+
if (tokenHandler)
|
|
232
|
+
tokenHandler(token, exprStartIndex + index);
|
|
233
|
+
|
|
234
|
+
out += `resolver(${JSON.stringify(token)})`;
|
|
235
|
+
index += token.length;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ----------------------------------------
|
|
240
|
+
// Operators
|
|
241
|
+
// ----------------------------------------
|
|
242
|
+
if ("&|+-*/%()<>!=?:@,.".includes(ch))
|
|
243
|
+
{
|
|
244
|
+
out += ch;
|
|
245
|
+
index++;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return out;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// --------------------------------------------------------------
|
|
257
|
+
// MAIN PROCESS splits literal text and {{ expressions }}
|
|
258
|
+
// --------------------------------------------------------------
|
|
259
|
+
function process(text, index = 0, inPlaceholder = false)
|
|
260
|
+
{
|
|
261
|
+
let output = "";
|
|
262
|
+
let inString = null;
|
|
263
|
+
let escape = false;
|
|
264
|
+
|
|
265
|
+
while (index < text.length)
|
|
266
|
+
{
|
|
267
|
+
let ch = text[index];
|
|
268
|
+
|
|
269
|
+
// Handle escapes
|
|
270
|
+
if (ch === '\\' && !escape)
|
|
271
|
+
{
|
|
272
|
+
escape = true;
|
|
273
|
+
output += JSON.stringify(ch) + " + "; // output the backslash as a literal
|
|
274
|
+
index++;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (escape)
|
|
279
|
+
{
|
|
280
|
+
escape = false;
|
|
281
|
+
output += JSON.stringify(ch) + " + "; // output escaped char as literal, but skip string boundary detection
|
|
282
|
+
index++;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Detect string boundaries
|
|
287
|
+
if (!escape && (ch === '"' || ch === "'"))
|
|
288
|
+
{
|
|
289
|
+
if (inString === null) inString = ch;
|
|
290
|
+
else if (inString === ch) inString = null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (inPlaceholder)
|
|
294
|
+
{
|
|
295
|
+
if (inString === null && ch === '}' && text[index + 1] === '}') // Find end of placeholder
|
|
296
|
+
{
|
|
297
|
+
return { value: output, index: index + 2 }; // Return processed content and new index after }}
|
|
298
|
+
}
|
|
299
|
+
// Otherwise accumulate inside placeholder
|
|
300
|
+
output += ch;
|
|
301
|
+
index++;
|
|
302
|
+
}
|
|
303
|
+
else if (ch === '{' && text[index + 1] === '{') // Outside placeholder: detect {{ to enter placeholder mode
|
|
304
|
+
{
|
|
305
|
+
// Recursive call to process inside placeholder
|
|
306
|
+
const innerResult = process(text, index + 2, true);
|
|
307
|
+
const exprStartIndex = index + 2;
|
|
308
|
+
if (!innerResult) return null; // invalid
|
|
309
|
+
|
|
310
|
+
const exprContent = innerResult.value;
|
|
311
|
+
index = innerResult.index;
|
|
312
|
+
|
|
313
|
+
const processed = processExpression(exprContent, exprStartIndex);
|
|
314
|
+
if (processed === null) return null;
|
|
315
|
+
|
|
316
|
+
output += "(" + processed + ")" + " + ";
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
else
|
|
320
|
+
{
|
|
321
|
+
output += JSON.stringify(ch) + " + "; // Default: literal text outside placeholders
|
|
322
|
+
index++;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// If we are inside a placeholder but no closing }}, invalid expression
|
|
327
|
+
if (inPlaceholder) return null;
|
|
328
|
+
|
|
329
|
+
// Return accumulated string and index (end of text)
|
|
330
|
+
return { value: output, index: index };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!expr)
|
|
334
|
+
return null;
|
|
335
|
+
|
|
336
|
+
const result = process(expr, 0, false);
|
|
337
|
+
if (!result) return null;
|
|
338
|
+
|
|
339
|
+
let output = result.value.replace(/\s*\+\s*$/, ""); // Trim trailing " + "
|
|
340
|
+
return Function('resolver', `"use strict"; return (${output})`); // Run JS-safe parsed code and accept resolver fn
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
})();
|
|
344
|
+
|
|
345
|
+
var ExpressionEngine = componyx.bindary_modules.expressionEngine;
|
|
346
|
+
|
|
347
|
+
export { ExpressionEngine as default };
|