@jinntec/fore 1.0.0-4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +26 -38
  2. package/dist/fore-dev.js +43 -0
  3. package/dist/fore-dev.js.map +1 -0
  4. package/dist/fore.js +37 -0
  5. package/dist/fore.js.map +1 -0
  6. package/index.js +4 -0
  7. package/package.json +42 -42
  8. package/resources/fore.css +186 -0
  9. package/resources/toastify.css +87 -0
  10. package/resources/{fore-styles.css → vars.css} +0 -292
  11. package/src/DependencyNotifyingDomFacade.js +10 -16
  12. package/src/ForeElementMixin.js +26 -19
  13. package/src/actions/abstract-action.js +30 -9
  14. package/src/actions/fx-action.js +6 -4
  15. package/src/actions/fx-append.js +8 -17
  16. package/src/actions/fx-confirm.js +5 -3
  17. package/src/actions/fx-delete.js +6 -3
  18. package/src/actions/fx-dispatch.js +9 -8
  19. package/src/actions/fx-hide.js +10 -6
  20. package/src/actions/fx-insert.js +49 -39
  21. package/src/actions/fx-message.js +3 -1
  22. package/src/actions/fx-refresh.js +15 -1
  23. package/src/actions/fx-replace.js +73 -0
  24. package/src/actions/fx-return.js +42 -0
  25. package/src/actions/fx-send.js +3 -1
  26. package/src/actions/fx-setfocus.js +37 -0
  27. package/src/actions/fx-setvalue.js +58 -51
  28. package/src/actions/fx-show.js +12 -4
  29. package/src/actions/fx-toggle.js +15 -10
  30. package/src/actions/fx-update.js +3 -1
  31. package/src/dep_graph.js +4 -2
  32. package/src/drawdown.js +67 -82
  33. package/src/fore.js +158 -12
  34. package/src/functions/fx-function.js +17 -3
  35. package/src/fx-bind.js +42 -202
  36. package/src/fx-fore.js +599 -567
  37. package/src/fx-header.js +3 -1
  38. package/src/fx-instance.js +9 -1
  39. package/src/fx-model.js +59 -23
  40. package/src/fx-submission.js +106 -48
  41. package/src/fx-var.js +7 -4
  42. package/src/getInScopeContext.js +37 -11
  43. package/src/modelitem.js +4 -4
  44. package/src/relevance.js +64 -0
  45. package/src/ui/abstract-control.js +64 -37
  46. package/src/ui/fx-alert.js +7 -1
  47. package/src/ui/fx-case.js +4 -3
  48. package/src/ui/fx-container.js +7 -1
  49. package/src/ui/fx-control.js +309 -34
  50. package/src/ui/fx-dialog.js +54 -40
  51. package/src/ui/fx-group.js +3 -1
  52. package/src/ui/fx-hint.js +4 -1
  53. package/src/ui/fx-inspector.js +120 -17
  54. package/src/ui/fx-items.js +8 -8
  55. package/src/ui/fx-output.js +16 -5
  56. package/src/ui/fx-repeat.js +33 -41
  57. package/src/ui/fx-repeatitem.js +10 -4
  58. package/src/ui/fx-switch.js +5 -3
  59. package/src/ui/fx-trigger.js +3 -1
  60. package/src/xpath-evaluation.js +621 -576
  61. package/src/xpath-util.js +15 -8
  62. package/dist/fore-all.js +0 -140
  63. package/dist/fore-debug.js +0 -140
  64. package/src/.DS_Store +0 -0
  65. package/src/actions/.DS_Store +0 -0
  66. package/src/ui/.DS_Store +0 -0
@@ -1,5 +1,9 @@
1
1
  import XfAbstractControl from './abstract-control.js';
2
- import { evaluateXPath, evaluateXPathToString, evaluateXPathToNodes } from '../xpath-evaluation.js';
2
+ import {
3
+ evaluateXPath,
4
+ evaluateXPathToString,
5
+ evaluateXPathToFirstNode,
6
+ } from '../xpath-evaluation.js';
3
7
  import getInScopeContext from '../getInScopeContext.js';
4
8
  import { Fore } from '../fore.js';
5
9
 
@@ -14,6 +18,16 @@ const WIDGETCLASS = 'widget';
14
18
  * @customElement
15
19
  * @demo demo/index.html
16
20
  */
