@jinntec/fore 3.3.2 → 4.0.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 (73) hide show
  1. package/README.md +98 -70
  2. package/dist/fore-dev.js +5593 -6832
  3. package/dist/fore.js +5602 -4887
  4. package/index.js +5 -10
  5. package/package.json +7 -7
  6. package/resources/fore.css +33 -0
  7. package/src/DependencyNotifyingDomFacade.js +90 -21
  8. package/src/DependentXPathQueries.js +15 -2
  9. package/src/ForeElementMixin.js +110 -16
  10. package/src/UndoManager.js +267 -0
  11. package/src/actions/abstract-action.js +71 -30
  12. package/src/actions/fx-action.js +5 -0
  13. package/src/actions/fx-append.js +3 -3
  14. package/src/actions/fx-commit-history.js +26 -0
  15. package/src/actions/fx-hide.js +1 -1
  16. package/src/actions/fx-insert.js +25 -22
  17. package/src/actions/fx-load.js +5 -5
  18. package/src/actions/fx-redo.js +58 -0
  19. package/src/actions/fx-refresh.js +2 -2
  20. package/src/actions/fx-reload.js +1 -1
  21. package/src/actions/fx-replace.js +1 -1
  22. package/src/actions/fx-send.js +27 -5
  23. package/src/actions/fx-setattribute.js +11 -7
  24. package/src/actions/fx-undo.js +58 -0
  25. package/src/createNodes.js +314 -0
  26. package/src/fore.js +53 -18
  27. package/src/functions/fx-functionlib.js +10 -10
  28. package/src/fx-bind.js +30 -18
  29. package/src/fx-fore.js +222 -200
  30. package/src/fx-instance.js +18 -1
  31. package/src/fx-model.js +236 -69
  32. package/src/fx-submission.js +37 -29
  33. package/src/fx-var.js +49 -13
  34. package/src/getInScopeContext.js +1 -1
  35. package/src/json/JSONDomFacade.js +1 -1
  36. package/src/json/JSONLens.js +2 -2
  37. package/src/ui/UIElement.js +18 -8
  38. package/src/ui/abstract-control.js +45 -3
  39. package/src/ui/fx-alert.js +4 -0
  40. package/src/ui/fx-case.js +1 -1
  41. package/src/ui/fx-container.js +3 -0
  42. package/src/ui/fx-control-menu.js +79 -11
  43. package/src/ui/fx-control.js +130 -41
  44. package/src/ui/fx-dialog.js +5 -0
  45. package/src/ui/fx-items.js +6 -6
  46. package/src/ui/fx-output.js +37 -1
  47. package/src/ui/fx-repeat.js +1065 -103
  48. package/src/ui/fx-repeatitem.js +4 -1
  49. package/src/ui/fx-switch.js +116 -3
  50. package/src/ui/fx-trigger.js +9 -4
  51. package/src/ui/fx-upload.js +10 -4
  52. package/src/ui/repeat-base.js +20 -12
  53. package/src/withDraggability.js +10 -1
  54. package/src/xpath-evaluation.js +30 -18
  55. package/src/xpath-path.js +122 -0
  56. package/src/xpath-util.js +11 -126
  57. package/src/actions/StringTpl.js +0 -17
  58. package/src/extract-predicate-deps.js +0 -57
  59. package/src/extractPredicateDependencies.js +0 -36
  60. package/src/json/lensFromNode.js +0 -5
  61. package/src/json-util.js +0 -27
  62. package/src/tools/adi.js +0 -1111
  63. package/src/tools/deprecation.md +0 -1
  64. package/src/tools/fx-action-log.js +0 -745
  65. package/src/tools/fx-devtools.js +0 -444
  66. package/src/tools/fx-dom-inspector.js +0 -610
  67. package/src/tools/fx-json-instance.js +0 -444
  68. package/src/tools/fx-log-item.js +0 -128
  69. package/src/tools/fx-log-settings.js +0 -533
  70. package/src/tools/fx-minimap.js +0 -203
  71. package/src/tools/helpers.js +0 -132
  72. package/src/ui/TemplateExpression.js +0 -12
  73. package/src/ui/fx-dom-inspector.js +0 -1255
