@jinntec/fore 2.6.0 → 2.7.1

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 (53) hide show
  1. package/dist/fore-dev.js +4472 -2645
  2. package/dist/fore.js +4417 -2622
  3. package/index.js +2 -0
  4. package/package.json +10 -4
  5. package/resources/fore.css +2 -6
  6. package/src/DependencyNotifyingDomFacade.js +27 -21
  7. package/src/ForeElementMixin.js +282 -277
  8. package/src/actions/abstract-action.js +7 -12
  9. package/src/actions/fx-append.js +23 -2
  10. package/src/actions/fx-call.js +2 -2
  11. package/src/actions/fx-delete.js +54 -29
  12. package/src/actions/fx-insert.js +23 -15
  13. package/src/actions/fx-load.js +2 -2
  14. package/src/actions/fx-refresh.js +15 -5
  15. package/src/actions/fx-replace.js +18 -3
  16. package/src/actions/fx-reset.js +6 -0
  17. package/src/actions/fx-send.js +10 -7
  18. package/src/actions/fx-setattribute.js +12 -0
  19. package/src/actions/fx-setfocus.js +11 -8
  20. package/src/actions/fx-setvalue.js +93 -93
  21. package/src/actions/fx-toggle.js +1 -1
  22. package/src/extract-predicate-deps.js +57 -0
  23. package/src/extractPredicateDependencies.js +36 -0
  24. package/src/fore.js +41 -16
  25. package/src/fx-bind.js +128 -23
  26. package/src/fx-connection.js +24 -7
  27. package/src/fx-fore.js +506 -259
  28. package/src/fx-instance.js +9 -11
  29. package/src/fx-model.js +154 -65
  30. package/src/fx-submission.js +19 -30
  31. package/src/fx-var.js +5 -0
  32. package/src/getInScopeContext.js +7 -8
  33. package/src/modelitem.js +247 -112
  34. package/src/tools/fx-action-log.js +21 -19
  35. package/src/tools/fx-log-settings.js +4 -2
  36. package/src/ui/TemplateExpression.js +12 -0
  37. package/src/ui/UIElement.js +125 -10
  38. package/src/ui/abstract-control.js +5 -0
  39. package/src/ui/fx-case.js +15 -3
  40. package/src/ui/fx-container.js +5 -0
  41. package/src/ui/fx-control-menu.js +23 -14
  42. package/src/ui/fx-control.js +55 -15
  43. package/src/ui/fx-items.js +10 -4
  44. package/src/ui/fx-repeat-attributes.js +111 -35
  45. package/src/ui/fx-repeat.js +332 -85
  46. package/src/ui/fx-repeatitem.js +23 -20
  47. package/src/ui/fx-switch.js +5 -3
  48. package/src/ui/fx-upload.js +36 -40
  49. package/src/ui/repeat-base.js +532 -0
  50. package/src/withDraggability.js +8 -4
  51. package/src/xpath-evaluation.js +26 -8
  52. package/src/xpath-path.js +79 -0
  53. package/src/xpath-util.js +357 -289
@@ -11,43 +11,43 @@ import getInScopeContext from '../getInScopeContext.js';
11
11
  * @customElement
12
12
  */
