diff --git a/.gitignore b/.gitignore
index ff4dcd8..d7aa998 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ bower_components
node_modules
yarn-error.log
yarn.lock
+.DS_Store
diff --git a/demo/target-programmatic-copy.html b/demo/target-programmatic-copy.html
new file mode 100644
index 0000000..0f00ded
--- /dev/null
+++ b/demo/target-programmatic-copy.html
@@ -0,0 +1,28 @@
+
+
+
+
+ target-programmatic-copy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/target-programmatic-cut.html b/demo/target-programmatic-cut.html
new file mode 100644
index 0000000..3b5e9f0
--- /dev/null
+++ b/demo/target-programmatic-cut.html
@@ -0,0 +1,28 @@
+
+
+
+
+ target-programmatic-cut
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/text-programmatic-copy.html b/demo/text-programmatic-copy.html
new file mode 100644
index 0000000..7cb7ed0
--- /dev/null
+++ b/demo/text-programmatic-copy.html
@@ -0,0 +1,27 @@
+
+
+
+
+ text-programmatic-copy
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/clipboard.js b/dist/clipboard.js
index d54de7e..e98e221 100644
--- a/dist/clipboard.js
+++ b/dist/clipboard.js
@@ -17,7 +17,7 @@
return /******/ (function() { // webpackBootstrap
/******/ var __webpack_modules__ = ({
-/***/ 134:
+/***/ 747:
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
@@ -36,265 +36,156 @@ 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/clipboard-action.js
+;// 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/clipboard-action-cut.js
+
+
+/**
+ * Cut action wrapper.
+ * @param {HTMLElement} target
+ * @return {String}
+ */
+
+var ClipboardActionCut = function ClipboardActionCut(target) {
+ var selectedText = select_default()(target);
+ command('cut');
+ return selectedText;
+};
+
+/* harmony default export */ var clipboard_action_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/clipboard-action-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 clipboard_action_copy = (ClipboardActionCopy);
+;// CONCATENATED MODULE: ./src/clipboard-action-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 clipboard_action_copy(text, {
+ container: container
+ });
+ } // Defines which selection strategy based on `target` property.
+
+
+ if (target) {
+ return action === 'cut' ? clipboard_action_cut(target) : clipboard_action_copy(target, {
+ container: container
+ });
+ }
+};
+
+/* harmony default export */ var clipboard_action_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 _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,
- */
-
- }, {
- key: "createFakeElement",
- value: function createFakeElement() {
- var 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
-
- var yPosition = window.pageYOffset || document.documentElement.scrollTop;
- this.fakeElem.style.top = "".concat(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.
- */
-
- }, {
- key: "selectFake",
- value: function selectFake() {
- var _this = this;
-
- var fakeElem = this.createFakeElement();
-
- this.fakeHandlerCallback = function () {
- return _this.removeFake();
- };
-
- this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true;
- this.container.appendChild(fakeElem);
- this.selectedText = select_default()(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.
- */
-
- }, {
- 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 */ var clipboard_action = (ClipboardAction);
-;// 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 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 clipboard_createClass(Constructor, protoProps, staticProps) { if (protoProps) clipboard_defineProperties(Constructor.prototype, protoProps); if (staticProps) clipboard_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); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
@@ -312,6 +203,8 @@ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.g
+
+
/**
* Helper function to retrieve attribute value.
* @param {String} suffix
@@ -345,9 +238,11 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
function Clipboard(trigger, options) {
var _this;
- clipboard_classCallCheck(this, Clipboard);
+ _classCallCheck(this, Clipboard);
_this = _super.call(this);
+ _this.ClipboardActionCut = clipboard_action_cut.bind(_assertThisInitialized(_this));
+ _this.ClipboardActionCopy = clipboard_action_copy.bind(_assertThisInitialized(_this));
_this.resolveOptions(options);
@@ -362,7 +257,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] : {};
@@ -394,18 +289,25 @@ 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({
+ var selectedText = clipboard_action_default({
action: this.action(trigger),
- target: this.target(trigger),
- text: this.text(trigger),
container: this.container,
+ target: this.target(trigger),
+ text: this.text(trigger)
+ }); // Fires an event based on the copy operation result.
+
+ this.emit(selectedText ? 'success' : 'error', {
+ action: this.action,
+ text: selectedText,
trigger: trigger,
- emitter: this
+ clearSelection: function clearSelection() {
+ if (trigger) {
+ trigger.focus();
+ }
+
+ document.activeElement.blur();
+ window.getSelection().removeAllRanges();
+ }
});
}
/**
@@ -432,12 +334,6 @@ var Clipboard = /*#__PURE__*/function (_Emitter) {
return document.querySelector(selector);
}
}
- /**
- * Returns the support of the given action, or all actions if no action is
- * given.
- * @param {String} [action]
- */
-
}, {
key: "defaultText",
@@ -456,13 +352,27 @@ 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 clipboard_action_copy(target, options);
+ }
+ }, {
+ key: "cut",
+ value: function cut(target) {
+ return clipboard_action_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'];
@@ -948,7 +858,7 @@ module.exports.TinyEmitter = E;
/******/ // 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__(747);
/******/ })()
.default;
});
\ No newline at end of file
diff --git a/dist/clipboard.min.js b/dist/clipboard.min.js
index 95f55d7..54b3c46 100644
--- a/dist/clipboard.min.js
+++ b/dist/clipboard.min.js
@@ -4,4 +4,4 @@
*
* Licensed MIT © Zeno Rocha
*/
-!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={134:function(t,e,n){"use strict";n.d(e,{default:function(){return r}});var e=n(279),i=n.n(e),e=n(370),a=n.n(e),e=n(817),o=n.n(e);function c(t){return(c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function u(t,e){for(var n=0;n {
+ 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;
diff --git a/src/actions/cut.js b/src/actions/cut.js
new file mode 100644
index 0000000..537ce48
--- /dev/null
+++ b/src/actions/cut.js
@@ -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;
diff --git a/src/actions/default.js b/src/actions/default.js
new file mode 100644
index 0000000..d1af8e5
--- /dev/null
+++ b/src/actions/default.js
@@ -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;
diff --git a/src/clipboard-action.js b/src/clipboard-action.js
deleted file mode 100644
index ab6a5f6..0000000
--- a/src/clipboard-action.js
+++ /dev/null
@@ -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;
diff --git a/src/clipboard.d.ts b/src/clipboard.d.ts
index b45dcc6..98b959f 100644
--- a/src/clipboard.d.ts
+++ b/src/clipboard.d.ts
@@ -2,11 +2,7 @@
type Action = 'cut' | 'copy';
type Response = 'success' | 'error';
-
-type Options = {
- text?: string;
- action?: Action;
- target?: Element;
+type CopyActionOptions = {
container?: Element;
};
@@ -38,6 +34,17 @@ declare class ClipboardJS {
* 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;
}
declare namespace ClipboardJS {
diff --git a/src/clipboard.js b/src/clipboard.js
index 6e26253..ffe370b 100644
--- a/src/clipboard.js
+++ b/src/clipboard.js
@@ -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,25 @@ class Clipboard extends Emitter {
*/
onClick(e) {
const trigger = e.delegateTarget || e.currentTarget;
-
- if (this.clipboardAction) {
- this.clipboardAction = null;
- }
-
- this.clipboardAction = new ClipboardAction({
+ const selectedText = ClipboardActionDefault({
action: this.action(trigger),
+ 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(selectedText ? 'success' : 'error', {
+ action: this.action,
+ text: selectedText,
trigger,
- emitter: this,
+ clearSelection() {
+ if (trigger) {
+ trigger.focus();
+ }
+ document.activeElement.blur();
+ window.getSelection().removeAllRanges();
+ },
});
}
@@ -102,6 +111,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 +159,6 @@ class Clipboard extends Emitter {
*/
destroy() {
this.listener.destroy();
-
- if (this.clipboardAction) {
- this.clipboardAction.destroy();
- this.clipboardAction = null;
- }
}
}
diff --git a/src/clipboard.test-d.ts b/src/clipboard.test-d.ts
index d09b4eb..dceb32e 100644
--- a/src/clipboard.test-d.ts
+++ b/src/clipboard.test-d.ts
@@ -1,4 +1,4 @@
import { expectType } from 'tsd';
-import Clipboard from './clipboard';
+import * as Clipboard from './clipboard';
expectType(new Clipboard('.btn'));
diff --git a/src/common/command.js b/src/common/command.js
new file mode 100644
index 0000000..5980cb4
--- /dev/null
+++ b/src/common/command.js
@@ -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;
+ }
+}
diff --git a/src/common/create-fake-element.js b/src/common/create-fake-element.js
new file mode 100644
index 0000000..aa8db8d
--- /dev/null
+++ b/src/common/create-fake-element.js
@@ -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;
+}
diff --git a/test/actions/copy.js b/test/actions/copy.js
new file mode 100644
index 0000000..201c1d6
--- /dev/null
+++ b/test/actions/copy.js
@@ -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);
+ });
+ });
+});
diff --git a/test/actions/cut.js b/test/actions/cut.js
new file mode 100644
index 0000000..44c1ac5
--- /dev/null
+++ b/test/actions/cut.js
@@ -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);
+ });
+ });
+});
diff --git a/test/actions/default.js b/test/actions/default.js
new file mode 100644
index 0000000..8fb99cd
--- /dev/null
+++ b/test/actions/default.js
@@ -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');
+ });
+ });
+});
diff --git a/test/clipboard-action.js b/test/clipboard-action.js
deleted file mode 100644
index b589b4c..0000000
--- a/test/clipboard-action.js
+++ /dev/null
@@ -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);
- });
- });
-});
diff --git a/test/clipboard.js b/test/clipboard.js
index 0a59e20..b5059c6 100644
--- a/test/clipboard.js
+++ b/test/clipboard.js
@@ -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,41 @@ 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.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);
+ });
+ });
});
diff --git a/test/common/command.js b/test/common/command.js
new file mode 100644
index 0000000..474d7c1
--- /dev/null
+++ b/test/common/command.js
@@ -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();
+ });
+});
diff --git a/test/common/create-fake-element.js b/test/common/create-fake-element.js
new file mode 100644
index 0000000..98934a8
--- /dev/null
+++ b/test/common/create-fake-element.js
@@ -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();
+ });
+});