21
+
22
+ function debounce(func, timeout = 300) {
23
+ let timer;
24
+ return (...args) => {
25
+ clearTimeout(timer);
26
+ timer = setTimeout(() => {
27
+ func.apply(this, args);
28
+ }, timeout);
29
+ };
30
+ }
17
31
  export default class FxControl extends XfAbstractControl {
18
32
  constructor() {
19
33
  super();
@@ -22,6 +36,12 @@ export default class FxControl extends XfAbstractControl {
22
36
  }
23
37
 
24
38
  connectedCallback() {
39
+ this.initial = this.hasAttribute('initial') ? this.getAttribute('initial') : null;
40
+ this.url = this.hasAttribute('url') ? this.getAttribute('url') : null;
41
+ this.loaded = false;
42
+ this.initialNode = null;
43
+ this.debounceDelay = this.hasAttribute('debounce') ? this.getAttribute('debounce') : null;
44
+
25
45
  this.updateEvent = this.hasAttribute('update-event')
26
46
  ? this.getAttribute('update-event')
27
47
  : 'blur';
@@ -33,21 +53,6 @@ export default class FxControl extends XfAbstractControl {
33
53
  }
34
54
  `;
35
55
 
36
- /*
37
- const controlHtml = `
38
- <slot></slot>
39
- <fx-setvalue id="setvalue" ref="${this.ref}"></fx-setvalue>
40
- `;
41
- */
42
-
43
- /*
44
- this.shadowRoot.innerHTML = `
45
- <style>
46
- ${style}
47
- </style>
48
- ${controlHtml}
49
- `
50
- */
51
56
  this.shadowRoot.innerHTML = `
52
57
  <style>
53
58
  ${style}
@@ -57,7 +62,15 @@ export default class FxControl extends XfAbstractControl {
57
62
 
58
63
  this.widget = this.getWidget();
59
64
  // console.log('widget ', this.widget);
65
+ let listenOn = this.widget // default: usually listening on widget
60
66
 
67
+ if(this.hasAttribute('listen-on')){
68
+ const q = this.getAttribute('listen-on');
69
+ const target = this.querySelector(q);
70
+ if(target){
71
+ listenOn = target;
72
+ }
73
+ }
61
74
  // ### convenience marker event
62
75
  if (this.updateEvent === 'enter') {
63
76
  this.widget.addEventListener('keyup', event => {
@@ -69,32 +82,119 @@ export default class FxControl extends XfAbstractControl {
69
82
  });
70
83
  this.updateEvent = 'blur'; // needs to be registered too
71
84
  }
72
- this.widget.addEventListener(this.updateEvent, () => {
73
- // console.log('eventlistener ', this.updateEvent);
74
- this.setValue(this.widget[this.valueProp]);
85
+ if (this.debounceDelay) {
86
+ listenOn.addEventListener(
87
+ this.updateEvent,
88
+ debounce(() => {
89
+ console.log('eventlistener ', this.updateEvent);
90
+ this.setValue(this.widget[this.valueProp]);
91
+ }, this.debounceDelay),
92
+ );
93
+ } else {
94
+ listenOn.addEventListener(this.updateEvent, () => {
95
+ console.log('eventlistener ', this.updateEvent);
96
+ this.setValue(this.widget[this.valueProp]);
97
+ });
98
+ }
99
+
100
+ this.addEventListener('return', e => {
101
+ console.log('catched return action on ', this);
102
+ console.log('return detail', e.detail);
103
+
104
+ console.log('return triggered on ', this);
105
+ console.log('this.ref', this.ref);
106
+ console.log('current outer instance', this.getInstance());
107
+
108
+ console.log(
109
+ '???? why ???? current nodeset should point to the node of the outer control',
110
+ e.currentTarget.nodeset,
111
+ );
112
+ console.log(
113
+ '???? why ???? current nodeset should point to the node of the outer control',
114
+ this.nodeset,
115
+ );
116
+ const newNodes = e.detail.nodeset;
117
+ console.log('new nodeset', newNodes);
118
+ console.log('currentTarget', e.currentTarget);
119
+ console.log('target', e.target);
120
+
121
+ e.stopPropagation();
122
+
123
+ this._replaceNode(newNodes);
75
124
  });
76
125
 
77
- const slot = this.shadowRoot.querySelector('slot');
78
126
  this.template = this.querySelector('template');
79
127
  // console.log('template',this.template);
80
128
  }
81
129
 
130
+ _debounce(func, timeout = 300) {
131
+ let timer;
132
+ return (...args) => {
133
+ const context = this;
134
+ clearTimeout(timer);
135
+ timer = setTimeout(() => {
136
+ func.apply(context, args);
137
+ }, timeout);
138
+ };
139
+ }
140
+
141
+ /**
142
+ * updates the model with a new value by executing it's `<fx-setvalue>` action.
143
+ *
144
+ * In case the `as='node'` is given the bound node is replaced with the widgets' value with is
145
+ * expected to be a node again.
146
+ *
147
+ * @param val the new value to be set
148
+ */
82
149
  setValue(val) {
83
150
  const modelitem = this.getModelItem();
151
+
152
+ if (modelitem?.readonly){
153
+ console.warn('attempt to change readonly node', modelitem);
154
+ return; // do nothing when modelItem is readonly
155
+ }
156
+
157
+ if (this.getAttribute('as') === 'node') {
158
+ const widgetValue = this.getWidget().value;
159
+ const replace = this.shadowRoot.getElementById('replace');
160
+ replace.replace(this.nodeset, this.getWidget().value);
161
+ if (modelitem && widgetValue && widgetValue !== modelitem.value) {
162
+ modelitem.value = widgetValue;
163
+ replace.actionPerformed();
164
+ }
165
+ return;
166
+ }
84
167
  const setval = this.shadowRoot.getElementById('setvalue');
85
168
  setval.setValue(modelitem, val);
86
169
  setval.actionPerformed();
87
170
  }
88
171
 
172
+ _replaceNode(node) {
173
+ // Note: clone the node while replacing to prevent the instances to leak through
174
+ this.modelItem.node.replaceWith(node.cloneNode(true));
175
+ this.getOwnerForm().refresh();
176
+ }
177
+
89
178
  renderHTML(ref) {
90
179
  return `
91
180
  ${this.label ? `${this.label}` : ''}
92
181
  <slot></slot>
93
- <fx-setvalue id="setvalue" ref="${ref}"></fx-setvalue>
182
+ ${
183
+ this.hasAttribute('as') && this.getAttribute('as') === 'node'
184
+ ? `<fx-replace id="replace" ref=".">`
185
+ : `<fx-setvalue id="setvalue" ref="${ref}"></fx-setvalue>`
186
+ }
187
+
94
188
  `;
95
189
  }
96
190
 
97
191
  /**
192
+ * The widget is the actual control being used in the UI e.g. a native input control or any
193
+ * other component that presents a control that can be interacted with.
194
+ *
195
+ * This function returns the widget by querying the children of this control for an element
196
+ * with `class="widget"`. If that cannot be found it searches for an native `input` of any type.
197
+ * If either cannot be found a `<input type="text">` is created.
98
198
  *
99
199
  * @returns {HTMLElement|*}
100
200
  */
@@ -114,37 +214,201 @@ export default class FxControl extends XfAbstractControl {
114
214
  return widget;
115
215
  }
116
216
 
117
- // todo: check again
217
+ /**
218
+ * updates the widget from the modelItem value. During refresh the a control
219
+ * evaluates it's binding expression to determine the bound node. The bound node corresponds
220
+ * to a modelItem which acts a the state object of a node. The modelItem determines the value
221
+ * and the state of the node and set the `value` property of this class.
222
+ *
223
+ * @returns {Promise<void>}
224
+ */
118
225
  async updateWidgetValue() {
119
226
  // this.widget[this.valueProp] = this.value;
227
+
228
+ let { widget } = this;
229
+ if (!widget) {
230
+ widget = this;
231
+ }
232
+ // ### value is bound to checkbox
120
233
  if (this.valueProp === 'checked') {
121
234
  if (this.value === 'true') {
122
- this.widget.checked = true;
235
+ widget.checked = true;
123
236
  } else {
124
- this.widget.checked = false;
237
+ widget.checked = false;
125
238
  }
126
- } else {
127
- let { widget } = this;
128
- if (!widget) {
129
- widget = this;
239
+ return;
240
+ }
241
+
242
+ if (this.hasAttribute('as')) {
243
+ const as = this.getAttribute('as');
244
+
245
+ // ### when there's an `as=text` attribute serialize nodeset to prettified string
246
+ if (as === 'text') {
247
+ const serializer = new XMLSerializer();
248
+ const pretty = Fore.prettifyXml(serializer.serializeToString(this.nodeset));
249
+ widget.value = pretty;
250
+ }
251
+ if (as === 'node' && this.nodeset !== widget.value) {
252
+ // const oldVal = this.nodeset.innerHTML;
253
+ const oldVal = this.nodeset;
254
+ if (widget.value) {
255
+ if (oldVal !== this.widget.value) {
256
+ console.log('changed');
257
+ widget.value = this.nodeset.cloneNode(true);
258
+ return;
259
+ }
260
+ }
261
+
262
+ widget.value = this.nodeset.cloneNode(true);
263
+ // todo: should be more like below but that can cause infinite loop when controll trigger update event due to calling a setter for property
264
+ // widget[this.valueProp] = this.nodeset.cloneNode(true);
265
+ console.log('passed value to widget', widget.value);
266
+ }
267
+
268
+ return;
269
+ }
270
+
271
+ // ### when there's a url Fore is used as widget and will be loaded from external file
272
+ if (this.url && !this.loaded) {
273
+ // ### evaluate initial data if necessary
274
+ if (this.initial) {
275
+ this.initialNode = evaluateXPathToFirstNode(this.initial, this.nodeset, this);
276
+ console.log('initialNodes', this.initialNode);
130
277
  }
278
+
279
+ // ### load the markup from Url
280
+ await this._loadForeFromUrl();
281
+ this.loaded = true;
282
+
283
+ // ### replace default instance of embedded Fore with initial nodes
284
+ // const innerInstance = this.querySelector('fx-instance');
285
+ // console.log('innerInstance',innerInstance);
286
+ return;
287
+ }
288
+
289
+ /*
290
+ if(this.url && !this.loaded){
291
+ this._loadForeFromUrl();
292
+ this.loaded=true;
293
+ return;
294
+ }
295
+ */
296
+ if (widget.value !== this.value) {
131
297
  widget.value = this.value;
132
298
  }
133
299
  }
134
300
 
301
+ /**
302
+ * loads an external Fore from an HTML file given by `url` attribute.
303
+ *
304
+ * Will look for the `<fx-fore>` element within the returned HTML file and return that element.
305
+ *
306
+ * If that cannot be found an error is dispatched.
307
+ *
308
+ * todo: dispatch link error
309
+ * @private
310
+ */
311
+ async _loadForeFromUrl() {
312
+ console.log('########## loading Fore from ', this.src, '##########');
313
+ try {
314
+ const response = await fetch(this.url, {
315
+ method: 'GET',
316
+ mode: 'cors',
317
+ credentials: 'include',
318
+ headers: {
319
+ 'Content-Type': 'text/html',
320
+ },
321
+ });
322
+ const responseContentType = response.headers.get('content-type').toLowerCase();
323
+ console.log('********** responseContentType *********', responseContentType);
324
+ let data;
325
+ if (responseContentType.startsWith('text/html')) {
326
+ data = await response.text().then(result =>
327
+ // console.log('xml ********', result);
328
+ new DOMParser().parseFromString(result, 'text/html'),
329
+ );
330
+ } else {
331
+ data = 'done';
332
+ }
333
+ // const theFore = fxEvaluateXPathToFirstNode('//fx-fore', data.firstElementChild);
334
+ const theFore = data.querySelector('fx-fore');
335
+ // console.log('thefore', theFore)
336
+ theFore.classList.add('widget'); // is the new widget
337
+ const dummy = this.querySelector('input');
338
+ if (this.hasAttribute('shadow')) {
339
+ dummy.parentNode.removeChild(dummy);
340
+ this.shadowRoot.appendChild(theFore);
341
+ } else {
342
+ dummy.replaceWith(theFore);
343
+ }
344
+
345
+ console.log(`########## loaded fore as component ##### ${this.url}`);
346
+ theFore.addEventListener(
347
+ 'model-construct-done',
348
+ e => {
349
+ console.log('subcomponent ready', e.target);
350
+ const defaultInst = theFore.querySelector('fx-instance');
351
+ console.log('defaultInst', defaultInst);
352
+ if(this.initialNode){
353
+ const doc = new DOMParser().parseFromString('<data></data>', 'application/xml');
354
+ // Note: Clone the input to prevent the inner fore from editing the outer node
355
+ doc.firstElementChild.appendChild(this.initialNode.cloneNode(true));
356
+ // defaultinst.setInstanceData(this.initialNode);
357
+ defaultInst.setInstanceData(doc);
358
+ }
359
+ console.log('new data', defaultInst.getInstanceData());
360
+ // theFore.getModel().modelConstruct();
361
+ theFore.getModel().updateModel();
362
+ theFore.refresh();
363
+ },
364
+ { once: true },
365
+ );
366
+
367
+ if (!theFore) {
368
+ this.dispatchEvent(
369
+ new CustomEvent('error', {
370
+ detail: {
371
+ message: `Fore element not found in '${this.src}'. Maybe wrapped within 'template' element?`,
372
+ },
373
+ }),
374
+ );
375
+ }
376
+ console.log('loaded');
377
+ this.dispatchEvent(new CustomEvent('loaded', { detail: { fore: theFore } }));
378
+ } catch (error) {
379
+ console.log('error', error);
380
+ this.getOwnerForm().dispatchEvent(
381
+ new CustomEvent('error', { detail: { message: `${this.url} not found` } }),
382
+ );
383
+ }
384
+ }
385
+
135
386
  getTemplate() {
136
387
  return this.querySelector('template');
137
388
  }
138
389
 
139
390
  async refresh(force) {
140
391
  // console.log('fx-control refresh', this);
141
- super.refresh();
392
+ super.refresh(force);
142
393
  // console.log('refresh template', this.template);
143
394
  // const {widget} = this;
144
395
 
145
396
  // ### if we find a ref on control we have a 'select' control of some kind
146
397
  const widget = this.getWidget();
147
- if (widget.hasAttribute('ref')) {
398
+ this._handleBoundWidget(widget);
399
+ Fore.refreshChildren(this, force);
400
+ }
401
+
402
+ /**
403
+ * If the widget itself has a `ref` it binds to another nodeset to provide some
404
+ * dynamic items to be created from a template usually. Examples are dynamic select option lists
405
+ * or a set of checkboxes.
406
+ *
407
+ * @param widget the widget to handle
408
+ * @private
409
+ */
410
+ _handleBoundWidget(widget) {
411
+ if (widget && widget.hasAttribute('ref')) {
148
412
  // ### eval nodeset for list control
149
413
  const ref = widget.getAttribute('ref');
150
414
  /*
@@ -158,6 +422,9 @@ export default class FxControl extends XfAbstractControl {
158
422
  // const nodeset = evaluateXPathToNodes(ref, inscope, this);
159
423
  const nodeset = evaluateXPath(ref, inscope, this);
160
424
 
425
+ // ### bail out when nodeset is array and empty
426
+ if (Array.isArray(nodeset) && nodeset.length === 0) return;
427
+
161
428
  // ### clear items
162
429
  const { children } = widget;
163
430
  Array.from(children).forEach(child => {
@@ -168,6 +435,14 @@ export default class FxControl extends XfAbstractControl {
168
435
 
169
436
  // ### build the items
170
437
  if (this.template) {
438
+ if(this.widget.nodeName === "SELECT" &&
439
+ this.widget.hasAttribute('selection') &&
440
+ this.widget.getAttribute('selection') === 'open'){
441
+ const firstTemplateChild=this.template.firstElementChild;
442
+ const option = document.createElement('option');
443
+ this.widget.insertBefore(option,firstTemplateChild);
444
+ }
445
+
171
446
  if (nodeset.length) {
172
447
  // console.log('nodeset', nodeset);
173
448
  Array.from(nodeset).forEach(node => {
@@ -184,7 +459,6 @@ export default class FxControl extends XfAbstractControl {
184
459
  }
185
460
  }
186
461
  }
187
- Fore.refreshChildren(this, force);
188
462
  }
189
463
 
190
464
  updateEntry(newEntry, node) {
@@ -194,7 +468,7 @@ export default class FxControl extends XfAbstractControl {
194
468
  const valueAttribute = this._getValueAttribute(newEntry);
195
469
  const valueExpr = valueAttribute.value;
196
470
  const cutted = valueExpr.substring(1, valueExpr.length - 1);
197
- const evaluated = evaluateXPath(cutted, node, newEntry);
471
+ const evaluated = evaluateXPathToString(cutted, node, newEntry);
198
472
  valueAttribute.value = evaluated;
199
473
 
200
474
  if (this.value === evaluated) {
@@ -230,5 +504,6 @@ export default class FxControl extends XfAbstractControl {
230
504
  return result;
231
505
  }
232
506
  }
233
-
234
- window.customElements.define('fx-control', FxControl);
507
+ if (!customElements.get('fx-control')) {
508
+ window.customElements.define('fx-control', FxControl);
509
+ }
@@ -1,20 +1,21 @@
1
+ import { Fore } from '../fore.js';
2
+
1
3
  export class FxDialog extends HTMLElement {
4
+ static get properties() {
5
+ return {
6
+ id: String,
7
+ };
8
+ }
2
9
 
3
- static get properties() {
4
- return {
5
- id: String
6
- };
7
- }
10
+ constructor() {
11
+ super();
12
+ this.attachShadow({ mode: 'open' });
13
+ }
8
14
 
9
- constructor() {
10
- super();
11
- this.attachShadow({mode: 'open'});
12
- }
13
-
14
- connectedCallback() {
15
- const style = `
15
+ connectedCallback() {
16
+ const style = `
16
17
  :host {
17
- display: none;
18
+ display:none;
18
19
  height: 100vh;
19
20
  width:100vw;
20
21
  position:fixed;
@@ -22,47 +23,60 @@ export class FxDialog extends HTMLElement {
22
23
  top:0;
23
24
  right:0;
24
25
  bottom:0;
26
+ transition:opacity 0.4s linear;
25
27
  }
26
- `;
27
28
 
28
- this.shadowRoot.innerHTML = this.render(style);
29
- this.id = this.getAttribute('id');
29
+ `;
30
30
 
31
- // const dialog = document.getElementById(this.id);
31
+ this.shadowRoot.innerHTML = this.render(style);
32
+ this.id = this.getAttribute('id');
32
33
 
33
- const closeBtn = this.querySelector('.close-dialog');
34
- if (closeBtn) {
35
- closeBtn.addEventListener('click', (e) => {
36
- document.getElementById(this.id).classList.remove('show');
37
- });
38
- }
34
+ // const dialog = document.getElementById(this.id);
39
35
 
40
- this.focus();
36
+ const closeBtn = this.querySelector('.close-dialog');
37
+ if (closeBtn) {
38
+ closeBtn.addEventListener('click', () => {
39
+ this.classList.remove('show');
40
+ });
41
41
  }
42
42
 
43
- render(styles) {
44
- return `
43
+ this.addEventListener('transitionend', () => {
44
+ console.log('transitionend');
45
+ // this.style.display = 'none';
46
+ });
47
+
48
+ this.focus();
49
+ }
50
+
51
+ render(styles) {
52
+ return `
45
53
  <style>
46
54
  ${styles}
47
55
  </style>
48
56
  <slot></slot>
49
57
  `;
50
- }
58
+ }
51
59
 
52
- open(){
53
- window.addEventListener('keyup', (e) => {
54
- if (e.key === "Escape") {
55
- this.hide();
56
- }
57
- },{once:true});
58
-
59
- this.classList.add('show');
60
- }
60
+ open() {
61
+ window.addEventListener(
62
+ 'keyup',
63
+ e => {
64
+ if (e.key === 'Escape') {
65
+ this.hide();
66
+ }
67
+ },
68
+ { once: true },
69
+ );
61
70
 
62
- hide(){
63
- this.classList.remove('show');
64
- }
71
+ this.classList.add('show');
72
+ }
65
73
 
74
+ async hide() {
75
+ await Fore.fadeOutElement(this, 400);
76
+ this.classList.remove('show');
77
+ }
66
78
  }
67
79
 
68
- customElements.define('fx-dialog', FxDialog);
80
+ if (!customElements.get('fx-dialog')) {
81
+ customElements.define('fx-dialog', FxDialog);
82
+ }
@@ -86,4 +86,6 @@ class FxGroup extends FxContainer {
86
86
  }
87
87
  }
88
88
 
89
- window.customElements.define('fx-group', FxGroup);
89
+ if (!customElements.get('fx-group')) {
90
+ window.customElements.define('fx-group', FxGroup);
91
+ }
package/src/ui/fx-hint.js CHANGED
@@ -45,4 +45,7 @@ export class FxHint extends XfAbstractControl {
45
45
  }
46
46
  */
47
47
  }
48
- customElements.define('fx-hint', FxHint);
48
+
49
+ if (!customElements.get('fx-hint')) {
50
+ customElements.define('fx-hint', FxHint);
51
+ }