package/index.js CHANGED
@@ -27,24 +27,16 @@ import './src/ui/fx-dialog.js';
27
27
  import './src/ui/fx-items.js';
28
28
  import './src/ui/fx-droptarget.js';
29
29
 
30
- import './src/tools/fx-action-log.js';
31
- import './src/tools/fx-log-settings.js';
32
- import './src/tools/fx-log-item.js';
33
- import './src/tools/fx-devtools.js';
34
- import './src/tools/fx-dom-inspector.js';
35
30
  import './src/lab/fore-component.js';
36
- import './src/tools/fx-json-instance.js';
37
31
  import './src/ui/fx-upload.js';
38
- // import './src/tools/fx-minimap.js';
39
-
40
- // import './src/ui/fx-checkbox-group.js';
41
32
 
42
33
  // action classes
43
34
  import './src/actions/fx-append.js';
44
35
  import './src/actions/fx-delete.js';
45
36
  import './src/actions/fx-setfocus.js';
46
37
  import './src/actions/fx-insert.js';
47
- // import './src/actions/fx-insertchild.js';
38
+ import './src/actions/fx-insertchild.js';
39
+ import './src/actions/fx-copy.js';
48
40
  import './src/actions/fx-message.js';
49
41
  import './src/actions/fx-setvalue.js';
50
42
  import './src/actions/fx-send.js';
@@ -59,6 +51,9 @@ import './src/actions/fx-show.js';
59
51
  import './src/actions/fx-hide.js';
60
52
  import './src/actions/fx-reload.js';
61
53
  import './src/actions/fx-reset.js';
54
+ import './src/actions/fx-undo.js';
55
+ import './src/actions/fx-redo.js';
56
+ import './src/actions/fx-commit-history.js';
62
57
  import './src/actions/fx-load.js';
63
58
  import './src/actions/fx-toggleboolean.js';
64
59
  import './src/actions/fx-call.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "3.3.2",
3
+ "version": "4.0.0",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -32,7 +32,6 @@
32
32
  "dependencies": {
33
33
  "@jinntec/jinn-toast": "^1.0.5",
34
34
  "@picocss/pico": "^2.0.6",
35
- "chai": "^5.2.1",
36
35
  "fontoxpath": "^3.34.0"
37
36
  },
38
37
  "devDependencies": {
@@ -40,10 +39,9 @@
40
39
  "@babel/plugin-proposal-private-methods": "^7.18.6",
41
40
  "@babel/plugin-transform-private-methods": "^7.28.6",
42
41
  "@jinntec/jinn-codemirror": "^1.17.6",
43
- "@open-wc/building-rollup": "^2.0.1",
44
42
  "@open-wc/eslint-config": "^7.0.0",
45
43
  "@open-wc/prettier-config": "^0.1.10",
46
- "@open-wc/semantic-dom-diff": "^0.19.6",
44
+ "@open-wc/semantic-dom-diff": "^0.20.1",
47
45
  "@open-wc/testing": "^3.1.5",
48
46
  "@open-wc/testing-karma": "^4.0.9",
49
47
  "@open-wc/testing-karma-bs": "^1.3.94",
@@ -54,16 +52,18 @@
54
52
  "@skypack/package-check": "^0.2.2",
55
53
  "@types/cypress": "^1.1.3",
56
54
  "@webcomponents/webcomponentsjs": "^2.6.0",
57
- "cypress": "^13.16.0",
55
+ "axe-core": "^4.12.1",
56
+ "chai": "^5.2.1",
57
+ "cypress": "^15.18.0",
58
+ "cypress-axe": "^1.7.0",
58
59
  "deepmerge": "^4.2.2",
59
60
  "eslint": "^8.16.0",
60
61
  "eslint-plugin-prettier": "^5.1.3",
61
62
  "husky": "^8.0.1",
62
- "jsdoc": "^3.6.10",
63
+ "jsdoc": "^4.0.5",
63
64
  "lint-staged": "^12.4.3",
64
65
  "lit-html": "^2.2.5",
65
66
  "mocha": "^10.0.0",
66
- "np": "^8.0.4",
67
67
  "replace-in-file": "^8.0.2",
68
68
  "rimraf": "^3.0.2",
69
69
  "rollup": "^2.75.4",
@@ -214,6 +214,39 @@
214
214
  position: relative;
215
215
  }
