diff --git a/lib/amd/build/local/reactive/dragdrop.min.js b/lib/amd/build/local/reactive/dragdrop.min.js index 14fbdd1bdf1..c6789ae2d8d 100644 --- a/lib/amd/build/local/reactive/dragdrop.min.js +++ b/lib/amd/build/local/reactive/dragdrop.min.js @@ -1,2 +1,2 @@ -define ("core/local/reactive/dragdrop",["exports","core/local/reactive/basecomponent"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c.\n\n/**\n * Drag and drop helper component.\n *\n * This component is used to delegate drag and drop handling.\n *\n * To delegate the logic to this particular element the component should create a new instance\n * passing \"this\" as param. The component will use all the necessary callbacks and add all the\n * necessary listeners to the component element.\n *\n * Component attributes used by dragdrop module:\n * - element: the draggable or dropzone element.\n * - (optional) classes: object with alternative CSS classes\n * - (optional) fullregion: page element affeted by the elementy dragging. Use this attribute if\n * the draggable element affects a bigger region (for example a draggable\n * title).\n * - (optional) autoconfigDraggable: by default, the component will be draggable if it has a\n * getDraggableData method. If this value is false draggable\n * property must be defined using setDraggable method.\n * - (optional) relativeDrag: by default the drag image is located at point (0,0) relative to the\n * mouse position to prevent the mouse from covering it. If this attribute\n * is true the drag image will be located at the click offset.\n *\n * Methods the parent component should have for making it draggable:\n *\n * - getDraggableData(): Object|data\n * Return the data that will be passed to any valid dropzone while it is dragged.\n * If the component has this method, the dragdrop module will enable the dragging,\n * this is the only required method for dragging.\n * If at the dragging moment this method returns a false|null|undefined, the dragging\n * actions won't be captured.\n *\n * - (optional) dragStart(Object dropdata, Event event): void\n * - (optional) dragEnd(Object dropdata, Event event): void\n * Callbacks dragdrop will call when the element is dragged and getDraggableData\n * return some data.\n *\n * Methods the parent component should have for enabling it as a dropzone:\n *\n * - validateDropData(Object dropdata): boolean\n * If that method exists, the dragdrop module will automathically configure the element as dropzone.\n * This method will return true if the dropdata is accepted. In case it returns false, no drag and\n * drop event will be listened for this specific dragged dropdata.\n *\n * - (Optional) showDropZone(Object dropdata, Event event): void\n * - (Optional) hideDropZone(Object dropdata, Event event): void\n * Methods called when a valid dragged data pass over the element.\n *\n * - (Optional) drop(Object dropdata, Event event): void\n * Called when a valid dragged element is dropped over the element.\n *\n * Note that none of this methods will be called if validateDropData\n * returns a false value.\n *\n * This module will also add or remove several CSS classes from both dragged elements and dropzones.\n * See the \"this.classes\" in the create method for more details. In case the parent component wants\n * to use the same classes, it can use the getClasses method. On the other hand, if the parent\n * component has an alternative \"classes\" attribute, this will override the default drag and drop\n * classes.\n *\n * @module core/local/reactive/dragdrop\n * @class core/local/reactive/dragdrop\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core/local/reactive/basecomponent';\n\n// Map with the dragged element generate by an specific reactive applications.\n// Potentially, any component can generate a draggable element to interact with other\n// page elements. However, the dragged data is specific and could only interact with\n// components of the same reactive instance.\nlet activeDropData = new Map();\n\n// Drag & Drop API provides the final drop point and incremental movements but we can\n// provide also starting points and displacements. Absolute displacements simplifies\n// moving components with aboslute position around the page.\nlet dragStartPoint = {};\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n *\n * @param {BaseComponent} parent the parent component.\n */\n create(parent) {\n // Optional component name for debugging.\n this.name = `${parent.name ?? 'unkown'}_dragdrop`;\n\n // Default drag and drop classes.\n this.classes = Object.assign(\n {\n // This class indicate a dragging action is active at a page level.\n BODYDRAGGING: 'dragging',\n\n // Added when draggable and drop are ready.\n DRAGGABLEREADY: 'draggable',\n DROPREADY: 'dropready',\n\n // When a valid drag element is over the element.\n DRAGOVER: 'dragover',\n // When a the component is dragged.\n DRAGGING: 'dragging',\n\n // Dropzones classes names.\n DROPUP: 'drop-up',\n DROPDOWN: 'drop-down',\n DROPZONE: 'drop-zone',\n\n // Drag icon class.\n DRAGICON: 'dragicon',\n },\n parent?.classes ?? {}\n );\n\n // Add the affected region if any.\n this.fullregion = parent.fullregion;\n\n // Keep parent to execute drap and drop handlers.\n this.parent = parent;\n\n // Check if parent handle draggable manually.\n this.autoconfigDraggable = this.parent.draggable ?? true;\n\n // Drag image relative position.\n this.relativeDrag = this.parent.relativeDrag ?? false;\n\n // Sub HTML elements will trigger extra dragEnter and dragOver all the time.\n // To prevent that from affecting dropzones, we need to count the enters and leaves.\n this.entercount = 0;\n\n // Stores if the droparea is shown or not.\n this.dropzonevisible = false;\n\n }\n\n /**\n * Return the component drag and drop CSS classes.\n *\n * @returns {Object} the dragdrop css classes\n */\n getClasses() {\n return this.classes;\n }\n\n /**\n * Initial state ready method.\n *\n * This method will add all the necessary event listeners to the component depending on the\n * parent methods.\n * - Add drop events to the element if the parent component has validateDropData method.\n * - Configure the elements draggable if the parent component has getDraggableData method.\n */\n stateReady() {\n // Add drop events to the element if the parent component has dropable types.\n if (typeof this.parent.validateDropData === 'function') {\n this.element.classList.add(this.classes.DROPREADY);\n this.addEventListener(this.element, 'dragenter', this._dragEnter);\n this.addEventListener(this.element, 'dragleave', this._dragLeave);\n this.addEventListener(this.element, 'dragover', this._dragOver);\n this.addEventListener(this.element, 'drop', this._drop);\n }\n\n // Configure the elements draggable if the parent component has dragable data.\n if (this.autoconfigDraggable && typeof this.parent.getDraggableData === 'function') {\n this.setDraggable(true);\n }\n }\n\n /**\n * Enable or disable the draggable property.\n *\n * @param {bool} value the new draggable value\n */\n setDraggable(value) {\n if (typeof this.parent.getDraggableData !== 'function') {\n throw new Error(`Draggable components must have a getDraggableData method`);\n }\n this.element.setAttribute('draggable', value);\n if (value) {\n this.addEventListener(this.element, 'dragstart', this._dragStart);\n this.addEventListener(this.element, 'dragend', this._dragEnd);\n this.element.classList.add(this.classes.DRAGGABLEREADY);\n } else {\n this.removeEventListener(this.element, 'dragstart', this._dragStart);\n this.removeEventListener(this.element, 'dragend', this._dragEnd);\n this.element.classList.remove(this.classes.DRAGGABLEREADY);\n }\n }\n\n /**\n * Drag start event handler.\n *\n * This method will generate the current dropable data. This data is the one used to determine\n * if a droparea accepts the dropping or not.\n *\n * @param {Event} event the event.\n */\n _dragStart(event) {\n const dropdata = this.parent.getDraggableData();\n if (!dropdata) {\n return;\n }\n\n // Save the starting point.\n dragStartPoint = {\n pageX: event.pageX,\n pageY: event.pageY,\n };\n\n // If the drag event is accepted we prevent any other draggable element from interfiering.\n event.stopPropagation();\n\n // Save the drop data of the current reactive intance.\n activeDropData.set(this.reactive, dropdata);\n\n // Add some CSS classes to indicate the state.\n document.body.classList.add(this.classes.BODYDRAGGING);\n this.element.classList.add(this.classes.DRAGGING);\n this.fullregion?.classList.add(this.classes.DRAGGING);\n\n // Force the drag image. This makes the UX more consistent in case the\n // user dragged an internal element like a link or some other element.\n let dragImage = this.element;\n if (this.parent.setDragImage !== undefined) {\n const customImage = this.parent.setDragImage(dropdata, event);\n if (customImage) {\n dragImage = customImage;\n }\n }\n // Define the image position relative to the mouse.\n const position = {x: 0, y: 0};\n if (this.relativeDrag) {\n position.x = event.offsetX;\n position.y = event.offsetY;\n }\n event.dataTransfer.setDragImage(dragImage, position.x, position.y);\n\n this._callParentMethod('dragStart', dropdata, event);\n }\n\n /**\n * Drag end event handler.\n *\n * @param {Event} event the event.\n */\n _dragEnd(event) {\n const dropdata = activeDropData.get(this.reactive);\n if (!dropdata) {\n return;\n }\n\n // Remove the current dropdata.\n activeDropData.delete(this.reactive);\n\n // Remove the dragging classes.\n document.body.classList.remove(this.classes.BODYDRAGGING);\n this.element.classList.remove(this.classes.DRAGGING);\n this.fullregion?.classList.remove(this.classes.DRAGGING);\n\n // We add the total movement to the event in case the component\n // wants to move its absolute position.\n this._addEventTotalMovement(event);\n\n this._callParentMethod('dragEnd', dropdata, event);\n }\n\n /**\n * Drag enter event handler.\n *\n * The JS drag&drop API triggers several dragenter events on the same element because it bubbles the\n * child events as well. To prevent this form affecting the dropzones display, this methods use\n * \"entercount\" to determine if it's one extra child event or a valid one.\n *\n * @param {Event} event the event.\n */\n _dragEnter(event) {\n const dropdata = this._processEvent(event);\n if (dropdata) {\n this.entercount++;\n this.element.classList.add(this.classes.DRAGOVER);\n if (this.entercount == 1 && !this.dropzonevisible) {\n this.dropzonevisible = true;\n this.element.classList.add(this.classes.DRAGOVER);\n this._callParentMethod('showDropZone', dropdata, event);\n }\n }\n }\n\n /**\n * Drag over event handler.\n *\n * We only use dragover event when a draggable action starts inside a valid dropzone. In those cases\n * the API won't trigger any dragEnter because the dragged alement was already there. We use the\n * dropzonevisible to determine if the component needs to display the dropzones or not.\n *\n * @param {Event} event the event.\n */\n _dragOver(event) {\n const dropdata = this._processEvent(event);\n if (dropdata && !this.dropzonevisible) {\n this.dropzonevisible = true;\n this.element.classList.add(this.classes.DRAGOVER);\n this._callParentMethod('showDropZone', dropdata, event);\n }\n }\n\n /**\n * Drag over leave handler.\n *\n * The JS drag&drop API triggers several dragleave events on the same element because it bubbles the\n * child events as well. To prevent this form affecting the dropzones display, this methods use\n * \"entercount\" to determine if it's one extra child event or a valid one.\n *\n * @param {Event} event the event.\n */\n _dragLeave(event) {\n const dropdata = this._processEvent(event);\n if (dropdata) {\n this.entercount--;\n if (this.entercount == 0 && this.dropzonevisible) {\n this.dropzonevisible = false;\n this.element.classList.remove(this.classes.DRAGOVER);\n this._callParentMethod('hideDropZone', dropdata, event);\n }\n }\n }\n\n /**\n * Drop event handler.\n *\n * This method will call both hideDropZones and drop methods on the parent component.\n *\n * @param {Event} event the event.\n */\n _drop(event) {\n const dropdata = this._processEvent(event);\n if (dropdata) {\n this.entercount = 0;\n if (this.dropzonevisible) {\n this.dropzonevisible = false;\n this._callParentMethod('hideDropZone', dropdata, event);\n }\n this.element.classList.remove(this.classes.DRAGOVER);\n this._callParentMethod('drop', dropdata, event);\n // An accepted drop resets the initial position.\n // Save the starting point.\n dragStartPoint = {};\n }\n }\n\n /**\n * Process a drag and drop event and delegate logic to the parent component.\n *\n * @param {Event} event the drag and drop event\n * @return {Object|false} the dropdata or null if the event should not be processed\n */\n _processEvent(event) {\n const dropdata = this._getDropData(event);\n if (!dropdata) {\n return null;\n }\n if (this.parent.validateDropData(dropdata)) {\n // All accepted drag&drop event must prevent bubbling and defaults, otherwise\n // parent dragdrop instances could capture it by mistake.\n event.preventDefault();\n event.stopPropagation();\n this._addEventTotalMovement(event);\n return dropdata;\n }\n return null;\n }\n\n /**\n * Add the total amout of movement to a mouse event.\n *\n * @param {MouseEvent} event\n */\n _addEventTotalMovement(event) {\n if (dragStartPoint.pageX === undefined || event.pageX === undefined) {\n return;\n }\n event.fixedMovementX = event.pageX - dragStartPoint.pageX;\n event.fixedMovementY = event.pageY - dragStartPoint.pageY;\n event.initialPageX = dragStartPoint.pageX;\n event.initialPageY = dragStartPoint.pageY;\n // The element possible new top.\n const current = this.element.getBoundingClientRect();\n // Add the new position fixed position.\n event.newFixedTop = current.top + event.fixedMovementY;\n event.newFixedLeft = current.left + event.fixedMovementX;\n // The affected region possible new top.\n if (this.fullregion !== undefined) {\n const current = this.fullregion.getBoundingClientRect();\n event.newRegionFixedxTop = current.top + event.fixedMovementY;\n event.newRegionFixedxLeft = current.left + event.fixedMovementX;\n }\n }\n\n /**\n * Convenient method for calling parent component functions if present.\n *\n * @param {string} methodname the name of the method\n * @param {Object} dropdata the current drop data object\n * @param {Event} event the original event\n */\n _callParentMethod(methodname, dropdata, event) {\n if (typeof this.parent[methodname] === 'function') {\n this.parent[methodname](dropdata, event);\n }\n }\n\n /**\n * Get the current dropdata for a specific event.\n *\n * The browser can generate drag&drop events related to several user interactions:\n * - Drag a page elements: this case is registered in the activeDropData map\n * - Drag some HTML selections: ignored for now\n * - Drag a file over the browser: file drag may appear in the future but for now they are ignored.\n *\n * @param {Event} event the original event.\n * @returns {Object|undefined} with the dragged data (or undefined if none)\n */\n _getDropData(event) {\n if (this._containsFiles(event)) {\n return undefined;\n }\n return activeDropData.get(this.reactive);\n }\n\n /**\n * Check if the dragged event contains files.\n *\n * Files dragging does not generate drop data because they came from outsite the page and the component\n * must check it before validating the event.\n *\n * @param {Event} event the original event.\n * @returns {boolean} if the drag dataTransfers contains files.\n */\n _containsFiles(event) {\n if (event.dataTransfer.types) {\n for (var i = 0; i < event.dataTransfer.types.length; i++) {\n if (event.dataTransfer.types[i] == \"Files\") {\n return true;\n }\n }\n }\n return false;\n }\n}\n"],"file":"dragdrop.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/reactive/dragdrop.js"],"names":["activeDropData","Map","dragStartPoint","parent","name","classes","Object","assign","BODYDRAGGING","DRAGGABLEREADY","DROPREADY","DRAGOVER","DRAGGING","DROPUP","DROPDOWN","DROPZONE","DRAGICON","fullregion","autoconfigDraggable","draggable","relativeDrag","entercount","dropzonevisible","validateDropData","element","classList","add","addEventListener","_dragEnter","_dragLeave","_dragOver","_drop","getDraggableData","setDraggable","value","Error","setAttribute","_dragStart","_dragEnd","removeEventListener","remove","event","document","activeElement","matches","preventDefault","dropdata","pageX","pageY","stopPropagation","set","reactive","body","dragImage","setDragImage","customImage","position","x","y","offsetX","offsetY","dataTransfer","_callParentMethod","get","delete","_addEventTotalMovement","_processEvent","_getDropData","fixedMovementX","fixedMovementY","initialPageX","initialPageY","current","getBoundingClientRect","newFixedTop","top","newFixedLeft","left","newRegionFixedxTop","newRegionFixedxLeft","methodname","_containsFiles","types","i","length","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Drag and drop helper component.\n *\n * This component is used to delegate drag and drop handling.\n *\n * To delegate the logic to this particular element the component should create a new instance\n * passing \"this\" as param. The component will use all the necessary callbacks and add all the\n * necessary listeners to the component element.\n *\n * Component attributes used by dragdrop module:\n * - element: the draggable or dropzone element.\n * - (optional) classes: object with alternative CSS classes\n * - (optional) fullregion: page element affeted by the elementy dragging. Use this attribute if\n * the draggable element affects a bigger region (for example a draggable\n * title).\n * - (optional) autoconfigDraggable: by default, the component will be draggable if it has a\n * getDraggableData method. If this value is false draggable\n * property must be defined using setDraggable method.\n * - (optional) relativeDrag: by default the drag image is located at point (0,0) relative to the\n * mouse position to prevent the mouse from covering it. If this attribute\n * is true the drag image will be located at the click offset.\n *\n * Methods the parent component should have for making it draggable:\n *\n * - getDraggableData(): Object|data\n * Return the data that will be passed to any valid dropzone while it is dragged.\n * If the component has this method, the dragdrop module will enable the dragging,\n * this is the only required method for dragging.\n * If at the dragging moment this method returns a false|null|undefined, the dragging\n * actions won't be captured.\n *\n * - (optional) dragStart(Object dropdata, Event event): void\n * - (optional) dragEnd(Object dropdata, Event event): void\n * Callbacks dragdrop will call when the element is dragged and getDraggableData\n * return some data.\n *\n * Methods the parent component should have for enabling it as a dropzone:\n *\n * - validateDropData(Object dropdata): boolean\n * If that method exists, the dragdrop module will automathically configure the element as dropzone.\n * This method will return true if the dropdata is accepted. In case it returns false, no drag and\n * drop event will be listened for this specific dragged dropdata.\n *\n * - (Optional) showDropZone(Object dropdata, Event event): void\n * - (Optional) hideDropZone(Object dropdata, Event event): void\n * Methods called when a valid dragged data pass over the element.\n *\n * - (Optional) drop(Object dropdata, Event event): void\n * Called when a valid dragged element is dropped over the element.\n *\n * Note that none of this methods will be called if validateDropData\n * returns a false value.\n *\n * This module will also add or remove several CSS classes from both dragged elements and dropzones.\n * See the \"this.classes\" in the create method for more details. In case the parent component wants\n * to use the same classes, it can use the getClasses method. On the other hand, if the parent\n * component has an alternative \"classes\" attribute, this will override the default drag and drop\n * classes.\n *\n * @module core/local/reactive/dragdrop\n * @class core/local/reactive/dragdrop\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core/local/reactive/basecomponent';\n\n// Map with the dragged element generate by an specific reactive applications.\n// Potentially, any component can generate a draggable element to interact with other\n// page elements. However, the dragged data is specific and could only interact with\n// components of the same reactive instance.\nlet activeDropData = new Map();\n\n// Drag & Drop API provides the final drop point and incremental movements but we can\n// provide also starting points and displacements. Absolute displacements simplifies\n// moving components with aboslute position around the page.\nlet dragStartPoint = {};\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n *\n * @param {BaseComponent} parent the parent component.\n */\n create(parent) {\n // Optional component name for debugging.\n this.name = `${parent.name ?? 'unkown'}_dragdrop`;\n\n // Default drag and drop classes.\n this.classes = Object.assign(\n {\n // This class indicate a dragging action is active at a page level.\n BODYDRAGGING: 'dragging',\n\n // Added when draggable and drop are ready.\n DRAGGABLEREADY: 'draggable',\n DROPREADY: 'dropready',\n\n // When a valid drag element is over the element.\n DRAGOVER: 'dragover',\n // When a the component is dragged.\n DRAGGING: 'dragging',\n\n // Dropzones classes names.\n DROPUP: 'drop-up',\n DROPDOWN: 'drop-down',\n DROPZONE: 'drop-zone',\n\n // Drag icon class.\n DRAGICON: 'dragicon',\n },\n parent?.classes ?? {}\n );\n\n // Add the affected region if any.\n this.fullregion = parent.fullregion;\n\n // Keep parent to execute drap and drop handlers.\n this.parent = parent;\n\n // Check if parent handle draggable manually.\n this.autoconfigDraggable = this.parent.draggable ?? true;\n\n // Drag image relative position.\n this.relativeDrag = this.parent.relativeDrag ?? false;\n\n // Sub HTML elements will trigger extra dragEnter and dragOver all the time.\n // To prevent that from affecting dropzones, we need to count the enters and leaves.\n this.entercount = 0;\n\n // Stores if the droparea is shown or not.\n this.dropzonevisible = false;\n\n }\n\n /**\n * Return the component drag and drop CSS classes.\n *\n * @returns {Object} the dragdrop css classes\n */\n getClasses() {\n return this.classes;\n }\n\n /**\n * Initial state ready method.\n *\n * This method will add all the necessary event listeners to the component depending on the\n * parent methods.\n * - Add drop events to the element if the parent component has validateDropData method.\n * - Configure the elements draggable if the parent component has getDraggableData method.\n */\n stateReady() {\n // Add drop events to the element if the parent component has dropable types.\n if (typeof this.parent.validateDropData === 'function') {\n this.element.classList.add(this.classes.DROPREADY);\n this.addEventListener(this.element, 'dragenter', this._dragEnter);\n this.addEventListener(this.element, 'dragleave', this._dragLeave);\n this.addEventListener(this.element, 'dragover', this._dragOver);\n this.addEventListener(this.element, 'drop', this._drop);\n }\n\n // Configure the elements draggable if the parent component has dragable data.\n if (this.autoconfigDraggable && typeof this.parent.getDraggableData === 'function') {\n this.setDraggable(true);\n }\n }\n\n /**\n * Enable or disable the draggable property.\n *\n * @param {bool} value the new draggable value\n */\n setDraggable(value) {\n if (typeof this.parent.getDraggableData !== 'function') {\n throw new Error(`Draggable components must have a getDraggableData method`);\n }\n this.element.setAttribute('draggable', value);\n if (value) {\n this.addEventListener(this.element, 'dragstart', this._dragStart);\n this.addEventListener(this.element, 'dragend', this._dragEnd);\n this.element.classList.add(this.classes.DRAGGABLEREADY);\n } else {\n this.removeEventListener(this.element, 'dragstart', this._dragStart);\n this.removeEventListener(this.element, 'dragend', this._dragEnd);\n this.element.classList.remove(this.classes.DRAGGABLEREADY);\n }\n }\n\n /**\n * Drag start event handler.\n *\n * This method will generate the current dropable data. This data is the one used to determine\n * if a droparea accepts the dropping or not.\n *\n * @param {Event} event the event.\n */\n _dragStart(event) {\n // Cancel dragging if any editable form element is focussed.\n if (document.activeElement.matches(`textarea, input`)) {\n event.preventDefault();\n return;\n }\n\n const dropdata = this.parent.getDraggableData();\n if (!dropdata) {\n return;\n }\n\n // Save the starting point.\n dragStartPoint = {\n pageX: event.pageX,\n pageY: event.pageY,\n };\n\n // If the drag event is accepted we prevent any other draggable element from interfiering.\n event.stopPropagation();\n\n // Save the drop data of the current reactive intance.\n activeDropData.set(this.reactive, dropdata);\n\n // Add some CSS classes to indicate the state.\n document.body.classList.add(this.classes.BODYDRAGGING);\n this.element.classList.add(this.classes.DRAGGING);\n this.fullregion?.classList.add(this.classes.DRAGGING);\n\n // Force the drag image. This makes the UX more consistent in case the\n // user dragged an internal element like a link or some other element.\n let dragImage = this.element;\n if (this.parent.setDragImage !== undefined) {\n const customImage = this.parent.setDragImage(dropdata, event);\n if (customImage) {\n dragImage = customImage;\n }\n }\n // Define the image position relative to the mouse.\n const position = {x: 0, y: 0};\n if (this.relativeDrag) {\n position.x = event.offsetX;\n position.y = event.offsetY;\n }\n event.dataTransfer.setDragImage(dragImage, position.x, position.y);\n\n this._callParentMethod('dragStart', dropdata, event);\n }\n\n /**\n * Drag end event handler.\n *\n * @param {Event} event the event.\n */\n _dragEnd(event) {\n const dropdata = activeDropData.get(this.reactive);\n if (!dropdata) {\n return;\n }\n\n // Remove the current dropdata.\n activeDropData.delete(this.reactive);\n\n // Remove the dragging classes.\n document.body.classList.remove(this.classes.BODYDRAGGING);\n this.element.classList.remove(this.classes.DRAGGING);\n this.fullregion?.classList.remove(this.classes.DRAGGING);\n\n // We add the total movement to the event in case the component\n // wants to move its absolute position.\n this._addEventTotalMovement(event);\n\n this._callParentMethod('dragEnd', dropdata, event);\n }\n\n /**\n * Drag enter event handler.\n *\n * The JS drag&drop API triggers several dragenter events on the same element because it bubbles the\n * child events as well. To prevent this form affecting the dropzones display, this methods use\n * \"entercount\" to determine if it's one extra child event or a valid one.\n *\n * @param {Event} event the event.\n */\n _dragEnter(event) {\n const dropdata = this._processEvent(event);\n if (dropdata) {\n this.entercount++;\n this.element.classList.add(this.classes.DRAGOVER);\n if (this.entercount == 1 && !this.dropzonevisible) {\n this.dropzonevisible = true;\n this.element.classList.add(this.classes.DRAGOVER);\n this._callParentMethod('showDropZone', dropdata, event);\n }\n }\n }\n\n /**\n * Drag over event handler.\n *\n * We only use dragover event when a draggable action starts inside a valid dropzone. In those cases\n * the API won't trigger any dragEnter because the dragged alement was already there. We use the\n * dropzonevisible to determine if the component needs to display the dropzones or not.\n *\n * @param {Event} event the event.\n */\n _dragOver(event) {\n const dropdata = this._processEvent(event);\n if (dropdata && !this.dropzonevisible) {\n this.dropzonevisible = true;\n this.element.classList.add(this.classes.DRAGOVER);\n this._callParentMethod('showDropZone', dropdata, event);\n }\n }\n\n /**\n * Drag over leave handler.\n *\n * The JS drag&drop API triggers several dragleave events on the same element because it bubbles the\n * child events as well. To prevent this form affecting the dropzones display, this methods use\n * \"entercount\" to determine if it's one extra child event or a valid one.\n *\n * @param {Event} event the event.\n */\n _dragLeave(event) {\n const dropdata = this._processEvent(event);\n if (dropdata) {\n this.entercount--;\n if (this.entercount == 0 && this.dropzonevisible) {\n this.dropzonevisible = false;\n this.element.classList.remove(this.classes.DRAGOVER);\n this._callParentMethod('hideDropZone', dropdata, event);\n }\n }\n }\n\n /**\n * Drop event handler.\n *\n * This method will call both hideDropZones and drop methods on the parent component.\n *\n * @param {Event} event the event.\n */\n _drop(event) {\n const dropdata = this._processEvent(event);\n if (dropdata) {\n this.entercount = 0;\n if (this.dropzonevisible) {\n this.dropzonevisible = false;\n this._callParentMethod('hideDropZone', dropdata, event);\n }\n this.element.classList.remove(this.classes.DRAGOVER);\n this._callParentMethod('drop', dropdata, event);\n // An accepted drop resets the initial position.\n // Save the starting point.\n dragStartPoint = {};\n }\n }\n\n /**\n * Process a drag and drop event and delegate logic to the parent component.\n *\n * @param {Event} event the drag and drop event\n * @return {Object|false} the dropdata or null if the event should not be processed\n */\n _processEvent(event) {\n const dropdata = this._getDropData(event);\n if (!dropdata) {\n return null;\n }\n if (this.parent.validateDropData(dropdata)) {\n // All accepted drag&drop event must prevent bubbling and defaults, otherwise\n // parent dragdrop instances could capture it by mistake.\n event.preventDefault();\n event.stopPropagation();\n this._addEventTotalMovement(event);\n return dropdata;\n }\n return null;\n }\n\n /**\n * Add the total amout of movement to a mouse event.\n *\n * @param {MouseEvent} event\n */\n _addEventTotalMovement(event) {\n if (dragStartPoint.pageX === undefined || event.pageX === undefined) {\n return;\n }\n event.fixedMovementX = event.pageX - dragStartPoint.pageX;\n event.fixedMovementY = event.pageY - dragStartPoint.pageY;\n event.initialPageX = dragStartPoint.pageX;\n event.initialPageY = dragStartPoint.pageY;\n // The element possible new top.\n const current = this.element.getBoundingClientRect();\n // Add the new position fixed position.\n event.newFixedTop = current.top + event.fixedMovementY;\n event.newFixedLeft = current.left + event.fixedMovementX;\n // The affected region possible new top.\n if (this.fullregion !== undefined) {\n const current = this.fullregion.getBoundingClientRect();\n event.newRegionFixedxTop = current.top + event.fixedMovementY;\n event.newRegionFixedxLeft = current.left + event.fixedMovementX;\n }\n }\n\n /**\n * Convenient method for calling parent component functions if present.\n *\n * @param {string} methodname the name of the method\n * @param {Object} dropdata the current drop data object\n * @param {Event} event the original event\n */\n _callParentMethod(methodname, dropdata, event) {\n if (typeof this.parent[methodname] === 'function') {\n this.parent[methodname](dropdata, event);\n }\n }\n\n /**\n * Get the current dropdata for a specific event.\n *\n * The browser can generate drag&drop events related to several user interactions:\n * - Drag a page elements: this case is registered in the activeDropData map\n * - Drag some HTML selections: ignored for now\n * - Drag a file over the browser: file drag may appear in the future but for now they are ignored.\n *\n * @param {Event} event the original event.\n * @returns {Object|undefined} with the dragged data (or undefined if none)\n */\n _getDropData(event) {\n if (this._containsFiles(event)) {\n return undefined;\n }\n return activeDropData.get(this.reactive);\n }\n\n /**\n * Check if the dragged event contains files.\n *\n * Files dragging does not generate drop data because they came from outsite the page and the component\n * must check it before validating the event.\n *\n * @param {Event} event the original event.\n * @returns {boolean} if the drag dataTransfers contains files.\n */\n _containsFiles(event) {\n if (event.dataTransfer.types) {\n for (var i = 0; i < event.dataTransfer.types.length; i++) {\n if (event.dataTransfer.types[i] == \"Files\") {\n return true;\n }\n }\n }\n return false;\n }\n}\n"],"file":"dragdrop.min.js"} \ No newline at end of file diff --git a/lib/amd/src/local/reactive/dragdrop.js b/lib/amd/src/local/reactive/dragdrop.js index e1ce8fdf449..95cb6a6f1f8 100644 --- a/lib/amd/src/local/reactive/dragdrop.js +++ b/lib/amd/src/local/reactive/dragdrop.js @@ -212,6 +212,12 @@ export default class extends BaseComponent { * @param {Event} event the event. */ _dragStart(event) { + // Cancel dragging if any editable form element is focussed. + if (document.activeElement.matches(`textarea, input`)) { + event.preventDefault(); + return; + } + const dropdata = this.parent.getDraggableData(); if (!dropdata) { return;