mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
Dialog fixes (#790)
* revert structure and styles to fix WA-A #123 * fix WA-A #201 * update changelog * fix search dialog position so it doesn't jump around * remove close watcher; fix dialog/drawer backdrop animations
This commit is contained in:
@@ -7,8 +7,9 @@
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
|
||||
&::part(base) {
|
||||
margin-block: 10rem;
|
||||
&::part(dialog) {
|
||||
margin-block-start: 10vh;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
&::part(body) {
|
||||
@@ -23,20 +24,20 @@
|
||||
@media screen and (max-width: 900px) {
|
||||
max-width: calc(100% - 2rem);
|
||||
|
||||
&::part(base) {
|
||||
&::part(dialog) {
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
#site-search-container {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#site-search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 20rem);
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
max-height: calc(100dvh - 2rem);
|
||||
}
|
||||
max-height: calc(100vh - 18rem);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
@@ -14,6 +14,10 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
|
||||
## Next
|
||||
|
||||
- Fixed the search dialog's styles so it doesn't jump around as you search
|
||||
- Removed close watcher logic to backdrop hide animation bugs in `<wa-dialog>` and `<wa-drawer>`; this logic is already handled and we'll revisit `CloseWatcher` when browser support is better and behaviors are consistent
|
||||
- Revert `<wa-dialog>` structure and CSS to fix clipped content in dialogs (WA-A #123) and light dismiss in iOS Safari (WA-A #201)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Added `appearance` to [`<wa-details>`](/docs/components/details) and [`<wa-card>`](/docs/components/card) and support for the [appearance utilities](/docs/utilities/appearance/) in the [`<details>` native styles](/docs/native/details).
|
||||
|
||||
@@ -1,24 +1,89 @@
|
||||
:host {
|
||||
--background-color: var(--wa-color-surface-raised);
|
||||
--border-radius: var(--wa-panel-border-radius);
|
||||
--box-shadow: var(--wa-shadow-l);
|
||||
--width: 31rem;
|
||||
--spacing: var(--wa-space-xl);
|
||||
--show-duration: 200ms;
|
||||
--hide-duration: 200ms;
|
||||
|
||||
display: contents;
|
||||
}
|
||||
|
||||
:host(:not([open])) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
dialog {
|
||||
width: inherit;
|
||||
max-width: inherit;
|
||||
max-height: inherit;
|
||||
background-color: inherit;
|
||||
border-radius: inherit;
|
||||
border: inherit;
|
||||
box-shadow: inherit;
|
||||
padding: inherit;
|
||||
:host([open]) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: var(--width);
|
||||
max-width: calc(100% - var(--wa-space-2xl));
|
||||
max-height: calc(100% - var(--wa-space-2xl));
|
||||
background-color: var(--background-color);
|
||||
border-radius: var(--border-radius);
|
||||
border: none;
|
||||
box-shadow: var(--box-shadow);
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
transition: inherit;
|
||||
|
||||
&.show {
|
||||
animation: show-dialog var(--show-duration) ease;
|
||||
|
||||
&::backdrop {
|
||||
animation: show-backdrop var(--show-duration, 200ms) ease;
|
||||
}
|
||||
}
|
||||
|
||||
&.hide {
|
||||
animation: show-dialog var(--hide-duration) ease reverse;
|
||||
|
||||
&::backdrop {
|
||||
animation: show-backdrop var(--hide-duration, 200ms) ease reverse;
|
||||
}
|
||||
}
|
||||
|
||||
&.pulse {
|
||||
animation: pulse 250ms ease;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Ensure there's enough vertical padding for phones that don't update vh when chrome appears (e.g. iPhone) */
|
||||
@media screen and (max-width: 420px) {
|
||||
.dialog {
|
||||
max-height: 80vh;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog--open {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
padding: var(--spacing);
|
||||
padding-block-end: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
align-self: center;
|
||||
flex: 1 1 auto;
|
||||
font-family: inherit;
|
||||
font-size: var(--wa-font-size-l);
|
||||
font-weight: var(--wa-font-weight-heading);
|
||||
line-height: var(--wa-line-height-condensed);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
@@ -28,13 +93,81 @@ dialog {
|
||||
flex-wrap: wrap;
|
||||
justify-content: end;
|
||||
gap: var(--wa-space-2xs);
|
||||
margin-inline-start: auto;
|
||||
padding-inline-start: var(--spacing);
|
||||
}
|
||||
|
||||
wa-icon-button,
|
||||
::slotted(wa-icon-button) {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--wa-font-size-m);
|
||||
.header-actions wa-icon-button,
|
||||
.header-actions ::slotted(wa-icon-button) {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--wa-font-size-m);
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1 1 auto;
|
||||
display: block;
|
||||
padding: var(--spacing);
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--wa-space-xs);
|
||||
justify-content: end;
|
||||
padding: var(--spacing);
|
||||
padding-block-start: 0;
|
||||
}
|
||||
|
||||
.footer ::slotted(wa-button:not(:first-of-type)) {
|
||||
margin-inline-start: var(--wa-spacing-xs);
|
||||
}
|
||||
|
||||
.dialog::backdrop {
|
||||
/*
|
||||
NOTE: the ::backdrop element doesn't inherit properly in Safari yet, but it will in 17.4! At that time, we can
|
||||
remove the fallback values here.
|
||||
*/
|
||||
background-color: var(--wa-color-overlay-modal, rgb(0 0 0 / 0.25));
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
scale: 1;
|
||||
}
|
||||
50% {
|
||||
scale: 1.02;
|
||||
}
|
||||
100% {
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes show-dialog {
|
||||
from {
|
||||
opacity: 0;
|
||||
scale: 0.8;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes show-backdrop {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.dialog {
|
||||
border: solid 1px white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { html, isServer } from 'lit';
|
||||
import { customElement, property, query, state } from 'lit/decorators.js';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { WaAfterHideEvent } from '../../events/after-hide.js';
|
||||
import { WaAfterShowEvent } from '../../events/after-show.js';
|
||||
import { WaHideEvent } from '../../events/hide.js';
|
||||
@@ -8,7 +9,6 @@ import { animateWithClass } from '../../internal/animate.js';
|
||||
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import WebAwesomeElement from '../../internal/webawesome-element.js';
|
||||
import dialogStyles from '../../styles/native/dialog.css';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import '../icon-button/icon-button.js';
|
||||
import styles from './dialog.css';
|
||||
@@ -35,7 +35,6 @@ import styles from './dialog.css';
|
||||
* behavior such as data loss.
|
||||
* @event wa-after-hide - Emitted after the dialog closes and all animations are complete.
|
||||
*
|
||||
* @csspart base - The inner `<dialog>` used to render this component.
|
||||
* @csspart header - The dialog's header. This element wraps the title and header actions.
|
||||
* @csspart header-actions - Optional actions to add to the header. Works best with `<wa-icon-button>`.
|
||||
* @csspart title - The dialog's title.
|
||||
@@ -44,16 +43,22 @@ import styles from './dialog.css';
|
||||
* @csspart body - The dialog's body.
|
||||
* @csspart footer - The dialog's footer.
|
||||
*
|
||||
* @cssproperty --background-color - The dialog's background color.
|
||||
* @cssproperty --border-radius - The radius of the dialog's corners.
|
||||
* @cssproperty --box-shadow - The shadow effects around the edges of the dialog.
|
||||
* @cssproperty --spacing - The amount of space around and between the dialog's content.
|
||||
* @cssproperty --width - The preferred width of the dialog. Note that the dialog will shrink to accommodate smaller screens.
|
||||
* @cssproperty [--show-duration=200ms] - The animation duration when showing the dialog.
|
||||
* @cssproperty [--hide-duration=200ms] - The animation duration when hiding the dialog.
|
||||
*/
|
||||
@customElement('wa-dialog')
|
||||
export default class WaDialog extends WebAwesomeElement {
|
||||
static shadowStyle = [dialogStyles, styles];
|
||||
static shadowStyle = styles;
|
||||
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private originalTrigger: HTMLElement | null;
|
||||
private closeWatcher: CloseWatcher | null;
|
||||
|
||||
@query('dialog') dialog: HTMLDialogElement;
|
||||
@query('.dialog') dialog: HTMLDialogElement;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the dialog is open. You can toggle this attribute to show and hide the dialog, or you can
|
||||
@@ -76,9 +81,6 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
/** When enabled, the dialog will be closed when the user clicks outside of it. */
|
||||
@property({ attribute: 'light-dismiss', type: Boolean }) lightDismiss = false;
|
||||
|
||||
@state()
|
||||
hasOpened = this.open;
|
||||
|
||||
firstUpdated() {
|
||||
if (this.open) {
|
||||
this.addOpenListeners();
|
||||
@@ -100,12 +102,14 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
|
||||
if (waHideEvent.defaultPrevented) {
|
||||
this.open = true;
|
||||
animateWithClass(this.dialog, 'wa-dialog-pulse');
|
||||
animateWithClass(this.dialog, 'pulse');
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeOpenListeners();
|
||||
|
||||
await animateWithClass(this.dialog, 'hide');
|
||||
|
||||
this.open = false;
|
||||
this.dialog.close();
|
||||
unlockBodyScrolling(this);
|
||||
@@ -120,16 +124,7 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
private addOpenListeners() {
|
||||
if ('CloseWatcher' in window) {
|
||||
this.closeWatcher?.destroy();
|
||||
this.closeWatcher = new CloseWatcher();
|
||||
this.closeWatcher.onclose = () => {
|
||||
this.requestClose(this.dialog);
|
||||
};
|
||||
} else {
|
||||
this.closeWatcher?.destroy();
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown);
|
||||
}
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown);
|
||||
}
|
||||
|
||||
private removeOpenListeners() {
|
||||
@@ -161,7 +156,7 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
if (this.lightDismiss) {
|
||||
this.requestClose(this.dialog);
|
||||
} else {
|
||||
await animateWithClass(this.dialog, 'wa-dialog-pulse');
|
||||
await animateWithClass(this.dialog, 'pulse');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +193,6 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
this.addOpenListeners();
|
||||
this.originalTrigger = document.activeElement as HTMLElement;
|
||||
this.open = true;
|
||||
this.hasOpened = true;
|
||||
this.dialog.showModal();
|
||||
|
||||
lockBodyScrolling(this);
|
||||
@@ -211,20 +205,28 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
}
|
||||
});
|
||||
|
||||
await animateWithClass(this.dialog, 'show');
|
||||
|
||||
this.dispatchEvent(new WaAfterShowEvent());
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<dialog
|
||||
part="base"
|
||||
part="dialog"
|
||||
class=${classMap({
|
||||
dialog: true,
|
||||
'dialog--open': this.open,
|
||||
'dialog--with-header': this.withHeader,
|
||||
'dialog--with-footer': this.withFooter,
|
||||
})}
|
||||
@cancel=${this.handleDialogCancel}
|
||||
@click=${this.handleDialogClick}
|
||||
@pointerdown=${this.handleDialogPointerDown}
|
||||
>
|
||||
${this.withHeader
|
||||
? html`
|
||||
<header part="header">
|
||||
<header part="header" class="header">
|
||||
<h2 part="title" class="title" id="title">
|
||||
<!-- If there's no label, use an invisible character to prevent the header from collapsing -->
|
||||
<slot name="label"> ${this.label.length > 0 ? this.label : String.fromCharCode(65279)} </slot>
|
||||
@@ -246,11 +248,11 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
`
|
||||
: ''}
|
||||
|
||||
<slot part="body" class="body"></slot>
|
||||
<div part="body" class="body"><slot></slot></div>
|
||||
|
||||
${this.withFooter
|
||||
? html`
|
||||
<footer part="footer">
|
||||
<footer part="footer" class="footer">
|
||||
<slot name="footer"></slot>
|
||||
</footer>
|
||||
`
|
||||
@@ -262,7 +264,7 @@ export default class WaDialog extends WebAwesomeElement {
|
||||
|
||||
// Ugly, but it fixes light dismiss in Safari: https://bugs.webkit.org/show_bug.cgi?id=267688
|
||||
if (!isServer) {
|
||||
document.body.addEventListener('pointerdown', () => {
|
||||
document.addEventListener('pointerdown', () => {
|
||||
/* empty */
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@ export default class WaDrawer extends WebAwesomeElement {
|
||||
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private originalTrigger: HTMLElement | null;
|
||||
private closeWatcher: CloseWatcher | null;
|
||||
|
||||
@query('.drawer') drawer: HTMLDialogElement;
|
||||
|
||||
@@ -136,16 +135,7 @@ export default class WaDrawer extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
private addOpenListeners() {
|
||||
if ('CloseWatcher' in window) {
|
||||
this.closeWatcher?.destroy();
|
||||
this.closeWatcher = new CloseWatcher();
|
||||
this.closeWatcher.onclose = () => {
|
||||
this.requestClose(this.drawer);
|
||||
};
|
||||
} else {
|
||||
this.closeWatcher?.destroy();
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown);
|
||||
}
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown);
|
||||
}
|
||||
|
||||
private removeOpenListeners() {
|
||||
|
||||
@@ -50,8 +50,6 @@ export default class WaDropdown extends WebAwesomeElement {
|
||||
@query('#trigger') trigger: HTMLSlotElement;
|
||||
@query('.panel') panel: HTMLSlotElement;
|
||||
|
||||
private closeWatcher: CloseWatcher | null;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the dropdown is open. You can toggle this attribute to show and hide the dropdown, or you
|
||||
* can use the `show()` and `hide()` methods and this attribute will reflect the dropdown's open state.
|
||||
@@ -148,7 +146,7 @@ export default class WaDropdown extends WebAwesomeElement {
|
||||
private handleKeyDown = (event: KeyboardEvent) => {
|
||||
// Close when escape is pressed inside an open dropdown. We need to listen on the panel itself and stop propagation
|
||||
// in case any ancestors are also listening for this key.
|
||||
if (this.open && event.key === 'Escape' && !this.closeWatcher) {
|
||||
if (this.open && event.key === 'Escape') {
|
||||
event.stopPropagation();
|
||||
this.hide();
|
||||
this.focusOnTrigger();
|
||||
@@ -344,16 +342,7 @@ export default class WaDropdown extends WebAwesomeElement {
|
||||
|
||||
addOpenListeners() {
|
||||
this.panel.addEventListener('wa-select', this.handlePanelSelect);
|
||||
if ('CloseWatcher' in window) {
|
||||
this.closeWatcher?.destroy();
|
||||
this.closeWatcher = new CloseWatcher();
|
||||
this.closeWatcher.onclose = () => {
|
||||
this.hide();
|
||||
this.focusOnTrigger();
|
||||
};
|
||||
} else {
|
||||
this.panel.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
this.panel.addEventListener('keydown', this.handleKeyDown);
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown);
|
||||
document.addEventListener('mousedown', this.handleDocumentMouseDown);
|
||||
}
|
||||
@@ -365,7 +354,6 @@ export default class WaDropdown extends WebAwesomeElement {
|
||||
}
|
||||
document.removeEventListener('keydown', this.handleDocumentKeyDown);
|
||||
document.removeEventListener('mousedown', this.handleDocumentMouseDown);
|
||||
this.closeWatcher?.destroy();
|
||||
}
|
||||
|
||||
@watch('open', { waitUntilFirstUpdate: true })
|
||||
|
||||
@@ -102,7 +102,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private typeToSelectString = '';
|
||||
private typeToSelectTimeout: number;
|
||||
private closeWatcher: CloseWatcher | null;
|
||||
|
||||
@query('.select') popup: WaPopup;
|
||||
@query('.combobox') combobox: HTMLSlotElement;
|
||||
@@ -320,17 +319,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
if (this.getRootNode() !== document) {
|
||||
this.getRootNode().addEventListener('focusin', this.handleDocumentFocusIn);
|
||||
}
|
||||
|
||||
if ('CloseWatcher' in window) {
|
||||
this.closeWatcher?.destroy();
|
||||
this.closeWatcher = new CloseWatcher();
|
||||
this.closeWatcher.onclose = () => {
|
||||
if (this.open) {
|
||||
this.hide();
|
||||
this.displayInput.focus({ preventScroll: true });
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private removeOpenListeners() {
|
||||
@@ -341,8 +329,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
if (this.getRootNode() !== document) {
|
||||
this.getRootNode().removeEventListener('focusin', this.handleDocumentFocusIn);
|
||||
}
|
||||
|
||||
this.closeWatcher?.destroy();
|
||||
}
|
||||
|
||||
private handleFocus() {
|
||||
|
||||
@@ -44,7 +44,6 @@ export default class WaTooltip extends WebAwesomeElement {
|
||||
static dependencies = { 'wa-popup': WaPopup };
|
||||
|
||||
private hoverTimeout: number;
|
||||
private closeWatcher: CloseWatcher | null;
|
||||
|
||||
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
|
||||
@query('.body') body: HTMLElement;
|
||||
@@ -127,7 +126,6 @@ export default class WaTooltip extends WebAwesomeElement {
|
||||
super.disconnectedCallback();
|
||||
|
||||
// Cleanup this event in case the tooltip is removed while open
|
||||
this.closeWatcher?.destroy();
|
||||
document.removeEventListener('keydown', this.handleDocumentKeyDown);
|
||||
this.eventController.abort();
|
||||
|
||||
@@ -212,15 +210,7 @@ export default class WaTooltip extends WebAwesomeElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('CloseWatcher' in window) {
|
||||
this.closeWatcher?.destroy();
|
||||
this.closeWatcher = new CloseWatcher();
|
||||
this.closeWatcher.onclose = () => {
|
||||
this.hide();
|
||||
};
|
||||
} else {
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown, { signal: this.eventController.signal });
|
||||
}
|
||||
document.addEventListener('keydown', this.handleDocumentKeyDown, { signal: this.eventController.signal });
|
||||
|
||||
this.body.hidden = false;
|
||||
this.popup.active = true;
|
||||
@@ -237,7 +227,6 @@ export default class WaTooltip extends WebAwesomeElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeWatcher?.destroy();
|
||||
document.removeEventListener('keydown', this.handleDocumentKeyDown);
|
||||
|
||||
await animateWithClass(this.popup.popup, 'hide-with-scale');
|
||||
|
||||
Reference in New Issue
Block a user