Skip to content

Commit 486ec6e

Browse files
authored
Merge pull request gridstack#2504 from adumesny/master
Revert "fix(drag): always put element under the mouse when dragging a…
2 parents c60dfd7 + 537344c commit 486ec6e

File tree

6 files changed

+95
-97
lines changed

6 files changed

+95
-97
lines changed

demo/transform.html

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="utf-8">
55
<meta http-equiv="X-UA-Compatible" content="IE=edge">
66
<meta name="viewport" content="width=device-width, initial-scale=1">
7-
<title>Transform (Scale) Parent demo</title>
7+
<title>Transform Parent demo</title>
88

99
<link rel="stylesheet" href="demo.css"/>
1010
<script src="../dist/gridstack-all.js"></script>
@@ -13,15 +13,16 @@
1313
<body>
1414
<div class="container-fluid">
1515
<h1>Transform Parent demo</h1>
16-
<p>example where the grid parent has a translate(50px, 100px) scale(0.5, 0.5) </p>
16+
<p>example where the grid parent has a translate(50px, 100px) <s>scale(0.5, 0.5)</s> (scale not working yet see #1275)</p>
1717
<div>
1818
<a class="btn btn-primary" onClick="addNewWidget()" href="#">Add Widget</a>
19-
<a class="btn btn-primary" onClick="zoomIn()" href="#">Zoom in</a>
20-
<a class="btn btn-primary" onClick="zoomOut()" href="#">Zoom out</a>
19+
<!-- <a class="btn btn-primary" onClick="zoomIn()" href="#">Zoom in</a>
20+
<a class="btn btn-primary" onClick="zoomOut()" href="#">Zoom out</a> -->
2121
</div>
2222
<br><br>
23-
<div style="transform: translate(50px, 100px) scale(var(--global-scale), var(--global-scale)); transform-origin: 0 0;">
24-
<div class="grid-stack"></div>
23+
<!-- <div style="transform: translate(50px, 100px) scale(var(--global-scale), var(--global-scale)); transform-origin: 0 0;"> -->
24+
<div style="transform: translate(50px, 100px)">
25+
<div class="grid-stack"></div>
2526
</div>
2627
</div>
2728
<script src="events.js"></script>

doc/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ Change log
103103
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
104104

105105
## 9.3.0-dev (TBD)
106+
* revert [#1275](https://github.com/gridstack/gridstack.js/issues/1275) div scale support - causing too many issues for now (#2498 #2497 #2491)
106107
* fix [#2492](https://github.com/gridstack/gridstack.js/issues/2492) calling load() allows overlapping widgets
107108

108109
## 9.3.0 (2023-09-30)

src/dd-draggable.ts

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ export interface DDDraggableOpt {
2424
drag?: (event: Event, ui: DDUIData) => void;
2525
}
2626

27+
interface DragOffset {
28+
left: number;
29+
top: number;
30+
width: number;
31+
height: number;
32+
offsetLeft: number;
33+
offsetTop: number;
34+
}
35+
2736
type DDDragEvent = 'drag' | 'dragstart' | 'dragstop';
2837

2938
// make sure we are not clicking on known object that handles mouseDown
@@ -39,6 +48,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
3948
/** @internal */
4049
protected mouseDownEvent: MouseEvent;
4150
/** @internal */
51+
protected dragOffset: DragOffset;
52+
/** @internal */
4253
protected dragElementOriginStyle: Array<string>;
4354
/** @internal */
4455
protected dragEl: HTMLElement;
@@ -52,7 +63,6 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
5263
protected static originStyleProp = ['transition', 'pointerEvents', 'position', 'left', 'top', 'minWidth', 'willChange'];
5364
/** @internal pause before we call the actual drag hit collision code */
5465
protected dragTimeout: number;
55-
protected origRelativeMouse: { x: number; y: number; };
5666

5767
constructor(el: HTMLElement, option: DDDraggableOpt = {}) {
5868
super();
@@ -195,10 +205,9 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
195205
} else {
196206
delete DDManager.dropElement;
197207
}
198-
const rect = this.el.getBoundingClientRect();
199-
this.origRelativeMouse = { x: s.clientX - rect.left, y: s.clientY - rect.top };
200208
this.helper = this._createHelper(e);
201209
this._setupHelperContainmentStyle();
210+
this.dragOffset = this._getDragOffset(e, this.el, this.helperContainment);
202211
const ev = Utils.initEvent<DragEvent>(e, { target: this.el, type: 'dragstart' });
203212

204213
this._setupHelperStyle(e);
@@ -276,9 +285,8 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
276285
const style = this.helper.style;
277286
style.pointerEvents = 'none'; // needed for over items to get enter/leave
278287
// style.cursor = 'move'; // TODO: can't set with pointerEvents=none ! (done in CSS as well)
279-
style.width = this.el.offsetWidth + 'px';
280-
style.height = this.el.offsetHeight + 'px';
281-
288+
style.width = this.dragOffset.width + 'px';
289+
style.height = this.dragOffset.height + 'px';
282290
style.willChange = 'left, top';
283291
style.position = 'fixed'; // let us drag between grids by not clipping as parent .grid-stack is position: 'relative'
284292
this._dragFollow(e); // now position it
@@ -314,19 +322,15 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
314322

315323
/** @internal updates the top/left position to follow the mouse */
316324
protected _dragFollow(e: DragEvent): void {
325+
let containmentRect = { left: 0, top: 0 };
326+
// if (this.helper.style.position === 'absolute') { // we use 'fixed'
327+
// const { left, top } = this.helperContainment.getBoundingClientRect();
328+
// containmentRect = { left, top };
329+
// }
317330
const style = this.helper.style;
318-
const { scaleX, scaleY } = Utils.getScaleForElement(this.helper);
319-
const transformParent = Utils.getContainerForPositionFixedElement(this.helper);
320-
const transformParentRect = transformParent.getBoundingClientRect();
321-
// when an element is scaled, the helper is positioned relative to the first transformed parent, so we need to remove the extra offset
322-
const offsetX = transformParentRect.left;
323-
const offsetY = transformParentRect.top;
324-
325-
// Position the element under the mouse
326-
const x = (e.clientX - offsetX - (this.origRelativeMouse?.x || 0)) / scaleX;
327-
const y = (e.clientY - offsetY - (this.origRelativeMouse?.y || 0)) / scaleY;
328-
style.left = `${x}px`;
329-
style.top = `${y}px`;
331+
const offset = this.dragOffset;
332+
style.left = e.clientX + offset.offsetLeft - containmentRect.left + 'px';
333+
style.top = e.clientY + offset.offsetTop - containmentRect.top + 'px';
330334
}
331335

332336
/** @internal */
@@ -341,23 +345,51 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
341345
return this;
342346
}
343347

348+
/** @internal */
349+
protected _getDragOffset(event: DragEvent, el: HTMLElement, parent: HTMLElement): DragOffset {
350+
351+
// in case ancestor has transform/perspective css properties that change the viewpoint
352+
let xformOffsetX = 0;
353+
let xformOffsetY = 0;
354+
if (parent) {
355+
const testEl = document.createElement('div');
356+
Utils.addElStyles(testEl, {
357+
opacity: '0',
358+
position: 'fixed',
359+
top: 0 + 'px',
360+
left: 0 + 'px',
361+
width: '1px',
362+
height: '1px',
363+
zIndex: '-999999',
364+
});
365+
parent.appendChild(testEl);
366+
const testElPosition = testEl.getBoundingClientRect();
367+
parent.removeChild(testEl);
368+
xformOffsetX = testElPosition.left;
369+
xformOffsetY = testElPosition.top;
370+
// TODO: scale ?
371+
}
372+
373+
const targetOffset = el.getBoundingClientRect();
374+
return {
375+
left: targetOffset.left,
376+
top: targetOffset.top,
377+
offsetLeft: - event.clientX + targetOffset.left - xformOffsetX,
378+
offsetTop: - event.clientY + targetOffset.top - xformOffsetY,
379+
width: targetOffset.width,
380+
height: targetOffset.height
381+
};
382+
}
383+
344384
/** @internal TODO: set to public as called by DDDroppable! */
345385
public ui(): DDUIData {
346386
const containmentEl = this.el.parentElement;
347-
const scrollElement = Utils.getScrollElement(this.el.parentElement);
348387
const containmentRect = containmentEl.getBoundingClientRect();
349388
const offset = this.helper.getBoundingClientRect();
350-
const { scaleX, scaleY } = Utils.getScaleForElement(this.helper);
351-
352-
// When an element is inside a scrolled element, the boundingClientRect will return the position of the element minus the scroll.
353-
const parentPositionIncludingScroll = containmentEl === scrollElement
354-
? { top: containmentRect.top + scrollElement.scrollTop, left: containmentRect.left + scrollElement.scrollLeft }
355-
: { top: containmentRect.top, left: containmentRect.left };
356-
357389
return {
358-
position: { // Current CSS position of the helper as { top, left } object
359-
top: (offset.top - parentPositionIncludingScroll.top) / scaleY,
360-
left: (offset.left - parentPositionIncludingScroll.left) / scaleX,
390+
position: { //Current CSS position of the helper as { top, left } object
391+
top: offset.top - containmentRect.top,
392+
left: offset.left - containmentRect.left
361393
}
362394
/* not used by GridStack for now...
363395
helper: [this.helper], //The object arr representing the helper that's being dragged.

src/dd-resizable.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,11 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
237237
/** @internal */
238238
protected _getChange(event: MouseEvent, dir: string): Rect {
239239
const oEvent = this.startEvent;
240-
const containerElement = Utils.getPositionContainerElement(this.el.parentElement);
241-
const containerRect = containerElement.getBoundingClientRect();
242-
243240
const newRect = { // Note: originalRect is a complex object, not a simple Rect, so copy out.
244241
width: this.originalRect.width,
245242
height: this.originalRect.height + this.scrolled,
246-
left: this.originalRect.left - containerRect.left,
247-
top: this.originalRect.top - this.scrolled - containerRect.top
243+
left: this.originalRect.left,
244+
top: this.originalRect.top - this.scrolled
248245
};
249246

250247
const offsetX = event.clientX - oEvent.clientX;
@@ -280,25 +277,28 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
280277

281278
/** @internal constrain the size to the set min/max values */
282279
protected _constrainSize(oWidth: number, oHeight: number): Size {
283-
const { scaleX, scaleY } = Utils.getScaleForElement(this.el);
284-
const o = this.option;
285-
const maxWidth = o.maxWidth ? o.maxWidth * scaleX : Number.MAX_SAFE_INTEGER;
286-
const minWidth = o.minWidth ? o.minWidth * scaleX : oWidth;
287-
const maxHeight = o.maxHeight ? o.maxHeight * scaleY : Number.MAX_SAFE_INTEGER;
288-
const minHeight = o.minHeight ? o.minHeight * scaleY : oHeight;
280+
const maxWidth = this.option.maxWidth || Number.MAX_SAFE_INTEGER;
281+
const minWidth = this.option.minWidth || oWidth;
282+
const maxHeight = this.option.maxHeight || Number.MAX_SAFE_INTEGER;
283+
const minHeight = this.option.minHeight || oHeight;
289284
const width = Math.min(maxWidth, Math.max(minWidth, oWidth));
290285
const height = Math.min(maxHeight, Math.max(minHeight, oHeight));
291286
return { width, height };
292287
}
293288

294289
/** @internal */
295290
protected _applyChange(): DDResizable {
291+
let containmentRect = { left: 0, top: 0, width: 0, height: 0 };
292+
if (this.el.style.position === 'absolute') {
293+
const containmentEl = this.el.parentElement;
294+
const { left, top } = containmentEl.getBoundingClientRect();
295+
containmentRect = { left, top, width: 0, height: 0 };
296+
}
296297
if (!this.temporalRect) return this;
297-
const { scaleX, scaleY } = Utils.getScaleForElement(this.el);
298-
this.el.style.width = `${Math.round(this.temporalRect.width / scaleX)}px`;
299-
this.el.style.height = `${Math.round(this.temporalRect.height / scaleY)}px`;
300-
this.el.style.top = `${Math.round(this.temporalRect.top / scaleY)}px`;
301-
this.el.style.left = `${Math.round(this.temporalRect.left / scaleX)}px`;
298+
Object.keys(this.temporalRect).forEach(key => {
299+
const value = this.temporalRect[key];
300+
this.el.style[key] = value - containmentRect[key] + 'px';
301+
});
302302
return this;
303303
}
304304

@@ -311,22 +311,23 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
311311

312312
/** @internal */
313313
protected _ui = (): DDUIData => {
314-
const { scaleX, scaleY } = Utils.getScaleForElement(this.el);
314+
const containmentEl = this.el.parentElement;
315+
const containmentRect = containmentEl.getBoundingClientRect();
315316
const newRect = { // Note: originalRect is a complex object, not a simple Rect, so copy out.
316317
width: this.originalRect.width,
317-
height: (this.originalRect.height + this.scrolled),
318+
height: this.originalRect.height + this.scrolled,
318319
left: this.originalRect.left,
319-
top: (this.originalRect.top - this.scrolled)
320+
top: this.originalRect.top - this.scrolled
320321
};
321322
const rect = this.temporalRect || newRect;
322323
return {
323324
position: {
324-
left: rect.left / scaleX,
325-
top: rect.top / scaleY,
325+
left: rect.left - containmentRect.left,
326+
top: rect.top - containmentRect.top
326327
},
327328
size: {
328-
width: rect.width / scaleX,
329-
height: rect.height / scaleY,
329+
width: rect.width,
330+
height: rect.height
330331
}
331332
/* Gridstack ONLY needs position set above... keep around in case.
332333
element: [this.el], // The object representing the element to be resized

src/gridstack.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,10 +1947,9 @@ export class GridStack {
19471947

19481948
helper = helper || el;
19491949
let parent = this.el.getBoundingClientRect();
1950-
const { scaleX, scaleY } = Utils.getScaleForElement(helper);
19511950
let {top, left} = helper.getBoundingClientRect();
1952-
left = (left - parent.left) / scaleX;
1953-
top = (top - parent.top) / scaleY;
1951+
left -= parent.left;
1952+
top -= parent.top;
19541953
let ui: DDUIData = {position: {top, left}};
19551954

19561955
if (node._temporaryRemoved) {

src/utils.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -373,26 +373,6 @@ export class Utils {
373373
}
374374
}
375375

376-
static getPositionContainerElement(el: HTMLElement): HTMLElement {
377-
if (!el) return null;
378-
379-
const style = getComputedStyle(el);
380-
381-
if (style.position === 'relative' || style.position === 'absolute' || style.position === 'fixed') {
382-
return el;
383-
} else {
384-
return Utils.getPositionContainerElement(el.parentElement);
385-
}
386-
}
387-
388-
static getContainerForPositionFixedElement(el: HTMLElement): HTMLElement {
389-
while (el !== document.documentElement && el.parentElement && getComputedStyle(el as HTMLElement).transform === 'none') {
390-
el = el.parentElement;
391-
}
392-
393-
return el;
394-
}
395-
396376
/** @internal */
397377
static updateScrollPosition(el: HTMLElement, position: {top: number}, distance: number): void {
398378
// is widget in view?
@@ -573,22 +553,6 @@ export class Utils {
573553
(target || e.target).dispatchEvent(simulatedEvent);
574554
}
575555

576-
public static getScaleForElement(element: HTMLElement) {
577-
// Check if element is visible, otherwise the width/height will be of 0
578-
while (element && !element.offsetParent) {
579-
element = element.parentElement;
580-
}
581-
582-
if (!element) {
583-
return { scaleX: 1, scaleY: 1 };
584-
}
585-
586-
const boundingClientRect = element.getBoundingClientRect();
587-
const scaleX = boundingClientRect.width / element.offsetWidth;
588-
const scaleY = boundingClientRect.height / element.offsetHeight;
589-
return { scaleX, scaleY };
590-
}
591-
592556
/** returns true if event is inside the given element rectangle */
593557
// Note: Safari Mac has null event.relatedTarget which causes #1684 so check if DragEvent is inside the coordinates instead
594558
// this.el.contains(event.relatedTarget as HTMLElement)

0 commit comments

Comments
 (0)