@jinntec/fore 2.9.0 → 3.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 (42) hide show
  1. package/README.md +0 -2
  2. package/dist/fore-dev.js +9505 -6057
  3. package/dist/fore.js +9218 -5996
  4. package/index.js +1 -0
  5. package/package.json +4 -2
  6. package/src/DependentXPathQueries.js +37 -2
  7. package/src/ForeElementMixin.js +64 -38
  8. package/src/actions/fx-delete.js +424 -73
  9. package/src/actions/fx-insert.js +472 -73
  10. package/src/actions/fx-setattribute.js +5 -5
  11. package/src/actions/fx-setvalue.js +11 -9
  12. package/src/authoring-check.js +231 -0
  13. package/src/fore.js +32 -77
  14. package/src/functions/registerFunction.js +65 -20
  15. package/src/fx-bind.js +128 -73
  16. package/src/fx-fore.js +653 -219
  17. package/src/fx-instance.js +145 -143
  18. package/src/fx-model.js +292 -102
  19. package/src/fx-speech.js +268 -0
  20. package/src/fx-submission.js +246 -135
  21. package/src/fx-var.js +28 -4
  22. package/src/json/JSONDomFacade.js +84 -0
  23. package/src/json/JSONLens.js +67 -0
  24. package/src/json/JSONNode.js +248 -0
  25. package/src/json/lensFromNode.js +5 -0
  26. package/src/modelitem.js +16 -2
  27. package/src/tools/fx-action-log.js +1 -1
  28. package/src/ui/UIElement.js +16 -2
  29. package/src/ui/abstract-control.js +14 -7
  30. package/src/ui/fx-case.js +3 -1
  31. package/src/ui/fx-control.js +4 -2
  32. package/src/ui/fx-group.js +1 -1
  33. package/src/ui/fx-items.js +26 -32
  34. package/src/ui/fx-output.js +8 -38
  35. package/src/ui/fx-repeat-attributes.js +1 -1
  36. package/src/ui/fx-repeat.js +683 -247
  37. package/src/ui/fx-repeatitem.js +16 -1
  38. package/src/ui/repeat-base.js +9 -5
  39. package/src/withDraggability.js +0 -1
  40. package/src/xpath-evaluation.js +1763 -740
  41. package/src/xpath-path.js +274 -24
  42. package/src/xpath-util.js +92 -46
