Skip to content

Commit

Permalink
feat: add API to define Dialog position programmatically (#7971)
Browse files Browse the repository at this point in the history
  • Loading branch information
DiegoCardoso authored Oct 28, 2024
1 parent 4661523 commit 2ce786e
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 12 deletions.
20 changes: 20 additions & 0 deletions packages/dialog/src/vaadin-dialog-base-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,24 @@ export declare class DialogBaseMixinClass {
* @attr {string} overlay-role
*/
overlayRole: string;

/**
* Set the distance of the overlay from the top of its container.
* If a unitless number is provided, pixels are assumed.
*
* Note that the overlay top edge may not be the same as the viewport
* top edge (e.g. the Lumo theme defines some spacing to prevent the
* overlay from stretching all the way to the top of the viewport).
*/
top: string;

/**
* Set the distance of the overlay from the left of its container.
* If a unitless number is provided, pixels are assumed.
*
* Note that the overlay left edge may not be the same as the viewport
* left edge (e.g. the Lumo theme defines some spacing to prevent the
* overlay from stretching all the way to the left of the viewport).
*/
left: string;
}
33 changes: 33 additions & 0 deletions packages/dialog/src/vaadin-dialog-base-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ export const DialogBaseMixin = (superClass) =>
value: false,
},

/**
* Set the distance of the overlay from the top of its container.
* If a unitless number is provided, pixels are assumed.
*
* Note that the overlay top edge may not be the same as the viewport
* top edge (e.g. the Lumo theme defines some spacing to prevent the
* overlay from stretching all the way to the top of the viewport).
*/
top: {
type: String,
},

/**
* Set the distance of the overlay from the left of its container.
* If a unitless number is provided, pixels are assumed.
*
* Note that the overlay left edge may not be the same as the viewport
* left edge (e.g. the Lumo theme defines some spacing to prevent the
* overlay from stretching all the way to the left of the viewport).
*/
left: {
type: String,
},

/**
* The `role` attribute value to be set on the overlay. Defaults to "dialog".
*
Expand All @@ -62,6 +86,10 @@ export const DialogBaseMixin = (superClass) =>
};
}

static get observers() {
return ['__positionChanged(top, left)'];
}

/** @protected */
ready() {
super.ready();
Expand Down Expand Up @@ -137,6 +165,11 @@ export const DialogBaseMixin = (superClass) =>
}
}

/** @private */
__positionChanged(top, left) {
this.$.overlay.setBounds({ top, left });
}

