Compare commits

..

48 Commits

Author SHA1 Message Date
Zeno Rocha
a4ab305297 Release v1.5.11 2016-06-08 21:46:20 -07:00
Zeno Rocha
294e9fcb5d Fix fake element position from "fixed" to "absolute" #194 #250 2016-06-08 21:44:54 -07:00
Ali Bozorgkhan
79c3361ca4 Press Ctrl+C to copy -> Press Command+C to copy (#252)
As Safari users are mostly using mac, coping a text needs Command+C instead of Ctrl+C
2016-06-02 15:28:16 -07:00
grabus
c3fefc1fc0 Fix memory leak because of fakeHandlers (#243)
addEventListener always returns undefined by spec
https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget-addEventListener
2016-05-26 11:35:18 -07:00
Zeno Rocha
42bd266f0b Adds basic JSFiddle to help people reproduce their bugs 2016-05-01 16:14:51 -07:00
Zeno Rocha
53a733fcb5 Release v1.5.10 2016-04-03 22:33:02 -07:00
Zeno Rocha
7a5a910bcd Removes error message when target/text attributes are null, undefined, or false 2016-04-03 22:16:22 -07:00
Zeno Rocha
0163f7cb72 Merge pull request #216 from sebastianekstrom/master
Use const instead of let when applicable
2016-04-01 08:48:49 -07:00
Sebastian Ekström
941bdbb16e Use const instead of let when applicable 2016-04-01 13:17:12 +02:00
Zeno Rocha
00d5cc4e96 Updates tagline #210 2016-03-17 09:12:23 -07:00
Zeno Rocha
07a1f8456b Throws error when disabled or readonly attributes are used on target element
Fixes #41
2016-03-01 15:38:25 -08:00
Zeno Rocha
ff3cd2c722 Release v1.5.9 2016-02-26 10:26:32 -08:00
Zeno Rocha
d346f30e5d Merge pull request #190 from heldr/master
update modules and fix #176
2016-02-25 12:19:22 -08:00
Helder Santana
e5fe34c524 WTF?! npm 2016-02-25 15:17:07 -05:00
Zeno Rocha
0a6aace544 Merge pull request #194 from stepancar/patch-1
minor fix for fake elem
2016-02-25 12:06:38 -08:00
Stepan Mikhaylyuk
43d9c11aaf minor fix for fake elem
after call clipboard all page scrolls down. As i understand, its relates to fake element, cause its append to body.
I just set position to 'fixed' and now it works right. I think 'fixed' is more css-style independence then 'absolute'
2016-02-25 15:20:13 +03:00
Helder Santana
90a52149ed fix babel 6 export default behaviour 2016-02-20 01:41:40 -05:00
Helder Santana
83b9d6a84d update modules and fix #176 2016-02-20 01:14:33 -05:00
Zeno Rocha
3d13baa385 Adds issue template 2016-02-18 11:40:00 -08:00
Zeno Rocha
7e37c95121 Release v1.5.8 2016-02-04 11:28:33 -08:00
Zeno Rocha
bc9bcdd678 Adds explanations on textarea styling #143 #179 #180 2016-02-04 11:25:00 -08:00
Zeno Rocha
4c9e29a0dc Resets invisible textarea's paddings and margins #179 2016-02-04 11:09:10 -08:00
speedplane
bd6dc9eb9f Prevent auto-zooming on an iphone by making the text area size appropriate size. Fixes Issue 180. 2016-02-04 11:12:04 -05:00
Zeno Rocha
a88bb77be4 Release v1.5.7 2016-02-03 08:31:41 -08:00
Zeno Rocha
e86dc2caa2 Revert "Upgrades to Babel 6"
This reverts commit ee1eb455f4.
2016-02-03 08:22:35 -08:00
Zeno Rocha
8392a7ba1c Release v1.5.6 2016-02-02 13:44:26 -08:00
RAFIE Younes
31e3622e17 Fixed RTL issue #161 2016-02-02 13:27:11 -08:00
Zeno Rocha
ee1eb455f4 Upgrades to Babel 6 2016-02-02 13:11:25 -08:00
Zeno Rocha
d50f2fb73f Updates good-listener
Fixes #170
2016-02-02 12:52:17 -08:00
Zeno Rocha
c8221c742d Serves the unminified version for Meteor
Fixes #167
2016-01-21 08:21:44 -08:00
Zeno Rocha
efc22cf651 Merge pull request #150 from nettofarah/patch-1
Add a bit more clarity to Invalid Target test
2015-12-17 10:48:14 -08:00
Netto Farah
4b27a72dce Add a bit more clarity to Invalid Target test 2015-12-13 13:11:02 -08:00
Zeno Rocha
c12c610b22 Merge pull request #134 from hmln/minor-readme-change
Fix typo in readme.
2015-11-19 07:21:32 -08:00
hmln
1d772a0cbe Fix typo in readme. 2015-11-19 00:38:20 -03:00
Zeno Rocha
d9254459b7 Merge pull request #131 from oskarcieslik/es2015_minor_fixes
Small ES2015 changes
2015-11-14 12:25:53 -06:00
Oskar Cieslik (rpawfuiml)
2aa163b1d0 Small ES2015 changes ;). 2015-11-13 22:01:59 +01:00
Zeno Rocha
783dc6f3cf Adds devDependencies since peerDependencies does not work anymore 2015-11-13 01:39:19 -08:00
Zeno Rocha
3188ffbce3 Release v1.5.5 2015-11-13 01:20:15 -08:00
Zeno Rocha
5dba68634e Fixes a bug where non-editable elements were being focused 2015-11-13 01:20:02 -08:00
Zeno Rocha
a12a056ef6 Release v1.5.4 2015-11-13 00:25:35 -08:00
Zeno Rocha
cb4301658c Updates delegate package which now exposes e.delegateTarget property #120 2015-11-13 00:24:46 -08:00
Zeno Rocha
5efcdf2810 Merge pull request #120 from pawlufelice/fix_event_target
use event's currentTarget instead of target
2015-11-13 00:20:20 -08:00
Zeno Rocha
72926580c3 Updates meteor version #123 2015-11-12 09:58:10 -08:00
Zeno Rocha
4967f118fe Release v1.5.3 2015-11-12 09:57:35 -08:00
Zeno Rocha
44d59b34a2 Merge pull request #123 from alippai/patch-1
Bump npm version to match the bower version.
2015-11-12 09:57:29 -08:00
Ádám Lippai
9d6375d20e Bump npm version to match the others. 2015-11-10 11:08:35 +01:00
Paul Felice
37136663df use event's currentTarget instead of target 2015-11-06 15:49:21 +01:00
Zeno Rocha
db575bb4df Removes babelify from being a dependency to a devDependency #15 #105 2015-10-28 13:06:14 -07:00
13 changed files with 518 additions and 484 deletions

4
.babelrc Normal file
View File

@@ -0,0 +1,4 @@
{
"presets": ["es2015-loose"],
"plugins": ["transform-es2015-modules-umd"]
}

15
.github/issue_template.md vendored Normal file
View File

@@ -0,0 +1,15 @@
### Minimal example
> Fork this [JSFiddle](https://jsfiddle.net/zenorocha/5kk0eysw/) and reproduce your issue.
### 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.
### Browsers affected
I tested on all major browsers and only IE 11 does not work.

View File

@@ -1,6 +1,6 @@
{
"name": "clipboard",
"version": "1.5.2",
"version": "1.5.11",
"description": "Modern copy to clipboard. No Flash. Just 2kb",
"license": "MIT",
"main": "dist/clipboard.js",

View File

@@ -6,7 +6,9 @@
</head>
<body>
<!-- 1. Define some markup -->
<button id="btn" data-clipboard-text="1">Copy</button>
<div id="btn" data-clipboard-text="1">
<span>Copy</span>
</div>
<!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script>

824
dist/clipboard.js vendored
View File

@@ -1,5 +1,5 @@
/*!
* clipboard.js v1.5.2
* clipboard.js v1.5.10
* 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();
@@ -348,395 +350,391 @@ E.prototype = {
module.exports = E;
},{}],8:[function(require,module,exports){
'use strict';
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(['module', 'select'], factory);
} else if (typeof exports !== "undefined") {
factory(module, require('select'));
} else {
var mod = {
exports: {}
};
factory(mod, global.select);
global.clipboardAction = mod.exports;
}
})(this, function (module, _select) {
'use strict';
exports.__esModule = true;
var _select2 = _interopRequireDefault(_select);
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
var _select = require('select');
var _select2 = _interopRequireDefault(_select);
/**
* Inner class which performs selection from either `text` or `target`
* properties and then executes copy or cut operations.
*/
var ClipboardAction = (function () {
/**
* @param {Object} options
*/
function ClipboardAction(options) {
_classCallCheck(this, ClipboardAction);
this.resolveOptions(options);
this.initSelection();
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
/**
* Defines base properties passed from constructor.
* @param {Object} options
*/
ClipboardAction.prototype.resolveOptions = function resolveOptions() {
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
this.action = options.action;
this.emitter = options.emitter;
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
this.selectedText = '';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj;
};
/**
* Decides which selection strategy is going to be applied based
* on the existence of `text` and `target` properties.
*/
ClipboardAction.prototype.initSelection = function initSelection() {
if (this.text && this.target) {
throw new Error('Multiple attributes declared, use either "target" or "text"');
} else if (this.text) {
this.selectFake();
} else if (this.target) {
this.selectTarget();
} else {
throw new Error('Missing required attributes, use either "target" or "text"');
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
}
/**
* Creates a fake textarea element, sets its value from `text` property,
* and makes a selection on it.
*/
ClipboardAction.prototype.selectFake = function selectFake() {
var _this = this;
this.removeFake();
this.fakeHandler = document.body.addEventListener('click', function () {
return _this.removeFake();
});
this.fakeElem = document.createElement('textarea');
this.fakeElem.style.position = 'absolute';
this.fakeElem.style.left = '-9999px';
this.fakeElem.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.value = this.text;
document.body.appendChild(this.fakeElem);
this.selectedText = _select2['default'](this.fakeElem);
this.copyText();
};
/**
* Only removes the fake element after another click event, that way
* a user can hit `Ctrl+C` to copy because selection still exists.
*/
ClipboardAction.prototype.removeFake = function removeFake() {
if (this.fakeHandler) {
document.body.removeEventListener('click');
this.fakeHandler = null;
}
if (this.fakeElem) {
document.body.removeChild(this.fakeElem);
this.fakeElem = null;
}
};
/**
* Selects the content from element passed on `target` property.
*/
ClipboardAction.prototype.selectTarget = function selectTarget() {
this.selectedText = _select2['default'](this.target);
this.copyText();
};
/**
* Executes the copy operation based on the current selection.
*/
ClipboardAction.prototype.copyText = function copyText() {
var succeeded = undefined;
try {
succeeded = document.execCommand(this.action);
} catch (err) {
succeeded = false;
}
this.handleResult(succeeded);
};
/**
* Fires an event based on the copy operation result.
* @param {Boolean} succeeded
*/
ClipboardAction.prototype.handleResult = function handleResult(succeeded) {
if (succeeded) {
this.emitter.emit('success', {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
} else {
this.emitter.emit('error', {
action: this.action,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
}
};
/**
* Removes current selection and focus from `target` element.
*/
ClipboardAction.prototype.clearSelection = function clearSelection() {
if (this.target) {
this.target.blur();
}
window.getSelection().removeAllRanges();
};
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
* @param {String} action
*/
/**
* Destroy lifecycle.
*/
ClipboardAction.prototype.destroy = function destroy() {
this.removeFake();
};
_createClass(ClipboardAction, [{
key: 'action',
set: function set() {
var action = arguments.length <= 0 || arguments[0] === undefined ? 'copy' : arguments[0];
this._action = action;
if (this._action !== 'copy' && this._action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
var _createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
},
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var ClipboardAction = function () {
/**
* Gets the `action` property.
* @return {String}
* @param {Object} options
*/
get: function get() {
return this._action;
function ClipboardAction(options) {
_classCallCheck(this, ClipboardAction);
this.resolveOptions(options);
this.initSelection();
}
/**
* Sets the `target` property using an element
* that will be have its content copied.
* @param {Element} target
* Defines base properties passed from constructor.
* @param {Object} options
*/
}, {
key: 'target',
set: function set(target) {
if (target !== undefined) {
if (target && typeof target === 'object' && target.nodeType === 1) {
this._target = target;
} else {
throw new Error('Invalid "target" value, use a valid Element');
ClipboardAction.prototype.resolveOptions = function resolveOptions() {
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
this.action = options.action;
this.emitter = options.emitter;
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
this.selectedText = '';
};
ClipboardAction.prototype.initSelection = function initSelection() {
if (this.text) {
this.selectFake();
} else if (this.target) {
this.selectTarget();
}
};
ClipboardAction.prototype.selectFake = function selectFake() {
var _this = this;
var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
this.removeFake();
this.fakeHandler = document.body.addEventListener('click', function () {
return _this.removeFake();
}) || true;
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[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;
document.body.appendChild(this.fakeElem);
this.selectedText = (0, _select2.default)(this.fakeElem);
this.copyText();
};
ClipboardAction.prototype.removeFake = function removeFake() {
if (this.fakeHandler) {
document.body.removeEventListener('click');
this.fakeHandler = null;
}
if (this.fakeElem) {
document.body.removeChild(this.fakeElem);
this.fakeElem = null;
}
};
ClipboardAction.prototype.selectTarget = function selectTarget() {
this.selectedText = (0, _select2.default)(this.target);
this.copyText();
};
ClipboardAction.prototype.copyText = function copyText() {
var succeeded = undefined;
try {
succeeded = document.execCommand(this.action);
} catch (err) {
succeeded = false;
}
this.handleResult(succeeded);
};
ClipboardAction.prototype.handleResult = function handleResult(succeeded) {
if (succeeded) {
this.emitter.emit('success', {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
} else {
this.emitter.emit('error', {
action: this.action,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
}
};
ClipboardAction.prototype.clearSelection = function clearSelection() {
if (this.target) {
this.target.blur();
}
window.getSelection().removeAllRanges();
};
ClipboardAction.prototype.destroy = function destroy() {
this.removeFake();
};
_createClass(ClipboardAction, [{
key: 'action',
set: function set() {
var action = arguments.length <= 0 || arguments[0] === undefined ? 'copy' : arguments[0];
this._action = action;
if (this._action !== 'copy' && this._action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
}
},
get: function get() {
return this._action;
}
},
}, {
key: 'target',
set: function set(target) {
if (target !== undefined) {
if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
if (this.action === 'copy' && target.hasAttribute('disabled')) {
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
}
/**
* Gets the `target` property.
* @return {String|HTMLElement}
*/
get: function get() {
return this._target;
}
}]);
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
}
return ClipboardAction;
})();
this._target = target;
} else {
throw new Error('Invalid "target" value, use a valid Element');
}
}
},
get: function get() {
return this._target;
}
}]);
exports['default'] = ClipboardAction;
module.exports = exports['default'];
return ClipboardAction;
}();
module.exports = ClipboardAction;
});
},{"select":6}],9:[function(require,module,exports){
'use strict';
exports.__esModule = true;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var _clipboardAction = require('./clipboard-action');
var _clipboardAction2 = _interopRequireDefault(_clipboardAction);
var _tinyEmitter = require('tiny-emitter');
var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);
var _goodListener = require('good-listener');
var _goodListener2 = _interopRequireDefault(_goodListener);
/**
* Base class which takes one or more elements, adds event listeners to them,
* and instantiates a new `ClipboardAction` on each click.
*/
var Clipboard = (function (_Emitter) {
_inherits(Clipboard, _Emitter);
/**
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
* @param {Object} options
*/
function Clipboard(trigger, options) {
_classCallCheck(this, Clipboard);
_Emitter.call(this);
this.resolveOptions(options);
this.listenClick(trigger);
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], factory);
} else if (typeof exports !== "undefined") {
factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener'));
} else {
var mod = {
exports: {}
};
factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener);
global.clipboard = mod.exports;
}
})(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) {
'use strict';
var _clipboardAction2 = _interopRequireDefault(_clipboardAction);
var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);
var _goodListener2 = _interopRequireDefault(_goodListener);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
var Clipboard = function (_Emitter) {
_inherits(Clipboard, _Emitter);
/**
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
* @param {Object} options
*/
function Clipboard(trigger, options) {
_classCallCheck(this, Clipboard);
var _this = _possibleConstructorReturn(this, _Emitter.call(this));
_this.resolveOptions(options);
_this.listenClick(trigger);
return _this;
}
/**
* Defines if attributes would be resolved using internal setter functions
* or custom functions that were passed in the constructor.
* @param {Object} options
*/
Clipboard.prototype.resolveOptions = function resolveOptions() {
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
this.text = typeof options.text === 'function' ? options.text : this.defaultText;
};
Clipboard.prototype.listenClick = function listenClick(trigger) {
var _this2 = this;
this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) {
return _this2.onClick(e);
});
};
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(trigger),
target: this.target(trigger),
text: this.text(trigger),
trigger: trigger,
emitter: this
});
};
Clipboard.prototype.defaultAction = function defaultAction(trigger) {
return getAttributeValue('action', trigger);
};
Clipboard.prototype.defaultTarget = function defaultTarget(trigger) {
var selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
}
};
Clipboard.prototype.defaultText = function defaultText(trigger) {
return getAttributeValue('text', trigger);
};
Clipboard.prototype.destroy = function destroy() {
this.listener.destroy();
if (this.clipboardAction) {
this.clipboardAction.destroy();
this.clipboardAction = null;
}
};
return Clipboard;
}(_tinyEmitter2.default);
/**
* Helper function to retrieve attribute value.
* @param {String} suffix
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
var attribute = 'data-clipboard-' + suffix;
/**
* Defines if attributes would be resolved using internal setter functions
* or custom functions that were passed in the constructor.
* @param {Object} options
*/
Clipboard.prototype.resolveOptions = function resolveOptions() {
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
this.text = typeof options.text === 'function' ? options.text : this.defaultText;
};
/**
* Adds a click event listener to the passed trigger.
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
*/
Clipboard.prototype.listenClick = function listenClick(trigger) {
var _this = this;
this.listener = _goodListener2['default'](trigger, 'click', function (e) {
return _this.onClick(e);
});
};
/**
* Defines a new `ClipboardAction` on each click event.
* @param {Event} e
*/
Clipboard.prototype.onClick = function onClick(e) {
if (this.clipboardAction) {
this.clipboardAction = null;
if (!element.hasAttribute(attribute)) {
return;
}
this.clipboardAction = new _clipboardAction2['default']({
action: this.action(e.target),
target: this.target(e.target),
text: this.text(e.target),
trigger: e.target,
emitter: this
});
};
/**
* Default `action` lookup function.
* @param {Element} trigger
*/
Clipboard.prototype.defaultAction = function defaultAction(trigger) {
return getAttributeValue('action', trigger);
};
/**
* Default `target` lookup function.
* @param {Element} trigger
*/
Clipboard.prototype.defaultTarget = function defaultTarget(trigger) {
var selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
}
};
/**
* Default `text` lookup function.
* @param {Element} trigger
*/
Clipboard.prototype.defaultText = function defaultText(trigger) {
return getAttributeValue('text', trigger);
};
/**
* Destroy lifecycle.
*/
Clipboard.prototype.destroy = function destroy() {
this.listener.destroy();
if (this.clipboardAction) {
this.clipboardAction.destroy();
this.clipboardAction = null;
}
};
return Clipboard;
})(_tinyEmitter2['default']);
function getAttributeValue(suffix, element) {
var attribute = 'data-clipboard-' + suffix;
if (!element.hasAttribute(attribute)) {
return;
return element.getAttribute(attribute);
}
return element.getAttribute(attribute);
}
module.exports = Clipboard;
});
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)
});

