Compare commits

..

27 Commits

Author SHA1 Message Date
Beto Muniz
98d92f2a42 2.0.10 2022-02-02 12:21:27 -03:00
Beto Muniz
7d675f5fc1 Fix Event API. Update demos. Update tests (#793) 2022-02-02 12:13:00 -03:00
Beto Muniz
9698b1176a 2.0.9 2022-01-27 22:50:17 -03:00
dependabot[bot]
2f70c7af6e Bump follow-redirects from 1.13.1 to 1.14.7 (#788)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-27 09:32:59 +00:00
dependabot[bot]
b0cd56df35 Bump engine.io from 4.1.0 to 4.1.2 (#789)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-27 09:32:46 +00:00
dependabot[bot]
d07940ecb0 Bump log4js from 6.3.0 to 6.4.0 (#790)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-27 09:32:32 +00:00
dependabot[bot]
57345ab3ce Bump browserslist from 4.16.1 to 4.16.6 (#763) 2021-06-29 21:03:06 +01:00
dependabot[bot]
641ac851e8 Bump ws from 7.4.2 to 7.4.6 (#764) 2021-06-29 21:02:45 +01:00
Beto Muniz
44df750c9f Isolate actions strategies in order to code improvement and programmatic usage. (#749)
* Isolate cut, copy and core helper functions.

* Update tests to accommodate new proposal

* Add/update tests

* Add tests to static copy/cut methods

* Update condition syntax based on PR reviews

* Migrate clipboard-action-default to functional approach. Update tests. Add tests

* Improve folder structure. Clean up code.

* Add types. Fix tsd check env. Improve in-code doc comments

* Improve in-code doc comments
2021-05-18 11:46:22 -03:00
dependabot[bot]
8762fc7c66 Bump ssri from 6.0.1 to 6.0.2 (#756)
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-17 13:36:22 +01:00
dependabot[bot]
957080dcad Bump lodash from 4.17.20 to 4.17.21 (#760)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-17 13:36:14 +01:00
dependabot[bot]
b9d1496b9c Bump ua-parser-js from 0.7.23 to 0.7.28 (#761)
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.23 to 0.7.28.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.23...0.7.28)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-17 13:36:06 +01:00
dependabot[bot]
f1b1ab2b1a Bump hosted-git-info from 2.8.8 to 2.8.9 (#762)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-17 13:35:48 +01:00
Vitor Alencar
5ffe395b3a Merge pull request #752 from XhmikosR/patch-1
Update CI config
2021-05-12 21:59:16 +02:00
XhmikosR
9c062df900 Update CI config
* add `FORCE_COLOR: 2` so that we get colored output
* update to `actions/setup-node@v2`
* fix formatting
2021-04-08 09:29:09 +03:00
Vitor Alencar
734d36b6ff Merge pull request #747 from zenorocha/feat-update-meteor-package
update release version for package.js
2021-03-21 12:57:36 +01:00
Vitor Alencar
4c2eb0e9cf update to a project wide babel config (#746) 2021-03-18 14:41:50 -03:00
Vitor Alencar
7da9deb2cc Merge pull request #744 from zenorocha/bump-bower-verssion
updating bower version
2021-03-16 17:41:47 +01:00
vitormalencar
cda958cd5f update release version for package.js 2021-03-16 17:32:48 +01:00
vitormalencar
983a8c0b41 updating bower version 2021-03-11 16:57:30 +01:00
Vitor Alencar
ac3ed34071 Merge pull request #743 from zenorocha/feat-fix-bower-release
updating production version for bower
2021-03-11 16:51:39 +01:00
vitormalencar
5c27fe02b8 updating production version for bower 2021-03-11 16:12:59 +01:00
vitormalencar
7dea403fbb 2.0.8 2021-03-10 21:28:10 +01:00
Vitor Alencar
47f64816eb Merge pull request #741 from zenorocha/feat-fix-webpack-target
feat adding es5 as target for webpack compilation
2021-03-10 17:29:00 +01:00
Vitor Alencar
801cdd4ee3 Merge pull request #736 from zenorocha/feat-update-type-definitions
updating type definitions
2021-03-10 16:39:23 +01:00
vitormalencar
6f10cf7e12 feat adding es5 as target for webpack compilation 2021-03-09 11:38:00 +01:00
vitormalencar
2d5e3d2317 updating type definitons 2021-03-07 14:15:36 +01:00
37 changed files with 12403 additions and 1140 deletions

View File

@@ -5,9 +5,12 @@ name: build
on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]
env:
FORCE_COLOR: 2
jobs:
build:
@@ -20,13 +23,12 @@ jobs:
# For now is not possible to target LTS verssions =/ check progress here https://github.com/actions/setup-node/issues/26
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm run lint
- run: npm test
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm run lint
- run: npm test

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ bower_components
node_modules
yarn-error.log
yarn.lock
.DS_Store

View File

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

View File

@@ -20,11 +20,15 @@
var clipboard = new ClipboardJS(btn);
clipboard.on('success', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>

View File

@@ -20,11 +20,15 @@
var clipboard = new ClipboardJS(btns);
clipboard.on('success', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>

View File

@@ -19,11 +19,15 @@
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>

View File

@@ -22,11 +22,15 @@
});
clipboard.on('success', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>

View File

@@ -21,11 +21,15 @@
});
clipboard.on('success', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>

View File

@@ -24,11 +24,15 @@
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>

View File

@@ -24,7 +24,9 @@
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>target-programmatic-copy</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<textarea id="bar">hello</textarea>
<button id="btn">
Copy
</button>
<!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script>
<!-- 3. Instantiate clipboard -->
<script>
var btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
const textCopied = ClipboardJS.copy(document.querySelector('#bar'));
console.log('copied!', textCopied);
})
</script>
</body>
</html>

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>target-programmatic-cut</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<textarea id="bar">hello</textarea>
<button id="btn">
Cut
</button>
<!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script>
<!-- 3. Instantiate clipboard -->
<script>
var btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
const textCut = ClipboardJS.cut(document.querySelector('#bar'));
console.log('cut!', textCut);
})
</script>
</body>
</html>

View File

@@ -24,11 +24,15 @@
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function (e) {
console.log(e);
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>text-programmatic-copy</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button id="btn">
Copy
</button>
<!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script>
<!-- 3. Instantiate clipboard -->
<script>
var btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
const textCopied = ClipboardJS.copy('123');
console.log('copied!', textCopied);
})
</script>
</body>
</html>

544
dist/clipboard.js vendored
View File

@@ -1,5 +1,5 @@
/*!
* clipboard.js v2.0.6
* clipboard.js v2.0.10
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
@@ -14,276 +14,177 @@
else
root["ClipboardJS"] = factory();
})(this, function() {
return /******/ (() => { // webpackBootstrap
return /******/ (function() { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ 134:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/***/ 686:
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
"default": () => /* binding */ clipboard
"default": function() { return /* binding */ clipboard; }
});
// EXTERNAL MODULE: ./node_modules/select/src/select.js
var src_select = __webpack_require__(817);
var select_default = /*#__PURE__*/__webpack_require__.n(src_select);
;// CONCATENATED MODULE: ./src/clipboard-action.js
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
/**
* Inner class which performs selection from either `text` or `target`
* properties and then executes copy or cut operations.
*/
var ClipboardAction = /*#__PURE__*/function () {
/**
* @param {Object} options
*/
function ClipboardAction(options) {
_classCallCheck(this, ClipboardAction);
this.resolveOptions(options);
this.initSelection();
}
/**
* Defines base properties passed from constructor.
* @param {Object} options
*/
_createClass(ClipboardAction, [{
key: "resolveOptions",
value: function resolveOptions() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.action = options.action;
this.container = options.container;
this.emitter = options.emitter;
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
this.selectedText = '';
}
/**
* Decides which selection strategy is going to be applied based
* on the existence of `text` and `target` properties.
*/
}, {
key: "initSelection",
value: function initSelection() {
if (this.text) {
this.selectFake();
} else if (this.target) {
this.selectTarget();
}
}
/**
* Creates a fake textarea element, sets its value from `text` property,
* and makes a selection on it.
*/
}, {
key: "selectFake",
value: function selectFake() {
var _this = this;
var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
this.removeFake();
this.fakeHandlerCallback = function () {
return _this.removeFake();
};
this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || 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
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = "".concat(yPosition, "px");
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.value = this.text;
this.container.appendChild(this.fakeElem);
this.selectedText = select_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.
*/
}, {
key: "removeFake",
value: function removeFake() {
if (this.fakeHandler) {
this.container.removeEventListener('click', this.fakeHandlerCallback);
this.fakeHandler = null;
this.fakeHandlerCallback = null;
}
if (this.fakeElem) {
this.container.removeChild(this.fakeElem);
this.fakeElem = null;
}
}
/**
* Selects the content from element passed on `target` property.
*/
}, {
key: "selectTarget",
value: function selectTarget() {
this.selectedText = select_default()(this.target);
this.copyText();
}
/**
* Executes the copy operation based on the current selection.
*/
}, {
key: "copyText",
value: function copyText() {
var succeeded;
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
*/
}, {
key: "handleResult",
value: function handleResult(succeeded) {
this.emitter.emit(succeeded ? 'success' : 'error', {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
}
/**
* Moves focus away from `target` and back to the trigger, removes current selection.
*/
}, {
key: "clearSelection",
value: function clearSelection() {
if (this.trigger) {
this.trigger.focus();
}
document.activeElement.blur();
window.getSelection().removeAllRanges();
}
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
* @param {String} action
*/
}, {
key: "destroy",
/**
* Destroy lifecycle.
*/
value: function destroy() {
this.removeFake();
}
}, {
key: "action",
set: function set() {
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';
this._action = action;
if (this._action !== 'copy' && this._action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
}
}
/**
* Gets the `action` property.
* @return {String}
*/
,
get: function get() {
return this._action;
}
/**
* Sets the `target` property using an element
* that will be have its content copied.
* @param {Element} target
*/
}, {
key: "target",
set: function set(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 {
throw new Error('Invalid "target" value, use a valid Element');
}
}
}
/**
* Gets the `target` property.
* @return {String|HTMLElement}
*/
,
get: function get() {
return this._target;
}
}]);
return ClipboardAction;
}();
/* harmony default export */ const clipboard_action = (ClipboardAction);
// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js
var tiny_emitter = __webpack_require__(279);
var tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);
// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js
var listen = __webpack_require__(370);
var listen_default = /*#__PURE__*/__webpack_require__.n(listen);
// EXTERNAL MODULE: ./node_modules/select/src/select.js
var src_select = __webpack_require__(817);
var select_default = /*#__PURE__*/__webpack_require__.n(src_select);
;// CONCATENATED MODULE: ./src/common/command.js
/**
* Executes a given operation type.
* @param {String} type
* @return {Boolean}
*/
function command(type) {
try {
return document.execCommand(type);
} catch (err) {
return false;
}
}
;// CONCATENATED MODULE: ./src/actions/cut.js
/**
* Cut action wrapper.
* @param {String|HTMLElement} target
* @return {String}
*/
var ClipboardActionCut = function ClipboardActionCut(target) {
var selectedText = select_default()(target);
command('cut');
return selectedText;
};
/* harmony default export */ var actions_cut = (ClipboardActionCut);
;// CONCATENATED MODULE: ./src/common/create-fake-element.js
/**
* Creates a fake textarea element with a value.
* @param {String} value
* @return {HTMLElement}
*/
function createFakeElement(value) {
var isRTL = document.documentElement.getAttribute('dir') === 'rtl';
var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS
fakeElement.style.fontSize = '12pt'; // Reset box model
fakeElement.style.border = '0';
fakeElement.style.padding = '0';
fakeElement.style.margin = '0'; // Move element out of screen horizontally
fakeElement.style.position = 'absolute';
fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically
var yPosition = window.pageYOffset || document.documentElement.scrollTop;
fakeElement.style.top = "".concat(yPosition, "px");
fakeElement.setAttribute('readonly', '');
fakeElement.value = value;
return fakeElement;
}
;// CONCATENATED MODULE: ./src/actions/copy.js
/**
* Copy action wrapper.
* @param {String|HTMLElement} target
* @param {Object} options
* @return {String}
*/
var ClipboardActionCopy = function ClipboardActionCopy(target) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
container: document.body
};
var selectedText = '';
if (typeof target === 'string') {
var fakeElement = createFakeElement(target);
options.container.appendChild(fakeElement);
selectedText = select_default()(fakeElement);
command('copy');
fakeElement.remove();
} else {
selectedText = select_default()(target);
command('copy');
}
return selectedText;
};
/* harmony default export */ var actions_copy = (ClipboardActionCopy);
;// CONCATENATED MODULE: ./src/actions/default.js
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
/**
* Inner function which performs selection from either `text` or `target`
* properties and then executes copy or cut operations.
* @param {Object} options
*/
var ClipboardActionDefault = function ClipboardActionDefault() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
// Defines base properties passed from constructor.
var _options$action = options.action,
action = _options$action === void 0 ? 'copy' : _options$action,
container = options.container,
target = options.target,
text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.
if (action !== 'copy' && action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
} // Sets the `target` property using an element that will be have its content copied.
if (target !== undefined) {
if (target && _typeof(target) === 'object' && target.nodeType === 1) {
if (action === 'copy' && target.hasAttribute('disabled')) {
throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
}
if (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');
}
} else {
throw new Error('Invalid "target" value, use a valid Element');
}
} // Define selection strategy based on `text` property.
if (text) {
return actions_copy(text, {
container: container
});
} // Defines which selection strategy based on `target` property.
if (target) {
return action === 'cut' ? actions_cut(target) : actions_copy(target, {
container: container
});
}
};
/* harmony default export */ var actions_default = (ClipboardActionDefault);
;// CONCATENATED MODULE: ./src/clipboard.js
function clipboard_typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return clipboard_typeof(obj); }
function clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function clipboard_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); } }
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); } }
function clipboard_createClass(Constructor, protoProps, staticProps) { if (protoProps) clipboard_defineProperties(Constructor.prototype, protoProps); if (staticProps) clipboard_defineProperties(Constructor, staticProps); return Constructor; }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
@@ -302,11 +203,29 @@ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.g
/**
* Helper function to retrieve attribute value.
* @param {String} suffix
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
var attribute = "data-clipboard-".concat(suffix);
if (!element.hasAttribute(attribute)) {
return;
}
return element.getAttribute(attribute);
}
/**
* Base class which takes one or more elements, adds event listeners to them,
* and instantiates a new `ClipboardAction` on each click.
*/
var Clipboard = /*#__PURE__*/function (_Emitter) {
_inherits(Clipboard, _Emitter);
@@ -319,7 +238,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
function Clipboard(trigger, options) {
var _this;
clipboard_classCallCheck(this, Clipboard);
_classCallCheck(this, Clipboard);
_this = _super.call(this);
@@ -336,7 +255,7 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
*/
clipboard_createClass(Clipboard, [{
_createClass(Clipboard, [{
key: "resolveOptions",
value: function resolveOptions() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -368,18 +287,26 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
key: "onClick",
value: function onClick(e) {
var trigger = e.delegateTarget || e.currentTarget;
if (this.clipboardAction) {
this.clipboardAction = null;
}
this.clipboardAction = new clipboard_action({
action: this.action(trigger),
target: this.target(trigger),
text: this.text(trigger),
var action = this.action(trigger) || 'copy';
var text = actions_default({
action: action,
container: this.container,
target: this.target(trigger),
text: this.text(trigger)
}); // Fires an event based on the copy operation result.
this.emit(text ? 'success' : 'error', {
action: action,
text: text,
trigger: trigger,
emitter: this
clearSelection: function clearSelection() {
if (trigger) {
trigger.focus();
}
document.activeElement.blur();
window.getSelection().removeAllRanges();
}
});
}
/**
@@ -407,9 +334,10 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
}
}
/**
* Returns the support of the given action, or all actions if no action is
* given.
* @param {String} [action]
* Allow fire programmatically a copy action
* @param {String|HTMLElement} target
* @param {Object} options
* @returns Text copied.
*/
}, {
@@ -430,13 +358,33 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
key: "destroy",
value: function destroy() {
this.listener.destroy();
if (this.clipboardAction) {
this.clipboardAction.destroy();
this.clipboardAction = null;
}
}
}], [{
key: "copy",
value: function copy(target) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
container: document.body
};
return actions_copy(target, options);
}
/**
* Allow fire programmatically a cut action
* @param {String|HTMLElement} target
* @returns Text cutted.
*/
}, {
key: "cut",
value: function cut(target) {
return actions_cut(target);
}
/**
* Returns the support of the given action, or all actions if no action is
* given.
* @param {String} [action]
*/
}, {
key: "isSupported",
value: function isSupported() {
var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];
@@ -451,29 +399,13 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
return Clipboard;
}((tiny_emitter_default()));
/**
* Helper function to retrieve attribute value.
* @param {String} suffix
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
var attribute = "data-clipboard-".concat(suffix);
if (!element.hasAttribute(attribute)) {
return;
}
return element.getAttribute(attribute);
}
/* harmony default export */ const clipboard = (Clipboard);
/* harmony default export */ var clipboard = (Clipboard);
/***/ }),
/***/ 828:
/***/ ((module) => {
/***/ (function(module) {
var DOCUMENT_NODE_TYPE = 9;
@@ -513,7 +445,7 @@ module.exports = closest;
/***/ }),
/***/ 438:
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
var closest = __webpack_require__(828);
@@ -598,7 +530,7 @@ module.exports = delegate;
/***/ }),
/***/ 879:
/***/ ((__unused_webpack_module, exports) => {
/***/ (function(__unused_webpack_module, exports) {
/**
* Check if argument is a HTML element.
@@ -654,7 +586,7 @@ exports.fn = function(value) {
/***/ }),
/***/ 370:
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
var is = __webpack_require__(879);
var delegate = __webpack_require__(438);
@@ -756,7 +688,7 @@ module.exports = listen;
/***/ }),
/***/ 817:
/***/ ((module) => {
/***/ (function(module) {
function select(element) {
var selectedText;
@@ -806,7 +738,7 @@ module.exports = select;
/***/ }),
/***/ 279:
/***/ ((module) => {
/***/ (function(module) {
function E () {
// Keep this empty so it's easier to inherit from
@@ -906,39 +838,39 @@ module.exports.TinyEmitter = E;
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ !function() {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ () => module['default'] :
/******/ () => module;
/******/ function() { return module['default']; } :
/******/ function() { return module; };
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/ }();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ !function() {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ __webpack_require__.d = function(exports, definition) {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/ }();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
/******/ })();
/******/ !function() {
/******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
/******/ }();
/******/
/************************************************************************/
/******/ // module exports must be returned from runtime so entry inlining is disabled
/******/ // startup
/******/ // Load entry module and return exports
/******/ return __webpack_require__(134);
/******/ return __webpack_require__(686);
/******/ })()
.default;
});

File diff suppressed because one or more lines are too long

11667
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
Package.describe({
name: 'zenorocha:clipboard',
summary: 'Modern copy to clipboard. No Flash. Just 3kb.',
version: '2.0.6',
version: '2.0.10',
git: 'https://github.com/zenorocha/clipboard.js',
});

View File

@@ -1,6 +1,6 @@
{
"name": "clipboard",
"version": "2.0.7",
"version": "2.0.10",
"description": "Modern copy to clipboard. No Flash. Just 2kb",
"homepage": "https://clipboardjs.com",
"repository": "zenorocha/clipboard.js",

29
src/actions/copy.js Normal file
View File

@@ -0,0 +1,29 @@
import select from 'select';
import command from '../common/command';
import createFakeElement from '../common/create-fake-element';
/**
* Copy action wrapper.
* @param {String|HTMLElement} target
* @param {Object} options
* @return {String}
*/
const ClipboardActionCopy = (
target,
options = { container: document.body }
) => {
let selectedText = '';
if (typeof target === 'string') {
const fakeElement = createFakeElement(target);
options.container.appendChild(fakeElement);
selectedText = select(fakeElement);
command('copy');
fakeElement.remove();
} else {
selectedText = select(target);
command('copy');
}
return selectedText;
};
export default ClipboardActionCopy;

15
src/actions/cut.js Normal file
View File

@@ -0,0 +1,15 @@
import select from 'select';
import command from '../common/command';
/**
* Cut action wrapper.
* @param {String|HTMLElement} target
* @return {String}
*/
const ClipboardActionCut = (target) => {
const selectedText = select(target);
command('cut');
return selectedText;
};
export default ClipboardActionCut;

53
src/actions/default.js Normal file
View File

@@ -0,0 +1,53 @@
import ClipboardActionCut from './cut';
import ClipboardActionCopy from './copy';
/**
* Inner function which performs selection from either `text` or `target`
* properties and then executes copy or cut operations.
* @param {Object} options
*/
const ClipboardActionDefault = (options = {}) => {
// Defines base properties passed from constructor.
const { action = 'copy', container, target, text } = options;
// Sets the `action` to be performed which can be either 'copy' or 'cut'.
if (action !== 'copy' && action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
}
// Sets the `target` property using an element that will be have its content copied.
if (target !== undefined) {
if (target && typeof target === 'object' && target.nodeType === 1) {
if (action === 'copy' && target.hasAttribute('disabled')) {
throw new Error(
'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'
);
}
if (
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'
);
}
} else {
throw new Error('Invalid "target" value, use a valid Element');
}
}
// Define selection strategy based on `text` property.
if (text) {
return ClipboardActionCopy(text, { container });
}
// Defines which selection strategy based on `target` property.
if (target) {
return action === 'cut'
? ClipboardActionCut(target)
: ClipboardActionCopy(target, { container });
}
};
export default ClipboardActionDefault;

View File

@@ -1,221 +0,0 @@
import select from 'select';
/**
* Inner class which performs selection from either `text` or `target`
* properties and then executes copy or cut operations.
*/
class ClipboardAction {
/**
* @param {Object} options
*/
constructor(options) {
this.resolveOptions(options);
this.initSelection();
}
/**
* Defines base properties passed from constructor.
* @param {Object} options
*/
resolveOptions(options = {}) {
this.action = options.action;
this.container = options.container;
this.emitter = options.emitter;
this.target = options.target;
this.text = options.text;
this.trigger = options.trigger;
this.selectedText = '';
}
/**
* Decides which selection strategy is going to be applied based
* on the existence of `text` and `target` properties.
*/
initSelection() {
if (this.text) {
this.selectFake();
} else if (this.target) {
this.selectTarget();
}
}
/**
* Creates a fake textarea element, sets its value from `text` property,
*/
createFakeElement() {
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
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
let yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = `${yPosition}px`;
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.value = this.text;
return this.fakeElem;
}
/**
* Get's the value of fakeElem,
* and makes a selection on it.
*/
selectFake() {
const fakeElem = this.createFakeElement();
this.fakeHandlerCallback = () => this.removeFake();
this.fakeHandler =
this.container.addEventListener('click', this.fakeHandlerCallback) ||
true;
this.container.appendChild(fakeElem);
this.selectedText = select(fakeElem);
this.copyText();
this.removeFake();
}
/**
* Only removes the fake element after another click event, that way
* a user can hit `Ctrl+C` to copy because selection still exists.
*/
removeFake() {
if (this.fakeHandler) {
this.container.removeEventListener('click', this.fakeHandlerCallback);
this.fakeHandler = null;
this.fakeHandlerCallback = null;
}
if (this.fakeElem) {
this.container.removeChild(this.fakeElem);
this.fakeElem = null;
}
}
/**
* Selects the content from element passed on `target` property.
*/
selectTarget() {
this.selectedText = select(this.target);
this.copyText();
}
/**
* Executes the copy operation based on the current selection.
*/
copyText() {
let succeeded;
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
*/
handleResult(succeeded) {
this.emitter.emit(succeeded ? 'success' : 'error', {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this),
});
}
/**
* Moves focus away from `target` and back to the trigger, removes current selection.
*/
clearSelection() {
if (this.trigger) {
this.trigger.focus();
}
document.activeElement.blur();
window.getSelection().removeAllRanges();
}
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
* @param {String} action
*/
set action(action = 'copy') {
this._action = action;
if (this._action !== 'copy' && this._action !== 'cut') {
throw new Error('Invalid "action" value, use either "copy" or "cut"');
}
}
/**
* Gets the `action` property.
* @return {String}
*/
get action() {
return this._action;
}
/**
* Sets the `target` property using an element
* that will be have its content copied.
* @param {Element} target
*/
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 {
throw new Error('Invalid "target" value, use a valid Element');
}
}
}
/**
* Gets the `target` property.
* @return {String|HTMLElement}
*/
get target() {
return this._target;
}
/**
* Destroy lifecycle.
*/
destroy() {
this.removeFake();
}
}
export default ClipboardAction;

191
src/clipboard.d.ts vendored
View File

@@ -1,136 +1,91 @@
/// <reference lib="dom"/>
import { TinyEmitter } from 'tiny-emitter';
type Action = 'cut' | 'copy';
type Target = string | HTMLElement;
type Trigger = string | HTMLElement | HTMLCollection | NodeList;
type Options = {
emmiter?: TinyEmitter;
text?: string;
action?: Action;
target?: Element;
trigger?: Trigger;
type Response = 'success' | 'error';
type CopyActionOptions = {
container?: Element;
selectedText?: string;
};
/**
* Inner class which performs selection from either `text` or `target`
* properties and then executes copy or cut operations.
*/
export declare class ClipboardAction {
constructor(options: Options);
/**
* Defines base properties passed from constructor.
*/
// better define the type of options
resolveOptions(options: Options): void;
/**
* Decides which selection strategy is going to be applied based
* on the existence of `text` and `target` properties.
*/
initSelection(): void;
/**
* Creates a fake textarea element, sets its value from `text` property,
* and makes a selection on it.
*/
selectFake(): void;
/**
* Only removes the fake element after another click event, that way
* a user can hit `Ctrl+C` to copy because selection still exists.
*/
removeFake(): void;
/**
* Selects the content from element passed on `target` property.
*/
selectTarget(): void;
/**
* Executes the copy operation based on the current selection.
*/
copyText(): void;
/**
* Fires an event based on the copy operation result.
*/
handleResult(succeeded: boolean): void;
/**
* Moves focus away from `target` and back to the trigger, removes current selection.
*/
clearSelection(): void;
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
*/
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
*/
action: Action;
/**
* Sets the `target` property using an element
* that will be have its content copied.
*/
target: Target;
/**
* Sets the `target` property using an element
* that will be have its content copied.
*/
/**
* Destroy lifecycle.
*/
destroy(): void;
}
/**
* Base class which takes one or more elements, adds event listeners to them,
* and instantiates a new `ClipboardAction` on each click.
*/
export declare class ClipboardJS {
constructor(trigger: Trigger, options?: Options);
declare class ClipboardJS {
constructor(
selector: string | Element | NodeListOf<Element>,
options?: ClipboardJS.Options
);
/**
* Defines if attributes would be resolved using internal setter functions
* or custom functions that were passed in the constructor.
* Subscribes to events that indicate the result of a copy/cut operation.
* @param type Event type ('success' or 'error').
* @param handler Callback function.
*/
resolveOptions(options: Options): void;
on(type: Response, handler: (e: ClipboardJS.Event) => void): this;
on(type: string, handler: (...args: any[]) => void): this;
/**
* Adds a click event listener to the passed trigger.
*/
listenClick(trigger: Trigger): void;
/**
* Defines a new `ClipboardAction` on each click event.
*/
onClick(e: Event): void;
/**
* Default `action` lookup function.
*/
defaultAction(trigger: Trigger): string | undefined;
/**
* Default `target` lookup function.
*/
// check the return here
defaultTarget(trigger: Trigger): void;
/**
* Returns the support of the given action, or all actions if no action is
* given.
*/
static isSupported(action?: Action): boolean;
/**
* Default `text` lookup function.
*/
defaultText(trigger: Trigger): string | undefined;
/**
* Destroy lifecycle.
* Clears all event bindings.
*/
destroy(): void;
/**
* Checks if clipboard.js is supported
*/
static isSupported(): boolean;
/**
* Fires a copy action
*/
static copy(target: string | Element, options: CopyActionOptions): string;
/**
* Fires a cut action
*/
static cut(target: string | Element): string;
}
/**
* Helper function to retrieve attribute value.
*/
export declare function getAttributeValue(
suffix: string,
element: Element
): string | undefined;
declare namespace ClipboardJS {
interface Options {
/**
* Overwrites default command ('cut' or 'copy').
* @param elem Current element
*/
action?(elem: Element): Action;
export default ClipboardJS;
/**
* Overwrites default target input element.
* @param elem Current element
* @returns <input> element to use.
*/
target?(elem: Element): Element;
/**
* Returns the explicit text to copy.
* @param elem Current element
* @returns Text to be copied.
*/
text?(elem: Element): string;
/**
* For use in Bootstrap Modals or with any
* other library that changes the focus
* you'll want to set the focused element
* as the container value.
*/
container?: Element;
}
interface Event {
action: string;
text: string;
trigger: Element;
clearSelection(): void;
}
}
export = ClipboardJS;
export as namespace ClipboardJS;

View File

@@ -1,6 +1,8 @@
import Emitter from 'tiny-emitter';
import listen from 'good-listener';
import ClipboardAction from './clipboard-action';
import ClipboardActionDefault from './actions/default';
import ClipboardActionCut from './actions/cut';
import ClipboardActionCopy from './actions/copy';
/**
* Helper function to retrieve attribute value.
@@ -67,18 +69,26 @@ class Clipboard extends Emitter {
*/
onClick(e) {
const trigger = e.delegateTarget || e.currentTarget;
if (this.clipboardAction) {
this.clipboardAction = null;
}
this.clipboardAction = new ClipboardAction({
action: this.action(trigger),
const action = this.action(trigger) || 'copy';
const text = ClipboardActionDefault({
action,
container: this.container,
target: this.target(trigger),
text: this.text(trigger),
container: this.container,
});
// Fires an event based on the copy operation result.
this.emit(text ? 'success' : 'error', {
action,
text,
trigger,
emitter: this,
clearSelection() {
if (trigger) {
trigger.focus();
}
document.activeElement.blur();
window.getSelection().removeAllRanges();
},
});
}
@@ -102,6 +112,25 @@ class Clipboard extends Emitter {
}
}
/**
* Allow fire programmatically a copy action
* @param {String|HTMLElement} target
* @param {Object} options
* @returns Text copied.
*/
static copy(target, options = { container: document.body }) {
return ClipboardActionCopy(target, options);
}
/**
* Allow fire programmatically a cut action
* @param {String|HTMLElement} target
* @returns Text cutted.
*/
static cut(target) {
return ClipboardActionCut(target);
}
/**
* Returns the support of the given action, or all actions if no action is
* given.
@@ -131,11 +160,6 @@ class Clipboard extends Emitter {
*/
destroy() {
this.listener.destroy();
if (this.clipboardAction) {
this.clipboardAction.destroy();
this.clipboardAction = null;
}
}
}

View File

@@ -1,4 +1,4 @@
import { expectType } from 'tsd';
import Clipboard from './clipboard';
import * as Clipboard from './clipboard';
expectType<Clipboard>(new Clipboard('.btn'));

12
src/common/command.js Normal file
View File

@@ -0,0 +1,12 @@
/**
* Executes a given operation type.
* @param {String} type
* @return {Boolean}
*/
export default function command(type) {
try {
return document.execCommand(type);
} catch (err) {
return false;
}
}

View File

@@ -0,0 +1,26 @@
/**
* Creates a fake textarea element with a value.
* @param {String} value
* @return {HTMLElement}
*/
export default function createFakeElement(value) {
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
const fakeElement = document.createElement('textarea');
// Prevent zooming on iOS
fakeElement.style.fontSize = '12pt';
// Reset box model
fakeElement.style.border = '0';
fakeElement.style.padding = '0';
fakeElement.style.margin = '0';
// Move element out of screen horizontally
fakeElement.style.position = 'absolute';
fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px';
// Move element to the same position vertically
let yPosition = window.pageYOffset || document.documentElement.scrollTop;
fakeElement.style.top = `${yPosition}px`;
fakeElement.setAttribute('readonly', '');
fakeElement.value = value;
return fakeElement;
}

55
test/actions/copy.js Normal file
View File

@@ -0,0 +1,55 @@
import ClipboardActionCopy from '../../src/actions/copy';
describe('ClipboardActionCopy', () => {
before(() => {
global.input = document.createElement('input');
global.input.setAttribute('id', 'input');
global.input.setAttribute('value', 'abc');
document.body.appendChild(global.input);
global.paragraph = document.createElement('p');
global.paragraph.setAttribute('id', 'paragraph');
global.paragraph.textContent = 'abc';
document.body.appendChild(global.paragraph);
});
after(() => {
document.body.innerHTML = '';
});
describe('#selectText', () => {
it('should select its value based on input target', () => {
const selectedText = ClipboardActionCopy(
document.querySelector('#input'),
{
container: document.body,
}
);
assert.equal(selectedText, document.querySelector('#input').value);
});
it('should select its value based on element target', () => {
const selectedText = ClipboardActionCopy(
document.querySelector('#paragraph'),
{
container: document.body,
}
);
assert.equal(
selectedText,
document.querySelector('#paragraph').textContent
);
});
it('should select its value based on text', () => {
const text = 'abc';
const selectedText = ClipboardActionCopy(text, {
container: document.body,
});
assert.equal(selectedText, text);
});
});
});

32
test/actions/cut.js Normal file
View File

@@ -0,0 +1,32 @@
import ClipboardActionCut from '../../src/actions/cut';
describe('ClipboardActionCut', () => {
before(() => {
global.input = document.createElement('input');
global.input.setAttribute('id', 'input');
global.input.setAttribute('value', 'abc');
document.body.appendChild(global.input);
global.paragraph = document.createElement('p');
global.paragraph.setAttribute('id', 'paragraph');
global.paragraph.textContent = 'abc';
document.body.appendChild(global.paragraph);
});
after(() => {
document.body.innerHTML = '';
});
describe('#selectText', () => {
it('should select its value', () => {
const selectedText = ClipboardActionCut(
document.querySelector('#input'),
{
container: document.body,
}
);
assert.equal(selectedText, document.querySelector('#input').value);
});
});
});

80
test/actions/default.js Normal file
View File

@@ -0,0 +1,80 @@
import ClipboardActionDefault from '../../src/actions/default';
describe('ClipboardActionDefault', () => {
before(() => {
global.input = document.createElement('input');
global.input.setAttribute('id', 'input');
global.input.setAttribute('value', 'abc');
document.body.appendChild(global.input);
global.paragraph = document.createElement('p');
global.paragraph.setAttribute('id', 'paragraph');
global.paragraph.textContent = 'abc';
document.body.appendChild(global.paragraph);
});
after(() => {
document.body.innerHTML = '';
});
describe('#resolveOptions', () => {
it('should set base properties', () => {
const selectedText = ClipboardActionDefault({
container: document.body,
text: 'foo',
});
assert.equal(selectedText, 'foo');
});
});
describe('#set action', () => {
it('should throw an error since "action" is invalid', (done) => {
try {
let clip = ClipboardActionDefault({
text: 'foo',
action: 'paste',
});
} catch (e) {
assert.equal(
e.message,
'Invalid "action" value, use either "copy" or "cut"'
);
done();
}
});
});
describe('#set target', () => {
it('should throw an error since "target" do not match any element', (done) => {
try {
let clip = ClipboardActionDefault({
target: document.querySelector('#foo'),
});
} catch (e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done();
}
});
});
describe('#selectedText', () => {
it('should select text from editable element', () => {
const selectedText = ClipboardActionDefault({
container: document.body,
target: document.querySelector('#input'),
});
assert.equal(selectedText, 'abc');
});
it('should select text from non-editable element', () => {
const selectedText = ClipboardActionDefault({
container: document.body,
target: document.querySelector('#paragraph'),
});
assert.equal(selectedText, 'abc');
});
});
});

View File

@@ -1,248 +0,0 @@
import Emitter from 'tiny-emitter';
import ClipboardAction from '../src/clipboard-action';
describe('ClipboardAction', () => {
before(() => {
global.input = document.createElement('input');
global.input.setAttribute('id', 'input');
global.input.setAttribute('value', 'abc');
document.body.appendChild(global.input);
global.paragraph = document.createElement('p');
global.paragraph.setAttribute('id', 'paragraph');
global.paragraph.textContent = 'abc';
document.body.appendChild(global.paragraph);
});
after(() => {
document.body.innerHTML = '';
});
describe('#resolveOptions', () => {
it('should set base properties', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'foo',
});
assert.property(clip, 'action');
assert.property(clip, 'container');
assert.property(clip, 'emitter');
assert.property(clip, 'target');
assert.property(clip, 'text');
assert.property(clip, 'trigger');
assert.property(clip, 'selectedText');
});
});
describe('#initSelection', () => {
it('should set the position right style property', (done) => {
// Set document direction
document.documentElement.setAttribute('dir', 'rtl');
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'foo',
});
const el = clip.createFakeElement();
assert.equal(el.style.right, '-9999px');
done();
});
});
describe('#set action', () => {
it('should throw an error since "action" is invalid', (done) => {
try {
let clip = new ClipboardAction({
text: 'foo',
action: 'paste',
});
} catch (e) {
assert.equal(
e.message,
'Invalid "action" value, use either "copy" or "cut"'
);
done();
}
});
});
describe('#set target', () => {
it('should throw an error since "target" do not match any element', (done) => {
try {
let clip = new ClipboardAction({
target: document.querySelector('#foo'),
});
} catch (e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done();
}
});
});
describe('#selectText', () => {
it('should create a fake element and select its value', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah',
});
const el = clip.createFakeElement();
assert.equal(clip.selectedText, el.value);
});
});
describe('#removeFake', () => {
it('should remove a temporary fake element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah',
});
clip.removeFake();
assert.equal(clip.fakeElem, null);
});
});
describe('#selectTarget', () => {
it('should select text from editable element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input'),
});
assert.equal(clip.selectedText, clip.target.value);
});
it('should select text from non-editable element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#paragraph'),
});
assert.equal(clip.selectedText, clip.target.textContent);
});
});
describe('#copyText', () => {
before(() => {
global.stub = sinon.stub(document, 'execCommand');
});
after(() => {
global.stub.restore();
});
it('should fire a success event on browsers that support copy command', (done) => {
global.stub.returns(true);
let emitter = new Emitter();
emitter.on('success', () => {
done();
});
let clip = new ClipboardAction({
emitter,
target: document.querySelector('#input'),
});
});
it('should fire an error event on browsers that support copy command', (done) => {
global.stub.returns(false);
let emitter = new Emitter();
emitter.on('error', () => {
done();
});
let clip = new ClipboardAction({
emitter,
target: document.querySelector('#input'),
});
});
});
describe('#handleResult', () => {
it('should fire a success event with certain properties', (done) => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input'),
});
clip.emitter.on('success', (e) => {
assert.property(e, 'action');
assert.property(e, 'text');
assert.property(e, 'trigger');
assert.property(e, 'clearSelection');
done();
});
clip.handleResult(true);
});
it('should fire a error event with certain properties', (done) => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input'),
});
clip.emitter.on('error', (e) => {
assert.property(e, 'action');
assert.property(e, 'trigger');
assert.property(e, 'clearSelection');
done();
});
clip.handleResult(false);
});
});
describe('#clearSelection', () => {
it('should remove focus from target and text selection', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
target: document.querySelector('#input'),
});
clip.clearSelection();
let selectedElem = document.activeElement;
let selectedText = window.getSelection().toString();
assert.equal(selectedElem, document.body);
assert.equal(selectedText, '');
});
});
describe('#destroy', () => {
it('should destroy an existing fake element', () => {
let clip = new ClipboardAction({
emitter: new Emitter(),
container: document.body,
text: 'blah',
});
clip.selectFake();
clip.destroy();
assert.equal(clip.fakeElem, null);
});
});
});

