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 all 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
52 changes: 19 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,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();
Expand Down Expand Up @@ -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<DragEvent>(e, { target: this.el, type: 'dragstart' });

Expand Down Expand Up @@ -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 */
Expand All @@ -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();
Expand All @@ -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
};
}

Expand All @@ -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.
Expand Down
18 changes: 3 additions & 15 deletions src/dd-resizable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/)) {
Expand Down
48 changes: 46 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 @@ -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;

/**
Expand Down Expand Up @@ -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';
Copy link
Member

Choose a reason for hiding this comment

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

not sure I understand the "slightly adjust its position" and code here - it s mapping exactly to the same relative point (vido seem that way). can't figure from the code right now

Copy link
Author

@Amdoun Amdoun Jan 21, 2024

Choose a reason for hiding this comment

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

Yes, that code is mapping to the same relative position after scaling. the mouse position relative to the element should be the same after scaling.

The "slightly" part refers to applying that extra code mentioned. Otherwise the relative position would look off after scaling.

Copy link

@creage creage Nov 8, 2024

Choose a reason for hiding this comment

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

@Amdoun @adumesny These lines (setting left and top on helper) don't work well when the draggable has different appendTo than body (defined in setupDragIn), and when this appendTo element has top/left greater than 0.

Helper element receives mouse positions + offset of the appendTo element, placing it wrong.

Copy link
Member

Choose a reason for hiding this comment

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

@creage, please file a bug with a test case showing the issue. I'll have to remind myself why we have appendTo:element to start with (likely historical jquery UI option) as I'm not sure the use case - if you can comment on that as well.
supporting too many options makes the lib complicated/briddle.

Copy link

Choose a reason for hiding this comment

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

@adumesny Well, we don't want to add draggable helper to the body, but we want them to stick under some parent container - this way we can adjust styling of such helpers easily, and less pollute to the global body scope.

Like, a Dialog that shows a GridStack with some items we can drag to the grid. All styling is defined within the Dialog component, which we can use in different areas in our app.

So, this option is quite useful - please don't remove it.

Copy link
Member

Choose a reason for hiding this comment

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

| All styling is defined within the Dialog component

ok makes sense. but then aren't items clipped by the dialog when dragging around which is a little odd, even if you can only drop them in the grid of the dialog.

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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
34 changes: 34 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,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)
Expand Down