@jinntec/fore 2.6.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 (53) hide show
  1. package/dist/fore-dev.js +4470 -2643
  2. package/dist/fore.js +4415 -2620
  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-refresh.js +15 -5
  14. package/src/actions/fx-replace.js +18 -3
  15. package/src/actions/fx-reset.js +6 -0
  16. package/src/actions/fx-send.js +10 -7
  17. package/src/actions/fx-setattribute.js +12 -0
  18. package/src/actions/fx-setfocus.js +11 -8
  19. package/src/actions/fx-setvalue.js +93 -93
  20. package/src/actions/fx-toggle.js +1 -1
  21. package/src/extract-predicate-deps.js +57 -0
  22. package/src/extractPredicateDependencies.js +36 -0
  23. package/src/fore.js +41 -16
  24. package/src/fx-bind.js +128 -23
  25. package/src/fx-connection.js +24 -7
  26. package/src/fx-fore.js +506 -259
  27. package/src/fx-instance.js +9 -11
  28. package/src/fx-model.js +154 -65
  29. package/src/fx-submission.js +19 -30
  30. package/src/fx-var.js +5 -0
  31. package/src/getInScopeContext.js +7 -8
  32. package/src/modelitem.js +247 -112
  33. package/src/tools/fx-action-log.js +21 -19
  34. package/src/tools/fx-log-settings.js +4 -2
  35. package/src/ui/TemplateExpression.js +12 -0
  36. package/src/ui/UIElement.js +125 -10
  37. package/src/ui/abstract-control.js +5 -0
  38. package/src/ui/fx-case.js +15 -3
  39. package/src/ui/fx-container.js +5 -0
  40. package/src/ui/fx-control-menu.js +23 -14
  41. package/src/ui/fx-control.js +55 -15
  42. package/src/ui/fx-items.js +10 -4
  43. package/src/ui/fx-repeat-attributes.js +111 -35
  44. package/src/ui/fx-repeat.js +332 -85
  45. package/src/ui/fx-repeat.updated.js +821 -0
  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
