diff --git a/packages/@uppy/dashboard/src/Dashboard.jsx b/packages/@uppy/dashboard/src/Dashboard.jsx index fca44f4fe0..aee7125610 100644 --- a/packages/@uppy/dashboard/src/Dashboard.jsx +++ b/packages/@uppy/dashboard/src/Dashboard.jsx @@ -8,7 +8,6 @@ import toArray from '@uppy/utils/lib/toArray' import getDroppedFiles from '@uppy/utils/lib/getDroppedFiles' import { nanoid } from 'nanoid/non-secure' import memoizeOne from 'memoize-one' -import FOCUSABLE_ELEMENTS from '@uppy/utils/lib/FOCUSABLE_ELEMENTS' import * as trapFocus from './utils/trapFocus.js' import createSuperFocus from './utils/createSuperFocus.js' import DashboardUI from './components/Dashboard.jsx' @@ -44,6 +43,8 @@ function defaultPickerIcon () { export default class Dashboard extends UIPlugin { static VERSION = packageJson.version + #disabledNodes = null + constructor (uppy, opts) { super(uppy, opts) this.id = this.opts.id || 'Dashboard' @@ -459,26 +460,34 @@ export default class Dashboard extends UIPlugin { } } - disableAllFocusableElements = (disable) => { - const focusableNodes = toArray(this.el.querySelectorAll(FOCUSABLE_ELEMENTS)) + disableInteractiveElements = (disable) => { + const NODES_TO_DISABLE = [ + 'a[href]', + 'input:not([disabled])', + 'select:not([disabled])', + 'textarea:not([disabled])', + 'button:not([disabled])', + '[role="button"]:not([disabled])', + ] + + const nodesToDisable = this.#disabledNodes ?? toArray(this.el.querySelectorAll(NODES_TO_DISABLE)) + .filter(node => !node.classList.contains('uppy-Dashboard-close')) + + for (const node of nodesToDisable) { + // Links can’t have `disabled` attr, so we use `aria-disabled` for a11y + if (node.tagName === 'A') { + node.setAttribute('aria-disabled', disable) + } else { + node.disabled = disable + } + } + if (disable) { - focusableNodes.forEach((node) => { - // save previous tabindex in a data-attribute, to restore when enabling - const currentTabIndex = node.getAttribute('tabindex') - if (currentTabIndex) { - node.dataset.inertTabindex = currentTabIndex // eslint-disable-line no-param-reassign - } - node.setAttribute('tabindex', '-1') - }) + this.#disabledNodes = nodesToDisable } else { - focusableNodes.forEach((node) => { - if ('inertTabindex' in node.dataset) { - node.setAttribute('tabindex', node.dataset.inertTabindex) - } else { - node.removeAttribute('tabindex') - } - }) + this.#disabledNodes = null } + this.dashboardIsDisabled = disable } @@ -834,12 +843,12 @@ export default class Dashboard extends UIPlugin { afterUpdate = () => { if (this.opts.disabled && !this.dashboardIsDisabled) { - this.disableAllFocusableElements(true) + this.disableInteractiveElements(true) return } if (!this.opts.disabled && this.dashboardIsDisabled) { - this.disableAllFocusableElements(false) + this.disableInteractiveElements(false) } this.superFocusOnEachUpdate() @@ -947,7 +956,7 @@ export default class Dashboard extends UIPlugin { activePickerPanel: pluginState.activePickerPanel, showFileEditor: pluginState.showFileEditor, saveFileEditor: this.saveFileEditor, - disableAllFocusableElements: this.disableAllFocusableElements, + disableInteractiveElements: this.disableInteractiveElements, animateOpenClose: this.opts.animateOpenClose, isClosing: pluginState.isClosing, progressindicators, diff --git a/packages/@uppy/dashboard/src/style.scss b/packages/@uppy/dashboard/src/style.scss index 3936271b86..2ac8763921 100644 --- a/packages/@uppy/dashboard/src/style.scss +++ b/packages/@uppy/dashboard/src/style.scss @@ -190,7 +190,7 @@ opacity: 0.6; filter: grayscale(100%); user-select: none; - pointer-events: none; + cursor: not-allowed; } } @@ -198,6 +198,14 @@ fill: #9f9f9f; } +// Disallow clicking on all interactive elements +.uppy-Dashboard--isDisabled { + [disabled], [aria-disabled] { + pointer-events: none; + cursor: not-allowed; + } +} + .uppy-Dashboard--modal .uppy-Dashboard-inner { position: fixed; top: 35px;