cssPointerEvents.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { PointerEventsCheckLevel } from '../../options.js';
  2. import { getWindow } from '../misc/getWindow.js';
  3. import { isElementType } from '../misc/isElementType.js';
  4. import { ApiLevel, getLevelRef } from '../misc/level.js';
  5. function hasPointerEvents(instance, element) {
  6. var _checkPointerEvents;
  7. return ((_checkPointerEvents = checkPointerEvents(instance, element)) === null || _checkPointerEvents === undefined ? undefined : _checkPointerEvents.pointerEvents) !== 'none';
  8. }
  9. function closestPointerEventsDeclaration(element) {
  10. const window = getWindow(element);
  11. for(let el = element, tree = []; el === null || el === undefined ? undefined : el.ownerDocument; el = el.parentElement){
  12. tree.push(el);
  13. const pointerEvents = window.getComputedStyle(el).pointerEvents;
  14. if (pointerEvents && ![
  15. 'inherit',
  16. 'unset'
  17. ].includes(pointerEvents)) {
  18. return {
  19. pointerEvents,
  20. tree
  21. };
  22. }
  23. }
  24. return undefined;
  25. }
  26. const PointerEventsCheck = Symbol('Last check for pointer-events');
  27. function checkPointerEvents(instance, element) {
  28. const lastCheck = element[PointerEventsCheck];
  29. const needsCheck = instance.config.pointerEventsCheck !== PointerEventsCheckLevel.Never && (!lastCheck || hasBitFlag(instance.config.pointerEventsCheck, PointerEventsCheckLevel.EachApiCall) && lastCheck[ApiLevel.Call] !== getLevelRef(instance, ApiLevel.Call) || hasBitFlag(instance.config.pointerEventsCheck, PointerEventsCheckLevel.EachTrigger) && lastCheck[ApiLevel.Trigger] !== getLevelRef(instance, ApiLevel.Trigger));
  30. if (!needsCheck) {
  31. return lastCheck === null || lastCheck === undefined ? undefined : lastCheck.result;
  32. }
  33. const declaration = closestPointerEventsDeclaration(element);
  34. element[PointerEventsCheck] = {
  35. [ApiLevel.Call]: getLevelRef(instance, ApiLevel.Call),
  36. [ApiLevel.Trigger]: getLevelRef(instance, ApiLevel.Trigger),
  37. result: declaration
  38. };
  39. return declaration;
  40. }
  41. function assertPointerEvents(instance, element) {
  42. const declaration = checkPointerEvents(instance, element);
  43. if ((declaration === null || declaration === undefined ? undefined : declaration.pointerEvents) === 'none') {
  44. throw new Error([
  45. `Unable to perform pointer interaction as the element ${declaration.tree.length > 1 ? 'inherits' : 'has'} \`pointer-events: none\`:`,
  46. '',
  47. printTree(declaration.tree)
  48. ].join('\n'));
  49. }
  50. }
  51. function printTree(tree) {
  52. return tree.reverse().map((el, i)=>[
  53. ''.padEnd(i),
  54. el.tagName,
  55. el.id && `#${el.id}`,
  56. el.hasAttribute('data-testid') && `(testId=${el.getAttribute('data-testid')})`,
  57. getLabelDescr(el),
  58. tree.length > 1 && i === 0 && ' <-- This element declared `pointer-events: none`',
  59. tree.length > 1 && i === tree.length - 1 && ' <-- Asserted pointer events here'
  60. ].filter(Boolean).join('')).join('\n');
  61. }
  62. function getLabelDescr(element) {
  63. var _element_labels;
  64. let label;
  65. if (element.hasAttribute('aria-label')) {
  66. label = element.getAttribute('aria-label');
  67. } else if (element.hasAttribute('aria-labelledby')) {
  68. var _element_ownerDocument_getElementById_textContent, _element_ownerDocument_getElementById;
  69. label = (_element_ownerDocument_getElementById = element.ownerDocument.getElementById(element.getAttribute('aria-labelledby'))) === null || _element_ownerDocument_getElementById === undefined ? undefined : (_element_ownerDocument_getElementById_textContent = _element_ownerDocument_getElementById.textContent) === null || _element_ownerDocument_getElementById_textContent === undefined ? undefined : _element_ownerDocument_getElementById_textContent.trim();
  70. } else if (isElementType(element, [
  71. 'button',
  72. 'input',
  73. 'meter',
  74. 'output',
  75. 'progress',
  76. 'select',
  77. 'textarea'
  78. ]) && ((_element_labels = element.labels) === null || _element_labels === undefined ? undefined : _element_labels.length)) {
  79. label = Array.from(element.labels).map((el)=>{
  80. var _el_textContent;
  81. return (_el_textContent = el.textContent) === null || _el_textContent === undefined ? undefined : _el_textContent.trim();
  82. }).join('|');
  83. } else if (isElementType(element, 'button')) {
  84. var _element_textContent;
  85. label = (_element_textContent = element.textContent) === null || _element_textContent === undefined ? undefined : _element_textContent.trim();
  86. }
  87. label = label === null || label === undefined ? undefined : label.replace(/\n/g, ' ');
  88. if (Number(label === null || label === undefined ? undefined : label.length) > 30) {
  89. label = `${label === null || label === undefined ? undefined : label.substring(0, 29)}…`;
  90. }
  91. return label ? `(label=${label})` : '';
  92. }
  93. // With the eslint rule and prettier the bitwise operation isn't nice to read
  94. function hasBitFlag(conf, flag) {
  95. // eslint-disable-next-line no-bitwise
  96. return (conf & flag) > 0;
  97. }
  98. export { assertPointerEvents, hasPointerEvents };