Skip to content

Commit

Permalink
moved scale getter to utils
Browse files Browse the repository at this point in the history
adjusted scale + position for elements dragged from outside
  • Loading branch information
Mohamed Amdoun committed Dec 16, 2023
1 parent c0abba2 commit 0db5f82
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 66 deletions.
56 changes: 23 additions & 33 deletions src/dd-draggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand All @@ -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<string>;
/** @internal */
protected dragEl: HTMLElement;
Expand All @@ -70,6 +63,15 @@ 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 transformReference: HTMLElement;
/** @internal */
protected dragTransform: DragTransform = {
xScale: 1,
yScale: 1,
xOffset: 0,
yOffset: 0
};

constructor(el: HTMLElement, option: DDDraggableOpt = {}) {
super();
Expand All @@ -79,6 +81,7 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
// get the element that is actually supposed to be dragged by
let handleName = option.handle.substring(1);
this.dragEl = el.classList.contains(handleName) ? el : el.querySelector(option.handle) || el;
this.transformReference = Utils.createTransformReferenceElement();
// create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions)
this._mouseDown = this._mouseDown.bind(this);
this._mouseMove = this._mouseMove.bind(this);
Expand Down Expand Up @@ -214,6 +217,10 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
}
this.helper = this._createHelper(e);
this._setupHelperContainmentStyle();
this.dragTransform = Utils.getValuesFromTransformedElement(
this.transformReference,
this.helperContainment
);
this.dragOffset = this._getDragOffset(e, this.el, this.helperContainment);
const ev = Utils.initEvent<DragEvent>(e, { target: this.el, type: 'dragstart' });

Expand Down Expand Up @@ -336,8 +343,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 */
Expand All @@ -359,25 +366,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();
Expand All @@ -386,8 +376,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
};
}

Expand All @@ -398,8 +388,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.
Expand Down
21 changes: 6 additions & 15 deletions src/dd-resizable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
protected parentOriginStylePosition: string;
/** @internal */
protected static _originStyleProp = ['width', 'height', 'position', 'left', 'top', 'opacity', 'zIndex'];
/** @internal */
protected transformReference: HTMLElement;

constructor(el: HTMLElement, opts: DDResizableOpt = {}) {
super();
this.el = el;
this.option = opts;
this.transformReference = Utils.createTransformReferenceElement();
// create var event binding so we can easily remove and still look like TS methods (unlike anonymous functions)
this._mouseOver = this._mouseOver.bind(this);
this._mouseOut = this._mouseOut.bind(this);
Expand Down Expand Up @@ -226,22 +229,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 transformValues = Utils.getValuesFromTransformedElement(this.transformReference, parent);
this.rectScale = {
x: 1 / testElPosition.width,
y: 1 / testElPosition.height
x: transformValues.xScale,
y: transformValues.yScale
};

if (getComputedStyle(this.el.parentElement).position.match(/static/)) {
Expand Down
54 changes: 36 additions & 18 deletions src/gridstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -2031,34 +2031,50 @@ export class GridStack {
let cellHeight: number, cellWidth: number;

// creates a reference element for tracking the right position after scaling
const testEl = document.createElement('div');
Utils.addElStyles(testEl, {
opacity: '0',
position: 'fixed',
top: 0 + 'px',
left: 0 + 'px',
width: '1px',
height: '1px',
zIndex: '-999999',
});
const transformReference = Utils.createTransformReferenceElement();

let onDrag = (event: DragEvent, el: GridItemHTMLElement, helper: GridItemHTMLElement) => {
let node = el.gridstackNode;
if (!node) return;

helper = helper || el;
helper.appendChild(testEl);
const testElPosition = testEl.getBoundingClientRect();
helper.removeChild(testEl);
const dragScale = {
x: 1 / testElPosition.width,
y: 1 / testElPosition.height,
let transformValues: DragTransform;
// if we are dragging an element in and out that is coming from a grid
// we get the transform values by using the helper attached to the grid
if (node.grid?.el) {
transformValues = Utils.getValuesFromTransformedElement(transformReference, helper)
}
// 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;
transformValues = Utils.getValuesFromTransformedElement(transformReference, gridEl);
// if the element is being dragged from outside, scale it down to match the grid's scale
helper.style.transform = `scale(${1 / transformValues.xScale},${1 / transformValues.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 + (transformValues.xScale - 1) * (event.clientX - helperRect.x) / transformValues.xScale + 'px';
helper.style.top = helperRect.y + (transformValues.yScale - 1) * (event.clientY - helperRect.y) / transformValues.yScale + 'px';
helper.style.transformOrigin = `0px 0px`
} // if all else fails, we might want to use the default transform value
else {
transformValues = {
xScale: 1,
xOffset: 0,
yScale: 1,
yOffset: 0,
}
}
let parent = this.el.getBoundingClientRect();
let {top, left} = helper.getBoundingClientRect();
left -= parent.left;
top -= parent.top;
let ui: DDUIData = {position: {top: top * dragScale.y, left: left * dragScale.x}};
let ui: DDUIData = {
position: {
top: top * transformValues.xScale,
left: left * transformValues.yScale
}
};

if (node._temporaryRemoved) {
node.x = Math.max(0, Math.round(left / cellWidth));
Expand Down Expand Up @@ -2534,6 +2550,8 @@ export class GridStack {
let node = el.gridstackNode;
if (!node) return;

helper = helper || el;
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
Expand Down
41 changes: 41 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -554,6 +561,40 @@ export class Utils {
(target || e.target).dispatchEvent(simulatedEvent);
}

/**
* defines an element that is used to get the offset and scale from grid transforms
* has to be hooked to a helper
* should be called once
*/
public static createTransformReferenceElement(): HTMLElement {
const transformReference = document.createElement('div');
Utils.addElStyles(transformReference, {
opacity: '0',
position: 'fixed',
top: 0 + 'px',
left: 0 + 'px',
width: '1px',
height: '1px',
zIndex: '-999999',
});
return transformReference;
}

/**
* can be used after setting the reference element from createTransformReferenceElement
*/
public static getValuesFromTransformedElement(transformReference: HTMLElement, helper: HTMLElement): DragTransform {
helper.appendChild(transformReference);
const transformValues = transformReference.getBoundingClientRect();
helper.removeChild(transformReference);
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)
Expand Down

0 comments on commit 0db5f82

Please sign in to comment.