Skip to content

Commit bbfcb60

Browse files
feat: keepInViewport enhancements (#11199)
* adjust position when opening or resizing * constrain dialog size to viewport * handle viewport resize * do not lock position if dialog is center positioned * adjust position when moving programmatically * move property, update JSDoc * Update packages/dialog/test/dialog.test.js Co-authored-by: Diego Cardoso <diego@vaadin.com> * improve viewport cleanup --------- Co-authored-by: Diego Cardoso <diego@vaadin.com>
1 parent dc9acfc commit bbfcb60

11 files changed

+327
-31
lines changed

packages/dialog/src/styles/vaadin-dialog-overlay-base-styles.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ const dialogResizableOverlay = css`
124124
125125
:host([has-bounds-set]) [part='overlay'] {
126126
min-width: 0;
127+
}
128+
129+
:host([has-bounds-set]:not([keep-in-viewport])) [part='overlay'] {
127130
max-width: none;
128131
max-height: none;
129132
}

packages/dialog/src/vaadin-dialog-base-mixin.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,14 @@ export declare class DialogBaseMixinClass {
6363
* additional spacing, which can be overridden by the theme.
6464
*/
6565
left: string;
66+
67+
/**
68+
* Set to true to prevent the dialog from moving outside the viewport bounds.
69+
* When enabled, all four edges of the dialog will remain visible, for example
70+
* when dragging the dialog or when the viewport is resized. Note that the
71+
* dialog will also adjust any programmatically configured size and position
72+
* so that it stays within the viewport.
73+
* @attr {boolean} keep-in-viewport
74+
*/
75+
keepInViewport: boolean;
6676
}

packages/dialog/src/vaadin-dialog-base-mixin.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,21 @@ export const DialogBaseMixin = (superClass) =>
9393
overlayRole: {
9494
type: String,
9595
},
96+
97+
/**
98+
* Set to true to prevent the dialog from moving outside the viewport bounds.
99+
* When enabled, all four edges of the dialog will remain visible, for example
100+
* when dragging the dialog or when the viewport is resized. Note that the
101+
* dialog will also adjust any programmatically configured size and position
102+
* so that it stays within the viewport.
103+
* @attr {boolean} keep-in-viewport
104+
* @type {boolean}
105+
*/
106+
keepInViewport: {
107+
type: Boolean,
108+
value: false,
109+
reflectToAttribute: true,
110+
},
96111
};
97112
}
98113

packages/dialog/src/vaadin-dialog-draggable-mixin.d.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,4 @@ export declare class DialogDraggableMixinClass {
2121
* "`draggable-leaf-only`" class name.
2222
*/
2323
draggable: boolean;
24-
25-
/**
26-
* Set to true to prevent dragging the dialog outside the viewport bounds.
27-
* When enabled, all four edges of the dialog will remain visible during dragging.
28-
* The dialog may still become partially hidden when the viewport is resized.
29-
* @attr {boolean} keep-in-viewport
30-
*/
31-
keepInViewport: boolean;
3224
}

packages/dialog/src/vaadin-dialog-draggable-mixin.js

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,6 @@ export const DialogDraggableMixin = (superClass) =>
3131
reflectToAttribute: true,
3232
},
3333

34-
/**
35-
* Set to true to prevent dragging the dialog outside the viewport bounds.
36-
* When enabled, all four edges of the dialog will remain visible during dragging.
37-
* The dialog may still become partially hidden when the viewport is resized.
38-
* @attr {boolean} keep-in-viewport
39-
* @type {boolean}
40-
*/
41-
keepInViewport: {
42-
type: Boolean,
43-
value: false,
44-
reflectToAttribute: true,
45-
},
46-
4734
/** @private */
4835
_touchDevice: {
4936
type: Boolean,

packages/dialog/src/vaadin-dialog-overlay-mixin.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export declare class DialogOverlayMixinClass {
2525
*/
2626
headerTitle: string;
2727

28+
/**
29+
* Whether to keep the overlay within the viewport.
30+
*/
31+
keepInViewport: boolean;
32+
2833
/**
2934
* Custom function for rendering the dialog header.
3035
*/

packages/dialog/src/vaadin-dialog-overlay-mixin.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ export const DialogOverlayMixin = (superClass) =>
3535
footerRenderer: {
3636
type: Object,
3737
},
38+
39+
/**
40+
* Whether to keep the overlay within the viewport.
41+
*/
42+
keepInViewport: {
43+
type: Boolean,
44+
reflectToAttribute: true,
45+
},
3846
};
3947
}
4048

@@ -77,6 +85,7 @@ export const DialogOverlayMixin = (superClass) =>
7785
// Update overflow attribute on resize
7886
this.__resizeObserver = new ResizeObserver(() => {
7987
requestAnimationFrame(() => {
88+
this.__adjustPosition();
8089
this.__updateOverflow();
8190
});
8291
});
@@ -104,6 +113,20 @@ export const DialogOverlayMixin = (superClass) =>
104113
setOverlayStateAttribute(this, 'has-footer', currentNodes.length > 0);
105114
this.__updateOverflow();
106115
});
116+
117+
this.__handleWindowResize = this.__handleWindowResize.bind(this);
118+
}
119+
120+
updated(props) {
121+
super.updated(props);
122+
123+
if (props.has('opened') || props.has('keepInViewport')) {
124+
if (this.opened && this.keepInViewport) {
125+
window.addEventListener('resize', this.__handleWindowResize);
126+
} else {
127+
window.removeEventListener('resize', this.__handleWindowResize);
128+
}
129+
}
107130
}
108131

109132
/** @private */
@@ -233,6 +256,15 @@ export const DialogOverlayMixin = (superClass) =>
233256
return { top, left, width, height };
234257
}
235258

259+
/**
260+
* Override method from OverlayMixin to adjust the position of the overlay if `keepInViewport` is true.
261+
* @override
262+
*/
263+
setBounds(bounds, absolute = true) {
264+
super.setBounds(bounds, absolute);
265+
this.__adjustPosition();
266+
}
267+
236268
/** @private */
237269
__updateOverflow() {
238270
let overflow = '';
@@ -254,4 +286,45 @@ export const DialogOverlayMixin = (superClass) =>
254286
setOverlayStateAttribute(this, 'overflow', null);
255287
}
256288
}
289+
290+
/** @private */
291+
__handleWindowResize() {
292+
this.__adjustPosition();
293+
}
294+
295+
/**
296+
* Adjusts the position of the overlay to keep it within the viewport if `keepInViewport` is true.
297+
* @private
298+
*/
299+
__adjustPosition() {
300+
if (!this.opened || !this.keepInViewport) {
301+
return;
302+
}
303+
304+
// Centered dialogs do not use absolute positioning and automatically adjust their position / size to fit the viewport
305+
const style = getComputedStyle(this.$.overlay);
306+
if (style.position !== 'absolute') {
307+
return;
308+
}
309+
310+
const overlayHostBounds = this.getBoundingClientRect();
311+
const bounds = this.getBounds();
312+
// Prefer dimensions from getComputedStyle, as bounding rect is affected
313+
// by scale transform applied by opening animation in Lumo
314+
const width = parseFloat(style.width) || bounds.width;
315+
const height = parseFloat(style.height) || bounds.height;
316+
317+
const maxLeft = overlayHostBounds.right - overlayHostBounds.left - width;
318+
const maxTop = overlayHostBounds.bottom - overlayHostBounds.top - height;
319+
320+
if (bounds.left > maxLeft || bounds.top > maxTop) {
321+
const left = Math.max(0, Math.min(bounds.left, maxLeft));
322+
const top = Math.max(0, Math.min(bounds.top, maxTop));
323+
324+
Object.assign(this.$.overlay.style, {
325+
left: `${left}px`,
326+
top: `${top}px`,
327+
});
328+
}
329+
}
257330
};

packages/dialog/src/vaadin-dialog.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class Dialog extends DialogSizeMixin(
148148
.renderer="${this.renderer}"
149149
.headerRenderer="${this.headerRenderer}"
150150
.footerRenderer="${this.footerRenderer}"
151+
.keepInViewport="${this.keepInViewport}"
151152
@opened-changed="${this._onOverlayOpened}"
152153
@mousedown="${this._bringOverlayToFront}"
153154
@touchstart="${this._bringOverlayToFront}"

0 commit comments

Comments
 (0)