216
216
 
217
+ /* ### NATIVE <dialog> STYLES (preferred over fx-dialog, which is deprecated) ### */
218
+ /* ### NATIVE <dialog> STYLES (preferred over fx-dialog, which is deprecated) ### */
219
+ /* ### NATIVE <dialog> STYLES (preferred over fx-dialog, which is deprecated) ### */
220
+ dialog {
221
+ border: thin solid;
222
+ border-radius: 0.5rem;
223
+ padding: 0;
224
+ position: relative;
225
+ }
226
+
227
+ dialog::backdrop {
228
+ background: rgba(0, 0, 0, 0.5);
229
+ }
230
+
231
+ dialog .dialog-content {
232
+ padding: 1rem;
233
+ }
234
+
235
+ dialog .close-dialog {
236
+ position: absolute;
237
+ right: 0.4rem;
238
+ top: 0.2rem;
239
+ color: var(--paper-grey-900);
240
+ text-decoration: none;
241
+ font-size: 1.2rem;
242
+ }
243
+
244
+ dialog .action {
245
+ width: 100%;
246
+ text-align: center;
247
+ display: block;
248
+ }
249
+
217
250
  fx-hint {
218
251
  display: none;
219
252
  }
@@ -5,15 +5,66 @@ import { getBucketsForNode } from 'fontoxpath';
5
5
  * as the `depends` function, but less explicit and will automatically be called for any node that
6
6
  * will be touched in the XPath.
7
7
  *
8
+ * By default it reads from the live DOM. When an `inner` facade is provided (e.g. the
9
+ * JSONDomFacade), all accessors delegate to it instead, still notifying `onNodeTouched` —
10
+ * this lets non-DOM data models participate in dependency tracking.
11
+ *
8
12
  * Maybe some more granularity is better. Maybe only notify a node's attributes are touched?
9
13
  *
10
14
  */