13
13
  export default class FxSetvalue extends AbstractAction {
14
- static get properties() {
15
- return {
16
- ...super.properties,
17
- ref: {
18
- type: String,
19
- },
20
- valueAttr: {
21
- type: String,
22
- },
23
- };
24
- }
14
+ static get properties() {
15
+ return {
16
+ ...super.properties,
17
+ ref: {
18
+ type: String,
19
+ },
20
+ valueAttr: {
21
+ type: String,
22
+ },
23
+ };
24
+ }
25
25
 
26
- constructor() {
27
- super();
28
- this.ref = '';
29
- this.valueAttr = '';
30
- }
26
+ constructor() {
27
+ super();
28
+ this.ref = '';
29
+ this.valueAttr = '';
30
+ }
31
31
 
32
- connectedCallback() {
33
- if (super.connectedCallback) {
34
- super.connectedCallback();
35
- }
32
+ connectedCallback() {
33
+ if (super.connectedCallback) {
34
+ super.connectedCallback();
35
+ }
36
36
 
37
- if (this.hasAttribute('ref')) {
38
- this.ref = this.getAttribute('ref');
39
- } else {
40
- throw new Error('fx-setvalue must specify a "ref" attribute');
41
- }
42
- this.valueAttr = this.getAttribute('value');
37
+ if (this.hasAttribute('ref')) {
38
+ this.ref = this.getAttribute('ref');
39
+ } else {
40
+ throw new Error('fx-setvalue must specify a "ref" attribute');
43
41
  }
42
+ this.valueAttr = this.getAttribute('value');
43
+ }
44
44
 
45
- async perform() {
46
- super.perform();
47
- let { value } = this;
48
- if (this.valueAttr !== null) {
49
- const inscopeContext = getInScopeContext(this, this.valueAttr);
50
- /*
45
+ async perform() {
46
+ super.perform();
47
+ let { value } = this;
48
+ if (this.valueAttr !== null) {
49
+ const inscopeContext = getInScopeContext(this, this.valueAttr);
50
+ /*
51
51
  todo: review @martin - shouldn't we always return a string value?
52
52
  this comes down to the question if setvalue should only allow setting of strings
53
53
  which i tend to agree. Can't remember a case where i wanted to set an attribute
@@ -58,76 +58,76 @@ export default class FxSetvalue extends AbstractAction {
58
58
  or getting unexpected results.
59
59
  */
60
60
 
61
- [value] = evaluateXPath(
62
- this.valueAttr,
63
- inscopeContext,
64
- this,
65
- this.detail,
66
- );
67
- } else if (this.textContent !== '') {
68
- value = this.textContent;
69
- } else {
70
- value = '';
71
- }
72
- if (value?.nodeType && value.nodeType === Node.ATTRIBUTE_NODE) {
73
- value = value.nodeValue;
74
- }
75
- const mi = this.getModelItem();
76
- this.setValue(mi, value);
77
- // todo: check this again - logically needsUpate should be set but makes tests fail
78
- // this.needsUpdate = true;
61
+ [value] = evaluateXPath(this.valueAttr, inscopeContext, this, this.detail);
62
+ } else if (this.textContent !== '') {
63
+ value = this.textContent;
64
+ } else {
65
+ value = '';
66
+ }
67
+ if (value?.nodeType && value.nodeType === Node.ATTRIBUTE_NODE) {
68
+ value = value.nodeValue;
79
69
  }
80
70
 
81
- /**
82
- * need to overwrite default dispatchExecute to do it ourselves. This is necessary for tracking control value changes
83
- * which call setvalue directly without perform().
84
- */
85
- dispatchExecute() {}
71
+ // Get the ModelItem without re-evaluating the context
72
+ const mi = this.getModelItem();
73
+
74
+ this.setValue(mi, value);
75
+ // todo: check this again - logically needsUpate should be set but makes tests fail
76
+ // this.needsUpdate = true;
77
+ }
86
78
 
87
- setValue(modelItem, newVal) {
88
- const item = modelItem;
89
- if (!item) return;
79
+ /**
80
+ * need to overwrite default dispatchExecute to do it ourselves. This is necessary for tracking control value changes
81
+ * which call setvalue directly without perform().
82
+ */
83
+ dispatchExecute() {}
90
84
 
91
- if (item.value !== newVal) {
92
- // const path = XPathUtil.getPath(modelItem.node);
93
- const path = Fore.getDomNodeIndexString(modelItem.node);
85
+ setValue(modelItem, newVal) {
86
+ console.log('setValue', modelItem, newVal);
87
+ const item = modelItem;
88
+ if (!item) return;
94
89
 
95
- const ev = this.event;
96
- const targetElem = this;
97
- this.dispatchEvent(
98
- new CustomEvent('execute-action', {
99
- composed: true,
100
- bubbles: true,
101
- cancelable: true,
102
- detail: {
103
- action: targetElem,
104
- event: ev,
105
- value: newVal,
106
- path,
107
- },
108
- }),
109
- );
90
+ if (item.value !== newVal) {
91
+ // const path = XPathUtil.getPath(modelItem.node);
92
+ const path = Fore.getDomNodeIndexString(modelItem.node);
110
93
 
111
- if (newVal?.nodeType) {
112
- if (newVal.nodeType === Node.ELEMENT_NODE) {
113
- item.value = newVal;
114
- }
115
- if (newVal.nodeType === Node.ATTRIBUTE_NODE) {
116
- item.value = newVal.getValue();
117
- }
118
- if (newVal.nodeType === Node.TEXT_NODE) {
119
- item.value = newVal.textContent;
120
- }
121
- } else {
122
- item.value = newVal;
123
- item.node.textContent = newVal;
124
- }
125
- this.getModel().changed.push(modelItem);
126
- this.needsUpdate = true;
94
+ const ev = this.event;
95
+ const targetElem = this;
96
+ this.dispatchEvent(
97
+ new CustomEvent('execute-action', {
98
+ composed: true,
99
+ bubbles: true,
100
+ cancelable: true,
101
+ detail: {
102
+ action: targetElem,
103
+ event: ev,
104
+ value: newVal,
105
+ path,
106
+ },
107
+ }),
108
+ );
109
+
110
+ if (newVal?.nodeType) {
111
+ if (newVal.nodeType === Node.ELEMENT_NODE) {
112
+ item.value = newVal;
113
+ }
114
+ if (newVal.nodeType === Node.ATTRIBUTE_NODE) {
115
+ item.value = newVal.getValue();
116
+ }
117
+ if (newVal.nodeType === Node.TEXT_NODE) {
118
+ item.value = newVal.textContent;
127
119
  }
120
+ } else {
121
+ item.value = newVal;
122
+ item.node.textContent = newVal;
123
+ }
124
+ this.getModel().changed.push(modelItem);
125
+ this.needsUpdate = true;
126
+ modelItem.notify();
128
127
  }
128
+ }
129
129
  }
130
130
 
131
131
  if (!customElements.get('fx-setvalue')) {
132
- window.customElements.define('fx-setvalue', FxSetvalue);
132
+ window.customElements.define('fx-setvalue', FxSetvalue);
133
133
  }
@@ -32,7 +32,7 @@ class FxToggle extends AbstractAction {
32
32
  const fxSwitch = caseElement.parentNode;
33
33
  fxSwitch.toggle(caseElement);
34
34
  }
35
- this.needsUpdate = true;
35
+ // this.needsUpdate = true;
36
36
  }
37
37
  }
