@jinntec/fore 2.5.0 → 2.7.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 (63) hide show
  1. package/dist/fore-dev.js +36088 -9
  2. package/dist/fore.js +35918 -9
  3. package/index.js +3 -1
  4. package/package.json +10 -4
  5. package/resources/fore.css +30 -5
  6. package/src/DataObserver.js +181 -0
  7. package/src/DependencyNotifyingDomFacade.js +27 -21
  8. package/src/DependentXPathQueries.js +32 -0
  9. package/src/ForeElementMixin.js +60 -26
  10. package/src/actions/abstract-action.js +24 -29
  11. package/src/actions/fx-append.js +25 -2
  12. package/src/actions/fx-call.js +2 -2
  13. package/src/actions/fx-delete.js +58 -21
  14. package/src/actions/fx-hide.js +1 -1
  15. package/src/actions/fx-insert.js +62 -48
  16. package/src/actions/fx-load.js +7 -2
  17. package/src/actions/fx-refresh.js +15 -5
  18. package/src/actions/fx-replace.js +18 -3
  19. package/src/actions/fx-reset.js +6 -0
  20. package/src/actions/fx-send.js +10 -7
  21. package/src/actions/fx-setattribute.js +12 -0
  22. package/src/actions/fx-setfocus.js +11 -8
  23. package/src/actions/fx-setvalue.js +20 -2
  24. package/src/actions/fx-show.js +4 -2
  25. package/src/actions/fx-toggle.js +1 -1
  26. package/src/extract-predicate-deps.js +57 -0
  27. package/src/extractPredicateDependencies.js +36 -0
  28. package/src/fore.js +78 -36
  29. package/src/fx-bind.js +128 -23
  30. package/src/fx-connection.js +24 -7
  31. package/src/fx-fore.js +552 -306
  32. package/src/fx-instance.js +9 -11
  33. package/src/fx-model.js +154 -65
  34. package/src/fx-submission.js +45 -51
  35. package/src/fx-var.js +5 -0
  36. package/src/getInScopeContext.js +8 -8
  37. package/src/modelitem.js +218 -72
  38. package/src/tools/fx-action-log.js +21 -19
  39. package/src/tools/fx-log-settings.js +4 -2
  40. package/src/ui/TemplateExpression.js +12 -0
  41. package/src/ui/UIElement.js +206 -0
  42. package/src/ui/abstract-control.js +15 -7
  43. package/src/ui/fx-case.js +15 -3
  44. package/src/ui/fx-container.js +10 -3
  45. package/src/ui/fx-control-menu.js +207 -0
  46. package/src/ui/fx-control.js +116 -32
  47. package/src/ui/fx-dialog.js +2 -2
  48. package/src/ui/fx-group.js +14 -0
  49. package/src/ui/fx-items.js +10 -4
  50. package/src/ui/fx-repeat-attributes.js +111 -35
  51. package/src/ui/fx-repeat.js +364 -87
  52. package/src/ui/fx-repeat.updated.js +821 -0
  53. package/src/ui/fx-repeatitem.js +23 -20
  54. package/src/ui/fx-switch.js +5 -3
  55. package/src/ui/fx-upload.js +36 -40
  56. package/src/ui/repeat-base.js +532 -0
  57. package/src/withDraggability.js +8 -4
  58. package/src/xpath-evaluation.js +26 -8
  59. package/src/xpath-path.js +79 -0
  60. package/src/xpath-util.js +107 -11
  61. package/dist/fore-dev.js.map +0 -1
  62. package/dist/fore.js.map +0 -1
  63. package/src/ui/fx-select.js +0 -89
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());
@@ -38,7 +38,7 @@ ${
38
38
  }
39
39
 
40
40
  static get observedAttributes() {
41
- return ['url', 'heartbeat', 'message-format'];
41
+ return ['url', 'heartbeat', 'message-format', 'messageformat'];
42
42
  }
43
43
 
44
44
  connectedCallback() {
@@ -78,6 +78,7 @@ ${
78
78
  this._setupHeartbeat();
79
79
  break;
80
80
  case 'messageformat':
81
+ case 'message-format':
81
82
  this._messageFormat = newValue;
82
83
  break;
83
84
  default:
@@ -87,19 +88,35 @@ ${
87
88
  }
88
89
 
89
90
  send(data) {
90
- this.evalInContext();
91
- data = this.nodeset;
91
+ // If data is provided directly, use it; otherwise evaluate from context
92
+ if (data === undefined) {
93
+ this.evalInContext();
94
+ data = this.nodeset;
95
+ }
96
+
92
97
  if (this._socket && this._socket.readyState === WebSocket.OPEN) {
93
98
  let message;
94
99
  switch (this._messageFormat) {
95
100
  case 'json':
96
- message = JSON.stringify(data);
101
+ if (typeof data === 'string') {
102
+ message = data; // Assume it's already JSON string
103
+ } else {
104
+ message = JSON.stringify(data);
105
+ }
97
106
  break;
98
107
  case 'xml':
99
- message = new XMLSerializer().serializeToString(data);
108
+ if (typeof data === 'string') {
109
+ message = data;
110
+ } else {
111
+ message = new XMLSerializer().serializeToString(data);
112
+ }
100
113
  break;
101
114
  case 'text':
102
- message = data.textContent;
115
+ if (typeof data === 'string') {
116
+ message = data;
117
+ } else {
118
+ message = data.textContent;
119
+ }
103
120
  break;
104
121
  default:
105
122
  throw new Error(`Unsupported message format: ${this._messageFormat}`);
@@ -123,7 +140,7 @@ ${
123
140
  _disconnect() {
124
141
  if (this._socket) {
125
142
  this._socket.removeEventListener('open', this._onOpen.bind(this));
126
- this._socket.removeEventListener('message', event => this._onMessage(event));
143
+ this._socket.removeEventListener('message', this._onMessage);
127
144
  this._socket.removeEventListener('close', this._onClose.bind(this));
128
145
  this._socket.close();
129
146
  this._socket = null;