@jinntec/fore 2.0.0 → 2.1.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.
package/index.js CHANGED
@@ -22,6 +22,7 @@ import './src/ui/fx-case.js';
22
22
  import './src/ui/fx-inspector.js';
23
23
  import './src/ui/fx-dialog.js';
24
24
  import './src/ui/fx-items.js';
25
+ import './src/ui/fx-droptarget.js';
25
26
 
26
27
  import './src/tools/fx-action-log.js';
27
28
  import './src/tools/fx-log-settings.js';
@@ -57,6 +58,8 @@ import './src/actions/fx-reset.js';
57
58
  import './src/actions/fx-load.js';
58
59
  import './src/actions/fx-toggleboolean.js';
59
60
  import './src/actions/fx-call.js';
61
+ import './src/actions/fx-setattribute.js';
62
+ import './src/actions/fx-construct-done.js';
60
63
 
61
64
  import './src/functions/fx-function.js';
62
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jinntec/fore",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "Fore - declarative user interfaces in plain HTML",
5
5
  "module": "./index.js",
6
6
  "publishConfig": {
@@ -1,5 +1,5 @@
1
1
  import {foreElementMixin} from '../ForeElementMixin.js';
2
- import {evaluateXPathToBoolean, resolveId} from '../xpath-evaluation.js';
2
+ import {evaluateXPathToBoolean, resolveId, evaluateXPath} from '../xpath-evaluation.js';
3
3
  import getInScopeContext from '../getInScopeContext.js';
4
4
  import {Fore} from '../fore.js';
5
5
  import {FxFore} from '../fx-fore.js';
@@ -56,6 +56,18 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
56
56
  ifExpr: {
57
57
  type: String,
58
58
  },
59
+ /**
60
+ * The iterate attribute can be added to any XForms action. It contains an expression
61
+ * that is evaluated once using the in-scope evaluation context before the action is
62
+ * executed, which will result in a sequence of items. The action will be executed with
63
+ * each item in the sequence as its context. This context replaces the default in scope
64
+ * evaluation context.
65
+ *
66
+ * The interaction with `@while` and `@if` is undefined.
67
+ */
68
+ iterateExpr: {
69
+ type: String
70
+ },
59
71
  /**
60
72
  * whether nor not an action needs to run the update cycle
61
73
  */
@@ -88,8 +100,8 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
88
100
  type: String,
89
101
  },
90
102
  /**
91
- * boolean XPath expression. If true loop will be executed. If an ifExpr is present this also needs to be true
92
- * to actually run the action.
103
+ * boolean XPath expression. If true loop will be executed. If an ifExpr is present this
104
+ * also needs to be true to actually run the action.
93
105
  */
94
106
  whileExpr: {
95
107
  type: String,
@@ -103,6 +115,9 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
103
115
  this.needsUpdate = false;
104
116
  }
105
117
 
