createSuperFocus.js 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. import debounce from 'lodash/debounce.js'
  2. import FOCUSABLE_ELEMENTS from '@uppy/utils/lib/FOCUSABLE_ELEMENTS'
  3. import getActiveOverlayEl from './getActiveOverlayEl.js'
  4. /*
  5. Focuses on some element in the currently topmost overlay.
  6. 1. If there are some [data-uppy-super-focusable] elements rendered already - focuses
  7. on the first superfocusable element, and leaves focus up to the control of
  8. a user (until currently focused element disappears from the screen [which
  9. can happen when overlay changes, or, e.g., when we click on a folder in googledrive]).
  10. 2. If there are no [data-uppy-super-focusable] elements yet (or ever) - focuses
  11. on the first focusable element, but switches focus if superfocusable elements appear on next render.
  12. */
  13. export default function createSuperFocus () {
  14. let lastFocusWasOnSuperFocusableEl = false
  15. const superFocus = (dashboardEl, activeOverlayType) => {
  16. const overlayEl = getActiveOverlayEl(dashboardEl, activeOverlayType)
  17. const isFocusInOverlay = overlayEl.contains(document.activeElement)
  18. // If focus is already in the topmost overlay, AND on last update we focused on the superfocusable
  19. // element - then leave focus up to the user.
  20. // [Practical check] without this line, typing in the search input in googledrive overlay won't work.
  21. if (isFocusInOverlay && lastFocusWasOnSuperFocusableEl) return
  22. const superFocusableEl = overlayEl.querySelector('[data-uppy-super-focusable]')
  23. // If we are already in the topmost overlay, AND there are no super focusable elements yet, - leave focus up to the user.
  24. // [Practical check] without this line, if you are in an empty folder in google drive, and something's uploading in the
  25. // bg, - focus will be jumping to Done all the time.
  26. if (isFocusInOverlay && !superFocusableEl) return
  27. if (superFocusableEl) {
  28. superFocusableEl.focus({ preventScroll: true })
  29. lastFocusWasOnSuperFocusableEl = true
  30. } else {
  31. const firstEl = overlayEl.querySelector(FOCUSABLE_ELEMENTS)
  32. firstEl?.focus({ preventScroll: true })
  33. lastFocusWasOnSuperFocusableEl = false
  34. }
  35. }
  36. // ___Why do we need to debounce?
  37. // 1. To deal with animations: overlay changes via animations, which results in the DOM updating AFTER plugin.update()
  38. // already executed.
  39. // [Practical check] without debounce, if we open the Url overlay, and click 'Done', Dashboard won't get focused again.
  40. // [Practical check] if we delay 250ms instead of 260ms - IE11 won't get focused in same situation.
  41. // 2. Performance: there can be many state update()s in a second, and this function is called every time.
  42. return debounce(superFocus, 260)
  43. }