From 1c010ffe5adaf9c2ffda8b818ee45a4feb359132 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Mon, 21 Jun 2021 09:40:11 -0400 Subject: [PATCH] add sl-request-close event --- docs/components/dialog.md | 20 ++++++++++-------- docs/components/drawer.md | 17 ++++++++------- docs/resources/changelog.md | 3 +++ src/components/dialog/dialog.ts | 36 ++++++++++++++++++++------------ src/components/drawer/drawer.ts | 37 +++++++++++++++++++++------------ 5 files changed, 71 insertions(+), 42 deletions(-) diff --git a/docs/components/dialog.md b/docs/components/dialog.md index 29cd3e755..2f7f7033f 100644 --- a/docs/components/dialog.md +++ b/docs/components/dialog.md @@ -76,27 +76,29 @@ By design, a dialog's height will never exceed that of the viewport. As such, di ``` -### Ignoring Clicks on the Overlay +### Preventing the Dialog from Closing -By default, dialogs are closed when the user clicks or taps on the overlay. To prevent this behavior, cancel the `sl-overlay-dismiss` event. +By default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the Escape key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur. + +To keep the dialog open in such cases, you can cancel the `sl-request-close` event. When canceled, the dialog will remain open and pulse briefly to draw the user's attention to it. ```html preview - - This dialog will not be closed when you click outside of it. - Close + + This dialog will not close unless you use the button below. + Save & Close Open Dialog ``` diff --git a/docs/components/drawer.md b/docs/components/drawer.md index 59b92f534..f8d3524fc 100644 --- a/docs/components/drawer.md +++ b/docs/components/drawer.md @@ -164,27 +164,30 @@ By design, a drawer's height will never exceed 100% of its container. As such, d ``` -### Ignoring Clicks on the Overlay +### Preventing the Drawer from Closing + +By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the Escape key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur. + +To keep the drawer open in such cases, you can cancel the `sl-request-close` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it. -By default, drawers are closed when the user clicks or taps on the overlay. To prevent this behavior, cancel the `sl-overlay-dismiss` event. ```html preview - - This drawer will not be closed when you click outside of it. - Close + + This dialog will not close unless you use the button below. + Save & Close Open Drawer ``` diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index f7fa51408..4c6b8e0ef 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -8,6 +8,9 @@ _During the beta period, these restrictions may be relaxed in the event of a mis ## Next +- 🚨 BREAKING: removed the `sl-overlay-click` event from `sl-dialog` and `sl-drawer` (use `sl-request-close` instead) [#471](https://github.com/shoelace-style/shoelace/discussions/471) +- Added `sl-request-close` event to `sl-dialog` and `sl-drawer` +- Added `dialog.denyClose` and `drawer.denyClose` animations - Fixed a bug in `sl-color-picker` where setting `value` immediately wouldn't trigger an update ## 2.0.0-beta.44 diff --git a/src/components/dialog/dialog.ts b/src/components/dialog/dialog.ts index cf86fbd28..20740b40f 100644 --- a/src/components/dialog/dialog.ts +++ b/src/components/dialog/dialog.ts @@ -42,6 +42,7 @@ let id = 0; * * @animation dialog.show - The animation to use when showing the dialog. * @animation dialog.hide - The animation to use when hiding the dialog. + * @animation dialog.denyClose - The animation to use when a request to close the dialog is denied. * @animation dialog.overlay.show - The animation to use when showing the dialog's overlay. * @animation dialog.overlay.hide - The animation to use when hiding the dialog's overlay. */ @@ -92,8 +93,12 @@ export default class SlDialog extends LitElement { */ @event('sl-initial-focus') slInitialFocus: EventEmitter; - /** Emitted when the overlay is clicked. Calling `event.preventDefault()` will prevent the dialog from closing. */ - @event('sl-overlay-dismiss') slOverlayDismiss: EventEmitter; + /** + * Emitted when the user attempts to close the dialog by clicking the close button, clicking the overlay, or pressing + * the escape key. Calling `event.preventDefault()` will prevent the dialog from closing. Avoid using this unless + * closing the dialog will result in destructive behavior such as data loss. + */ + @event('sl-request-close') slRequestClose: EventEmitter; connectedCallback() { super.connectedCallback(); @@ -131,14 +136,21 @@ export default class SlDialog extends LitElement { return waitForEvent(this, 'sl-after-hide'); } - handleCloseClick() { + private requestClose() { + const slRequestClose = this.slRequestClose.emit({ cancelable: true }); + if (slRequestClose.defaultPrevented) { + const animation = getAnimation(this, 'dialog.denyClose'); + animateTo(this.panel, animation.keyframes, animation.options); + return; + } + this.hide(); } handleKeyDown(event: KeyboardEvent) { if (event.key === 'Escape') { event.stopPropagation(); - this.hide(); + this.requestClose(); } } @@ -206,13 +218,6 @@ export default class SlDialog extends LitElement { } } - handleOverlayClick() { - const slOverlayDismiss = this.slOverlayDismiss.emit({ cancelable: true }); - if (!slOverlayDismiss.defaultPrevented) { - this.hide(); - } - } - handleSlotChange() { this.hasFooter = hasSlot(this, 'footer'); } @@ -228,7 +233,7 @@ export default class SlDialog extends LitElement { })} @keydown=${this.handleKeyDown} > -
+
` @@ -286,6 +291,11 @@ setDefaultAnimation('dialog.hide', { options: { duration: 250, easing: 'ease' } }); +setDefaultAnimation('dialog.denyClose', { + keyframes: [{ transform: 'scale(1)' }, { transform: 'scale(1.02)' }, { transform: 'scale(1)' }], + options: { duration: 250 } +}); + setDefaultAnimation('dialog.overlay.show', { keyframes: [{ opacity: 0 }, { opacity: 1 }], options: { duration: 250 } diff --git a/src/components/drawer/drawer.ts b/src/components/drawer/drawer.ts index 916d45a12..5d77a12c8 100644 --- a/src/components/drawer/drawer.ts +++ b/src/components/drawer/drawer.ts @@ -50,6 +50,7 @@ let id = 0; * @animation drawer.hideEnd - The animation to use when hiding a drawer with `end` placement. * @animation drawer.hideBottom - The animation to use when hiding a drawer with `bottom` placement. * @animation drawer.hideStart - The animation to use when hiding a drawer with `start` placement. + * @animation drawer.denyClose - The animation to use when a request to close the drawer is denied. * @animation drawer.overlay.show - The animation to use when showing the drawer's overlay. * @animation drawer.overlay.hide - The animation to use when hiding the drawer's overlay. */ @@ -106,8 +107,12 @@ export default class SlDrawer extends LitElement { /** Emitted when the drawer opens and the panel gains focus. Calling `event.preventDefault()` will prevent focus and allow you to set it on a different element in the drawer, such as an input or button. */ @event('sl-initial-focus') slInitialFocus: EventEmitter; - /** Emitted when the overlay is clicked. Calling `event.preventDefault()` will prevent the drawer from closing. */ - @event('sl-overlay-dismiss') slOverlayDismiss: EventEmitter; + /** + * Emitted when the user attempts to close the drawer by clicking the close button, clicking the overlay, or pressing + * the escape key. Calling `event.preventDefault()` will prevent the drawer from closing. Avoid using this unless + * closing the drawer will result in destructive behavior such as data loss. + */ + @event('sl-request-close') slRequestClose: EventEmitter; connectedCallback() { super.connectedCallback(); @@ -145,14 +150,21 @@ export default class SlDrawer extends LitElement { return waitForEvent(this, 'sl-after-hide'); } - handleCloseClick() { + private requestClose() { + const slRequestClose = this.slRequestClose.emit({ cancelable: true }); + if (slRequestClose.defaultPrevented) { + const animation = getAnimation(this, 'drawer.denyClose'); + animateTo(this.panel, animation.keyframes, animation.options); + return; + } + this.hide(); } handleKeyDown(event: KeyboardEvent) { if (event.key === 'Escape') { event.stopPropagation(); - this.hide(); + this.requestClose(); } } @@ -223,13 +235,6 @@ export default class SlDrawer extends LitElement { } } - handleOverlayClick() { - const slOverlayDismiss = this.slOverlayDismiss.emit({ cancelable: true }); - if (!slOverlayDismiss.defaultPrevented) { - this.hide(); - } - } - handleSlotChange() { this.hasFooter = hasSlot(this, 'footer'); } @@ -251,7 +256,7 @@ export default class SlDrawer extends LitElement { })} @keydown=${this.handleKeyDown} > -
+
` @@ -362,6 +367,12 @@ setDefaultAnimation('drawer.hideStart', { options: { duration: 250, easing: 'ease' } }); +// Deny close +setDefaultAnimation('drawer.denyClose', { + keyframes: [{ transform: 'scale(1)' }, { transform: 'scale(1.01)' }, { transform: 'scale(1)' }], + options: { duration: 250 } +}); + // Overlay setDefaultAnimation('drawer.overlay.show', { keyframes: [{ opacity: 0 }, { opacity: 1 }],