118
+ disconnectedCallback() {
119
+ }
120
+
106
121
  connectedCallback() {
107
122
  this.style.display = 'none';
108
123
  this.propagate = this.hasAttribute('propagate') ? this.getAttribute('propagate') : 'continue';
@@ -132,6 +147,7 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
132
147
  this.ifExpr = this.hasAttribute('if') ? this.getAttribute('if') : null;
133
148
  this.whileExpr = this.hasAttribute('while') ? this.getAttribute('while') : null;
134
149
  this.delay = this.hasAttribute('delay') ? Number(this.getAttribute('delay')) : 0;
150
+ this.iterateExpr = this.hasAttribute('iterate') ? this.getAttribute('iterate') : null;
135
151
 
136
152
  this._addUpdateListener();
137
153
 
@@ -186,8 +202,6 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
186
202
  */
187
203
  async execute(e) {
188
204
  console.log(this, this.event);
189
- // console.log('execute', this.event);
190
-
191
205
 
192
206
  if (e && e.target.nodeType !== Node.DOCUMENT_NODE && e.target !== window ){
193
207
  /*
@@ -219,19 +233,8 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
219
233
  );
220
234
  }
221
235
 
222
- // console.log('executing', this);
223
- // console.log('executing e', e);
224
- // console.log('executing e phase', e.eventPhase);
236
+ // Outermost handling
225
237
  if (FxFore.outermostHandler === null) {
226
- // console.time('outermostHandler');
227
- /*
228
- console.info(
229
- `%coutermost Action `,
230
- 'background:#e65100; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
231
- this,
232
- );
233
- */
234
- // console.log('starting outermost handler',this);
235
238
  FxFore.outermostHandler = this;
236
239
  this.dispatchEvent(new CustomEvent('outermost-action-start', {
237
240
  composed: true,
@@ -240,16 +243,6 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
240
243
  }));
241
244
  }
242
245
 
243
- /*
244
- if (FxFore.outermostHandler !== this) {
245
- console.info(
246
- `%cAction `,
247
- 'background:orange; color:white; padding:0.3rem; display:inline-block; white-space: nowrap; border-radius:0.3rem;',
248
- this,
249
- );
250
- }
251
- */
252
- // console.log('>>> outermostHandler', FxFore.outermostHandler);
253
246
 
254
247
  if (e) {
255
248
  this.currentEvent = e;
@@ -265,51 +258,26 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
265
258
  this.nodeset = this.targetElement.nodeset;
266
259
  }
267
260
 
268
- // First check if 'if' condition is true - otherwise exist right away
261
+ // Order of application between if / while and iterate is undefined. See
262
+ // https://www.w3.org/MarkUp/Forms/wiki/@iterate
263
+ if (this.iterateExpr) {
264
+ // Same as whileExpr, let it go update UI afterwards
265
+ await this.handleIterateExpr();
266
+ this._finalizePerform(resolveThisEvent);
267
+ return;
268
+ }
269
+
270
+ // Check if 'if' condition is true - otherwise exist right away
269
271
  if (this.ifExpr && !evaluateXPathToBoolean(this.ifExpr, getInScopeContext(this), this)) {
270
272
  this._finalizePerform(resolveThisEvent);
271
273
  return;
272
274
  }
273
275
 
274
- // console.log('performing action', this);
275
-
276
276
  if (this.whileExpr) {
277
- // While: while the condition is true, delay a bit and execute the action
278
- const loop = async () => {
279
- // Start by waiting
280
- await wait(this.delay || 0);
281
-
282
- if (!XPathUtil.contains(this.getOwnerForm(), this)) {
283
- // We are no longer in the document. Stop working
284
- return;
285
- }
286
-
287
- if (!evaluateXPathToBoolean(this.whileExpr, getInScopeContext(this), this)) {
288
- // Done with iterating
289
- return;
290
- }
291
-
292
- // Perform the action once. But quit if it errored
293
- if (!this.performSafe()) {
294
- return;
295
- }
296
-
297
- // Go for one more iteration
298
- if (this.delay) {
299
- // If we have a delay, fire and forget this.
300
- // Otherwise, if we have no delay, keep waiting for all iterations to be done.
301
- // The while is then uninterruptable and immediate
302
- loop();
303
- return;
304
- }
305
- await loop();
306
- };
307
-
308
- // After loop is done call actionPerformed to update the model and UI
309
- await loop();
310
- this._finalizePerform(resolveThisEvent);
311
-
312
- return;
277
+ // After loop is done call actionPerformed to update the model and UI
278
+ await this.handleWhileExpr();
279
+ this._finalizePerform(resolveThisEvent);
280
+ return;
313
281
  }
314
282
 
315
283
  if (this.delay) {
@@ -327,6 +295,72 @@ export class AbstractAction extends foreElementMixin(HTMLElement) {
327
295
  this._finalizePerform(resolveThisEvent);
328
296
  }
329
297
 
298
+ async handleWhileExpr () {
299
+ // While: while the condition is true, delay a bit and execute the action
300
+ // Start by waiting
301
+ await wait(this.delay || 0);
302
+
303
+ if (!XPathUtil.contains(this.getOwnerForm(), this)) {
304
+ // We are no longer in the document. Stop working
305
+ return;
306
+ }
307
+
308
+ if (!evaluateXPathToBoolean(this.whileExpr, getInScopeContext(this), this)) {
309
+ // Done with iterating
310
+ return;
311
+ }
312
+
313
+ // Perform the action once. But quit if it failed
314
+ if (!this.performSafe()) {
315
+ return;
316
+ }
317
+
318
+ // Go for one more iteration
319
+ if (this.delay) {
320
+ // If we have a delay, fire and forget this.
321
+ // Otherwise, if we have no delay, keep waiting for all iterations to be done.
322
+ // The while is then uninterruptable and immediate
323
+
324
+ this.handleWhileExpr();
325
+ return;
326
+ }
327
+
328
+
329
+ await this.handleWhileExpr();
330
+ }
331
+
332
+ async handleIterateExpr () {
333
+ try {
334
+ // Iterate: get the context sequence and perform the action once per item.
335
+ const contextSequence = evaluateXPath(this.iterateExpr, getInScopeContext(this), this);
336
+
337
+ if (contextSequence.length === 0) {
338
+ return;
339
+ }
340
+
341
+ if (!XPathUtil.contains(this.getOwnerForm(), this)) {
342
+ // We are no longer in the document. Stop working
343
+ return;
344
+ }
345
+
346
+ for (const item of contextSequence) {
347
+ if (this.delay) {
348
+ await wait(this.delay || 0);
349
+ }
350
+
351
+ // This will be picked up in `getInscopeContext`
352
+ this.currentContext = item;
353
+
354
+ // Perform the action once. But quit if it failed
355
+ if (!await this.performSafe()) {
356
+ return;
357
+ }
358
+ }
359
+ } finally {
360
+ this.currentContext = null;
361
+ }
362
+ }
363
+
330
364
  _finalizePerform(resolveThisEvent) {
331
365
  this.currentEvent = null;
332
366
  this.actionPerformed();
@@ -18,15 +18,15 @@ export class FxConfirm extends FxAction {
18
18
  }
19
19
 
20
20
  connectedCallback() {
21
- if(super.connectedCallback){
22
- super.connectedCallback();
21
+ if (super.connectedCallback){
22
+ super.connectedCallback();
23
23
  }
24
24
  this.message = this.hasAttribute('message') ? this.getAttribute('message') : null;
25
25
  }
26
26
 
27
27
  async perform() {
28
28
  if (window.confirm(this.message)) {
29
- super.perform();
29
+ await super.perform();
30
30
  }
31
31
  }
32
32
  }
@@ -0,0 +1,28 @@
1
+ import { AbstractAction } from './abstract-action.js';
2
+ import {FxAction} from "./fx-action.js";
3
+ import {Fore} from "../fore.js";
4
+
5
+ /**
6
+ * `fx-action`
7
+ * an action to wrap other actions and defers the update cycle until the end of the block.
8
+ *
9
+ * @customElement
10
+ * @demo demo/index.html
11
+ */
12
+ export class FxConstructDone extends FxAction {
13
+ connectedCallback() {
14
+ // eslint-disable-next-line wc/guard-super-call
15
+ super.connectedCallback();
16
+ console.log('parentNode', this.parentNode);
17
+ if(this.parentNode.nodeName !== 'FX-MODEL'){
18
+ Fore.dispatch(this, 'error', {message:'parent is not a model'})
19
+ return;
20
+ }
21
+ this.parentNode.addEventListener('model-construct-done', e => {
22
+ super.perform();
23
+ });
24
+ }
25
+ }
26
+ if (!customElements.get('fx-construct-done')) {
27
+ window.customElements.define('fx-construct-done', FxConstructDone);
28
+ }
@@ -15,23 +15,23 @@ import {Fore} from '../fore.js';
15
15
  * @customElement
16
16
  */
17
17
  export class FxInsert extends AbstractAction {
18
- static get properties() {
19
- return {
20
- ...super.properties,
21
- at: {
22
- type: Number,
23
- },
24
- position: {
25
- type: Number,
26
- },
27
- origin: {
28
- type: Object,
29
- },
30
- keepValues: {
31
- type: Boolean,
32
- },
33
- };
34
- }
18
+ static get properties() {
19
+ return {
20
+ ...super.properties,
21
+ at: {
22
+ type: Number,
23
+ },
24
+ position: {
25
+ type: Number,
26
+ },
27
+ origin: {
28
+ type: Object,
29
+ },
30
+ keepValues: {
31
+ type: Boolean,
32
+ },
33
+ };
34
+ }
35
35
 
36
36
  constructor() {
37
37
  super();
@@ -127,26 +127,23 @@ export class FxInsert extends AbstractAction {
127
127
  }
128
128
 
129
129
  async perform() {
130
- // super.perform();
131
- // as we're overwriting the superclass we need to dispatch the execute-action ourselves
132
-
133
-
134
- /*
135
- todo: !!! calling super here does not correctly give the nodeset - it's likely still a bug in ForeElementMixin !!!
136
- // super.perform();
137
- console.log('this.nodeset', this.nodeset);
138
- */
139
-
130
+ // We have a few terms here: `inScope` is the 'current item' we have. It is the item we're
131
+ // copying and inserting elsewhere. If we have a `ref`, one of the nodes returned will
132
+ // become the sibling of this copy. The `context` is the new parent of the copied
133
+ // element. It's usually better to add a `context` because that deals with empty elements.
140
134
  let inscope;
135
+ let context;
136
+ let targetSequence = [];
137
+ const inscopeContext = getInScopeContext(this);
138
+
141
139
  // ### 'context' attribute takes precedence over 'ref'
142
- let targetSequence;
143
140
  if (this.hasAttribute('context')) {
144
- inscope = getInScopeContext(this.getAttributeNode('context'), this.getAttribute('context'));
145
- targetSequence = evaluateXPathToNodes(
141
+ [context] = evaluateXPathToNodes(
146
142
  this.getAttribute('context'),
147
- inscope,
143
+ inscopeContext,
148
144
  this.getOwnerForm(),
149
145
  );
146
+ inscope = inscopeContext;
150
147
  }
151
148
 
152
149
  if (this.hasAttribute('ref')) {
@@ -163,14 +160,19 @@ export class FxInsert extends AbstractAction {
163
160
  let insertLocationNode;
164
161
  let index;
165
162
 
166
- // const idx = this._getInsertIndex(inscope, targetSequence);
167
- // console.log('insert index', idx);
168
-
169
163
  // if the targetSequence is empty but we got an originSequence use inscope as context and ignore 'at' and 'position'
170
164
  if (targetSequence.length === 0) {
171
- insertLocationNode = inscope;
172
- inscope.appendChild(originSequenceClone);
173
- index = 1;
165
+ if (context) {
166
+ insertLocationNode = context;
167
+ context.appendChild(originSequenceClone);
168
+ index = 1;
169
+ } else {
170
+ // No context. We can insert into the `inscope`.
171
+ insertLocationNode = inscope;
172
+ inscope.appendChild(originSequenceClone);
173
+ index = 1;
174
+
175
+ }
174
176
  } else {
175
177
  /* ### insert at position given by 'at' or use the last item in the targetSequence ### */
176
178
  if (this.hasAttribute('at')) {
@@ -221,31 +223,31 @@ export class FxInsert extends AbstractAction {
221
223
  }
222
224
  }
223
225
  }
224
- // instance('default')/items/item[index()]
226
+ // instance('default')/items/item[index()]
225
227
 
226
228
  // console.log('insert context item ', insertLocationNode);
227
229
  // console.log('parent ', insertLocationNode.parentNode);
228
230
  // console.log('instance ', this.getModel().getDefaultContext());
229
231
  // Fore.dispatch()
230
232
 
231
- // const instanceId = XPathUtil.resolveInstance(this, this.getAttribute('context'));
233
+ // const instanceId = XPathUtil.resolveInstance(this, this.getAttribute('context'));
232
234
  const instanceId = XPathUtil.resolveInstance(this, this.ref);
233
235
  const inst = this.getModel().getInstance(instanceId);
234
- // console.log('<<<<<<< resolved instance', inst);
235
- // Note: the parent to insert under is always the parent of the inserted node. The 'context' is not always the parent if the sequence is empty, or the position is different
236
- // const xpath = XPathUtil.getPath(originSequenceClone.parentNode, instanceId);
236
+ // console.log('<<<<<<< resolved instance', inst);
237
+ // Note: the parent to insert under is always the parent of the inserted node. The 'context' is not always the parent if the sequence is empty, or the position is different
238
+ // const xpath = XPathUtil.getPath(originSequenceClone.parentNode, instanceId);
237
239
  const xpath = XPathUtil.getPath(insertLocationNode.parentNode, instanceId);
238
240
 
239
241
 
240
242
 
241
243
  const path = Fore.getDomNodeIndexString(originSequenceClone);
242
244
  this.dispatchEvent(
243
- new CustomEvent('execute-action', {
244
- composed: true,
245
- bubbles: true,
246
- cancelable:true,
247
- detail: { action: this, event:this.event, path },
248
- }),
245
+ new CustomEvent('execute-action', {
246
+ composed: true,
247
+ bubbles: true,
248
+ cancelable:true,
249
+ detail: { action: this, event:this.event, path },
250
+ }),
249
251
  );
250
252
 
251
253
  Fore.dispatch(inst,'insert',{
@@ -267,9 +269,9 @@ export class FxInsert extends AbstractAction {
267
269
  }),
268
270
  );
269
271
 
270
- this.needsUpdate = true;
271
- console.log('Changed!', xpath)
272
- return [xpath];
272
+ this.needsUpdate = true;
273
+ console.log('Changed!', xpath)
274
+ return [xpath];
273
275
  }
274
276
 
275
277
  // eslint-disable-next-line class-methods-use-this
@@ -0,0 +1,68 @@
1
+ // import { FxAction } from './fx-action.js';
2
+ import '../fx-model.js';
3
+ import {AbstractAction} from './abstract-action.js';
4
+ import {evaluateXPathToString} from '../xpath-evaluation.js';
5
+ import {Fore} from '../fore.js';
6
+ import getInScopeContext from "../getInScopeContext";
7
+
8
+ /**
9
+ * `fx-setattribute` allows to create and set an attribute value in the data.
10
+ *
11
+ * @customElement
12
+ */
13
+ export default class FxSetattribute extends AbstractAction {
14
+ static get properties() {
15
+ return {
16
+ ...super.properties,
17
+ ref: {
18
+ type: String,
19
+ },
20
+ attrName: {
21
+ type: String,
22
+ },
23
+ attrValue:{
24
+ type: String
25
+ }
26
+ };
27
+ }
28
+
29
+ constructor() {
30
+ super();
31
+ this.ref = '';
32
+ this.attrName = '';
33
+ this.attrValue = '';
34
+ }
35
+
36
+ connectedCallback() {
37
+ if (super.connectedCallback) {
38
+ super.connectedCallback();
39
+ }
40
+
41
+ if (this.hasAttribute('ref')) {
42
+ this.ref = this.getAttribute('ref');
43
+ } else {
44
+ throw new Error('fx-setvalue must specify a "ref" attribute');
45
+ }
46
+ this.attrName = this.hasAttribute('name') ? this.getAttribute('name') : null;
47
+ this.attrValue = this.hasAttribute('value') ? this.getAttribute('value') : '';
48
+ if(!this.attrName){
49
+ Fore.dispatch('this', 'error',{message:'name or value not specified'});
50
+ }
51
+ }
52
+
53
+ async perform() {
54
+ super.perform();
55
+ const mi = this.getModelItem();
56
+ if(mi.node.nodeType !== Node.ELEMENT_NODE){
57
+ Fore.dispatch('this', 'error',{message:'referenced item is not an element'});
58
+ return;
59
+ }
60
+ mi.node.setAttribute(this.attrName, this.attrValue);
61
+ this.needsUpdate = true;
62
+ }
63
+
64
+ }
65
+
66
+ if (!customElements.get('fx-setattribute')) {
67
+ window.customElements.define('fx-setattribute', FxSetattribute);
68
+ }
package/src/fore.js CHANGED
@@ -18,7 +18,7 @@ export class Fore {
18
18
 
19
19
  static TYPE_DEFAULT = 'xs:string';
20
20
 
21
- /**
21
+ /**
22
22
  * Loads and return a piece of HTML
23
23
  * @param url - the Url to load from
24
24
  * @returns {Promise<string>}
@@ -412,6 +412,7 @@ export class Fore {
412
412
  return 'done';
413
413
  }
414
414
 
415
+ /*
415
416
  static evaluateAttributeTemplateExpression(expr, node) {
416
417
  const matches = expr.match(/{[^}]*}/g);
417
418
  if (matches) {
@@ -427,6 +428,7 @@ export class Fore {
427
428
  }
428
429
  return expr;
429
430
  }
431
+ */
430
432
 
431
433
  static fadeInElement(element) {
432
434
  const duration = 600;
package/src/fx-bind.js CHANGED
@@ -72,6 +72,7 @@ export class FxBind extends foreElementMixin(HTMLElement) {
72
72
  this._buildBindGraph();
73
73
  this._createModelItems();
74
74
  }
75
+ // todo: support json
75
76
 
76
77
  // ### process child bindings
77
78
  this._processChildren(model);
package/src/fx-fore.js CHANGED
@@ -26,6 +26,7 @@ import {FxRepeatAttributes} from './ui/fx-repeat-attributes.js';
26
26
  */
27
27
  export class FxFore extends HTMLElement {
28
28
  static outermostHandler = null;
29
+ static draggedItem = null;
29
30
 
30
31
  static get properties() {
31
32
  return {
@@ -103,6 +104,8 @@ export class FxFore extends HTMLElement {
103
104
  // updates are included in that one
104
105
  this.outermostHandler = null;
105
106
 
107
+ this.copiedElements = new WeakSet();
108
+
106
109
  const style = `
107
110
  :host {
108
111
  display: block;
@@ -179,7 +182,6 @@ export class FxFore extends HTMLElement {
179
182
  `;
180
183
 
181
184
  const html = `
182
- <noscript>This page uses Web Components and needs JavaScript to be enabled..</noscript>
183
185
  <!-- <slot name="errors"></slot> -->
184
186
  <jinn-toast id="message" gravity="bottom" position="left"></jinn-toast>
185
187
  <jinn-toast id="sticky" gravity="bottom" position="left" duration="-1" close="true" data-class="sticky-message"></jinn-toast>
@@ -390,6 +392,7 @@ export class FxFore extends HTMLElement {
390
392
  }
391
393
 
392
394
  disconnectedCallback() {
395
+ this.removeEventListener('dragstart', this.dragstart);
393
396
  /*
394
397
  this.removeEventListener('model-construct-done', this._handleModelConstructDone);
395
398
  this.removeEventListener('message', this._displayMessage);
@@ -530,6 +533,7 @@ export class FxFore extends HTMLElement {
530
533
  this._updateTemplateExpressions();
531
534
  this._scanForNewTemplateExpressionsNextRefresh = false; // reset
532
535
  }
536
+
533
537
  this._processTemplateExpressions();
534
538
 
535
539
  // console.log('### <<<<< dispatching refresh-done - end of UI update cycle >>>>>');
@@ -931,8 +935,51 @@ export class FxFore extends HTMLElement {
931
935
  Fore.dispatch(this, 'ready', {});
932
936
  // console.log('dataChanged', FxModel.dataChanged);
933
937
  console.timeEnd('init');
938
+
939
+ this.addEventListener('dragstart', this._handleDragStart);
940
+ // this.addEventListener('dragend', this._handleDragEnd);
941
+ this.handleDrop = event => this._handleDrop(event);
942
+ this.ownerDocument.body.addEventListener('drop', this.handleDrop);
943
+ this.ownerDocument.body.addEventListener('dragover', e=>{
944
+ e.preventDefault();
945
+ e.stopPropagation();
946
+ e.dataTransfer.dropEffect = "move";
947
+ });
934
948
  }
935
949
 
950
+ _handleDragStart (event) {
951
+ const draggedItem = event.target.closest('[draggable="true"]');
952
+ this.originalDraggedItem = draggedItem;
953
+ console.log('DRAG START', this);
954
+ if (draggedItem.getAttribute('drop-action') === 'copy') {
955
+ event.dataTransfer.dropEffect = 'copy';
956
+ event.dataTransfer.effectAllowed = 'copy';
957
+ this.draggedItem = draggedItem.cloneNode(true);
958
+ this.draggedItem.setAttribute('drop-action', 'move');
959
+ this.copiedElements.add(this.draggedItem);
960
+ } else {
961
+ event.dataTransfer.dropEffect = 'move';
962
+ event.dataTransfer.effectAllowed = 'move';
963
+ this.draggedItem = draggedItem;
964
+ }
965
+ }
966
+
967
+ _handleDrop (event) {
968
+ console.log('DROP ON BODY', this)
969
+ if (!this.draggedItem) {
970
+ return;
971
+ }
972
+ // A drop on 'body' should be a removal.
973
+ if (event.dataTransfer.dropEffect === 'none') {
974
+ if (this.copiedElements.has(this.originalDraggedItem)) {
975
+ this.originalDraggedItem.remove();
976
+ }
977
+ }
978
+ this.originalDraggedItem = null
979
+ this.draggedItem = null;
980
+ event.stopPropagation();
981
+ }
982
+
936
983
  registerLazyElement(element) {
937
984
  if (this.intersectionObserver) {
938
985
  // console.log('registerLazyElement',element);