Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apply position scaling when acceptwidgets is enabled #2578

Merged
merged 4 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason to save this as all ? we only need to calculate the scale/offset so no need to keep that element around (which then also needs to be freed

Have Utils.getValuesFromTransformedElement() create and delete it instead (internally)

Copy link
Author

@Amdoun Amdoun Dec 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed.

also it used to create a single instance of that div that could be reused for the next calls. i don't know what were the implications of creating this div and clearing it up on every single mousemove call. probably not much to worry about, so now it creates that element, gets the values, then deletes it.

on a side note, it turns out just calling parent.removeChild(transformReference) was not enough to delete the element: i had to call transformReference.remove() too

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

| every single mousemove call
the scale/xform should not be called every move event - we know that info when we start/enter/leave - that should be fixed instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dragTransform is only calculated in onStartMoving now

// 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
42 changes: 40 additions & 2 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 @@ -2030,16 +2030,51 @@ export class GridStack {
// vars shared across all methods
let cellHeight: number, cellWidth: number;

// creates a reference element for tracking the right position after scaling
const transformReference = Utils.createTransformReferenceElement();

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

helper = helper || el;
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, left}};
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 @@ -2515,6 +2550,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
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 {
Amdoun marked this conversation as resolved.
Show resolved Hide resolved
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