From 355405daa3296f20ebb5161002650a59fc195d9c Mon Sep 17 00:00:00 2001 From: Joachim Date: Fri, 17 Dec 2021 20:40:58 +0100 Subject: [PATCH] Front-end: Fix Safari details display and enhance dropdown on mobile --- bookwyrm/static/css/bookwyrm.css | 68 +++++++++++-- bookwyrm/static/js/bookwyrm.js | 100 ++++++++++++++++++++ bookwyrm/templates/components/dropdown.html | 2 +- 3 files changed, 159 insertions(+), 11 deletions(-) diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index f385e629..45d976e3 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -93,6 +93,10 @@ body { display: inline !important; } + +/** File input styles + ******************************************************************************/ + input[type=file]::file-selector-button { -moz-appearance: none; -webkit-appearance: none; @@ -119,17 +123,12 @@ input[type=file]::file-selector-button:hover { color: #363636; } -details .dropdown-menu { - display: block !important; -} -details.dropdown[open] summary.dropdown-trigger::before { - content: ""; - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; +/** General `details` element styles + ******************************************************************************/ + +summary::-webkit-details-marker { + display: none; } summary::marker { @@ -147,6 +146,55 @@ summary::marker { margin-top: 1em; } +/** Details dropdown + ******************************************************************************/ + +details.dropdown[open] summary.dropdown-trigger::before { + content: ""; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +details .dropdown-menu { + display: block !important; +} + +details .dropdown-menu button { + /* Fix weird Safari defaults */ + box-sizing: border-box; +} + +details.dropdown .dropdown-menu button:focus-visible, +details.dropdown .dropdown-menu a:focus-visible { + outline-style: auto; + outline-offset: -2px; +} + +@media only screen and (max-width: 768px) { + details.dropdown[open] summary.dropdown-trigger::before { + background-color: rgba(0, 0, 0, 0.5); + z-index: 30; + } + details .dropdown-menu { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex !important; + align-items: center; + justify-content: center; + pointer-events: none; + z-index: 100; + } + details .dropdown-menu > * { + pointer-events: all; + } +} + /** Shelving ******************************************************************************/ diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js index 2b78bf51..3e0b1ad8 100644 --- a/bookwyrm/static/js/bookwyrm.js +++ b/bookwyrm/static/js/bookwyrm.js @@ -51,6 +51,12 @@ let BookWyrm = new class { 'click', this.duplicateInput.bind(this) + )) + document.querySelectorAll('details.dropdown') + .forEach(node => node.addEventListener( + 'toggle', + this.handleDetailsDropdown.bind(this) + )) } @@ -482,4 +488,98 @@ let BookWyrm = new class { textareaEl.parentNode.appendChild(copyButtonEl) } + + /** + * Handle the details dropdown component. + * + * @param {Event} event - Event fired by a `details` element + * with the `dropdown` class name, on toggle. + * @return {undefined} + */ + handleDetailsDropdown(event) { + const detailsElement = event.target; + const summaryElement = detailsElement.querySelector('summary'); + const menuElement = detailsElement.querySelector('.dropdown-menu'); + const htmlElement = document.querySelector('html'); + + if (detailsElement.open) { + // Focus first menu element + menuElement.querySelectorAll( + 'a[href]:not([disabled]), button:not([disabled])' + )[0].focus(); + // Enable focus trap + menuElement.addEventListener('keydown', this.handleFocusTrap); + // Close on Esc + detailsElement.addEventListener('keydown', handleEscKey); + + // Clip page if Mobile + if (this.isMobile()) { + htmlElement.classList.add('is-clipped'); + } + } else { + summaryElement.focus(); + // Disable focus trap + menuElement.removeEventListener('keydown', this.handleFocusTrap); + + // Unclip page + if (this.isMobile()) { + htmlElement.classList.remove('is-clipped'); + } + } + + function handleEscKey(event) { + if (event.key !== 'Escape') { + return; + } + + summaryElement.click(); + } + } + + /** + * Check if windows matches mobile media query. + * + * @return {Boolean} + */ + isMobile() { + return window.matchMedia("(max-width: 768px)").matches; + } + + /** + * Focus trap handler + * + * @param {Event} event - Keydown event. + * @return {undefined} + */ + handleFocusTrap(event) { + if (event.key !== 'Tab') { + return; + } + + const focusableEls = event.currentTarget.querySelectorAll( + [ + 'a[href]:not([disabled])', + 'button:not([disabled])', + 'textarea:not([disabled])', + 'input:not([type="hidden"]):not([disabled])', + 'select:not([disabled])', + 'details:not([disabled])', + '[tabindex]:not([tabindex="-1"]):not([disabled])' + ].join(',') + ); + const firstFocusableEl = focusableEls[0]; + const lastFocusableEl = focusableEls[focusableEls.length - 1]; + + if (event.shiftKey ) /* Shift + tab */ { + if (document.activeElement === firstFocusableEl) { + lastFocusableEl.focus(); + event.preventDefault(); + } + } else /* Tab */ { + if (document.activeElement === lastFocusableEl) { + firstFocusableEl.focus(); + event.preventDefault(); + } + } + } }(); diff --git a/bookwyrm/templates/components/dropdown.html b/bookwyrm/templates/components/dropdown.html index b3710271..0e07bf99 100644 --- a/bookwyrm/templates/components/dropdown.html +++ b/bookwyrm/templates/components/dropdown.html @@ -15,7 +15,7 @@ {% block dropdown-trigger %}{% endblock %} -