Skip to content

Commit 9186189

Browse files
authored
Merge pull request #3250 from danielgindi/feature/full_rtl
implemented full rtl support
2 parents 03f7940 + dc64360 commit 9186189

File tree

6 files changed

+134
-51
lines changed

6 files changed

+134
-51
lines changed

src/dd-draggable.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import { isTouch, touchend, touchmove, touchstart, pointerdown, DDTouch } from '
1212
import { GridHTMLElement } from './gridstack';
1313

1414
interface DragOffset {
15-
left: number;
15+
x: number;
1616
top: number;
1717
width: number;
1818
height: number;
19-
offsetLeft: number;
19+
offsetX: number;
2020
offsetTop: number;
2121
}
2222

@@ -51,7 +51,7 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
5151
/** @internal */
5252
protected helperContainment: HTMLElement;
5353
/** @internal properties we change during dragging, and restore back */
54-
protected static originStyleProp = ['width', 'height', 'transform', 'transform-origin', 'transition', 'pointerEvents', 'position', 'left', 'top', 'minWidth', 'willChange'];
54+
protected static originStyleProp = ['width', 'height', 'transform', 'transform-origin', 'transition', 'pointerEvents', 'position', 'left', 'right', 'top', 'minWidth', 'willChange'];
5555
/** @internal pause before we call the actual drag hit collision code */
5656
protected dragTimeout: number;
5757
/** @internal */
@@ -289,7 +289,10 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
289289
n._origRotate = n._origRotate || { ...n._orig }; // store the real orig size in case we Esc after doing rotation
290290
delete n._moving; // force rotate to happen (move waits for >50% coverage otherwise)
291291
grid.setAnimation(false) // immediate rotate so _getDragOffset() gets the right dom size below
292-
.rotate(n.el, { top: -this.dragOffset.offsetTop, left: -this.dragOffset.offsetLeft })
292+
.rotate(n.el, {
293+
top: -this.dragOffset.offsetTop,
294+
left: -this.dragOffset.offsetX
295+
})
293296
.setAnimation();
294297
n._moving = true;
295298
this.dragOffset = this._getDragOffset(this.lastDrag, n.el, this.helperContainment);
@@ -326,7 +329,7 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
326329
// style.cursor = 'move'; // TODO: can't set with pointerEvents=none ! (no longer in CSS either as no-op)
327330
style.width = this.dragOffset.width + 'px';
328331
style.height = this.dragOffset.height + 'px';
329-
style.willChange = 'left, top';
332+
style.willChange = 'left, right, top';
330333
style.position = 'fixed'; // let us drag between grids by not clipping as parent .grid-stack is position: 'relative'
331334
this._dragFollow(e); // now position it
332335
style.transition = 'none'; // show up instantly
@@ -362,15 +365,18 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
362365

363366
/** @internal updates the top/left position to follow the mouse */
364367
public _dragFollow(e: DragEvent): void {
365-
const containmentRect = { left: 0, top: 0 };
366-
// if (this.helper.style.position === 'absolute') { // we use 'fixed'
367-
// const { left, top } = this.helperContainment.getBoundingClientRect();
368-
// containmentRect = { left, top };
369-
// }
370368
const style = this.helper.style;
371369
const offset = this.dragOffset;
372-
style.left = (e.clientX + offset.offsetLeft - containmentRect.left) * this.dragTransform.xScale + 'px';
373-
style.top = (e.clientY + offset.offsetTop - containmentRect.top) * this.dragTransform.yScale + 'px';
370+
if (this.option.rtl) {
371+
style.right = ((window.innerWidth - e.clientX) + offset.offsetX) * this.dragTransform.xScale + 'px';
372+
if (style.left)
373+
style.left = '';
374+
} else {
375+
style.left = (e.clientX + offset.offsetX) * this.dragTransform.xScale + 'px';
376+
if (style.right)
377+
style.right = '';
378+
}
379+
style.top = (e.clientY + offset.offsetTop) * this.dragTransform.yScale + 'px';
374380
}
375381

376382
/** @internal */
@@ -397,10 +403,15 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
397403
}
398404

