Compare commits

...

103 Commits

Author SHA1 Message Date
Beto Muniz
2b2f9eef6f 2.0.11 2022-05-04 14:51:40 -03:00
Beto Muniz
21db7250ed Support more HTML input types. Close #800 (#808)
* Support more HTML input types.

* Improve test description. Remove .only

* Apply logic only when target is an input element
2022-05-04 14:47:44 -03:00
Patrick H. Lauke
08169bce8c Fix #805 don't blur() the trigger after a clipboard action (#807)
* Remove the `blur()` following a clipboard action

It's pointless to set `focus()` on the trigger first, if in the next step you're just going to `blur()` the active element anyway.

* Tweak test to not expect active element to be body

Since it's now not `blur()`ing anymore

* Fix test

see https://github.com/zenorocha/clipboard.js/pull/807#discussion_r862080076
2022-05-04 14:45:44 -03:00
dependabot[bot]
c7c7fda422 chore(deps): bump ansi-regex from 3.0.0 to 3.0.1 (#806)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-04 09:32:02 +01:00
Shabai Liu
9b0c87b184 Fix type for copy function (#795)
Co-authored-by: Shabai Liu <shabai_liu@apple.com>
2022-04-28 17:15:48 -03:00
dependabot[bot]
98c96a1136 chore(deps): bump minimist from 1.2.5 to 1.2.6 (#803)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-22 09:40:26 +01:00
dependabot[bot]
7bb4433be0 chore(deps-dev): bump karma from 6.3.14 to 6.3.16 (#799) 2022-03-06 13:53:54 +00:00
dependabot[bot]
67067f316f Bump pathval from 1.1.0 to 1.1.1 (#796)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-18 16:08:43 +00:00
dependabot[bot]
2d11cf1a9d Bump karma from 6.0.0 to 6.3.14 (#797)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-18 16:08:32 +00:00
dependabot[bot]
88bb463cc5 Bump follow-redirects from 1.14.7 to 1.14.8 (#798)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-18 16:08:20 +00:00
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
vitormalencar
c38b4ee76f 2.0.7 2021-03-06 13:51:09 +01:00
Vitor Alencar
15737fe877 Merge pull request #733 from zenorocha/feature-732-removing-dom-el 2021-03-01 14:07:29 +01:00
vitormalencar
c23b30d052 feat : add blank line between class members 2021-02-25 16:29:36 +01:00
vitormalencar
4ab89f3d98 feat: merge with master 2021-02-25 16:25:24 +01:00
vitormalencar
7b7ce32b65 Merge branch 'master' of github.com:zenorocha/clipboard.js into feature-732-removing-dom-el
* 'master' of github.com:zenorocha/clipboard.js:
  refactor(workflows): remove unused lint file
  feat(workflows): add lint code job
  chore(eslint): add comments and new rules
  chore(deps): remove sort-package-json
  refactor: remove eslint ignore rules comments
  ci(lint): create a ci workflow
  chore(clipboard): remove linter bugs
  chore(deps): add dependencies and new scripts
  chore(test): remove linter bugs
  chore(linter): add linter configuration
  feat(eslint): add linter configuration
  chore(deps): add linter
  updating naming
  adding deploy action
2021-02-25 16:21:53 +01:00
vitormalencar
5d3da80640 feat: updating test coverage 2021-02-25 16:16:42 +01:00
vitormalencar
b66010bf77 feat: decoupling create fakeElemet logic 2021-02-25 16:16:12 +01:00
Vitor Alencar
17be1af63e Merge pull request #727 from zenorocha/feature-npm-deploy 2021-02-25 15:00:21 +01:00
vitormalencar
eff98406b9 updating tests 2021-02-24 22:47:58 +01:00
vitormalencar
dc2b5bf0ea removing element after selection 2021-02-24 22:47:49 +01:00
Vitor Alencar
16966aac8d Merge pull request #731 from r3nanp/master 2021-02-23 10:27:38 +01:00
r3nanp
20b70bdbca refactor(workflows): remove unused lint file 2021-02-20 12:58:24 -03:00
r3nanp
fd836b82d4 feat(workflows): add lint code job 2021-02-16 19:05:21 -03:00
r3nanp
0daa6ccd05 chore(eslint): add comments and new rules 2021-02-16 19:03:28 -03:00
r3nanp
ddbbc238b6 chore(deps): remove sort-package-json 2021-02-16 19:02:02 -03:00
r3nanp
cb1fec4c6a refactor: remove eslint ignore rules comments 2021-02-16 19:01:13 -03:00
r3nanp
b229b550f6 ci(lint): create a ci workflow 2021-02-13 11:04:34 -03:00
r3nanp
b21b99fe5f chore(clipboard): remove linter bugs 2021-02-13 10:56:07 -03:00
r3nanp
da6b7dd7a3 chore(deps): add dependencies and new scripts 2021-02-13 10:55:16 -03:00
r3nanp
99c1b9488b chore(test): remove linter bugs 2021-02-13 10:53:22 -03:00
r3nanp
5d7ce2f7f6 chore(linter): add linter configuration 2021-02-13 10:52:08 -03:00
r3nanp
14ee2e3137 feat(eslint): add linter configuration 2021-02-13 09:55:38 -03:00
r3nanp
9f0e246f64 chore(deps): add linter 2021-02-13 09:47:15 -03:00
Vitor Alencar
982d5ef906 Merge pull request #730 from zenorocha/helderburato-patch-1
Fix readme.md badge
2021-02-12 18:16:40 +01:00
Helder Burato Berto
4553dfee2a Fix readme.md badge 2021-02-12 00:39:27 +00:00
Helder Burato Berto
28e9d15d13 Merge pull request #729 from zenorocha/chore-upgrade-husky
Chore upgrade husky
2021-02-11 23:25:41 +00:00
Helder Burato Berto
8718bdd2a2 chore(husky): add pre-commit
It triggers lint-staged via package manager
2021-02-11 15:45:35 +00:00
Helder Burato Berto
dd74f53217 chore(packages): add husky v5.x.x 2021-02-11 15:44:57 +00:00
Vitor Alencar
119edb067d Merge pull request #726 from zenorocha/feature-travis-migration 2021-02-11 15:12:54 +01:00
vitormalencar
9e34b45841 updating naming 2021-02-08 18:40:39 +01:00
vitormalencar
9e3330084d adding deploy action 2021-02-07 12:50:33 +01:00
vitormalencar
94a9d8d3ea updating readme 2021-02-07 12:25:17 +01:00
vitormalencar
6f2b95fb81 removing travis config 2021-02-07 12:25:05 +01:00
Vitor Alencar
2b48909ff1 Update test.js.yml 2021-02-07 11:44:29 +01:00
Vitor Alencar
5b8db05ad3 Create test.js.yml
Adding GH Action
2021-02-07 11:31:44 +01:00
Helder Burato Berto
17fd75cfb6 Merge pull request #723 from zenorocha/docs-gh-templates
Add GH templates
2021-01-28 19:40:39 +00:00
Helder Burato Berto
40bf3736c4 Add Operational System to bug template 2021-01-28 14:36:50 +00:00
Helder Burato Berto
82e0dca2ea Add pull request template 2021-01-28 14:30:22 +00:00
Helder Burato Berto
e6826488d7 Add issues template
It adds templates to the following types:
- Bugs
- Proposals
- Documentation
2021-01-26 21:28:48 +00:00
Helder Burato Berto
ac2deb40e9 Remove old issue template 2021-01-26 21:28:23 +00:00
Vitor Alencar
41c73cb7eb Merge pull request #722 from zenorocha/feature-typescript 2021-01-26 12:33:43 +01:00
vitormalencar
b460d6864c update type for emitter 2021-01-24 16:53:30 +01:00
vitormalencar
9870b14e80 Add initial type definitions 2021-01-22 15:59:50 +01:00
vitormalencar
12a4f43337 Add types entry and tsd dependency 2021-01-22 15:59:23 +01:00
Beto Muniz
62eef3e5b0 Merge pull request #720 from zenorocha/small-enhancements
Remove unnecessary code and add node version control
2021-01-21 17:11:55 -03:00
Beto Muniz
ddb5adc6f8 Merge branch 'master' into small-enhancements
* master:
  update code style
  update prettier config
  update dependencies
  update code style
  Add prettier husky and  lint-staged
  Add prettier config file
  add prettier config files
2021-01-21 14:25:36 -03:00
Vitor Alencar
221efae529 Merge pull request #721 from zenorocha/feature-prettier 2021-01-21 17:32:52 +01:00
vitormalencar
971834388c update code style 2021-01-21 11:45:33 +01:00
vitormalencar
d8a51544bd update prettier config 2021-01-21 11:44:11 +01:00
vitormalencar
2239c05b4d update dependencies 2021-01-20 17:33:16 +01:00
vitormalencar
6efd3ba519 Merge branch 'master' of github.com:zenorocha/clipboard.js into feature-prettier
* 'master' of github.com:zenorocha/clipboard.js:
  Update dev dependencies.
2021-01-20 17:26:47 +01:00
Beto Muniz
d9b90c2f5d Add node version control to the project 2021-01-20 12:58:11 -03:00
Beto Muniz
e5d3f91f35 Remove unnecessary dependency 2021-01-20 12:57:58 -03:00
vitormalencar
2660565b61 update code style 2021-01-20 16:55:03 +01:00
vitormalencar
35688322f3 Add prettier husky and lint-staged 2021-01-20 16:32:40 +01:00
vitormalencar
498dfb9bdf Add prettier config file 2021-01-20 16:31:18 +01:00
vitormalencar
b937bd631f add prettier config files 2021-01-19 19:06:14 +01:00
Beto Muniz
6479739564 Merge pull request #719 from zenorocha/update-devdeps
Update dev dependencies.
2021-01-19 15:02:14 -03:00
Beto Muniz
43aa5786c9 Update dev dependencies. 2021-01-19 12:42:06 -03:00
Beto Muniz
5ea83e4473 Merge pull request #703 from realityking/babel7
Upgrade babel to version 7
2021-01-19 11:08:05 -03:00
dependabot[bot]
27ee5bbbd0 Bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-19 04:34:05 -08:00
Christian Oliff
dd84bc1f8a Add homepage to package.json 2021-01-19 04:31:03 -08:00
Rouven Weßling
4456d61877 Upgrade babel to version 7 2020-10-04 15:20:54 +02:00
dependabot[bot]
26a8d63924 Bump acorn from 6.4.0 to 6.4.1
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.1.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-14 17:11:32 -07:00
Zeno Rocha
fddd2aac5f 2.0.6 2020-03-04 22:26:34 -08:00
Zeno Rocha
e430d056ad Fix "isSupported" behavior - Closes #666 2020-03-04 22:24:24 -08:00
56 changed files with 18889 additions and 5521 deletions

View File

@@ -1,11 +1,9 @@
{
"presets": [
[
"env",
"@babel/env",
{
"targets": {
"uglify": true
},
"forceAllTransforms": true,
"modules": false
}
]

View File

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

12
.eslintignore Normal file
View File

@@ -0,0 +1,12 @@
# Ignore artifacts:
dist
lib
npm-debug.log
bower_components
node_modules
yarn-error.log
yarn.lock
src/*.ts
/*.js

24
.eslintrc.json Normal file
View File

@@ -0,0 +1,24 @@
{
"env": {
"browser": true,
"es2021": true,
"mocha": true
},
"extends": ["airbnb-base", "plugin:prettier/recommended"],
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
"prefer-const": "off",
"camelcase": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
/* Remove the necessity to use this on classes */
"class-methods-use-this": "off",
/* Enable variables declarations from shadowing variables declared in the outer scope */
"no-shadow": "off"
}
}

57
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,57 @@
---
name: 🐛 Bug Report
about: Submit a bug report to help us improve
labels: 'bug, needs triage'
---
<!--
! PLEASE HELP US HELP YOU !
Bugs are fixed faster if you include:
- a repro repository to inspect the code
- an url to see the problem live
-->
## 🐛 Bug Report
> Fork this [JSFiddle](https://jsfiddle.net/zenorocha/5kk0eysw/) and reproduce your issue.
(A clear and concise description of what the issue is.)
### Have you read the [Contributing Guidelines on issues](https://github.com/zenorocha/clipboard.js/blob/master/contributing.md)?
(Write your answer here.)
### Expected Behaviour
<!--
How did you expect your project to behave?
Its fine if youre not sure your understanding is correct.
Write down what you thought would happen.
-->
I thought that by going to the page '...' and pressing the button '...' then '...' would happen.
_Tip: Try to use screenshots, gifs, videos, always remember people better understand with a visual way._
### Actual Behaviour
Instead of '...', what I saw was that '...' happened instead.
### To Reproduce
(Write your steps such as:)
1. Step 1...
1. Step 2...
1. Step 3...
### Browsers Affected
I tested on all major browsers and only IE 11 does not work.
### Operational System
(Place here your Operational System.)

13
.github/ISSUE_TEMPLATE/documentation.md vendored Normal file
View File

@@ -0,0 +1,13 @@
---
name: 📚 Documentation
about: Report an issue related to documentation
labels: 'documentation, needs triage'
---
## 📚 Documentation
(A clear and concise description of what the issue is.)
### Have you read the [Contributing Guidelines on issues](https://github.com/zenorocha/clipboard.js/blob/master/contributing.md)?
(Write your answer here.)

26
.github/ISSUE_TEMPLATE/proposal.md vendored Normal file
View File

@@ -0,0 +1,26 @@
---
name: 💥 Proposal
about: Propose a non-trivial change to Clipboard.js
labels: 'proposal, needs triage'
---
## 💥 Proposal
**Is your feature request related to a problem? Please describe**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Are you able to assist to bring the feature to reality?**
no | yes, I can...
**Additional context**
Add any other context or screenshots about the feature request here.
### Have you read the [Contributing Guidelines on issues](https://github.com/zenorocha/clipboard.js/blob/master/contributing.md)?
(Write your answer here.)

35
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,35 @@
<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/zenorocha/clipboard.js/blob/master/contributing.md#proposing-pull-requests
-->
<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->
**What kind of change does this PR introduce?** (check at least one)
- [ ] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:
**Does this PR introduce a breaking change?** (check one)
- [ ] Yes
- [ ] No
If yes, please describe the impact and migration path for existing applications:
**The PR fulfills these requirements:**
- [ ] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [ ] New/updated tests are included
If adding a **new feature**, the PR's description includes:
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)
**Other information:**

View File

@@ -1,15 +0,0 @@
### Minimal example
> Fork this [JSFiddle](https://jsfiddle.net/zenorocha/5kk0eysw/) and reproduce your issue.
### Expected behaviour
I thought that by going to the page '...' and pressing the button '...' then '...' would happen.
### Actual behaviour
Instead of '...', what I saw was that '...' happened instead.
### Browsers affected
I tested on all major browsers and only IE 11 does not work.

47
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
name: publish
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14
- run: npm ci
- run: npm test
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
publish-gpr:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14
registry-url: https://npm.pkg.github.com/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

34
.github/workflows/test.js.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: build
on:
push:
branches: [master]
pull_request:
branches: [master]
env:
FORCE_COLOR: 2
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 15.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
# 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@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

1
.husky/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install lint-staged

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
14

9
.prettierignore Normal file
View File

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

9
.prettierrc.json Normal file
View File

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

View File

@@ -1,4 +0,0 @@
sudo: false
language: node_js
node_js:
- stable

View File

@@ -1,6 +1,6 @@
{
"name": "clipboard",
"version": "2.0.5",
"version": "2.0.11",
"description": "Modern copy to clipboard. No Flash. Just 3kb",
"license": "MIT",
"main": "dist/clipboard.js",
@@ -14,9 +14,5 @@
"/src",
"/lib"
],
"keywords": [
"clipboard",
"copy",
"cut"
]
"keywords": ["clipboard", "copy", "cut"]
}

View File

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

View File

@@ -1,14 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-node</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<div id="btn" data-clipboard-text="1">
<span>Copy</span>
<span>Copy</span>
</div>
<!-- 2. Include library -->
@@ -16,16 +16,20 @@
<!-- 3. Instantiate clipboard by passing a HTML element -->
<script>
var btn = document.getElementById('btn');
var clipboard = new ClipboardJS(btn);
var btn = document.getElementById('btn');
var clipboard = new ClipboardJS(btn);
clipboard.on('success', function(e) {
console.log(e);
});
clipboard.on('success', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function(e) {
console.log(e);
});
clipboard.on('error', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>
</body>
</html>

View File

@@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-nodelist</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button data-clipboard-text="1">Copy</button>
<button data-clipboard-text="2">Copy</button>
@@ -16,16 +16,20 @@
<!-- 3. Instantiate clipboard by passing a list of HTML elements -->
<script>
var btns = document.querySelectorAll('button');
var clipboard = new ClipboardJS(btns);
var btns = document.querySelectorAll('button');
var clipboard = new ClipboardJS(btns);
clipboard.on('success', function(e) {
console.log(e);
});
clipboard.on('success', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function(e) {
console.log(e);
});
clipboard.on('error', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>
</body>
</html>

View File

@@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-selector</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button class="btn" data-clipboard-text="1">Copy</button>
<button class="btn" data-clipboard-text="2">Copy</button>
@@ -16,15 +16,19 @@
<!-- 3. Instantiate clipboard by passing a string selector -->
<script>
var clipboard = new ClipboardJS('.btn');
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
console.log(e);
});
clipboard.on('success', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
clipboard.on('error', function(e) {
console.log(e);
});
clipboard.on('error', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
});
</script>
</body>
</body>
</html>

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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>

1515
dist/clipboard.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

20468
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,10 +1,12 @@
{
"name": "clipboard",
"version": "2.0.5",
"version": "2.0.11",
"description": "Modern copy to clipboard. No Flash. Just 2kb",
"homepage": "https://clipboardjs.com",
"repository": "zenorocha/clipboard.js",
"license": "MIT",
"main": "dist/clipboard.js",
"types": "src/clipboard.d.ts",
"keywords": [
"clipboard",
"copy",
@@ -16,29 +18,46 @@
"tiny-emitter": "^2.0.0"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"babel-preset-env": "^1.7.0",
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"babel-loader": "^8.2.2",
"chai": "^4.2.0",
"cross-env": "^5.2.0",
"karma": "^3.1.1",
"cross-env": "^7.0.3",
"eslint": "^7.20.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^7.2.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.3.1",
"husky": "^5.0.9",
"karma": "^6.0.0",
"karma-chai": "^0.1.0",
"karma-mocha": "^1.2.0",
"karma-chrome-launcher": "^2.2.0",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-sinon": "^1.0.4",
"karma-webpack": "^3.0.5",
"mocha": "^5.2.0",
"sinon": "^7.1.1",
"uglifyjs-webpack-plugin": "^2.0.1",
"webpack": "^4.5.0",
"webpack-cli": "^3.1.2"
"karma-webpack": "^5.0.0-alpha.5",
"lint-staged": "^10.5.3",
"mocha": "^8.2.1",
"prettier": "2.2.1",
"sinon": "^9.2.3",
"tsd": "^0.7.2",
"uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^5.15.0",
"webpack-cli": "^4.4.0"
},
"scripts": {
"test:types": "tsd",
"build": "npm run build-debug && npm run build-min",
"build-debug": "webpack",
"build-min": "cross-env NODE_ENV=production webpack",
"build-watch": "webpack --watch",
"test": "karma start --single-run",
"prepublish": "npm run build"
"prepublish": "npm run build",
"lint": "eslint --ext .js src/"
},
"lint-staged": {
"*.{js,css,md}": [
"prettier --write",
"eslint --fix"
]
}
}

View File

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

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

@@ -0,0 +1,47 @@
import select from 'select';
import command from '../common/command';
import createFakeElement from '../common/create-fake-element';
/**
* Create fake copy action wrapper using a fake element.
* @param {String} target
* @param {Object} options
* @return {String}
*/
const fakeCopyAction = (value, options) => {
const fakeElement = createFakeElement(value);
options.container.appendChild(fakeElement);
const selectedText = select(fakeElement);
command('copy');
fakeElement.remove();
return selectedText;
};
/**
* 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') {
selectedText = fakeCopyAction(target, options);
} else if (
target instanceof HTMLInputElement &&
!['text', 'search', 'url', 'tel', 'password'].includes(target?.type)
) {
// If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange
selectedText = fakeCopyAction(target.value, options);
} 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,204 +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,
* and makes a selection on it.
*/
selectFake() {
const isRTL = document.documentElement.getAttribute('dir') == 'rtl';
this.removeFake();
this.fakeHandlerCallback = () => 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
let yPosition = window.pageYOffset || document.documentElement.scrollTop;
this.fakeElem.style.top = `${yPosition}px`;
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.value = this.text;
this.container.appendChild(this.fakeElem);
this.selectedText = select(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.
*/
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;

91
src/clipboard.d.ts vendored Normal file
View File

@@ -0,0 +1,91 @@
/// <reference lib="dom"/>
type Action = 'cut' | 'copy';
type Response = 'success' | 'error';
type CopyActionOptions = {
container?: Element;
};
/**
* Base class which takes one or more elements, adds event listeners to them,
* and instantiates a new `ClipboardAction` on each click.
*/
declare class ClipboardJS {
constructor(
selector: string | Element | NodeListOf<Element>,
options?: ClipboardJS.Options
);
/**
* Subscribes to events that indicate the result of a copy/cut operation.
* @param type Event type ('success' or 'error').
* @param handler Callback function.
*/
on(type: Response, handler: (e: ClipboardJS.Event) => void): this;
on(type: string, handler: (...args: any[]) => void): this;
/**
* 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;
}
declare namespace ClipboardJS {
interface Options {
/**
* Overwrites default command ('cut' or 'copy').
* @param elem Current element
*/
action?(elem: Element): Action;
/**
* 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,121 +1,8 @@
import ClipboardAction from './clipboard-action';
import Emitter from 'tiny-emitter';
import listen from 'good-listener';
/**
* Base class which takes one or more elements, adds event listeners to them,
* and instantiates a new `ClipboardAction` on each click.
*/
class Clipboard extends Emitter {
/**
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
* @param {Object} options
*/
constructor(trigger, options) {
super();
this.resolveOptions(options);
this.listenClick(trigger);
}
/**
* Defines if attributes would be resolved using internal setter functions
* or custom functions that were passed in the constructor.
* @param {Object} options
*/
resolveOptions(options = {}) {
this.action = (typeof options.action === 'function') ? options.action : this.defaultAction;
this.target = (typeof options.target === 'function') ? options.target : this.defaultTarget;
this.text = (typeof options.text === 'function') ? options.text : this.defaultText;
this.container = (typeof options.container === 'object') ? options.container : document.body;
}
/**
* Adds a click event listener to the passed trigger.
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
*/
listenClick(trigger) {
this.listener = listen(trigger, 'click', (e) => this.onClick(e));
}
/**
* Defines a new `ClipboardAction` on each click event.
* @param {Event} e
*/
onClick(e) {
const trigger = e.delegateTarget || e.currentTarget;
if (this.clipboardAction) {
this.clipboardAction = null;
}
this.clipboardAction = new ClipboardAction({
action : this.action(trigger),
target : this.target(trigger),
text : this.text(trigger),
container : this.container,
trigger : trigger,
emitter : this
});
}
/**
* Default `action` lookup function.
* @param {Element} trigger
*/
defaultAction(trigger) {
return getAttributeValue('action', trigger);
}
/**
* Default `target` lookup function.
* @param {Element} trigger
*/
defaultTarget(trigger) {
const selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
}
}
/**
* Returns the support of the given action, or all actions if no action is
* given.
* @param {String} [action]
*/
static isSupported(action = ['copy', 'cut']) {
const actions = (typeof action === 'string') ? [action] : action;
let support = !document.queryCommandSupported;
actions.forEach((action) => {
support = support && !document.queryCommandSupported(action);
});
return support;
}
/**
* Default `text` lookup function.
* @param {Element} trigger
*/
defaultText(trigger) {
return getAttributeValue('text', trigger);
}
/**
* Destroy lifecycle.
*/
destroy() {
this.listener.destroy();
if (this.clipboardAction) {
this.clipboardAction.destroy();
this.clipboardAction = null;
}
}
}
import ClipboardActionDefault from './actions/default';
import ClipboardActionCut from './actions/cut';
import ClipboardActionCopy from './actions/copy';
/**
* Helper function to retrieve attribute value.
@@ -123,13 +10,156 @@ class Clipboard extends Emitter {
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
const attribute = `data-clipboard-${suffix}`;
const attribute = `data-clipboard-${suffix}`;
if (!element.hasAttribute(attribute)) {
return;
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.
*/
class Clipboard extends Emitter {
/**
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
* @param {Object} options
*/
constructor(trigger, options) {
super();
this.resolveOptions(options);
this.listenClick(trigger);
}
/**
* Defines if attributes would be resolved using internal setter functions
* or custom functions that were passed in the constructor.
* @param {Object} options
*/
resolveOptions(options = {}) {
this.action =
typeof options.action === 'function'
? options.action
: this.defaultAction;
this.target =
typeof options.target === 'function'
? options.target
: this.defaultTarget;
this.text =
typeof options.text === 'function' ? options.text : this.defaultText;
this.container =
typeof options.container === 'object' ? options.container : document.body;
}
/**
* Adds a click event listener to the passed trigger.
* @param {String|HTMLElement|HTMLCollection|NodeList} trigger
*/
listenClick(trigger) {
this.listener = listen(trigger, 'click', (e) => this.onClick(e));
}
/**
* Defines a new `ClipboardAction` on each click event.
* @param {Event} e
*/
onClick(e) {
const trigger = e.delegateTarget || e.currentTarget;
const action = this.action(trigger) || 'copy';
const text = ClipboardActionDefault({
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,
text,
trigger,
clearSelection() {
if (trigger) {
trigger.focus();
}
window.getSelection().removeAllRanges();
},
});
}
/**
* Default `action` lookup function.
* @param {Element} trigger
*/
defaultAction(trigger) {
return getAttributeValue('action', trigger);
}
/**
* Default `target` lookup function.
* @param {Element} trigger
*/
defaultTarget(trigger) {
const selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
}
}
return element.getAttribute(attribute);
/**
* 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.
* @param {String} [action]
*/
static isSupported(action = ['copy', 'cut']) {
const actions = typeof action === 'string' ? [action] : action;
let support = !!document.queryCommandSupported;
actions.forEach((action) => {
support = support && !!document.queryCommandSupported(action);
});
return support;
}
/**
* Default `text` lookup function.
* @param {Element} trigger
*/
defaultText(trigger) {
return getAttributeValue('text', trigger);
}
/**
* Destroy lifecycle.
*/
destroy() {
this.listener.destroy();
}
}
export default Clipboard;

4
src/clipboard.test-d.ts Normal file
View File

@@ -0,0 +1,4 @@
import { expectType } from 'tsd';
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;
}

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

@@ -0,0 +1,69 @@
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);
});
it('should select its value in a input number based on text', () => {
const value = 1;
document.querySelector('#input').setAttribute('type', 'number');
document.querySelector('#input').setAttribute('value', value);
const selectedText = ClipboardActionCopy(
document.querySelector('#input'),
{
container: document.body,
}
);
assert.equal(Number(selectedText), value);
});
});
});

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,243 +0,0 @@
import ClipboardAction from '../src/clipboard-action';
import Emitter from 'tiny-emitter';
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'
});
assert.equal(clip.fakeElem.style.right, '-9999px');
done();
});
});
describe('#set action', () => {
it('should throw an error since "action" is invalid', done => {
try {
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 {
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'
});
assert.equal(clip.selectedText, clip.fakeElem.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,132 +1,192 @@
import Clipboard from '../src/clipboard';
import ClipboardAction from '../src/clipboard-action';
import listen from 'good-listener';
describe('Clipboard', () => {
before(() => {
global.button = document.createElement('button');
global.button.setAttribute('class', 'btn');
global.button.setAttribute('data-clipboard-text', 'foo');
document.body.appendChild(global.button);
global.span = document.createElement('span');
global.span.innerHTML = 'bar';
global.button.appendChild(span);
global.event = {
target: global.button,
currentTarget: global.button,
};
});
after(() => {
document.body.innerHTML = '';
});
describe('#resolveOptions', () => {
before(() => {
global.button = document.createElement('button');
global.button.setAttribute('class', 'btn');
global.button.setAttribute('data-clipboard-text', 'foo');
document.body.appendChild(global.button);
global.span = document.createElement('span');
global.span.innerHTML = 'bar';
global.button.appendChild(span);
global.event = {
target: global.button,
currentTarget: global.button
};
global.fn = () => {};
});
after(() => {
document.body.innerHTML = '';
it('should set action as a function', () => {
let clipboard = new Clipboard('.btn', {
action: global.fn,
});
assert.equal(global.fn, clipboard.action);
});
describe('#resolveOptions', () => {
before(() => {
global.fn = () => {};
});
it('should set target as a function', () => {
let clipboard = new Clipboard('.btn', {
target: global.fn,
});
it('should set action as a function', () => {
let clipboard = new Clipboard('.btn', {
action: global.fn
});
assert.equal(global.fn, clipboard.action);
});
it('should set target as a function', () => {
let clipboard = new Clipboard('.btn', {
target: global.fn
});
assert.equal(global.fn, clipboard.target);
});
it('should set text as a function', () => {
let clipboard = new Clipboard('.btn', {
text: global.fn
});
assert.equal(global.fn, clipboard.text);
});
it('should set container as an object', () => {
let clipboard = new Clipboard('.btn', {
container: document.body
});
assert.equal(document.body, clipboard.container);
});
it('should set container as body by default', () => {
let clipboard = new Clipboard('.btn');
assert.equal(document.body, clipboard.container);
});
assert.equal(global.fn, clipboard.target);
});
describe('#listenClick', () => {
it('should add a click event listener to the passed selector', () => {
let clipboard = new Clipboard('.btn');
assert.isObject(clipboard.listener);
});
it('should set text as a function', () => {
let clipboard = new Clipboard('.btn', {
text: global.fn,
});
assert.equal(global.fn, clipboard.text);
});
describe('#onClick', () => {
it('should create a new instance of ClipboardAction', () => {
let clipboard = new Clipboard('.btn');
it('should set container as an object', () => {
let clipboard = new Clipboard('.btn', {
container: document.body,
});
clipboard.onClick(global.event);
assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
});
it('should use an event\'s currentTarget when not equal to target', () => {
let clipboard = new Clipboard('.btn');
let bubbledEvent = { target: global.span, currentTarget: global.button };
clipboard.onClick(bubbledEvent);
assert.instanceOf(clipboard.clipboardAction, ClipboardAction);
});
it('should throw an exception when target is invalid', done => {
try {
const clipboard = new Clipboard('.btn', {
target() {
return null;
}
});
clipboard.onClick(global.event);
}
catch(e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done();
}
});
assert.equal(document.body, clipboard.container);
});
describe('#static isSupported', () => {
it('should return the support of the given action', () => {
assert.equal(Clipboard.isSupported('copy'), false);
assert.equal(Clipboard.isSupported('cut'), false);
});
it('should set container as body by default', () => {
let clipboard = new Clipboard('.btn');
it('should return the support of the cut and copy actions', () => {
assert.equal(Clipboard.isSupported(), false);
});
assert.equal(document.body, clipboard.container);
});
});
describe('#listenClick', () => {
it('should add a click event listener to the passed selector', () => {
let clipboard = new Clipboard('.btn');
assert.isObject(clipboard.listener);
});
});
describe('#onClick', () => {
it('should init when called', (done) => {
let clipboard = new Clipboard('.btn');
clipboard.on('success', () => {
done();
});
clipboard.onClick(global.event);
});
describe('#destroy', () => {
it('should destroy an existing instance of ClipboardAction', () => {
let clipboard = new Clipboard('.btn');
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.onClick(global.event);
clipboard.destroy();
clipboard.on('success', () => {
done();
});
assert.equal(clipboard.clipboardAction, null);
});
clipboard.onClick(bubbledEvent);
});
it('should throw an exception when target is invalid', (done) => {
try {
const clipboard = new Clipboard('.btn', {
target() {
return null;
},
});
clipboard.onClick(global.event);
} catch (e) {
assert.equal(e.message, 'Invalid "target" value, use a valid Element');
done();
}
});
});
describe('#static isSupported', () => {
it('should return the support of the given action', () => {
assert.equal(Clipboard.isSupported('copy'), true);
assert.equal(Clipboard.isSupported('cut'), true);
});
it('should return the support of the cut and copy actions', () => {
assert.equal(Clipboard.isSupported(), true);
});
});
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 ClipboardActionDefault', () => {
let clipboard = new Clipboard('.btn');
clipboard.onClick(global.event);
clipboard.destroy();
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 clear text selection without moving focus', (done) => {
let clipboard = new Clipboard('.btn');
clipboard.on('success', (e) => {
e.clearSelection();
let selectedElem = document.activeElement;
let selectedText = window.getSelection().toString();
assert.equal(selectedElem, e.trigger);
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

@@ -11,36 +11,36 @@ https://clipboardjs.com/
Licensed MIT © Zeno Rocha`;
module.exports = {
entry: './src/clipboard.js',
mode: 'production',
output: {
filename: production ? 'clipboard.min.js' : 'clipboard.js',
path: path.resolve(__dirname, 'dist'),
library: 'ClipboardJS',
globalObject: 'this',
libraryExport: 'default',
libraryTarget: 'umd'
},
module: {
rules: [
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'}
]
},
optimization: {
minimize: production,
minimizer: [
new UglifyJSPlugin({
parallel: require('os').cpus().length,
uglifyOptions: {
ie8: false,
keep_fnames: false,
output: {
beautify: false,
comments: (node, {value, type}) => type == 'comment2' && value.startsWith('!')
}
}
})
]
},
plugins: [new webpack.BannerPlugin({ banner })]
entry: './src/clipboard.js',
mode: 'production',
target: ['web', 'es5'],
output: {
filename: production ? 'clipboard.min.js' : 'clipboard.js',
path: path.resolve(__dirname, 'dist'),
library: 'ClipboardJS',
globalObject: 'this',
libraryExport: 'default',
libraryTarget: 'umd',
},
module: {
rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }],
},
optimization: {
minimize: production,
minimizer: [
new UglifyJSPlugin({
parallel: require('os').cpus().length,
uglifyOptions: {
ie8: false,
keep_fnames: false,
output: {
beautify: false,
comments: (node, { value, type }) =>
type == 'comment2' && value.startsWith('!'),
},
},
}),
],
},
plugins: [new webpack.BannerPlugin({ banner })],
};