Merge pull request #721 from zenorocha/feature-prettier

This commit is contained in:
Vitor Alencar 2021-01-21 17:32:52 +01:00 committed by GitHub
commit 221efae529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 8650 additions and 856 deletions

View File

@ -7,7 +7,7 @@ root = true
[*] [*]
# Change these settings to your own preference # Change these settings to your own preference
indent_style = space indent_style = space
indent_size = 4 indent_size = 2
# We recommend you to keep these unchanged # We recommend you to keep these unchanged
end_of_line = lf end_of_line = lf

9
.prettierignore Normal file
View File

@ -0,0 +1,9 @@
# Ignore artifacts:
dist
lib
npm-debug.log
bower_components
node_modules
yarn-error.log
yarn.lock

9
.prettierrc.json Normal file
View File

@ -0,0 +1,9 @@
{
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always"
}

View File

@ -14,9 +14,5 @@
"/src", "/src",
"/lib" "/lib"
], ],
"keywords": [ "keywords": ["clipboard", "copy", "cut"]
"clipboard",
"copy",
"cut"
]
} }

View File

@ -24,5 +24,6 @@ Implement your bug fix or feature, write tests to cover it and make sure all tes
Documentation is extremely important and takes a fair deal of time and effort to write and keep updated. Please submit any and all improvements you can make to the repository's docs. Documentation is extremely important and takes a fair deal of time and effort to write and keep updated. Please submit any and all improvements you can make to the repository's docs.
## Known issues ## Known issues
If you're using npm@3 you'll probably face some issues related to peerDependencies. If you're using npm@3 you'll probably face some issues related to peerDependencies.
https://github.com/npm/npm/issues/9204 https://github.com/npm/npm/issues/9204

View File

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>constructor-node</title> <title>constructor-node</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>
<!-- 1. Define some markup --> <!-- 1. Define some markup -->
<div id="btn" data-clipboard-text="1"> <div id="btn" data-clipboard-text="1">
<span>Copy</span> <span>Copy</span>
@ -19,13 +19,13 @@
var btn = document.getElementById('btn'); var btn = document.getElementById('btn');
var clipboard = new ClipboardJS(btn); var clipboard = new ClipboardJS(btn);
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.log(e); console.log(e);
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.log(e); console.log(e);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>constructor-nodelist</title> <title>constructor-nodelist</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>
<!-- 1. Define some markup --> <!-- 1. Define some markup -->
<button data-clipboard-text="1">Copy</button> <button data-clipboard-text="1">Copy</button>
<button data-clipboard-text="2">Copy</button> <button data-clipboard-text="2">Copy</button>
@ -19,13 +19,13 @@
var btns = document.querySelectorAll('button'); var btns = document.querySelectorAll('button');
var clipboard = new ClipboardJS(btns); var clipboard = new ClipboardJS(btns);
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.log(e); console.log(e);
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.log(e); console.log(e);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>constructor-selector</title> <title>constructor-selector</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>
<!-- 1. Define some markup --> <!-- 1. Define some markup -->
<button class="btn" data-clipboard-text="1">Copy</button> <button class="btn" data-clipboard-text="1">Copy</button>
<button class="btn" data-clipboard-text="2">Copy</button> <button class="btn" data-clipboard-text="2">Copy</button>
@ -18,13 +18,13 @@
<script> <script>
var clipboard = new ClipboardJS('.btn'); var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.log(e); console.log(e);
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.log(e); console.log(e);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>function-target</title> <title>function-target</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>
<!-- 1. Define some markup --> <!-- 1. Define some markup -->
<button class="btn">Copy</button> <button class="btn">Copy</button>
<div>hello</div> <div>hello</div>
@ -16,18 +16,18 @@
<!-- 3. Instantiate clipboard --> <!-- 3. Instantiate clipboard -->
<script> <script>
var clipboard = new ClipboardJS('.btn', { var clipboard = new ClipboardJS('.btn', {
target: function() { target: function () {
return document.querySelector('div'); return document.querySelector('div');
} },
}); });
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.log(e); console.log(e);
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.log(e); console.log(e);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>function-text</title> <title>function-text</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>
<!-- 1. Define some markup --> <!-- 1. Define some markup -->
<button class="btn">Copy</button> <button class="btn">Copy</button>
@ -15,18 +15,18 @@
<!-- 3. Instantiate clipboard --> <!-- 3. Instantiate clipboard -->
<script> <script>
var clipboard = new ClipboardJS('.btn', { var clipboard = new ClipboardJS('.btn', {
text: function() { text: function () {
return 'to be or not to be'; return 'to be or not to be';
} },
}); });
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.log(e); console.log(e);
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.log(e); console.log(e);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,14 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>target-div</title> <title>target-div</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>
<!-- 1. Define some markup --> <!-- 1. Define some markup -->
<div>hello</div> <div>hello</div>
<button class="btn" data-clipboard-action="copy" data-clipboard-target="div">Copy</button> <button
class="btn"
data-clipboard-action="copy"
data-clipboard-target="div"
>
Copy
</button>
<!-- 2. Include library --> <!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script> <script src="../dist/clipboard.min.js"></script>
@ -17,13 +23,13 @@
<script> <script>
var clipboard = new ClipboardJS('.btn'); var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.log(e); console.log(e);
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.log(e); console.log(e);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,14 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>target-input</title> <title>target-input</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>
<!-- 1. Define some markup --> <!-- 1. Define some markup -->
<input id="foo" type="text" value="hello"> <input id="foo" type="text" value="hello" />
<button class="btn" data-clipboard-action="copy" data-clipboard-target="#foo">Copy</button> <button
class="btn"
data-clipboard-action="copy"
data-clipboard-target="#foo"
>
Copy
</button>
<!-- 2. Include library --> <!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script> <script src="../dist/clipboard.min.js"></script>
@ -17,13 +23,13 @@
<script> <script>
var clipboard = new ClipboardJS('.btn'); var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.log(e); console.log(e);
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.log(e); console.log(e);
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,14 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>target-textarea</title> <title>target-textarea</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>
<!-- 1. Define some markup --> <!-- 1. Define some markup -->
<textarea id="bar">hello</textarea> <textarea id="bar">hello</textarea>
<button class="btn" data-clipboard-action="cut" data-clipboard-target="#bar">Cut</button> <button
class="btn"
data-clipboard-action="cut"
data-clipboard-target="#bar"
>
Cut
</button>
<!-- 2. Include library --> <!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script> <script src="../dist/clipboard.min.js"></script>
@ -17,13 +23,13 @@
<script> <script>
var clipboard = new ClipboardJS('.btn'); var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.log(e); console.log(e);
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.log(e); console.log(e);
}); });
</script> </script>
</body> </body>
</html> </html>