/**
* Fired when the dialog is closed.
*
Expand Down
3 changes: 2 additions & 1 deletion packages/dialog/src/vaadin-dialog-draggable-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export const DialogDraggableMixin = (superClass) =>
if (eventInWindow(event)) {
const top = this._originalBounds.top + (event.pageY - this._originalMouseCoords.top);
const left = this._originalBounds.left + (event.pageX - this._originalMouseCoords.left);
this.$.overlay.setBounds({ top, left });
this.top = top;
this.left = left;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/dialog/src/vaadin-dialog-overlay-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export const DialogOverlayMixin = (superClass) =>
}

Object.keys(parsedBounds).forEach((arg) => {
if (typeof parsedBounds[arg] === 'number') {
if (!isNaN(parsedBounds[arg])) {
parsedBounds[arg] = `${parsedBounds[arg]}px`;
}
});
Expand Down
48 changes: 48 additions & 0 deletions packages/dialog/test/dialog.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,4 +262,52 @@ describe('vaadin-dialog', () => {
expect(getDeepActiveElement()).to.equal(button);
});
});

describe('position', () => {
let dialog, overlay;

beforeEach(async () => {
dialog = fixtureSync('<vaadin-dialog></vaadin-dialog>');
await nextRender();
overlay = dialog.$.overlay;
});

afterEach(async () => {
dialog.opened = false;
await nextRender();
});

it('should default to px unit when unitless values are provided', async () => {
dialog.opened = true;
await nextRender();
dialog.left = 100;
dialog.top = 200;
await nextRender();
expect(overlay.$.overlay.style.position).to.equal('absolute');
expect(overlay.$.overlay.style.top).to.equal('200px');
expect(overlay.$.overlay.style.left).to.equal('100px');
});

it('should allow setting position with units', async () => {
dialog.opened = true;
await nextRender();
dialog.left = '100px';
dialog.top = '10em';
await nextRender();
expect(overlay.$.overlay.style.position).to.equal('absolute');
expect(overlay.$.overlay.style.top).to.equal('10em');
expect(overlay.$.overlay.style.left).to.equal('100px');
});

it('should allow setting position through attribute', async () => {
dialog.opened = true;
await nextRender();
dialog.setAttribute('left', 100);
dialog.setAttribute('top', 200);
await nextRender();
expect(overlay.$.overlay.style.position).to.equal('absolute');
expect(overlay.$.overlay.style.top).to.equal('200px');
expect(overlay.$.overlay.style.left).to.equal('100px');
});
});
});
39 changes: 29 additions & 10 deletions packages/dialog/test/draggable-resizable.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,22 +370,25 @@ describe('draggable', () => {
dx = 100;
});

it('should drag and move dialog if mousedown on .resizer-container', () => {
it('should drag and move dialog if mousedown on .resizer-container', async () => {
drag(container);
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
});

it('should drag and move dialog if mousedown on [part="content"]', () => {
it('should drag and move dialog if mousedown on [part="content"]', async () => {
drag(content);
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
});

it('should drag and move dialog if mousedown on element with [class="draggable"]', () => {
it('should drag and move dialog if mousedown on element with [class="draggable"]', async () => {
drag(dialog.$.overlay.querySelector('.draggable'));
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
Expand All @@ -401,8 +404,9 @@ describe('draggable', () => {
expect(style.left).to.be.ok;
});

it('should drag and move dialog if mousedown on element with [class="draggable"] in another shadow root', () => {
it('should drag and move dialog if mousedown on element with [class="draggable"] in another shadow root', async () => {
drag(dialog.$.overlay.querySelector('internally-draggable').shadowRoot.querySelector('.draggable'));
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
Expand All @@ -418,52 +422,57 @@ describe('draggable', () => {
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left));
});

it('should drag by a draggable-leaf-only if it is directly the dragged element', () => {
it('should drag by a draggable-leaf-only if it is directly the dragged element', async () => {
const draggable = dialog.$.overlay.querySelector('internally-draggable').shadowRoot.querySelector('.draggable');
draggable.classList.add('draggable-leaf-only');
drag(draggable);
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
});

it('should drag by a draggable-leaf-only child if it is marked as draggable', () => {
it('should drag by a draggable-leaf-only child if it is marked as draggable', async () => {
const draggable = dialog.$.overlay.querySelector('internally-draggable').shadowRoot.querySelector('.draggable');
draggable.classList.add('draggable-leaf-only');
const child = draggable.firstElementChild;
child.classList.add('draggable');
drag(child);
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
});

it('should drag by a draggable-leaf-only child if it is marked as draggable-leaf-only', () => {
it('should drag by a draggable-leaf-only child if it is marked as draggable-leaf-only', async () => {
const draggable = dialog.$.overlay.querySelector('internally-draggable').shadowRoot.querySelector('.draggable');
draggable.classList.add('draggable-leaf-only');
const child = draggable.firstElementChild;
child.classList.add('draggable-leaf-only');
drag(child);
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
});

it('should drag by a child of a draggable node ', () => {
it('should drag by a child of a draggable node ', async () => {
const draggable = dialog.$.overlay.querySelector('internally-draggable').shadowRoot.querySelector('.draggable');
const child = draggable.firstElementChild;
drag(child);
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
});

it('should drag and move dialog after resizing', () => {
it('should drag and move dialog after resizing', async () => {
resize(container.querySelector('.s'), 0, dx);
const bounds = container.getBoundingClientRect();
const coords = { y: bounds.top + bounds.height / 2, x: bounds.left + bounds.width / 2 };
const target = dialog.$.overlay.shadowRoot.elementFromPoint(coords.x, coords.y);
drag(target);
await nextRender();
const draggedBounds = container.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + dx));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + dx));
Expand Down Expand Up @@ -540,6 +549,15 @@ describe('draggable', () => {
drag(container);
expect(container.scrollTop).to.equal(100);
});

it('should update "top" and "left" properties on drag', async () => {
drag(container);
await nextRender();
const overlay = dialog.$.overlay.$.overlay;
const bounds = overlay.getBoundingClientRect();
expect(dialog.top).to.be.equal(bounds.top);
expect(dialog.left).to.be.equal(bounds.left);
});
});

describe('touch', () => {
Expand Down Expand Up @@ -672,10 +690,11 @@ describe('touch', () => {
);
});

it('should drag and move dialog', () => {
it('should drag and move dialog', async () => {
const d = 1;
const bounds = draggableContainer.getBoundingClientRect();
touchDrag(draggableContainer, d, d);
await nextRender();
const draggedBounds = draggableContainer.getBoundingClientRect();
expect(Math.floor(draggedBounds.top)).to.be.eql(Math.floor(bounds.top + d));
expect(Math.floor(draggedBounds.left)).to.be.eql(Math.floor(bounds.left + d));
Expand Down

0 comments on commit 2ce786e

Please sign in to comment.