diff --git a/src/dd-draggable.ts b/src/dd-draggable.ts index cef08688..6592ec00 100644 --- a/src/dd-draggable.ts +++ b/src/dd-draggable.ts @@ -4,7 +4,7 @@ */ import { DDManager } from './dd-manager'; -import { Utils } from './utils'; +import { DragTransform, Utils } from './utils'; import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl'; import { GridItemHTMLElement, DDUIData } from './types'; import { DDElementHost } from './dd-element'; @@ -33,11 +33,6 @@ interface DragOffset { offsetTop: number; } -interface DragScaleReciprocal { - x: number; - y: number; -} - type DDDragEvent = 'drag' | 'dragstart' | 'dragstop'; // make sure we are not clicking on known object that handles mouseDown @@ -55,8 +50,6 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt /** @internal */ protected dragOffset: DragOffset; /** @internal */ - protected dragScale: DragScaleReciprocal = { x: 1, y: 1 }; - /** @internal */ protected dragElementOriginStyle: Array; /** @internal */ protected dragEl: HTMLElement; @@ -70,6 +63,13 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt protected static originStyleProp = ['transition', 'pointerEvents', 'position', 'left', 'top', 'minWidth', 'willChange']; /** @internal pause before we call the actual drag hit collision code */ protected dragTimeout: number; + /** @internal */ + protected dragTransform: DragTransform = { + xScale: 1, + yScale: 1, + xOffset: 0, + yOffset: 0 + }; constructor(el: HTMLElement, option: DDDraggableOpt = {}) { super(); @@ -214,6 +214,9 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt } this.helper = this._createHelper(e); this._setupHelperContainmentStyle(); + this.dragTransform = Utils.getValuesFromTransformedElement( + this.helperContainment + ); this.dragOffset = this._getDragOffset(e, this.el, this.helperContainment); const ev = Utils.initEvent(e, { target: this.el, type: 'dragstart' }); @@ -336,8 +339,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt // } const style = this.helper.style; const offset = this.dragOffset; - style.left = (e.clientX + offset.offsetLeft - containmentRect.left) * this.dragScale.x + 'px'; - style.top = (e.clientY + offset.offsetTop - containmentRect.top) * this.dragScale.y + 'px'; + style.left = (e.clientX + offset.offsetLeft - containmentRect.left) * this.dragTransform.xScale + 'px'; + style.top = (e.clientY + offset.offsetTop - containmentRect.top) * this.dragTransform.yScale + 'px'; } /** @internal */ @@ -359,25 +362,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt let xformOffsetX = 0; let xformOffsetY = 0; if (parent) { - const testEl = document.createElement('div'); - Utils.addElStyles(testEl, { - opacity: '0', - position: 'fixed', - top: 0 + 'px', - left: 0 + 'px', - width: '1px', - height: '1px', - zIndex: '-999999', - }); - parent.appendChild(testEl); - const testElPosition = testEl.getBoundingClientRect(); - parent.removeChild(testEl); - xformOffsetX = testElPosition.left; - xformOffsetY = testElPosition.top; - this.dragScale = { - x: 1 / testElPosition.width, - y: 1 / testElPosition.height - }; + xformOffsetX = this.dragTransform.xOffset; + xformOffsetY = this.dragTransform.yOffset; } const targetOffset = el.getBoundingClientRect(); @@ -386,8 +372,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt top: targetOffset.top, offsetLeft: - event.clientX + targetOffset.left - xformOffsetX, offsetTop: - event.clientY + targetOffset.top - xformOffsetY, - width: targetOffset.width * this.dragScale.x, - height: targetOffset.height * this.dragScale.y + width: targetOffset.width * this.dragTransform.xScale, + height: targetOffset.height * this.dragTransform.yScale }; } @@ -398,8 +384,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt const offset = this.helper.getBoundingClientRect(); return { position: { //Current CSS position of the helper as { top, left } object - top: (offset.top - containmentRect.top) * this.dragScale.y, - left: (offset.left - containmentRect.left) * this.dragScale.x + top: (offset.top - containmentRect.top) * this.dragTransform.yScale, + left: (offset.left - containmentRect.left) * this.dragTransform.xScale } /* not used by GridStack for now... helper: [this.helper], //The object arr representing the helper that's being dragged. diff --git a/src/dd-resizable.ts b/src/dd-resizable.ts index 478f28aa..8afafb16 100644 --- a/src/dd-resizable.ts +++ b/src/dd-resizable.ts @@ -226,22 +226,10 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt this.parentOriginStylePosition = this.el.parentElement.style.position; const parent = this.el.parentElement; - const testEl = document.createElement('div'); - Utils.addElStyles(testEl, { - opacity: '0', - position: 'fixed', - top: 0 + 'px', - left: 0 + 'px', - width: '1px', - height: '1px', - zIndex: '-999999', - }); - parent.appendChild(testEl); - const testElPosition = testEl.getBoundingClientRect(); - parent.removeChild(testEl); + const dragTransform = Utils.getValuesFromTransformedElement(parent); this.rectScale = { - x: 1 / testElPosition.width, - y: 1 / testElPosition.height + x: dragTransform.xScale, + y: dragTransform.yScale }; if (getComputedStyle(this.el.parentElement).position.match(/static/)) { diff --git a/src/gridstack.ts b/src/gridstack.ts index abab3811..7306af87 100644 --- a/src/gridstack.ts +++ b/src/gridstack.ts @@ -6,7 +6,7 @@ * see root license https://github.com/gridstack/gridstack.js/tree/master/LICENSE */ import { GridStackEngine } from './gridstack-engine'; -import { Utils, HeightData, obsolete } from './utils'; +import { Utils, HeightData, obsolete, DragTransform } from './utils'; import { gridDefaults, ColumnOptions, GridItemHTMLElement, GridStackElement, GridStackEventHandlerCallback, GridStackNode, GridStackWidget, numberOrString, DDUIData, DDDragInOpt, GridStackPosition, GridStackOptions, dragInDefaultOptions, GridStackEventHandler, GridStackNodesHandler, AddRemoveFcn, SaveFcn, CompactOptions, GridStackMoveOpts, ResizeToContentFcn } from './types'; @@ -259,6 +259,8 @@ export class GridStack { protected _extraDragRow = 0; /** @internal true if nested grid should get column count from our width */ protected _autoColumn?: boolean; + /** @internal meant to store the scale of the active grid */ + protected dragTransform: DragTransform = { xScale: 1, yScale: 1, xOffset: 0, yOffset: 0 }; private _skipInitialResize: boolean; /** @@ -2035,11 +2037,29 @@ export class GridStack { if (!node) return; helper = helper || el; + + // if the element is being dragged from outside, scale it down to match the grid's scale + // and slightly adjust its position relative to the mouse + if (!node.grid?.el) { + // this scales the helper down + helper.style.transform = `scale(${1 / this.dragTransform.xScale},${1 / this.dragTransform.yScale})`; + // this makes it so that the helper is well positioned relative to the mouse after scaling + const helperRect = helper.getBoundingClientRect(); + helper.style.left = helperRect.x + (this.dragTransform.xScale - 1) * (event.clientX - helperRect.x) / this.dragTransform.xScale + 'px'; + helper.style.top = helperRect.y + (this.dragTransform.yScale - 1) * (event.clientY - helperRect.y) / this.dragTransform.yScale + 'px'; + helper.style.transformOrigin = `0px 0px` + } + let parent = this.el.getBoundingClientRect(); let {top, left} = helper.getBoundingClientRect(); left -= parent.left; top -= parent.top; - let ui: DDUIData = {position: {top, left}}; + let ui: DDUIData = { + position: { + top: top * this.dragTransform.xScale, + left: left * this.dragTransform.yScale + } + }; if (node._temporaryRemoved) { node.x = Math.max(0, Math.round(left / cellWidth)); @@ -2395,6 +2415,27 @@ export class GridStack { this.el.appendChild(this.placeholder); // console.log('_onStartMoving placeholder') // TEST + // if the element is inside a grid, it has already been scaled + // we can use that as a scale reference + if (node.grid?.el) { + this.dragTransform = Utils.getValuesFromTransformedElement(el); + } + // if the element is being dragged from outside (not from any grid) + // we use the grid as the transformation reference, since the helper is not subject to transformation + else if (this.placeholder && this.placeholder.closest('.grid-stack')) { + const gridEl = this.placeholder.closest('.grid-stack') as HTMLElement; + this.dragTransform = Utils.getValuesFromTransformedElement(gridEl); + } + // Fallback + else { + this.dragTransform = { + xScale: 1, + xOffset: 0, + yScale: 1, + yOffset: 0, + } + } + node.el = this.placeholder; node._lastUiPosition = ui.position; node._prevYPix = ui.position.top; @@ -2515,6 +2556,9 @@ export class GridStack { let node = el.gridstackNode; if (!node) return; + helper = helper || el; + // restore the scale of the helper on leave + helper.style.transform = 'scale(1)'; dd.off(el, 'drag'); // no need to track while being outside // this gets called when cursor leaves and shape is outside, so only do this once diff --git a/src/utils.ts b/src/utils.ts index 46358d5e..15849a5e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,6 +10,13 @@ export interface HeightData { unit: string; } +export interface DragTransform { + xScale: number; + yScale: number; + xOffset: number; + yOffset: number; +} + /** checks for obsolete method names */ // eslint-disable-next-line export function obsolete(self, f, oldName: string, newName: string, rev: string): (...args: any[]) => any { @@ -554,6 +561,33 @@ export class Utils { (target || e.target).dispatchEvent(simulatedEvent); } + /** + * defines an element that is used to get the offset and scale from grid transforms + * returns the scale and offsets from said element + */ + public static getValuesFromTransformedElement(parent: HTMLElement): DragTransform { + const transformReference = document.createElement('div'); + Utils.addElStyles(transformReference, { + opacity: '0', + position: 'fixed', + top: 0 + 'px', + left: 0 + 'px', + width: '1px', + height: '1px', + zIndex: '-999999', + }); + parent.appendChild(transformReference); + const transformValues = transformReference.getBoundingClientRect(); + parent.removeChild(transformReference); + transformReference.remove(); + return { + xScale: 1 / transformValues.width, + yScale: 1 / transformValues.height, + xOffset: transformValues.left, + yOffset: transformValues.top, + } + } + /** returns true if event is inside the given element rectangle */ // Note: Safari Mac has null event.relatedTarget which causes #1684 so check if DragEvent is inside the coordinates instead // this.el.contains(event.relatedTarget as HTMLElement)