56
dist/clipboard.js vendored
View File

@ -71,7 +71,7 @@ var ClipboardAction = /*#__PURE__*/function () {
this.target = options.target; this.target = options.target;
this.text = options.text; this.text = options.text;
this.trigger = options.trigger; this.trigger = options.trigger;
this.selectedText = ''; this.selectedText = "";
} }
/** /**
* Decides which selection strategy is going to be applied based * Decides which selection strategy is going to be applied based
@ -97,28 +97,28 @@ var ClipboardAction = /*#__PURE__*/function () {
value: function selectFake() { value: function selectFake() {
var _this = this; var _this = this;
var isRTL = document.documentElement.getAttribute('dir') == 'rtl'; var isRTL = document.documentElement.getAttribute("dir") == "rtl";
this.removeFake(); this.removeFake();
this.fakeHandlerCallback = function () { this.fakeHandlerCallback = function () {
return _this.removeFake(); return _this.removeFake();
}; };
this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true; this.fakeHandler = this.container.addEventListener("click", this.fakeHandlerCallback) || true;
this.fakeElem = document.createElement('textarea'); // Prevent zooming on iOS this.fakeElem = document.createElement("textarea"); // Prevent zooming on iOS
this.fakeElem.style.fontSize = '12pt'; // Reset box model this.fakeElem.style.fontSize = "12pt"; // Reset box model
this.fakeElem.style.border = '0'; this.fakeElem.style.border = "0";
this.fakeElem.style.padding = '0'; this.fakeElem.style.padding = "0";
this.fakeElem.style.margin = '0'; // Move element out of screen horizontally this.fakeElem.style.margin = "0"; // Move element out of screen horizontally
this.fakeElem.style.position = 'absolute'; this.fakeElem.style.position = "absolute";
this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically this.fakeElem.style[isRTL ? "right" : "left"] = "-9999px"; // Move element to the same position vertically
var yPosition = window.pageYOffset || document.documentElement.scrollTop; var yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = "".concat(yPosition, "px"); this.fakeElem.style.top = "".concat(yPosition, "px");
this.fakeElem.setAttribute('readonly', ''); this.fakeElem.setAttribute("readonly", "");
this.fakeElem.value = this.text; this.fakeElem.value = this.text;
this.container.appendChild(this.fakeElem); this.container.appendChild(this.fakeElem);
this.selectedText = select_default()(this.fakeElem); this.selectedText = select_default()(this.fakeElem);
@ -133,7 +133,7 @@ var ClipboardAction = /*#__PURE__*/function () {
key: "removeFake", key: "removeFake",
value: function removeFake() { value: function removeFake() {
if (this.fakeHandler) { if (this.fakeHandler) {
this.container.removeEventListener('click', this.fakeHandlerCallback); this.container.removeEventListener("click", this.fakeHandlerCallback);
this.fakeHandler = null; this.fakeHandler = null;
this.fakeHandlerCallback = null; this.fakeHandlerCallback = null;
} }
@ -178,7 +178,7 @@ var ClipboardAction = /*#__PURE__*/function () {
}, { }, {
key: "handleResult", key: "handleResult",
value: function handleResult(succeeded) { value: function handleResult(succeeded) {
this.emitter.emit(succeeded ? 'success' : 'error', { this.emitter.emit(succeeded ? "success" : "error", {
action: this.action, action: this.action,
text: this.selectedText, text: this.selectedText,
trigger: this.trigger, trigger: this.trigger,
@ -216,10 +216,10 @@ var ClipboardAction = /*#__PURE__*/function () {
}, { }, {
key: "action", key: "action",
set: function set() { set: function set() {
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy'; var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "copy";
this._action = action; this._action = action;
if (this._action !== 'copy' && this._action !== 'cut') { if (this._action !== "copy" && this._action !== "cut") {
throw new Error('Invalid "action" value, use either "copy" or "cut"'); throw new Error('Invalid "action" value, use either "copy" or "cut"');
} }
} }
@ -241,12 +241,12 @@ var ClipboardAction = /*#__PURE__*/function () {
key: "target", key: "target",
set: function set(target) { set: function set(target) {
if (target !== undefined) { if (target !== undefined) {
if (target && _typeof(target) === 'object' && target.nodeType === 1) { if (target && _typeof(target) === "object" && target.nodeType === 1) {
if (this.action === 'copy' && target.hasAttribute('disabled')) { if (this.action === "copy" && target.hasAttribute("disabled")) {
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
} }
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) { 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'); throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
} }
@ -340,10 +340,10 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
key: "resolveOptions", key: "resolveOptions",
value: function resolveOptions() { value: function resolveOptions() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.action = typeof options.action === 'function' ? options.action : this.defaultAction; this.action = typeof options.action === "function" ? options.action : this.defaultAction;
this.target = typeof options.target === 'function' ? options.target : this.defaultTarget; this.target = typeof options.target === "function" ? options.target : this.defaultTarget;
this.text = typeof options.text === 'function' ? options.text : this.defaultText; this.text = typeof options.text === "function" ? options.text : this.defaultText;
this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body; this.container = clipboard_typeof(options.container) === "object" ? options.container : document.body;
} }
/** /**
* Adds a click event listener to the passed trigger. * Adds a click event listener to the passed trigger.
@ -355,7 +355,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
value: function listenClick(trigger) { value: function listenClick(trigger) {
var _this2 = this; var _this2 = this;
this.listener = listen_default()(trigger, 'click', function (e) { this.listener = listen_default()(trigger, "click", function (e) {
return _this2.onClick(e); return _this2.onClick(e);
}); });
} }
@ -390,7 +390,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
}, { }, {
key: "defaultAction", key: "defaultAction",
value: function defaultAction(trigger) { value: function defaultAction(trigger) {
return getAttributeValue('action', trigger); return getAttributeValue("action", trigger);
} }
/** /**
* Default `target` lookup function. * Default `target` lookup function.
@ -400,7 +400,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
}, { }, {
key: "defaultTarget", key: "defaultTarget",
value: function defaultTarget(trigger) { value: function defaultTarget(trigger) {
var selector = getAttributeValue('target', trigger); var selector = getAttributeValue("target", trigger);
if (selector) { if (selector) {
return document.querySelector(selector); return document.querySelector(selector);
@ -420,7 +420,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
* @param {Element} trigger * @param {Element} trigger
*/ */
value: function defaultText(trigger) { value: function defaultText(trigger) {
return getAttributeValue('text', trigger); return getAttributeValue("text", trigger);
} }
/** /**
* Destroy lifecycle. * Destroy lifecycle.
@ -439,8 +439,8 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
}], [{ }], [{
key: "isSupported", key: "isSupported",
value: function isSupported() { value: function isSupported() {
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut']; var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ["copy", "cut"];
var actions = typeof action === 'string' ? [action] : action; var actions = typeof action === "string" ? [action] : action;
var support = !!document.queryCommandSupported; var support = !!document.queryCommandSupported;
actions.forEach(function (action) { actions.forEach(function (action) {
support = support && !!document.queryCommandSupported(action); support = support && !!document.queryCommandSupported(action);

View File

@ -1,25 +1,25 @@
var webpackConfig = require("./webpack.config.js"); var webpackConfig = require('./webpack.config.js');
module.exports = function (karma) { module.exports = function (karma) {
karma.set({ karma.set({
plugins: [ plugins: [
"karma-webpack", 'karma-webpack',
"karma-chai", 'karma-chai',
"karma-sinon", 'karma-sinon',
"karma-mocha", 'karma-mocha',
"karma-chrome-launcher", 'karma-chrome-launcher',
], ],
frameworks: ["chai", "sinon", "mocha", "webpack"], frameworks: ['chai', 'sinon', 'mocha', 'webpack'],
files: [ files: [
{ pattern: "src/**/*.js", watched: false }, { pattern: 'src/**/*.js', watched: false },
{ pattern: "test/**/*.js", watched: false }, { pattern: 'test/**/*.js', watched: false },
], ],
preprocessors: { preprocessors: {
"src/**/*.js": ["webpack"], 'src/**/*.js': ['webpack'],
"test/**/*.js": ["webpack"], 'test/**/*.js': ['webpack'],
}, },
webpack: { webpack: {
@ -28,9 +28,9 @@ module.exports = function (karma) {
}, },
webpackMiddleware: { webpackMiddleware: {
stats: "errors-only", stats: 'errors-only',
}, },
browsers: ["ChromeHeadless"], browsers: ['ChromeHeadless'],
}); });
}; };

7772
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
// Package metadata for Meteor.js. // Package metadata for Meteor.js.
Package.describe({ Package.describe({
name: "zenorocha:clipboard", name: 'zenorocha:clipboard',
summary: "Modern copy to clipboard. No Flash. Just 3kb.", summary: 'Modern copy to clipboard. No Flash. Just 3kb.',
version: "2.0.6", version: '2.0.6',
git: "https://github.com/zenorocha/clipboard.js" git: 'https://github.com/zenorocha/clipboard.js',
}); });
Package.onUse(function(api) { Package.onUse(function (api) {
api.addFiles("dist/clipboard.js", "client"); api.addFiles('dist/clipboard.js', 'client');
}); });

View File

@ -22,13 +22,16 @@
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"chai": "^4.2.0", "chai": "^4.2.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"husky": "^4.3.8",
"karma": "^6.0.0", "karma": "^6.0.0",
"karma-chai": "^0.1.0", "karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0", "karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1", "karma-mocha": "^2.0.1",
"karma-sinon": "^1.0.4", "karma-sinon": "^1.0.4",
"karma-webpack": "^5.0.0-alpha.5", "karma-webpack": "^5.0.0-alpha.5",
"lint-staged": "^10.5.3",
"mocha": "^8.2.1", "mocha": "^8.2.1",
"prettier": "2.2.1",
"sinon": "^9.2.3", "sinon": "^9.2.3",
"uglifyjs-webpack-plugin": "^2.2.0", "uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^5.15.0", "webpack": "^5.15.0",
@ -41,5 +44,13 @@
"build-watch": "webpack --watch", "build-watch": "webpack --watch",
"test": "karma start --single-run", "test": "karma start --single-run",
"prepublish": "npm run build" "prepublish": "npm run build"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,css,md}": "prettier --write"
} }
} }

View File

@ -55,11 +55,11 @@ The value you include on this attribute needs to match another's element selecto
```html ```html
<!-- Target --> <!-- Target -->
<input id="foo" value="https://github.com/zenorocha/clipboard.js.git"> <input id="foo" value="https://github.com/zenorocha/clipboard.js.git" />
<!-- Trigger --> <!-- Trigger -->
<button class="btn" data-clipboard-target="#foo"> <button class="btn" data-clipboard-target="#foo">
<img src="assets/clippy.svg" alt="Copy to clipboard"> <img src="assets/clippy.svg" alt="Copy to clipboard" />
</button> </button>
``` ```
@ -91,7 +91,10 @@ Truth is, you don't even need another element to copy its content from. You can
```html ```html
<!-- Trigger --> <!-- Trigger -->
<button class="btn" data-clipboard-text="Just because you can doesn't mean you should — clipboard.js"> <button
class="btn"
data-clipboard-text="Just because you can doesn't mean you should — clipboard.js"
>
Copy to clipboard Copy to clipboard
</button> </button>
``` ```
@ -105,7 +108,7 @@ That's why we fire custom events such as `success` and `error` for you to listen
```js ```js
var clipboard = new ClipboardJS('.btn'); var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) { clipboard.on('success', function (e) {
console.info('Action:', e.action); console.info('Action:', e.action);
console.info('Text:', e.text); console.info('Text:', e.text);
console.info('Trigger:', e.trigger); console.info('Trigger:', e.trigger);
@ -113,7 +116,7 @@ clipboard.on('success', function(e) {
e.clearSelection(); e.clearSelection();
}); });
clipboard.on('error', function(e) { clipboard.on('error', function (e) {
console.error('Action:', e.action); console.error('Action:', e.action);
console.error('Trigger:', e.trigger); console.error('Trigger:', e.trigger);
}); });
@ -135,9 +138,9 @@ For instance, if you want to dynamically set a `target`, you'll need to return a
```js ```js
new ClipboardJS('.btn', { new ClipboardJS('.btn', {
target: function(trigger) { target: function (trigger) {
return trigger.nextElementSibling; return trigger.nextElementSibling;
} },
}); });
``` ```
@ -145,9 +148,9 @@ If you want to dynamically set a `text`, you'll return a String.
```js ```js
new ClipboardJS('.btn', { new ClipboardJS('.btn', {
text: function(trigger) { text: function (trigger) {
return trigger.getAttribute('aria-label'); return trigger.getAttribute('aria-label');
} },
}); });
``` ```
@ -155,7 +158,7 @@ For use in Bootstrap Modals or with any other library that changes the focus you
```js ```js
new ClipboardJS('.btn', { new ClipboardJS('.btn', {
container: document.getElementById('modal') container: document.getElementById('modal'),
}); });
``` ```
@ -171,7 +174,7 @@ clipboard.destroy();
This library relies on both [Selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection) and [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) APIs. The first one is [supported by all browsers](https://caniuse.com/#search=selection) while the second one is supported in the following browsers. This library relies on both [Selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection) and [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) APIs. The first one is [supported by all browsers](https://caniuse.com/#search=selection) while the second one is supported in the following browsers.
| <img src="https://clipboardjs.com/assets/images/chrome.png" width="48px" height="48px" alt="Chrome logo"> | <img src="https://clipboardjs.com/assets/images/edge.png" width="48px" height="48px" alt="Edge logo"> | <img src="https://clipboardjs.com/assets/images/firefox.png" width="48px" height="48px" alt="Firefox logo"> | <img src="https://clipboardjs.com/assets/images/ie.png" width="48px" height="48px" alt="Internet Explorer logo"> | <img src="https://clipboardjs.com/assets/images/opera.png" width="48px" height="48px" alt="Opera logo"> | <img src="https://clipboardjs.com/assets/images/safari.png" width="48px" height="48px" alt="Safari logo"> | | <img src="https://clipboardjs.com/assets/images/chrome.png" width="48px" height="48px" alt="Chrome logo"> | <img src="https://clipboardjs.com/assets/images/edge.png" width="48px" height="48px" alt="Edge logo"> | <img src="https://clipboardjs.com/assets/images/firefox.png" width="48px" height="48px" alt="Firefox logo"> | <img src="https://clipboardjs.com/assets/images/ie.png" width="48px" height="48px" alt="Internet Explorer logo"> | <img src="https://clipboardjs.com/assets/images/opera.png" width="48px" height="48px" alt="Opera logo"> | <img src="https://clipboardjs.com/assets/images/safari.png" width="48px" height="48px" alt="Safari logo"> |
|:---:|:---:|:---:|:---:|:---:|:---:| | :-------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: |
| 42+ ✔ | 12+ ✔ | 41+ ✔ | 9+ ✔ | 29+ ✔ | 10+ ✔ | | 42+ ✔ | 12+ ✔ | 41+ ✔ | 9+ ✔ | 29+ ✔ | 10+ ✔ |
The good news is that clipboard.js gracefully degrades if you need to support older browsers. All you have to do is 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. The good news is that clipboard.js gracefully degrades if you need to support older browsers. All you have to do is 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.
@ -180,7 +183,7 @@ You can also check if clipboard.js is supported or not by running `ClipboardJS.i
## Bonus ## Bonus
A browser extension that adds a "copy to clipboard" button to every code block on *GitHub, MDN, Gist, StackOverflow, StackExchange, npm, and even Medium.* A browser extension that adds a "copy to clipboard" button to every code block on _GitHub, MDN, Gist, StackOverflow, StackExchange, npm, and even Medium._
Install for [Chrome](https://chrome.google.com/webstore/detail/codecopy/fkbfebkcoelajmhanocgppanfoojcdmg) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/codecopy/). Install for [Chrome](https://chrome.google.com/webstore/detail/codecopy/fkbfebkcoelajmhanocgppanfoojcdmg) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/codecopy/).

View File

@ -35,8 +35,7 @@ class ClipboardAction {
initSelection() { initSelection() {
if (this.text) { if (this.text) {
this.selectFake(); this.selectFake();
} } else if (this.target) {
else if (this.target) {
this.selectTarget(); this.selectTarget();
} }
} }
@ -51,7 +50,9 @@ class ClipboardAction {
this.removeFake(); this.removeFake();
this.fakeHandlerCallback = () => this.removeFake(); this.fakeHandlerCallback = () => this.removeFake();
this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true; this.fakeHandler =
this.container.addEventListener('click', this.fakeHandlerCallback) ||
true;
this.fakeElem = document.createElement('textarea'); this.fakeElem = document.createElement('textarea');
// Prevent zooming on iOS // Prevent zooming on iOS
@ -62,7 +63,7 @@ class ClipboardAction {
this.fakeElem.style.margin = '0'; this.fakeElem.style.margin = '0';
// Move element out of screen horizontally // Move element out of screen horizontally
this.fakeElem.style.position = 'absolute'; this.fakeElem.style.position = 'absolute';
this.fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px'; this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
// Move element to the same position vertically // Move element to the same position vertically
let yPosition = window.pageYOffset || document.documentElement.scrollTop; let yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = `${yPosition}px`; this.fakeElem.style.top = `${yPosition}px`;
@ -109,8 +110,7 @@ class ClipboardAction {
try { try {
succeeded = document.execCommand(this.action); succeeded = document.execCommand(this.action);
} } catch (err) {
catch (err) {
succeeded = false; succeeded = false;
} }
@ -126,7 +126,7 @@ class ClipboardAction {
action: this.action, action: this.action,
text: this.selectedText, text: this.selectedText,
trigger: this.trigger, trigger: this.trigger,
clearSelection: this.clearSelection.bind(this) clearSelection: this.clearSelection.bind(this),
}); });
} }
@ -170,16 +170,22 @@ class ClipboardAction {
if (target !== undefined) { if (target !== undefined) {
if (target && typeof target === 'object' && target.nodeType === 1) { if (target && typeof target === 'object' && target.nodeType === 1) {
if (this.action === 'copy' && target.hasAttribute('disabled')) { if (this.action === 'copy' && target.hasAttribute('disabled')) {
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); throw new Error(
'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'
);
} }
if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) { if (
throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); 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; this._target = target;
} } else {
else {
throw new Error('Invalid "target" value, use a valid Element'); throw new Error('Invalid "target" value, use a valid Element');
} }
} }

View File

@ -24,10 +24,18 @@ class Clipboard extends Emitter {
* @param {Object} options * @param {Object} options
*/ */
resolveOptions(options = {}) { resolveOptions(options = {}) {
this.action = (typeof options.action === 'function') ? options.action : this.defaultAction; this.action =
this.target = (typeof options.target === 'function') ? options.target : this.defaultTarget; typeof options.action === 'function'
this.text = (typeof options.text === 'function') ? options.text : this.defaultText; ? options.action
this.container = (typeof options.container === 'object') ? options.container : document.body; : this.defaultAction;
this.target =
typeof options.target === 'function'
? options.target
: this.defaultTarget;
this.text =
typeof options.text === 'function' ? options.text : this.defaultText;
this.container =
typeof options.container === 'object' ? options.container : document.body;
} }
/** /**
@ -50,12 +58,12 @@ class Clipboard extends Emitter {
} }
this.clipboardAction = new ClipboardAction({ this.clipboardAction = new ClipboardAction({
action : this.action(trigger), action: this.action(trigger),
target : this.target(trigger), target: this.target(trigger),
text : this.text(trigger), text: this.text(trigger),
container : this.container, container: this.container,
trigger : trigger, trigger: trigger,
emitter : this emitter: this,
}); });
} }
@ -85,7 +93,7 @@ class Clipboard extends Emitter {
* @param {String} [action] * @param {String} [action]
*/ */
static isSupported(action = ['copy', 'cut']) { static isSupported(action = ['copy', 'cut']) {
const actions = (typeof action === 'string') ? [action] : action; const actions = typeof action === 'string' ? [action] : action;
let support = !!document.queryCommandSupported; let support = !!document.queryCommandSupported;
actions.forEach((action) => { actions.forEach((action) => {
@ -116,7 +124,6 @@ class Clipboard extends Emitter {
} }
} }
/** /**
* Helper function to retrieve attribute value. * Helper function to retrieve attribute value.
* @param {String} suffix * @param {String} suffix

View File

@ -23,7 +23,7 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
text: 'foo' text: 'foo',
}); });
assert.property(clip, 'action'); assert.property(clip, 'action');
@ -37,14 +37,14 @@ describe('ClipboardAction', () => {
}); });
describe('#initSelection', () => { describe('#initSelection', () => {
it('should set the position right style property', done => { it('should set the position right style property', (done) => {
// Set document direction // Set document direction
document.documentElement.setAttribute('dir', 'rtl'); document.documentElement.setAttribute('dir', 'rtl');
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
text: 'foo' text: 'foo',
}); });
assert.equal(clip.fakeElem.style.right, '-9999px'); assert.equal(clip.fakeElem.style.right, '-9999px');
@ -53,28 +53,29 @@ describe('ClipboardAction', () => {
}); });
describe('#set action', () => { describe('#set action', () => {
it('should throw an error since "action" is invalid', done => { it('should throw an error since "action" is invalid', (done) => {
try { try {
new ClipboardAction({ new ClipboardAction({
text: 'foo', text: 'foo',
action: 'paste' action: 'paste',
}); });
} } catch (e) {
catch(e) { assert.equal(
assert.equal(e.message, 'Invalid "action" value, use either "copy" or "cut"'); e.message,
'Invalid "action" value, use either "copy" or "cut"'
);
done(); done();
} }
}); });
}); });
describe('#set target', () => { describe('#set target', () => {
it('should throw an error since "target" do not match any element', done => { it('should throw an error since "target" do not match any element', (done) => {
try { try {
new ClipboardAction({ new ClipboardAction({
target: document.querySelector('#foo') target: document.querySelector('#foo'),
}); });
} } catch (e) {
catch(e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element'); assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done(); done();
} }
@ -86,7 +87,7 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
text: 'blah' text: 'blah',
}); });
assert.equal(clip.selectedText, clip.fakeElem.value); assert.equal(clip.selectedText, clip.fakeElem.value);
@ -98,7 +99,7 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
text: 'blah' text: 'blah',
}); });
clip.removeFake(); clip.removeFake();
@ -112,7 +113,7 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
target: document.querySelector('#input') target: document.querySelector('#input'),
}); });
assert.equal(clip.selectedText, clip.target.value); assert.equal(clip.selectedText, clip.target.value);
@ -122,7 +123,7 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
target: document.querySelector('#paragraph') target: document.querySelector('#paragraph'),
}); });
assert.equal(clip.selectedText, clip.target.textContent); assert.equal(clip.selectedText, clip.target.textContent);
@ -138,7 +139,7 @@ describe('ClipboardAction', () => {
global.stub.restore(); global.stub.restore();
}); });
it('should fire a success event on browsers that support copy command', done => { it('should fire a success event on browsers that support copy command', (done) => {
global.stub.returns(true); global.stub.returns(true);
let emitter = new Emitter(); let emitter = new Emitter();
@ -149,11 +150,11 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter, emitter,
target: document.querySelector('#input') target: document.querySelector('#input'),
}); });
}); });
it('should fire an error event on browsers that support copy command', done => { it('should fire an error event on browsers that support copy command', (done) => {
global.stub.returns(false); global.stub.returns(false);
let emitter = new Emitter(); let emitter = new Emitter();
@ -164,17 +165,17 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter, emitter,
target: document.querySelector('#input') target: document.querySelector('#input'),
}); });
}); });
}); });
describe('#handleResult', () => { describe('#handleResult', () => {
it('should fire a success event with certain properties', done => { it('should fire a success event with certain properties', (done) => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
target: document.querySelector('#input') target: document.querySelector('#input'),
}); });
clip.emitter.on('success', (e) => { clip.emitter.on('success', (e) => {
@ -189,11 +190,11 @@ describe('ClipboardAction', () => {
clip.handleResult(true); clip.handleResult(true);
}); });
it('should fire a error event with certain properties', done => { it('should fire a error event with certain properties', (done) => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
target: document.querySelector('#input') target: document.querySelector('#input'),
}); });
clip.emitter.on('error', (e) => { clip.emitter.on('error', (e) => {
@ -213,7 +214,7 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
target: document.querySelector('#input') target: document.querySelector('#input'),
}); });
clip.clearSelection(); clip.clearSelection();
@ -231,7 +232,7 @@ describe('ClipboardAction', () => {
let clip = new ClipboardAction({ let clip = new ClipboardAction({
emitter: new Emitter(), emitter: new Emitter(),
container: document.body, container: document.body,
text: 'blah' text: 'blah',
}); });
clip.selectFake(); clip.selectFake();

View File

@ -16,7 +16,7 @@ describe('Clipboard', () => {
global.event = { global.event = {
target: global.button, target: global.button,
currentTarget: global.button currentTarget: global.button,
}; };
}); });
@ -31,7 +31,7 @@ describe('Clipboard', () => {
it('should set action as a function', () => { it('should set action as a function', () => {
let clipboard = new Clipboard('.btn', { let clipboard = new Clipboard('.btn', {
action: global.fn action: global.fn,
}); });
assert.equal(global.fn, clipboard.action); assert.equal(global.fn, clipboard.action);
@ -39,7 +39,7 @@ describe('Clipboard', () => {
it('should set target as a function', () => { it('should set target as a function', () => {
let clipboard = new Clipboard('.btn', { let clipboard = new Clipboard('.btn', {
target: global.fn target: global.fn,
}); });
assert.equal(global.fn, clipboard.target); assert.equal(global.fn, clipboard.target);
@ -47,7 +47,7 @@ describe('Clipboard', () => {
it('should set text as a function', () => { it('should set text as a function', () => {
let clipboard = new Clipboard('.btn', { let clipboard = new Clipboard('.btn', {
text: global.fn text: global.fn,
}); });
assert.equal(global.fn, clipboard.text); assert.equal(global.fn, clipboard.text);
@ -55,7 +55,7 @@ describe('Clipboard', () => {
it('should set container as an object', () => { it('should set container as an object', () => {
let clipboard = new Clipboard('.btn', { let clipboard = new Clipboard('.btn', {
container: document.body container: document.body,
}); });
assert.equal(document.body, clipboard.container); assert.equal(document.body, clipboard.container);
@ -83,25 +83,27 @@ describe('Clipboard', () => {
assert.instanceOf(clipboard.clipboardAction, ClipboardAction); assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
}); });
it('should use an event\'s currentTarget when not equal to target', () => { it("should use an event's currentTarget when not equal to target", () => {
let clipboard = new Clipboard('.btn'); let clipboard = new Clipboard('.btn');
let bubbledEvent = { target: global.span, currentTarget: global.button }; let bubbledEvent = {
target: global.span,
currentTarget: global.button,
};
clipboard.onClick(bubbledEvent); clipboard.onClick(bubbledEvent);
assert.instanceOf(clipboard.clipboardAction, ClipboardAction); assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
}); });
it('should throw an exception when target is invalid', done => { it('should throw an exception when target is invalid', (done) => {
try { try {
const clipboard = new Clipboard('.btn', { const clipboard = new Clipboard('.btn', {
target() { target() {
return null; return null;
} },
}); });
clipboard.onClick(global.event); clipboard.onClick(global.event);
} } catch (e) {
catch(e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element'); assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done(); done();
} }

View File

@ -19,12 +19,10 @@ module.exports = {
library: 'ClipboardJS', library: 'ClipboardJS',
globalObject: 'this', globalObject: 'this',
libraryExport: 'default', libraryExport: 'default',
libraryTarget: 'umd' libraryTarget: 'umd',
}, },
module: { module: {
rules: [ rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }],
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'}
]
}, },
optimization: { optimization: {
minimize: production, minimize: production,
@ -36,11 +34,12 @@ module.exports = {
keep_fnames: false, keep_fnames: false,
output: { output: {
beautify: false, beautify: false,
comments: (node, {value, type}) => type == 'comment2' && value.startsWith('!') comments: (node, { value, type }) =>
} type == 'comment2' && value.startsWith('!'),
}
})
]
}, },
plugins: [new webpack.BannerPlugin({ banner })] },
}),
],
},
plugins: [new webpack.BannerPlugin({ banner })],
}; };