38
38
 
@@ -0,0 +1,57 @@
1
+ // extract-predicate-deps.js
2
+ import { DependencyNotifyingDomFacade } from './DependencyNotifyingDomFacade.js';
3
+ import { evaluateXPathToBoolean } from './xpath-evaluation.js';
4
+
5
+ /**
6
+ * Extracts an instance ID from an XPath expression like instance('myInstance')/something
7
+ * @param {string} xpathExpr
8
+ * @returns {string|null} The extracted instance ID or null if not found
9
+ */
10
+ function extractInstanceIdFromXPath(xpathExpr) {
11
+ const instanceRegex = /instance\(['"]([^)'"]+)['"]\)/;
12
+ const match = xpathExpr.match(instanceRegex);
13
+ return match ? match[1] : null;
14
+ }
15
+
16
+ /**
17
+ * Gets the context node for a predicate XPath expression.
18
+ * @param {string} predicateExpr
19
+ * @param {import('./fx-model.js').FxModel} model
20
+ * @returns {Node}
21
+ */
22
+ function getContextNodeForPredicate(predicateExpr, model) {
23
+ const instanceId = extractInstanceIdFromXPath(predicateExpr) || 'default';
24
+ const instance = model.getInstance(instanceId);
25
+ return instance.getDefaultContext();
26
+ }
27
+
28
+ /**
29
+ * Extracts and registers dependencies from XPath predicates.
30
+ *
31
+ * @param {string} ref - The XPath expression possibly containing predicates.
32
+ * @param {import('./fx-model.js').FxModel} model - The model to resolve instances from.
33
+ * @param {(modelItem: import('./modelitem.js').ModelItem) => void} register - A callback to register observers.
34
+ * @param {(node: Node) => import('./modelitem.js').ModelItem} resolveModelItem - A callback to resolve modelItems.
35
+ */
36
+ export function extractPredicateDependencies(ref, contextNode, register, resolveModelItem) {
37
+ const predicateRegex = /\[(.*?)\]/g;
38
+ let match;
39
+
40
+ while ((match = predicateRegex.exec(ref)) !== null) {
41
+ const predicate = match[1];
42
+ try {
43
+ const domFacade = new DependencyNotifyingDomFacade(node => {
44
+ const mi = resolveModelItem(node);
45
+ if (mi) {
46
+ register(mi);
47
+ console.log(`[PredicateDependency] Observing ${mi.path} from predicate: [${predicate}]`);
48
+ }
49
+ });
50
+
51
+ const predContext = getContextNodeForPredicate(predicate, contextNode);
52
+ evaluateXPathToBoolean(predicate, predContext, { dispatchEvent() {} }, domFacade);
53
+ } catch (e) {
54
+ console.warn('Failed to evaluate predicate expression:', predicate, e);
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,36 @@
1
+ // extract-predicate-deps.js
2
+ import { DependencyNotifyingDomFacade } from './DependencyNotifyingDomFacade.js';
3
+ import { evaluateXPathToBoolean } from './xpath-evaluation.js';
4
+
5
+ /**
6
+ * Extracts and registers dependencies from XPath predicates.
7
+ *
8
+ * @param {string} ref - The XPath expression possibly containing predicates.
9
+ * @param {Node} contextNode - The context node for evaluation.
10
+ * @param {(modelItem: import('./modelitem.js').ModelItem) => void} register - A callback to register observers.
11
+ * @param {(node: Node) => import('./modelitem.js').ModelItem} resolveModelItem - A callback to resolve modelItems.
12
+ */
13
+ export function extractPredicateDependencies(ref, contextNode, register, resolveModelItem) {
14
+ const predicateRegex = /\[(.*?)\]/g;
15
+ let match;
16
+ const touchedNodes = new Set();
17
+
18
+ while ((match = predicateRegex.exec(ref)) !== null) {
19
+ const predicate = match[1];
20
+ try {
21
+ const domFacade = new DependencyNotifyingDomFacade(n => touchedNodes.add(n));
22
+ const fakeContext = { dispatchEvent: () => {} }; // prevent null dispatchEvent crash
23
+ evaluateXPathToBoolean(predicate, contextNode, undefined, domFacade);
24
+
25
+ touchedNodes.forEach(node => {
26
+ const mi = resolveModelItem(node);
27
+ if (mi) {
28
+ register(mi);
29
+ console.log(`[PredicateDependency] Observing ${mi.path} from predicate: [${predicate}]`);
30
+ }
31
+ });
32
+ } catch (e) {
33
+ console.warn('Failed to evaluate predicate expression:', predicate, e);
34
+ }
35
+ }
36
+ }
package/src/fore.js CHANGED
@@ -17,7 +17,7 @@ export class Fore {
17
17
 
18
18
  static REQUIRED_DEFAULT = false;
19
19
 
20
- static RELEVANT_DEFAULT = true;
20
+ static RELEVANT_DEFAULT = true
21
21
 
22
22
  static CONSTRAINT_DEFAULT = true;
23
23
 
@@ -230,7 +230,7 @@ export class Fore {
230
230
  }
231
231
 
232
232
  static get UI_ELEMENTS() {
233
- return [
233
+ return new Set([
234
234
  'FX-ALERT',
235
235
  'FX-CONTROL',
236
236
  'FX-DIALOG',
@@ -252,11 +252,7 @@ export class Fore {
252
252
  'FX-TRIGGER',
253
253
  'FX-UPLOAD',
254
254
  'FX-VAR',
255
- ];
256
- }
257
-
258
- static get MODEL_ELEMENTS() {
259
- return ['FX-BIND', 'FX-FUNCTION', 'FX-MODEL', 'FX-INSTANCE', 'FX-SUBMISSION'];
255
+ ]);
260
256
  }
261
257
 
262
258
  /**
@@ -264,13 +260,30 @@ export class Fore {
264
260
  * @returns {boolean}
265
261
  */
266
262
  static isUiElement(elementName) {
267
- const found = Fore.UI_ELEMENTS.includes(elementName);
268
- if (found) {
269
- // console.log('_isUiElement ', found);
270
- }
271
- return Fore.UI_ELEMENTS.includes(elementName);
263
+ return Fore.UI_ELEMENTS.has(elementName);
264
+ }
265
+
266
+ static get MODEL_ELEMENTS() {
267
+ return ['FX-BIND', 'FX-FUNCTION', 'FX-MODEL', 'FX-INSTANCE', 'FX-SUBMISSION'];
272
268
  }
273
269
 
270
+
271
+ static async initUI(startElement) {
272
+ const inited = new Promise(resolve => {
273
+ const { children } = startElement;
274
+ if (children) {
275
+ for (const element of Array.from(children)) {
276
+ if (element.nodeName.toUpperCase() === 'FX-FORE') {
277
+ break;
278
+ }
279
+ if (Fore.isUiElement(element.nodeName) && typeof element.refresh === 'function') {
280
+ element.init();
281
+ }
282
+ }
283
+ }
284
+ resolve('done');
285
+ });
286
+ }
274
287
  /**
275
288
  * recursively refreshes all UI Elements.
276
289
  *
@@ -311,7 +324,13 @@ export class Fore {
311
324
  if (!force) {
312
325
  continue;
313
326
  }
327
+ /*
328
+ if(element.nodeName === 'FX-CASE') {
329
+ console.log('hey - got a case', element);
330
+ }
331
+ */
314
332
  if (force === true) {
333
+ // console.log('🔄 refreshing ', element);
315
334
  // Unconditional force refresh
316
335
  bound.refresh(force);
317
336
  continue;
@@ -319,10 +338,13 @@ export class Fore {
319
338
  if (typeof force !== 'object') {
320
339
  continue;
321
340
  }
341
+ /*
322
342
  if (
323
343
  force.reason === 'index-function' &&
324
344
  bound.dependencies.isInvalidatedByIndexFunction()
325
345
  ) {
346
+ console.log('🔄 refreshing ', element);
347
+
326
348
  bound.refresh(force);
327
349
  continue;
328
350
  }
@@ -330,12 +352,12 @@ export class Fore {
330
352
  if (
331
353
  bound.dependencies.isInvalidatedByChildlistChanges(force.elementLocalnamesWithChanges)
332
354
  ) {
355
+ console.log('🔄 refreshing ', element);
356
+
333
357
  bound.refresh(force);
334
358
  continue;
335
359
  }
336
-
337
- // console.log('refreshing', element, element?.ref);
338
- // console.log('refreshing ',element);
360
+ */
339
361
  }
340
362
  if (!(element.inert === true)) {
341
363
  // testing for inert catches model and action elements and should just leave updateable html elements
@@ -542,7 +564,10 @@ export class Fore {
542
564
  const reg = /(>)(<)(\/*)/g;
543
565
  const wsexp = / *(.*) +\n/g;
544
566
  const contexp = /(<.+>)(.+\n)/g;
545
- xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
567
+ xml = xml
568
+ .replace(reg, '$1\n$2$3')
569
+ .replace(wsexp, '$1\n')
570
+ .replace(contexp, '$1\n$2');
546
571
  let formatted = '';
547
572
  const lines = xml.split('\n');
548
573
  let indent = 0;
package/src/fx-bind.js CHANGED
@@ -8,6 +8,8 @@ import {
8
8
  } from './xpath-evaluation.js';
9
9
  import { XPathUtil } from './xpath-util.js';
10
10
  import getInScopeContext from './getInScopeContext.js';
11
+ import { getPath } from './xpath-path.js';
12
+ import { evaluateXPathToFirstNode } from 'fontoxpath';
11
13
 
12
14
  /**
13
15
  * FxBind declaratively attaches constraints to nodes in the data (instances).
@@ -35,6 +37,9 @@ export class FxBind extends ForeElementMixin {
35
37
 
36
38
  constructor() {
37
39
  super();
40
+ /**
41
+ * @type {Node[]}
42
+ */
38
43
  this.nodeset = [];
39
44
  this.model = {};
40
45
  this.contextNode = {};
@@ -45,12 +50,102 @@ export class FxBind extends ForeElementMixin {
45
50
  // console.log('connectedCallback ', this);
46
51
  // this.id = this.hasAttribute('id')?this.getAttribute('id'):;
47
52
  this.constraint = this.getAttribute('constraint');
48
- this.ref = this.getAttribute('ref');
53
+ this.ref = this.getAttribute('ref') || '.';
49
54
  this.readonly = this.getAttribute('readonly');
50
55
  this.required = this.getAttribute('required');
51
56
  this.relevant = this.getAttribute('relevant');
52
57
  this.type = this.hasAttribute('type') ? this.getAttribute('type') : FxBind.TYPE_DEFAULT;
53
58
  this.calculate = this.getAttribute('calculate');
59
+ // For self-references, just apply the facets to the parent bind
60
+ if (this.ref === '.') {
61
+ const parent = this.parentNode;
62
+ if (parent instanceof FxBind) {
63
+ // For overlapping binds, the last one wins
64
+ parent.calculate ||= this.calculate;
65
+ parent.readonly ||= this.readonly;
66
+ parent.required ||= this.required;
67
+ parent.relevant ||= this.relevant;
68
+ parent.constraint ||= this.constraint;
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * @param {string} ref -
74
+ * @param {Node} node -
75
+ * @param {ForeElementMixin} boundElement -
76
+ *
77
+ * @returns {ModelItem}
78
+ */
79
+ static createModelItem(ref, node, boundElement, opNum) {
80
+ const instanceId = XPathUtil.resolveInstance(boundElement, boundElement.ref);
81
+ if (Array.isArray(node)) {
82
+ node = node[0];
83
+ }
84
+ if (!node.nodeType) {
85
+ // This node set is not pointing to nodes, but some other type.
86
+ return new ModelItem(
87
+ ref,
88
+ 'non-node item',
89
+ node,
90
+ null,
91
+ instanceId,
92
+ boundElement.getOwnerForm(),
93
+ );
94
+ }
95
+
96
+ // ✅ only the repeat item gets the _<opNum> suffix; children do not.
97
+ const basePath = XPathUtil.getPath(node, instanceId);
98
+ const path = opNum ? `${basePath}_${opNum}` : basePath;
99
+
100
+ // const path = XPathUtil.getPath(node, instanceId);
101
+
102
+ // naive approach to finding matching bind elements for given ref if not provided by caller.
103
+ // Use XPath and variables to escape XPaths in the ref
104
+ /**
105
+ * @type {import('./fx-bind.js').FxBind}
106
+ */
107
+ /*
108
+ const bind = evaluateXPathToFirstNode(
109
+ 'descendant::fx-bind[@ref=$ref]',
110
+ boundElement.getModel(),
111
+ null,
112
+ {
113
+ ref: boundElement.ref,
114
+ },
115
+ );
116
+ */
117
+ const bind = boundElement.getOwnerForm().querySelector(`fx-bind[ref="${ref}"]`);
118
+
119
+ let modelItem = boundElement.getModel().getModelItem(node);
120
+ if (!modelItem) {
121
+ if (bind) {
122
+ modelItem = new ModelItem(
123
+ path,
124
+ boundElement.getBindingExpr(),
125
+ node,
126
+ bind,
127
+ instanceId,
128
+ boundElement.getOwnerForm(),
129
+ );
130
+ const alert = bind.getAlert();
131
+ if (alert) {
132
+ modelItem.addAlert(alert);
133
+ }
134
+ } else {
135
+ // no binding facets apply
136
+ modelItem = new ModelItem(
137
+ path,
138
+ boundElement.getBindingExpr(),
139
+ node,
140
+ null,
141
+ instanceId,
142
+ boundElement.getOwnerForm(),
143
+ );
144
+ }
145
+ // boundElement.getModel().registerModelItem(modelItem);
146
+ }
147
+
148
+ return modelItem;
54
149
  }
55
150
 
56
151
  /**
@@ -80,10 +175,10 @@ export class FxBind extends ForeElementMixin {
80
175
 
81
176
  _buildBindGraph() {
82
177
  if (this.bindType === 'xml') {
83
- this.nodeset.forEach((node) => {
178
+ this.nodeset.forEach(node => {
84
179
  const instance = XPathUtil.resolveInstance(this, this.ref);
85
180
 
86
- const path = XPathUtil.getPath(node, instance);
181
+ const path = getPath(node, instance);
87
182
  this.model.mainGraph.addNode(path, node);
88
183
 
89
184
  /* ### catching references in the 'ref' itself...
@@ -142,6 +237,26 @@ export class FxBind extends ForeElementMixin {
142
237
  }
143
238
  }
144
239
 
240
+ /**
241
+ * Resolves a referenced ModelItem using the model's graph and node registry.
242
+ * @param {string} refName
243
+ * @returns {ModelItem | null}
244
+ */
245
+ resolveModelItem(refName) {
246
+ if (!this.model?.mainGraph || !this.model?.getModelItemForNode) return null;
247
+
248
+ const suffixes = [`/${refName}`, `:${refName}`];
249
+
250
+ for (const [path, node] of this.model.mainGraph.nodeMap.entries()) {
251
+ if (suffixes.some(suffix => path.endsWith(suffix))) {
252
+ const modelItem = this.model.getModelItemForNode(node);
253
+ if (modelItem) return modelItem;
254
+ }
255
+ }
256
+
257
+ return null;
258
+ }
259
+
145
260
  /**
146
261
  * Add the dependencies of this bind
147
262
  *
@@ -158,10 +273,10 @@ export class FxBind extends ForeElementMixin {
158
273
  if (!this.model.mainGraph.hasNode(nodeHash)) {
159
274
  this.model.mainGraph.addNode(nodeHash, node);
160
275
  }
161
- refs.forEach((ref) => {
276
+ refs.forEach(ref => {
162
277
  const instance = XPathUtil.resolveInstance(this, path);
163
278
 
164
- const otherPath = XPathUtil.getPath(ref, instance);
279
+ const otherPath = getPath(ref, instance);
165
280
  // console.log('otherPath', otherPath)
166
281
 
167
282
  // todo: nasty hack to prevent duplicate pathes like 'a[1]' and 'a[1]/text()[1]' to end up as separate nodes in the graph
@@ -179,7 +294,7 @@ export class FxBind extends ForeElementMixin {
179
294
 
180
295
  _processChildren(model) {
181
296
  const childbinds = this.querySelectorAll(':scope > fx-bind');
182
- Array.from(childbinds).forEach((bind) => {
297
+ Array.from(childbinds).forEach(bind => {
183
298
  // console.log('init child bind ', bind);
184
299
  bind.init(model);
185
300
  });
@@ -208,14 +323,14 @@ export class FxBind extends ForeElementMixin {
208
323
  if (this.ref === '' || this.ref === null) {
209
324
  this.nodeset = inscopeContext;
210
325
  } else if (Array.isArray(inscopeContext)) {
211
- inscopeContext.forEach((n) => {
326
+ inscopeContext.forEach(n => {
212
327
  if (XPathUtil.isSelfReference(this.ref)) {
213
328
  this.nodeset = inscopeContext;
214
329
  } else {
215
330
  // eslint-disable-next-line no-lonely-if
216
331
  if (this.ref) {
217
332
  const localResult = evaluateXPathToNodes(this.ref, n, this);
218
- localResult.forEach((item) => {
333
+ localResult.forEach(item => {
219
334
  this.nodeset.push(item);
220
335
  });
221
336
  /*
@@ -243,7 +358,7 @@ export class FxBind extends ForeElementMixin {
243
358
  if (Array.isArray(this.nodeset)) {
244
359
  // console.log('################################################ ', this.nodeset);
245
360
  // Array.from(this.nodeset).forEach((n, index) => {
246
- Array.from(this.nodeset).forEach((n) => {
361
+ Array.from(this.nodeset).forEach(n => {
247
362
  // console.log('node ',n);
248
363
  // this._createModelItem(n, index);
249
364
  this._createModelItem(n);
@@ -308,25 +423,15 @@ export class FxBind extends ForeElementMixin {
308
423
  // const path = this.getPath(node);
309
424
  const instanceId = XPathUtil.resolveInstance(this, this.ref);
310
425
 
311
- const path = XPathUtil.getPath(node, instanceId);
426
+ const path = getPath(node, instanceId);
312
427
  // const shortPath = this.shortenPath(path);
313
428
 
314
429
  // ### constructing default modelitem - will get evaluated during recalculate()
315
430
  // ### constructing default modelitem - will get evaluated during recalculate()
316
431
  // ### constructing default modelitem - will get evaluated during recalculate()
317
432
  // const newItem = new ModelItem(shortPath,
318
- const newItem = new ModelItem(
319
- path,
320
- this.getBindingExpr(),
321
- FxBind.READONLY_DEFAULT,
322
- FxBind.RELEVANT_DEFAULT,
323
- FxBind.REQUIRED_DEFAULT,
324
- FxBind.CONSTRAINT_DEFAULT,
325
- this.type,
326
- targetNode,
327
- this,
328
- instanceId
329
- );
433
+ const fore = this.closest('fx-fore');
434
+ const newItem = new ModelItem(path, this.getBindingExpr(), targetNode, this, instanceId, fore);
330
435
 
331
436
  const alert = this.getAlert();
332
437
  if (alert) {
@@ -355,7 +460,7 @@ export class FxBind extends ForeElementMixin {
355
460
  getReferences(propertyExpr) {
356
461
  const touchedNodes = new Set();
357
462
  const domFacade = new DependencyNotifyingDomFacade(otherNode => touchedNodes.add(otherNode));
358
- this.nodeset.forEach((node) => {
463
+ this.nodeset.forEach(node => {
359
464
  evaluateXPathToString(propertyExpr, node, this, domFacade);
360
465
  });
361
466
  return Array.from(touchedNodes.values());