@@ -0,0 +1,532 @@
1
+ /**
2
+ * repeat-base.js — Common logic extracted from fx-repeat (kept intact) for reuse.
3
+ *
4
+ * IMPORTANT:
5
+ * - fx-repeat stays exactly as-is. We do NOT change its code.
6
+ * - This base mirrors the common logic (method names + behavior) so other repeat
7
+ * variants (like fx-repeat-attributes) can reuse it without duplicating code.
8
+ *
9
+ * Integration options (no changes to fx-repeat required):
10
+ * - Import this file where you implement other repeat-like components and extend RepeatBase.
11
+ * - Optionally, you may mix these methods into FxRepeat at runtime:
12
+ * customElements.whenDefined('fx-repeat').then(() => {
13
+ * Object.assign(customElements.get('fx-repeat').prototype, RepeatBase.prototype);
14
+ * });
15
+ * (Only if you really must; by default we leave FxRepeat untouched.)
16
+ */
17
+ import { Fore } from '../fore.js';
18
+ import { evaluateXPath } from '../xpath-evaluation.js';
19
+ import getInScopeContext from '../getInScopeContext.js';
20
+ import { withDraggability } from '../withDraggability.js';
21
+ import { UIElement } from './UIElement.js';
22
+ import { FxBind } from '../fx-bind.js';
23
+
24
+ const BaseEl = typeof UIElement !== 'undefined' ? UIElement : HTMLElement;
25
+ const DraggableBase =
26
+ typeof withDraggability === 'function' ? withDraggability(BaseEl, false) : BaseEl;
27
+
28
+ export class RepeatBase extends withDraggability(UIElement, false) {
29
+ get repeatSize() {
30
+ return this.querySelectorAll(':scope > fx-repeatitem').length;
31
+ }
32
+
33
+ set repeatSize(size) {
34
+ this.size = size;
35
+ }
36
+
37
+ async init() {
38
+ // ### there must be a single 'template' child
39
+
40
+ const inited = new Promise(resolve => {
41
+ // console.log('##### repeat-attributes init ', this.id);
42
+ // if(!this.inited) this.init();
43
+ // does not use this.evalInContext as it is expecting a nodeset instead of single node
44
+ this._evalNodeset();
45
+ // console.log('##### ',this.id, this.nodeset);
46
+
47
+ this._initTemplate();
48
+ // this._initRepeatItems();
49
+
50
+ this.setAttribute('index', this.index);
51
+ this.inited = true;
52
+ resolve('done');
53
+ });
54
+
55
+ return inited;
56
+ }
57
+
58
+ async refresh(force) {
59
+ console.log('🔄 fx-repeat.refresh on', this.id);
60
+
61
+ if (!this.inited) this.init();
62
+ // console.time('repeat-refresh', this);
63
+ this._evalNodeset();
64
+
65
+ // console.log('repeat refresh nodeset ', this.nodeset);
66
+ // console.log('repeatCount', this.repeatCount);
67
+
68
+ const repeatItems = this.querySelectorAll(':scope > fx-repeatitem');
69
+ const repeatItemCount = repeatItems.length;
70
+
71
+ let nodeCount = 1;
72
+ if (Array.isArray(this.nodeset)) {
73
+ nodeCount = this.nodeset.length;
74
+ }
75
+
76
+ // const contextSize = this.nodeset.length;
77
+ const contextSize = nodeCount;
78
+ // todo: review - cant the context really never be smaller than the repeat count?
79
+ // todo: this code can be deprecated probably but check first
80
+ if (contextSize < repeatItemCount) {
81
+ for (let position = repeatItemCount; position > contextSize; position -= 1) {
82
+ // remove repeatitem
83
+ const itemToRemove = repeatItems[position - 1];
84
+ itemToRemove.parentNode.removeChild(itemToRemove);
85
+ this.getOwnerForm().unRegisterLazyElement(itemToRemove);
86
+ // this._fadeOut(itemToRemove);
87
+ // Fore.fadeOutElement(itemToRemove)
88
+ }
89
+ }
90
+
91
+ if (contextSize > repeatItemCount) {
92
+ for (let position = repeatItemCount + 1; position <= contextSize; position += 1) {
93
+ // add new repeatitem
94
+
95
+ const newItem = this._createNewRepeatItem();
96
+
97
+ this.appendChild(newItem);
98
+
99
+ this._initVariables(newItem);
100
+
101
+ newItem.nodeset = this.nodeset[position - 1];
102
+ newItem.index = position;
103
+
104
+ if (this.getOwnerForm().createNodes) {
105
+ this.getOwnerForm().initData(newItem);
106
+ }
107
+
108
+ // Tell the owner form we might have new template expressions here
109
+ this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
110
+
111
+ newItem.refresh(true);
112
+ }
113
+ }
114
+
115
+ // ### update nodeset of repeatitems
116
+ for (let position = 0; position < repeatItemCount; position += 1) {
117
+ const item = repeatItems[position];
118
+ this.getOwnerForm().registerLazyElement(item);
119
+
120
+ if (item.nodeset !== this.nodeset[position]) {
121
+ item.nodeset = this.nodeset[position];
122
+ }
123
+ }
124
+
125
+ // Fore.refreshChildren(clone, true);
126
+ const fore = this.getOwnerForm();
127
+ // if (!fore.lazyRefresh || force) {
128
+ if (!fore.lazyRefresh || force) {
129
+ // Turn the possibly conditional force refresh into a forced one: we changed our children
130
+ Fore.refreshChildren(this, force);
131
+ }
132
+ // this.style.display = 'block';
133
+ // this.style.display = this.display;
134
+ this.setIndex(this.index);
135
+ // console.timeEnd('repeat-refresh');
136
+
137
+ // this.replaceWith(clone);
138
+
139
+ // this.repeatCount = contextSize;
140
+ // console.log('repeatCount', this.repeatCount);
141
+ }
142
+
143
+ connectedCallback() {
144
+ super.connectedCallback();
145
+
146
+ // Listen for insertion events
147
+ this.handleInsert = event => {
148
+ const { detail } = event;
149
+ console.log('insert catched', detail);
150
+
151
+ // Step 1: Refresh/re-evaluate the nodeset
152
+ const oldNodesetLength = this.nodeset.length;
153
+ this._evalNodeset();
154
+ const newNodesetLength = this.nodeset.length;
155
+ if (oldNodesetLength === newNodesetLength) {
156
+ return;
157
+ }
158
+
159
+ this._insertHandler(detail.insertedNodes);
160
+ };
161
+
162
+ this.getOwnerForm().addEventListener('insert', this.handleInsert);
163
+
164
+ this.handleDelete = event => {
165
+ console.log('delete catched', event);
166
+ const { detail } = event;
167
+ if (!detail || !detail.deletedNodes) {
168
+ return;
169
+ }
170
+
171
+ // Remove corresponding repeat items for deleted nodes
172
+ detail.deletedNodes.forEach(node => {
173
+ this._deleteHandler(node);
174
+ // this.removeRepeatItemForNode(node);
175
+ });
176
+ this.getOwnerForm().addToBatchedNotifications(this);
177
+ };
178
+ this.getOwnerForm().addEventListener('deleted', this.handleDelete);
179
+ }
180
+
181
+ disconnectedCallback() {
182
+ this.getOwnerForm().removeEventListener('deleted', this.handleDelete);
183
+ this.getOwnerForm().removeEventListener('insert', this.handleInsert);
184
+ }
185
+
186
+ /**
187
+ * Handle an insert
188
+ *
189
+ * @param {Node} node
190
+ */
191
+ _insertHandler(node) {
192
+ /**
193
+ * @type {number}
194
+ */
195
+ const insertionIndex = this.nodeset.indexOf(node) + 1;
196
+ // Step 2: Get current repeat items and create a new item
197
+ // todo: search fx-bind elements with same nodeset as this repeat - if present update modelItem instead of creating one
198
+ const newRepeatItem = this._createNewRepeatItem(insertionIndex, node);
199
+
200
+ // Generate the parent `modelItem` for the new repeat item
201
+ this.opNum++;
202
+ const parentModelItem = FxBind.createModelItem(this.ref, node, this, this.opNum);
203
+ newRepeatItem.modelItem = parentModelItem;
204
+
205
+ this.setIndex(insertionIndex);
206
+
207
+ this.getModel().registerModelItem(parentModelItem);
208
+
209
+ // Step 5: Create modelItems recursively for child elements
210
+ this._createModelItemsRecursively(newRepeatItem, parentModelItem);
211
+
212
+ // Step 6: Notify and refresh the UI
213
+ this.getOwnerForm().scanForNewTemplateExpressionsNextRefresh();
214
+
215
+ this.getOwnerForm().addToBatchedNotifications(newRepeatItem);
216
+ }
217
+
218
+ /**
219
+ * @abstract
220
+ *
221
+ * @param {number} index - the one-based index of where to insert the new node
222
+ * @param {Node} node - the new node that's inserted
223
+ *
224
+ * @returns {HTMLElement}
225
+ */
226
+ _createNewRepeatItem(index, node) {
227
+ throw new Error('Not implemented');
228
+ }
229
+
230
+ setInScopeVariables(inScopeVariables) {
231
+ // Repeats are interesting: the variables should be scoped per repeat item, they should not be
232
+ // able to see the variables in adjacent repeat items!
233
+ this.inScopeVariables = new Map(inScopeVariables);
234
+ }
235
+
236
+ /**
237
+ * repeat has no own modelItems
238
+ * @private
239
+ */
240
+ _evalNodeset() {
241
+ // const inscope = this.getInScopeContext();
242
+ const inscope = getInScopeContext(this.getAttributeNode('ref') || this, this.ref);
243
+ // console.log('##### inscope ', inscope);
244
+ // console.log('##### ref ', this.ref);
245
+ // now we got a nodeset and attach MutationObserver to it
246
+
247
+ if (this.mutationObserver && inscope.nodeName) {
248
+ this.mutationObserver.observe(inscope, {
249
+ childList: true,
250
+ subtree: true,
251
+ });
252
+ }
253
+
254
+ /*
255
+ this.touchedPaths = new Set();
256
+ const instance = XPathUtil.resolveInstance(this, this.ref);
257
+ const depTrackDomfacade = new DependencyNotifyingDomFacade((node) => {
258
+ this.touchedPaths.add(XPathUtil.getPath(node, instance));
259
+ });
260
+ const rawNodeset = evaluateXPath(this.ref, inscope, this, {}, {}, depTrackDomfacade );
261
+ */
262
+ const rawNodeset = evaluateXPath(this.ref, inscope, this);
263
+
264
+ // console.log('Touched!', this.ref, [...this.touchedPaths].join(', '));
265
+ if (rawNodeset.length === 1 && Array.isArray(rawNodeset[0])) {
266
+ // This XPath likely returned an XPath array. Just collapse to that array
267
+ this.nodeset = rawNodeset[0];
268
+ return;
269
+ }
270
+ this.nodeset = rawNodeset;
271
+ }
272
+
273
+ _initTemplate() {
274
+ this.template = this.querySelector('template');
275
+ // console.log('### init template for repeat ', this.id, this.template);
276
+ // todo: this.dropTarget not needed?
277
+ this.dropTarget = this.template.getAttribute('drop-target');
278
+ this.isDraggable = this.template.hasAttribute('draggable')
279
+ ? this.template.getAttribute('draggable')
280
+ : null;
281
+
282
+ if (this.template === null) {
283
+ // todo: catch this on form element
284
+ this.dispatchEvent(
285
+ new CustomEvent('no-template-error', {
286
+ composed: true,
287
+ bubbles: true,
288
+ detail: { message: `no template found for repeat:${this.id}` },
289
+ }),
290
+ );
291
+ }
292
+
293
+ this.shadowRoot.appendChild(this.template);
294
+ }
295
+
296
+ _initRepeatItems() {
297
+ this.nodeset.forEach((item, index) => {
298
+ const repeatItem = this._createNewRepeatItem();
299
+ repeatItem.nodeset = this.nodeset[index];
300
+ repeatItem.index = index + 1; // 1-based index
301
+
302
+ // this.appendChild(repeatItem);
303
+
304
+ if (this.getOwnerForm().createNodes) {
305
+ this.getOwnerForm().initData(repeatItem);
306
+ const repeatItemClone = repeatItem.nodeset.cloneNode(true);
307
+ this.clearTextValues(repeatItemClone);
308
+
309
+ // this.createdNodeset = repeatItem.nodeset.cloneNode(true);
310
+ this.createdNodeset = repeatItemClone;
311
+ // console.log('createdNodeset', this.createdNodeset)
312
+ }
313
+
314
+ if (repeatItem.index === 1) {
315
+ this.applyIndex(repeatItem);
316
+ }
317
+ // console.log('*********repeat item created', repeatItem.nodeset);
318
+ Fore.dispatch(this, 'item-created', { nodeset: repeatItem.nodeset, pos: index + 1 });
319
+ this._initVariables(repeatItem);
320
+ });
321
+ }
322
+
323
+ getRepeatItems() {
324
+ return Array.from(this.querySelectorAll(':scope > fx-repeatitem'));
325
+ }
326
+
327
+ _deleteHandler(deleted) {
328
+ console.log('handleDelete', deleted);
329
+ // grab the current repeat items (tweak selector if yours differs)
330
+ /**
331
+ * @type {import('./fx-repeatitem.js').FxRepeatitem[]}
332
+ */
333
+ const items = this.getRepeatItems();
334
+
335
+ /*
336
+ const items = Array.from(
337
+ this.querySelectorAll(
338
+ ':scope > fx-repeatitem, :scope > .fx-repeatitem',
339
+ ),
340
+ );
341
+ */
342
+
343
+ this._evalNodeset();
344
+
345
+ const indexToRemove = items.findIndex(item => item.nodeset === deleted);
346
+ if (indexToRemove === -1) {
347
+ return;
348
+ }
349
+ const itemToRemove = items[indexToRemove];
350
+
351
+ itemToRemove.remove();
352
+
353
+ // Make the next item the 'current'
354
+ this.setIndex(indexToRemove + 1);
355
+ }
356
+
357
+ /**
358
+ * @abstract
359
+ */
360
+ setIndex(index) {
361
+ throw new Error('Not implemented');
362
+ }
363
+
364
+ applyIndex(repeatItem) {
365
+ this._removeIndexMarker();
366
+ if (repeatItem) {
367
+ repeatItem.setAttribute('repeat-index', '');
368
+ }
369
+ }
370
+
371
+ _initVariables(newRepeatItem) {
372
+ const inScopeVariables = new Map(this.inScopeVariables);
373
+ newRepeatItem.setInScopeVariables(inScopeVariables);
374
+ (function registerVariables(node) {
375
+ for (const child of node.children) {
376
+ if ('setInScopeVariables' in child) {
377
+ child.setInScopeVariables(inScopeVariables);
378
+ }
379
+ registerVariables(child);
380
+ }
381
+ })(newRepeatItem);
382
+ }
383
+
384
+ clearTextValues(node) {
385
+ if (!node) return;
386
+
387
+ // Clear text node content
388
+ if (node.nodeType === Node.TEXT_NODE) {
389
+ node.nodeValue = '';
390
+ }
391
+
392
+ // Clear all attribute values
393
+ if (node.nodeType === Node.ELEMENT_NODE) {
394
+ for (const attr of Array.from(node.attributes)) {
395
+ attr.value = ''; // Clear attribute value
396
+ }
397
+ }
398
+
399
+ // Recursively clear child nodes
400
+ for (const child of node.childNodes) {
401
+ this.clearTextValues(child);
402
+ }
403
+ }
404
+
405
+ _clone() {
406
+ // const content = this.template.content.cloneNode(true);
407
+ this.template = this.shadowRoot.querySelector('template');
408
+ const content = this.template.content.cloneNode(true);
409
+ return document.importNode(content, true);
410
+ }
411
+
412
+ _removeIndexMarker() {
413
+ Array.from(this.children).forEach(item => {
414
+ item.removeAttribute('repeat-index');
415
+ });
416
+ }
417
+
418
+ _createModelItemsRecursively(parentNode, parentModelItem) {
419
+ const parentWithDewey = parentModelItem?.path || null; // e.g. $default/AllowanceCharge[2]_1
420
+ const parentBase = parentWithDewey ? parentWithDewey.replace(/_\d+$/, '') : null; // e.g. $default/AllowanceCharge[2]
421
+
422
+ // Robust Dewey rewrite that tolerates $inst vs instance('inst') forms
423
+ const __applyDeweyRewrite = mi => {
424
+ if (!mi || typeof mi.path !== 'string' || !parentModelItem?.path) return;
425
+
426
+ const pWith = parentModelItem.path; // e.g. $default/AllowanceCharge[2]_1 or instance('default')/AllowanceCharge[2]_1
427
+ const opMatch = pWith.match(/_(\d+)$/);
428
+ if (!opMatch) return;
429
+ const op = opMatch[1];
430
+
431
+ // Normalize to $name/ and strip _n on parent; normalize child for prefix test only
432
+ const toDollar = s => s.replace(/^instance\('([^']+)'\)\//, (_m, g1) => `$${g1}/`);
433
+ const parentBaseNorm = toDollar(pWith).replace(/_\d+$/, ''); // $default/AllowanceCharge[2]
434
+ const childNorm = toDollar(mi.path);
435
+
436
+ if (!childNorm.startsWith(parentBaseNorm)) return; // unrelated subtree
437
+
438
+ // Preserve original style of child's instance prefix
439
+ const childUsesInstanceFn = /^instance\('/.test(mi.path);
440
+ const parentBaseInChildStyle = childUsesInstanceFn
441
+ ? parentBaseNorm.replace(/^\$([A-Za-z0-9_-]+)\//, `instance('$1')/`)
442
+ : parentBaseNorm;
443
+
444
+ // If already suffixed for this parent, nothing to do
445
+ if (mi.path.startsWith(`${parentBaseInChildStyle}_`)) return;
446
+
447
+ // Inject _op immediately after the parent base segment
448
+ mi.path = `${parentBaseInChildStyle}_${op}${mi.path.slice(parentBaseInChildStyle.length)}`;
449
+ };
450
+
451
+ if (parentNode.attachObserver) {
452
+ parentNode.attachObserver();
453
+ }
454
+ Array.from(parentNode.children).forEach(child => {
455
+ const nextParentMI = parentModelItem;
456
+
457
+ // Skip native/embedded widgets that may carry a 'ref' but are UI only
458
+ const isWidgetEl =
459
+ child &&
460
+ ((child.classList && child.classList.contains('widget')) ||
461
+ (typeof Fore !== 'undefined' && Fore.isWidget && Fore.isWidget(child)) ||
462
+ (child.tagName &&
463
+ ['INPUT', 'SELECT', 'TEXTAREA', 'OPTION', 'DATALIST'].includes(child.tagName)));
464
+
465
+ if (!isWidgetEl && child.hasAttribute('ref')) {
466
+ const ref = child.getAttribute('ref').trim();
467
+ if (ref && ref !== '.') {
468
+ // Evaluate the FULL ref once — this yields the terminal (last) node(s)
469
+ let node = evaluateXPath(ref, parentModelItem.node, this);
470
+ if (Array.isArray(node)) node = node[0];
471
+
472
+ if (node) {
473
+ let modelItem = this.getModel().getModelItem(node);
474
+ if (!modelItem) {
475
+ // Create a ModelItem only for the final node; children never get their own opNum
476
+ modelItem = FxBind.createModelItem(ref, node, child, null);
477
+ modelItem.parentModelItem = parentModelItem;
478
+ this.getModel().registerModelItem(modelItem);
479
+ }
480
+
481
+ // Always apply Dewey rewrite (handles both $inst and instance('inst') forms)
482
+ __applyDeweyRewrite(modelItem);
483
+
484
+ child.nodeset = node;
485
+ if (child.attachObserver) child.attachObserver();
486
+ }
487
+ }
488
+ }
489
+
490
+ // Recurse into non-widget subtrees
491
+ if (!isWidgetEl) this._createModelItemsRecursively(child, nextParentMI);
492
+ });
493
+ }
494
+
495
+ // eslint-disable-next-line class-methods-use-this
496
+ _fadeOut(el) {
497
+ el.style.opacity = 1;
498
+
499
+ (function fade() {
500
+ // eslint-disable-next-line no-cond-assign
501
+ if ((el.style.opacity -= 0.1) < 0) {
502
+ el.style.display = 'none';
503
+ } else {
504
+ requestAnimationFrame(fade);
505
+ }
506
+ })();
507
+ }
508
+
509
+ // eslint-disable-next-line class-methods-use-this
510
+ _fadeIn(el) {
511
+ if (!el) return;
512
+
513
+ el.style.opacity = 0;
514
+ el.style.display = this.display;
515
+
516
+ (function fade() {
517
+ // setTimeout(() => {
518
+ let val = parseFloat(el.style.opacity);
519
+ // eslint-disable-next-line no-cond-assign
520
+ if (!((val += 0.1) > 1)) {
521
+ el.style.opacity = val;
522
+ requestAnimationFrame(fade);
523
+ }
524
+ // }, 40);
525
+ })();
526
+ }
527
+ }
528
+
529
+ // Global accessor for non-module inclusion
530
+ if (typeof window !== 'undefined') {
531
+ window.RepeatBase = window.RepeatBase || RepeatBase;
532
+ }
@@ -1,5 +1,10 @@
1
1
  import getInScopeContext from './getInScopeContext.js';
2
2
 
3
+ /**
4
+ * @template {typeof import('./ForeElementMixin.js').default} T
5
+ * @param {T} superclass
6
+ * @returns {T}
7
+ */
3
8
  export const withDraggability = (superclass, isAlsoDraggable) =>
4
9
  /**
5
10
  * Adds draggability to generic components.
@@ -15,11 +20,9 @@ export const withDraggability = (superclass, isAlsoDraggable) =>
15
20
  };
16
21
  }
17
22
 
18
- constructor() {
19
- super();
20
- }
21
-
22
23
  connectedCallback() {
24
+ super.connectedCallback();
25
+
23
26
  this.drop = event => this._drop(event);
24
27
  this.addEventListener('drop', this.drop);
25
28
  this.dragOver = event => this._dragOver(event);
@@ -82,6 +85,7 @@ export const withDraggability = (superclass, isAlsoDraggable) =>
82
85
 
83
86
  _dragEnd(event) {
84
87
  const item = this.getOwnerForm().draggedItem;
88
+ if (!item) return;
85
89
  if (item.getAttribute('drop-action') === 'copy') {
86
90
  item.remove();
87
91
  }
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  import { XPathUtil } from './xpath-util.js';
15
15
  import { prettifyXml } from './functions/common-function.js';
16
+ import * as fx from 'fontoxpath';
16
17
 
17
18
  const XFORMS_NAMESPACE_URI = 'http://www.w3.org/2002/xforms';
18
19
 
@@ -241,7 +242,7 @@ function findInstanceReferences(xpathQuery) {
241
242
  * @param {HTMLElement} formElement
242
243
  * @returns {NamespaceResolver} The namespace resolver for this context
243
244
  */
244
- function createNamespaceResolver(xpathQuery, formElement) {
245
+ export function createNamespaceResolver(xpathQuery, formElement) {
245
246
  const cachedResolver = getCachedNamespaceResolver(xpathQuery, formElement);
246
247
  if (cachedResolver) {
247
248
  return cachedResolver;
@@ -304,11 +305,13 @@ function createNamespaceResolver(xpathQuery, formElement) {
304
305
  return resolveNamespacePrefix;
305
306
  }
306
307
  }
308
+ /*
307
309
  if (instanceReferences.length > 1) {
308
310
  console.warn(
309
311
  `More than one instance is used in the query "${xpathQuery}". The default namespace resolving will be used`,
310
312
  );
311
313
  }
314
+ */
312
315
 
313
316
  const xpathDefaultNamespace =
314
317
  fxEvaluateXPathToString('ancestor-or-self::*/@xpath-default-namespace[last()]', formElement) ||
@@ -447,6 +450,8 @@ function getVariablesInScope(formElement) {
447
450
  * @param {import('./ForeElementMixin.js').default} formElement The form element associated to the XPath
448
451
  * @param {Object} variables Any variables to pass to the XPath
449
452
  * @param {Object} options Any options to pass to the XPath
453
+ *
454
+ * @returns {any[]}
450
455
  */
451
456
  /*
452
457
  export function evaluateXPath(xpath, contextNode, formElement, variables = {}, options={}, domFacade = null) {
@@ -503,7 +508,7 @@ export function evaluateXPath(xpath, contextNode, formElement, variables = {}, o
503
508
  bubbles: true,
504
509
  detail: {
505
510
  origin: formElement,
506
- message: `Expression '${xpath}' failed`,
511
+ message: `Expression '${xpath}' failed: ${e}`,
507
512
  expr: xpath,
508
513
  level: 'Error',
509
514
  },
@@ -558,7 +563,7 @@ export function evaluateXPathToFirstNode(xpath, contextNode, formElement) {
558
563
  bubbles: true,
559
564
  detail: {
560
565
  origin: formElement,
561
- message: `Expression '${xpath}' failed`,
566
+ message: `Expression '${xpath}' failed: ${e}`,
562
567
  expr: xpath,
563
568
  level: 'Error',
564
569
  },
@@ -597,7 +602,7 @@ export function evaluateXPathToNodes(xpath, contextNode, formElement) {
597
602
  bubbles: true,
598
603
  detail: {
599
604
  origin: formElement,
600
- message: `Expression '${xpath}' failed`,
605
+ message: `Expression '${xpath}' failed: ${e}`,
601
606
  expr: xpath,
602
607
  level: 'Error',
603
608
  },
@@ -634,7 +639,7 @@ export function evaluateXPathToBoolean(xpath, contextNode, formElement) {
634
639
  bubbles: true,
635
640
  detail: {
636
641
  origin: formElement,
637
- message: `Expression '${xpath}' failed`,
642
+ message: `Expression '${xpath}' failed: ${e}`,
638
643
  expr: xpath,
639
644
  level: 'Error',
640
645
  },
@@ -674,7 +679,7 @@ export function evaluateXPathToString(xpath, contextNode, formElement, domFacade
674
679
  bubbles: true,
675
680
  detail: {
676
681
  origin: formElement,
677
- message: `Expression '${xpath}' failed`,
682
+ message: `Expression '${xpath}' failed: ${e}`,
678
683
  expr: xpath,
679
684
  level: 'Error',
680
685
  },
@@ -718,7 +723,7 @@ export function evaluateXPathToStrings(xpath, contextNode, formElement, domFacad
718
723
  bubbles: true,
719
724
  detail: {
720
725
  origin: formElement,
721
- message: `Expression '${xpath}' failed`,
726
+ message: `Expression '${xpath}' failed: ${e}`,
722
727
  expr: xpath,
723
728
  level: 'Error',
724
729
  },
@@ -758,7 +763,7 @@ export function evaluateXPathToNumber(xpath, contextNode, formElement, domFacade
758
763
  bubbles: true,
759
764
  detail: {
760
765
  origin: formElement,
761
- message: `Expression '${xpath}' failed`,
766
+ message: `Expression '${xpath}' failed: ${e}`,
762
767
  expr: xpath,
763
768
  level: 'Error',
764
769
  },
@@ -1364,3 +1369,16 @@ registerCustomXPathFunction(
1364
1369
  return uri.substring(uri.indexOf(':') + 1, uri.length);
1365
1370
  },
1366
1371
  );
1372
+
1373
+ /**
1374
+ * @param {Node} node
1375
+ * @returns string
1376
+ */
1377
+ /*
1378
+ export static getDocPath(node) {
1379
+ const path = fx.evaluateXPathToString('path()', node);
1380
+ // Path is like `$default/x[1]/y[1]`
1381
+ const shortened = XPathUtil.shortenPath(path);
1382
+ return shortened.startsWith('/') ? `${shortened}` : `/${shortened}`;
1383
+ }
1384
+ */