@@ -0,0 +1,84 @@
1
+ // src/json/JSONDomFacade.js
2
+ export class JSONDomFacade {
3
+ // Treat JSONNodes as "element-like" nodes for XPath purposes.
4
+ // FontoXPath uses DOM nodeType numbers internally; 1 corresponds to ELEMENT_NODE.
5
+ getNodeType(/* node */) {
6
+ return 1;
7
+ }
8
+
9
+ getParentNode(node) {
10
+ return node.getParent();
11
+ }
12
+
13
+ getChildNodes(node) {
14
+ return node.getChildren();
15
+ }
16
+
17
+ getChildren(node) {
18
+ return node.getChildren();
19
+ }
20
+
21
+ getChildNodeCount(node) {
22
+ return node.getChildren().length;
23
+ }
24
+
25
+ getFirstChild(node) {
26
+ const children = node.getChildren();
27
+ return children.length ? children[0] : null;
28
+ }
29
+
30
+ getLastChild(node) {
31
+ const children = node.getChildren();
32
+ return children.length ? children[children.length - 1] : null;
33
+ }
34
+
35
+ getNextSibling(node) {
36
+ const parent = node.getParent();
37
+ if (!parent) return null;
38
+ const siblings = parent.getChildren();
39
+ const idx = siblings.indexOf(node);
40
+ return siblings[idx + 1] || null;
41
+ }
42
+
43
+ getPreviousSibling(node) {
44
+ const parent = node.getParent();
45
+ if (!parent) return null;
46
+ const siblings = parent.getChildren();
47
+ const idx = siblings.indexOf(node);
48
+ return idx > 0 ? siblings[idx - 1] : null;
49
+ }
50
+
51
+ getNodeName(node) {
52
+ return String(node.getKey());
53
+ }
54
+
55
+ getNodeValue(node) {
56
+ return node.getValue();
57
+ }
58
+
59
+ /**
60
+ * CRITICAL for fontoxpath: atomization / string-value.
61
+ * If this returns '' for JSON nodes, contains(), string(), lower-case(), etc. will behave as if empty.
62
+ */
63
+ getData(node) {
64
+ const v = node.getValue();
65
+ if (v === null || v === undefined) return '';
66
+ if (typeof v === 'string') return v;
67
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v);
68
+
69
+ // object/array: pragmatic string-value
70
+ try {
71
+ return JSON.stringify(v);
72
+ } catch (_e) {
73
+ return String(v);
74
+ }
75
+ }
76
+
77
+ getAllAttributes() {
78
+ return [];
79
+ }
80
+
81
+ getAttribute() {
82
+ return null;
83
+ }
84
+ }
@@ -0,0 +1,67 @@
1
+ // json-lens.js
2
+ import { JSONNode } from './JSONNode.js';
3
+
4
+ export function wrapJson(value, parent = null, keyOrIndex = null, instanceId = 'default') {
5
+ return new JSONNode(value, parent, keyOrIndex, instanceId);
6
+ }
7
+
8
+ export class JSONLens {
9
+ constructor(root, path = []) {
10
+ this.root = root; // the raw JSON object
11
+ this.path = path; // path to target node, e.g., ['invoice', 'items', 0]
12
+ }
13
+
14
+ _resolveParent() {
15
+ const lastKey = this.path[this.path.length - 1];
16
+ const parentPath = this.path.slice(0, -1);
17
+ const parent = parentPath.reduce((obj, key) => obj?.[key], this.root);
18
+ return [parent, lastKey];
19
+ }
20
+
21
+ get() {
22
+ return this.path.reduce((obj, key) => obj?.[key], this.root);
23
+ }
24
+
25
+ set(value) {
26
+ const [parent, key] = this._resolveParent();
27
+ if (parent !== undefined) {
28
+ parent[key] = value;
29
+ }
30
+ }
31
+
32
+ delete() {
33
+ const [parent, key] = this._resolveParent();
34
+ if (Array.isArray(parent)) {
35
+ parent.splice(key, 1);
36
+ } else if (parent && typeof parent === 'object') {
37
+ delete parent[key];
38
+ }
39
+ }
40
+
41
+ insert(value, keyOrIndex = null) {
42
+ const target = this.get();
43
+
44
+ if (Array.isArray(target)) {
45
+ if (keyOrIndex === null || keyOrIndex >= target.length) {
46
+ target.push(value); // append
47
+ } else {
48
+ target.splice(keyOrIndex, 0, value); // insert at index
49
+ }
50
+ } else if (target && typeof target === 'object') {
51
+ if (typeof keyOrIndex !== 'string') {
52
+ throw new Error('Inserting into an object requires a string key.');
53
+ }
54
+ target[keyOrIndex] = value;
55
+ } else {
56
+ throw new Error('Target is not insertable (must be object or array).');
57
+ }
58
+ }
59
+
60
+ lensForChild(key) {
61
+ return new JSONLens(this.root, this.path.concat(key));
62
+ }
63
+
64
+ pathString() {
65
+ return '/' + this.path.map(k => (typeof k === 'number' ? `[${k}]` : k)).join('/');
66
+ }
67
+ }
@@ -0,0 +1,248 @@
1
+ // json-node.js
2
+
3
+ export class JSONNode {
4
+ constructor(value, parent = null, keyOrIndex = null, instanceId = 'default') {
5
+ this.value = value;
6
+ this.parent = parent;
7
+ this.keyOrIndex = keyOrIndex;
8
+ this.instanceId = instanceId;
9
+ this.__jsonlens__ = true;
10
+
11
+ if (Array.isArray(value)) {
12
+ this.children = value.map((child, index) => new JSONNode(child, this, index, instanceId));
13
+ } else if (typeof value === 'object' && value !== null) {
14
+ this.children = Object.entries(value).map(
15
+ ([key, val]) => new JSONNode(val, this, key, instanceId),
16
+ );
17
+ } else {
18
+ this.children = [];
19
+ }
20
+ }
21
+
22
+ // Methods for JSONDomFacade compatibility
23
+ getParent() {
24
+ return this.parent;
25
+ }
26
+
27
+ getChildren() {
28
+ return this.children;
29
+ }
30
+
31
+ getKey() {
32
+ return this.keyOrIndex;
33
+ }
34
+
35
+ getValue() {
36
+ return this.value;
37
+ }
38
+
39
+ /**
40
+ * Get the value of this node or a child node by key.
41
+ * @param {string|number} [key] - Optional key or index to get a child node
42
+ * @returns {*|JSONNode|undefined} The raw value of this node or a child node
43
+ */
44
+ get(key) {
45
+ // If no key is provided, return the raw value of this node
46
+ if (arguments.length === 0) {
47
+ return this.value;
48
+ }
49
+
50
+ // Otherwise, get child node by key or index
51
+ if (Array.isArray(this.value) && typeof key === 'number') {
52
+ return this.children[key];
53
+ }
54
+ if (typeof this.value === 'object' && this.value !== null) {
55
+ return this.children.find(c => c.keyOrIndex === key);
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ /**
61
+ * Get the XPath-style path to this node including instance prefix.
62
+ * @returns {string|null}
63
+ */
64
+ getPath() {
65
+ // Detached: no parent and no key
66
+ const isDetached = this.parent === null && this.keyOrIndex === null;
67
+
68
+ // Special case: this is NOT the original root (value-only root has no children)
69
+ if (isDetached && this.children?.length === 0) {
70
+ return null;
71
+ }
72
+
73
+ const segments = [];
74
+ let node = this;
75
+
76
+ while (node.parent !== null) {
77
+ const key = node.keyOrIndex;
78
+
79
+ if (key === null || key === undefined) return null;
80
+
81
+ if (typeof key === 'number') {
82
+ segments.unshift(`[${key + 1}]`);
83
+ } else if (/^[a-zA-Z_][\w\-]*$/.test(key)) {
84
+ segments.unshift(`/${key}`);
85
+ } else {
86
+ const escaped = String(key).replace(/'/g, `''`);
87
+ segments.unshift(`/'${escaped}'`);
88
+ }
89
+
90
+ node = node.parent;
91
+ }
92
+
93
+ return `$${this.instanceId}${segments.join('')}`;
94
+ }
95
+
96
+ /**
97
+ * Set the raw value of this node.
98
+ * @param {*} value
99
+ */
100
+ set(value) {
101
+ this.value = value;
102
+
103
+ // Update the parent's data structure if this is a child node
104
+ if (this.parent && this.keyOrIndex !== null && this.keyOrIndex !== undefined) {
105
+ this.parent.value[this.keyOrIndex] = value;
106
+ }
107
+
108
+ this.children = [];
109
+ if (Array.isArray(value)) {
110
+ this.children = value.map(
111
+ (child, index) => new JSONNode(child, this, index, this.instanceId),
112
+ );
113
+ } else if (typeof value === 'object' && value !== null) {
114
+ this.children = Object.entries(value).map(
115
+ ([key, val]) => new JSONNode(val, this, key, this.instanceId),
116
+ );
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Insert into array or object node.
122
+ * @param {*} value
123
+ * @param {string|number|null} keyOrIndex
124
+ */
125
+ insert(value, keyOrIndex = null) {
126
+ if (Array.isArray(this.value)) {
127
+ const index = keyOrIndex === null ? this.value.length : keyOrIndex;
128
+ this.value.splice(index, 0, value);
129
+ this.children.splice(index, 0, new JSONNode(value, this, index, this.instanceId));
130
+ // update keyOrIndex of all children after insertion
131
+ for (let i = index + 1; i < this.children.length; i++) {
132
+ this.children[i].keyOrIndex = i;
133
+ }
134
+ } else if (typeof this.value === 'object') {
135
+ if (typeof keyOrIndex !== 'string') {
136
+ throw new Error('Insert into object requires a string key.');
137
+ }
138
+ this.value[keyOrIndex] = value;
139
+ this.children.push(new JSONNode(value, this, keyOrIndex, this.instanceId));
140
+ } else {
141
+ throw new Error('Cannot insert into primitive value.');
142
+ }
143
+ }
144
+
145
+ delete() {
146
+ if (!this.parent || this.keyOrIndex == null) return;
147
+
148
+ const parentVal = this.parent.value;
149
+
150
+ if (Array.isArray(parentVal)) {
151
+ parentVal.splice(this.keyOrIndex, 1);
152
+ this.parent.set(parentVal);
153
+ } else if (typeof parentVal === 'object' && parentVal !== null) {
154
+ delete parentVal[this.keyOrIndex];
155
+ this.parent.set(parentVal);
156
+ } else {
157
+ // Parent is not deletable (primitive etc.)
158
+ throw new Error('Parent is not an object or array — cannot delete child');
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Wrap a raw JSON value as a lensable node tree.
165
+ * @param {*} value
166
+ * @param {*} parent
167
+ * @param {*} keyOrIndex
168
+ * @param {string} instanceId
169
+ * @returns {JSONNode}
170
+ */
171
+ export function wrapJson(value, parent = null, keyOrIndex = null, instanceId = 'default') {
172
+ const jsonNode = new JSONNode(value, parent, keyOrIndex, instanceId);
173
+ console.log('wrapJson', jsonNode);
174
+ return jsonNode;
175
+ // return new JSONNode(value, parent, keyOrIndex, instanceId);
176
+ }
177
+
178
+ /**
179
+ * Returns the correct lens for a JSON node based on its parent and key/index.
180
+ * Assumes the node is already in a wrapped structure, or retrievable via parent.
181
+ *
182
+ * @param {any} node - The JSON node (primitive value or structure)
183
+ * @param {JSONNode} parent - The parent JSONNode
184
+ * @param {string|number} keyOrIndex - The key (for objects) or index (for arrays)
185
+ * @returns {JSONNode} A JSONNode wrapping the value with lens and context
186
+ */
187
+
188
+ /**
189
+ * Returns the correct lens for a JSON node based on its parent and key/index.
190
+ * Assumes the node is already in a wrapped structure, or retrievable via parent.
191
+ *
192
+ * @param {any} node - The JSON node (primitive value or structure)
193
+ * @param {JSONNode} parent - The parent JSONNode
194
+ * @param {string|number} keyOrIndex - The key (for objects) or index (for arrays)
195
+ * @returns {JSONNode} A JSONNode wrapping the value with lens and context
196
+ */
197
+ /*
198
+ export function getLensForNode(node, parent, keyOrIndex) {
199
+ if (!parent || !parent.__jsonlens__) {
200
+ console.warn('getLensForNode called without proper parent lens');
201
+ return node;
202
+ }
203
+
204
+ const lens = parent.__jsonlens__.lens;
205
+ const wrapped = lens.get(parent.value, keyOrIndex);
206
+ return wrapped;
207
+ }
208
+ */
209
+
210
+ /**
211
+ * Attempts to wrap a raw value into a JSONNode, using fallback logic if needed.
212
+ * @param {any} value - The raw value to wrap.
213
+ * @param {JSONNode|null} parent - The parent JSONNode (if known).
214
+ * @param {string|number|null} key - The key/index in the parent (if known).
215
+ * @param {string} instanceId - The ID of the instance.
216
+ * @param {Object} instanceRoot - The full wrapped instance root, to help recover parent/key.
217
+ * @returns {JSONNode|any}
218
+ */
219
+ export function getLensForNode(value, parent = null, key = null, instanceId, instanceRoot = null) {
220
+ if (value?.__jsonlens__) return value;
221
+
222
+ // Attempt fallback recovery if key or parent missing
223
+ if ((!parent || typeof key === 'undefined') && instanceRoot) {
224
+ const queue = [instanceRoot];
225
+ while (queue.length) {
226
+ const current = queue.shift();
227
+ if (!current?.children) continue;
228
+
229
+ for (const child of current.children) {
230
+ if (child.value === value) {
231
+ parent = current;
232
+ key = child.keyOrIndex;
233
+ break;
234
+ } else {
235
+ queue.push(child);
236
+ }
237
+ }
238
+ if (parent) break;
239
+ }
240
+
241
+ if (!parent || typeof key === 'undefined') {
242
+ console.warn('[getLensForNode] Unable to determine parent/key for value:', value);
243
+ return value; // Bail out, return raw value
244
+ }
245
+ }
246
+
247
+ return new JSONNode(value, parent, key, instanceId);
248
+ }
@@ -0,0 +1,5 @@
1
+ import { JSONLens } from './JSONLens.js';
2
+
3
+ export function lensFromNode(jsonRoot, jsonNode) {
4
+ return new JSONLens(jsonRoot, jsonNode.getPath());
5
+ }
package/src/modelitem.js CHANGED
@@ -25,7 +25,7 @@ export class ModelItem {
25
25
  * @param {string} instance - The fx-instance id having created this ModelItem
26
26
  * @param {import('./fx-fore').FxFore} fore - The fx-fore element this ModelItem belongs to
27
27
  */
28
- constructor(path, ref, node, bind, instance, fore) {
28
+ constructor(path, ref, nodeOrLens, bind, instance, fore) {
29
29
  this.path = path;
30
30
  this.ref = ref;
31
31
  this.readonly = ModelItem.READONLY_DEFAULT;
@@ -33,7 +33,13 @@ export class ModelItem {
33
33
  this.required = ModelItem.REQUIRED_DEFAULT;
34
34
  this.constraint = ModelItem.CONSTRAINT_DEFAULT;
35
35
  this.type = ModelItem.TYPE_DEFAULT;
36
- this.node = node;
36
+ this.node = null;
37
+ this.lens = null;
38
+ if (nodeOrLens?.get && nodeOrLens?.set) {
39
+ this.lens = nodeOrLens;
40
+ } else {
41
+ this.node = nodeOrLens;
42
+ }
37
43
  this.bind = bind;
38
44
  this.instanceId = instance;
39
45
  this.fore = fore;
@@ -59,6 +65,7 @@ export class ModelItem {
59
65
  }
60
66
 
61
67
  get value() {
68
+ if (this.lens) return this.lens.get();
62
69
  if (!this.node) return null;
63
70
  if (!this.node.nodeType) return this.node;
64
71
  if (this.node.nodeType === Node.ATTRIBUTE_NODE) {
@@ -68,6 +75,13 @@ export class ModelItem {
68
75
  }
69
76
 
70
77
  set value(newVal) {
78
+ if (this.lens) {
79
+ const oldVal = this.lens.get();
80
+ this.lens.set(newVal);
81
+ if (oldVal !== newVal) this.notify();
82
+ return;
83
+ }
84
+
71
85
  if (!this.node) return;
72
86
  const oldVal = this.value;
73
87
 
@@ -521,7 +521,7 @@ export class FxActionLog extends HTMLElement {
521
521
  */
522
522
  _logDetails(e) {
523
523
  const eventType = e.type;
524
- const path = getPath(e.target, '');
524
+ const path = getDocPath(e.target);
525
525
  // console.log('>>>> _logDetails', path);
526
526
  const cut = path.substring(path.indexOf('/fx-fore'), path.length);
527
527
  const xpath = `/${cut}`;
@@ -1,7 +1,6 @@
1
1
  import ForeElementMixin from '../ForeElementMixin.js';
2
2
  import { Fore } from '../fore.js';
3
- import { evaluateXPath, evaluateXPathToBoolean, resolveId } from '../xpath-evaluation';
4
- import { DependencyNotifyingDomFacade } from '../DependencyNotifyingDomFacade';
3
+ import { resolveId } from '../xpath-evaluation.js';
5
4
 
6
5
  export class UIElement extends ForeElementMixin {
7
6
  constructor() {
@@ -120,6 +119,8 @@ export class UIElement extends ForeElementMixin {
120
119
  * Called by ModelItem when it changes
121
120
  * @param {import('../modelitem.js').ModelItem} modelItem - The ModelItem that changed
122
121
  */
122
+
123
+ /*
123
124
  update(modelItem) {
124
125
  if (this.isBound()) {
125
126
  // console.log('[UIElement] update()', modelItem);
@@ -127,7 +128,20 @@ export class UIElement extends ForeElementMixin {
127
128
  this.refresh();
128
129
  }
129
130
  }
131
+ */
132
+
133
+ update(_modelItem) {
134
+ if (!this.isBound()) return;
135
+ const fore = this.getOwnerForm();
136
+ if (!fore) return;
130
137
 
138
+ // Preserve legacy eager updates unless we're already in a refresh phase.
139
+ if (fore.isRefreshPhase) {
140
+ fore.addToBatchedNotifications(this);
141
+ } else {
142
+ this.refresh();
143
+ }
144
+ }
131
145
  // init() {
132
146
  // throw new Error('You have to implement the method init!');
133
147
  // }
@@ -335,17 +335,25 @@ export default class AbstractControl extends UIElement {
335
335
  }
336
336
 
337
337
  // todo - review alert handling altogether. There could be potentially multiple ones in model
338
+ // TODO: both required and handleValid set valid attrs and aria attrs. Duplicate code
338
339
  handleValid() {
339
340
  // console.log('mip valid', this.modelItem.required);
340
341
 
341
342
  // console.log('late modelItem', mi);
342
- if (this.isValid() !== this.modelItem.constraint) {
343
- if (this.modelItem.constraint) {
343
+ const hasValue = this.modelItem.value !== '';
344
+ const isRequired = this.modelItem.required;
345
+ const isValidAccordingToRequired = isRequired ? hasValue : true;
346
+ const isValidNow = this.modelItem.constraint && isValidAccordingToRequired;
347
+
348
+ if (this.isValid() !== isValidNow) {
349
+ if (isValidNow) {
344
350
  // if (alert) alert.style.display = 'none';
345
351
  this._dispatchEvent('valid');
346
352
  this.setAttribute('valid', '');
347
353
  this.removeAttribute('invalid');
348
354
  this.getWidget().setAttribute('aria-invalid', 'false');
355
+ // also reset other dependent CSS classes
356
+ this.classList.remove('isEmpty');
349
357
  } else {
350
358
  this.setAttribute('invalid', '');
351
359
  this.getWidget().setAttribute('aria-invalid', 'true');
@@ -379,7 +387,6 @@ export default class AbstractControl extends UIElement {
379
387
  // Ensure aria-invalid matches the current control state even if
380
388
  // we didn't enter the state-change branch above.
381
389
  this._syncAriaInvalid();
382
-
383
390
  }
384
391
 
385
392
  handleRelevant() {
@@ -400,12 +407,12 @@ export default class AbstractControl extends UIElement {
400
407
 
401
408
  // Apply attributes
402
409
  if (newEnabled) {
403
- this.setAttribute('relevant', '');
410
+ this.setAttribute('relevant', '');
404
411
  this.removeAttribute('nonrelevant');
405
- } else {
406
- this.setAttribute('nonrelevant', '');
412
+ } else {
413
+ this.setAttribute('nonrelevant', '');
407
414
  this.removeAttribute('relevant');
408
- }
415
+ }
409
416
 
410
417
  // Dispatch only on actual change
411
418
  if (wasEnabled !== newEnabled) {
package/src/ui/fx-case.js CHANGED
@@ -65,6 +65,7 @@ export class FxCase extends FxContainer {
65
65
 
66
66
  this.addEventListener('select', async () => {
67
67
  const ownerForm = this.getOwnerForm();
68
+ let target = this;
68
69
  if (this.src) {
69
70
  // We will replace the node. So this node will be detached after these async function
70
71
  // calls. Save all important state first.
@@ -79,9 +80,10 @@ export class FxCase extends FxContainer {
79
80
  return;
80
81
  }
81
82
  await parentNode.replaceCase(this, replacement);
83
+ target = replacement;
82
84
  }
83
85
  const model = ownerForm.getModel();
84
- ownerForm.addToBatchedNotifications(this);
86
+ ownerForm.addToBatchedNotifications(target);
85
87
  ownerForm.refresh(false);
86
88
  });
87
89
  this.addEventListener('deselect', (event) => {
@@ -441,7 +441,7 @@ export default class FxControl extends XfAbstractControl {
441
441
  }
442
442
 
443
443
  // ### when there's a src Fore is used as widget and will be loaded from external file
444
- if (this.src && !this.loaded && this.modelItem.relevant) {
444
+ if (this.src && !this.loaded && !this.loading && this.modelItem.relevant) {
445
445
  // ### evaluate initial data if necessary
446
446
 
447
447
  if (this.initial) {
@@ -449,9 +449,11 @@ export default class FxControl extends XfAbstractControl {
449
449
  // console.log('initialNodes', this.initialNode);
450
450
  }
451
451
 
452
+ this.loading = true;
452
453
  // ### load the markup from src
453
454
  await this._loadForeFromSrc();
454
455
  this.loaded = true;
456
+ this.loading = false;
455
457
 
456
458
  // ### replace default instance of embedded Fore with initial nodes
457
459
  // const innerInstance = this.querySelector('fx-instance');
@@ -577,7 +579,7 @@ export default class FxControl extends XfAbstractControl {
577
579
  } finally {
578
580
  this._isRefreshing = false;
579
581
  }
580
- Fore.refreshChildren(this, force);
582
+ await Fore.refreshChildren(this, force);
581
583
  }
582
584
 
583
585
  /**
@@ -93,7 +93,7 @@ class FxGroup extends FxContainer {
93
93
  super.refresh(force);
94
94
  // Make the maybe filtered refresh an unconditional forced refresh: This fx-group changes the
95
95
  // context item
96
- Fore.refreshChildren(this, !!force);
96
+ return Fore.refreshChildren(this, !!force);
97
97
  }
98
98
 
99
99
  // todo: this code should go