diff --git a/js/a11y.js b/js/a11y.js index 145705ee..ed07f600 100644 --- a/js/a11y.js +++ b/js/a11y.js @@ -438,13 +438,16 @@ class A11y extends Backbone.Controller { const config = this.config; $element = $($element).first(); + const isInDOM = Boolean($element.parents('body').length); + if (!isInDOM) return false; + + const isOutsideOpenPopup = this.isPopupOpen && !this.popupStack?.lastItem[0]?.contains($element[0]); + if (isOutsideOpenPopup) return null; + const $branch = checkParents ? $element.add($element.parents()) : $element; - const isInDOM = Boolean($element.parents('body').length); - if (!isInDOM) return false; - const isNotVisible = $branch.toArray().some(item => { const style = window.getComputedStyle(item); // make sure item is not explicitly invisible diff --git a/js/a11y/popup.js b/js/a11y/popup.js index f6a14997..07d1ee9c 100644 --- a/js/a11y/popup.js +++ b/js/a11y/popup.js @@ -1,4 +1,5 @@ import Adapt from 'core/js/adapt'; +import logging from '../logging'; /** * Tabindex and aria-hidden manager for popups. @@ -94,6 +95,12 @@ export default class Popup extends Backbone.Controller { } this._floorStack.push($popupElement); this._focusStack.push($(document.activeElement)); + if ($popupElement.is('dialog')) { + $popupElement[0].addEventListener('cancel', event => event.preventDefault()); + $popupElement[0].showModal(); + return; + } + logging.deprecated('a11y/popup opened: Use native dialog tag for', $popupElement); let $elements = $(config._options._tabbableElements).filter(config._options._tabbableElementsExcludes); const $branch = $popupElement.add($popupElement.parents()); const $siblings = $branch.siblings().filter(config._options._tabbableElementsExcludes); @@ -167,7 +174,11 @@ export default class Popup extends Backbone.Controller { if (this._floorStack.length <= 1) { return; } - this._floorStack.pop(); + const $popupElement = this._floorStack.pop(); + if ($popupElement.is('dialog')) { + $popupElement[0].close(); + return this._focusStack.pop(); + } $(config._options._tabbableElements).filter(config._options._tabbableElementsExcludes).each((index, item) => { const $item = $(item); let previousTabIndex = ''; diff --git a/js/app.js b/js/app.js index acf8e0d5..0b349809 100644 --- a/js/app.js +++ b/js/app.js @@ -22,6 +22,7 @@ import 'core/js/navigation'; import 'core/js/startController'; import 'core/js/DOMElementModifications'; import 'core/js/tooltips'; +import 'core/js/shadow'; import 'plugins'; $('body').append(Handlebars.templates.loading()); diff --git a/js/drawer.js b/js/drawer.js index 79603dc3..1f13179b 100644 --- a/js/drawer.js +++ b/js/drawer.js @@ -18,6 +18,7 @@ class Drawer extends Backbone.Controller { onAdaptStart() { this._drawerView = new DrawerView({ collection: DrawerCollection }); + this._drawerView.$el.insertAfter('#shadow'); } onLanguageChanged() { diff --git a/js/helpers.js b/js/helpers.js index 062fd4ca..fd18e2ac 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -318,7 +318,7 @@ const helpers = { */ a11y_wrap_focus() { const cfg = Adapt.config.get('_accessibility'); - if (cfg._isPopupWrapFocusEnabled === false) return ''; + if (cfg._options?._isPopupWrapFocusEnabled === false) return ''; return new Handlebars.SafeString(' '); }, diff --git a/js/shadow.js b/js/shadow.js new file mode 100644 index 00000000..d57ad8d9 --- /dev/null +++ b/js/shadow.js @@ -0,0 +1,37 @@ +import Backbone from 'backbone'; +import Adapt from 'core/js/adapt'; +import ShadowView from './views/ShadowView'; + +class Shadow extends Backbone.Controller { + + initialize() { + this.listenTo(Adapt, { + 'adapt:start': this.onAdaptStart + }); + } + + onAdaptStart() { + this._shadowView = new ShadowView(); + this._shadowView.$el.prependTo('body'); + } + + get isOpen() { + return this._shadowView?.isOpen ?? false; + } + + async show() { + return this._shadowView?.showShadow(); + } + + async hide() { + return this._shadowView?.hideShadow(); + } + + remove() { + this._shadowView?.remove(); + this._shadowView = null; + } + +} + +export default new Shadow(); diff --git a/js/transitions.js b/js/transitions.js index 1b9b6518..be0265d2 100644 --- a/js/transitions.js +++ b/js/transitions.js @@ -1,5 +1,13 @@ import logging from 'core/js/logging'; +/** + * Wait for one requestAnimationFrame to allow classes to settle + * @returns {Promise} + */ +export async function transitionNextFrame() { + return new Promise(resolve => requestAnimationFrame(resolve)); +} + /** * Handler to await completion of active `CSSTransitions`. * An optional `transition-property` to await can be specified, else all properties will be evaluated. diff --git a/js/views/ShadowView.js b/js/views/ShadowView.js new file mode 100644 index 00000000..75717528 --- /dev/null +++ b/js/views/ShadowView.js @@ -0,0 +1,57 @@ +import Adapt from 'core/js/adapt'; +import Backbone from 'backbone'; +import { transitionNextFrame, transitionsEnded } from '../transitions'; + +class ShadowView extends Backbone.View { + + className() { + return 'shadow js-shadow u-display-none'; + } + + attributes() { + return { + id: 'shadow' + }; + } + + initialize() { + this._isOpen = false; + this.disableAnimation = Adapt.config.get('_disableAnimation') ?? false; + this.$el.toggleClass('disable-animation', Boolean(this.disableAnimation)); + this.render(); + } + + render() { + const template = Handlebars.templates.shadow; + this.$el.html(template({ _globals: Adapt.course.get('_globals') })); + return this; + } + + get isOpen() { + return this._isOpen; + } + + async showShadow() { + this._isOpen = true; + this.$el.addClass('anim-show-before'); + await transitionNextFrame(); + this.$el.removeClass('u-display-none'); + await transitionNextFrame(); + this.$el.addClass('anim-show-after'); + await transitionsEnded(this.$el); + } + + async hideShadow() { + this._isOpen = false; + this.$el.addClass('anim-hide-before'); + await transitionNextFrame(); + this.$el.addClass('anim-hide-after'); + await transitionsEnded(this.$el); + this.$el.addClass('u-display-none'); + await transitionNextFrame(); + this.$el.removeClass('anim-open-before anim-open-after anim-hide-before anim-hide-after'); + } + +} + +export default ShadowView; diff --git a/js/views/drawerView.js b/js/views/drawerView.js index a845d5c9..f083727b 100644 --- a/js/views/drawerView.js +++ b/js/views/drawerView.js @@ -1,10 +1,20 @@ import Adapt from 'core/js/adapt'; +import shadow from '../shadow'; import a11y from 'core/js/a11y'; import DrawerItemView from 'core/js/views/drawerItemView'; import Backbone from 'backbone'; +import { + transitionNextFrame, + transitionsEnded +} from '../transitions'; +import logging from '../logging'; class DrawerView extends Backbone.View { + tagName() { + return 'dialog'; + } + className() { return [ 'drawer', @@ -14,7 +24,6 @@ class DrawerView extends Backbone.View { attributes() { return { - role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'drawer-heading', 'aria-hidden': 'true', @@ -30,17 +39,48 @@ class DrawerView extends Backbone.View { } initialize() { + _.bindAll(this, 'onShadowClicked'); this._isVisible = false; - this.disableAnimation = Adapt.config.has('_disableAnimation') ? Adapt.config.get('_disableAnimation') : false; - this._globalDrawerPosition = Adapt.config.get('_drawer')?._position ?? 'auto'; - this.drawerDuration = Adapt.config.get('_drawer')?._duration ?? 400; + this.disableAnimation = Adapt.config.get('_disableAnimation') ?? false; + this.$el.toggleClass('disable-animation', Boolean(this.disableAnimation)); + this._globalDrawerPosition = this.config?._position ?? 'auto'; + const drawerDuration = this.config?._duration ?? 400; + let showEasing = this.config?._showEasing || 'easeOutQuart'; + let hideEasing = this.config?._hideEasing || 'easeInQuart'; + showEasing = showEasing.toLowerCase(); + hideEasing = hideEasing.toLowerCase(); + if (showEasing.includes('elastic')) { + logging.removed('drawer show elastic easing is replaced with quint'); + showEasing = showEasing.replace('elastic', 'quint'); + } + if (showEasing.includes('bounce')) { + logging.removed('drawer show bounce easing is replaced with back'); + showEasing = showEasing.replace('bounce', 'back'); + } + if (hideEasing.includes('elastic')) { + logging.removed('drawer hide elastic easing is replaced with quint'); + hideEasing = hideEasing.replace('elastic', 'quint'); + } + if (hideEasing.includes('bounce')) { + logging.removed('drawer hide bounce easing is replaced with back'); + hideEasing = hideEasing.replace('bounce', 'back'); + } + const documentElementStyle = document.documentElement.style; + documentElementStyle.setProperty('--adapt-drawer-duration', `${drawerDuration}ms`); + documentElementStyle.setProperty('--adapt-drawer-show-easing', `var(--adapt-cubic-bezier-${showEasing})`); + documentElementStyle.setProperty('--adapt-drawer-hide-easing', `var(--adapt-cubic-bezier-${hideEasing})`); this.setupEventListeners(); this.render(); } + get config () { + return Adapt.config.get('_drawer'); + } + setupEventListeners() { this.onKeyUp = this.onKeyUp.bind(this); $(window).on('keyup', this.onKeyUp); + this.el.addEventListener('click', this.onShadowClicked, { capture: true }); } onKeyUp(event) { @@ -49,18 +89,24 @@ class DrawerView extends Backbone.View { this.hideDrawer(); } + onShadowClicked(event) { + const dialog = this.el; + const rect = dialog.getBoundingClientRect(); + const isInDialog = (rect.top <= event.clientY && event.clientY <= rect.top + rect.height && + rect.left <= event.clientX && event.clientX <= rect.left + rect.width); + if (isInDialog) return; + event.preventDefault(); + this.hideDrawer(); + } + render() { const template = Handlebars.templates.drawer; - $(this.el).html(template({ _globals: Adapt.course.get('_globals') })).prependTo('body'); - const shadowTemplate = Handlebars.templates.shadow; - $(shadowTemplate()).prependTo('body'); + this.$el.html(template({ _globals: Adapt.course.get('_globals') })); _.defer(this.postRender.bind(this)); return this; } postRender() { - this.$('a, button, input, select, textarea').attr('tabindex', -1); - this.checkIfDrawerIsAvailable(); } @@ -74,7 +120,6 @@ class DrawerView extends Backbone.View { .removeClass(`is-position-${this.drawerPosition}`) .addClass(`is-position-${position}`); this.drawerPosition = position; - this.drawerAnimationDir = (position === 'auto') ? (isRTL ? 'left' : 'right') : position; } openCustomView(view, hasBackButton = true, position) { @@ -109,7 +154,8 @@ class DrawerView extends Backbone.View { return (this._isVisible && this._isCustomViewVisible === false); } - showDrawer(emptyDrawer, position = null) { + async showDrawer(emptyDrawer, position = null) { + shadow.show(); this.setDrawerPosition(position); this.$el .removeClass('u-display-none') @@ -122,9 +168,6 @@ class DrawerView extends Backbone.View { this._isVisible = true; } - // Sets tab index to 0 for all tabbable elements in Drawer - this.$('a, button, input, select, textarea').attr('tabindex', 0); - if (emptyDrawer) { this.$('.drawer__back').addClass('u-display-none'); this._isCustomViewVisible = false; @@ -148,34 +191,15 @@ class DrawerView extends Backbone.View { Adapt.trigger('drawer:openedCustomView'); } - $('.js-shadow').removeClass('u-display-none'); $('.js-drawer-holder').scrollTop(0); - const direction = {}; - direction[this.drawerAnimationDir] = 0; - - const complete = () => { - this.addShadowEvent(); - $('.js-nav-drawer-btn').attr('aria-expanded', true); - Adapt.trigger('drawer:opened'); - // focus on first tabbable element in drawer - a11y.focusFirst(this.$el, { defer: true }); - }; + $('.js-nav-drawer-btn').attr('aria-expanded', true); + Adapt.trigger('drawer:opened'); + + this.$el.addClass('anim-show-before'); + await transitionNextFrame(); + this.$el.addClass('anim-show-after'); + await transitionsEnded(this.$el); - // delay drawer animation until after background fadeout animation is complete - if (this.disableAnimation) { - this.$el.css(direction); - complete(); - } else { - const easing = Adapt.config.get('_drawer')?._showEasing || 'easeOutQuart'; - this.$el.velocity(direction, this.drawerDuration, easing); - - $('.js-shadow').velocity({ opacity: 1 }, { - duration: this.drawerDuration, - begin: () => { - complete(); - } - }); - } } emptyDrawer() { @@ -191,56 +215,34 @@ class DrawerView extends Backbone.View { this.collection.forEach(model => new DrawerItemView({ model })); } - hideDrawer($toElement) { + async hideDrawer($toElement) { if (!this._isVisible) return; this._useMenuPosition = false; - const direction = {}; - a11y.popupClosed($toElement); - this._isVisible = false; - a11y.scrollEnable('body'); - direction[this.drawerAnimationDir] = -this.$el.width(); - - const complete = () => { - this.$el - .removeAttr('style') - .addClass('u-display-none') - .attr('aria-hidden', 'true') - .attr('aria-expanded', 'false'); - this.$('.js-drawer-holder').removeAttr('role'); - this._customView = null; - $('.js-nav-drawer-btn').attr('aria-expanded', false); - Adapt.trigger('drawer:closed'); - this.setDrawerPosition(this._globalDrawerPosition); - }; - - if (this.disableAnimation) { - this.$el.css(direction); - $('.js-shadow').addClass('u-display-none'); - complete(); - } else { - const easing = Adapt.config.get('_drawer')?._hideEasing || 'easeInQuart'; - this.$el.velocity(direction, this.drawerDuration, easing, () => { - complete(); - }); - - $('.js-shadow').velocity({ opacity: 0 }, { - duration: this.drawerDuration, - complete() { - $('.js-shadow').addClass('u-display-none'); - } - }); - } this._isCustomViewVisible = false; - this.removeShadowEvent(); - } + shadow.hide(); - addShadowEvent() { - $('.js-shadow').one('click touchstart', () => this.hideDrawer()); - } + this.$el.addClass('anim-hide-before'); + await transitionNextFrame(); + this.$el.addClass('anim-hide-after'); + await transitionsEnded(this.$el); + + this.$el.removeClass('anim-show-before anim-show-after anim-hide-before anim-hide-after'); + + a11y.popupClosed($toElement); + this._isVisible = false; + a11y.scrollEnable('body'); - removeShadowEvent() { - $('.js-shadow').off('click touchstart'); + this.$el + .removeAttr('style') + .addClass('u-display-none') + .attr('aria-hidden', 'true') + .attr('aria-expanded', 'false'); + this.$('.js-drawer-holder').removeAttr('role'); + this._customView = null; + $('.js-nav-drawer-btn').attr('aria-expanded', false); + Adapt.trigger('drawer:closed'); + this.setDrawerPosition(this._globalDrawerPosition); } remove() { @@ -249,7 +251,6 @@ class DrawerView extends Backbone.View { $(window).off('keyup', this.onKeyUp); Adapt.trigger('drawer:empty'); this.collection.reset(); - $('.js-shadow').remove(); } } diff --git a/js/views/notifyPopupView.js b/js/views/notifyPopupView.js index ca0530d5..64d8dd7f 100644 --- a/js/views/notifyPopupView.js +++ b/js/views/notifyPopupView.js @@ -4,6 +4,7 @@ import data from 'core/js/data'; import a11y from 'core/js/a11y'; import AdaptView from 'core/js/views/adaptView'; import Backbone from 'backbone'; +import { transitionNextFrame, transitionsEnded } from '../transitions'; export default class NotifyPopupView extends Backbone.View { @@ -12,39 +13,36 @@ export default class NotifyPopupView extends Backbone.View { } attributes() { - return Object.assign({ - role: 'dialog', - 'aria-labelledby': 'notify-heading', - 'aria-modal': 'true' - }, this.model.get('_attributes')); + return this.model.get('_attributes'); } events() { return { 'click .js-notify-btn-alert': 'onAlertButtonClicked', 'click .js-notify-btn-prompt': 'onPromptButtonClicked', - 'click .js-notify-close-btn': 'onCloseButtonClicked', - 'click .js-notify-shadow-click': 'onShadowClicked' + 'click .js-notify-close-btn': 'onCloseButtonClicked' }; } initialize({ notify }) { this.notify = notify; - _.bindAll(this, 'resetNotifySize', 'onKeyUp'); - this.disableAnimation = Adapt.config.get('_disableAnimation') || false; + _.bindAll(this, 'onShadowClicked', 'resetNotifySize', 'onKeyUp'); + this.disableAnimation = Adapt.config.get('_disableAnimation') ?? false; + this.$el.toggleClass('disable-animation', Boolean(this.disableAnimation)); this.isOpen = false; this.hasOpened = false; this.setupEventListeners(); this.render(); + const dialog = this.$('.notify__popup')[0]; + dialog.addEventListener('click', this.onShadowClicked, { capture: true }); } setupEventListeners() { this.listenTo(Adapt, { remove: this.closeNotify, - 'notify:resize': this.resetNotifySize, + 'notify:resize device:resize': this.resetNotifySize, 'notify:cancel': this.cancelNotify, - 'notify:close': this.closeNotify, - 'device:resize': this.resetNotifySize + 'notify:close': this.closeNotify }); this.setupEscapeKey(); } @@ -62,14 +60,7 @@ export default class NotifyPopupView extends Backbone.View { render() { const data = this.model.toJSON(); const template = Handlebars.templates.notifyPopup; - // hide notify container - this.$el.css('visibility', 'hidden'); - // attach popup + shadow this.$el.html(template(data)).appendTo('.notify__popup-container'); - // hide popup - this.$('.notify__popup').css('visibility', 'hidden'); - // show notify container - this.$el.css('visibility', 'visible'); this.showNotify(); return this; } @@ -95,6 +86,11 @@ export default class NotifyPopupView extends Backbone.View { } onShadowClicked(event) { + const dialog = this.$('.notify__popup')[0]; + const rect = dialog.getBoundingClientRect(); + const isInDialog = (rect.top <= event.clientY && event.clientY <= rect.top + rect.height && + rect.left <= event.clientX && event.clientX <= rect.left + rect.width); + if (isInDialog) return; event.preventDefault(); if (this.model.get('_closeOnShadowClick') === false) return; this.cancelNotify(); @@ -117,9 +113,7 @@ export default class NotifyPopupView extends Backbone.View { const notifyHeight = this.$('.notify__popup-inner').outerHeight(); const isFullWindow = (notifyHeight >= windowHeight); this.$('.notify__popup').css({ - height: isFullWindow ? '100%' : 'auto', - top: isFullWindow ? 0 : '', - 'margin-top': isFullWindow ? '' : -(notifyHeight / 2), + height: isFullWindow ? '100%' : notifyHeight, 'overflow-y': isFullWindow ? 'scroll' : '', '-webkit-overflow-scrolling': isFullWindow ? 'touch' : '' }); @@ -136,42 +130,19 @@ export default class NotifyPopupView extends Backbone.View { this.$el.imageready(this.onLoaded.bind(this)); } - onLoaded() { - if (this.disableAnimation) { - this.$('.notify__shadow').css('display', 'block'); - } else { - this.$('.notify__shadow').velocity({ opacity: 0 }, { duration: 0 }).velocity({ opacity: 1 }, { - duration: 400, - begin: () => { - this.$('.notify__shadow').css('display', 'block'); - } - }); - } - this.resizeNotify(); - if (this.disableAnimation) { - this.$('.notify__popup').css('visibility', 'visible'); - this.onOpened(); - } else { - this.$('.notify__popup').velocity({ opacity: 0 }, { duration: 0 }).velocity({ opacity: 1 }, { - duration: 400, - begin: () => { - // Make sure to make the notify visible and then set - // focus, disabled scroll and manage tabs - this.$('.notify__popup').css('visibility', 'visible'); - this.onOpened(); - } - }); - } - } - - onOpened() { + async onLoaded() { this.hasOpened = true; // Allows popup manager to control focus - a11y.popupOpened(this.$el); + a11y.popupOpened(this.$('.notify__popup')); a11y.scrollDisable('body'); $('html').addClass('notify'); - // Set focus to first accessible element - a11y.focusFirst(this.$('.notify__popup'), { defer: false }); + + this.$el.addClass('anim-show-before'); + await transitionNextFrame(); + this.resetNotifySize(); + await transitionNextFrame(); + this.$el.addClass('anim-show-after'); + await transitionsEnded(this.$('.notify__popup, .notify__shadow')); } async addSubView() { @@ -217,26 +188,14 @@ export default class NotifyPopupView extends Backbone.View { }); } - onCloseReady() { - if (this.disableAnimation) { - this.$('.notify__popup').css('visibility', 'hidden'); - this.$el.css('visibility', 'hidden'); - this.remove(); - } else { - this.$('.notify__popup').velocity({ opacity: 0 }, { - duration: 400, - complete: () => { - this.$('.notify__popup').css('visibility', 'hidden'); - } - }); - this.$('.notify__shadow').velocity({ opacity: 0 }, { - duration: 400, - complete: () => { - this.$el.css('visibility', 'hidden'); - this.remove(); - } - }); - } + async onCloseReady() { + this.$el.addClass('anim-hide-before'); + await transitionNextFrame(); + this.$el.addClass('anim-hide-after'); + await transitionsEnded(this.$('.notify__popup, .notify__shadow')); + + this.remove(); + a11y.scrollEnable('body'); $('html').removeClass('notify'); // Return focus to previous active element diff --git a/js/views/notifyPushView.js b/js/views/notifyPushView.js index 47c1c7fd..67acdea0 100644 --- a/js/views/notifyPushView.js +++ b/js/views/notifyPushView.js @@ -2,6 +2,10 @@ import Adapt from 'core/js/adapt'; export default class NotifyPushView extends Backbone.View { + tagName() { + return 'dialog'; + } + className() { const classes = [ 'notify-push', @@ -13,7 +17,6 @@ export default class NotifyPushView extends Backbone.View { attributes() { return { - role: 'dialog', 'aria-labelledby': 'notify-push-heading', 'aria-modal': 'false' }; diff --git a/js/views/notifyView.js b/js/views/notifyView.js index ee5ed4a0..b6a751d1 100644 --- a/js/views/notifyView.js +++ b/js/views/notifyView.js @@ -14,6 +14,7 @@ export default class NotifyView extends Backbone.View { this._stack = []; this.notifyPushes = new NotifyPushCollection(); this.listenTo(Adapt, { + 'app:dataReady': this.onDataReady, 'notify:popup': this._deprecated.bind(this, 'popup'), 'notify:alert': this._deprecated.bind(this, 'alert'), 'notify:prompt': this._deprecated.bind(this, 'prompt'), @@ -22,6 +23,11 @@ export default class NotifyView extends Backbone.View { this.render(); } + onDataReady() { + const notifyDuration = Adapt.config.get('_notify')?._duration ?? 400; + document.documentElement.style.setProperty('--adapt-notify-duration', `${notifyDuration}ms`); + } + get stack() { return this._stack; } diff --git a/less/_defaults/base.less b/less/_defaults/base.less index 9d330118..dbeb14bf 100644 --- a/less/_defaults/base.less +++ b/less/_defaults/base.less @@ -74,3 +74,18 @@ zw { nb { white-space: nowrap; } + +dialog { + background: transparent; + padding: 0; + margin: 0; + max-width: inherit; + max-height: inherit; + width: 100%; + height: 100%; + border: 0; + + &::backdrop { + background: transparent; + } +} diff --git a/less/_defaults/easing.less b/less/_defaults/easing.less new file mode 100644 index 00000000..14f4a535 --- /dev/null +++ b/less/_defaults/easing.less @@ -0,0 +1,44 @@ +// sources: +// https://joshcollinsworth.com/blog/easing-curves +// https://easings.net/ + +:root { + --adapt-cubic-bezier-linear: linear; + --adapt-cubic-bezier-ease: ease; + --adapt-cubic-bezier-easein: ease-in; + --adapt-cubic-bezier-easeout: ease-out; + --adapt-cubic-bezier-eastinout: ease-in-out; + --adapt-cubic-bezier-easeinsine: cubic-bezier(0.47, 0, 0.745, 0.715); + --adapt-cubic-bezier-easeoutsine: cubic-bezier(0.39, 0.575, 0.565, 1); + --adapt-cubic-bezier-easeinoutsine: cubic-bezier(0.445, 0.05, 0.55, 0.95); + --adapt-cubic-bezier-easeinquad: cubic-bezier(0.55, 0.085, 0.68, 0.53); + --adapt-cubic-bezier-easeoutquad: cubic-bezier(0.25, 0.46, 0.45, 0.94); + --adapt-cubic-bezier-easeinoutquad: cubic-bezier(0.455, 0.03, 0.515, 0.955); + --adapt-cubic-bezier-easeincubic: cubic-bezier(0.55, 0.055, 0.675, 0.19); + --adapt-cubic-bezier-easeoutcubic: cubic-bezier(0.215, 0.61, 0.355, 1); + --adapt-cubic-bezier-easeinoutcubic: cubic-bezier(0.645, 0.045, 0.355, 1); + --adapt-cubic-bezier-easeinquart: cubic-bezier(0.895, 0.03, 0.685, 0.22); + --adapt-cubic-bezier-easeoutquart: cubic-bezier(0.165, 0.84, 0.44, 1); + --adapt-cubic-bezier-easeinoutquart: cubic-bezier(0.77, 0, 0.175, 1); + --adapt-cubic-bezier-easeinquint: cubic-bezier(0.755, 0.05, 0.855, 0.06); + --adapt-cubic-bezier-easeoutquint: cubic-bezier(0.23, 1, 0.32, 1); + --adapt-cubic-bezier-easeinoutquint: cubic-bezier(0.86, 0, 0.07, 1); + --adapt-cubic-bezier-easeinexpo: cubic-bezier(0.95, 0.05, 0.795, 0.035); + --adapt-cubic-bezier-easeoutexpo: cubic-bezier(0.19, 1, 0.22, 1); + --adapt-cubic-bezier-easeinoutexpo: cubic-bezier(1, 0, 0, 1); + --adapt-cubic-bezier-easeincirc: cubic-bezier(0.55, 0, 1, 0.45); + --adapt-cubic-bezier-easeoutcirc: cubic-bezier(0, 0.55, 0.45, 1); + --adapt-cubic-bezier-easeinoutcirc: cubic-bezier(0.85, 0, 0.15, 1); + --adapt-cubic-bezier-easeinback: cubic-bezier(0.6, -0.28, 0.735, 0.045); + --adapt-cubic-bezier-easeoutback: cubic-bezier(0.175, 0.885, 0.32, 1.275); + --adapt-cubic-bezier-easeinoutback: cubic-bezier(0.68, -0.55, 0.265, 1.55); + // note: the following cannot be represented with cubic-bezier + // elastic should be replaced with quint + // bounce should be replaced with back + // --adapt-cubic-bezier-easeinelastic + // --adapt-cubic-bezier-easeoutelastic + // --adapt-cubic-bezier-easeinoutelastic + // --adapt-cubic-bezier-easeinbounce + // --adapt-cubic-bezier-easeoutbounce + // --adapt-cubic-bezier-easeinoutbounce +} diff --git a/less/core/drawer.less b/less/core/drawer.less index f857bee2..79aece1f 100644 --- a/less/core/drawer.less +++ b/less/core/drawer.less @@ -11,12 +11,28 @@ &.is-position-right { left: inherit; right: -@drawer-width; + + &.anim-show-after { + right: 0; + } + + &.anim-hide-after { + right: -@drawer-width; + } } .dir-rtl &:not(.is-position-right), &.is-position-left { right: inherit; left: -@drawer-width; + + &.anim-show-after { + left: 0; + } + + &.anim-hide-after { + left: -@drawer-width; + } } &__inner { diff --git a/less/core/notify.less b/less/core/notify.less index c55beac4..a0d89d03 100644 --- a/less/core/notify.less +++ b/less/core/notify.less @@ -1,12 +1,14 @@ .notify { - position: relative; z-index: 100; &__popup { position: fixed; top: 50%; + transform: translateY(-50%); + height: fit-content; width: 100%; - visibility: hidden; + overflow-y: hidden; + max-height: 100vh; z-index: 100; background-color: @background-inverted; } @@ -45,7 +47,7 @@ .notify__text { width: 60%; } - + .notify__image-container { width: 40%; } diff --git a/less/core/shadow.less b/less/core/shadow.less index a3196ced..9b41061d 100644 --- a/less/core/shadow.less +++ b/less/core/shadow.less @@ -1,5 +1,4 @@ .shadow { .l-fill-viewport-fixed; - opacity: 0; z-index: 90; } diff --git a/schema/config.model.schema b/schema/config.model.schema index f0e86885..dc98b78e 100644 --- a/schema/config.model.schema +++ b/schema/config.model.schema @@ -361,6 +361,21 @@ } } }, + "_notify": { + "type": "object", + "isSetting": false, + "title": "", + "properties": { + "_duration": { + "type": "number", + "required": true, + "default": 400, + "title": "Duration", + "inputType": "Number", + "validators": ["required", "number"] + } + } + }, "_generateSourcemap": { "type": "boolean", "default": false, diff --git a/schema/config.schema.json b/schema/config.schema.json index de443d0c..14139f2e 100644 --- a/schema/config.schema.json +++ b/schema/config.schema.json @@ -317,6 +317,21 @@ "isSetting": false } }, + "_notify": { + "type": "object", + "title": "Notify popup animation", + "default": {}, + "properties": { + "_duration": { + "type": "number", + "title": "Duration", + "default": 400 + } + }, + "_adapt": { + "isSetting": false + } + }, "_generateSourcemap": { "type": "boolean", "title": "Generate source maps", diff --git a/templates/notifyPopup.hbs b/templates/notifyPopup.hbs index e0042f40..5bfdaf4c 100644 --- a/templates/notifyPopup.hbs +++ b/templates/notifyPopup.hbs @@ -1,7 +1,7 @@ {{! make the _globals object in course.json available to this template}} {{import_globals}} -