patchFocus.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { dispatchDOMEvent } from '../event/dispatchEvent.js';
  2. import '../utils/dataTransfer/Clipboard.js';
  3. import { getActiveElement } from '../utils/focus/getActiveElement.js';
  4. import '@testing-library/dom';
  5. const patched = Symbol('patched focus/blur methods');
  6. function patchFocus(HTMLElement) {
  7. if (HTMLElement.prototype[patched]) {
  8. return;
  9. }
  10. // eslint-disable-next-line @typescript-eslint/unbound-method
  11. const { focus, blur } = HTMLElement.prototype;
  12. Object.defineProperties(HTMLElement.prototype, {
  13. focus: {
  14. configurable: true,
  15. get: ()=>patchedFocus
  16. },
  17. blur: {
  18. configurable: true,
  19. get: ()=>patchedBlur
  20. },
  21. [patched]: {
  22. configurable: true,
  23. get: ()=>({
  24. focus,
  25. blur
  26. })
  27. }
  28. });
  29. let activeCall;
  30. function patchedFocus(options) {
  31. if (this.ownerDocument.visibilityState !== 'hidden') {
  32. return focus.call(this, options);
  33. }
  34. const blurred = getActiveTarget(this.ownerDocument);
  35. if (blurred === this) {
  36. return;
  37. }
  38. const thisCall = Symbol('focus call');
  39. activeCall = thisCall;
  40. if (blurred) {
  41. blur.call(blurred);
  42. dispatchDOMEvent(blurred, 'blur', {
  43. relatedTarget: this
  44. });
  45. dispatchDOMEvent(blurred, 'focusout', {
  46. relatedTarget: activeCall === thisCall ? this : null
  47. });
  48. }
  49. if (activeCall === thisCall) {
  50. focus.call(this, options);
  51. dispatchDOMEvent(this, 'focus', {
  52. relatedTarget: blurred
  53. });
  54. }
  55. if (activeCall === thisCall) {
  56. dispatchDOMEvent(this, 'focusin', {
  57. relatedTarget: blurred
  58. });
  59. }
  60. }
  61. function patchedBlur() {
  62. if (this.ownerDocument.visibilityState !== 'hidden') {
  63. return blur.call(this);
  64. }
  65. const blurred = getActiveTarget(this.ownerDocument);
  66. if (blurred !== this) {
  67. return;
  68. }
  69. const thisCall = Symbol('blur call');
  70. activeCall = thisCall;
  71. blur.call(this);
  72. dispatchDOMEvent(blurred, 'blur', {
  73. relatedTarget: null
  74. });
  75. dispatchDOMEvent(blurred, 'focusout', {
  76. relatedTarget: null
  77. });
  78. }
  79. }
  80. function getActiveTarget(document) {
  81. const active = getActiveElement(document);
  82. return (active === null || active === undefined ? undefined : active.tagName) === 'BODY' ? null : active;
  83. }
  84. function restoreFocus(HTMLElement) {
  85. if (HTMLElement.prototype[patched]) {
  86. const { focus, blur } = HTMLElement.prototype[patched];
  87. Object.defineProperties(HTMLElement.prototype, {
  88. focus: {
  89. configurable: true,
  90. get: ()=>focus
  91. },
  92. blur: {
  93. configurable: true,
  94. get: ()=>blur
  95. }
  96. });
  97. }
  98. }
  99. export { patchFocus, restoreFocus };