11
15
  export class DependencyNotifyingDomFacade {
12
16
  /**
13
17
  * @param onNodeTouched - onNodeTouched A function what will be executed whenever a node is 'touched' by the XPath
18
+ * @param inner - optional facade to delegate all accessors to (e.g. JSONDomFacade)
14
19
  */
15
- constructor(onNodeTouched) {
20
+ constructor(onNodeTouched, inner = null) {
16
21
  this._onNodeTouched = onNodeTouched;
22
+ this._inner = inner;
23
+ }
24
+
25
+ /**
26
+ * Returns a facade sharing the same onNodeTouched callback but delegating to `inner`.
27
+ * Used by xpath-evaluation.js to route JSON contexts through the JSONDomFacade
28
+ * without callers having to know the context type.
29
+ *
30
+ * @param inner - the facade to delegate to
31
+ */
32
+ withInner(inner) {
33
+ return new DependencyNotifyingDomFacade(this._onNodeTouched, inner);
34
+ }
35
+
36
+ /**
37
+ * Node identity/shape reads (used by the JSONDomFacade surface). Not treated as
38
+ * data dependencies — no notification.
39
+ */
40
+ getNodeType(node) {
41
+ if (this._inner) return this._inner.getNodeType(node);
42
+ return node.nodeType;
43
+ }
44
+
45
+ getNodeName(node) {
46
+ if (this._inner) return this._inner.getNodeName(node);
47
+ return node.nodeName;
48
+ }
49
+
50
+ getNodeValue(node) {
51
+ this._onNodeTouched(node);
52
+ if (this._inner) return this._inner.getNodeValue(node);
53
+ return node.nodeValue;
54
+ }
55
+
56
+ getChildren(node) {
57
+ if (this._inner) {
58
+ const children = this._inner.getChildren(node);
59
+ children.forEach(child => this._onNodeTouched(child));
60
+ return children;
61
+ }
62
+ return this.getChildNodes(node);
63
+ }
64
+
65
+ getChildNodeCount(node) {
66
+ if (this._inner) return this._inner.getChildNodeCount(node);
67
+ return node.childNodes.length;
17
68
  }
18
69
 
19
70
  /**
@@ -22,9 +73,9 @@ export class DependencyNotifyingDomFacade {
22
73
  *
23
74
  * @param node -
24
75
  */
25
- // eslint-disable-next-line class-methods-use-this
26
76
  getAllAttributes(node) {
27
77
  this._onNodeTouched(node); // <== Important!
78
+ if (this._inner) return this._inner.getAllAttributes(node);
28
79
  return Array.from(node.attributes);
29
80
  }
30
81
 
@@ -34,10 +85,12 @@ export class DependencyNotifyingDomFacade {
34
85
  * @param node -
35
86
  * @param attributeName -
36
87
  */
37
- // eslint-disable-next-line class-methods-use-this
38
88
  getAttribute(node, attributeName) {
89
+ if (this._inner) {
90
+ this._onNodeTouched(node);
91
+ return this._inner.getAttribute(node, attributeName);
92
+ }
39
93
  const attr = node.getAttributeNode(attributeName);
40
- console.log('[DomFacade] getAttribute touched:', attr);
41
94
  if (attr) this._onNodeTouched(attr);
42
95
  return attr?.value ?? null;
43
96
  }
@@ -49,8 +102,12 @@ export class DependencyNotifyingDomFacade {
49
102
  * @param node -
50
103
  * @param bucket - The bucket that matches the attribute that will be used.
51
104
  */
52
- // eslint-disable-next-line class-methods-use-this
53
105
  getChildNodes(node, bucket) {
106
+ if (this._inner) {
107
+ const children = this._inner.getChildNodes(node, bucket);
108
+ children.forEach(child => this._onNodeTouched(child));
109
+ return children;
110
+ }
54
111
  const matchingNodes = Array.from(node.childNodes).filter(
55
112
  childNode => !bucket || getBucketsForNode(childNode).includes(bucket),
56
113
  );
@@ -64,13 +121,15 @@ export class DependencyNotifyingDomFacade {
64
121
  * @param node -
65
122
  */
66
123
  getData(node) {
124
+ if (this._inner) {
125
+ this._onNodeTouched(node);
126
+ return this._inner.getData(node);
127
+ }
67
128
  if (node.nodeType === Node.ATTRIBUTE_NODE) {
68
- // console.log('[DomFacade] getData on attribute:', node);
69
129
  this._onNodeTouched(node);
70
130
  return node.value;
71
131
  }
72
132
  // Text node
73
- // console.log('[DomFacade] getData on text node parent:', node.parentNode);
74
133
  this._onNodeTouched(node.parentNode);
75
134
  return node.data;
76
135
  }
@@ -83,6 +142,11 @@ export class DependencyNotifyingDomFacade {
83
142
  * @param bucket - The bucket that matches the attribute that will be used.
84
143
  */
85
144
  getFirstChild(node, bucket) {
145
+ if (this._inner) {
146
+ const child = this._inner.getFirstChild(node, bucket);
147
+ if (child) this._onNodeTouched(child);
148
+ return child;
149
+ }
86
150
  for (const child of node.childNodes) {
87
151
  if (!bucket || getBucketsForNode(child).includes(bucket)) {
88
152
  this._onNodeTouched(child);
@@ -99,8 +163,12 @@ export class DependencyNotifyingDomFacade {
99
163
  * @param node -
100
164
  * @param bucket - The bucket that matches the attribute that will be used.
101
165
  */
102
- // eslint-disable-next-line class-methods-use-this
103
166
  getLastChild(node, bucket) {
167
+ if (this._inner) {
168
+ const child = this._inner.getLastChild(node, bucket);
169
+ if (child) this._onNodeTouched(child);
170
+ return child;
171
+ }
104
172
  const children = Array.from(node.childNodes).filter(
105
173
  child => !bucket || getBucketsForNode(child).includes(bucket),
106
174
  );
@@ -116,8 +184,12 @@ export class DependencyNotifyingDomFacade {
116
184
  * @param node -
117
185
  * @param bucket - The bucket that matches the nextSibling that is requested.
118
186
  */
119
- // eslint-disable-next-line class-methods-use-this
120
187
  getNextSibling(node, bucket) {
188
+ if (this._inner) {
189
+ const sibling = this._inner.getNextSibling(node, bucket);
190
+ if (sibling) this._onNodeTouched(sibling);
191
+ return sibling;
192
+ }
121
193
  for (let sibling = node.nextSibling; sibling; sibling = sibling.nextSibling) {
122
194
  if (bucket && !getBucketsForNode(sibling).includes(bucket)) {
123
195
  // eslint-disable-next-line no-continue
@@ -135,18 +207,10 @@ export class DependencyNotifyingDomFacade {
135
207
  *
136
208
  * @param node - the starting node
137
209
  */
138
- // eslint-disable-next-line class-methods-use-this
139
210
  getParentNode(node) {
140
- const parent = node.parentNode;
141
- /*
142
- if (
143
- parent &&
144
- (parent.nodeType === Node.ELEMENT_NODE || parent.nodeType === Node.ATTRIBUTE_NODE)
145
- ) {
146
- this._onNodeTouched(parent);
147
- }
148
- */
149
- return parent;
211
+ // Deliberately not notifying: navigating up must not register a dependency on the parent.
212
+ if (this._inner) return this._inner.getParentNode(node);
213
+ return node.parentNode;
150
214
  }
151
215
 
152
216
  /**
@@ -156,9 +220,14 @@ export class DependencyNotifyingDomFacade {
156
220
  * @param node -
157
221
  * @param bucket - The bucket that matches the attribute that will be used.
158
222
  */
159
- // eslint-disable-next-line class-methods-use-this
160
223
  getPreviousSibling(node, bucket) {
224
+ if (this._inner) {
225
+ const sibling = this._inner.getPreviousSibling(node, bucket);
226
+ if (sibling) this._onNodeTouched(sibling);
227
+ return sibling;
228
+ }
161
229
  for (let sibling = node.previousSibling; sibling; sibling = sibling.previousSibling) {
230
+ // eslint-disable-next-line no-continue
162
231
  if (bucket && !getBucketsForNode(sibling).includes(bucket)) continue;
163
232
  this._onNodeTouched(sibling);
164
233
  return sibling;
@@ -58,10 +58,23 @@ export default class DependentXPathQueries {
58
58
  }
59
59
 
60
60
  /**
61
- * Add an XPath to the dependencies
61
+ * Detects whether any tracked XPath references one of the given variables.
62
62
  *
63
- * @param {string} xpath the XPath to add
63
+ * Pessimistic substring scan (`$name` also matches `$names`) — false positives are
64
+ * cheap extra refreshes, mirroring isInvalidatedByChildlistChanges.
65
+ *
66
+ * @param {string[]} varNames variable names without the leading '$'
64
67
  */
68
+ isInvalidatedByVariableChange(varNames) {
69
+ for (const xpath of this._xpaths) {
70
+ if (varNames.some(name => xpath.includes(`$${name}`))) {
71
+ return true;
72
+ }
73
+ }
74
+ // We can also depend on these variables if they were used in our ancestry
75
+ return !!this._parentDependencies?.isInvalidatedByVariableChange(varNames);
76
+ }
77
+
65
78
  /**
66
79
  * Add an XPath to the dependencies
67
80
  *
@@ -9,6 +9,13 @@ import getInScopeContext from './getInScopeContext.js';
9
9
  import { Fore } from './fore.js';
10
10
  import DependentXPathQueries from './DependentXPathQueries.js';
11
11
  import { getPath } from './xpath-path.js';
12
+ import { DependencyNotifyingDomFacade } from './DependencyNotifyingDomFacade.js';
13
+
14
+ /**
15
+ * Matches any XPath function call, optionally prefixed (e.g. `count(`, `fn:string-join(`).
16
+ * Used by _refNeedsDependencyTracking after the navigation-anchor functions are stripped.
17
+ */
18
+ const FUNCTION_CALL_RE = /[A-Za-z][\w.-]*(?::[A-Za-z][\w.-]*)?\s*\(/;
12
19
 
13
20
  /**
14
21
  * Mixin containing all general functions that are shared by all Fore element classes.
@@ -66,6 +73,15 @@ export default class ForeElementMixin extends HTMLElement {
66
73
 
67
74
  this.dependencies = new DependentXPathQueries();
68
75
  this.ownerForm = null;
76
+
77
+ /**
78
+ * ModelItems this element observes because its ref expression read their nodes.
79
+ * Bucket-keyed: fx-control tracks two independent expressions (its own ref and its
80
+ * widget's ref) — a single Set would make each eval wrongly unregister the other's
81
+ * observers when diffing.
82
+ * @type {Map<string, Set<import('./modelitem.js').ModelItem>>}
83
+ */
84
+ this._refTrackedModelItems = new Map();
69
85
  }
70
86
 
71
87
  getDebugInfo() {
@@ -134,6 +150,76 @@ export default class ForeElementMixin extends HTMLElement {
134
150
  return null;
135
151
  }
136
152
 
153
+ /**
154
+ * Decides whether a ref expression needs generic dependency tracking. Navigation-only
155
+ * refs bind to exactly the node they return — attachObserver() already covers those.
156
+ * A ref that filters (predicate) or computes (any function call) reads nodes it never
157
+ * binds to, so its result can go stale without tracking. instance() and index() are
158
+ * navigation anchors, not computations: instance() only selects the evaluation root,
159
+ * and index() is handled by a dedicated 'item-changed' listener in UIElement.
160
+ * Restricted to elements with an `update()` observer callback (skips fx-bind).
161
+ * `$var`-only refs are covered by the variable-change consumer instead.
162
+ *
163
+ * @param {string} [refString] the expression to check, defaults to this element's ref
164
+ * @returns {boolean}
165
+ */
166
+ _refNeedsDependencyTracking(refString = this.ref) {
167
+ if (!refString) return false;
168
+ if (typeof this.update !== 'function') return false;
169
+ if (refString.includes('[')) return true;
170
+ const stripped = refString.replace(/\b(?:instance|index)\s*\(/g, '');
171
+ return FUNCTION_CALL_RE.test(stripped);
172
+ }
173
+
174
+ /**
175
+ * Resolves a node touched during ref evaluation to its (possibly lazily created)
176
+ * ModelItem so this element can observe it.
177
+ *
178
+ * @param {import('./fx-model.js').FxModel} model
179
+ * @param {Node} touchedNode a node reported by DependencyNotifyingDomFacade
180
+ * @returns {import('./modelitem.js').ModelItem|null}
181
+ */
182
+ _resolveModelItemForTouchedNode(model, touchedNode) {
183
+ let targetNode = touchedNode;
184
+ if (targetNode?.nodeType === Node.TEXT_NODE) targetNode = targetNode.parentNode;
185
+ if (!targetNode) return null;
186
+ return (
187
+ model.getModelItem(targetNode) ??
188
+ // the touched node may live in a different instance than the ref's primary one —
189
+ // resolve its instance per node, not from the expression
190
+ FxModel.lazyCreateModelItem(
191
+ model,
192
+ this.ref,
193
+ targetNode,
194
+ this,
195
+ model.getInstanceIdForNode(targetNode),
196
+ )
197
+ );
198
+ }
199
+
200
+ /**
201
+ * Registers this element as observer on the ModelItems of all nodes its ref
202
+ * evaluation touched, and unregisters from ModelItems no longer read (per bucket).
203
+ *
204
+ * @param {Set<Node>} touchedNodes nodes reported by DependencyNotifyingDomFacade
205
+ * @param {string} [bucket] tracking bucket, e.g. 'ref' or 'widget'
206
+ */
207
+ _trackRefDependencies(touchedNodes, bucket = 'ref') {
208
+ const model = this.getModel();
209
+ if (!model) return;
210
+ const newModelItems = new Set();
211
+ touchedNodes.forEach(n => {
212
+ const mi = this._resolveModelItemForTouchedNode(model, n);
213
+ if (mi) newModelItems.add(mi);
214
+ });
215
+ const previous = this._refTrackedModelItems.get(bucket);
216
+ previous?.forEach(mi => {
217
+ if (!newModelItems.has(mi)) mi.removeObserver(this);
218
+ });
219
+ newModelItems.forEach(mi => mi.addObserver(this));
220
+ this._refTrackedModelItems.set(bucket, newModelItems);
221
+ }
222
+
137
223
  /**
138
224
  * evaluation of fx-bind and UiElements differ in details so that each class needs it's own implementation.
139
225
  */
@@ -159,6 +245,13 @@ export default class ForeElementMixin extends HTMLElement {
159
245
  // console.warn('using default context ', this);
160
246
  // return;
161
247
  }
248
+ let touchedNodes = null;
249
+ let domFacade = null;
250
+ if (this._refNeedsDependencyTracking()) {
251
+ touchedNodes = new Set();
252
+ domFacade = new DependencyNotifyingDomFacade(node => touchedNodes.add(node));
253
+ }
254
+
162
255
  if (this.ref === '') {
163
256
  this.nodeset = inscopeContext;
164
257
  } else if (Array.isArray(inscopeContext)) {
@@ -174,17 +267,20 @@ export default class ForeElementMixin extends HTMLElement {
174
267
  });
175
268
  */
176
269
  // this.nodeset = evaluateXPathToFirstNode(this.ref, inscopeContext[0], this);
177
- this.nodeset = evaluateXPath(this.ref, inscopeContext[0], this);
270
+ this.nodeset = evaluateXPath(this.ref, inscopeContext[0], this, {}, {}, domFacade);
178
271
  } else {
179
272
  // this.nodeset = fx.evaluateXPathToFirstNode(this.ref, inscopeContext, null, {namespaceResolver: this.namespaceResolver});
180
273
  if (!inscopeContext) return;
181
274
  if (this.nodeName === 'FX-REPEAT') {
182
275
  // Repeats are special: they have multiple nodes in their nodeset
183
- this.nodeset = evaluateXPath(this.ref, inscopeContext, this);
276
+ this.nodeset = evaluateXPath(this.ref, inscopeContext, this, {}, {}, domFacade);
184
277
  } else {
185
- this.nodeset = evaluateXPath(this.ref, inscopeContext, this)[0] || null;
278
+ this.nodeset = evaluateXPath(this.ref, inscopeContext, this, {}, {}, domFacade)[0] || null;
186
279
  }
187
280
  }
281
+ if (touchedNodes) {
282
+ this._trackRefDependencies(touchedNodes);
283
+ }
188
284
  // console.log('UiElement evaluated to nodeset: ', this.nodeset);
189
285
  }
190
286
 
@@ -314,22 +410,19 @@ export default class ForeElementMixin extends HTMLElement {
314
410
  }
315
411
 
316
412
  if (path) {
317
- existed = model.modelItems.find(item => item.path === path) || null;
413
+ existed = model._modelItemsByPath.get(path) || null;
318
414
  if (existed) {
319
415
  // CRITICAL: retarget existing ModelItem to the current backing object
320
416
  const isLensObject =
321
- targetNode &&
322
- typeof targetNode === 'object' &&
323
- typeof targetNode.get === 'function' &&
324
- typeof targetNode.set === 'function';
325
-
326
- if (isLensObject) {
327
- existed.lens = targetNode;
328
- existed.node = null;
329
- } else {
330
- existed.node = targetNode;
331
- existed.lens = null;
332
- }
417
+ targetNode &&
418
+ typeof targetNode === 'object' &&
419
+ typeof targetNode.get === 'function' &&
420
+ typeof targetNode.set === 'function';
421
+
422
+ model._setModelItemTarget(
423
+ existed,
424
+ isLensObject ? { lens: targetNode } : { node: targetNode },
425
+ );
333
426
 
334
427
  this.modelItem = existed;
335
428
  return existed;
@@ -341,6 +434,7 @@ export default class ForeElementMixin extends HTMLElement {
341
434
  this.modelItem = lazyCreatedModelItem;
342
435
  return lazyCreatedModelItem;
343
436
  }
437
+
344
438
  /**
345
439
  * Returns the effective value for the element.
346
440
  * a: look for 'value' attribute and if present evaluate it and return the resulting value