diff --git a/addons/mass_mailing/static/tests/tours/mailing_editor.js b/addons/mass_mailing/static/tests/tours/mailing_editor.js
index d9b2d292df9..b4feeb782fd 100644
--- a/addons/mass_mailing/static/tests/tours/mailing_editor.js
+++ b/addons/mass_mailing/static/tests/tours/mailing_editor.js
@@ -23,9 +23,9 @@ registry.category("web_tour.tours").add('mailing_editor', {
run: () => {},
}, {
content: 'drag the "Title" snippet from the design panel and drop it in the editor',
- trigger: '[name="body_arch"] iframe #email_designer_default_body [name="Title"] .ui-draggable-handle',
+ trigger: '[name="body_arch"] iframe #email_designer_default_body [name="Title"] .oe_snippet_thumbnail',
run: function (actions) {
- actions.drag_and_drop('[name="body_arch"] iframe .o_editable', this.$anchor);
+ actions.drag_and_drop_native('[name="body_arch"] iframe .o_editable', this.$anchor);
}
}, {
content: 'wait for the snippet menu to finish the drop process',
diff --git a/addons/mass_mailing/static/tests/tours/mass_mailing_code_view.js b/addons/mass_mailing/static/tests/tours/mass_mailing_code_view.js
index 45afec6e2ee..da30bc4b012 100644
--- a/addons/mass_mailing/static/tests/tours/mass_mailing_code_view.js
+++ b/addons/mass_mailing/static/tests/tours/mass_mailing_code_view.js
@@ -50,10 +50,10 @@ registry.category("web_tour.tours").add('mass_mailing_code_view_tour', {
content: 'Verify that the dropable zone was not removed',
run: () => {},
}, {
- trigger: '[name="body_arch"] iframe #email_designer_default_body [name="Title"] .ui-draggable-handle',
+ trigger: '[name="body_arch"] iframe #email_designer_default_body [name="Title"] .oe_snippet_thumbnail',
content: 'Drag the "Title" snippet from the design panel and drop it in the editor',
run: function (actions) {
- actions.drag_and_drop('[name="body_arch"] iframe .o_editable', this.$anchor);
+ actions.drag_and_drop_native('[name="body_arch"] iframe .o_editable', this.$anchor);
}
}, {
trigger: '[name="body_arch"] iframe .o_editable h1',
diff --git a/addons/test_website/static/tests/tours/custom_snippets.js b/addons/test_website/static/tests/tours/custom_snippets.js
index 4a8358119e7..1a770c46fcb 100644
--- a/addons/test_website/static/tests/tours/custom_snippets.js
+++ b/addons/test_website/static/tests/tours/custom_snippets.js
@@ -66,13 +66,7 @@ wTourUtils.registerWebsitePreviewTour('test_custom_snippet', {
content: "confirm rename",
trigger: ".oe_snippet[name='Custom Banner'] we-button.o_we_confirm_btn",
},
- {
- content: "drop custom snippet",
- trigger: ".oe_snippet[name='Bruce Banner'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
- extra_trigger: "iframe body.editor_enable",
- moveTrigger: ".oe_drop_zone",
- run: "drag_and_drop iframe #wrap",
- },
+ wTourUtils.dragNDrop({ name: "Bruce Banner" }),
{
content: "ensure banner section exists",
trigger: "iframe #wrap section[data-name='Banner']",
diff --git a/addons/test_website/static/tests/tours/image_upload_progress.js b/addons/test_website/static/tests/tours/image_upload_progress.js
index 22f156dc973..61121fc447b 100644
--- a/addons/test_website/static/tests/tours/image_upload_progress.js
+++ b/addons/test_website/static/tests/tours/image_upload_progress.js
@@ -48,13 +48,13 @@ const setupSteps = [{
content: "drop a snippet",
trigger: "#oe_snippets .oe_snippet[name='Text - Image'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
moveTrigger: "iframe .oe_drop_zone",
- run: "drag_and_drop iframe #wrap",
+ run: "drag_and_drop_native iframe #wrap",
}, {
content: "drop a snippet",
trigger: "#oe_snippets .oe_snippet[name='Image Gallery'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
extra_trigger: "body.editor_has_snippets",
moveTrigger: ".oe_drop_zone",
- run: "drag_and_drop iframe #wrap",
+ run: "drag_and_drop_native iframe #wrap",
}];
const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg, .webp";
diff --git a/addons/test_website/static/tests/tours/reset_views.js b/addons/test_website/static/tests/tours/reset_views.js
index 01fddd2a314..8adb5f8fa39 100644
--- a/addons/test_website/static/tests/tours/reset_views.js
+++ b/addons/test_website/static/tests/tours/reset_views.js
@@ -23,7 +23,7 @@ wTourUtils.registerWebsitePreviewTour('test_reset_page_view_complete_flow_part1'
content: "drop a snippet",
trigger: ".oe_snippet:has(.s_cover) .oe_snippet_thumbnail",
// id starting by 'oe_structure..' will actually create an inherited view
- run: "drag_and_drop iframe #oe_structure_test_website_page",
+ run: "drag_and_drop_native iframe #oe_structure_test_website_page",
},
...wTourUtils.clickOnSave(),
// 2. Edit that COW'd view in the HTML editor to break it.
diff --git a/addons/web/static/lib/jquery.ui/jquery-ui.css b/addons/web/static/lib/jquery.ui/jquery-ui.css
index e1fcdc9ddcf..82ecf7f92e5 100644
--- a/addons/web/static/lib/jquery.ui/jquery-ui.css
+++ b/addons/web/static/lib/jquery.ui/jquery-ui.css
@@ -1,13 +1,9 @@
-/*! jQuery UI - v1.12.1 - 2023-10-04
+/*! jQuery UI - v1.12.1 - 2023-10-10
* http://jqueryui.com
-* Includes: draggable.css, core.css, autocomplete.css, menu.css, theme.css
+* Includes: core.css, autocomplete.css, menu.css, theme.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
* Copyright jQuery Foundation and other contributors; Licensed MIT */
-.ui-draggable-handle {
- -ms-touch-action: none;
- touch-action: none;
-}
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
diff --git a/addons/web/static/lib/jquery.ui/jquery-ui.js b/addons/web/static/lib/jquery.ui/jquery-ui.js
index 8c8a3586093..4dad240a9dc 100644
--- a/addons/web/static/lib/jquery.ui/jquery-ui.js
+++ b/addons/web/static/lib/jquery.ui/jquery-ui.js
@@ -1,6 +1,6 @@
-/*! jQuery UI - v1.12.1 - 2023-10-04
+/*! jQuery UI - v1.12.1 - 2023-10-10
* http://jqueryui.com
-* Includes: widget.js, position.js, data.js, keycode.js, scroll-parent.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/autocomplete.js, widgets/menu.js, widgets/mouse.js
+* Includes: widget.js, position.js, data.js, keycode.js, unique-id.js, widgets/autocomplete.js, widgets/menu.js, widgets/mouse.js
* Copyright jQuery Foundation and other contributors; Licensed MIT */
(function( factory ) {
@@ -1295,41 +1295,6 @@ var keycode = $.ui.keyCode = {
};
-/*!
- * jQuery UI Scroll Parent 1.12.1
- * http://jqueryui.com
- *
- * Copyright jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- */
-
-//>>label: scrollParent
-//>>group: Core
-//>>description: Get the closest ancestor element that is scrollable.
-//>>docs: http://api.jqueryui.com/scrollParent/
-
-
-
-var scrollParent = $.fn.scrollParent = function( includeHidden ) {
- var position = this.css( "position" ),
- excludeStaticParent = position === "absolute",
- overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,
- scrollParent = this.parents().filter( function() {
- var parent = $( this );
- if ( excludeStaticParent && parent.css( "position" ) === "static" ) {
- return false;
- }
- return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) +
- parent.css( "overflow-x" ) );
- } ).eq( 0 );
-
- return position === "fixed" || !scrollParent.length ?
- $( this[ 0 ].ownerDocument || document ) :
- scrollParent;
-};
-
-
/*!
* jQuery UI Unique ID 1.12.1
* http://jqueryui.com
@@ -1660,1716 +1625,6 @@ var safeBlur = $.ui.safeBlur = function( element ) {
};
-/*!
- * jQuery UI Draggable 1.12.1
- * http://jqueryui.com
- *
- * Copyright jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- */
-
-//>>label: Draggable
-//>>group: Interactions
-//>>description: Enables dragging functionality for any element.
-//>>docs: http://api.jqueryui.com/draggable/
-//>>demos: http://jqueryui.com/draggable/
-//>>css.structure: ../../themes/base/draggable.css
-
-
-
-$.widget( "ui.draggable", $.ui.mouse, {
- version: "1.12.1",
- widgetEventPrefix: "drag",
- options: {
- addClasses: true,
- appendTo: "parent",
- axis: false,
- connectToSortable: false,
- containment: false,
- cursor: "auto",
- cursorAt: false,
- grid: false,
- handle: false,
- helper: "original",
- iframeFix: false,
- opacity: false,
- refreshPositions: false,
- revert: false,
- revertDuration: 500,
- scope: "default",
- scroll: true,
- scrollSensitivity: 20,
- scrollSpeed: 20,
- snap: false,
- snapMode: "both",
- snapTolerance: 20,
- stack: false,
- zIndex: false,
-
- // Callbacks
- drag: null,
- start: null,
- stop: null
- },
- _create: function() {
-
- if ( this.options.helper === "original" ) {
- this._setPositionRelative();
- }
- if ( this.options.addClasses ) {
- this._addClass( "ui-draggable" );
- }
- this._setHandleClassName();
-
- this._mouseInit();
- },
-
- _setOption: function( key, value ) {
- this._super( key, value );
- if ( key === "handle" ) {
- this._removeHandleClassName();
- this._setHandleClassName();
- }
- },
-
- _destroy: function() {
- if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
- this.destroyOnClear = true;
- return;
- }
- this._removeHandleClassName();
- this._mouseDestroy();
- },
-
- _mouseCapture: function( event ) {
- var o = this.options;
-
- // Among others, prevent a drag on a resizable-handle
- if ( this.helper || o.disabled ||
- $( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) {
- return false;
- }
-
- //Quit if we're not on a valid handle
- this.handle = this._getHandle( event );
- if ( !this.handle ) {
- return false;
- }
-
- this._blurActiveElement( event );
-
- this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix );
-
- return true;
-
- },
-
- _blockFrames: function( selector ) {
- this.iframeBlocks = this.document.find( selector ).map( function() {
- var iframe = $( this );
-
- return $( "
" )
- .css( "position", "absolute" )
- .appendTo( iframe.parent() )
- .outerWidth( iframe.outerWidth() )
- .outerHeight( iframe.outerHeight() )
- .offset( iframe.offset() )[ 0 ];
- } );
- },
-
- _unblockFrames: function() {
- if ( this.iframeBlocks ) {
- this.iframeBlocks.remove();
- delete this.iframeBlocks;
- }
- },
-
- _blurActiveElement: function( event ) {
- var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),
- target = $( event.target );
-
- // Don't blur if the event occurred on an element that is within
- // the currently focused element
- // See #10527, #12472
- if ( target.closest( activeElement ).length ) {
- return;
- }
-
- // Blur any element that currently has focus, see #4261
- $.ui.safeBlur( activeElement );
- },
-
- _mouseStart: function( event ) {
-
- var o = this.options;
-
- //Create and append the visible helper
- this.helper = this._createHelper( event );
-
- this._addClass( this.helper, "ui-draggable-dragging" );
-
- //Cache the helper size
- this._cacheHelperProportions();
-
- //If ddmanager is used for droppables, set the global draggable
- if ( $.ui.ddmanager ) {
- $.ui.ddmanager.current = this;
- }
-
- /*
- * - Position generation -
- * This block generates everything position related - it's the core of draggables.
- */
-
- //Cache the margins of the original element
- this._cacheMargins();
-
- //Store the helper's css position
- this.cssPosition = this.helper.css( "position" );
- this.scrollParent = this.helper.scrollParent( true );
- this.offsetParent = this.helper.offsetParent();
- this.hasFixedAncestor = this.helper.parents().filter( function() {
- return $( this ).css( "position" ) === "fixed";
- } ).length > 0;
-
- //The element's absolute position on the page minus margins
- this.positionAbs = this.element.offset();
- this._refreshOffsets( event );
-
- //Generate the original position
- this.originalPosition = this.position = this._generatePosition( event, false );
- this.originalPageX = event.pageX;
- this.originalPageY = event.pageY;
-
- //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
- ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
-
- //Set a containment if given in the options
- this._setContainment();
-
- //Trigger event + callbacks
- if ( this._trigger( "start", event ) === false ) {
- this._clear();
- return false;
- }
-
- //Recache the helper size
- this._cacheHelperProportions();
-
- //Prepare the droppable offsets
- if ( $.ui.ddmanager && !o.dropBehaviour ) {
- $.ui.ddmanager.prepareOffsets( this, event );
- }
-
- // Execute the drag once - this causes the helper not to be visible before getting its
- // correct position
- this._mouseDrag( event, true );
-
- // If the ddmanager is used for droppables, inform the manager that dragging has started
- // (see #5003)
- if ( $.ui.ddmanager ) {
- $.ui.ddmanager.dragStart( this, event );
- }
-
- return true;
- },
-
- _refreshOffsets: function( event ) {
- this.offset = {
- top: this.positionAbs.top - this.margins.top,
- left: this.positionAbs.left - this.margins.left,
- scroll: false,
- parent: this._getParentOffset(),
- relative: this._getRelativeOffset()
- };
-
- this.offset.click = {
- left: event.pageX - this.offset.left,
- top: event.pageY - this.offset.top
- };
- },
-
- _mouseDrag: function( event, noPropagation ) {
-
- // reset any necessary cached properties (see #5009)
- if ( this.hasFixedAncestor ) {
- this.offset.parent = this._getParentOffset();
- }
-
- //Compute the helpers position
- this.position = this._generatePosition( event, true );
- this.positionAbs = this._convertPositionTo( "absolute" );
-
- //Call plugins and callbacks and use the resulting position if something is returned
- if ( !noPropagation ) {
- var ui = this._uiHash();
- if ( this._trigger( "drag", event, ui ) === false ) {
- this._mouseUp( new $.Event( "mouseup", event ) );
- return false;
- }
- this.position = ui.position;
- }
-
- this.helper[ 0 ].style.left = this.position.left + "px";
- this.helper[ 0 ].style.top = this.position.top + "px";
-
- if ( $.ui.ddmanager ) {
- $.ui.ddmanager.drag( this, event );
- }
-
- return false;
- },
-
- _mouseStop: function( event ) {
-
- //If we are using droppables, inform the manager about the drop
- var that = this,
- dropped = false;
- if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
- dropped = $.ui.ddmanager.drop( this, event );
- }
-
- //if a drop comes from outside (a sortable)
- if ( this.dropped ) {
- dropped = this.dropped;
- this.dropped = false;
- }
-
- if ( ( this.options.revert === "invalid" && !dropped ) ||
- ( this.options.revert === "valid" && dropped ) ||
- this.options.revert === true || ( $.isFunction( this.options.revert ) &&
- this.options.revert.call( this.element, dropped ) )
- ) {
- $( this.helper ).animate(
- this.originalPosition,
- parseInt( this.options.revertDuration, 10 ),
- function() {
- if ( that._trigger( "stop", event ) !== false ) {
- that._clear();
- }
- }
- );
- } else {
- if ( this._trigger( "stop", event ) !== false ) {
- this._clear();
- }
- }
-
- return false;
- },
-
- _mouseUp: function( event ) {
- this._unblockFrames();
-
- // If the ddmanager is used for droppables, inform the manager that dragging has stopped
- // (see #5003)
- if ( $.ui.ddmanager ) {
- $.ui.ddmanager.dragStop( this, event );
- }
-
- // Only need to focus if the event occurred on the draggable itself, see #10527
- if ( this.handleElement.is( event.target ) ) {
-
- // The interaction is over; whether or not the click resulted in a drag,
- // focus the element
- this.element.trigger( "focus" );
- }
-
- return $.ui.mouse.prototype._mouseUp.call( this, event );
- },
-
- cancel: function() {
-
- if ( this.helper.is( ".ui-draggable-dragging" ) ) {
- this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) );
- } else {
- this._clear();
- }
-
- return this;
-
- },
-
- _getHandle: function( event ) {
- return this.options.handle ?
- !!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
- true;
- },
-
- _setHandleClassName: function() {
- this.handleElement = this.options.handle ?
- this.element.find( this.options.handle ) : this.element;
- this._addClass( this.handleElement, "ui-draggable-handle" );
- },
-
- _removeHandleClassName: function() {
- this._removeClass( this.handleElement, "ui-draggable-handle" );
- },
-
- _createHelper: function( event ) {
-
- var o = this.options,
- helperIsFunction = $.isFunction( o.helper ),
- helper = helperIsFunction ?
- $( o.helper.apply( this.element[ 0 ], [ event ] ) ) :
- ( o.helper === "clone" ?
- this.element.clone().removeAttr( "id" ) :
- this.element );
-
- if ( !helper.parents( "body" ).length ) {
- helper.appendTo( ( o.appendTo === "parent" ?
- this.element[ 0 ].parentNode :
- o.appendTo ) );
- }
-
- // Http://bugs.jqueryui.com/ticket/9446
- // a helper function can return the original element
- // which wouldn't have been set to relative in _create
- if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {
- this._setPositionRelative();
- }
-
- if ( helper[ 0 ] !== this.element[ 0 ] &&
- !( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) {
- helper.css( "position", "absolute" );
- }
-
- return helper;
-
- },
-
- _setPositionRelative: function() {
- if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) {
- this.element[ 0 ].style.position = "relative";
- }
- },
-
- _adjustOffsetFromHelper: function( obj ) {
- if ( typeof obj === "string" ) {
- obj = obj.split( " " );
- }
- if ( $.isArray( obj ) ) {
- obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
- }
- if ( "left" in obj ) {
- this.offset.click.left = obj.left + this.margins.left;
- }
- if ( "right" in obj ) {
- this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
- }
- if ( "top" in obj ) {
- this.offset.click.top = obj.top + this.margins.top;
- }
- if ( "bottom" in obj ) {
- this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
- }
- },
-
- _isRootNode: function( element ) {
- return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
- },
-
- _getParentOffset: function() {
-
- //Get the offsetParent and cache its position
- var po = this.offsetParent.offset(),
- document = this.document[ 0 ];
-
- // This is a special case where we need to modify a offset calculated on start, since the
- // following happened:
- // 1. The position of the helper is absolute, so it's position is calculated based on the
- // next positioned parent
- // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
- // the document, which means that the scroll is included in the initial calculation of the
- // offset of the parent, and never recalculated upon drag
- if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document &&
- $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
- po.left += this.scrollParent.scrollLeft();
- po.top += this.scrollParent.scrollTop();
- }
-
- if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
- po = { top: 0, left: 0 };
- }
-
- return {
- top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
- left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
- };
-
- },
-
- _getRelativeOffset: function() {
- if ( this.cssPosition !== "relative" ) {
- return { top: 0, left: 0 };
- }
-
- var p = this.element.position(),
- scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
-
- return {
- top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
- ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
- left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
- ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
- };
-
- },
-
- _cacheMargins: function() {
- this.margins = {
- left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ),
- top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ),
- right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ),
- bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 )
- };
- },
-
- _cacheHelperProportions: function() {
- this.helperProportions = {
- width: this.helper.outerWidth(),
- height: this.helper.outerHeight()
- };
- },
-
- _setContainment: function() {
-
- var isUserScrollable, c, ce,
- o = this.options,
- document = this.document[ 0 ];
-
- this.relativeContainer = null;
-
- if ( !o.containment ) {
- this.containment = null;
- return;
- }
-
- if ( o.containment === "window" ) {
- this.containment = [
- $( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
- $( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
- $( window ).scrollLeft() + $( window ).width() -
- this.helperProportions.width - this.margins.left,
- $( window ).scrollTop() +
- ( $( window ).height() || document.body.parentNode.scrollHeight ) -
- this.helperProportions.height - this.margins.top
- ];
- return;
- }
-
- if ( o.containment === "document" ) {
- this.containment = [
- 0,
- 0,
- $( document ).width() - this.helperProportions.width - this.margins.left,
- ( $( document ).height() || document.body.parentNode.scrollHeight ) -
- this.helperProportions.height - this.margins.top
- ];
- return;
- }
-
- if ( o.containment.constructor === Array ) {
- this.containment = o.containment;
- return;
- }
-
- if ( o.containment === "parent" ) {
- o.containment = this.helper[ 0 ].parentNode;
- }
-
- c = $( o.containment );
- ce = c[ 0 ];
-
- if ( !ce ) {
- return;
- }
-
- isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) );
-
- this.containment = [
- ( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) +
- ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
- ( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) +
- ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
- ( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
- ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) -
- ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) -
- this.helperProportions.width -
- this.margins.left -
- this.margins.right,
- ( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
- ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) -
- ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) -
- this.helperProportions.height -
- this.margins.top -
- this.margins.bottom
- ];
- this.relativeContainer = c;
- },
-
- _convertPositionTo: function( d, pos ) {
-
- if ( !pos ) {
- pos = this.position;
- }
-
- var mod = d === "absolute" ? 1 : -1,
- scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
-
- return {
- top: (
-
- // The absolute mouse position
- pos.top +
-
- // Only for relative positioned nodes: Relative offset from element to offset parent
- this.offset.relative.top * mod +
-
- // The offsetParent's offset without borders (offset + border)
- this.offset.parent.top * mod -
- ( ( this.cssPosition === "fixed" ?
- -this.offset.scroll.top :
- ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod )
- ),
- left: (
-
- // The absolute mouse position
- pos.left +
-
- // Only for relative positioned nodes: Relative offset from element to offset parent
- this.offset.relative.left * mod +
-
- // The offsetParent's offset without borders (offset + border)
- this.offset.parent.left * mod -
- ( ( this.cssPosition === "fixed" ?
- -this.offset.scroll.left :
- ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )
- )
- };
-
- },
-
- _generatePosition: function( event, constrainPosition ) {
-
- var containment, co, top, left,
- o = this.options,
- scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
- pageX = event.pageX,
- pageY = event.pageY;
-
- // Cache the scroll
- if ( !scrollIsRootNode || !this.offset.scroll ) {
- this.offset.scroll = {
- top: this.scrollParent.scrollTop(),
- left: this.scrollParent.scrollLeft()
- };
- }
-
- /*
- * - Position constraining -
- * Constrain the position to a mix of grid, containment.
- */
-
- // If we are not dragging yet, we won't check for options
- if ( constrainPosition ) {
- if ( this.containment ) {
- if ( this.relativeContainer ) {
- co = this.relativeContainer.offset();
- containment = [
- this.containment[ 0 ] + co.left,
- this.containment[ 1 ] + co.top,
- this.containment[ 2 ] + co.left,
- this.containment[ 3 ] + co.top
- ];
- } else {
- containment = this.containment;
- }
-
- if ( event.pageX - this.offset.click.left < containment[ 0 ] ) {
- pageX = containment[ 0 ] + this.offset.click.left;
- }
- if ( event.pageY - this.offset.click.top < containment[ 1 ] ) {
- pageY = containment[ 1 ] + this.offset.click.top;
- }
- if ( event.pageX - this.offset.click.left > containment[ 2 ] ) {
- pageX = containment[ 2 ] + this.offset.click.left;
- }
- if ( event.pageY - this.offset.click.top > containment[ 3 ] ) {
- pageY = containment[ 3 ] + this.offset.click.top;
- }
- }
-
- if ( o.grid ) {
-
- //Check for grid elements set to 0 to prevent divide by 0 error causing invalid
- // argument errors in IE (see ticket #6950)
- top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -
- this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;
- pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||
- top - this.offset.click.top > containment[ 3 ] ) ?
- top :
- ( ( top - this.offset.click.top >= containment[ 1 ] ) ?
- top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;
-
- left = o.grid[ 0 ] ? this.originalPageX +
- Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :
- this.originalPageX;
- pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||
- left - this.offset.click.left > containment[ 2 ] ) ?
- left :
- ( ( left - this.offset.click.left >= containment[ 0 ] ) ?
- left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;
- }
-
- if ( o.axis === "y" ) {
- pageX = this.originalPageX;
- }
-
- if ( o.axis === "x" ) {
- pageY = this.originalPageY;
- }
- }
-
- return {
- top: (
-
- // The absolute mouse position
- pageY -
-
- // Click offset (relative to the element)
- this.offset.click.top -
-
- // Only for relative positioned nodes: Relative offset from element to offset parent
- this.offset.relative.top -
-
- // The offsetParent's offset without borders (offset + border)
- this.offset.parent.top +
- ( this.cssPosition === "fixed" ?
- -this.offset.scroll.top :
- ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
- ),
- left: (
-
- // The absolute mouse position
- pageX -
-
- // Click offset (relative to the element)
- this.offset.click.left -
-
- // Only for relative positioned nodes: Relative offset from element to offset parent
- this.offset.relative.left -
-
- // The offsetParent's offset without borders (offset + border)
- this.offset.parent.left +
- ( this.cssPosition === "fixed" ?
- -this.offset.scroll.left :
- ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
- )
- };
-
- },
-
- _clear: function() {
- this._removeClass( this.helper, "ui-draggable-dragging" );
- if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) {
- this.helper.remove();
- }
- this.helper = null;
- this.cancelHelperRemoval = false;
- if ( this.destroyOnClear ) {
- this.destroy();
- }
- },
-
- // From now on bulk stuff - mainly helpers
-
- _trigger: function( type, event, ui ) {
- ui = ui || this._uiHash();
- $.ui.plugin.call( this, type, [ event, ui, this ], true );
-
- // Absolute position and offset (see #6884 ) have to be recalculated after plugins
- if ( /^(drag|start|stop)/.test( type ) ) {
- this.positionAbs = this._convertPositionTo( "absolute" );
- ui.offset = this.positionAbs;
- }
- return $.Widget.prototype._trigger.call( this, type, event, ui );
- },
-
- plugins: {},
-
- _uiHash: function() {
- return {
- helper: this.helper,
- position: this.position,
- originalPosition: this.originalPosition,
- offset: this.positionAbs
- };
- }
-
-} );
-
-$.ui.plugin.add( "draggable", "connectToSortable", {
- start: function( event, ui, draggable ) {
- var uiSortable = $.extend( {}, ui, {
- item: draggable.element
- } );
-
- draggable.sortables = [];
- $( draggable.options.connectToSortable ).each( function() {
- var sortable = $( this ).sortable( "instance" );
-
- if ( sortable && !sortable.options.disabled ) {
- draggable.sortables.push( sortable );
-
- // RefreshPositions is called at drag start to refresh the containerCache
- // which is used in drag. This ensures it's initialized and synchronized
- // with any changes that might have happened on the page since initialization.
- sortable.refreshPositions();
- sortable._trigger( "activate", event, uiSortable );
- }
- } );
- },
- stop: function( event, ui, draggable ) {
- var uiSortable = $.extend( {}, ui, {
- item: draggable.element
- } );
-
- draggable.cancelHelperRemoval = false;
-
- $.each( draggable.sortables, function() {
- var sortable = this;
-
- if ( sortable.isOver ) {
- sortable.isOver = 0;
-
- // Allow this sortable to handle removing the helper
- draggable.cancelHelperRemoval = true;
- sortable.cancelHelperRemoval = false;
-
- // Use _storedCSS To restore properties in the sortable,
- // as this also handles revert (#9675) since the draggable
- // may have modified them in unexpected ways (#8809)
- sortable._storedCSS = {
- position: sortable.placeholder.css( "position" ),
- top: sortable.placeholder.css( "top" ),
- left: sortable.placeholder.css( "left" )
- };
-
- sortable._mouseStop( event );
-
- // Once drag has ended, the sortable should return to using
- // its original helper, not the shared helper from draggable
- sortable.options.helper = sortable.options._helper;
- } else {
-
- // Prevent this Sortable from removing the helper.
- // However, don't set the draggable to remove the helper
- // either as another connected Sortable may yet handle the removal.
- sortable.cancelHelperRemoval = true;
-
- sortable._trigger( "deactivate", event, uiSortable );
- }
- } );
- },
- drag: function( event, ui, draggable ) {
- $.each( draggable.sortables, function() {
- var innermostIntersecting = false,
- sortable = this;
-
- // Copy over variables that sortable's _intersectsWith uses
- sortable.positionAbs = draggable.positionAbs;
- sortable.helperProportions = draggable.helperProportions;
- sortable.offset.click = draggable.offset.click;
-
- if ( sortable._intersectsWith( sortable.containerCache ) ) {
- innermostIntersecting = true;
-
- $.each( draggable.sortables, function() {
-
- // Copy over variables that sortable's _intersectsWith uses
- this.positionAbs = draggable.positionAbs;
- this.helperProportions = draggable.helperProportions;
- this.offset.click = draggable.offset.click;
-
- if ( this !== sortable &&
- this._intersectsWith( this.containerCache ) &&
- $.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {
- innermostIntersecting = false;
- }
-
- return innermostIntersecting;
- } );
- }
-
- if ( innermostIntersecting ) {
-
- // If it intersects, we use a little isOver variable and set it once,
- // so that the move-in stuff gets fired only once.
- if ( !sortable.isOver ) {
- sortable.isOver = 1;
-
- // Store draggable's parent in case we need to reappend to it later.
- draggable._parent = ui.helper.parent();
-
- sortable.currentItem = ui.helper
- .appendTo( sortable.element )
- .data( "ui-sortable-item", true );
-
- // Store helper option to later restore it
- sortable.options._helper = sortable.options.helper;
-
- sortable.options.helper = function() {
- return ui.helper[ 0 ];
- };
-
- // Fire the start events of the sortable with our passed browser event,
- // and our own helper (so it doesn't create a new one)
- event.target = sortable.currentItem[ 0 ];
- sortable._mouseCapture( event, true );
- sortable._mouseStart( event, true, true );
-
- // Because the browser event is way off the new appended portlet,
- // modify necessary variables to reflect the changes
- sortable.offset.click.top = draggable.offset.click.top;
- sortable.offset.click.left = draggable.offset.click.left;
- sortable.offset.parent.left -= draggable.offset.parent.left -
- sortable.offset.parent.left;
- sortable.offset.parent.top -= draggable.offset.parent.top -
- sortable.offset.parent.top;
-
- draggable._trigger( "toSortable", event );
-
- // Inform draggable that the helper is in a valid drop zone,
- // used solely in the revert option to handle "valid/invalid".
- draggable.dropped = sortable.element;
-
- // Need to refreshPositions of all sortables in the case that
- // adding to one sortable changes the location of the other sortables (#9675)
- $.each( draggable.sortables, function() {
- this.refreshPositions();
- } );
-
- // Hack so receive/update callbacks work (mostly)
- draggable.currentItem = draggable.element;
- sortable.fromOutside = draggable;
- }
-
- if ( sortable.currentItem ) {
- sortable._mouseDrag( event );
-
- // Copy the sortable's position because the draggable's can potentially reflect
- // a relative position, while sortable is always absolute, which the dragged
- // element has now become. (#8809)
- ui.position = sortable.position;
- }
- } else {
-
- // If it doesn't intersect with the sortable, and it intersected before,
- // we fake the drag stop of the sortable, but make sure it doesn't remove
- // the helper by using cancelHelperRemoval.
- if ( sortable.isOver ) {
-
- sortable.isOver = 0;
- sortable.cancelHelperRemoval = true;
-
- // Calling sortable's mouseStop would trigger a revert,
- // so revert must be temporarily false until after mouseStop is called.
- sortable.options._revert = sortable.options.revert;
- sortable.options.revert = false;
-
- sortable._trigger( "out", event, sortable._uiHash( sortable ) );
- sortable._mouseStop( event, true );
-
- // Restore sortable behaviors that were modfied
- // when the draggable entered the sortable area (#9481)
- sortable.options.revert = sortable.options._revert;
- sortable.options.helper = sortable.options._helper;
-
- if ( sortable.placeholder ) {
- sortable.placeholder.remove();
- }
-
- // Restore and recalculate the draggable's offset considering the sortable
- // may have modified them in unexpected ways. (#8809, #10669)
- ui.helper.appendTo( draggable._parent );
- draggable._refreshOffsets( event );
- ui.position = draggable._generatePosition( event, true );
-
- draggable._trigger( "fromSortable", event );
-
- // Inform draggable that the helper is no longer in a valid drop zone
- draggable.dropped = false;
-
- // Need to refreshPositions of all sortables just in case removing
- // from one sortable changes the location of other sortables (#9675)
- $.each( draggable.sortables, function() {
- this.refreshPositions();
- } );
- }
- }
- } );
- }
-} );
-
-$.ui.plugin.add( "draggable", "cursor", {
- start: function( event, ui, instance ) {
- var t = $( "body" ),
- o = instance.options;
-
- if ( t.css( "cursor" ) ) {
- o._cursor = t.css( "cursor" );
- }
- t.css( "cursor", o.cursor );
- },
- stop: function( event, ui, instance ) {
- var o = instance.options;
- if ( o._cursor ) {
- $( "body" ).css( "cursor", o._cursor );
- }
- }
-} );
-
-$.ui.plugin.add( "draggable", "opacity", {
- start: function( event, ui, instance ) {
- var t = $( ui.helper ),
- o = instance.options;
- if ( t.css( "opacity" ) ) {
- o._opacity = t.css( "opacity" );
- }
- t.css( "opacity", o.opacity );
- },
- stop: function( event, ui, instance ) {
- var o = instance.options;
- if ( o._opacity ) {
- $( ui.helper ).css( "opacity", o._opacity );
- }
- }
-} );
-
-$.ui.plugin.add( "draggable", "scroll", {
- start: function( event, ui, i ) {
- if ( !i.scrollParentNotHidden ) {
- i.scrollParentNotHidden = i.helper.scrollParent( false );
- }
-
- if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] &&
- i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
- i.overflowOffset = i.scrollParentNotHidden.offset();
- }
- },
- drag: function( event, ui, i ) {
-
- var o = i.options,
- scrolled = false,
- scrollParent = i.scrollParentNotHidden[ 0 ],
- document = i.document[ 0 ];
-
- if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
- if ( !o.axis || o.axis !== "x" ) {
- if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY <
- o.scrollSensitivity ) {
- scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
- } else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
- scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
- }
- }
-
- if ( !o.axis || o.axis !== "y" ) {
- if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX <
- o.scrollSensitivity ) {
- scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
- } else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
- scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
- }
- }
-
- } else {
-
- if ( !o.axis || o.axis !== "x" ) {
- if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) {
- scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed );
- } else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) <
- o.scrollSensitivity ) {
- scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed );
- }
- }
-
- if ( !o.axis || o.axis !== "y" ) {
- if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) {
- scrolled = $( document ).scrollLeft(
- $( document ).scrollLeft() - o.scrollSpeed
- );
- } else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) <
- o.scrollSensitivity ) {
- scrolled = $( document ).scrollLeft(
- $( document ).scrollLeft() + o.scrollSpeed
- );
- }
- }
-
- }
-
- if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
- $.ui.ddmanager.prepareOffsets( i, event );
- }
-
- }
-} );
-
-$.ui.plugin.add( "draggable", "snap", {
- start: function( event, ui, i ) {
-
- var o = i.options;
-
- i.snapElements = [];
-
- $( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap )
- .each( function() {
- var $t = $( this ),
- $o = $t.offset();
- if ( this !== i.element[ 0 ] ) {
- i.snapElements.push( {
- item: this,
- width: $t.outerWidth(), height: $t.outerHeight(),
- top: $o.top, left: $o.left
- } );
- }
- } );
-
- },
- drag: function( event, ui, inst ) {
-
- var ts, bs, ls, rs, l, r, t, b, i, first,
- o = inst.options,
- d = o.snapTolerance,
- x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
- y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
-
- for ( i = inst.snapElements.length - 1; i >= 0; i-- ) {
-
- l = inst.snapElements[ i ].left - inst.margins.left;
- r = l + inst.snapElements[ i ].width;
- t = inst.snapElements[ i ].top - inst.margins.top;
- b = t + inst.snapElements[ i ].height;
-
- if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d ||
- !$.contains( inst.snapElements[ i ].item.ownerDocument,
- inst.snapElements[ i ].item ) ) {
- if ( inst.snapElements[ i ].snapping ) {
- ( inst.options.snap.release &&
- inst.options.snap.release.call(
- inst.element,
- event,
- $.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } )
- ) );
- }
- inst.snapElements[ i ].snapping = false;
- continue;
- }
-
- if ( o.snapMode !== "inner" ) {
- ts = Math.abs( t - y2 ) <= d;
- bs = Math.abs( b - y1 ) <= d;
- ls = Math.abs( l - x2 ) <= d;
- rs = Math.abs( r - x1 ) <= d;
- if ( ts ) {
- ui.position.top = inst._convertPositionTo( "relative", {
- top: t - inst.helperProportions.height,
- left: 0
- } ).top;
- }
- if ( bs ) {
- ui.position.top = inst._convertPositionTo( "relative", {
- top: b,
- left: 0
- } ).top;
- }
- if ( ls ) {
- ui.position.left = inst._convertPositionTo( "relative", {
- top: 0,
- left: l - inst.helperProportions.width
- } ).left;
- }
- if ( rs ) {
- ui.position.left = inst._convertPositionTo( "relative", {
- top: 0,
- left: r
- } ).left;
- }
- }
-
- first = ( ts || bs || ls || rs );
-
- if ( o.snapMode !== "outer" ) {
- ts = Math.abs( t - y1 ) <= d;
- bs = Math.abs( b - y2 ) <= d;
- ls = Math.abs( l - x1 ) <= d;
- rs = Math.abs( r - x2 ) <= d;
- if ( ts ) {
- ui.position.top = inst._convertPositionTo( "relative", {
- top: t,
- left: 0
- } ).top;
- }
- if ( bs ) {
- ui.position.top = inst._convertPositionTo( "relative", {
- top: b - inst.helperProportions.height,
- left: 0
- } ).top;
- }
- if ( ls ) {
- ui.position.left = inst._convertPositionTo( "relative", {
- top: 0,
- left: l
- } ).left;
- }
- if ( rs ) {
- ui.position.left = inst._convertPositionTo( "relative", {
- top: 0,
- left: r - inst.helperProportions.width
- } ).left;
- }
- }
-
- if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) {
- ( inst.options.snap.snap &&
- inst.options.snap.snap.call(
- inst.element,
- event,
- $.extend( inst._uiHash(), {
- snapItem: inst.snapElements[ i ].item
- } ) ) );
- }
- inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first );
-
- }
-
- }
-} );
-
-$.ui.plugin.add( "draggable", "stack", {
- start: function( event, ui, instance ) {
- var min,
- o = instance.options,
- group = $.makeArray( $( o.stack ) ).sort( function( a, b ) {
- return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) -
- ( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 );
- } );
-
- if ( !group.length ) { return; }
-
- min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0;
- $( group ).each( function( i ) {
- $( this ).css( "zIndex", min + i );
- } );
- this.css( "zIndex", ( min + group.length ) );
- }
-} );
-
-$.ui.plugin.add( "draggable", "zIndex", {
- start: function( event, ui, instance ) {
- var t = $( ui.helper ),
- o = instance.options;
-
- if ( t.css( "zIndex" ) ) {
- o._zIndex = t.css( "zIndex" );
- }
- t.css( "zIndex", o.zIndex );
- },
- stop: function( event, ui, instance ) {
- var o = instance.options;
-
- if ( o._zIndex ) {
- $( ui.helper ).css( "zIndex", o._zIndex );
- }
- }
-} );
-
-var widgetsDraggable = $.ui.draggable;
-
-
-/*!
- * jQuery UI Droppable 1.12.1
- * http://jqueryui.com
- *
- * Copyright jQuery Foundation and other contributors
- * Released under the MIT license.
- * http://jquery.org/license
- */
-
-//>>label: Droppable
-//>>group: Interactions
-//>>description: Enables drop targets for draggable elements.
-//>>docs: http://api.jqueryui.com/droppable/
-//>>demos: http://jqueryui.com/droppable/
-
-
-
-$.widget( "ui.droppable", {
- version: "1.12.1",
- widgetEventPrefix: "drop",
- options: {
- accept: "*",
- addClasses: true,
- greedy: false,
- scope: "default",
- tolerance: "intersect",
-
- // Callbacks
- activate: null,
- deactivate: null,
- drop: null,
- out: null,
- over: null
- },
- _create: function() {
-
- var proportions,
- o = this.options,
- accept = o.accept;
-
- this.isover = false;
- this.isout = true;
-
- this.accept = $.isFunction( accept ) ? accept : function( d ) {
- return d.is( accept );
- };
-
- this.proportions = function( /* valueToWrite */ ) {
- if ( arguments.length ) {
-
- // Store the droppable's proportions
- proportions = arguments[ 0 ];
- } else {
-
- // Retrieve or derive the droppable's proportions
- return proportions ?
- proportions :
- proportions = {
- width: this.element[ 0 ].offsetWidth,
- height: this.element[ 0 ].offsetHeight
- };
- }
- };
-
- this._addToManager( o.scope );
-
- o.addClasses && this._addClass( "ui-droppable" );
-
- },
-
- _addToManager: function( scope ) {
-
- // Add the reference and positions to the manager
- $.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || [];
- $.ui.ddmanager.droppables[ scope ].push( this );
- },
-
- _splice: function( drop ) {
- var i = 0;
- for ( ; i < drop.length; i++ ) {
- if ( drop[ i ] === this ) {
- drop.splice( i, 1 );
- }
- }
- },
-
- _destroy: function() {
- var drop = $.ui.ddmanager.droppables[ this.options.scope ];
-
- this._splice( drop );
- },
-
- _setOption: function( key, value ) {
-
- if ( key === "accept" ) {
- this.accept = $.isFunction( value ) ? value : function( d ) {
- return d.is( value );
- };
- } else if ( key === "scope" ) {
- var drop = $.ui.ddmanager.droppables[ this.options.scope ];
-
- this._splice( drop );
- this._addToManager( value );
- }
-
- this._super( key, value );
- },
-
- _activate: function( event ) {
- var draggable = $.ui.ddmanager.current;
-
- this._addActiveClass();
- if ( draggable ) {
- this._trigger( "activate", event, this.ui( draggable ) );
- }
- },
-
- _deactivate: function( event ) {
- var draggable = $.ui.ddmanager.current;
-
- this._removeActiveClass();
- if ( draggable ) {
- this._trigger( "deactivate", event, this.ui( draggable ) );
- }
- },
-
- _over: function( event ) {
-
- var draggable = $.ui.ddmanager.current;
-
- // Bail if draggable and droppable are same element
- if ( !draggable || ( draggable.currentItem ||
- draggable.element )[ 0 ] === this.element[ 0 ] ) {
- return;
- }
-
- if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||
- draggable.element ) ) ) {
- this._addHoverClass();
- this._trigger( "over", event, this.ui( draggable ) );
- }
-
- },
-
- _out: function( event ) {
-
- var draggable = $.ui.ddmanager.current;
-
- // Bail if draggable and droppable are same element
- if ( !draggable || ( draggable.currentItem ||
- draggable.element )[ 0 ] === this.element[ 0 ] ) {
- return;
- }
-
- if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||
- draggable.element ) ) ) {
- this._removeHoverClass();
- this._trigger( "out", event, this.ui( draggable ) );
- }
-
- },
-
- _drop: function( event, custom ) {
-
- var draggable = custom || $.ui.ddmanager.current,
- childrenIntersection = false;
-
- // Bail if draggable and droppable are same element
- if ( !draggable || ( draggable.currentItem ||
- draggable.element )[ 0 ] === this.element[ 0 ] ) {
- return false;
- }
-
- this.element
- .find( ":data(ui-droppable)" )
- .not( ".ui-draggable-dragging" )
- .each( function() {
- var inst = $( this ).droppable( "instance" );
- if (
- inst.options.greedy &&
- !inst.options.disabled &&
- inst.options.scope === draggable.options.scope &&
- inst.accept.call(
- inst.element[ 0 ], ( draggable.currentItem || draggable.element )
- ) &&
- intersect(
- draggable,
- $.extend( inst, { offset: inst.element.offset() } ),
- inst.options.tolerance, event
- )
- ) {
- childrenIntersection = true;
- return false; }
- } );
- if ( childrenIntersection ) {
- return false;
- }
-
- if ( this.accept.call( this.element[ 0 ],
- ( draggable.currentItem || draggable.element ) ) ) {
- this._removeActiveClass();
- this._removeHoverClass();
-
- this._trigger( "drop", event, this.ui( draggable ) );
- return this.element;
- }
-
- return false;
-
- },
-
- ui: function( c ) {
- return {
- draggable: ( c.currentItem || c.element ),
- helper: c.helper,
- position: c.position,
- offset: c.positionAbs
- };
- },
-
- // Extension points just to make backcompat sane and avoid duplicating logic
- // TODO: Remove in 1.13 along with call to it below
- _addHoverClass: function() {
- this._addClass( "ui-droppable-hover" );
- },
-
- _removeHoverClass: function() {
- this._removeClass( "ui-droppable-hover" );
- },
-
- _addActiveClass: function() {
- this._addClass( "ui-droppable-active" );
- },
-
- _removeActiveClass: function() {
- this._removeClass( "ui-droppable-active" );
- }
-} );
-
-var intersect = $.ui.intersect = ( function() {
- function isOverAxis( x, reference, size ) {
- return ( x >= reference ) && ( x < ( reference + size ) );
- }
-
- return function( draggable, droppable, toleranceMode, event ) {
-
- if ( !droppable.offset ) {
- return false;
- }
-
- var x1 = ( draggable.positionAbs ||
- draggable.position.absolute ).left + draggable.margins.left,
- y1 = ( draggable.positionAbs ||
- draggable.position.absolute ).top + draggable.margins.top,
- x2 = x1 + draggable.helperProportions.width,
- y2 = y1 + draggable.helperProportions.height,
- l = droppable.offset.left,
- t = droppable.offset.top,
- r = l + droppable.proportions().width,
- b = t + droppable.proportions().height;
-
- switch ( toleranceMode ) {
- case "fit":
- return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );
- case "intersect":
- return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half
- x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half
- t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half
- y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half
- case "pointer":
- return isOverAxis( event.pageY, t, droppable.proportions().height ) &&
- isOverAxis( event.pageX, l, droppable.proportions().width );
- case "touch":
- return (
- ( y1 >= t && y1 <= b ) || // Top edge touching
- ( y2 >= t && y2 <= b ) || // Bottom edge touching
- ( y1 < t && y2 > b ) // Surrounded vertically
- ) && (
- ( x1 >= l && x1 <= r ) || // Left edge touching
- ( x2 >= l && x2 <= r ) || // Right edge touching
- ( x1 < l && x2 > r ) // Surrounded horizontally
- );
- default:
- return false;
- }
- };
-} )();
-
-/*
- This manager tracks offsets of draggables and droppables
-*/
-$.ui.ddmanager = {
- current: null,
- droppables: { "default": [] },
- prepareOffsets: function( t, event ) {
-
- var i, j,
- m = $.ui.ddmanager.droppables[ t.options.scope ] || [],
- type = event ? event.type : null, // workaround for #2317
- list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack();
-
- droppablesLoop: for ( i = 0; i < m.length; i++ ) {
-
- // No disabled and non-accepted
- if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ],
- ( t.currentItem || t.element ) ) ) ) {
- continue;
- }
-
- // Filter out elements in the current dragged item
- for ( j = 0; j < list.length; j++ ) {
- if ( list[ j ] === m[ i ].element[ 0 ] ) {
- m[ i ].proportions().height = 0;
- continue droppablesLoop;
- }
- }
-
- m[ i ].visible = m[ i ].element.css( "display" ) !== "none";
- if ( !m[ i ].visible ) {
- continue;
- }
-
- // Activate the droppable if used directly from draggables
- if ( type === "mousedown" ) {
- m[ i ]._activate.call( m[ i ], event );
- }
-
- m[ i ].offset = m[ i ].element.offset();
- m[ i ].proportions( {
- width: m[ i ].element[ 0 ].offsetWidth,
- height: m[ i ].element[ 0 ].offsetHeight
- } );
-
- }
-
- },
- drop: function( draggable, event ) {
-
- var dropped = false;
-
- // Create a copy of the droppables in case the list changes during the drop (#9116)
- $.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {
-
- if ( !this.options ) {
- return;
- }
- if ( !this.options.disabled && this.visible &&
- intersect( draggable, this, this.options.tolerance, event ) ) {
- dropped = this._drop.call( this, event ) || dropped;
- }
-
- if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ],
- ( draggable.currentItem || draggable.element ) ) ) {
- this.isout = true;
- this.isover = false;
- this._deactivate.call( this, event );
- }
-
- } );
- return dropped;
-
- },
- dragStart: function( draggable, event ) {
-
- // Listen for scrolling so that if the dragging causes scrolling the position of the
- // droppables can be recalculated (see #5003)
- draggable.element.parentsUntil( "body" ).on( "scroll.droppable", function() {
- if ( !draggable.options.refreshPositions ) {
- $.ui.ddmanager.prepareOffsets( draggable, event );
- }
- } );
- },
- drag: function( draggable, event ) {
-
- // If you have a highly dynamic page, you might try this option. It renders positions
- // every time you move the mouse.
- if ( draggable.options.refreshPositions ) {
- $.ui.ddmanager.prepareOffsets( draggable, event );
- }
-
- // Run through all droppables and check their positions based on specific tolerance options
- $.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {
-
- if ( this.options.disabled || this.greedyChild || !this.visible ) {
- return;
- }
-
- var parentInstance, scope, parent,
- intersects = intersect( draggable, this, this.options.tolerance, event ),
- c = !intersects && this.isover ?
- "isout" :
- ( intersects && !this.isover ? "isover" : null );
- if ( !c ) {
- return;
- }
-
- if ( this.options.greedy ) {
-
- // find droppable parents with same scope
- scope = this.options.scope;
- parent = this.element.parents( ":data(ui-droppable)" ).filter( function() {
- return $( this ).droppable( "instance" ).options.scope === scope;
- } );
-
- if ( parent.length ) {
- parentInstance = $( parent[ 0 ] ).droppable( "instance" );
- parentInstance.greedyChild = ( c === "isover" );
- }
- }
-
- // We just moved into a greedy child
- if ( parentInstance && c === "isover" ) {
- parentInstance.isover = false;
- parentInstance.isout = true;
- parentInstance._out.call( parentInstance, event );
- }
-
- this[ c ] = true;
- this[ c === "isout" ? "isover" : "isout" ] = false;
- this[ c === "isover" ? "_over" : "_out" ].call( this, event );
-
- // We just moved out of a greedy child
- if ( parentInstance && c === "isout" ) {
- parentInstance.isout = false;
- parentInstance.isover = true;
- parentInstance._over.call( parentInstance, event );
- }
- } );
-
- },
- dragStop: function( draggable, event ) {
- draggable.element.parentsUntil( "body" ).off( "scroll.droppable" );
-
- // Call prepareOffsets one final time since IE does not fire return scroll events when
- // overflow was caused by drag (see #5003)
- if ( !draggable.options.refreshPositions ) {
- $.ui.ddmanager.prepareOffsets( draggable, event );
- }
- }
-};
-
-// DEPRECATED
-// TODO: switch return back to widget declaration at top of file when this is removed
-if ( $.uiBackCompat !== false ) {
-
- // Backcompat for activeClass and hoverClass options
- $.widget( "ui.droppable", $.ui.droppable, {
- options: {
- hoverClass: false,
- activeClass: false
- },
- _addActiveClass: function() {
- this._super();
- if ( this.options.activeClass ) {
- this.element.addClass( this.options.activeClass );
- }
- },
- _removeActiveClass: function() {
- this._super();
- if ( this.options.activeClass ) {
- this.element.removeClass( this.options.activeClass );
- }
- },
- _addHoverClass: function() {
- this._super();
- if ( this.options.hoverClass ) {
- this.element.addClass( this.options.hoverClass );
- }
- },
- _removeHoverClass: function() {
- this._super();
- if ( this.options.hoverClass ) {
- this.element.removeClass( this.options.hoverClass );
- }
- }
- } );
-}
-
-var widgetsDroppable = $.ui.droppable;
-
/*!
* jQuery UI Menu 1.12.1
diff --git a/addons/web/static/src/core/utils/draggable_hook_builder.js b/addons/web/static/src/core/utils/draggable_hook_builder.js
index d7ab14638a2..b7362cdb99e 100644
--- a/addons/web/static/src/core/utils/draggable_hook_builder.js
+++ b/addons/web/static/src/core/utils/draggable_hook_builder.js
@@ -94,6 +94,7 @@ const DEFAULT_ACCEPTED_PARAMS = {
edgeScrolling: [Object, Function],
delay: [Number],
tolerance: [Number],
+ iframeWindow: [Object, Function],
};
const DEFAULT_DEFAULT_PARAMS = {
elements: `.${DRAGGABLE_CLASS}`,
@@ -231,7 +232,7 @@ function makeDOMHelpers(cleanup) {
if (!el || !classNames.length) {
return;
}
- cleanup.add(saveAttribute(el, "class"));
+ cleanup.add(() => el.classList.remove(...classNames));
el.classList.add(...classNames);
};
@@ -552,6 +553,11 @@ export function makeDraggableHook(hookParams) {
}
dom.addClass(document.body, "pe-none", "user-select-none");
+ if (params.iframeWindow) {
+ dom.addClass(params.iframeWindow.body, "pe-none", "user-select-none");
+ }
+ // FIXME: adding pe-none and cursor on the same element makes
+ // no sense as pe-none prevents the cursor to be displayed.
if (ctx.cursor) {
dom.addStyle(document.body, { cursor: ctx.cursor });
}
@@ -861,6 +867,12 @@ export function makeDraggableHook(hookParams) {
passive: false,
noAddedStyle: true,
});
+ if (params.iframeWindow) {
+ dom.addListener(params.iframeWindow, "touchmove", safePrevent, {
+ passive: false,
+ noAddedStyle: true,
+ });
+ }
}
};
@@ -987,14 +999,20 @@ export function makeDraggableHook(hookParams) {
},
() => [ctx.ref.el]
);
+ const addWindowListener = (type, listener, options) => {
+ if (params.iframeWindow) {
+ setupHooks.addListener(params.iframeWindow, type, listener, options);
+ }
+ setupHooks.addListener(window, type, listener, options);
+ };
// Other global event listeners.
const throttledOnPointerMove = setupHooks.throttle(onPointerMove);
- setupHooks.addListener(window, "pointermove", throttledOnPointerMove, {
+ addWindowListener("pointermove", throttledOnPointerMove, {
passive: false,
});
- setupHooks.addListener(window, "pointerup", onPointerUp);
- setupHooks.addListener(window, "pointercancel", onPointerCancel);
- setupHooks.addListener(window, "keydown", onKeyDown, { capture: true });
+ addWindowListener("pointerup", onPointerUp);
+ addWindowListener("pointercancel", onPointerCancel);
+ addWindowListener("keydown", onKeyDown, { capture: true });
setupHooks.teardown(() => dragEnd(null));
return state;
diff --git a/addons/web_editor/__manifest__.py b/addons/web_editor/__manifest__.py
index 32a5e67b3cb..122a9fd1c32 100644
--- a/addons/web_editor/__manifest__.py
+++ b/addons/web_editor/__manifest__.py
@@ -127,7 +127,7 @@ Odoo Web Editor widget.
'web_editor/static/src/js/editor/odoo-editor/src/commands/toggleList.js',
# utils
- 'web_editor/static/src/js/editor/smooth_scroll_on_drag.js',
+ 'web_editor/static/src/js/editor/drag_and_drop.js',
'web_editor/static/src/js/wysiwyg/linkDialogCommand.js',
'web_editor/static/src/js/wysiwyg/MoveNodePlugin.js',
'web_editor/static/src/js/wysiwyg/PeerToPeer.js',
diff --git a/addons/web_editor/static/src/js/editor/drag_and_drop.js b/addons/web_editor/static/src/js/editor/drag_and_drop.js
new file mode 100644
index 00000000000..40738411f21
--- /dev/null
+++ b/addons/web_editor/static/src/js/editor/drag_and_drop.js
@@ -0,0 +1,201 @@
+/** @odoo-module **/
+import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder";
+import { pick } from "@web/core/utils/objects";
+import { reactive } from "@odoo/owl";
+import { throttleForAnimation } from "@web/core/utils/timing";
+import { touching } from "@web/core/utils/ui";
+
+/** @typedef {import("@web/core/utils/draggable_hook_builder").DraggableHandlerParams} DraggableHandlerParams */
+/** @typedef {import("@web/core/utils/draggable_hook_builder").DraggableBuilderParams} DraggableBuilderParams */
+/** @typedef {import("@web/core/utils/draggable").DraggableParams} DraggableParams */
+
+/** @typedef {DraggableHandlerParams & { dropzone: HTMLElement | null, helper: HTMLElement }} DragAndDropHandlerParams */
+/** @typedef {DraggableHandlerParams & { helper: HTMLElement }} DragAndDropStartParams */
+/** @typedef {DraggableHandlerParams & { dropzone: HTMLElement }} DropzoneHandlerParams */
+/**
+ * @typedef DragAndDropParams
+ * @extends {DraggableParams}
+ *
+ * MANDATORY
+ * @property {(() => Array)} dropzones a function that returns the available dropzones
+ * @property {(() => HTMLElement)} helper a function that returns a helper element
+ * that will follow the cursor when dragging
+ * @property {HTMLElement || (() => HTMLElement)} scrollingElement the element on
+ * which a scroll should be triggered
+ *
+ * HANDLERS (Optional)
+ * @property {(params: DragAndDropStartParams) => any} [onDragStart]
+ * called when a dragging sequence is initiated
+ * @property {(params: DropzoneHandlerParams) => any} [dropzoneOver]
+ * called when an element is over a dropzone
+ * @property {(params: DropzoneHandlerParams) => any} [dropzoneOut]
+ * called when an element is leaving a dropzone
+ * @property {(params: DragAndDropHandlerParams) => any} [onDrag]
+ * called when an element is being dragged
+ * @property {(params: DragAndDropHandlerParams) => any} [onDragEnd]
+ * called when the dragging sequence is over
+ */
+/**
+ * @typedef NativeDraggableState
+ * @property {(params: DraggableParams) => any} update
+ * method to update the params of the draggable
+ * @property {import("@web/core/utils/draggable").DraggableState} state
+ * state of the draggable component
+ * @property {() => any} destroy
+ * method to destroy and unbind the draggable component
+ */
+/**
+ * Utility function to create a native draggable component
+ *
+ * @param {DraggableBuilderParams} hookParams
+ * @param {DraggableParams} initialParams
+ * @returns {NativeDraggableState}
+ */
+export function useNativeDraggable(hookParams, initialParams) {
+ const setupFunctions = new Map();
+ const cleanupFunctions = [];
+ const currentParams = { ...initialParams };
+ const setupHooks = {
+ wrapState: reactive,
+ throttle: throttleForAnimation,
+ addListener: (el, type, callback, options) => {
+ el.addEventListener(type, callback, options);
+ cleanupFunctions.push(() => el.removeEventListener(type, callback));
+ },
+ setup: (setupFn, depsFn) => setupFunctions.set(setupFn, depsFn),
+ teardown: (cleanupFn) => {
+ cleanupFunctions.push(cleanupFn);
+ }
+ };
+ // Compatibility for tests
+ const el = initialParams.ref.el;
+ el.classList.add("o_draggable");
+ cleanupFunctions.push(() => el.classList.remove("o_draggable"));
+
+ const draggableState = makeDraggableHook({ setupHooks, ...hookParams})(currentParams);
+ draggableState.enable = true;
+ const draggableComponent = {
+ state: draggableState,
+ update: (newParams) => {
+ Object.assign(currentParams, newParams);
+ setupFunctions.forEach((depsFn, setupFn) => setupFn(...depsFn()));
+ },
+ destroy: () => {
+ cleanupFunctions.forEach((cleanupFn) => cleanupFn());
+ }
+ };
+ draggableComponent.update({});
+ return draggableComponent;
+}
+
+function updateElementPosition(el, { x, y }, styleFn, offset = { x: 0, y: 0 }) {
+ return styleFn(el, { top: `${y - offset.y}px`, left: `${x - offset.x}px`});
+}
+/** @type DraggableBuilderParams */
+const dragAndDropHookParams = {
+ name: "useDragAndDrop",
+ acceptedParams: {
+ dropzones: [Function],
+ scrollingElement: [Object, Function],
+ helper: [Function],
+ extraWindow: [Object, Function],
+ },
+ edgeScrolling: { enabled: true },
+ onComputeParams({ ctx, params }) {
+ // The helper is mandatory and will follow the cursor instead
+ ctx.followCursor = false;
+ ctx.scrollingElement = params.scrollingElement;
+ ctx.getHelper = params.helper;
+ ctx.getDropZones = params.dropzones;
+ },
+ onWillStartDrag: ({ ctx }) => {
+ ctx.current.container = ctx.scrollingElement;
+ ctx.current.helperOffset = { x: 0, y: 0 };
+ },
+ onDragStart: ({ ctx, addStyle, addCleanup }) => {
+ // Use the helper as the tracking element to properly update scroll values.
+ ctx.current.element = ctx.getHelper({ ...ctx.current, ...ctx.pointer });
+ ctx.current.helper = ctx.current.element;
+ ctx.current.helper.style.position = "fixed";
+ // We want the pointer events on the helper so that the cursor
+ // is properly displayed.
+ ctx.current.helper.classList.remove("o_dragged");
+ ctx.current.helper.style.cursor = ctx.cursor;
+ ctx.current.helper.style.pointerEvents = "auto";
+
+ // If the helper is inside the iframe, we want pointer events on the
+ // frame element so that they reach the window and properly apply
+ // the cursor.
+ const frameElement = ctx.current.helper.ownerDocument.defaultView.frameElement;
+ if (frameElement) {
+ addStyle(frameElement, { pointerEvents: "auto" });
+ }
+
+ addCleanup(() => ctx.current.helper.remove());
+
+ updateElementPosition(ctx.current.helper, ctx.pointer, addStyle, ctx.current.helperOffset);
+
+ return pick(ctx.current, "element", "helper");
+ },
+ onDrag: ({ ctx, addStyle, callHandler }) => {
+ ctx.current.helper.classList.add("o_draggable_dragging");
+
+ updateElementPosition(ctx.current.helper, ctx.pointer, addStyle, ctx.current.helperOffset);
+ // Unfortunately, DOMRect is not an Object, so spreading operator from
+ // `touching` does not work, so convert DOMRect to plain object.
+ let helperRect = ctx.current.helper.getBoundingClientRect();
+ helperRect = {
+ x: helperRect.x,
+ y: helperRect.y,
+ width: helperRect.width,
+ height: helperRect.height,
+ };
+ const dropzoneEl = touching(ctx.getDropZones(), helperRect)[0];
+ // Update the drop zone if it's in grid mode
+ if (ctx.current.dropzone?.el && ctx.current.dropzone.el.classList.contains("oe_grid_zone")) {
+ ctx.current.dropzone.rect = ctx.current.dropzone.el.getBoundingClientRect();
+ }
+ if (
+ ctx.current.dropzone &&
+ (
+ ctx.current.dropzone.el === dropzoneEl ||
+ touching([ctx.current.helper], ctx.current.dropzone.rect).length > 0
+ )
+ ) {
+ // If no new dropzone but old one is still valid, return early.
+ return pick(ctx.current, "element", "dropzone", "helper");
+ }
+
+ if (ctx.current.dropzone && dropzoneEl !== ctx.current.dropzone.el) {
+ callHandler("dropzoneOut", { dropzone: ctx.current.dropzone });
+ delete ctx.current.dropzone;
+ }
+
+ if (dropzoneEl) {
+ // Save rect information prior to calling the over function
+ // to keep a consistent dropzone even if content was added.
+ const rect = DOMRect.fromRect(dropzoneEl.getBoundingClientRect());
+ ctx.current.dropzone = {
+ el: dropzoneEl,
+ rect: {
+ x: rect.x, y: rect.y, width: rect.width, height: rect.height
+ }
+ };
+ callHandler("dropzoneOver", { dropzone: ctx.current.dropzone });
+ }
+ return pick(ctx.current, "element", "dropzone", "helper");
+ },
+ onDragEnd({ ctx }) {
+ return pick(ctx.current, "element", "dropzone", "helper");
+ }
+};
+/**
+ * Function to start a drag and drop handler
+ *
+ * @param {DragAndDropParams} initialParams params given to the drag and drop
+ * component
+ * @returns {NativeDraggableState}
+ */
+export function useDragAndDrop(initialParams) {
+ return useNativeDraggable(dragAndDropHookParams, initialParams);
+}
diff --git a/addons/web_editor/static/src/js/editor/smooth_scroll_on_drag.js b/addons/web_editor/static/src/js/editor/smooth_scroll_on_drag.js
deleted file mode 100644
index 7c4c3ab109e..00000000000
--- a/addons/web_editor/static/src/js/editor/smooth_scroll_on_drag.js
+++ /dev/null
@@ -1,420 +0,0 @@
-/** @odoo-module **/
-
-/**
- * Provides a helper for SmoothScrollOnDrag options.offsetElements
- */
-class OffsetElementsHelper {
- /**
- * @constructor
- * @param {Object} offsetElements
- * @param {jQuery} [offsetElements.$top] top offset element
- * @param {jQuery} [offsetElements.$right] right offset element
- * @param {jQuery} [offsetElements.$bottom] bottom offset element
- * @param {jQuery} [offsetElements.$left] left offset element
- */
- constructor(offsetElements) {
- this.offsetElements = offsetElements;
- }
- top () {
- if (!this.offsetElements.$top || !this.offsetElements.$top.length) {
- return 0;
- }
- return this.offsetElements.$top.get(0).getBoundingClientRect().bottom;
- }
- right () {
- if (!this.offsetElements.$right || !this.offsetElements.$right.length) {
- return 0;
- }
- return this.offsetElements.$right.get(0).getBoundingClientRect().left;
- }
- bottom () {
- if (!this.offsetElements.$bottom || !this.offsetElements.$bottom.length) {
- return 0;
- }
- return this.offsetElements.$bottom.get(0).getBoundingClientRect().top;
- }
- left () {
- if (!this.offsetElements.$left || !this.offsetElements.$left.length) {
- return 0;
- }
- return this.offsetElements.$left.get(0).getBoundingClientRect().right;
- }
-}
-
-/**
- * Provides smooth scroll behaviour on drag.
- */
-export class SmoothScrollOnDrag {
-
- /**
- * @constructor
- * @param {Object} parent The parent widget that uses this class.
- * @param {jQuery} $element The element the smooth scroll on drag has to be set on.
- * @param {jQuery} $scrollTarget The element the scroll will be triggered on.
- * @param {Object} [options={}]
- * @param {Object} [options.jQueryDraggableOptions={}] The configuration to be passed to
- * the jQuery draggable function (all will be passed except scroll which will
- * be overridden to false).
- * @param {Number} [options.scrollOffsetThreshold=150] (Integer) The distance from the
- * bottom/top of the options.$scrollTarget from which the smooth scroll will be
- * triggered.
- * @param {Number} [options.scrollStep=20] (Integer) The step of the scroll.
- * @param {Number} [options.scrollTimerInterval=5] (Integer) The interval (in ms) the
- * scrollStep will be applied.
- * @param {Object} [options.scrollBoundaries = {}] Specifies whether scroll can still be triggered
- * when dragging $element outside of target.
- * @param {Object} [options.scrollBoundaries.top = true] Specifies whether scroll can still be triggered
- * when dragging $element above the top edge of target.
- * @param {Object} [options.scrollBoundaries.right = true] Specifies whether scroll can still be triggered
- * when dragging $element after the right edge of target.
- * @param {Object} [options.scrollBoundaries.bottom = true] Specifies whether scroll can still be triggered
- * when dragging $element bellow the bottom edge of target.
- * @param {Object} [options.scrollBoundaries.left = true] Specifies whether scroll can still be triggered
- * when dragging $element before the left edge of target.
- * @param {Object} [options.offsetElements={}] Visible elements in $scrollTarget that
- * reduce $scrollTarget drag visible area (scroll will be triggered sooner than
- * normally). A selector is passed so that elements such as automatically hidden
- * menu can then be correctly handled.
- * @param {jQuery} [options.offsetElements.$top] Visible top offset element which height will
- * be taken into account when triggering scroll at the top of the $scrollTarget.
- * @param {jQuery} [options.offsetElements.$right] Visible right offset element which width
- * will be taken into account when triggering scroll at the right side of the
- * $scrollTarget.
- * @param {jQuery} [options.offsetElements.$bottom] Visible bottom offset element which height
- * will be taken into account when triggering scroll at bottom of the $scrollTarget.
- * @param {jQuery} [options.offsetElements.$left] Visible left offset element which width
- * will be taken into account when triggering scroll at the left side of the
- * $scrollTarget.
- * @param {boolean} [options.disableHorizontalScroll = false] Disable horizontal scroll if not needed.
- */
- constructor($element, $scrollTarget, options = {}) {
- this.$element = $element;
- this.$scrollTarget = $scrollTarget;
- this.options = options;
- this.targetWindow = this.$element[0].ownerDocument.defaultView;
- const insideIframe = this.targetWindow !== window.top;
-
- // Setting optional options to their default value if not provided
- this.options.jQueryDraggableOptions = this.options.jQueryDraggableOptions || {};
- if (!this.options.jQueryDraggableOptions.cursorAt) {
- this.$element.on('mousedown.smooth_scroll', this._onElementMouseDown.bind(this));
- }
- this.options.scrollOffsetThreshold = this.options.scrollOffsetThreshold || 150;
- this.options.scrollStep = this.options.scrollStep || 20;
- this.options.scrollTimerInterval = this.options.scrollTimerInterval || 5;
- this.options.offsetElements = this.options.offsetElements || {};
- this.options.offsetElementsManager = new OffsetElementsHelper(this.options.offsetElements);
- this.options.scrollBoundaries = Object.assign({
- top: true,
- right: true,
- bottom: true,
- left: true
- }, this.options.scrollBoundaries);
-
- this.autoScrollHandler = null;
-
- this.scrollStepDirectionEnum = {
- up: -1,
- right: 1,
- down: 1,
- left: -1,
- };
-
- this.options.jQueryDraggableOptions.scroll = false;
- this.options.disableHorizontalScroll = this.options.disableHorizontalScroll || false;
- const draggableOptions = Object.assign({}, this.options.jQueryDraggableOptions, {
- start: (ev, ui) => {
- this._onSmoothDragStart(ev, ui, this.options.jQueryDraggableOptions.start);
- if (insideIframe) {
- this.onParentWindowMouseup = this._onParentWindowMouseup.bind(this);
- window.top.addEventListener('mouseup', this.onParentWindowMouseup, {once: true});
- }
- },
- drag: (ev, ui) => this._onSmoothDrag(ev, ui, this.options.jQueryDraggableOptions.drag),
- stop: (ev, ui) => {
- if (insideIframe) {
- window.top.removeEventListener('mouseup', this.onParentWindowMouseup, {once: true});
- }
- this._onSmoothDragStop(ev, ui, this.options.jQueryDraggableOptions.stop);
- }
- });
- this.$element.draggable(draggableOptions);
- }
- /**
- * @override
- */
- destroy() {
- this.$element.off('.smooth_scroll');
- this._stopSmoothScroll();
- }
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Starts the scroll process using the options.
- * The options will be updated dynamically when the handler _onSmoothDrag
- * will be called. The interval will be cleared when the handler
- * _onSmoothDragStop will be called.
- *
- * @private
- * @param {Object} ui The jQuery drag handler ui parameter.
- */
- _startSmoothScroll(ui) {
- this._stopSmoothScroll();
-
- this._initialScrollBehavior = this.$scrollTarget[0].style.scrollBehavior;
- this.$scrollTarget[0].style.scrollBehavior = 'auto';
-
- this.autoScrollHandler = setInterval(
- () => {
- // Prevents Delta's from being different from 0 when scroll should not occur (except when
- // helper is dragged outside of this.$scrollTarget's visible area as it increases
- // this.$scrollTarget's scrollHeight).
- // Also, this code prevents the helper from being incorrectly repositioned when target is
- // a child of this.$scrollTarget.
- this.verticalDelta = Math.min(
- // Ensures scrolling stops when dragging bellow this.$scrollTarget bottom.
- Math.max(
- 0,
- this.$scrollTarget.get(0).scrollHeight
- - (this.$scrollTarget.scrollTop() + this.$scrollTarget.innerHeight())
- ),
- // Ensures scrolling stops when dragging above this.$scrollTarget top.
- Math.max(
- this.verticalDelta,
- -this.$scrollTarget.scrollTop()
- )
- );
- this.horizontalDelta = Math.min(
- //Ensures scrolling stops when dragging left to this.$scrollTarget.
- Math.max(
- 0,
- this.$scrollTarget.get(0).scrollWidth
- - (this.$scrollTarget.scrollLeft() + this.$scrollTarget.innerWidth())
- ),
- //Ensures scrolling stops when dragging right to this.$scrollTarget.
- Math.max(
- this.horizontalDelta,
- -this.$scrollTarget.scrollLeft()
- )
- );
-
- // Keep helper at right position while scrolling when helper is a child of this.$scrollTarget.
- if (this.scrollTargetIsParent) {
- const offset = ui.helper.offset();
- ui.helper.offset({
- top: offset.top + this.verticalDelta,
- left: offset.left + this.horizontalDelta
- });
- }
- this.$scrollTarget.scrollTop(
- this.$scrollTarget.scrollTop() +
- this.verticalDelta
- );
- if (!this.options.disableHorizontalScroll) {
- this.$scrollTarget.scrollLeft(
- this.$scrollTarget.scrollLeft() +
- this.horizontalDelta
- );
- }
- },
- this.options.scrollTimerInterval
- );
- }
- /**
- * Stops the scroll process if any is running.
- *
- * @private
- */
- _stopSmoothScroll() {
- clearInterval(this.autoScrollHandler);
-
- this.$scrollTarget[0].style.scrollBehavior = this._initialScrollBehavior || '';
- }
- /**
- * Updates the options depending on the offset position of the draggable
- * helper. In the same time options are used by an interval to trigger
- * scroll behaviour.
- * @see {@link _startSmoothScroll} for interval implementation details.
- *
- * @private
- * @param {Object} ui The jQuery drag handler ui parameter.
- */
- _updatePositionOptions(ui) {
- const draggableHelperOffset = ui.offset;
- const scrollTargetOffset = this.$scrollTarget.offset();
- let visibleOffset = {
- top: draggableHelperOffset.top
- - scrollTargetOffset.top
- + this.options.jQueryDraggableOptions.cursorAt.top
- - this.options.offsetElementsManager.top(),
- right: scrollTargetOffset.left + this.$scrollTarget.outerWidth()
- - draggableHelperOffset.left
- - this.options.jQueryDraggableOptions.cursorAt.left
- - this.options.offsetElementsManager.right(),
- bottom: scrollTargetOffset.top + this.$scrollTarget.outerHeight()
- - draggableHelperOffset.top
- - this.options.jQueryDraggableOptions.cursorAt.top
- - this.options.offsetElementsManager.bottom(),
- left: draggableHelperOffset.left
- - scrollTargetOffset.left
- + this.options.jQueryDraggableOptions.cursorAt.left
- - this.options.offsetElementsManager.left(),
- };
-
- if (this.iframeOffset) {
- const { x, y } = this.iframeOffset;
- visibleOffset.left -= x;
- visibleOffset.top -= y;
- visibleOffset.bottom += y;
- visibleOffset.right += x;
- }
-
- // If this.$scrollTarget is the html tag, we need to take the scroll
- // position into account as offsets positions are calculated relative to
- // the document (thus ). Except in the case where what is being
- // dragged is out of an iframe and that the scrolling is being done
- // inside that iframe.
- if (this.scrollTargetIsDocument && !this.iframeOffset) {
- const scrollTargetScrollTop = this.$scrollTarget.scrollTop();
- const scrollTargetScrollLeft = this.$scrollTarget.scrollLeft();
- visibleOffset.top -= scrollTargetScrollTop;
- visibleOffset.right += scrollTargetScrollLeft;
- visibleOffset.bottom += scrollTargetScrollTop;
- visibleOffset.left -= scrollTargetScrollLeft;
- }
-
- const scrollDecelerator = {
- vertical: 0,
- horizontal: 0,
- };
-
- const scrollStepDirection = {
- vertical: this.scrollStepDirectionEnum.down,
- horizontal: this.scrollStepDirectionEnum.right,
- };
-
- // Prevent scroll if outside of scroll boundaries
- if ((!this.options.scrollBoundaries.top && visibleOffset.top < 0) ||
- (!this.options.scrollBoundaries.right && visibleOffset.right < 0) ||
- (!this.options.scrollBoundaries.bottom && visibleOffset.bottom < 0) ||
- (!this.options.scrollBoundaries.left && visibleOffset.left < 0)) {
- scrollDecelerator.horizontal = 1;
- scrollDecelerator.vertical = 1;
- } else {
- // Manage vertical scroll
- if (visibleOffset.bottom <= this.options.scrollOffsetThreshold) {
- scrollDecelerator.vertical = Math.max(0, visibleOffset.bottom)
- / this.options.scrollOffsetThreshold;
- } else if (visibleOffset.top <= this.options.scrollOffsetThreshold) {
- scrollDecelerator.vertical = Math.max(0, visibleOffset.top)
- / this.options.scrollOffsetThreshold;
- scrollStepDirection.vertical = this.scrollStepDirectionEnum.up;
- } else {
- scrollDecelerator.vertical = 1;
- }
-
- // Manage horizontal scroll
- if (visibleOffset.right <= this.options.scrollOffsetThreshold) {
- scrollDecelerator.horizontal = Math.max(0, visibleOffset.right)
- / this.options.scrollOffsetThreshold;
- } else if (visibleOffset.left <= this.options.scrollOffsetThreshold) {
- scrollDecelerator.horizontal = Math.max(0, visibleOffset.left)
- / this.options.scrollOffsetThreshold;
- scrollStepDirection.horizontal = this.scrollStepDirectionEnum.left;
- } else {
- scrollDecelerator.horizontal = 1;
- }
- }
-
- this.verticalDelta = Math.ceil(scrollStepDirection.vertical *
- this.options.scrollStep *
- (1 - Math.sqrt(scrollDecelerator.vertical)));
- this.horizontalDelta = Math.ceil(scrollStepDirection.horizontal *
- this.options.scrollStep *
- (1 - Math.sqrt(scrollDecelerator.horizontal)));
- }
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * Called when mouse button is down on this.$element.
- * Updates the mouse cursor position variable.
- *
- * @private
- * @param {Object} ev The jQuery mousedown handler event parameter.
- */
- _onElementMouseDown(ev) {
- const elementOffset = $(ev.target).offset();
- this.options.jQueryDraggableOptions.cursorAt = {
- top: ev.pageY - elementOffset.top,
- left: ev.pageX - elementOffset.left,
- };
- }
- /**
- * Called when dragging the element.
- * Updates the position options and call the provided callback if any.
- *
- * @private
- * @param {Object} ev The jQuery drag handler event parameter.
- * @param {Object} ui The jQuery drag handler ui parameter.
- * @param {Function} onDragCallback The jQuery drag callback.
- */
- _onSmoothDrag(ev, ui, onDragCallback) {
- this._updatePositionOptions(ui);
- if (typeof onDragCallback === 'function') {
- onDragCallback.call(ui.helper, ev, ui);
- }
- }
- /**
- * Called when starting to drag the element.
- * Updates the position params, starts smooth scrolling process and call the
- * provided callback if any.
- *
- * @private
- * @param {Object} ev The jQuery drag handler event parameter.
- * @param {Object} ui The jQuery drag handler ui parameter.
- * @param {Function} onDragStartCallBack The jQuery drag callback.
- */
- _onSmoothDragStart(ev, ui, onDragStartCallBack) {
- this.scrollTargetIsDocument = this.$scrollTarget.is('html');
- this.iframeOffset = false;
- this.scrollTargetIsParent = this.$scrollTarget.get(0).contains(this.$element.get(0));
- this._updatePositionOptions(ui);
- this._startSmoothScroll(ui);
- if (typeof onDragStartCallBack === 'function') {
- onDragStartCallBack.call(ui.helper, ev, ui);
- }
- }
- /**
- * Called when stopping to drag the element.
- * Stops the smooth scrolling process and call the provided callback if any.
- *
- * @private
- * @param {Object} ev The jQuery drag handler event parameter.
- * @param {Object} ui The jQuery drag handler ui parameter.
- * @param {Function} onDragEndCallBack The jQuery drag callback.
- */
- _onSmoothDragStop(ev, ui, onDragEndCallBack) {
- this._stopSmoothScroll();
- if (typeof onDragEndCallBack === 'function') {
- onDragEndCallBack.call(ui.helper, ev, ui);
- }
- }
- /**
- * Called when the mouse is released outside the page iframe (e.g. the
- * editor panel in Website). This is only useful in Chrome, where the 'stop'
- * event of jQuery Draggable does not trigger a 'mouseup' event outside of
- * the "preview" page iframe.
- *
- * @private
- */
- _onParentWindowMouseup() {
- this.targetWindow.document.dispatchEvent(new Event('mouseup'));
- }
-}
-
diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js
index b787d32cb04..6d4742608bd 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -6,8 +6,8 @@ import core from "@web/legacy/js/services/core";
import Dialog from "@web/legacy/js/core/dialog";
import dom from "@web/legacy/js/core/dom";
import Widget from "@web/legacy/js/core/widget";
+import { useDragAndDrop } from "@web_editor/js/editor/drag_and_drop";
import options from "@web_editor/js/editor/snippets.options";
-import { SmoothScrollOnDrag } from "@web_editor/js/editor/smooth_scroll_on_drag";
import weUtils from "@web_editor/js/common/utils";
import * as gridUtils from "@web_editor/js/common/grid_layout_utils";
import { escape } from "@web/core/utils/strings";
@@ -113,10 +113,11 @@ var SnippetEditor = Widget.extend({
// Initialize move/clone/remove buttons
if (this.isTargetMovable) {
this.dropped = false;
- this.draggableComponent = this._initSmoothScrollOnDrag(".o_move_handle", this.$el);
+ this.draggableComponent = this._initDragAndDrop(".o_move_handle", ".oe_overlay", this.el);
if (!this.$target[0].matches("section")) {
// Allow the user to drag the image itself to move the target.
- this.draggableComponentImgs = this._initSmoothScrollOnDrag("img", this.$target);
+ this.$target[0].classList.add("o_draggable");
+ this.draggableComponentImgs = this._initDragAndDrop("img", ".o_draggable", this.$target[0]);
}
} else {
this.$('.o_overlay_move_options').addClass('d-none');
@@ -821,20 +822,26 @@ var SnippetEditor = Widget.extend({
});
},
/**
- * Creates a SmoothScrollOnDrag object.
+ * Initialize drag and drop handlers.
*
* @private
* @param {String} handle css selector for grabble element
- * @param {jQuery} $element the smooth scroll on drag has to be set on
- * @returns {SmoothScrollOnDrag}
+ * @param {String} elementsSelector selector for elements that will be dragged.
+ * @param {HTMLElement} element element to listen for drag events.
+ * @returns {Object} the drag state.
*/
- _initSmoothScrollOnDrag(handle, $element) {
- const jQueryDraggableOptions = {
- cursorAt: {
- left: 10,
- top: 10
- },
+ _initDragAndDrop(handle, elementsSelector, element) {
+ const modalAncestorEl = this.$target[0].closest('.modal');
+ const $scrollable = modalAncestorEl && $(modalAncestorEl)
+ || (this.options.wysiwyg.snippetsMenu && this.options.wysiwyg.snippetsMenu.$scrollable)
+ || (this.$scrollingElement.length && this.$scrollingElement)
+ || $().getScrollingElement(this.ownerDocument);
+ const dragAndDropOptions = {
+ ref: { el: element },
+ elements: elementsSelector,
handle: handle,
+ scrollingElement: $scrollable[0],
+ enable: () => !!this.$el.find('.o_move_handle:visible').length || this.dragStarted,
helper: () => {
const cloneEl = this.$el[0].cloneNode(true);
cloneEl.style.width = "24px";
@@ -842,18 +849,17 @@ var SnippetEditor = Widget.extend({
cloneEl.style.border = "0";
this.$el[0].ownerDocument.body.appendChild(cloneEl);
cloneEl.classList.remove("d-none");
- return $(cloneEl);
+ cloneEl.classList.remove("o_dragged");
+ return cloneEl;
},
- start: (ev, ui) => {
- if (!this.$el.find('.o_move_handle:visible').length) {
- return false;
- }
+ onDragStart: (args) => {
this.dragStarted = true;
- this.mousePositionYOnElement = ev.originalEvent.clientY - ui.offset.top;
- this.mousePositionXOnElement = ev.originalEvent.clientX - ui.offset.left;
- this._onDragAndDropStart();
+ const targetRect = this.$target[0].getBoundingClientRect();
+ this.mousePositionYOnElement = args.y - targetRect.y;
+ this.mousePositionXOnElement = args.x - targetRect.x;
+ this._onDragAndDropStart(args);
},
- stop: (...args) => {
+ onDragEnd: (...args) => {
if (!this.dragStarted) {
return false;
}
@@ -866,16 +872,13 @@ var SnippetEditor = Widget.extend({
this._onDragAndDropStop(...args);
}, 0);
},
- refreshPositions: true, // So the dropzone expands when its size increases.
+ onDrag: this._onDragMove.bind(this),
+ dropzoneOver: this.dropzoneOver.bind(this),
+ dropzoneOut: this.dropzoneOut.bind(this),
+ dropzones: () => this.$dropZones?.toArray() || [],
};
- const scrollOptions =
- this.options.getScrollOptions({ jQueryDraggableOptions: jQueryDraggableOptions });
- const modalAncestorEl = this.$target[0].closest('.modal');
- const $scrollable = modalAncestorEl && $(modalAncestorEl)
- || (this.options.wysiwyg.snippetsMenu && this.options.wysiwyg.snippetsMenu.$scrollable)
- || (this.$scrollingElement.length && this.$scrollingElement)
- || $().getScrollingElement(this.ownerDocument);
- return new SmoothScrollOnDrag($element, $scrollable, scrollOptions);
+ const finalOptions = this.options.getDragAndDropOptions(dragAndDropOptions);
+ return useDragAndDrop(finalOptions);
},
/**
* @private
@@ -935,7 +938,7 @@ var SnippetEditor = Widget.extend({
const rowEl = previousDropzoneEl.parentNode;
if (rowEl.classList.contains('o_grid_mode')) {
- self.$body[0].removeEventListener('mousemove', self.onDragMove, false);
+ self.dragState.gridMode = false;
const fromGridToGrid = currentDropzoneEl.classList.contains('oe_grid_zone');
if (fromGridToGrid) {
// If we went from a grid dropzone to an other grid one.
@@ -989,7 +992,15 @@ var SnippetEditor = Widget.extend({
*
* @private
*/
- _onDragAndDropStart: function () {
+ _onDragAndDropStart({ helper, addStyle }) {
+ // If the helper is inside the iframe, we want pointer events on the
+ // frame element so that they reach the window and properly apply
+ // the cursor.
+ const frameElement = helper.ownerDocument.defaultView.frameElement;
+ if (frameElement) {
+ addStyle(frameElement, { pointerEvents: "auto" });
+ }
+
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
this.trigger_up('drag_and_drop_start');
this.options.wysiwyg.odooEditor.automaticStepUnactive();
@@ -1018,8 +1029,6 @@ var SnippetEditor = Widget.extend({
const allowGridMode = hasGridLayoutOption || rowEl.classList.contains('o_grid_mode');
// Number of grid columns and rows in the grid item (BS column).
- let columnColCount;
- let columnRowCount;
if (rowEl.classList.contains('row') && this.options.isWebsite) {
if (allowGridMode) {
// Toggle grid mode if it is not already on.
@@ -1037,10 +1046,8 @@ var SnippetEditor = Widget.extend({
const rowStart = self.$target[0].style.gridRowStart;
const rowEnd = self.$target[0].style.gridRowEnd;
- columnColCount = columnEnd - columnStart;
- columnRowCount = rowEnd - rowStart;
- this.dragState.columnColCount = columnColCount;
- this.dragState.columnRowCount = columnRowCount;
+ this.dragState.columnColCount = columnEnd - columnStart;
+ this.dragState.columnRowCount = rowEnd - rowStart;
// Storing the current grid and grid area to use them for the
// history.
@@ -1173,156 +1180,146 @@ var SnippetEditor = Widget.extend({
} else if (canBeSanitizedUnless === 'form') {
this.$dropZones = this.$dropZones.not('[data-oe-sanitize][data-oe-sanitize!="allow_form"] .oe_drop_zone');
}
- this.$dropZones.droppable({
- over: function () {
- if (self.dropped) {
- self.$target.detach();
- }
+ },
+ dropzoneOver({ dropzone }) {
+ if (this.dropped) {
+ this.$target.detach();
+ }
- // Prevent a column to be trapped in an upper grid dropzone at
- // the start of the drag.
- if (self.dragState.isColumn && self.dragState.overFirstDropzone) {
- self.dragState.overFirstDropzone = false;
+ // Prevent a column to be trapped in an upper grid dropzone at
+ // the start of the drag.
+ if (this.dragState.isColumn && this.dragState.overFirstDropzone) {
+ this.dragState.overFirstDropzone = false;
- // The column is considered as glued to the dropzone if the
- // dropzone is above and if the space between them is less
- // than 25px (the move handle height is 22px so 25 is a
- // safety margin).
- const columnTop = self.dragState.columnTop;
- const dropzoneBottom = this.getBoundingClientRect().bottom;
- const areDropzonesGlued = (columnTop >= dropzoneBottom) && (columnTop - dropzoneBottom < 25);
+ // The column is considered as glued to the dropzone if the
+ // dropzone is above and if the space between them is less
+ // than 25px (the move handle height is 22px so 25 is a
+ // safety margin).
+ const columnTop = this.dragState.columnTop;
+ const dropzoneBottom = dropzone.el.getBoundingClientRect().bottom;
+ const areDropzonesGlued = (columnTop >= dropzoneBottom) && (columnTop - dropzoneBottom < 25);
- if (areDropzonesGlued && this.classList.contains('oe_grid_zone')) {
- return;
- }
- }
+ if (areDropzonesGlued && dropzone.el.classList.contains('oe_grid_zone')) {
+ return;
+ }
+ }
- self.dropped = true;
- const $dropzone = $(this).first().after(self.$target);
- $dropzone.addClass('invisible');
+ this.dropped = true;
+ const $dropzone = $(dropzone.el).first().after(this.$target);
+ $dropzone.addClass('invisible');
- // Checking if the "out" event happened before this "over": if
- // `self.dragState.currentDropzoneEl` exists, "out" didn't
- // happen because it deletes it. We are therefore in the case
- // of an "over" after an "over" and we need to escape the
- // previous dropzone first.
- if (self.dragState.currentDropzoneEl) {
- self._outPreviousDropzone.apply(self.dragState.currentDropzoneEl, [self, $dropzone[0]]);
- }
- self.dragState.currentDropzoneEl = $dropzone[0];
+ // Checking if the "out" event happened before dropzone.el "over": if
+ // `this.dragState.currentDropzoneEl` exists, "out" didn't
+ // happen because it deletes it. We are therefore in the case
+ // of an "over" after an "over" and we need to escape the
+ // previous dropzone first.
+ if (this.dragState.currentDropzoneEl) {
+ this._outPreviousDropzone.apply(this.dragState.currentDropzoneEl, [this, $dropzone[0]]);
+ }
+ this.dragState.currentDropzoneEl = $dropzone[0];
- if ($dropzone[0].classList.contains('oe_grid_zone')) {
- // Case where the column we are dragging is over a grid
- // dropzone.
- const rowEl = $dropzone[0].parentNode;
+ if ($dropzone[0].classList.contains('oe_grid_zone')) {
+ // Case where the column we are dragging is over a grid
+ // dropzone.
+ const rowEl = $dropzone[0].parentNode;
- // If the column doesn't come from a grid mode snippet.
- if (!self.$target[0].classList.contains('o_grid_item')) {
- // Converting the column to grid.
- self.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
- const spans = gridUtils._convertColumnToGrid(rowEl, self.$target[0], self.dragState.columnWidth, self.dragState.columnHeight);
- self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
- columnColCount = spans.columnColCount;
- columnRowCount = spans.columnRowCount;
+ // If the column doesn't come from a grid mode snippet.
+ if (!this.$target[0].classList.contains('o_grid_item')) {
+ // Converting the column to grid.
+ this.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
+ const spans = gridUtils._convertColumnToGrid(rowEl, this.$target[0], this.dragState.columnWidth, this.dragState.columnHeight);
+ this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
+ this.dragState.columnColCount = spans.columnColCount;
+ this.dragState.columnRowCount = spans.columnRowCount;
- // Storing the column spans.
- self.dragState.columnColCount = columnColCount;
- self.dragState.columnRowCount = columnRowCount;
- }
+ // Storing the column spans.
+ }
- // Creating the drag helper.
- const dragHelperEl = document.createElement('div');
- dragHelperEl.classList.add('o_we_drag_helper');
- dragHelperEl.style.gridArea = `1 / 1 / ${1 + columnRowCount} / ${1 + columnColCount}`;
- rowEl.append(dragHelperEl);
+ const columnColCount = this.dragState.columnColCount;
+ const columnRowCount = this.dragState.columnRowCount;
+ // Creating the drag helper.
+ const dragHelperEl = document.createElement('div');
+ dragHelperEl.classList.add('o_we_drag_helper');
+ dragHelperEl.style.gridArea = `1 / 1 / ${1 + columnRowCount} / ${1 + columnColCount}`;
+ rowEl.append(dragHelperEl);
- // Creating the background grid and updating the dropzone
- // (in the case where the column over the dropzone is
- // bigger than the grid).
- const backgroundGridEl = gridUtils._addBackgroundGrid(rowEl, columnRowCount);
- const rowCount = Math.max(rowEl.dataset.rowCount, columnRowCount);
- $dropzone[0].style.gridRowEnd = rowCount + 1;
+ // Creating the background grid and updating the dropzone
+ // (in the case where the column over the dropzone is
+ // bigger than the grid).
+ const backgroundGridEl = gridUtils._addBackgroundGrid(rowEl, columnRowCount);
+ const rowCount = Math.max(rowEl.dataset.rowCount, columnRowCount);
+ $dropzone[0].style.gridRowEnd = rowCount + 1;
- self.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
- // Setting the moving grid item, the background grid and
- // the drag helper z-indexes. The grid item z-index is set
- // to its original one if we are in its starting grid, or
- // to the maximum z-index of the grid otherwise.
- if (rowEl === self.dragState.startingGrid) {
- self.$target[0].style.zIndex = self.dragState.startingZIndex;
- } else {
- gridUtils._setElementToMaxZindex(self.$target[0], rowEl);
- }
- gridUtils._setElementToMaxZindex(backgroundGridEl, rowEl);
- gridUtils._setElementToMaxZindex(dragHelperEl, rowEl);
+ this.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
+ // Setting the moving grid item, the background grid and
+ // the drag helper z-indexes. The grid item z-index is set
+ // to its original one if we are in its starting grid, or
+ // to the maximum z-index of the grid otherwise.
+ if (rowEl === this.dragState.startingGrid) {
+ this.$target[0].style.zIndex = this.dragState.startingZIndex;
+ } else {
+ gridUtils._setElementToMaxZindex(this.$target[0], rowEl);
+ }
+ gridUtils._setElementToMaxZindex(backgroundGridEl, rowEl);
+ gridUtils._setElementToMaxZindex(dragHelperEl, rowEl);
- // Setting the column height and width to keep its size
- // when the grid-area is removed (as it prevents it from
- // moving with the mouse).
- const gridProp = gridUtils._getGridProperties(rowEl);
- const columnHeight = columnRowCount * (gridProp.rowSize + gridProp.rowGap) - gridProp.rowGap;
- const columnWidth = columnColCount * (gridProp.columnSize + gridProp.columnGap) - gridProp.columnGap;
- self.$target[0].style.height = columnHeight + 'px';
- self.$target[0].style.width = columnWidth + 'px';
- self.$target[0].style.position = 'absolute';
- self.$target[0].style.removeProperty('grid-area');
- rowEl.style.position = 'relative';
- self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
+ // Setting the column height and width to keep its size
+ // when the grid-area is removed (as it prevents it from
+ // moving with the mouse).
+ const gridProp = gridUtils._getGridProperties(rowEl);
+ const columnHeight = columnRowCount * (gridProp.rowSize + gridProp.rowGap) - gridProp.rowGap;
+ const columnWidth = columnColCount * (gridProp.columnSize + gridProp.columnGap) - gridProp.columnGap;
+ this.$target[0].style.height = columnHeight + 'px';
+ this.$target[0].style.width = columnWidth + 'px';
+ this.$target[0].style.position = 'absolute';
+ this.$target[0].style.removeProperty('grid-area');
+ rowEl.style.position = 'relative';
+ this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
- // Storing useful information and adding an event listener.
- self.dragState.startingHeight = rowEl.clientHeight;
- self.dragState.currentHeight = rowEl.clientHeight;
- self.dragState.dragHelperEl = dragHelperEl;
- self.dragState.backgroundGridEl = backgroundGridEl;
- self.onDragMove = self._onDragMove.bind(self);
- self.$body[0].addEventListener('mousemove', self.onDragMove, false);
- }
- },
- out: function () {
- const dropzoneEl = this;
- const rowEl = dropzoneEl.parentNode;
+ // Storing useful information and adding an event listener.
+ this.dragState.startingHeight = rowEl.clientHeight;
+ this.dragState.currentHeight = rowEl.clientHeight;
+ this.dragState.dragHelperEl = dragHelperEl;
+ this.dragState.backgroundGridEl = backgroundGridEl;
+ this.dragState.gridMode = true;
+ }
+ },
+ dropzoneOut({ dropzone }) {
+ const rowEl = dropzone.el.parentNode;
- // Checking if the "out" event happens right after the "over"
- // of the same dropzone. If it is not the case, we don't do
- // anything since the previous dropzone was already escaped (at
- // the start of the over).
- const sameDropzoneAsCurrent = self.dragState.currentDropzoneEl === dropzoneEl;
+ // Checking if the "out" event happens right after the "over"
+ // of the same dropzone. If it is not the case, we don't do
+ // anything since the previous dropzone was already escaped (at
+ // the start of the over).
+ const sameDropzoneAsCurrent = this.dragState.currentDropzoneEl === dropzone.el;
- if (sameDropzoneAsCurrent) {
- if (rowEl.classList.contains('o_grid_mode')) {
- // Removing the listener + cleaning.
- self.$body[0].removeEventListener('mousemove', self.onDragMove, false);
- gridUtils._gridCleanUp(rowEl, self.$target[0]);
- self.$target[0].style.removeProperty('z-index');
+ if (sameDropzoneAsCurrent) {
+ if (rowEl.classList.contains('o_grid_mode')) {
+ // Removing the listener + cleaning.
+ this.dragState.gridMode = false;
+ gridUtils._gridCleanUp(rowEl, this.$target[0]);
+ this.$target[0].style.removeProperty('z-index');
- // Removing the drag helper and the background grid and
- // resizing the grid and the dropzone.
- self.dragState.dragHelperEl.remove();
- self.dragState.backgroundGridEl.remove();
- self.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
- gridUtils._resizeGrid(rowEl);
- self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
- const rowCount = parseInt(rowEl.dataset.rowCount);
- dropzoneEl.style.gridRowEnd = Math.max(rowCount + 1, 1);
- }
+ // Removing the drag helper and the background grid and
+ // resizing the grid and the dropzone.
+ this.dragState.dragHelperEl.remove();
+ this.dragState.backgroundGridEl.remove();
+ this.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
+ gridUtils._resizeGrid(rowEl);
+ this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
+ const rowCount = parseInt(rowEl.dataset.rowCount);
+ dropzone.el.style.gridRowEnd = Math.max(rowCount + 1, 1);
+ }
- var prev = self.$target.prev();
- if (this === prev[0]) {
- self.dropped = false;
- self.$target.detach();
- $(this).removeClass('invisible');
- }
+ var prev = this.$target.prev();
+ if (dropzone.el === prev[0]) {
+ this.dropped = false;
+ this.$target.detach();
+ $(dropzone.el).removeClass('invisible');
+ }
- delete self.dragState.currentDropzoneEl;
- }
- },
- });
-
- // Trigger a scroll on the draggable element so that jQuery updates
- // the position of the drop zones.
- self.draggableComponent.$scrollTarget.on('scroll.scrolling_element', function () {
- self.$el.trigger('scroll');
- });
+ delete this.dragState.currentDropzoneEl;
+ }
},
/**
* Called when the snippet is dropped after being dragged thanks to the
@@ -1332,7 +1329,7 @@ var SnippetEditor = Widget.extend({
* @param {Event} ev
* @param {Object} ui
*/
- _onDragAndDropStop: function (ev, ui) {
+ _onDragAndDropStop({ x, y }) {
this.options.wysiwyg.odooEditor.automaticStepActive();
this.options.wysiwyg.odooEditor.automaticStepSkipStack();
this.options.wysiwyg.odooEditor.unbreakableStepUnactive();
@@ -1341,8 +1338,8 @@ var SnippetEditor = Widget.extend({
if (rowEl && rowEl.classList.contains('o_grid_mode')) {
// Case when dropping the column in a grid.
- // Removing the event listener.
- this.$body[0].removeEventListener('mousemove', this.onDragMove, false);
+ // Disable dragMove handler
+ this.dragState.gridMode = false;
// Defining the column grid area with its position.
const gridProp = gridUtils._getGridProperties(rowEl);
@@ -1374,7 +1371,7 @@ var SnippetEditor = Widget.extend({
// TODO lot of this is duplicated code of the d&d feature of snippets
if (!this.dropped) {
- let $el = $(closest(this.$body[0].querySelectorAll('.oe_drop_zone'), {x: ui.position.left, y: ui.position.top}));
+ let $el = $(closest(this.$body[0].querySelectorAll('.oe_drop_zone'), {x, y}));
// Some drop zones might have been disabled.
$el = $el.filter(this.$dropZones);
if ($el.length) {
@@ -1429,7 +1426,6 @@ var SnippetEditor = Widget.extend({
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
}
- this.$dropZones.droppable('destroy');
this.$editable.find('.oe_drop_zone').remove();
var prev = this.$target.first()[0].previousSibling;
@@ -1470,10 +1466,6 @@ var SnippetEditor = Widget.extend({
this.trigger_up('drag_and_drop_stop', {
$snippet: this.$target,
});
- this.draggableComponent.$scrollTarget.off('scroll.scrolling_element');
- if (this.draggableComponentImgs) {
- this.draggableComponentImgs.$scrollTarget.off('scroll.scrolling_element');
- }
const samePositionAsStart = this.$target[0].classList.contains('o_grid_item')
? (this.$target[0].parentNode === this.dragState.startingGrid
&& this.$target[0].style.gridArea === this.dragState.prevGridArea)
@@ -1650,7 +1642,10 @@ var SnippetEditor = Widget.extend({
* @private
* @param {Event} ev
*/
- _onDragMove(ev) {
+ _onDragMove({ x, y }) {
+ if (!this.dragState.gridMode || !this.dragState.currentDropzoneEl) {
+ return;
+ }
const columnEl = this.$target[0];
const rowEl = columnEl.parentNode;
@@ -1664,9 +1659,9 @@ var SnippetEditor = Widget.extend({
const columnWidth = columnEl.clientWidth + 2 * borderWidth;
// Placing the column where the mouse is.
- let top = ev.pageY - rowElTop - this.mousePositionYOnElement;
+ let top = y - rowElTop - this.mousePositionYOnElement;
const bottom = top + columnHeight;
- let left = ev.pageX - rowElLeft - this.mousePositionXOnElement;
+ let left = x - rowElLeft - this.mousePositionXOnElement;
// Horizontal & vertical overflow.
left = clamp(left, 0, rowEl.clientWidth - columnWidth);
@@ -1912,7 +1907,7 @@ var SnippetsMenu = Widget.extend({
id: 'oe_manipulators',
});
this.$body.prepend(this.$snippetEditorArea);
- this.options.getScrollOptions = this._getScrollOptions.bind(this);
+ this.options.getDragAndDropOptions = this._getDragAndDropOptions.bind(this);
// Add tooltips on we-title elements whose text overflows and on all
// elements with available tooltip text. Note that the tooltips of the
@@ -3068,7 +3063,7 @@ var SnippetsMenu = Widget.extend({
this.$el.html($html);
this.$el.append(this.customizePanel);
this.$el.append(this.invisibleDOMPanelEl);
- this._makeSnippetDraggable(this.$snippets);
+ this._makeSnippetDraggable();
this._disableUndroppableSnippets();
this.$el.addClass('o_loaded');
@@ -3212,18 +3207,14 @@ var SnippetsMenu = Widget.extend({
* @param {Object} [options={}]
* @returns {Object}
*/
- _getScrollOptions(options = {}) {
+ _getDragAndDropOptions(options = {}) {
+ let iframeWindow = false;
+ if (this.$body[0].ownerDocument.defaultView !== window) {
+ iframeWindow = this.$body[0].ownerDocument.defaultView;
+ }
return Object.assign({}, options, {
- scrollBoundaries: Object.assign({
- right: false,
- }, options.scrollBoundaries),
- jQueryDraggableOptions: Object.assign({
- appendTo: this.$body,
- cursor: 'move',
- greedy: true,
- scroll: false,
- }, options.jQueryDraggableOptions),
- disableHorizontalScroll: true,
+ iframeWindow,
+ cursor: "move",
});
},
/**
@@ -3285,10 +3276,11 @@ var SnippetsMenu = Widget.extend({
* Make given snippets be draggable/droppable thanks to their thumbnail.
*
* @private
- * @param {jQuery} $snippets
*/
- _makeSnippetDraggable: function ($snippets) {
- var self = this;
+ _makeSnippetDraggable() {
+ if (this.draggableComponent) {
+ this.draggableComponent.destroy();
+ }
var $toInsert, dropped, $snippet;
let $dropZones;
@@ -3298,178 +3290,179 @@ var SnippetsMenu = Widget.extend({
$scrollingElement = $(this.ownerDocument).find('.o_editable');
}
- const smoothScrollOptions = this._getScrollOptions({
- jQueryDraggableOptions: {
- handle: '.oe_snippet_thumbnail:not(.o_we_already_dragging)',
- cancel: '.oe_snippet.o_disabled',
- helper: function () {
- const dragSnip = this.cloneNode(true);
- dragSnip.querySelectorAll('.o_delete_btn, .o_rename_btn').forEach(
- el => el.remove()
- );
- self.$el[0].ownerDocument.body.append(dragSnip);
- return dragSnip;
- },
- start: function () {
- self._hideSnippetTooltips();
+ const dragAndDropOptions = this.options.getDragAndDropOptions({
+ el: this.$el[0],
+ elements: ".oe_snippet",
+ scrollingElement: $scrollingElement[0],
+ handle: '.oe_snippet_thumbnail:not(.o_we_already_dragging)',
+ cancel: '.oe_snippet.o_disabled',
+ dropzones: () => {
+ return $dropZones.toArray();
+ },
+ helper: ({ element, elementRect, helperOffset, x, y }) => {
+ const dragSnip = element.cloneNode(true);
+ dragSnip.querySelectorAll('.o_delete_btn, .o_rename_btn').forEach(
+ el => el.remove()
+ );
+ dragSnip.style.position = "fixed";
+ this.$el[0].ownerDocument.body.append(dragSnip);
+ // Prepare the offset of the helper to be at the position it was dragged from
+ helperOffset.x = x - elementRect.x;
+ helperOffset.y = y - elementRect.y;
+ return dragSnip;
+ },
+ onDragStart: ({ element }) => {
+ this._hideSnippetTooltips();
- const prom = new Promise(resolve => dragAndDropResolve = () => resolve());
- self._mutex.exec(() => prom);
+ const prom = new Promise(resolve => dragAndDropResolve = () => resolve());
+ this._mutex.exec(() => prom);
- const doc = self.options.wysiwyg.odooEditor.document;
- $(doc.body).addClass('oe_dropzone_active');
+ const doc = this.options.wysiwyg.odooEditor.document;
+ $(doc.body).addClass('oe_dropzone_active');
- self.options.wysiwyg.odooEditor.automaticStepUnactive();
+ this.options.wysiwyg.odooEditor.automaticStepUnactive();
- self.$el.find('.oe_snippet_thumbnail').addClass('o_we_already_dragging');
- self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
+ this.$el.find('.oe_snippet_thumbnail').addClass('o_we_already_dragging');
+ this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
- dropped = false;
- $snippet = $(this);
- var $baseBody = $snippet.find('.oe_snippet_body');
- var $selectorSiblings = $();
- var $selectorChildren = $();
- const selectorExcludeAncestor = [];
- var temp = self.templateOptions;
- for (var k in temp) {
- if ($baseBody.is(temp[k].base_selector) && !$baseBody.is(temp[k].base_exclude)) {
- if (temp[k]['drop-near']) {
- $selectorSiblings = $selectorSiblings.add(temp[k]['drop-near'].all());
- }
- if (temp[k]['drop-in']) {
- $selectorChildren = $selectorChildren.add(temp[k]['drop-in'].all());
- }
- if (temp[k]['drop-exclude-ancestor']) {
- selectorExcludeAncestor.push(temp[k]['drop-exclude-ancestor']);
- }
+ dropped = false;
+ $snippet = $(element);
+ var $baseBody = $snippet.find('.oe_snippet_body');
+ var $selectorSiblings = $();
+ var $selectorChildren = $();
+ const selectorExcludeAncestor = [];
+ var temp = this.templateOptions;
+ for (var k in temp) {
+ if ($baseBody.is(temp[k].base_selector) && !$baseBody.is(temp[k].base_exclude)) {
+ if (temp[k]['drop-near']) {
+ $selectorSiblings = $selectorSiblings.add(temp[k]['drop-near'].all());
+ }
+ if (temp[k]['drop-in']) {
+ $selectorChildren = $selectorChildren.add(temp[k]['drop-in'].all());
+ }
+ if (temp[k]['drop-exclude-ancestor']) {
+ selectorExcludeAncestor.push(temp[k]['drop-exclude-ancestor']);
}
}
+ }
- // Prevent dropping an element into another one.
- // (E.g. ToC inside another ToC)
- for (const excludedAncestorSelector of selectorExcludeAncestor) {
- $selectorSiblings = $selectorSiblings.filter((i, el) => !el.closest(excludedAncestorSelector));
- $selectorChildren = $selectorChildren.filter((i, el) => !el.closest(excludedAncestorSelector));
- }
+ // Prevent dropping an element into another one.
+ // (E.g. ToC inside another ToC)
+ for (const excludedAncestorSelector of selectorExcludeAncestor) {
+ $selectorSiblings = $selectorSiblings.filter((i, el) => !el.closest(excludedAncestorSelector));
+ $selectorChildren = $selectorChildren.filter((i, el) => !el.closest(excludedAncestorSelector));
+ }
- $toInsert = $baseBody.clone();
- // Color-customize dynamic SVGs in dropped snippets with current theme colors.
- [...$toInsert.find('img[src^="/web_editor/shape/"]')].forEach(dynamicSvg => {
- const colorCustomizedURL = new URL(dynamicSvg.getAttribute('src'), window.location.origin);
- colorCustomizedURL.searchParams.forEach((value, key) => {
- const match = key.match(/^c([1-5])$/);
- if (match) {
- colorCustomizedURL.searchParams.set(key, weUtils.getCSSVariableValue(`o-color-${match[1]}`));
- }
- });
- dynamicSvg.src = colorCustomizedURL.pathname + colorCustomizedURL.search;
+ $toInsert = $baseBody.clone();
+ // Color-customize dynamic SVGs in dropped snippets with current theme colors.
+ [...$toInsert.find('img[src^="/web_editor/shape/"]')].forEach(dynamicSvg => {
+ const colorCustomizedURL = new URL(dynamicSvg.getAttribute('src'), window.location.origin);
+ colorCustomizedURL.searchParams.forEach((value, key) => {
+ const match = key.match(/^c([1-5])$/);
+ if (match) {
+ colorCustomizedURL.searchParams.set(key, weUtils.getCSSVariableValue(`o-color-${match[1]}`));
+ }
});
+ dynamicSvg.src = colorCustomizedURL.pathname + colorCustomizedURL.search;
+ });
- if (!$selectorSiblings.length && !$selectorChildren.length) {
- console.warn($snippet.find('.oe_snippet_thumbnail_title').text() + " have not insert action: data-drop-near or data-drop-in");
- return;
- }
+ if (!$selectorSiblings.length && !$selectorChildren.length) {
+ console.warn($snippet.find('.oe_snippet_thumbnail_title').text() + " have not insert action: data-drop-near or data-drop-in");
+ return;
+ }
- const forbidSanitize = $snippet.data('oeForbidSanitize');
- const canBeSanitizedUnless = forbidSanitize === 'form' ? 'form' : !forbidSanitize;
- // Specific case for inline snippet (e.g. "s_badge")
+ const forbidSanitize = $snippet.data('oeForbidSanitize');
+ const canBeSanitizedUnless = forbidSanitize === 'form' ? 'form' : !forbidSanitize;
+ // Specific case for inline snippet (e.g. "s_badge")
$baseBody[0].classList.remove("oe_snippet_body");
const toInsertInline = window.getComputedStyle($baseBody[0]).display.includes('inline');
$baseBody[0].classList.add("oe_snippet_body");
- self._activateInsertionZones($selectorSiblings, $selectorChildren, canBeSanitizedUnless, toInsertInline);
- $dropZones = self.getEditableArea().find('.oe_drop_zone');
- if (forbidSanitize === 'form') {
- $dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize]:not([data-oe-sanitize="allow_form"]) .oe_drop_zone'));
- } else if (forbidSanitize) {
- $dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize] .oe_drop_zone'));
+ this._activateInsertionZones($selectorSiblings, $selectorChildren, canBeSanitizedUnless, toInsertInline);
+ $dropZones = this.getEditableArea().find('.oe_drop_zone');
+ if (forbidSanitize === 'form') {
+ $dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize]:not([data-oe-sanitize="allow_form"]) .oe_drop_zone'));
+ } else if (forbidSanitize) {
+ $dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize] .oe_drop_zone'));
+ }
+ // If a modal is open, the scroll target must be that modal
+ const $openModal = this.getEditableArea().find('.modal:visible');
+ if ($openModal.length) {
+ this.draggableComponent.update({ scrollingElement: $openModal[0]});
+ $scrollingElement = $openModal;
+ }
+ this.trigger_up('drop_zone_start');
+ },
+ dropzoneOver: ({ dropzone }) => {
+ if (dropped) {
+ $toInsert.detach();
+ $toInsert.addClass('oe_snippet_body');
+ [...$dropZones].forEach(dropzoneEl =>
+ dropzoneEl.classList.remove("invisible"));
+ }
+ dropped = true;
+ $(dropzone.el).first().after($toInsert).addClass('invisible');
+ $toInsert.removeClass('oe_snippet_body');
+ this.trigger_up('drop_zone_over');
+ },
+ dropzoneOut: ({ dropzone }) => {
+ var prev = $toInsert.prev();
+ if (dropzone.el === prev[0]) {
+ dropped = false;
+ $toInsert.detach();
+ $(dropzone.el).removeClass('invisible');
+ $toInsert.addClass('oe_snippet_body');
+ }
+ this.trigger_up('drop_zone_out');
+ },
+ onDragEnd: async ({ x, y, helper }) => {
+ const doc = this.options.wysiwyg.odooEditor.document;
+ $(doc.body).removeClass('oe_dropzone_active');
+ this.options.wysiwyg.odooEditor.automaticStepUnactive();
+ this.options.wysiwyg.odooEditor.automaticStepSkipStack();
+ $toInsert.removeClass('oe_snippet_body');
+ $scrollingElement.off('scroll.scrolling_element');
+ if (!dropped && y > 3 && x + helper.getBoundingClientRect().height < this.el.getBoundingClientRect().left) {
+ const point = { x, y };
+ let droppedOnNotNearest = touching(doc.body.querySelectorAll('.oe_structure_not_nearest'), point);
+ // If dropped outside of a dropzone with class oe_structure_not_nearest,
+ // move the snippet to the nearest dropzone without it
+ const selector = droppedOnNotNearest
+ ? '.oe_drop_zone'
+ : ':not(.oe_structure_not_nearest) > .oe_drop_zone';
+ let $el = $(closest(doc.body.querySelectorAll(selector), point));
+ // Some drop zones might have been disabled.
+ $el = $el.filter($dropZones);
+ if ($el.length) {
+ $el.after($toInsert);
+ dropped = true;
}
- $dropZones.droppable({
- over: function () {
- if (dropped) {
- $toInsert.detach();
- $toInsert.addClass('oe_snippet_body');
- [...$dropZones].forEach(dropzoneEl =>
- dropzoneEl.classList.remove("invisible"));
- }
- dropped = true;
- $(this).first().after($toInsert).addClass('invisible');
- $toInsert.removeClass('oe_snippet_body');
- self.trigger_up('drop_zone_over');
- },
- out: function () {
- var prev = $toInsert.prev();
- if (this === prev[0]) {
- dropped = false;
- $toInsert.detach();
- $(this).removeClass('invisible');
- $toInsert.addClass('oe_snippet_body');
- }
- self.trigger_up('drop_zone_out');
- },
- });
- // If a modal is open, the scroll target must be that modal
- const $openModal = self.getEditableArea().find('.modal:visible');
- if ($openModal.length) {
- self.draggableComponent.$scrollTarget = $openModal;
+ }
+
+ this.getEditableArea().find('.oe_drop_zone').remove();
+
+ let $toInsertParent;
+ let prev;
+ let next;
+ if (dropped) {
+ prev = $toInsert.first()[0].previousSibling;
+ next = $toInsert.last()[0].nextSibling;
+
+ $toInsertParent = $toInsert.parent();
+ $toInsert.detach();
+ }
+
+ this.options.wysiwyg.odooEditor.observerActive('dragAndDropCreateSnippet');
+
+ if (dropped) {
+ if (prev) {
+ $toInsert.insertAfter(prev);
+ } else if (next) {
+ $toInsert.insertBefore(next);
+ } else {
+ $toInsertParent.prepend($toInsert);
}
- // Trigger a scroll on the draggable element so that jQuery updates
- // the position of the drop zones.
- self.draggableComponent.$scrollTarget.on('scroll.scrolling_element', function () {
- self.$el.trigger('scroll');
- });
- self.trigger_up('drop_zone_start');
- },
- stop: async function (ev, ui) {
- const doc = self.options.wysiwyg.odooEditor.document;
- $(doc.body).removeClass('oe_dropzone_active');
- self.options.wysiwyg.odooEditor.automaticStepUnactive();
- self.options.wysiwyg.odooEditor.automaticStepSkipStack();
- $toInsert.removeClass('oe_snippet_body');
- self.draggableComponent.$scrollTarget.off('scroll.scrolling_element');
- if (!dropped && ui.position.top > 3 && ui.position.left + ui.helper.outerHeight() < self.el.getBoundingClientRect().left) {
- const point = {x: ui.position.left, y: ui.position.top};
- let droppedOnNotNearest = touching(doc.body.querySelectorAll('.oe_structure_not_nearest'), point);
- // If dropped outside of a dropzone with class oe_structure_not_nearest,
- // move the snippet to the nearest dropzone without it
- const selector = droppedOnNotNearest
- ? '.oe_drop_zone'
- : ':not(.oe_structure_not_nearest) > .oe_drop_zone';
- let $el = $(closest(doc.body.querySelectorAll(selector), point));
- // Some drop zones might have been disabled.
- $el = $el.filter($dropZones);
- if ($el.length) {
- $el.after($toInsert);
- dropped = true;
- }
- }
-
- $dropZones.droppable('destroy');
- self.getEditableArea().find('.oe_drop_zone').remove();
-
- let $toInsertParent;
- let prev;
- let next;
- if (dropped) {
- prev = $toInsert.first()[0].previousSibling;
- next = $toInsert.last()[0].nextSibling;
-
- $toInsertParent = $toInsert.parent();
- $toInsert.detach();
- }
-
- self.options.wysiwyg.odooEditor.observerActive('dragAndDropCreateSnippet');
-
- if (dropped) {
- if (prev) {
- $toInsert.insertAfter(prev);
- } else if (next) {
- $toInsert.insertBefore(next);
- } else {
- $toInsertParent.prepend($toInsert);
- }
-
- var $target = $toInsert;
+ var $target = $toInsert;
if ($target[0].classList.contains("o_snippet_drop_in_only")) {
// If it's a "drop in only" snippet, after dropping
@@ -3481,36 +3474,35 @@ var SnippetsMenu = Widget.extend({
delete $target[0].dataset.name;
}
- self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
- await self._scrollToSnippet($target, self.$scrollable);
- self.options.wysiwyg.odooEditor.observerActive('dragAndDropCreateSnippet');
+ this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
+ await this._scrollToSnippet($target, this.$scrollable);
+ this.options.wysiwyg.odooEditor.observerActive('dragAndDropCreateSnippet');
- browser.setTimeout(async () => {
- // Free the mutex now to allow following operations
- // (mutexed as well).
- dragAndDropResolve();
+ browser.setTimeout(async () => {
+ // Free the mutex now to allow following operations
+ // (mutexed as well).
+ dragAndDropResolve();
- await self.callPostSnippetDrop($target);
+ await this.callPostSnippetDrop($target);
- // Restore editor to its normal edition state, also
- // make sure the undroppable snippets are updated.
- self._disableUndroppableSnippets();
- self.options.wysiwyg.odooEditor.unbreakableStepUnactive();
- self.options.wysiwyg.odooEditor.historyStep();
- self.$el.find('.oe_snippet_thumbnail').removeClass('o_we_already_dragging');
- });
- } else {
- $toInsert.remove();
- if (dragAndDropResolve) {
- dragAndDropResolve();
- }
- self.$el.find('.oe_snippet_thumbnail').removeClass('o_we_already_dragging');
+ // Restore editor to its normal edition state, also
+ // make sure the undroppable snippets are updated.
+ this._disableUndroppableSnippets();
+ this.options.wysiwyg.odooEditor.unbreakableStepUnactive();
+ this.options.wysiwyg.odooEditor.historyStep();
+ this.$el.find('.oe_snippet_thumbnail').removeClass('o_we_already_dragging');
+ });
+ } else {
+ $toInsert.remove();
+ if (dragAndDropResolve) {
+ dragAndDropResolve();
}
- self.trigger_up('drop_zone_stop');
- },
+ this.$el.find('.oe_snippet_thumbnail').removeClass('o_we_already_dragging');
+ }
+ this.trigger_up('drop_zone_stop');
},
});
- this.draggableComponent = new SmoothScrollOnDrag($snippets, $scrollingElement, smoothScrollOptions);
+ this.draggableComponent = useDragAndDrop({ ref: { el: this.el }, ...dragAndDropOptions });
},
/**
* Adds the 'o_default_snippet_text' class on nodes which contain only
diff --git a/addons/web_editor/static/src/js/wysiwyg/MoveNodePlugin.js b/addons/web_editor/static/src/js/wysiwyg/MoveNodePlugin.js
index edb3b7fd35b..12fde5fa913 100644
--- a/addons/web_editor/static/src/js/wysiwyg/MoveNodePlugin.js
+++ b/addons/web_editor/static/src/js/wysiwyg/MoveNodePlugin.js
@@ -1,12 +1,39 @@
/** @odoo-module */
-
import {
ancestors,
closestElement,
resetOuids,
setSelection,
} from '@web_editor/js/editor/odoo-editor/src/OdooEditor';
-import { SmoothScrollOnDrag } from "@web_editor/js/editor/smooth_scroll_on_drag";
+import { useNativeDraggable } from "@web_editor/js/editor/drag_and_drop";
+
+const simpleDraggableHook = {
+ acceptedParams: {
+ helper: [Function],
+ },
+ edgeScrolling: { enable: true },
+ onComputeParams({ ctx, params }) {
+ ctx.helper = params.helper;
+ ctx.followCursor = false;
+ },
+ onDragStart({ ctx }) {
+ ctx.current.element = ctx.helper();
+ ctx.current.element.style.left = `${ctx.pointer.x + 10}px`;
+ ctx.current.element.style.top = `${ctx.pointer.y + 10}px`;
+ ctx.current.element.style.position = "fixed";
+ // makeDraggableHook disables pointer events, we want them in this case
+ document.body.classList.remove("pe-none");
+ return ctx.current;
+ },
+ onDrag({ ctx }) {
+ ctx.current.element.style.left = `${ctx.pointer.x}px`;
+ ctx.current.element.style.top = `${ctx.pointer.y}px`;
+ },
+ onDragEnd({ ctx }) {
+ ctx.current.element.remove();
+ return ctx.current;
+ },
+};
const WIDGET_CONTAINER_WIDTH = 25;
const WIDGET_MOVE_SIZE = 20;
@@ -231,28 +258,24 @@ export class MoveNodePlugin {
if (this._scrollableElement) {
this.smoothScrollOnDrag && this.smoothScrollOnDrag.destroy();
- this.smoothScrollOnDrag = new SmoothScrollOnDrag($(this._moveWidget), $(this._scrollableElement), {
- scrollBoundaries: {
- right: false,
- },
- jQueryDraggableOptions: {
- start: () => this._startDropzones(movableElement, containerRect),
- stop: () => this._stopDropzones(movableElement),
- helper: () => {
- const container = document.createElement('div');
- container.append(movableElement.cloneNode(true))
- const style = getComputedStyle(movableElement);
- container.style.height = style.height;
- container.style.width = style.width;
- container.style.paddingLeft = '25px';
- container.style.opacity = '0.4';
- return $(container);
- },
- appendTo: this._dragHelperContainer,
- cursor: 'move',
- scroll: false,
- },
- disableHorizontalScroll: true,
+ // TODO: This should be made more generic, one hook for the entire
+ // editable with each element handled.
+ this.smoothScrollOnDrag = useNativeDraggable(simpleDraggableHook, {
+ ref: { el: this._widgetContainer },
+ elements: ".oe-sidewidget-move",
+ onDragStart: () => this._startDropzones(movableElement, containerRect),
+ onDragEnd: () => this._stopDropzones(movableElement),
+ helper: () => {
+ const container = document.createElement('div');
+ container.append(movableElement.cloneNode(true));
+ const style = getComputedStyle(movableElement);
+ container.style.height = style.height;
+ container.style.width = style.width;
+ container.style.paddingLeft = '25px';
+ container.style.opacity = '0.4';
+ this._dragHelperContainer.append(container);
+ return container;
+ }
});
}
}
diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
index c36f44e4fc3..c2e194e34d9 100644
--- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
@@ -25,7 +25,7 @@
width: 77px;
background-color: $o-we-sidebar-blocks-content-snippet-bg;
- &.ui-draggable-dragging {
+ &.o_draggable_dragging {
transform: rotate(-3deg) scale(1.2);
box-shadow: 0 5px 25px -10px black;
transition: transform 0.3s, box-shadow 0.3s;
@@ -2467,7 +2467,7 @@ we-select.o_we_border_preview_aligned_select {
pointer-events: auto;
}
}
-.oe_overlay.ui-draggable-dragging .o_handles {
+.oe_overlay.o_draggable_dragging .o_handles {
display: none;
}
diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js
index f6a4d095399..b2b1a2c8a98 100644
--- a/addons/website/static/src/js/editor/snippets.editor.js
+++ b/addons/website/static/src/js/editor/snippets.editor.js
@@ -203,7 +203,10 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
/**
* @override
*/
- _getScrollOptions(options = {}) {
+ _getDragAndDropOptions(options = {}) {
+ // TODO: This is currently not in use by Odoo's D&D
+ // There is currently no way in Odoo D&D to offset the edge scrolling.
+ // When there is, this code should be adapted.
const finalOptions = this._super(...arguments);
if (!options.offsetElements || !options.offsetElements.$top) {
const $header = $('#top');
@@ -212,7 +215,6 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
finalOptions.offsetElements.$top = $header;
}
}
- finalOptions.jQueryDraggableOptions.iframeFix = true;
return finalOptions;
},
/**
diff --git a/addons/website/static/src/js/tours/tour_utils.js b/addons/website/static/src/js/tours/tour_utils.js
index 919f405cec3..1a12b1129da 100644
--- a/addons/website/static/src/js/tours/tour_utils.js
+++ b/addons/website/static/src/js/tours/tour_utils.js
@@ -69,7 +69,7 @@ function selectColorPalette(position = "left") {
function changeColumnSize(position = "right") {
return {
- trigger: `iframe .oe_overlay.ui-draggable.o_we_overlay_sticky.oe_active .o_handle.e`,
+ trigger: `iframe .oe_overlay.o_draggable.o_we_overlay_sticky.oe_active .o_handle.e`,
content: markup(_t("Slide this button to change the column size.")),
position: position,
};
@@ -133,7 +133,7 @@ function changePaddingSize(direction) {
position = "bottom";
}
return {
- trigger: `iframe .oe_overlay.ui-draggable.o_we_overlay_sticky.oe_active .o_handle.${paddingDirection}`,
+ trigger: `iframe .oe_overlay.o_draggable.o_we_overlay_sticky.oe_active .o_handle.${paddingDirection}`,
content: markup(_t("Slide this button to change the %s padding", direction)),
consumeEvent: 'mousedown',
position: position,
@@ -239,7 +239,7 @@ function dragNDrop(snippet, position = "bottom") {
position: position,
// Normally no main snippet can be dropped in the default footer but
// targeting it allows to force "dropping at the end of the page".
- run: "drag_and_drop iframe #wrapwrap > footer",
+ run: "drag_and_drop_native iframe #wrapwrap > footer",
};
}
diff --git a/addons/website/static/tests/tours/conditional_visibility.js b/addons/website/static/tests/tours/conditional_visibility.js
index 0515bbdb801..c4c884ef372 100644
--- a/addons/website/static/tests/tours/conditional_visibility.js
+++ b/addons/website/static/tests/tours/conditional_visibility.js
@@ -171,8 +171,8 @@ wTourUtils.clickOnSnippet(snippets[0]),
wTourUtils.clickOnSnippet(snippets[1]),
{
content: "Drag the 'Banner' snippet to the end of the page",
- trigger: "iframe .o_overlay_move_options .ui-draggable-handle",
- run: "drag_and_drop iframe #wrapwrap footer",
+ trigger: "iframe .o_overlay_move_options .o_move_handle",
+ run: "drag_and_drop_native iframe #wrapwrap footer",
},
...checkEyesIconAfterSave(false),
{
diff --git a/addons/website/static/tests/tours/focus_blur_snippets.js b/addons/website/static/tests/tours/focus_blur_snippets.js
index 363c7c4d249..3723e348bca 100644
--- a/addons/website/static/tests/tours/focus_blur_snippets.js
+++ b/addons/website/static/tests/tours/focus_blur_snippets.js
@@ -29,7 +29,7 @@ function clickAndCheck(blockID, expected) {
}, {
content: 'Once the related overlays are enabled/disabled, check that the focus/blur calls have been correct.',
trigger: blockID
- ? `iframe .oe_overlay.ui-draggable:eq(${blockData.overlayIndex}).oe_active`
+ ? `iframe .oe_overlay.o_draggable:eq(${blockData.overlayIndex}).oe_active`
: `iframe #oe_manipulators:not(:has(.oe_active))`,
allowInvisible: !blockID,
run: function (actions) {
@@ -57,7 +57,7 @@ wTourUtils.registerWebsitePreviewTour("focus_blur_snippets", {
{
content: 'Drag the custom block into the page',
trigger: '#snippet_structure .oe_snippet:has(.oe_snippet_body.s_focusblur) .oe_snippet_thumbnail',
- run: 'drag_and_drop iframe #wrap',
+ run: 'drag_and_drop_native iframe #wrap',
},
...clickAndCheck('parent', ['focus parent']),
...clickAndCheck(null, ['blur parent']),
diff --git a/addons/website/static/tests/tours/html_editor.js b/addons/website/static/tests/tours/html_editor.js
index d5296d9b5a7..a931793175a 100644
--- a/addons/website/static/tests/tours/html_editor.js
+++ b/addons/website/static/tests/tours/html_editor.js
@@ -16,7 +16,7 @@ wTourUtils.registerWebsitePreviewTour('html_editor_multiple_templates', {
content: "drop a snippet",
trigger: ".oe_snippet:has(.s_cover) .oe_snippet_thumbnail",
// id starting by 'oe_structure..' will actually create an inherited view
- run: "drag_and_drop iframe #oe_structure_test_ui",
+ run: "drag_and_drop_native iframe #oe_structure_test_ui",
},
...wTourUtils.clickOnSave(),
// 2. Edit generic view
diff --git a/addons/website/static/tests/tours/snippet_editor_panel_options.js b/addons/website/static/tests/tours/snippet_editor_panel_options.js
index 155a4c81ed2..5e947a5c4f1 100644
--- a/addons/website/static/tests/tours/snippet_editor_panel_options.js
+++ b/addons/website/static/tests/tours/snippet_editor_panel_options.js
@@ -107,7 +107,7 @@ wTourUtils.dragNDrop({
trigger: '.snippet-option-layout_column [data-select-count="3"]',
}, {
content: "The snippet should have the correct number of columns.",
- trigger: 'iframe .s_text_block .container > .row',
+ trigger: 'iframe .s_text_block .container > .row .col-lg-4:eq(3)',
run() {
if (this.$anchor[0].childElementCount !== 3) {
console.error("The snippet does not have the correct number of columns");
diff --git a/addons/website/static/tests/tours/snippet_popup_and_animations.js b/addons/website/static/tests/tours/snippet_popup_and_animations.js
index 477bb6eb87b..50989d3dc2c 100644
--- a/addons/website/static/tests/tours/snippet_popup_and_animations.js
+++ b/addons/website/static/tests/tours/snippet_popup_and_animations.js
@@ -83,7 +83,7 @@ wTourUtils.registerWebsitePreviewTour("snippet_popup_and_animations", {
content: "Drag the Columns building block and drop it at the bottom of the popup.",
trigger: '#oe_snippets .oe_snippet[name="Columns"] .oe_snippet_thumbnail:not(.o_we_already_dragging)',
extra_trigger: ".o_website_preview.editor_enable.editor_has_snippets",
- run: "drag_and_drop iframe #wrapwrap .modal-content .s_media_list .container > .row > :last-child",
+ run: "drag_and_drop_native iframe #wrapwrap .modal-content .s_media_list .container > .row > :last-child",
},
wTourUtils.clickOnElement("3rd columns", "iframe .s_popup .s_three_columns .row > :last-child"),
...setOnScrollAnim(),
diff --git a/addons/website/static/tests/tours/snippet_popup_and_scrollbar.js b/addons/website/static/tests/tours/snippet_popup_and_scrollbar.js
index 89419da10b7..e55f22ce333 100644
--- a/addons/website/static/tests/tours/snippet_popup_and_scrollbar.js
+++ b/addons/website/static/tests/tours/snippet_popup_and_scrollbar.js
@@ -52,7 +52,7 @@ wTourUtils.registerWebsitePreviewTour("snippet_popup_and_scrollbar", {
{
content: "Drag the Media List block and drop it in the popup.",
trigger: "#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail",
- run: "drag_and_drop iframe #wrap .s_popup .modal-content.oe_structure",
+ run: "drag_and_drop_native iframe #wrap .s_popup .modal-content.oe_structure",
},
checkScrollbar(false),
{
@@ -89,7 +89,7 @@ wTourUtils.registerWebsitePreviewTour("snippet_popup_and_scrollbar", {
{
content: "Drag the Media List block and drop it in the popup.",
trigger: "#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail",
- run: "drag_and_drop iframe #wrap .s_popup .modal-content.oe_structure",
+ run: "drag_and_drop_native iframe #wrap .s_popup .modal-content.oe_structure",
},
checkScrollbar(false),
{
@@ -108,7 +108,7 @@ wTourUtils.registerWebsitePreviewTour("snippet_popup_and_scrollbar", {
{
content: "Drag a Media List snippet and drop it in the Cookies Bar.",
trigger: "#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail",
- run: "drag_and_drop iframe #website_cookies_bar .modal-content.oe_structure",
+ run: "drag_and_drop_native iframe #website_cookies_bar .modal-content.oe_structure",
},
{
content: "Select the Media List snippet in the Cookies Bar.",
diff --git a/addons/website/static/tests/tours/snippet_table_of_content.js b/addons/website/static/tests/tours/snippet_table_of_content.js
index 2f82ebd30a7..ddbfa45e717 100644
--- a/addons/website/static/tests/tours/snippet_table_of_content.js
+++ b/addons/website/static/tests/tours/snippet_table_of_content.js
@@ -47,6 +47,8 @@ wTourUtils.registerWebsitePreviewTour('snippet_table_of_content', {
content: "Hide the first TOC on mobile",
trigger: '[data-toggle-device-visibility="no_mobile"]',
},
+ // Go back to blocks tabs to avoid changing the first ToC options
+ wTourUtils.goBackToBlocks(),
{
content: "Click on the second TOC's title",
trigger: 'iframe .s_table_of_content:eq(1) h2',
diff --git a/addons/website/static/tests/tours/snippet_version.js b/addons/website/static/tests/tours/snippet_version.js
index e0d0a69e1c0..94a1ffc7427 100644
--- a/addons/website/static/tests/tours/snippet_version.js
+++ b/addons/website/static/tests/tours/snippet_version.js
@@ -17,10 +17,10 @@ wTourUtils.registerWebsitePreviewTour("snippet_version", {
}),
{
content: "Test t-snippet and t-snippet-call: snippets have data-snippet set",
- trigger: '#oe_snippets .o_panel_body > .oe_snippet.ui-draggable',
+ trigger: '#oe_snippets .o_panel_body > .oe_snippet',
run: function () {
// Tests done here as all these are not visible on the page
- const draggableSnippets = document.querySelectorAll('#oe_snippets .o_panel_body > .oe_snippet.ui-draggable > :nth-child(2)');
+ const draggableSnippets = document.querySelectorAll('#oe_snippets .o_panel_body > .oe_snippet.o_draggable > :nth-child(2)');
if (![...draggableSnippets].every(el => el.dataset.snippet)) {
console.error("error Some t-snippet are missing their template name");
}
diff --git a/addons/website/static/tests/tours/snippets_all_drag_and_drop.js b/addons/website/static/tests/tours/snippets_all_drag_and_drop.js
index 3974bf9ed7e..48092afd0e3 100644
--- a/addons/website/static/tests/tours/snippets_all_drag_and_drop.js
+++ b/addons/website/static/tests/tours/snippets_all_drag_and_drop.js
@@ -41,7 +41,7 @@ for (const snippet of snippetsNames) {
const snippetSteps = [{
content: `Drop ${snippet} snippet [${n}/${snippetsNames.length}]`,
trigger: `#oe_snippets .oe_snippet:has( > [data-snippet='${snippet}']) .oe_snippet_thumbnail`,
- run: "drag_and_drop iframe #wrap",
+ run: "drag_and_drop_native iframe #wrap",
}, {
content: `Edit ${snippet} snippet`,
trigger: `iframe #wrap.o_editable [data-snippet='${snippet}']${isModal ? ' .modal.show' : ''}`,
diff --git a/addons/website/static/tests/tours/start_cloned_snippet.js b/addons/website/static/tests/tours/start_cloned_snippet.js
index 37d8e8698a1..ace86f121a4 100644
--- a/addons/website/static/tests/tours/start_cloned_snippet.js
+++ b/addons/website/static/tests/tours/start_cloned_snippet.js
@@ -12,7 +12,7 @@ wTourUtils.registerWebsitePreviewTour('website_start_cloned_snippet', {
id: 's_countdown',
};
const dragNDropOutOfFooter = wTourUtils.dragNDrop(countdownSnippet);
- dragNDropOutOfFooter.run = 'drag_and_drop iframe #wrapwrap #wrap';
+ dragNDropOutOfFooter.run = 'drag_and_drop_native iframe #wrapwrap #wrap';
return [
dragNDropOutOfFooter,
wTourUtils.clickOnSnippet(countdownSnippet),
diff --git a/addons/website/static/tests/tours/website_form_editor.js b/addons/website/static/tests/tours/website_form_editor.js
index e49fe06f44a..b3ce4af913b 100644
--- a/addons/website/static/tests/tours/website_form_editor.js
+++ b/addons/website/static/tests/tours/website_form_editor.js
@@ -137,7 +137,7 @@
{
content: "Drop the form snippet",
trigger: '#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail',
- run: 'drag_and_drop iframe #wrap',
+ run: 'drag_and_drop_native iframe #wrap',
}, {
content: "Select form by clicking on an input field",
extra_trigger: 'iframe .s_website_form_field',
@@ -551,7 +551,7 @@
{
content: "Add the form snippet",
trigger: '#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail',
- run: 'drag_and_drop iframe #wrap',
+ run: 'drag_and_drop_native iframe #wrap',
}, {
content: "Select the form by clicking on an input field",
extra_trigger: 'iframe .s_website_form_field',
diff --git a/addons/website_event/static/src/js/tours/event_tour.js b/addons/website_event/static/src/js/tours/event_tour.js
index 879c574a60d..1b3782b57c2 100644
--- a/addons/website_event/static/src/js/tours/event_tour.js
+++ b/addons/website_event/static/src/js/tours/event_tour.js
@@ -22,7 +22,7 @@ patch(EventAdditionalTourSteps.prototype, {
trigger: '#oe_snippets.o_loaded div[name="Image - Text"] .oe_snippet_thumbnail',
content: markup(_t("Drag and Drop this snippet below the event title.")),
position: 'bottom',
- run: 'drag_and_drop iframe #o_wevent_event_main_col',
+ run: 'drag_and_drop_native iframe #o_wevent_event_main_col',
}, {
trigger: 'button[data-action="save"]',
content: markup(_t("Don't forget to click save when you're done.")),
diff --git a/addons/website_event/static/tests/tours/website_event.js b/addons/website_event/static/tests/tours/website_event.js
index f9cff739fb6..cad75445e99 100644
--- a/addons/website_event/static/tests/tours/website_event.js
+++ b/addons/website_event/static/tests/tours/website_event.js
@@ -39,7 +39,7 @@
trigger: "#oe_snippets.o_loaded #snippet_structure .oe_snippet:eq(2) .oe_snippet_thumbnail",
content: _t("Drag this block and drop it in your page."),
position: "bottom",
- run: "drag_and_drop",
+ run: "drag_and_drop_native iframe #wrapwrap > main",
}, {
trigger: "button[data-action=save]",
content: _t("Once you click on save, your event is updated."),
diff --git a/addons/website_sale/static/src/js/tours/website_sale_shop.js b/addons/website_sale/static/src/js/tours/website_sale_shop.js
index f9b0fbe4072..55ae54922ac 100644
--- a/addons/website_sale/static/src/js/tours/website_sale_shop.js
+++ b/addons/website_sale/static/src/js/tours/website_sale_shop.js
@@ -56,7 +56,7 @@
extra_trigger: "body:not(.modal-open)",
content: _t("Drag this website block and drop it in your page."),
position: "bottom",
- run: "drag_and_drop",
+ run: "drag_and_drop_native iframe #wrapwrap > main",
}, {
trigger: "button[data-action=save]",
content: markup(_t("Once you click on Save, your product is updated.")),
diff --git a/addons/website_sale/static/tests/tours/website_sale_category_page_and_products_snippet.js b/addons/website_sale/static/tests/tours/website_sale_category_page_and_products_snippet.js
index a12bc3adc9e..1a5b0c18985 100644
--- a/addons/website_sale/static/tests/tours/website_sale_category_page_and_products_snippet.js
+++ b/addons/website_sale/static/tests/tours/website_sale_category_page_and_products_snippet.js
@@ -12,7 +12,7 @@ wTourUtils.registerWebsitePreviewTour('category_page_and_products_snippet_editio
}, () => [
Object.assign(wTourUtils.dragNDrop({id: 's_dynamic_snippet_products', name: 'Products'}), {
content: "Drag and drop the product snippet inside the category area",
- run: 'drag_and_drop iframe #category_header',
+ run: 'drag_and_drop_native iframe #category_header',
}),
{
content: "Click on the product snippet to show its options",