diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..aeb709a --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,19 @@ +### Expected behaviour + +I thought that by going to the page '...' and pressing the button '...' then '...' would happen. + +### Actual behaviour + +Instead of '...', what I saw was that '...' happened instead. + +### Steps to reproduce + +1. Go this JSFiddle, JSBin, CodePen, etc +2. Click on '....' +3. Scroll down to '....' +4. Refresh the page and wait 5 secs +5. Finally the error will magically happen (if it is raining) + +### Browsers affected + +I tested on all major browsers and only IE 11 does not work. diff --git a/bower.json b/bower.json index 5fa2b30..93937a4 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "clipboard", - "version": "1.5.2", + "version": "1.5.8", "description": "Modern copy to clipboard. No Flash. Just 2kb", "license": "MIT", "main": "dist/clipboard.js", diff --git a/demo/constructor-node.html b/demo/constructor-node.html index f84857a..c118772 100644 --- a/demo/constructor-node.html +++ b/demo/constructor-node.html @@ -6,7 +6,9 @@ - +
+ Copy +
diff --git a/dist/clipboard.js b/dist/clipboard.js index 97d6cd3..24dbac6 100644 --- a/dist/clipboard.js +++ b/dist/clipboard.js @@ -1,5 +1,5 @@ /*! - * clipboard.js v1.5.2 + * clipboard.js v1.5.8 * https://zenorocha.github.io/clipboard.js * * Licensed MIT © Zeno Rocha @@ -16,48 +16,7 @@ module.exports = function (element, selector, checkYoSelf) { } } -},{"matches-selector":2}],2:[function(require,module,exports){ - -/** - * Element prototype. - */ - -var proto = Element.prototype; - -/** - * Vendor function. - */ - -var vendor = proto.matchesSelector - || proto.webkitMatchesSelector - || proto.mozMatchesSelector - || proto.msMatchesSelector - || proto.oMatchesSelector; - -/** - * Expose `match()`. - */ - -module.exports = match; - -/** - * Match `el` to `selector`. - * - * @param {Element} el - * @param {String} selector - * @return {Boolean} - * @api public - */ - -function match(el, selector) { - if (vendor) return vendor.call(el, selector); - var nodes = el.parentNode.querySelectorAll(selector); - for (var i = 0; i < nodes.length; ++i) { - if (nodes[i] == el) return true; - } - return false; -} -},{}],3:[function(require,module,exports){ +},{"matches-selector":5}],2:[function(require,module,exports){ var closest = require('closest'); /** @@ -67,16 +26,17 @@ var closest = require('closest'); * @param {String} selector * @param {String} type * @param {Function} callback + * @param {Boolean} useCapture * @return {Object} */ -function delegate(element, selector, type, callback) { +function delegate(element, selector, type, callback, useCapture) { var listenerFn = listener.apply(this, arguments); - element.addEventListener(type, listenerFn); + element.addEventListener(type, listenerFn, useCapture); return { destroy: function() { - element.removeEventListener(type, listenerFn); + element.removeEventListener(type, listenerFn, useCapture); } } } @@ -92,13 +52,9 @@ function delegate(element, selector, type, callback) { */ function listener(element, selector, type, callback) { return function(e) { - var delegateTarget = closest(e.target, selector, true); - - if (delegateTarget) { - Object.defineProperty(e, 'target', { - value: delegateTarget - }); + e.delegateTarget = closest(e.target, selector, true); + if (e.delegateTarget) { callback.call(element, e); } } @@ -106,7 +62,7 @@ function listener(element, selector, type, callback) { module.exports = delegate; -},{"closest":1}],4:[function(require,module,exports){ +},{"closest":1}],3:[function(require,module,exports){ /** * Check if argument is a HTML element. * @@ -151,13 +107,13 @@ exports.string = function(value) { * @param {Object} value * @return {Boolean} */ -exports.function = function(value) { +exports.fn = function(value) { var type = Object.prototype.toString.call(value); return type === '[object Function]'; }; -},{}],5:[function(require,module,exports){ +},{}],4:[function(require,module,exports){ var is = require('./is'); var delegate = require('delegate'); @@ -179,7 +135,7 @@ function listen(target, type, callback) { throw new TypeError('Second argument must be a String'); } - if (!is.function(callback)) { + if (!is.fn(callback)) { throw new TypeError('Third argument must be a Function'); } @@ -254,16 +210,62 @@ function listenSelector(selector, type, callback) { module.exports = listen; -},{"./is":4,"delegate":3}],6:[function(require,module,exports){ +},{"./is":3,"delegate":2}],5:[function(require,module,exports){ + +/** + * Element prototype. + */ + +var proto = Element.prototype; + +/** + * Vendor function. + */ + +var vendor = proto.matchesSelector + || proto.webkitMatchesSelector + || proto.mozMatchesSelector + || proto.msMatchesSelector + || proto.oMatchesSelector; + +/** + * Expose `match()`. + */ + +module.exports = match; + +/** + * Match `el` to `selector`. + * + * @param {Element} el + * @param {String} selector + * @return {Boolean} + * @api public + */ + +function match(el, selector) { + if (vendor) return vendor.call(el, selector); + var nodes = el.parentNode.querySelectorAll(selector); + for (var i = 0; i < nodes.length; ++i) { + if (nodes[i] == el) return true; + } + return false; +} +},{}],6:[function(require,module,exports){ function select(element) { var selectedText; if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { - element.select(); + element.focus(); + element.setSelectionRange(0, element.value.length); selectedText = element.value; } else { + if (element.hasAttribute('contenteditable')) { + element.focus(); + } + var selection = window.getSelection(); var range = document.createRange(); @@ -421,6 +423,8 @@ var ClipboardAction = (function () { ClipboardAction.prototype.selectFake = function selectFake() { var _this = this; + var isRTL = document.documentElement.getAttribute('dir') == 'rtl'; + this.removeFake(); this.fakeHandler = document.body.addEventListener('click', function () { @@ -428,8 +432,16 @@ var ClipboardAction = (function () { }); this.fakeElem = document.createElement('textarea'); + // Prevent zooming on iOS + this.fakeElem.style.fontSize = '12pt'; + // Reset box model + this.fakeElem.style.border = '0'; + this.fakeElem.style.padding = '0'; + this.fakeElem.style.margin = '0'; + // Move element out of screen horizontally this.fakeElem.style.position = 'absolute'; - this.fakeElem.style.left = '-9999px'; + this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; + // Move element to the same position vertically this.fakeElem.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px'; this.fakeElem.setAttribute('readonly', ''); this.fakeElem.value = this.text; @@ -665,15 +677,17 @@ var Clipboard = (function (_Emitter) { */ Clipboard.prototype.onClick = function onClick(e) { + var trigger = e.delegateTarget || e.currentTarget; + if (this.clipboardAction) { this.clipboardAction = null; } this.clipboardAction = new _clipboardAction2['default']({ - action: this.action(e.target), - target: this.target(e.target), - text: this.text(e.target), - trigger: e.target, + action: this.action(trigger), + target: this.target(trigger), + text: this.text(trigger), + trigger: trigger, emitter: this }); }; @@ -725,6 +739,7 @@ var Clipboard = (function (_Emitter) { return Clipboard; })(_tinyEmitter2['default']); +exports['default'] = Clipboard; function getAttributeValue(suffix, element) { var attribute = 'data-clipboard-' + suffix; @@ -734,9 +749,7 @@ function getAttributeValue(suffix, element) { return element.getAttribute(attribute); } - -exports['default'] = Clipboard; module.exports = exports['default']; -},{"./clipboard-action":8,"good-listener":5,"tiny-emitter":7}]},{},[9])(9) +},{"./clipboard-action":8,"good-listener":4,"tiny-emitter":7}]},{},[9])(9) }); \ No newline at end of file diff --git a/dist/clipboard.min.js b/dist/clipboard.min.js index 4ab5cc4..15210c8 100644 --- a/dist/clipboard.min.js +++ b/dist/clipboard.min.js @@ -1,7 +1,7 @@ /*! - * clipboard.js v1.5.2 + * clipboard.js v1.5.8 * https://zenorocha.github.io/clipboard.js * * Licensed MIT © Zeno Rocha */ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,r){function o(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;ar;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;nr;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;n this.removeFake()); this.fakeElem = document.createElement('textarea'); + // Prevent zooming on iOS + this.fakeElem.style.fontSize = '12pt'; + // Reset box model + this.fakeElem.style.border = '0'; + this.fakeElem.style.padding = '0'; + this.fakeElem.style.margin = '0'; + // Move element out of screen horizontally this.fakeElem.style.position = 'absolute'; - this.fakeElem.style.left = '-9999px'; + this.fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px'; + // Move element to the same position vertically this.fakeElem.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px'; this.fakeElem.setAttribute('readonly', ''); this.fakeElem.value = this.text; @@ -192,5 +202,3 @@ class ClipboardAction { this.removeFake(); } } - -export default ClipboardAction; diff --git a/src/clipboard.js b/src/clipboard.js index da513be..517091a 100644 --- a/src/clipboard.js +++ b/src/clipboard.js @@ -6,7 +6,7 @@ import listen from 'good-listener'; * Base class which takes one or more elements, adds event listeners to them, * and instantiates a new `ClipboardAction` on each click. */ -class Clipboard extends Emitter { +export default class Clipboard extends Emitter { /** * @param {String|HTMLElement|HTMLCollection|NodeList} trigger * @param {Object} options @@ -42,15 +42,17 @@ class Clipboard extends Emitter { * @param {Event} e */ onClick(e) { + let trigger = e.delegateTarget || e.currentTarget; + if (this.clipboardAction) { this.clipboardAction = null; } this.clipboardAction = new ClipboardAction({ - action : this.action(e.target), - target : this.target(e.target), - text : this.text(e.target), - trigger : e.target, + action : this.action(trigger), + target : this.target(trigger), + text : this.text(trigger), + trigger : trigger, emitter : this }); } @@ -111,5 +113,3 @@ function getAttributeValue(suffix, element) { return element.getAttribute(attribute); } - -export default Clipboard; diff --git a/test/clipboard-action.js b/test/clipboard-action.js index b1b5781..4797abb 100644 --- a/test/clipboard-action.js +++ b/test/clipboard-action.js @@ -57,6 +57,19 @@ describe('ClipboardAction', () => { done(); } }); + + it('should set the position right style property', done => { + // Set document direction + document.documentElement.setAttribute('dir', 'rtl'); + + let clip = new ClipboardAction({ + emitter: new Emitter(), + text: 'foo' + }); + + assert.equal(clip.fakeElem.style.right, '-9999px'); + done(); + }); }); describe('#set action', () => { diff --git a/test/clipboard.js b/test/clipboard.js index c3ffcea..e35a7c5 100644 --- a/test/clipboard.js +++ b/test/clipboard.js @@ -9,8 +9,14 @@ describe('Clipboard', () => { global.button.setAttribute('data-clipboard-text', 'foo'); document.body.appendChild(global.button); + global.span = document.createElement('span'); + global.span.innerHTML = 'bar'; + + global.button.appendChild(span); + global.event = { - target: global.button + target: global.button, + currentTarget: global.button }; }); @@ -63,7 +69,15 @@ describe('Clipboard', () => { assert.instanceOf(clipboard.clipboardAction, ClipboardAction); }); - it('should throws exception target', done => { + it('should use an event\'s currentTarget when not equal to target', () => { + let clipboard = new Clipboard('.btn'); + let bubbledEvent = { target: global.span, currentTarget: global.button }; + + clipboard.onClick(bubbledEvent); + assert.instanceOf(clipboard.clipboardAction, ClipboardAction); + }); + + it('should throw an exception when target is invalid', done => { try { var clipboard = new Clipboard('.btn', { target: function() {