@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,371 @@
|
|
|
1
|
+
/*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
|
|
2
|
+
(async function (window)
|
|
3
|
+
{
|
|
4
|
+
let _attr =
|
|
5
|
+
{
|
|
6
|
+
placeHolderId: 'placeholderid',
|
|
7
|
+
css: 'css',
|
|
8
|
+
js: 'js',
|
|
9
|
+
onPostDetect: 'onPostDetect',
|
|
10
|
+
onMatchMediaChange: 'onMatchMediaChange',
|
|
11
|
+
onActivate: 'onActivate',
|
|
12
|
+
onDeactivate: 'onDeactivate',
|
|
13
|
+
onLoadSrc: 'onLoadSrc',
|
|
14
|
+
onLoadComplete: 'onLoadComplete'
|
|
15
|
+
},
|
|
16
|
+
_templates = {},
|
|
17
|
+
_placeHolders = {},
|
|
18
|
+
_activeElement;
|
|
19
|
+
|
|
20
|
+
$lib.each(_attr, function (v, k) { _attr[k] = 'data-rsp-template-' + k.toLowerCase(); }); // create correct attribute keys with 'data-rsp-template' prefix
|
|
21
|
+
_attr.template = 'data-rsp-template'; // defines a template element
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* componyx.responsiveTemplates HTML data-attributes.
|
|
25
|
+
* @typedef {Object} HTML_Attributes
|
|
26
|
+
* @memberof componyx.responsiveTemplates
|
|
27
|
+
* @property {String} ["data-rsp-template-placeHolderId"] - a place-holder element for moving content between templates.
|
|
28
|
+
* @property {String} ["data-rsp-template-css"] - css resources (space-delimited) that should be loaded when the template is activated.
|
|
29
|
+
* @property {String} ["data-rsp-template-js"] - js resources (space-delimited) that should be loaded when the template is activated
|
|
30
|
+
* @property {String} ["data-rsp-template-onPostDetect"] - method which is invoked when the view templates have been detected and initialized.
|
|
31
|
+
* @property {String} ["data-rsp-template-onMatchMediaChange"] - method which is invoked when a `matchMedia` query changes its match state (e.g., from matching to non-matching or vice versa).
|
|
32
|
+
* @property {String} ["data-rsp-template-onActivate"] - method which is invoked when a template is activated.
|
|
33
|
+
* @property {String} ["data-rsp-template-onDeactivate"] - method which is invoked when a template is deactivated.
|
|
34
|
+
* @property {String} ["data-rsp-template-onLoadSrc"] - method which is invoked when a single template resource is loaded.
|
|
35
|
+
* @property {String} ["data-rsp-template-onLoadComplete"] - method which is invoked when all template resources have been loaded.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A registered responsive template. Passed to the template events and callbacks.
|
|
40
|
+
* @typedef {Object} Template
|
|
41
|
+
* @memberof componyx.responsiveTemplates
|
|
42
|
+
* @property {HTMLElement} element - The template element.
|
|
43
|
+
* @property {HTMLElement[]} placeHolders - The place-holder elements inside the template.
|
|
44
|
+
* @property {String} css - Space separated list of css sources loaded when the template is active.
|
|
45
|
+
* @property {String} js - Space separated list of javascript sources loaded when the template is active.
|
|
46
|
+
* @property {Object} callbacks - The callbacks specified when the template was added.
|
|
47
|
+
* @property {MediaQueryList} [mediaQuery] - The media query list linked to the template, when a media query was specified.
|
|
48
|
+
* @property {Boolean} [sourcesLoaded] - A value indicating if all template sources have been loaded.
|
|
49
|
+
* @property {HTMLElement[]} [tags] - The script and link tags added for the template sources.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Enables responsive templates based on media-queries defined through HTML data-attributes.
|
|
54
|
+
* Event handlers are invoked with window as context and receive the arguments described per event.
|
|
55
|
+
* @namespace componyx.responsiveTemplates
|
|
56
|
+
* @memberof componyx
|
|
57
|
+
*/
|
|
58
|
+
const rspTemplates = componyx.responsiveTemplates =
|
|
59
|
+
{
|
|
60
|
+
/** Defines if responsive design templates are automatically detected when the DOM is ready.
|
|
61
|
+
* @type {Boolean}
|
|
62
|
+
*/
|
|
63
|
+
autoDetect: true,
|
|
64
|
+
|
|
65
|
+
/** The id of the template to load at startup.
|
|
66
|
+
* @type {String|null}
|
|
67
|
+
*/
|
|
68
|
+
launchTemplateId: null,
|
|
69
|
+
|
|
70
|
+
/** The id of the template to load when media queries are not supported/used.
|
|
71
|
+
* @type {String|null}
|
|
72
|
+
*/
|
|
73
|
+
fallbackTemplateId: null,
|
|
74
|
+
|
|
75
|
+
/** Current active template.
|
|
76
|
+
* @type {componyx.responsiveTemplates.Template|null}
|
|
77
|
+
*/
|
|
78
|
+
activeTemplate: null,
|
|
79
|
+
|
|
80
|
+
/** Event which fires when the view templates have been detected and initialized. Handlers receive no arguments.
|
|
81
|
+
* @type {componyx.library.Event}
|
|
82
|
+
*/
|
|
83
|
+
onPostDetect: $lib.createEvent('onPostDetect'),
|
|
84
|
+
|
|
85
|
+
/** Event which fires when a `matchMedia` query changes its match state (e.g., from matching to non-matching or vice versa), before the template is activated or deactivated. Handlers receive the template of the media query.
|
|
86
|
+
* @type {componyx.library.Event}
|
|
87
|
+
* @see {@link componyx.responsiveTemplates.Template}
|
|
88
|
+
*/
|
|
89
|
+
onMatchMediaChange: $lib.createEvent('onMatchMediaChange'),
|
|
90
|
+
|
|
91
|
+
/** Event which fires when a view template is activated. Handlers receive the activated template.
|
|
92
|
+
* @type {componyx.library.Event}
|
|
93
|
+
* @see {@link componyx.responsiveTemplates.Template}
|
|
94
|
+
*/
|
|
95
|
+
onActivate: $lib.createEvent('onActivate'),
|
|
96
|
+
|
|
97
|
+
/** Event which fires when a view template is deactivated. Handlers receive the deactivated template.
|
|
98
|
+
* @type {componyx.library.Event}
|
|
99
|
+
* @see {@link componyx.responsiveTemplates.Template}
|
|
100
|
+
*/
|
|
101
|
+
onDeactivate: $lib.createEvent('onDeactivate'),
|
|
102
|
+
|
|
103
|
+
/** Event which fires when a single template resource is loaded. Handlers receive the loaded source (String).
|
|
104
|
+
* @type {componyx.library.Event}
|
|
105
|
+
*/
|
|
106
|
+
onLoadSrc: $lib.createEvent('onLoadSrc'),
|
|
107
|
+
|
|
108
|
+
/** Event which fires when all template resources have been loaded. Handlers receive no arguments.
|
|
109
|
+
* @type {componyx.library.Event}
|
|
110
|
+
*/
|
|
111
|
+
onLoadComplete: $lib.createEvent('onLoadComplete'),
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Detects and initializes responsive design templates on the page.
|
|
115
|
+
*/
|
|
116
|
+
detect: async function ()
|
|
117
|
+
{
|
|
118
|
+
let templates = $lib(function (el) { return (el.hasAttribute(_attr.template) && el.id) }, document.body, 'div section'),
|
|
119
|
+
postDetect;
|
|
120
|
+
|
|
121
|
+
_templates = {};
|
|
122
|
+
_placeHolders = {};
|
|
123
|
+
_activeElement = null;
|
|
124
|
+
|
|
125
|
+
$lib.each(templates, function (t)
|
|
126
|
+
{
|
|
127
|
+
rspTemplates.add(t.id, t.getAttribute(_attr.template), t.getAttribute(_attr.css), t.getAttribute(_attr.js),
|
|
128
|
+
{
|
|
129
|
+
onMatchMediaChange: getHandler(t.getAttribute(_attr.onMatchMediaChange)),
|
|
130
|
+
onActivate: getHandler(t.getAttribute(_attr.onActivate)),
|
|
131
|
+
onDeactivate: getHandler(t.getAttribute(_attr.onDeactivate)),
|
|
132
|
+
onLoadSrc: getHandler(t.getAttribute(_attr.onLoadSrc)),
|
|
133
|
+
onLoadComplete: getHandler(t.getAttribute(_attr.onLoadComplete))
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
postDetect = getHandler(t.getAttribute(_attr.onPostDetect));
|
|
137
|
+
|
|
138
|
+
if (postDetect)
|
|
139
|
+
postDetect();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
rspTemplates.onPostDetect.fire(window);
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Adds a responsive template which can be linked to the specified media query.
|
|
147
|
+
*
|
|
148
|
+
* @param {String} templateId The id of the DOM Element which will act as responsive design template.
|
|
149
|
+
* @param {String} mediaQuery The media query (or comma separated list) which is linked to the responsive design template.
|
|
150
|
+
* @param {String} css Space separated list of css sources to load when the responsive design template is active.
|
|
151
|
+
* @param {String} js Space separated list of javascript sources to load when the responsive design template is active.
|
|
152
|
+
* @param {Object} callbacks An object containing optional callback functions. The template is passed as first argument to all callbacks.
|
|
153
|
+
* @param {Function} [callbacks.onMatchMediaChange] Called when the media query changes its match state, before the template is activated or deactivated.
|
|
154
|
+
* @param {Function} [callbacks.onActivate] Called when a template is activated.
|
|
155
|
+
* @param {Function} [callbacks.onDeactivate] Called when a template is deactivated.
|
|
156
|
+
* @param {Function} [callbacks.onLoadSrc] Called when a JS or CSS source is loaded. The source (String) is passed as second parameter.
|
|
157
|
+
* @param {Function} [callbacks.onLoadComplete] Called when all JS and CSS sources have been loaded.
|
|
158
|
+
*/
|
|
159
|
+
add: async function (templateId, mediaQuery, css, js, callbacks)
|
|
160
|
+
{
|
|
161
|
+
let template = $lib('#' + templateId),
|
|
162
|
+
placeHolders = getPlaceHolders($lib('#' + templateId)),
|
|
163
|
+
qry;
|
|
164
|
+
|
|
165
|
+
_templates[templateId] = { element: template, placeHolders: placeHolders, css: $lib.trim(css) || "", js: $lib.trim(js) || "", callbacks: callbacks };
|
|
166
|
+
rspTemplates.deactivate(templateId);
|
|
167
|
+
|
|
168
|
+
if (mediaQuery && window.matchMedia)
|
|
169
|
+
{
|
|
170
|
+
qry = window.matchMedia(mediaQuery);
|
|
171
|
+
qry.addEventListener('change', async function (e) { await matchMediaChange(e.target, templateId); });
|
|
172
|
+
|
|
173
|
+
_templates[templateId].mediaQuery = qry;
|
|
174
|
+
|
|
175
|
+
if (($lib.isEmpty(rspTemplates.launchTemplateId) && qry.matches) || (rspTemplates.launchTemplateId === templateId))
|
|
176
|
+
rspTemplates.activate(templateId);
|
|
177
|
+
}
|
|
178
|
+
else if (rspTemplates.launchTemplateId === templateId)
|
|
179
|
+
rspTemplates.activate(templateId);
|
|
180
|
+
else if (rspTemplates.fallbackTemplateId === templateId)
|
|
181
|
+
rspTemplates.activate(templateId);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Activates the responsive template with the specified id.
|
|
186
|
+
*
|
|
187
|
+
* @param {String} templateId The id of the DOM Element which will act as responsive design template.
|
|
188
|
+
*/
|
|
189
|
+
activate: async function (templateId)
|
|
190
|
+
{
|
|
191
|
+
let template = _templates[templateId],
|
|
192
|
+
templatePlaceHolder = $lib('#' + templateId);
|
|
193
|
+
|
|
194
|
+
rspTemplates.activeTemplate = template;
|
|
195
|
+
|
|
196
|
+
// replace placeholder with original template
|
|
197
|
+
templatePlaceHolder.parentNode.replaceChild(template.element, templatePlaceHolder);
|
|
198
|
+
template.element.style.display = '';
|
|
199
|
+
|
|
200
|
+
$lib.each(template.placeHolders, function (target)
|
|
201
|
+
{
|
|
202
|
+
let id = target.getAttribute(_attr.placeHolderId),
|
|
203
|
+
source;
|
|
204
|
+
|
|
205
|
+
if ($lib.isEmpty(target.id))
|
|
206
|
+
{
|
|
207
|
+
source = getActivePlaceHolder(id);
|
|
208
|
+
target.id = id; // only a placeholder of the active template has an id
|
|
209
|
+
source.removeAttribute('id');
|
|
210
|
+
|
|
211
|
+
$lib.appendChildren(source, target);
|
|
212
|
+
_placeHolders[id] = target;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (_activeElement && document.body.contains(_activeElement))
|
|
217
|
+
_activeElement.focus();
|
|
218
|
+
|
|
219
|
+
await call(template, 'onActivate');
|
|
220
|
+
rspTemplates.onActivate.fire(window, [template]);
|
|
221
|
+
|
|
222
|
+
if (template.sourcesLoaded)
|
|
223
|
+
toggleSources(template, false);
|
|
224
|
+
else if (template.js || template.css)
|
|
225
|
+
registerSources(templateId);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Deactivates the responsive template with the specified id.
|
|
230
|
+
*
|
|
231
|
+
* @param {String} templateId The id of the DOM Element which will act as responsive design template.
|
|
232
|
+
*/
|
|
233
|
+
deactivate: async function (templateId)
|
|
234
|
+
{
|
|
235
|
+
let template = _templates[templateId],
|
|
236
|
+
templatePlaceHolder = document.createElement('div');
|
|
237
|
+
|
|
238
|
+
_activeElement = document.activeElement;
|
|
239
|
+
|
|
240
|
+
if (!template.element.parentNode)
|
|
241
|
+
return;
|
|
242
|
+
|
|
243
|
+
// remove the original template but keep a placeholder on the page so that when the template is activated it has the same position in the DOM tree
|
|
244
|
+
templatePlaceHolder.id = templateId;
|
|
245
|
+
templatePlaceHolder.style.display = 'none';
|
|
246
|
+
template.element.parentNode.replaceChild(templatePlaceHolder, template.element);
|
|
247
|
+
|
|
248
|
+
toggleSources(template, true);
|
|
249
|
+
|
|
250
|
+
if (rspTemplates.activeTemplate === template) // another template may already have been activated
|
|
251
|
+
rspTemplates.activeTemplate = null;
|
|
252
|
+
|
|
253
|
+
await call(template, 'onDeactivate');
|
|
254
|
+
rspTemplates.onDeactivate.fire(window, [template]);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
function getHandler(name)
|
|
259
|
+
{
|
|
260
|
+
if (!name)
|
|
261
|
+
return null;
|
|
262
|
+
|
|
263
|
+
let contextTree = name.split('.'), length = contextTree.length, context = window, method;
|
|
264
|
+
|
|
265
|
+
$lib.each(contextTree, function (name, index)
|
|
266
|
+
{
|
|
267
|
+
method = context[name];
|
|
268
|
+
|
|
269
|
+
if (index != length - 1)
|
|
270
|
+
context = context[name];
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
return method;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function getPlaceHolders(template)
|
|
277
|
+
{
|
|
278
|
+
let placeHolders = $lib(function (el) { return (el.hasAttribute(_attr.placeHolderId)) }, template);
|
|
279
|
+
|
|
280
|
+
$lib.each(placeHolders, function (el)
|
|
281
|
+
{
|
|
282
|
+
if (!$lib.isEmpty(el.id))
|
|
283
|
+
_placeHolders[el.id] = el;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return placeHolders;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getActivePlaceHolder(id)
|
|
290
|
+
{
|
|
291
|
+
if (_placeHolders[id])
|
|
292
|
+
return _placeHolders[id];
|
|
293
|
+
|
|
294
|
+
return _placeHolders[id] = $lib('#' + id);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function matchMediaChange(qry, templateId)
|
|
298
|
+
{
|
|
299
|
+
let template = _templates[templateId];
|
|
300
|
+
|
|
301
|
+
await call(template, 'onMatchMediaChange');
|
|
302
|
+
rspTemplates.onMatchMediaChange.fire(window, [template]);
|
|
303
|
+
|
|
304
|
+
if (qry.matches)
|
|
305
|
+
rspTemplates.activate(templateId);
|
|
306
|
+
else
|
|
307
|
+
rspTemplates.deactivate(templateId);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function registerSources(templateId)
|
|
311
|
+
{
|
|
312
|
+
let template = _templates[templateId],
|
|
313
|
+
js = (template.js) ? template.js.split(/\s+/) : [],
|
|
314
|
+
css = (template.css) ? template.css.split(/\s+/) : [],
|
|
315
|
+
length = js.length + css.length,
|
|
316
|
+
srcLoaded = 0,
|
|
317
|
+
callback = async function (src)
|
|
318
|
+
{
|
|
319
|
+
await call(template, 'onLoadSrc', [src]);
|
|
320
|
+
rspTemplates.onLoadSrc.fire(window, [src]);
|
|
321
|
+
|
|
322
|
+
if (length == srcLoaded)
|
|
323
|
+
{
|
|
324
|
+
template.sourcesLoaded = true;
|
|
325
|
+
await call(template, 'onLoadComplete');
|
|
326
|
+
rspTemplates.onLoadComplete.fire(window);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
template.tags = []; // keep the added tags so they can be toggled when the template is (de)activated
|
|
331
|
+
|
|
332
|
+
$lib.each(js, function (src)
|
|
333
|
+
{
|
|
334
|
+
template.tags.push($lib.addScriptSource(src, '', { onComplete: function () { ++srcLoaded; callback(src); } }));
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
$lib.each(css, function (src)
|
|
338
|
+
{
|
|
339
|
+
template.tags.push($lib.addCssSource(src, '', { onComplete: function () { ++srcLoaded; callback(src); } }));
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function toggleSources(template, disable)
|
|
344
|
+
{
|
|
345
|
+
$lib.each(template.tags || [], function (tag)
|
|
346
|
+
{
|
|
347
|
+
if (tag && tag.nodeName === 'LINK') // only stylesheets can be disabled, an executed script cannot be undone
|
|
348
|
+
tag.disabled = disable;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function call(template, name, args)
|
|
353
|
+
{
|
|
354
|
+
args = args || [];
|
|
355
|
+
|
|
356
|
+
if (template.callbacks && template.callbacks[name])
|
|
357
|
+
{
|
|
358
|
+
args.unshift(template);
|
|
359
|
+
// Use await to ensure that async handlers are handled properly
|
|
360
|
+
await template.callbacks[name].apply(window, args);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
$lib.ready(function ()
|
|
365
|
+
{
|
|
366
|
+
// detect templates when DOM is ready
|
|
367
|
+
if (rspTemplates.autoDetect)
|
|
368
|
+
rspTemplates.detect();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
})(window);
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
|
|
2
|
+
componyx.base_modules = componyx.base_modules || {};
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sanitizes HTML content by removing unsafe tags, attributes, and styles.
|
|
6
|
+
* @class
|
|
7
|
+
* @property {String[]} allowTags - A list of HTML tags to allow during HTML sanitization.
|
|
8
|
+
* @property {Array<String|RegExp>} denyAttributes - A list of attributes to deny during HTML sanitization. String attributes are matched using 'starts with', while regex can be used for more complex matching. Attributes allowing JavaScript are denied by default.
|
|
9
|
+
* @property {Array<String|RegExp>} allowCss - A list of css properties to allow during HTML sanitization. String attributes are matched using equal to, while regex can be used for more complex matching.
|
|
10
|
+
*/
|
|
11
|
+
componyx.base_modules.Sanitizer = class Sanitizer
|
|
12
|
+
{
|
|
13
|
+
#allowTags = ['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'b', 'bdi', 'big', 'blockquote', 'br', 'button',
|
|
14
|
+
'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dir', 'div', 'dl', 'dt',
|
|
15
|
+
'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'i', 'img',
|
|
16
|
+
'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'main', 'map', 'mark', 'menu', 'menuitem', 'meter', 'nav', 'ol', 'optgroup', 'option',
|
|
17
|
+
'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'summary',
|
|
18
|
+
'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'tt', 'u', 'ul', 'var', 'wbr'];
|
|
19
|
+
#denyAttributes = [
|
|
20
|
+
/^data-bindary/i,
|
|
21
|
+
/^b-/i,
|
|
22
|
+
/^mso-/i,
|
|
23
|
+
/^o:/i,
|
|
24
|
+
/^w:/i,
|
|
25
|
+
/^v:/i,
|
|
26
|
+
/^xmlns(:.*)?$/i
|
|
27
|
+
];
|
|
28
|
+
#allowCss = ['display', 'color', 'background-color', /^font(-.*)?$/, /^text(-.*)?$/,
|
|
29
|
+
'margin', /^margin(-.*)?$/, 'padding', /^padding(-.*)?$/, 'border', /^border(-.*)?$/,
|
|
30
|
+
'width', 'height', 'max-width', 'max-height', 'min-width', 'min-height', 'opacity',
|
|
31
|
+
/^flex(-.*)?$/, 'justify-content', 'align-items', 'align-content', 'align-self', 'order',
|
|
32
|
+
/^grid(-.*)?$/];
|
|
33
|
+
#attributeValueFilters = {
|
|
34
|
+
class: value => value.split(/\s+/).filter(c => !/^mso/i.test(c)).join(' ')
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {object} options
|
|
39
|
+
* @param {String[]} options.allowTags - A list of HTML tags to allow during HTML sanitization.
|
|
40
|
+
* @param {Array<String|RegExp>} options.denyAttributes - A list of attributes to deny during HTML sanitization. String attributes are matched using 'starts with', while regex can be used for more complex matching. Attributes allowing JavaScript are denied by default.
|
|
41
|
+
* @param {Array<String|RegExp>} options.allowCss - A list of css properties to allow during HTML sanitization. String attributes are matched using equal to, while regex can be used for more complex matching.
|
|
42
|
+
* @param {Object<string, function(string): string>} options.attributeValueFilters - An object with attribute name as key and a transformation function as value. If the function returns an empty string, the attribute will be removed.
|
|
43
|
+
*/
|
|
44
|
+
constructor(options = {})
|
|
45
|
+
{
|
|
46
|
+
this.allowTags = options.allowTags || this.#allowTags;
|
|
47
|
+
this.denyAttributes = options.denyAttributes || this.#denyAttributes;
|
|
48
|
+
this.allowCss = options.allowCss || this.#allowCss;
|
|
49
|
+
this.attributeValueFilters = options.attributeValueFilters || this.#attributeValueFilters;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sanitize given HTML string
|
|
54
|
+
* @param {string} html
|
|
55
|
+
* @returns {string} sanitized HTML
|
|
56
|
+
*/
|
|
57
|
+
sanitize(html)
|
|
58
|
+
{
|
|
59
|
+
let temp = document.createElement('div');
|
|
60
|
+
|
|
61
|
+
temp.innerHTML = html;
|
|
62
|
+
this.sanitizeNode(temp);
|
|
63
|
+
return temp.innerHTML;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Recursively sanitize a DOM node and its children
|
|
68
|
+
* @param {Node} parent
|
|
69
|
+
*/
|
|
70
|
+
sanitizeNode(parent)
|
|
71
|
+
{
|
|
72
|
+
let node = parent.firstChild;
|
|
73
|
+
|
|
74
|
+
while (node)
|
|
75
|
+
{
|
|
76
|
+
const nodeType = node.nodeType;
|
|
77
|
+
let next = node.nextSibling;
|
|
78
|
+
|
|
79
|
+
if (nodeType === 1)
|
|
80
|
+
{
|
|
81
|
+
const tag = node.nodeName.toLowerCase();
|
|
82
|
+
|
|
83
|
+
if (this.allowTags.includes(tag))
|
|
84
|
+
{
|
|
85
|
+
for (let index = node.attributes.length - 1; index >= 0; index--)
|
|
86
|
+
{
|
|
87
|
+
const attr = node.attributes[index],
|
|
88
|
+
attrName = attr.name.toLowerCase(),
|
|
89
|
+
attrValue = attr.value,
|
|
90
|
+
isEventHandler = attrName.startsWith('on'),
|
|
91
|
+
isDeniedAttr = this.denyAttributes.some(item => item instanceof RegExp ? item.test(attrName) : attrName.startsWith(item)),
|
|
92
|
+
isSafe = this.isSafeAttrValue(attrName, attrValue);
|
|
93
|
+
|
|
94
|
+
if (isEventHandler || isDeniedAttr || !isSafe)
|
|
95
|
+
{
|
|
96
|
+
node.removeAttribute(attr.name);
|
|
97
|
+
}
|
|
98
|
+
else if (this.attributeValueFilters?.[attrName])
|
|
99
|
+
{
|
|
100
|
+
const newValue = this.applyAttributeValueFilter(attrName, attrValue);
|
|
101
|
+
|
|
102
|
+
if (newValue)
|
|
103
|
+
node.setAttribute(attrName, newValue);
|
|
104
|
+
else
|
|
105
|
+
node.removeAttribute(attrName);
|
|
106
|
+
}
|
|
107
|
+
else if (attrName === 'style')
|
|
108
|
+
{
|
|
109
|
+
const safeStyle = this.sanitizeStyleAttr(attrValue);
|
|
110
|
+
if (safeStyle)
|
|
111
|
+
node.setAttribute('style', safeStyle);
|
|
112
|
+
else
|
|
113
|
+
node.removeAttribute('style');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.sanitizeNode(node);
|
|
118
|
+
}
|
|
119
|
+
else
|
|
120
|
+
{
|
|
121
|
+
next = node.nextSibling;
|
|
122
|
+
|
|
123
|
+
while (node.attributes.length > 0)
|
|
124
|
+
node.removeAttribute(node.attributes[0].name); // first remove all attributes otherwise 'on' events can still fire
|
|
125
|
+
|
|
126
|
+
node.remove();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (nodeType != 3)
|
|
130
|
+
{
|
|
131
|
+
next = node.nextSibling;
|
|
132
|
+
node.remove();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
node = next || node.nextSibling;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Decode HTML entities in a string
|
|
141
|
+
* @param {string} str
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
decodeHTML(str)
|
|
145
|
+
{
|
|
146
|
+
const txt = document.createElement('textarea');
|
|
147
|
+
txt.innerHTML = str;
|
|
148
|
+
return txt.value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Check if CSS property name is allowed
|
|
153
|
+
* @param {string} prop
|
|
154
|
+
* @returns {boolean}
|
|
155
|
+
*/
|
|
156
|
+
isAllowedCssProperty(prop)
|
|
157
|
+
{
|
|
158
|
+
return this.allowCss.some(item =>
|
|
159
|
+
{
|
|
160
|
+
if (typeof item === 'string')
|
|
161
|
+
{
|
|
162
|
+
return item === prop;
|
|
163
|
+
}
|
|
164
|
+
else if (item instanceof RegExp)
|
|
165
|
+
{
|
|
166
|
+
return item.test(prop);
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if URI protocol/value is safe (does not contain dangerous protocols)
|
|
174
|
+
* @param {string} decoded
|
|
175
|
+
* @returns {boolean}
|
|
176
|
+
*/
|
|
177
|
+
isSafeProtocol(decoded)
|
|
178
|
+
{
|
|
179
|
+
try
|
|
180
|
+
{
|
|
181
|
+
const normalized = decoded
|
|
182
|
+
.replace(/\\[nrtf]/gi, '') // Remove \n \t etc (escaped)
|
|
183
|
+
.replace(/[\s\u0000-\u001F]+/g, '') // Remove actual whitespace + control chars
|
|
184
|
+
.toLowerCase();
|
|
185
|
+
|
|
186
|
+
if (normalized.includes('javascript:') || normalized.includes('data:text/html'))
|
|
187
|
+
return false;
|
|
188
|
+
|
|
189
|
+
if (normalized.includes('data:') && !/^data:image\/(png|jpeg|jpg|gif|webp);base64,[a-z0-9+/=]+$/i.test(normalized))
|
|
190
|
+
return false;
|
|
191
|
+
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
catch (e)
|
|
195
|
+
{
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check if CSS value is safe
|
|
202
|
+
* @param {string} value
|
|
203
|
+
* @returns {boolean}
|
|
204
|
+
*/
|
|
205
|
+
isSafeCssValue(value)
|
|
206
|
+
{
|
|
207
|
+
const v = value.toLowerCase().trim();
|
|
208
|
+
|
|
209
|
+
if (v.includes('expression(') || v.includes('javascript:') || v.startsWith('@') || v.startsWith('_'))
|
|
210
|
+
return false;
|
|
211
|
+
|
|
212
|
+
if (v.includes('data:') && !v.includes('data:image/'))
|
|
213
|
+
{
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Sanitize style attribute value and return safe subset CSS string
|
|
222
|
+
* @param {string} styleValue
|
|
223
|
+
* @returns {string} sanitized style string
|
|
224
|
+
*/
|
|
225
|
+
sanitizeStyleAttr(styleValue)
|
|
226
|
+
{
|
|
227
|
+
const el = document.createElement('div');
|
|
228
|
+
el.style.cssText = styleValue;
|
|
229
|
+
|
|
230
|
+
const safeProps = [];
|
|
231
|
+
|
|
232
|
+
for (let index = 0; index < el.style.length; index++)
|
|
233
|
+
{
|
|
234
|
+
const propName = el.style[index].toLowerCase(),
|
|
235
|
+
propValue = el.style.getPropertyValue(propName).trim();
|
|
236
|
+
|
|
237
|
+
if (this.isAllowedCssProperty(propName) && this.isSafeCssValue(propValue))
|
|
238
|
+
{
|
|
239
|
+
safeProps.push(`${propName}: ${propValue}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return safeProps.join('; ');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Check if attribute value is safe for the attribute
|
|
248
|
+
* @param {string} attrName
|
|
249
|
+
* @param {string} attrValue
|
|
250
|
+
* @returns {boolean}
|
|
251
|
+
*/
|
|
252
|
+
isSafeAttrValue(attrName, attrValue)
|
|
253
|
+
{
|
|
254
|
+
try
|
|
255
|
+
{
|
|
256
|
+
if (attrName === 'style')
|
|
257
|
+
return true;
|
|
258
|
+
|
|
259
|
+
const decoded = this.decodeHTML(attrValue);
|
|
260
|
+
|
|
261
|
+
if (!this.isSafeProtocol(decoded))
|
|
262
|
+
return false;
|
|
263
|
+
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
catch (e)
|
|
267
|
+
{
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Apply configured attribute value filter to a given attribute value.
|
|
274
|
+
* @param {string} attrName - The attribute name.
|
|
275
|
+
* @param {string} attrValue - The original attribute value.
|
|
276
|
+
* @returns {string} The transformed attribute value, or empty string if filtered out.
|
|
277
|
+
*/
|
|
278
|
+
applyAttributeValueFilter(attrName, attrValue)
|
|
279
|
+
{
|
|
280
|
+
const filter = this.attributeValueFilters?.[attrName];
|
|
281
|
+
if (!filter) return attrValue;
|
|
282
|
+
return filter(attrValue);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
var Sanitizer = componyx.base_modules.Sanitizer;
|
|
287
|
+
|
|
288
|
+
export { Sanitizer as default };
|