From 7c1edc61564f38efe176283c1966725e20981be5 Mon Sep 17 00:00:00 2001 From: Hunter Perrin Date: Thu, 1 Mar 2018 17:48:56 -0800 Subject: [PATCH] Listen only to trusted events. Stop action event propagation by default. Make cursor styles important. Detect nonblocking elements when event is fired on their descendants. Use new event constructors. Version bump. --- NonBlock.es5.js | 146 ++++++++++++++++++++-------- NonBlock.js | 236 ++++++++++++++++++++++++++++------------------ README.md | 6 +- index.html | 4 +- package-lock.json | 2 +- package.json | 2 +- 6 files changed, 264 insertions(+), 132 deletions(-) diff --git a/NonBlock.es5.js b/NonBlock.es5.js index 92b2200..d858556 100644 --- a/NonBlock.es5.js +++ b/NonBlock.es5.js @@ -31,7 +31,7 @@ })(function () { var styling = document.createElement('style'); styling.setAttribute('type', 'text/css'); - var css = '\n .nonblock{transition:opacity .3s ease;}\n .nonblock:hover{opacity:.1 !important;}\n .nonblock-hide{display:none !important;}\n .nonblock-cursor-auto{cursor:auto;}\n .nonblock-cursor-default{cursor:default;}\n .nonblock-cursor-none{cursor:none;}\n .nonblock-cursor-context-menu{cursor:context-menu;}\n .nonblock-cursor-help{cursor:help;}\n .nonblock-cursor-pointer{cursor:pointer;}\n .nonblock-cursor-progress{cursor:progress;}\n .nonblock-cursor-wait{cursor:wait;}\n .nonblock-cursor-cell{cursor:cell;}\n .nonblock-cursor-crosshair{cursor:crosshair;}\n .nonblock-cursor-text{cursor:text;}\n .nonblock-cursor-vertical-text{cursor:vertical-text;}\n .nonblock-cursor-alias{cursor:alias;}\n .nonblock-cursor-copy{cursor:copy;}\n .nonblock-cursor-move{cursor:move;}\n .nonblock-cursor-no-drop{cursor:no-drop;}\n .nonblock-cursor-not-allowed{cursor:not-allowed;}\n .nonblock-cursor-all-scroll{cursor:all-scroll;}\n .nonblock-cursor-col-resize{cursor:col-resize;}\n .nonblock-cursor-row-resize{cursor:row-resize;}\n .nonblock-cursor-n-resize{cursor:n-resize;}\n .nonblock-cursor-e-resize{cursor:e-resize;}\n .nonblock-cursor-s-resize{cursor:s-resize;}\n .nonblock-cursor-w-resize{cursor:w-resize;}\n .nonblock-cursor-ne-resize{cursor:ne-resize;}\n .nonblock-cursor-nw-resize{cursor:nw-resize;}\n .nonblock-cursor-se-resize{cursor:se-resize;}\n .nonblock-cursor-sw-resize{cursor:sw-resize;}\n .nonblock-cursor-ew-resize{cursor:ew-resize;}\n .nonblock-cursor-ns-resize{cursor:ns-resize;}\n .nonblock-cursor-nesw-resize{cursor:nesw-resize;}\n .nonblock-cursor-nwse-resize{cursor:nwse-resize;}\n .nonblock-cursor-zoom-in{cursor:zoom-in;}\n .nonblock-cursor-zoom-out{cursor:zoom-out;}\n .nonblock-cursor-grab{cursor:grab;}\n .nonblock-cursor-grabbing{cursor:grabbing;}\n '; + var css = '\n .nonblock{transition:opacity .3s ease;}\n .nonblock:hover{opacity:.1 !important;}\n .nonblock-hide{display:none !important;}\n .nonblock-cursor-auto{cursor:auto !important;}\n .nonblock-cursor-default{cursor:default !important;}\n .nonblock-cursor-none{cursor:none !important;}\n .nonblock-cursor-context-menu{cursor:context-menu !important;}\n .nonblock-cursor-help{cursor:help !important;}\n .nonblock-cursor-pointer{cursor:pointer !important;}\n .nonblock-cursor-progress{cursor:progress !important;}\n .nonblock-cursor-wait{cursor:wait !important;}\n .nonblock-cursor-cell{cursor:cell !important;}\n .nonblock-cursor-crosshair{cursor:crosshair !important;}\n .nonblock-cursor-text{cursor:text !important;}\n .nonblock-cursor-vertical-text{cursor:vertical-text !important;}\n .nonblock-cursor-alias{cursor:alias !important;}\n .nonblock-cursor-copy{cursor:copy !important;}\n .nonblock-cursor-move{cursor:move !important;}\n .nonblock-cursor-no-drop{cursor:no-drop !important;}\n .nonblock-cursor-not-allowed{cursor:not-allowed !important;}\n .nonblock-cursor-all-scroll{cursor:all-scroll !important;}\n .nonblock-cursor-col-resize{cursor:col-resize !important;}\n .nonblock-cursor-row-resize{cursor:row-resize !important;}\n .nonblock-cursor-n-resize{cursor:n-resize !important;}\n .nonblock-cursor-e-resize{cursor:e-resize !important;}\n .nonblock-cursor-s-resize{cursor:s-resize !important;}\n .nonblock-cursor-w-resize{cursor:w-resize !important;}\n .nonblock-cursor-ne-resize{cursor:ne-resize !important;}\n .nonblock-cursor-nw-resize{cursor:nw-resize !important;}\n .nonblock-cursor-se-resize{cursor:se-resize !important;}\n .nonblock-cursor-sw-resize{cursor:sw-resize !important;}\n .nonblock-cursor-ew-resize{cursor:ew-resize !important;}\n .nonblock-cursor-ns-resize{cursor:ns-resize !important;}\n .nonblock-cursor-nesw-resize{cursor:nesw-resize !important;}\n .nonblock-cursor-nwse-resize{cursor:nwse-resize !important;}\n .nonblock-cursor-zoom-in{cursor:zoom-in !important;}\n .nonblock-cursor-zoom-out{cursor:zoom-out !important;}\n .nonblock-cursor-grab{cursor:grab !important;}\n .nonblock-cursor-grabbing{cursor:grabbing !important;}\n '; if (styling.styleSheet) { styling.styleSheet.cssText = css; // IE } else { @@ -51,12 +51,23 @@ regexUiEvents = /^(focus|blur|select|change|reset)$|^key(press|down|up)$/, regexHtmlEvents = /^(scroll|resize|(un)?load|abort|error)$/; - function isNonBlocking(el) { - return el.classList.contains('nonblock'); + function getNonBlocking(el) { + var nonblock = el; + while (nonblock) { + if (nonblock.classList && nonblock.classList.contains('nonblock')) { + return nonblock; + } + nonblock = nonblock.parentNode; + } + return false; } function isNotPropagating(el) { - return el.classList.contains('nonblock-stoppropagation'); + return el.classList.contains('nonblock-stop-propagation'); + } + + function isActionNotPropagating(el) { + return !el.classList.contains('nonblock-allow-action-propagation'); } function getCursor(el) { @@ -65,6 +76,9 @@ } function setCursor(el, value) { + if (el.classList.contains('nonblock-cursor-' + value)) { + return; + } remCursor(el); el.classList.add('nonblock-cursor-' + value); } @@ -78,93 +92,151 @@ } document.body.addEventListener('mouseenter', function (ev) { - if (isNonBlocking(ev.target)) { - nonBlockLastElem = ev.target; - if (isNotPropagating(ev.target)) { + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonBlockLastElem = nonblock; + if (isNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('mouseleave', function (ev) { - if (isNonBlocking(ev.target)) { - remCursor(ev.target); + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + remCursor(nonblock); nonBlockLastElem = null; isSelectingText = false; - if (isNotPropagating(ev.target)) { + if (isNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('mouseover', function (ev) { - if (isNonBlocking(ev.target) && isNotPropagating(ev.target)) { + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target)) && isNotPropagating(nonblock)) { ev.stopPropagation(); } }, true); document.body.addEventListener('mouseout', function (ev) { - if (isNonBlocking(ev.target) && isNotPropagating(ev.target)) { + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target)) && isNotPropagating(nonblock)) { ev.stopPropagation(); } }, true); document.body.addEventListener('mousemove', function (ev) { - if (isNonBlocking(ev.target)) { - nonblockPass(ev.target, ev, 'onmousemove'); + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'onmousemove'); // If the user just clicks somewhere, we don't want to select text, so this // detects that the user moved their mouse. if (isSelectingText === null) { window.getSelection().removeAllRanges(); isSelectingText = true; } - if (isNotPropagating(ev.target)) { + if (isNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('mousedown', function (ev) { - if (isNonBlocking(ev.target)) { - ev.preventDefault(); - nonblockPass(ev.target, ev, 'onmousedown'); + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'onmousedown'); isSelectingText = null; - if (isNotPropagating(ev.target)) { + if (isNotPropagating(nonblock) || isActionNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('mouseup', function (ev) { - if (isNonBlocking(ev.target)) { - ev.preventDefault(); - nonblockPass(ev.target, ev, 'onmouseup'); + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'onmouseup'); if (isSelectingText === null) { window.getSelection().removeAllRanges(); } isSelectingText = false; - if (isNotPropagating(ev.target)) { + if (isNotPropagating(nonblock) || isActionNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('click', function (ev) { - if (isNonBlocking(ev.target)) { - nonblockPass(ev.target, ev, 'onclick'); - if (isNotPropagating(ev.target)) { + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'onclick'); + if (isNotPropagating(nonblock) || isActionNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('dblclick', function (ev) { - if (isNonBlocking(ev.target)) { - nonblockPass(ev.target, ev, 'ondblclick'); - if (isNotPropagating(ev.target)) { + var nonblock = void 0; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'ondblclick'); + if (isNotPropagating(nonblock) || isActionNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); // Fire a DOM event. + var useEventConstructors = true; + try { + var e = new MouseEvent('click'); + } catch (e) { + useEventConstructors = false; + } var domEvent = function domEvent(elem, event, origEvent, bubbles) { var eventObject = void 0; event = event.toLowerCase(); - if (document.createEvent && elem.dispatchEvent) { - // FireFox, Opera, Safari, Chrome + if (useEventConstructors) { + // New browsers + event = event.replace(regexOn, ''); + if (event.match(regexMouseEvents)) { + eventObject = new MouseEvent(event, { + screenX: origEvent.screenX, + screenY: origEvent.screenY, + clientX: origEvent.clientX, + clientY: origEvent.clientY, + ctrlKey: origEvent.ctrlKey, + shiftKey: origEvent.shiftKey, + altKey: origEvent.altKey, + metaKey: origEvent.metaKey, + button: origEvent.button, + buttons: origEvent.buttons, + relatedTarget: origEvent.relatedTarget, + region: origEvent.region, + + detail: origEvent.detail, + view: origEvent.view, + + bubbles: bubbles === undefined ? origEvent.bubbles : bubbles, + cancelable: origEvent.cancelable, + composed: origEvent.composed + }); + } else if (event.match(regexUiEvents)) { + eventObject = new UIEvent(event, { + detail: origEvent.detail, + view: origEvent.view, + + bubbles: bubbles === undefined ? origEvent.bubbles : bubbles, + cancelable: origEvent.cancelable, + composed: origEvent.composed + }); + } else if (event.match(regexHtmlEvents)) { + eventObject = new Event(event, { + bubbles: bubbles === undefined ? origEvent.bubbles : bubbles, + cancelable: origEvent.cancelable, + composed: origEvent.composed + }); + } + if (!eventObject) { + return; + } + elem.dispatchEvent(eventObject); + } else if (document.createEvent && elem.dispatchEvent) { + // Old method for FireFox, Opera, Safari, Chrome event = event.replace(regexOn, ''); if (event.match(regexMouseEvents)) { // This allows the click event to fire on the notice. There is @@ -181,7 +253,7 @@ } if (!eventObject) { return; - }; + } elem.dispatchEvent(eventObject); } else { // Internet Explorer @@ -204,12 +276,12 @@ offset = void 0; if (document.caretPositionFromPoint) { range = document.caretPositionFromPoint(event.clientX, event.clientY); - textNode = range.offsetNode; - offset = range.offset; + textNode = range ? range.offsetNode : null; + offset = range ? range.offset : null; } else if (document.caretRangeFromPoint) { range = document.caretRangeFromPoint(event.clientX, event.clientY); - textNode = range.startContainer; - offset = range.startOffset; + textNode = range ? range.startContainer : null; + offset = range ? range.startOffset : null; } if (range) { whitespaceBefore = range.startContainer.textContent.match(/^[\s\n]*/)[0]; @@ -221,7 +293,7 @@ isOverTextNode = false; if (cursorStyle === 'auto' && elBelow.tagName === 'A') { cursorStyle = 'pointer'; - } else if ((!whitespaceBefore.length || offset > whitespaceBefore.length) && offset < text.length) { + } else if (range && (!whitespaceBefore.length || offset > whitespaceBefore.length) && offset < text.length) { if (cursorStyle === 'auto') { cursorStyle = 'text'; } diff --git a/NonBlock.js b/NonBlock.js index 0e83e0e..ed29fad 100644 --- a/NonBlock.js +++ b/NonBlock.js @@ -20,42 +20,42 @@ .nonblock{transition:opacity .3s ease;} .nonblock:hover{opacity:.1 !important;} .nonblock-hide{display:none !important;} - .nonblock-cursor-auto{cursor:auto;} - .nonblock-cursor-default{cursor:default;} - .nonblock-cursor-none{cursor:none;} - .nonblock-cursor-context-menu{cursor:context-menu;} - .nonblock-cursor-help{cursor:help;} - .nonblock-cursor-pointer{cursor:pointer;} - .nonblock-cursor-progress{cursor:progress;} - .nonblock-cursor-wait{cursor:wait;} - .nonblock-cursor-cell{cursor:cell;} - .nonblock-cursor-crosshair{cursor:crosshair;} - .nonblock-cursor-text{cursor:text;} - .nonblock-cursor-vertical-text{cursor:vertical-text;} - .nonblock-cursor-alias{cursor:alias;} - .nonblock-cursor-copy{cursor:copy;} - .nonblock-cursor-move{cursor:move;} - .nonblock-cursor-no-drop{cursor:no-drop;} - .nonblock-cursor-not-allowed{cursor:not-allowed;} - .nonblock-cursor-all-scroll{cursor:all-scroll;} - .nonblock-cursor-col-resize{cursor:col-resize;} - .nonblock-cursor-row-resize{cursor:row-resize;} - .nonblock-cursor-n-resize{cursor:n-resize;} - .nonblock-cursor-e-resize{cursor:e-resize;} - .nonblock-cursor-s-resize{cursor:s-resize;} - .nonblock-cursor-w-resize{cursor:w-resize;} - .nonblock-cursor-ne-resize{cursor:ne-resize;} - .nonblock-cursor-nw-resize{cursor:nw-resize;} - .nonblock-cursor-se-resize{cursor:se-resize;} - .nonblock-cursor-sw-resize{cursor:sw-resize;} - .nonblock-cursor-ew-resize{cursor:ew-resize;} - .nonblock-cursor-ns-resize{cursor:ns-resize;} - .nonblock-cursor-nesw-resize{cursor:nesw-resize;} - .nonblock-cursor-nwse-resize{cursor:nwse-resize;} - .nonblock-cursor-zoom-in{cursor:zoom-in;} - .nonblock-cursor-zoom-out{cursor:zoom-out;} - .nonblock-cursor-grab{cursor:grab;} - .nonblock-cursor-grabbing{cursor:grabbing;} + .nonblock-cursor-auto{cursor:auto !important;} + .nonblock-cursor-default{cursor:default !important;} + .nonblock-cursor-none{cursor:none !important;} + .nonblock-cursor-context-menu{cursor:context-menu !important;} + .nonblock-cursor-help{cursor:help !important;} + .nonblock-cursor-pointer{cursor:pointer !important;} + .nonblock-cursor-progress{cursor:progress !important;} + .nonblock-cursor-wait{cursor:wait !important;} + .nonblock-cursor-cell{cursor:cell !important;} + .nonblock-cursor-crosshair{cursor:crosshair !important;} + .nonblock-cursor-text{cursor:text !important;} + .nonblock-cursor-vertical-text{cursor:vertical-text !important;} + .nonblock-cursor-alias{cursor:alias !important;} + .nonblock-cursor-copy{cursor:copy !important;} + .nonblock-cursor-move{cursor:move !important;} + .nonblock-cursor-no-drop{cursor:no-drop !important;} + .nonblock-cursor-not-allowed{cursor:not-allowed !important;} + .nonblock-cursor-all-scroll{cursor:all-scroll !important;} + .nonblock-cursor-col-resize{cursor:col-resize !important;} + .nonblock-cursor-row-resize{cursor:row-resize !important;} + .nonblock-cursor-n-resize{cursor:n-resize !important;} + .nonblock-cursor-e-resize{cursor:e-resize !important;} + .nonblock-cursor-s-resize{cursor:s-resize !important;} + .nonblock-cursor-w-resize{cursor:w-resize !important;} + .nonblock-cursor-ne-resize{cursor:ne-resize !important;} + .nonblock-cursor-nw-resize{cursor:nw-resize !important;} + .nonblock-cursor-se-resize{cursor:se-resize !important;} + .nonblock-cursor-sw-resize{cursor:sw-resize !important;} + .nonblock-cursor-ew-resize{cursor:ew-resize !important;} + .nonblock-cursor-ns-resize{cursor:ns-resize !important;} + .nonblock-cursor-nesw-resize{cursor:nesw-resize !important;} + .nonblock-cursor-nwse-resize{cursor:nwse-resize !important;} + .nonblock-cursor-zoom-in{cursor:zoom-in !important;} + .nonblock-cursor-zoom-out{cursor:zoom-out !important;} + .nonblock-cursor-grab{cursor:grab !important;} + .nonblock-cursor-grabbing{cursor:grabbing !important;} `; if (styling.styleSheet) { styling.styleSheet.cssText = css; // IE @@ -76,12 +76,23 @@ regexUiEvents = /^(focus|blur|select|change|reset)$|^key(press|down|up)$/, regexHtmlEvents = /^(scroll|resize|(un)?load|abort|error)$/; - function isNonBlocking(el) { - return el.classList.contains('nonblock'); + function getNonBlocking(el) { + let nonblock = el; + while (nonblock) { + if (nonblock.classList && nonblock.classList.contains('nonblock')) { + return nonblock; + } + nonblock = nonblock.parentNode; + } + return false; } function isNotPropagating(el) { - return el.classList.contains('nonblock-stoppropagation'); + return el.classList.contains('nonblock-stop-propagation'); + } + + function isActionNotPropagating(el) { + return !el.classList.contains('nonblock-allow-action-propagation'); } function getCursor(el) { @@ -90,6 +101,9 @@ } function setCursor(el, value) { + if (el.classList.contains('nonblock-cursor-' + value)) { + return; + } remCursor(el); el.classList.add('nonblock-cursor-' + value); } @@ -103,116 +117,158 @@ } document.body.addEventListener('mouseenter', (ev) => { - if (isNonBlocking(ev.target)) { - nonBlockLastElem = ev.target; - if (isNotPropagating(ev.target)) { + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonBlockLastElem = nonblock; + if (isNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('mouseleave', (ev) => { - if (isNonBlocking(ev.target)) { - remCursor(ev.target); + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + remCursor(nonblock); nonBlockLastElem = null; isSelectingText = false; - if (isNotPropagating(ev.target)) { + if (isNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('mouseover', (ev) => { - if (isNonBlocking(ev.target) && isNotPropagating(ev.target)) { + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target)) && isNotPropagating(nonblock)) { ev.stopPropagation(); } }, true); document.body.addEventListener('mouseout', (ev) => { - if (isNonBlocking(ev.target) && isNotPropagating(ev.target)) { + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target)) && isNotPropagating(nonblock)) { ev.stopPropagation(); } }, true); document.body.addEventListener('mousemove', (ev) => { - if (isNonBlocking(ev.target)) { - nonblockPass(ev.target, ev, 'onmousemove'); + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'onmousemove'); // If the user just clicks somewhere, we don't want to select text, so this // detects that the user moved their mouse. if (isSelectingText === null) { window.getSelection().removeAllRanges(); isSelectingText = true; } - if (isNotPropagating(ev.target)) { + if (isNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('mousedown', (ev) => { - if (isNonBlocking(ev.target)) { - ev.preventDefault(); - nonblockPass(ev.target, ev, 'onmousedown'); + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'onmousedown'); isSelectingText = null; - if (isNotPropagating(ev.target)) { + if (isNotPropagating(nonblock) || isActionNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('mouseup', (ev) => { - if (isNonBlocking(ev.target)) { - ev.preventDefault(); - nonblockPass(ev.target, ev, 'onmouseup'); + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'onmouseup'); if (isSelectingText === null) { window.getSelection().removeAllRanges(); } isSelectingText = false; - if (isNotPropagating(ev.target)) { + if (isNotPropagating(nonblock) || isActionNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('click', (ev) => { - if (isNonBlocking(ev.target)) { - nonblockPass(ev.target, ev, 'onclick'); - if (isNotPropagating(ev.target)) { + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'onclick'); + if (isNotPropagating(nonblock) || isActionNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); document.body.addEventListener('dblclick', (ev) => { - if (isNonBlocking(ev.target)) { - nonblockPass(ev.target, ev, 'ondblclick'); - if (isNotPropagating(ev.target)) { + let nonblock; + if (ev.isTrusted && (nonblock = getNonBlocking(ev.target))) { + nonblockPass(nonblock, ev, 'ondblclick'); + if (isNotPropagating(nonblock) || isActionNotPropagating(nonblock)) { ev.stopPropagation(); } } }, true); // Fire a DOM event. + let useEventConstructors = true; + try { + const e = new MouseEvent('click'); + } catch (e) { + useEventConstructors = false; + } const domEvent = (elem, event, origEvent, bubbles) => { let eventObject; event = event.toLowerCase(); - if (document.createEvent && elem.dispatchEvent) { - // FireFox, Opera, Safari, Chrome + if (useEventConstructors) { + // New browsers + event = event.replace(regexOn, ''); + if (event.match(regexMouseEvents)) { + eventObject = new MouseEvent(event, { + screenX: origEvent.screenX, + screenY: origEvent.screenY, + clientX: origEvent.clientX, + clientY: origEvent.clientY, + ctrlKey: origEvent.ctrlKey, + shiftKey: origEvent.shiftKey, + altKey: origEvent.altKey, + metaKey: origEvent.metaKey, + button: origEvent.button, + buttons: origEvent.buttons, + relatedTarget: origEvent.relatedTarget, + region: origEvent.region, + + detail: origEvent.detail, + view: origEvent.view, + + bubbles: bubbles === undefined ? origEvent.bubbles : bubbles, + cancelable: origEvent.cancelable, + composed: origEvent.composed + }); + } else if (event.match(regexUiEvents)) { + eventObject = new UIEvent(event, { + detail: origEvent.detail, + view: origEvent.view, + + bubbles: bubbles === undefined ? origEvent.bubbles : bubbles, + cancelable: origEvent.cancelable, + composed: origEvent.composed + }); + } else if (event.match(regexHtmlEvents)) { + eventObject = new Event(event, { + bubbles: bubbles === undefined ? origEvent.bubbles : bubbles, + cancelable: origEvent.cancelable, + composed: origEvent.composed + }); + } + if (!eventObject) { + return; + } + elem.dispatchEvent(eventObject); + } else if (document.createEvent && elem.dispatchEvent) { + // Old method for FireFox, Opera, Safari, Chrome event = event.replace(regexOn, ''); if (event.match(regexMouseEvents)) { // This allows the click event to fire on the notice. There is // probably a much better way to do it. elem.getBoundingClientRect(); eventObject = document.createEvent("MouseEvents"); - eventObject.initMouseEvent( - event, - bubbles === undefined ? origEvent.bubbles : bubbles, - origEvent.cancelable, - origEvent.view, - origEvent.detail, - origEvent.screenX, - origEvent.screenY, - origEvent.clientX, - origEvent.clientY, - origEvent.ctrlKey, - origEvent.altKey, - origEvent.shiftKey, - origEvent.metaKey, - origEvent.button, - origEvent.relatedTarget - ); + eventObject.initMouseEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable, origEvent.view, origEvent.detail, origEvent.screenX, origEvent.screenY, origEvent.clientX, origEvent.clientY, origEvent.ctrlKey, origEvent.altKey, origEvent.shiftKey, origEvent.metaKey, origEvent.button, origEvent.relatedTarget); } else if (event.match(regexUiEvents)) { eventObject = document.createEvent("UIEvents"); eventObject.initUIEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable, origEvent.view, origEvent.detail); @@ -221,8 +277,8 @@ eventObject.initEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable); } if (!eventObject) { - return - }; + return; + } elem.dispatchEvent(eventObject); } else { // Internet Explorer @@ -241,12 +297,12 @@ let range, textNode, whitespaceBefore, text, offset; if (document.caretPositionFromPoint) { range = document.caretPositionFromPoint(event.clientX, event.clientY); - textNode = range.offsetNode; - offset = range.offset; + textNode = range ? range.offsetNode : null; + offset = range ? range.offset : null; } else if (document.caretRangeFromPoint) { range = document.caretRangeFromPoint(event.clientX, event.clientY); - textNode = range.startContainer; - offset = range.startOffset; + textNode = range ? range.startContainer : null; + offset = range ? range.startOffset : null; } if (range) { whitespaceBefore = range.startContainer.textContent.match(/^[\s\n]*/)[0]; @@ -258,7 +314,7 @@ isOverTextNode = false; if (cursorStyle === 'auto' && elBelow.tagName === 'A') { cursorStyle = 'pointer'; - } else if ((!whitespaceBefore.length || offset > whitespaceBefore.length) && offset < text.length) { + } else if (range && (!whitespaceBefore.length || offset > whitespaceBefore.length) && offset < text.length) { if (cursorStyle === 'auto') { cursorStyle = 'text'; } diff --git a/README.md b/README.md index f796b0e..7a990fc 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,11 @@ npm install --save nonblockjs Add the class `nonblock` to any element you want to make nonblocking. -Add the class `nonblock-stoppropagation` if you want NonBlock.js to stop event propagation for mouse events, effectively disguising it from its ancestors. +By default, NonBlock.js will propagate mouse events that are unrelated to clicking the mouse. + +Add the class `nonblock-stop-propagation` if you want NonBlock.js to stop event propagation for all mouse events, effectively disguising it from its ancestors. + +Add the class `nonblock-allow-action-propagation` if you want NonBlock.js to allow event propagation for action events (related to clicking the mouse). This may cause components that are designed to open on mouse clicks (like dropdown menus) to detect the click on the nonblocking element and mistakenly assume the user has clicked elsewhere and make the component inaccessible (close the menu). ## Demos diff --git a/index.html b/index.html index a5c7a8c..4d94884 100644 --- a/index.html +++ b/index.html @@ -97,11 +97,11 @@

- class="nonblock nonblock-stoppropagation" + class="nonblock nonblock-stop-propagation"