View File

@@ -1,5 +1,4 @@
import Clipboard from '../src/clipboard';
import ClipboardAction from '../src/clipboard-action';
describe('Clipboard', () => {
before(() => {
@@ -75,22 +74,28 @@ describe('Clipboard', () => {
});
describe('#onClick', () => {
it('should create a new instance of ClipboardAction', () => {
it('should init when called', (done) => {
let clipboard = new Clipboard('.btn');
clipboard.on('success', () => {
done();
});
clipboard.onClick(global.event);
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", (done) => {
let clipboard = new Clipboard('.btn');
let bubbledEvent = {
target: global.span,
currentTarget: global.button,
};
clipboard.on('success', () => {
done();
});
clipboard.onClick(bubbledEvent);
assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
});
it('should throw an exception when target is invalid', (done) => {
@@ -120,8 +125,24 @@ describe('Clipboard', () => {
});
});
describe('#static copy', () => {
it('should copy in an programatic way based on text', () => {
assert.equal(Clipboard.copy('lorem'), 'lorem');
});
it('should copy in an programatic way based on target', () => {
assert.equal(Clipboard.copy(document.querySelector('span')), 'bar');
});
});
describe('#static cut', () => {
it('should cut in an programatic way based on text', () => {
assert.equal(Clipboard.cut(document.querySelector('span')), 'bar');
});
});
describe('#destroy', () => {
it('should destroy an existing instance of ClipboardAction', () => {
it('should destroy an existing instance of ClipboardActionDefault', () => {
let clipboard = new Clipboard('.btn');
clipboard.onClick(global.event);
@@ -130,4 +151,42 @@ describe('Clipboard', () => {
assert.equal(clipboard.clipboardAction, null);
});
});
describe('#events', () => {
it('should fire a success event with certain properties', (done) => {
let clipboard = new Clipboard('.btn');
clipboard.on('success', (e) => {
assert.property(e, 'action');
assert.equal(e.action, 'copy');
assert.property(e, 'text');
assert.property(e, 'trigger');
assert.property(e, 'clearSelection');
done();
});
clipboard.onClick(global.event);
});
});
describe('#clearSelection', () => {
it('should remove focus from target and text selection', (done) => {
let clipboard = new Clipboard('.btn');
clipboard.on('success', (e) => {
let selectedElem = document.activeElement;
let selectedText = window.getSelection().toString();
e.clearSelection();
assert.equal(selectedElem, document.body);
assert.equal(selectedText, '');
done();
});
clipboard.onClick(global.event);
});
});
});

49
test/common/command.js Normal file
View File

@@ -0,0 +1,49 @@
import select from 'select';
import command from '../../src/common/command';
describe('#command', () => {
before(() => {
global.stub = sinon.stub(document, 'execCommand');
global.input = document.createElement('input');
global.input.setAttribute('id', 'input');
global.input.setAttribute('value', 'abc');
document.body.appendChild(global.input);
});
after(() => {
global.stub.restore();
document.body.innerHTML = '';
});
it('should execute cut', (done) => {
global.stub.returns(true);
select(document.querySelector('#input'));
assert.isTrue(command('cut'));
done();
});
it('should execute copy', (done) => {
global.stub.returns(true);
select(document.querySelector('#input'));
assert.isTrue(command('copy'));
done();
});
it('should not execute copy', (done) => {
global.stub.returns(false);
select(document.querySelector('#input'));
assert.isFalse(command('copy'));
done();
});
it('should not execute cut', (done) => {
global.stub.returns(false);
select(document.querySelector('#input'));
assert.isFalse(command('cut'));
done();
});
});

View File

@@ -0,0 +1,13 @@
import createFakeElement from '../../src/common/create-fake-element';
describe('createFakeElement', () => {
it('should define a fake element and set the position right style property', (done) => {
// Set document direction
document.documentElement.setAttribute('dir', 'rtl');
const el = createFakeElement(document.body);
assert.equal(el.style.right, '-9999px');
done();
});
});

View File

@@ -13,6 +13,7 @@ Licensed MIT © Zeno Rocha`;
module.exports = {
entry: './src/clipboard.js',
mode: 'production',
target: ['web', 'es5'],
output: {
filename: production ? 'clipboard.min.js' : 'clipboard.js',
path: path.resolve(__dirname, 'dist'),