File diff suppressed because one or more lines are too long

View File

@@ -3,10 +3,10 @@
Package.describe({
name: "zenorocha:clipboard",
summary: "Modern copy to clipboard. No Flash. Just 2kb.",
version: "1.5.1",
version: "1.5.11",
git: "https://github.com/zenorocha/clipboard.js"
});
Package.onUse(function(api) {
api.addFiles("dist/clipboard.min.js", "client");
api.addFiles("dist/clipboard.js", "client");
});

View File

@@ -1,51 +1,48 @@
{
"name": "clipboard",
"version": "1.5.2",
"version": "1.5.11",
"description": "Modern copy to clipboard. No Flash. Just 2kb",
"repository": "zenorocha/clipboard.js",
"license": "MIT",
"main": "lib/clipboard.js",
"browserify": {
"transform": [
[
"babelify",
{
"loose": "all"
}
]
]
},
"keywords": [
"clipboard",
"copy",
"cut"
],
"dependencies": {
"good-listener": "^1.1.2",
"select": "^1.0.4",
"good-listener": "^1.1.6",
"select": "^1.0.6",
"tiny-emitter": "^1.0.0"
},
"devDependencies": {
"babel": "^5.8.29",
"babelify": "^6.3.0",
"browserify": "^11.2.0",
"babel-cli": "^6.5.1",
"babel-core": "^6.5.2",
"babel-plugin-transform-es2015-modules-umd": "^6.5.0",
"babel-preset-es2015": "^6.5.0",
"babel-preset-es2015-loose": "^7.0.0",
"babelify": "^7.2.0",
"bannerify": "Vekat/bannerify#feature-option",
"browserify": "^13.0.0",
"chai": "^3.4.1",
"install": "^0.4.4",
"karma": "^0.13.10",
"karma-browserify": "^4.4.0",
"karma-browserify": "^5.0.1",
"karma-chai": "^0.1.0",
"karma-mocha": "^0.2.0",
"karma-phantomjs-launcher": "^0.2.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sinon": "^1.0.4",
"mocha": "^2.3.3",
"phantomjs-polyfill": "0.0.1",
"phantomjs-prebuilt": "^2.1.4",
"sinon": "^1.17.2",
"uglify-js": "^2.4.24",
"watchify": "^3.4.0",
"bannerify": "Vekat/bannerify#feature-option"
"watchify": "^3.4.0"
},
"scripts": {
"build": "npm run build-debug && npm run build-min",
"build-debug": "browserify src/clipboard.js -s Clipboard -p [bannerify --file .banner ] -o dist/clipboard.js",
"build-debug": "browserify src/clipboard.js -s Clipboard -t [babelify] -p [bannerify --file .banner ] -o dist/clipboard.js",
"build-min": "uglifyjs dist/clipboard.js --comments '/!/' -m screw_ie8=true -c screw_ie8=true,unused=false -o dist/clipboard.min.js",
"build-watch": "watchify src/clipboard.js -s Clipboard -o dist/clipboard.js -v",
"build-watch": "watchify src/clipboard.js -s Clipboard -t [babelify] -o dist/clipboard.js -v",
"test": "karma start --single-run",
"prepublish": "babel src --out-dir lib --loose all"
}

View File

@@ -3,7 +3,7 @@
[![Build Status](http://img.shields.io/travis/zenorocha/clipboard.js/master.svg?style=flat)](https://travis-ci.org/zenorocha/clipboard.js)
![Killing Flash](https://img.shields.io/badge/killing-flash-brightgreen.svg?style=flat)
> Modern copy to clipboard. No Flash. Just 2kb
> Modern copy to clipboard. No Flash. Just 3kb gzipped.
<a href="http://clipboardjs.com/"><img width="728" src="https://cloud.githubusercontent.com/assets/398893/9983535/5ab0a950-5fb4-11e5-9602-e73c0b661883.jpg" alt="Demo"></a>
@@ -151,7 +151,7 @@ new Clipboard('.btn', {
});
```
Also, with are working with single page apps, you may want to manage the lifecycle of the DOM more precisely. Here's how you clean up the events and objects that we create.
Also, if you are working with single page apps, you may want to manage the lifecycle of the DOM more precisely. Here's how you clean up the events and objects that we create.
```js
var clipboard = new Clipboard('.btn');
@@ -168,7 +168,7 @@ This library relies on both [Selection](https://developer.mozilla.org/en-US/docs
Although copy/cut operations with [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) aren't supported on Safari yet (including mobile), it gracefully degrades because [Selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection) is supported.
That means you can show a tooltip saying `Copied!` when `success` event is called and `Press Ctrl+C to copy` when `error` event is called because the text is already selected.
That means you can show a tooltip saying `Copied!` when `success` event is called and `Press Command+C to copy` when `error` event is called because the text is already selected.
For a live demonstration, open this [site](http://clipboardjs.com) on Safari.

View File

@@ -32,18 +32,12 @@ class ClipboardAction {
* on the existence of `text` and `target` properties.
*/
initSelection() {
if (this.text && this.target) {
throw new Error('Multiple attributes declared, use either "target" or "text"');
}
else if (this.text) {
if (this.text) {
this.selectFake();
}
else if (this.target) {
this.selectTarget();
}
else {
throw new Error('Missing required attributes, use either "target" or "text"');
}
}
/**
@@ -51,13 +45,23 @@ class ClipboardAction {
* and makes a selection on it.
*/
selectFake() {
const isRTL = document.documentElement.getAttribute('dir') == 'rtl';
this.removeFake();
this.fakeHandler = document.body.addEventListener('click', () => this.removeFake());
this.fakeHandler = document.body.addEventListener('click', () => this.removeFake()) || true;
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;
@@ -169,6 +173,14 @@ class ClipboardAction {
set target(target) {
if (target !== undefined) {
if (target && typeof target === 'object' && target.nodeType === 1) {
if (this.action === 'copy' && target.hasAttribute('disabled')) {
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
}
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
}
this._target = target;
}
else {
@@ -193,4 +205,4 @@ class ClipboardAction {
}
}
export default ClipboardAction;
module.exports = ClipboardAction;

View File

@@ -42,15 +42,17 @@ class Clipboard extends Emitter {
* @param {Event} e
*/
onClick(e) {
const 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
});
}
@@ -68,7 +70,7 @@ class Clipboard extends Emitter {
* @param {Element} trigger
*/
defaultTarget(trigger) {
let selector = getAttributeValue('target', trigger);
const selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
@@ -103,7 +105,7 @@ class Clipboard extends Emitter {
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
let attribute = `data-clipboard-${suffix}`;
const attribute = `data-clipboard-${suffix}`;
if (!element.hasAttribute(attribute)) {
return;
@@ -112,4 +114,4 @@ function getAttributeValue(suffix, element) {
return element.getAttribute(attribute);
}
export default Clipboard;
module.exports = Clipboard;

View File

@@ -35,27 +35,17 @@ describe('ClipboardAction', () => {
});
describe('#initSelection', () => {
it('should throw an error since both "text" and "target" were passed', done => {
try {
new ClipboardAction({
text: 'foo',
target: document.querySelector('#input')
});
}
catch(e) {
assert.equal(e.message, 'Multiple attributes declared, use either "target" or "text"');
done();
}
});
it('should set the position right style property', done => {
// Set document direction
document.documentElement.setAttribute('dir', 'rtl');
it('should throw an error since neither "text" nor "target" were passed', done => {
try {
new ClipboardAction();
}
catch(e) {
assert.equal(e.message, 'Missing required attributes, use either "target" or "text"');
done();
}
let clip = new ClipboardAction({
emitter: new Emitter(),
text: 'foo'
});
assert.equal(clip.fakeElem.style.right, '-9999px');
done();
});
});

View File

@@ -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() {