@vaadin/overlay
Advanced tools
Comparing version 24.2.0-alpha1 to 24.2.0-alpha10
{ | ||
"name": "@vaadin/overlay", | ||
"version": "24.2.0-alpha1", | ||
"version": "24.2.0-alpha10", | ||
"publishConfig": { | ||
@@ -24,2 +24,4 @@ "access": "public" | ||
"src", | ||
"!src/vaadin-lit-overlay.d.ts", | ||
"!src/vaadin-lit-overlay.js", | ||
"theme", | ||
@@ -40,15 +42,15 @@ "vaadin-*.d.ts", | ||
"@polymer/polymer": "^3.0.0", | ||
"@vaadin/a11y-base": "24.2.0-alpha1", | ||
"@vaadin/component-base": "24.2.0-alpha1", | ||
"@vaadin/vaadin-lumo-styles": "24.2.0-alpha1", | ||
"@vaadin/vaadin-material-styles": "24.2.0-alpha1", | ||
"@vaadin/vaadin-themable-mixin": "24.2.0-alpha1" | ||
"@vaadin/a11y-base": "24.2.0-alpha10", | ||
"@vaadin/component-base": "24.2.0-alpha10", | ||
"@vaadin/vaadin-lumo-styles": "24.2.0-alpha10", | ||
"@vaadin/vaadin-material-styles": "24.2.0-alpha10", | ||
"@vaadin/vaadin-themable-mixin": "24.2.0-alpha10" | ||
}, | ||
"devDependencies": { | ||
"@esm-bundle/chai": "^4.3.4", | ||
"@vaadin/testing-helpers": "^0.4.2", | ||
"@vaadin/testing-helpers": "^0.5.0", | ||
"lit": "^2.0.0", | ||
"sinon": "^13.0.2" | ||
}, | ||
"gitHead": "0dbb118320203ab6c0c07450a3e718815367589f" | ||
"gitHead": "ca16b5f88b00ae05fb6d7c7e9874525048e389f0" | ||
} |
@@ -6,8 +6,7 @@ /** | ||
*/ | ||
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js'; | ||
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js'; | ||
import { OverlayMixin } from './vaadin-overlay-mixin.js'; | ||
export type OverlayRenderer = (root: HTMLElement, owner: HTMLElement, model?: object) => void; | ||
export { OverlayRenderer } from './vaadin-overlay-mixin.js'; | ||
@@ -124,55 +123,3 @@ /** | ||
*/ | ||
declare class Overlay extends OverlayFocusMixin(ThemableMixin(DirMixin(HTMLElement))) { | ||
/** | ||
* When true, the overlay is visible and attached to body. | ||
*/ | ||
opened: boolean | null | undefined; | ||
/** | ||
* Owner element passed with renderer function | ||
*/ | ||
owner: HTMLElement | null; | ||
/** | ||
* Custom function for rendering the content of the overlay. | ||
* Receives three arguments: | ||
* | ||
* - `root` The root container DOM element. Append your content to it. | ||
* - `owner` The host element of the renderer function. | ||
* - `model` The object with the properties related with rendering. | ||
*/ | ||
renderer: OverlayRenderer | null | undefined; | ||
/** | ||
* When true the overlay has backdrop on top of content when opened. | ||
*/ | ||
withBackdrop: boolean; | ||
/** | ||
* Object with properties that is passed to `renderer` function | ||
*/ | ||
model: object | null | undefined; | ||
/** | ||
* When true the overlay won't disable the main content, showing | ||
* it doesn't change the functionality of the user interface. | ||
*/ | ||
modeless: boolean; | ||
/** | ||
* When set to true, the overlay is hidden. This also closes the overlay | ||
* immediately in case there is a closing animation in progress. | ||
*/ | ||
hidden: boolean; | ||
close(sourceEvent?: Event | null): void; | ||
/** | ||
* Requests an update for the content of the overlay. | ||
* While performing the update, it invokes the renderer passed in the `renderer` property. | ||
* | ||
* It is not guaranteed that the update happens immediately (synchronously) after it is requested. | ||
*/ | ||
requestContentUpdate(): void; | ||
declare class Overlay extends OverlayMixin(ThemableMixin(DirMixin(HTMLElement))) { | ||
addEventListener<K extends keyof OverlayEventMap>( | ||
@@ -189,10 +136,2 @@ type: K, | ||
): void; | ||
protected _flushAnimation(type: 'closing' | 'opening'): void; | ||
/** | ||
* Whether to close the overlay on outside click or not. | ||
* Override this method to customize the closing logic. | ||
*/ | ||
protected _shouldCloseOnOutsideClick(event: Event): boolean; | ||
} | ||
@@ -199,0 +138,0 @@ |
@@ -6,11 +6,11 @@ /** | ||
*/ | ||
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js'; | ||
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
import { isIOS } from '@vaadin/component-base/src/browser-utils.js'; | ||
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; | ||
import { processTemplates } from '@vaadin/component-base/src/templates.js'; | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { OverlayFocusMixin } from './vaadin-overlay-focus-mixin.js'; | ||
import { OverlayStackMixin } from './vaadin-overlay-stack-mixin.js'; | ||
import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { OverlayMixin } from './vaadin-overlay-mixin.js'; | ||
import { overlayStyles } from './vaadin-overlay-styles.js'; | ||
registerStyles('vaadin-overlay', overlayStyles, { moduleId: 'vaadin-overlay-styles' }); | ||
/** | ||
@@ -78,72 +78,7 @@ * `<vaadin-overlay>` is a Web Component for creating overlays. The content of the overlay | ||
* @mixes DirMixin | ||
* @mixes OverlayFocusMixin | ||
* @mixes OverlayMixin | ||
*/ | ||
class Overlay extends OverlayStackMixin(OverlayFocusMixin(ThemableMixin(DirMixin(PolymerElement)))) { | ||
class Overlay extends OverlayMixin(ThemableMixin(DirMixin(PolymerElement))) { | ||
static get template() { | ||
return html` | ||
<style> | ||
:host { | ||
z-index: 200; | ||
position: fixed; | ||
/* Despite of what the names say, <vaadin-overlay> is just a container | ||
for position/sizing/alignment. The actual overlay is the overlay part. */ | ||
/* Default position constraints: the entire viewport. Note: themes can | ||
override this to introduce gaps between the overlay and the viewport. */ | ||
top: 0; | ||
right: 0; | ||
bottom: var(--vaadin-overlay-viewport-bottom); | ||
left: 0; | ||
/* Use flexbox alignment for the overlay part. */ | ||
display: flex; | ||
flex-direction: column; /* makes dropdowns sizing easier */ | ||
/* Align to center by default. */ | ||
align-items: center; | ||
justify-content: center; | ||
/* Allow centering when max-width/max-height applies. */ | ||
margin: auto; | ||
/* The host is not clickable, only the overlay part is. */ | ||
pointer-events: none; | ||
/* Remove tap highlight on touch devices. */ | ||
-webkit-tap-highlight-color: transparent; | ||
/* CSS API for host */ | ||
--vaadin-overlay-viewport-bottom: 0; | ||
} | ||
:host([hidden]), | ||
:host(:not([opened]):not([closing])) { | ||
display: none !important; | ||
} | ||
[part='overlay'] { | ||
-webkit-overflow-scrolling: touch; | ||
overflow: auto; | ||
pointer-events: auto; | ||
/* Prevent overflowing the host in MSIE 11 */ | ||
max-width: 100%; | ||
box-sizing: border-box; | ||
-webkit-tap-highlight-color: initial; /* reenable tap highlight inside */ | ||
} | ||
[part='backdrop'] { | ||
z-index: -1; | ||
content: ''; | ||
background: rgba(0, 0, 0, 0.5); | ||
position: fixed; | ||
top: 0; | ||
left: 0; | ||
bottom: 0; | ||
right: 0; | ||
pointer-events: auto; | ||
} | ||
</style> | ||
<div id="backdrop" part="backdrop" hidden$="[[!withBackdrop]]"></div> | ||
@@ -162,110 +97,2 @@ <div part="overlay" id="overlay" tabindex="0"> | ||
static get properties() { | ||
return { | ||
/** | ||
* When true, the overlay is visible and attached to body. | ||
*/ | ||
opened: { | ||
type: Boolean, | ||
notify: true, | ||
observer: '_openedChanged', | ||
reflectToAttribute: true, | ||
}, | ||
/** | ||
* Owner element passed with renderer function | ||
* @type {HTMLElement} | ||
*/ | ||
owner: Element, | ||
/** | ||
* Custom function for rendering the content of the overlay. | ||
* Receives three arguments: | ||
* | ||
* - `root` The root container DOM element. Append your content to it. | ||
* - `owner` The host element of the renderer function. | ||
* - `model` The object with the properties related with rendering. | ||
* @type {OverlayRenderer | null | undefined} | ||
*/ | ||
renderer: Function, | ||
/** | ||
* When true the overlay has backdrop on top of content when opened. | ||
* @type {boolean} | ||
*/ | ||
withBackdrop: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true, | ||
}, | ||
/** | ||
* Object with properties that is passed to `renderer` function | ||
*/ | ||
model: Object, | ||
/** | ||
* When true the overlay won't disable the main content, showing | ||
* it doesn't change the functionality of the user interface. | ||
* @type {boolean} | ||
*/ | ||
modeless: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true, | ||
observer: '_modelessChanged', | ||
}, | ||
/** | ||
* When set to true, the overlay is hidden. This also closes the overlay | ||
* immediately in case there is a closing animation in progress. | ||
* @type {boolean} | ||
*/ | ||
hidden: { | ||
type: Boolean, | ||
reflectToAttribute: true, | ||
observer: '_hiddenChanged', | ||
}, | ||
/** @private */ | ||
_mouseDownInside: { | ||
type: Boolean, | ||
}, | ||
/** @private */ | ||
_mouseUpInside: { | ||
type: Boolean, | ||
}, | ||
/** @private */ | ||
_oldOwner: Element, | ||
/** @private */ | ||
_oldModel: Object, | ||
/** @private */ | ||
_oldRenderer: Object, | ||
/** @private */ | ||
_oldOpened: Boolean, | ||
}; | ||
} | ||
static get observers() { | ||
return ['_rendererOrDataChanged(renderer, owner, model, opened)']; | ||
} | ||
constructor() { | ||
super(); | ||
this._boundMouseDownListener = this._mouseDownListener.bind(this); | ||
this._boundMouseUpListener = this._mouseUpListener.bind(this); | ||
this._boundOutsideClickListener = this._outsideClickListener.bind(this); | ||
this._boundKeydownListener = this._keydownListener.bind(this); | ||
/* c8 ignore next 3 */ | ||
if (isIOS) { | ||
this._boundIosResizeListener = () => this._detectIosNavbar(); | ||
} | ||
} | ||
/** @protected */ | ||
@@ -275,363 +102,6 @@ ready() { | ||
// Need to add dummy click listeners to this and the backdrop or else | ||
// the document click event listener (_outsideClickListener) may never | ||
// get invoked on iOS Safari (reproducible in <vaadin-dialog> | ||
// and <vaadin-context-menu>). | ||
this.addEventListener('click', () => {}); | ||
this.$.backdrop.addEventListener('click', () => {}); | ||
processTemplates(this); | ||
} | ||
/** @private */ | ||
_detectIosNavbar() { | ||
/* c8 ignore next 15 */ | ||
if (!this.opened) { | ||
return; | ||
} | ||
const innerHeight = window.innerHeight; | ||
const innerWidth = window.innerWidth; | ||
const landscape = innerWidth > innerHeight; | ||
const clientHeight = document.documentElement.clientHeight; | ||
if (landscape && clientHeight > innerHeight) { | ||
this.style.setProperty('--vaadin-overlay-viewport-bottom', `${clientHeight - innerHeight}px`); | ||
} else { | ||
this.style.setProperty('--vaadin-overlay-viewport-bottom', '0'); | ||
} | ||
} | ||
/** | ||
* @param {Event=} sourceEvent | ||
*/ | ||
close(sourceEvent) { | ||
const evt = new CustomEvent('vaadin-overlay-close', { | ||
bubbles: true, | ||
cancelable: true, | ||
detail: { sourceEvent }, | ||
}); | ||
this.dispatchEvent(evt); | ||
if (!evt.defaultPrevented) { | ||
this.opened = false; | ||
} | ||
} | ||
/** @protected */ | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
/* c8 ignore next 3 */ | ||
if (this._boundIosResizeListener) { | ||
this._detectIosNavbar(); | ||
window.addEventListener('resize', this._boundIosResizeListener); | ||
} | ||
} | ||
/** @protected */ | ||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
/* c8 ignore next 3 */ | ||
if (this._boundIosResizeListener) { | ||
window.removeEventListener('resize', this._boundIosResizeListener); | ||
} | ||
} | ||
/** | ||
* Requests an update for the content of the overlay. | ||
* While performing the update, it invokes the renderer passed in the `renderer` property. | ||
* | ||
* It is not guaranteed that the update happens immediately (synchronously) after it is requested. | ||
*/ | ||
requestContentUpdate() { | ||
if (this.renderer) { | ||
this.renderer.call(this.owner, this, this.owner, this.model); | ||
} | ||
} | ||
/** @private */ | ||
_mouseDownListener(event) { | ||
this._mouseDownInside = event.composedPath().indexOf(this.$.overlay) >= 0; | ||
} | ||
/** @private */ | ||
_mouseUpListener(event) { | ||
this._mouseUpInside = event.composedPath().indexOf(this.$.overlay) >= 0; | ||
} | ||
/** | ||
* Whether to close the overlay on outside click or not. | ||
* Override this method to customize the closing logic. | ||
* | ||
* @param {Event} _event | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
_shouldCloseOnOutsideClick(_event) { | ||
return this._last; | ||
} | ||
/** | ||
* Outside click listener used in capture phase to close the overlay before | ||
* propagating the event to the listener on the element that triggered it. | ||
* Otherwise, calling `open()` would result in closing and re-opening. | ||
* | ||
* @private | ||
*/ | ||
_outsideClickListener(event) { | ||
if (event.composedPath().includes(this.$.overlay) || this._mouseDownInside || this._mouseUpInside) { | ||
this._mouseDownInside = false; | ||
this._mouseUpInside = false; | ||
return; | ||
} | ||
if (!this._shouldCloseOnOutsideClick(event)) { | ||
return; | ||
} | ||
const evt = new CustomEvent('vaadin-overlay-outside-click', { | ||
bubbles: true, | ||
cancelable: true, | ||
detail: { sourceEvent: event }, | ||
}); | ||
this.dispatchEvent(evt); | ||
if (this.opened && !evt.defaultPrevented) { | ||
this.close(event); | ||
} | ||
} | ||
/** | ||
* Listener used to close whe overlay on Escape press, if it is the last one. | ||
* @private | ||
*/ | ||
_keydownListener(event) { | ||
if (!this._last) { | ||
return; | ||
} | ||
// Only close modeless overlay on Esc press when it contains focus | ||
if (this.modeless && !event.composedPath().includes(this.$.overlay)) { | ||
return; | ||
} | ||
if (event.key === 'Escape') { | ||
const evt = new CustomEvent('vaadin-overlay-escape-press', { | ||
bubbles: true, | ||
cancelable: true, | ||
detail: { sourceEvent: event }, | ||
}); | ||
this.dispatchEvent(evt); | ||
if (this.opened && !evt.defaultPrevented) { | ||
this.close(event); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_openedChanged(opened, wasOpened) { | ||
if (opened) { | ||
this._saveFocus(); | ||
this._animatedOpening(); | ||
afterNextRender(this, () => { | ||
this._trapFocus(); | ||
const evt = new CustomEvent('vaadin-overlay-open', { bubbles: true }); | ||
this.dispatchEvent(evt); | ||
}); | ||
document.addEventListener('keydown', this._boundKeydownListener); | ||
if (!this.modeless) { | ||
this._addGlobalListeners(); | ||
} | ||
} else if (wasOpened) { | ||
this._resetFocus(); | ||
this._animatedClosing(); | ||
document.removeEventListener('keydown', this._boundKeydownListener); | ||
if (!this.modeless) { | ||
this._removeGlobalListeners(); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_hiddenChanged(hidden) { | ||
if (hidden && this.hasAttribute('closing')) { | ||
this._flushAnimation('closing'); | ||
} | ||
} | ||
/** | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
_shouldAnimate() { | ||
const style = getComputedStyle(this); | ||
const name = style.getPropertyValue('animation-name'); | ||
const hidden = style.getPropertyValue('display') === 'none'; | ||
return !hidden && name && name !== 'none'; | ||
} | ||
/** | ||
* @param {string} type | ||
* @param {Function} callback | ||
* @private | ||
*/ | ||
_enqueueAnimation(type, callback) { | ||
const handler = `__${type}Handler`; | ||
const listener = (event) => { | ||
if (event && event.target !== this) { | ||
return; | ||
} | ||
callback(); | ||
this.removeEventListener('animationend', listener); | ||
delete this[handler]; | ||
}; | ||
this[handler] = listener; | ||
this.addEventListener('animationend', listener); | ||
} | ||
/** | ||
* @param {string} type | ||
* @protected | ||
*/ | ||
_flushAnimation(type) { | ||
const handler = `__${type}Handler`; | ||
if (typeof this[handler] === 'function') { | ||
this[handler](); | ||
} | ||
} | ||
/** @private */ | ||
_animatedOpening() { | ||
if (this.parentNode === document.body && this.hasAttribute('closing')) { | ||
this._flushAnimation('closing'); | ||
} | ||
this._attachOverlay(); | ||
if (!this.modeless) { | ||
this._enterModalState(); | ||
} | ||
this.setAttribute('opening', ''); | ||
if (this._shouldAnimate()) { | ||
this._enqueueAnimation('opening', () => { | ||
this._finishOpening(); | ||
}); | ||
} else { | ||
this._finishOpening(); | ||
} | ||
} | ||
/** @private */ | ||
_attachOverlay() { | ||
this._placeholder = document.createComment('vaadin-overlay-placeholder'); | ||
this.parentNode.insertBefore(this._placeholder, this); | ||
document.body.appendChild(this); | ||
this.bringToFront(); | ||
} | ||
/** @private */ | ||
_finishOpening() { | ||
this.removeAttribute('opening'); | ||
} | ||
/** @private */ | ||
_finishClosing() { | ||
this._detachOverlay(); | ||
this.$.overlay.style.removeProperty('pointer-events'); | ||
this.removeAttribute('closing'); | ||
this.dispatchEvent(new CustomEvent('vaadin-overlay-closed')); | ||
} | ||
/** @private */ | ||
_animatedClosing() { | ||
if (this.hasAttribute('opening')) { | ||
this._flushAnimation('opening'); | ||
} | ||
if (this._placeholder) { | ||
this._exitModalState(); | ||
this.setAttribute('closing', ''); | ||
this.dispatchEvent(new CustomEvent('vaadin-overlay-closing')); | ||
if (this._shouldAnimate()) { | ||
this._enqueueAnimation('closing', () => { | ||
this._finishClosing(); | ||
}); | ||
} else { | ||
this._finishClosing(); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_detachOverlay() { | ||
this._placeholder.parentNode.insertBefore(this, this._placeholder); | ||
this._placeholder.parentNode.removeChild(this._placeholder); | ||
} | ||
/** @private */ | ||
_modelessChanged(modeless) { | ||
if (!modeless) { | ||
if (this.opened) { | ||
this._addGlobalListeners(); | ||
this._enterModalState(); | ||
} | ||
} else { | ||
this._removeGlobalListeners(); | ||
this._exitModalState(); | ||
} | ||
} | ||
/** @private */ | ||
_addGlobalListeners() { | ||
document.addEventListener('mousedown', this._boundMouseDownListener); | ||
document.addEventListener('mouseup', this._boundMouseUpListener); | ||
// Firefox leaks click to document on contextmenu even if prevented | ||
// https://bugzilla.mozilla.org/show_bug.cgi?id=990614 | ||
document.documentElement.addEventListener('click', this._boundOutsideClickListener, true); | ||
} | ||
/** @private */ | ||
_removeGlobalListeners() { | ||
document.removeEventListener('mousedown', this._boundMouseDownListener); | ||
document.removeEventListener('mouseup', this._boundMouseUpListener); | ||
document.documentElement.removeEventListener('click', this._boundOutsideClickListener, true); | ||
} | ||
/** @private */ | ||
_rendererOrDataChanged(renderer, owner, model, opened) { | ||
const ownerOrModelChanged = this._oldOwner !== owner || this._oldModel !== model; | ||
this._oldModel = model; | ||
this._oldOwner = owner; | ||
const rendererChanged = this._oldRenderer !== renderer; | ||
this._oldRenderer = renderer; | ||
const openedChanged = this._oldOpened !== opened; | ||
this._oldOpened = opened; | ||
if (rendererChanged) { | ||
this.innerHTML = ''; | ||
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into. | ||
// When clearing the rendered content, this part needs to be manually disposed of. | ||
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward. | ||
delete this._$litPart$; | ||
} | ||
if (opened && renderer && (rendererChanged || openedChanged || ownerOrModelChanged)) { | ||
this.requestContentUpdate(); | ||
} | ||
} | ||
/** | ||
* @event vaadin-overlay-open | ||
@@ -638,0 +108,0 @@ * Fired after the overlay is opened. |
67170
20
1465
127
+ Added@vaadin/a11y-base@24.2.0-alpha10(transitive)
+ Added@vaadin/component-base@24.2.0-alpha10(transitive)
+ Added@vaadin/icon@24.2.0-alpha10(transitive)
+ Added@vaadin/vaadin-lumo-styles@24.2.0-alpha10(transitive)
+ Added@vaadin/vaadin-material-styles@24.2.0-alpha10(transitive)
+ Added@vaadin/vaadin-themable-mixin@24.2.0-alpha10(transitive)
- Removed@vaadin/a11y-base@24.2.0-alpha1(transitive)
- Removed@vaadin/component-base@24.2.0-alpha1(transitive)
- Removed@vaadin/icon@24.2.0-alpha1(transitive)
- Removed@vaadin/vaadin-lumo-styles@24.2.0-alpha1(transitive)
- Removed@vaadin/vaadin-material-styles@24.2.0-alpha1(transitive)
- Removed@vaadin/vaadin-themable-mixin@24.2.0-alpha1(transitive)