cssPointerEvents.js 5.2 KB

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