399405
const targetOffset = el.getBoundingClientRect();
406+
let x = this.option.rtl ? targetOffset.right : targetOffset.left;
407+
let offsetX = this.option.rtl
408+
? (event.clientX - targetOffset.right + xformOffsetX)
409+
: (-event.clientX + targetOffset.left - xformOffsetX);
410+
400411
return {
401-
left: targetOffset.left,
412+
x,
402413
top: targetOffset.top,
403-
offsetLeft: - event.clientX + targetOffset.left - xformOffsetX,
414+
offsetX,
404415
offsetTop: - event.clientY + targetOffset.top - xformOffsetY,
405416
width: targetOffset.width * this.dragTransform.xScale,
406417
height: targetOffset.height * this.dragTransform.yScale
@@ -473,10 +484,18 @@ export class DDDraggable extends DDBaseImplement implements HTMLElementExtendOpt
473484
const containmentEl = this.el.parentElement;
474485
const containmentRect = containmentEl.getBoundingClientRect();
475486
const offset = this.helper.getBoundingClientRect();
487+
488+
// RTL: GridStack measures column positions from the right side of the container,
489+
// so we report `left` as the distance between the helper's right edge and the
490+
// container's right edge (both in viewport-left coordinates via getBoundingClientRect).
491+
const leftPos = this.option.rtl
492+
? (containmentRect.right - offset.right) * this.dragTransform.xScale
493+
: (offset.left - containmentRect.left) * this.dragTransform.xScale;
494+
476495
return {
477496
position: { //Current CSS position of the helper as { top, left } object
478497
top: (offset.top - containmentRect.top) * this.dragTransform.yScale,
479-
left: (offset.left - containmentRect.left) * this.dragTransform.xScale
498+
left: leftPos
480499
}
481500
/* not used by GridStack for now...
482501
helper: [this.helper], //The object arr representing the helper that's being dragged.

src/dd-gridstack.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ export class DDGridStack {
9696
...{
9797
start: opts.start,
9898
stop: opts.stop,
99-
resize: opts.resize
99+
resize: opts.resize,
100+
rtl: opts.rtl,
100101
}
101102
});
102103
}
@@ -111,6 +112,7 @@ export class DDGridStack {
111112
* @param opts - Drag options or command ('enable', 'disable', 'destroy', 'option', or config object)
112113
* @param key - Option key when using 'option' command
113114
* @param value - Option value when using 'option' command
115+
* @param rtl - Are we in rtl mode?
114116
* @returns this instance for chaining
115117
*
116118
* @example
@@ -133,7 +135,8 @@ export class DDGridStack {
133135
// containment: (grid.parentGridNode && grid.opts.dragOut === false) ? grid.el.parentElement : (grid.opts.draggable.containment || null),
134136
start: opts.start,
135137
stop: opts.stop,
136-
drag: opts.drag
138+
drag: opts.drag,
139+
rtl: opts.rtl,
137140
}
138141
});
139142
}

src/dd-resizable.ts

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { DDResizableHandle } from './dd-resizable-handle';
77
import { DDBaseImplement, HTMLElementExtendOpt } from './dd-base-impl';
88
import { Utils } from './utils';
9-
import { DDResizeOpt, DDUIData, GridItemHTMLElement, GridStackMouseEvent, Rect, Size } from './types';
9+
import { DDResizeOpt, DDUIData, GridItemHTMLElement, GridStackMouseEvent, Size } from './types';
1010
import { DDManager } from './dd-manager';
1111

1212
// import { GridItemHTMLElement } from './types'; let count = 0; // TEST
@@ -22,22 +22,31 @@ export interface DDResizableOpt extends DDResizeOpt {
2222
start?: (event: Event, ui: DDUIData) => void;
2323
stop?: (event: Event) => void;
2424
resize?: (event: Event, ui: DDUIData) => void;
25+
rtl?: boolean;
2526
}
2627

2728
interface RectScaleReciprocal {
2829
x: number;
2930
y: number;
3031
}
3132

33+
interface TemporalRect {
34+
width: number;
35+
height: number;
36+
left: number;
37+
right: number;
38+
top: number;
39+
}
40+
3241
export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt<DDResizableOpt> {
3342
/** @internal */
3443
protected handlers: DDResizableHandle[];
3544
/** @internal */
36-
protected originalRect: Rect;
45+
protected originalRect: DOMRectReadOnly;
3746
/** @internal */
3847
protected rectScale: RectScaleReciprocal = { x: 1, y: 1 };
3948
/** @internal */
40-
protected temporalRect: Rect;
49+
protected temporalRect: TemporalRect;
4150
/** @internal */
4251
protected scrollY: number;
4352
/** @internal */
@@ -51,7 +60,7 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
5160
/** @internal */
5261
protected parentOriginStylePosition: string;
5362
/** @internal */
54-
protected static _originStyleProp = ['width', 'height', 'position', 'left', 'top', 'opacity', 'zIndex'];
63+
protected static _originStyleProp = ['width', 'height', 'position', 'left', 'right', 'top', 'opacity', 'zIndex'];
5564
/** @internal */
5665
protected sizeToContent: boolean;
5766

@@ -185,6 +194,8 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
185194
this._applyChange();
186195
const ev = Utils.initEvent<GridStackMouseEvent>(event, { type: 'resize', target: this.el });
187196
ev.resizeDir = dir; // expose handle direction so _dragOrResize can avoid position drift
197+
ev.hasMovedX = this.option.rtl ? dir.includes('e') : dir.includes('w');
198+
ev.hasMovedY = dir.includes('n');
188199
if (this.option.resize) {
189200
this.option.resize(ev, this._ui());
190201
}
@@ -240,38 +251,51 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
240251
}
241252

242253
/** @internal */
243-
protected _getChange(event: MouseEvent, dir: string): Rect {
254+
protected _getChange(event: MouseEvent, dir: string): TemporalRect {
244255
const oEvent = this.startEvent;
245256
const newRect = { // Note: originalRect is a complex object, not a simple Rect, so copy out.
246257
width: this.originalRect.width,
247258
height: this.originalRect.height + this.scrolled,
248259
left: this.originalRect.left,
249-
top: this.originalRect.top - this.scrolled
260+
right: this.originalRect.right,
261+
top: this.originalRect.top - this.scrolled,
250262
};
251263

252264
const offsetX = event.clientX - oEvent.clientX;
253265
const offsetY = this.sizeToContent ? 0 : event.clientY - oEvent.clientY; // prevent vert resize
254266
let moveLeft: boolean;
255267
let moveUp: boolean;
256268

257-
if (dir.indexOf('e') > -1) {
269+
const isRtl = this.option.rtl;
270+
271+
if (!isRtl && dir.indexOf('e') > -1) {
258272
newRect.width += offsetX;
259-
} else if (dir.indexOf('w') > -1) {
273+
} else if (isRtl && dir.indexOf('w') > -1) {
274+
newRect.width -= offsetX;
275+
} else if (!isRtl && dir.indexOf('w') > -1) {
260276
newRect.width -= offsetX;
261277
newRect.left += offsetX;
262278
moveLeft = true;
279+
} else if (isRtl && dir.indexOf('e') > -1) {
280+
newRect.width += offsetX;
281+
newRect.right += offsetX;
282+
moveLeft = true;
263283
}
284+
264285
if (dir.indexOf('s') > -1) {
265286
newRect.height += offsetY;
266287
} else if (dir.indexOf('n') > -1) {
267288
newRect.height -= offsetY;
268289
newRect.top += offsetY
269290
moveUp = true;
270291
}
292+
271293
const constrain = this._constrainSize(newRect.width, newRect.height, moveLeft, moveUp);
272294
if (Math.round(newRect.width) !== Math.round(constrain.width)) { // round to ignore slight round-off errors
273-
if (dir.indexOf('w') > -1) {
295+
if (!isRtl && dir.indexOf('w') > -1) {
274296
newRect.left += newRect.width - constrain.width;
297+
} else if (isRtl && dir.indexOf('e') > -1) {
298+
newRect.right -= newRect.width - constrain.width;
275299
}
276300
newRect.width = constrain.width;
277301
}
@@ -298,17 +322,29 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
298322

299323
/** @internal */
300324
protected _applyChange(): DDResizable {
301-
let containmentRect = { left: 0, top: 0, width: 0, height: 0 };
325+
let containmentRect = { left: 0, right: 0, top: 0, width: 0, height: 0 };
302326
if (this.el.style.position === 'absolute') {
303327
const containmentEl = this.el.parentElement;
304-
const { left, top } = containmentEl.getBoundingClientRect();
305-
containmentRect = { left, top, width: 0, height: 0 };
328+
const { left, right, top } = containmentEl.getBoundingClientRect();
329+
containmentRect = { left, right, top, width: 0, height: 0 };
306330
}
307331
if (!this.temporalRect) return this;
308-
Object.keys(this.temporalRect).forEach(key => {
309-
const value = this.temporalRect[key];
310-
const scaleReciprocal = key === 'width' || key === 'left' ? this.rectScale.x : key === 'height' || key === 'top' ? this.rectScale.y : 1;
311-
this.el.style[key] = (value - containmentRect[key]) * scaleReciprocal + 'px';
332+
Object.entries(this.temporalRect).forEach(([key, value]) => {
333+
if (this.option.rtl ? key === 'left' : key === 'right')
334+
return;
335+
336+
const scaleReciprocal = key === 'width' || key === 'left' || key === 'right'
337+
? this.rectScale.x
338+
: key === 'height' || key === 'top'
339+
? this.rectScale.y
340+
: 1;
341+
let finalValue: string;
342+
if (key === 'right') {
343+
finalValue = (containmentRect.right - value) * this.rectScale.x + 'px';
344+
} else {
345+
finalValue = (value - containmentRect[key]) * scaleReciprocal + 'px';
346+
}
347+
this.el.style[key] = finalValue;
312348
});
313349
return this;
314350
}
@@ -328,12 +364,17 @@ export class DDResizable extends DDBaseImplement implements HTMLElementExtendOpt
328364
width: this.originalRect.width,
329365
height: this.originalRect.height + this.scrolled,
330366
left: this.originalRect.left,
367+
right: this.originalRect.right,
331368
top: this.originalRect.top - this.scrolled
332369
};
333370
const rect = this.temporalRect || newRect;
371+
372+
const leftPos = this.option.rtl
373+
? (containmentRect.right - rect.right) * this.rectScale.x
374+
: (rect.left - containmentRect.left) * this.rectScale.x;
334375
return {
335376
position: {
336-
left: (rect.left - containmentRect.left) * this.rectScale.x,
377+
left: leftPos,
337378
top: (rect.top - containmentRect.top) * this.rectScale.y
338379
},
339380
size: {

src/gridstack.scss

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
.grid-stack > .grid-stack-item {
2929
position: absolute;
3030
padding: 0;
31-
top: 0; left: 0; // some default to reduce at least first row/column inline styles
31+
top: 0; // some default to reduce at least first row inline styles
3232
width: var(--gs-column-width); // reduce 1x1 items inline styles
3333
height: var(--gs-cell-height);
3434

@@ -43,9 +43,16 @@
4343
overflow-y: hidden;
4444
}
4545
}
46-
46+
47+
.grid-stack:not(.grid-stack-rtl) > .grid-stack-item {
48+
left: 0; // some default to reduce at least first column inline styles
49+
}
50+
.grid-stack.grid-stack-rtl > .grid-stack-item {
51+
right: 0; // some default to reduce at least first column inline styles
52+
}
53+
4754
.grid-stack {
48-
> .grid-stack-item > .grid-stack-item-content,
55+
> .grid-stack-item > .grid-stack-item-content,
4956
> .grid-stack-placeholder > .placeholder-content {
5057
top: var(--gs-item-margin-top);
5158
right: var(--gs-item-margin-right);
@@ -105,7 +112,7 @@
105112
}
106113

107114
&.ui-draggable-dragging {
108-
will-change: left, top;
115+
will-change: left, right, top;
109116
}
110117

111118
&.ui-resizable-resizing {
@@ -126,21 +133,22 @@
126133

127134
.grid-stack-animate,
128135
.grid-stack-animate .grid-stack-item {
129-
transition: left $animation_speed, top $animation_speed, height $animation_speed, width $animation_speed;
136+
transition: left $animation_speed, right $animation_speed, top $animation_speed, height $animation_speed, width $animation_speed;
130137
}
131138

132139
.grid-stack-animate .grid-stack-item.ui-draggable-dragging,
133140
.grid-stack-animate .grid-stack-item.ui-resizable-resizing,
134141
.grid-stack-animate .grid-stack-item.grid-stack-placeholder{
135-
transition: left 0s, top 0s, height 0s, width 0s;
142+
transition: left 0s, right 0s, top 0s, height 0s, width 0s;
136143
}
137144

138145
// make those more unique as to not conflict with side panel items, but apply to all column layouts (so not in loop below)
139146
.grid-stack > .grid-stack-item[gs-y="0"] {
140147
top: 0px;
141148
}
142-
.grid-stack > .grid-stack-item[gs-x="0"] {
149+
.grid-stack:not(.grid-stack-rtl) > .grid-stack-item[gs-x="0"] {
143150
left: 0%;
144151
}
145-
146-
152+
.grid-stack.grid-stack-rtl > .grid-stack-item[gs-x="0"] {
153+
right: 0%;
154+
}

0 commit comments

Comments
 (0)