diff options
| author | Laussel Loïc <[email protected]> | 2018-08-31 09:18:28 +0200 |
|---|---|---|
| committer | XhmikosR <[email protected]> | 2018-11-13 08:47:32 +0200 |
| commit | 4cac833447c53ec7f140c26260ddf36d78ff298f (patch) | |
| tree | 7c7b0631c775c79a39949c10d7d9d0bea5761219 | |
| parent | 2f81ab007cad06dd333a7431a3a653f812bbf246 (diff) | |
| download | bootstrap-4cac833447c53ec7f140c26260ddf36d78ff298f.tar.xz bootstrap-4cac833447c53ec7f140c26260ddf36d78ff298f.zip | |
Implement `data-dismiss="toast"` to allow user to interact itself with the component (#27155)
| -rw-r--r-- | js/src/toast.js | 66 | ||||
| -rw-r--r-- | js/tests/unit/toast.js | 29 | ||||
| -rw-r--r-- | js/tests/visual/toast.html | 10 | ||||
| -rw-r--r-- | site/docs/4.1/components/toasts.md | 44 |
4 files changed, 117 insertions, 32 deletions
diff --git a/js/src/toast.js b/js/src/toast.js index cb6de974b..1e70e091f 100644 --- a/js/src/toast.js +++ b/js/src/toast.js @@ -22,10 +22,11 @@ const Toast = (($) => { const JQUERY_NO_CONFLICT = $.fn[NAME] const Event = { - HIDE : `hide${EVENT_KEY}`, - HIDDEN : `hidden${EVENT_KEY}`, - SHOW : `show${EVENT_KEY}`, - SHOWN : `shown${EVENT_KEY}` + CLICK_DISMISS : `click.dismiss${EVENT_KEY}`, + HIDE : `hide${EVENT_KEY}`, + HIDDEN : `hidden${EVENT_KEY}`, + SHOW : `show${EVENT_KEY}`, + SHOWN : `shown${EVENT_KEY}` } const ClassName = { @@ -49,6 +50,10 @@ const Toast = (($) => { } } + const Selector = { + DATA_DISMISS : '[data-dismiss="toast"]' + } + /** * ------------------------------------------------------------------------ * Class Definition @@ -60,6 +65,7 @@ const Toast = (($) => { this._element = element this._config = this._getConfig(config) this._timeout = null + this._setListeners() } // Getters @@ -104,30 +110,20 @@ const Toast = (($) => { }, this._config.delay.show) } - hide() { + hide(withoutTimeout) { if (!this._element.classList.contains(ClassName.SHOW)) { return } $(this._element).trigger(Event.HIDE) - const complete = () => { - $(this._element).trigger(Event.HIDDEN) + if (withoutTimeout) { + this._close() + } else { + this._timeout = setTimeout(() => { + this._close() + }, this._config.delay.hide) } - - this._timeout = setTimeout(() => { - this._element.classList.remove(ClassName.SHOW) - - if (this._config.animation) { - const transitionDuration = Util.getTransitionDurationFromElement(this._element) - - $(this._element) - .one(Util.TRANSITION_END, complete) - .emulateTransitionEnd(transitionDuration) - } else { - complete() - } - }, this._config.delay.hide) } dispose() { @@ -138,6 +134,8 @@ const Toast = (($) => { this._element.classList.remove(ClassName.SHOW) } + $(this._element).off(Event.CLICK_DISMISS) + $.removeData(this._element, DATA_KEY) this._element = null this._config = null @@ -168,6 +166,32 @@ const Toast = (($) => { return config } + _setListeners() { + $(this._element).on( + Event.CLICK_DISMISS, + Selector.DATA_DISMISS, + () => this.hide(true) + ) + } + + _close() { + const complete = () => { + $(this._element).trigger(Event.HIDDEN) + } + + this._element.classList.remove(ClassName.SHOW) + + if (this._config.animation) { + const transitionDuration = Util.getTransitionDurationFromElement(this._element) + + $(this._element) + .one(Util.TRANSITION_END, complete) + .emulateTransitionEnd(transitionDuration) + } else { + complete() + } + } + // Static static _jQueryInterface(config) { diff --git a/js/tests/unit/toast.js b/js/tests/unit/toast.js index 873661c76..d9c5e1fb6 100644 --- a/js/tests/unit/toast.js +++ b/js/tests/unit/toast.js @@ -232,4 +232,33 @@ $(function () { }) .bootstrapToast('show') }) + + + QUnit.test('should close toast when close element with data-dismiss attribute is set', function (assert) { + assert.expect(2) + var done = assert.async() + + var toastHtml = + '<div class="toast" data-delay="1" data-autohide="false" data-animation="false">' + + '<button type="button" class="ml-2 mb-1 close" data-dismiss="toast">' + + 'close' + + '</button>' + + '</div>' + + var $toast = $(toastHtml) + .bootstrapToast() + .appendTo($('#qunit-fixture')) + + $toast + .on('shown.bs.toast', function () { + assert.strictEqual($toast.hasClass('show'), true) + var button = $toast.find('.close') + button.trigger('click') + }) + .on('hidden.bs.toast', function () { + assert.strictEqual($toast.hasClass('show'), false) + done() + }) + .bootstrapToast('show') + }) }) diff --git a/js/tests/visual/toast.html b/js/tests/visual/toast.html index 6897022c0..902194617 100644 --- a/js/tests/visual/toast.html +++ b/js/tests/visual/toast.html @@ -26,22 +26,28 @@ </div> <div class="notifications"> - <div id="toastAutoHide" class="toast"> + <div id="toastAutoHide" class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small>11 mins ago</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> Hello, world! This is a toast message with <strong>autohide</strong> in 2 seconds </div> </div> - <div class="toast" data-autohide="false"> + <div class="toast" data-autohide="false" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small class="text-muted">2 seconds ago</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> Heads up, toasts will stack automatically diff --git a/site/docs/4.1/components/toasts.md b/site/docs/4.1/components/toasts.md index 54e0a1c52..84359084d 100644 --- a/site/docs/4.1/components/toasts.md +++ b/site/docs/4.1/components/toasts.md @@ -24,11 +24,14 @@ A basic toast can include a header (though it doesn't strictly need one) with wh <div class="bg-light"> {% capture example %} -<div class="toast"> +<div class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small>11 mins ago</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> Hello, world! This is a toast message. @@ -42,11 +45,14 @@ They're slightly translucent, too, so they blend over whatever they might appear <div class="bg-dark"> {% capture example %} -<div class="toast"> +<div class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small class="text-muted">11 mins ago</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> Hello, world! This is a toast message. @@ -60,22 +66,28 @@ Plus, they'll easily stack. <div class="bg-light"> {% capture example %} -<div class="toast"> +<div class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small class="text-muted">just now</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> See? Just like this. </div> </div> -<div class="toast"> +<div class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small class="text-muted">2 seconds ago</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> Heads up, toasts will stack automatically @@ -88,10 +100,12 @@ Plus, they'll easily stack. ## Accessibility Toasts are intended to be small interruptions to your visitors or users, so to help those on screen readers, you should wrap your toasts in an [`aria-live` region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). This allows screen readers the ability to see suggested interruptions without any visual cues. +To improve accessibility level, we strongly recomend to use `autohide: false` and add a `close` button into the header to let user dismiss that element. +You also need to adapt the `role` and `aria-live` level depending on the content. If it's an important message like error, use an `alert` role `assertive` otherwise use a role `status` with a `polite` level. {% highlight html %} -<div role="region" aria-live="polite"> - <div class="toast">...</div> +<div role="alert" aria-live="assertive" aria-atomic="true"> + <div role="alert" aria-live="assertive" aria-atomic="true">...</div> </div> {% endhighlight %} @@ -107,6 +121,9 @@ Place toasts with custom CSS as you need them. The top right is often used for n <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small>11 mins ago</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> Hello, world! This is a toast message. @@ -126,22 +143,28 @@ For systems that generate more notifications, consider using a wrapping element <div style="position: absolute; top: 0; right: 0;"> <!-- Then put toasts within --> - <div class="toast"> + <div class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small class="text-muted">just now</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> See? Just like this. </div> </div> - <div class="toast"> + <div class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small class="text-muted">2 seconds ago</small> + <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> Heads up, toasts will stack automatically @@ -162,11 +185,14 @@ You can also get fancy with flexbox utilities. <div class="d-flex justify-content-center" style="position: absolute; top: 0; right: 0; left: 0;"> <!-- Then put toasts within --> - <div class="toast"> + <div class="toast" role="alert" aria-live="assertive" aria-atomic="true"> <div class="toast-header"> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <strong class="mr-auto">Bootstrap</strong> <small>11 mins ago</small> + <button type="button" class="close" data-dismiss="toast" aria-label="Close" style=""> + <span aria-hidden="true">×</span> + </button> </div> <div class="toast-body"> Hello, world! This is a toast message. |
