1
0
mirror of https://github.com/zenorocha/clipboard.js.git synced 2023-08-10 21:12:48 +03:00

Compare commits

...

231 Commits

Author SHA1 Message Date
dependabot[bot]
899378dee9
chore(deps): bump nanoid and mocha ()
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-10 15:13:04 +00:00
dependabot[bot]
b1bcc3dd73
chore(deps): bump qs and body-parser ()
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-10 15:08:42 +00:00
dependabot[bot]
5d497db230
chore(deps): bump decode-uri-component from 0.2.0 to 0.2.2 ()
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-10 15:08:29 +00:00
dependabot[bot]
57ccdf4253
chore(deps): bump engine.io and socket.io ()
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Helder Berto <helder.burato@gmail.com>
2022-12-10 15:08:17 +00:00
dependabot[bot]
07ba4a89da
chore(deps): bump loader-utils from 1.4.0 to 1.4.2 ()
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-10 15:07:47 +00:00
dependabot[bot]
f4daa96342
chore(deps): bump socket.io-parser from 4.0.4 to 4.0.5 ()
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-10 15:07:35 +00:00
dependabot[bot]
b31ef09fd0
chore(deps): bump terser from 5.5.1 to 5.14.2 ()
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-29 11:33:13 +01:00
Beto Muniz
2b2f9eef6f 2.0.11 2022-05-04 14:51:40 -03:00
Beto Muniz
21db7250ed
Support more HTML input types. Close ()
* 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 don't blur() the trigger after a clipboard action ()
* 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 ()
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 ()
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 ()
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 () 2022-03-06 13:53:54 +00:00
dependabot[bot]
67067f316f
Bump pathval from 1.1.0 to 1.1.1 ()
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 ()
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 ()
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 () 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 ()
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 ()
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 ()
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 () 2021-06-29 21:03:06 +01:00
dependabot[bot]
641ac851e8
Bump ws from 7.4.2 to 7.4.6 () 2021-06-29 21:02:45 +01:00
Beto Muniz
44df750c9f
Isolate actions strategies in order to code improvement and programmatic usage. ()
* 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 ()
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 ()
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 ()
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 ()
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 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 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 () 2021-03-18 14:41:50 -03:00
Vitor Alencar
7da9deb2cc
Merge pull request 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 2020-03-04 22:24:24 -08:00
Zeno Rocha
289389322e 2.0.5 2020-03-03 18:20:40 -08:00
LeuisKen
894a3bef4a chore: make babel not transform es modules. 2020-03-03 18:09:24 -08:00
LeuisKen
780d390856 chore: fix es6 export issue. 2020-03-03 18:09:24 -08:00
LeuisKen
5de8be447f fix: change export from commonjs to es module for import from src usage. 2020-03-03 18:09:24 -08:00
Zeno Rocha
132fcd16b1
Add Stale Bot 2020-02-29 12:18:50 -08:00
Zeno Rocha
e7f0ff0392 Fix composer deprecated package 2020-02-29 10:03:07 -08:00
domecardillo
393dbe34e0 replaced invalid property url with homepage in composer.json authors array 2020-02-29 09:55:42 -08:00
Zeno Rocha
d3fc3c1e7b Regenerate package-lock and dist files after PhantomJS removal 2020-02-29 09:55:14 -08:00
ossdev
83824fa248 ClipboardJS: Removed PhantomJS Dependency
Added karma-chrome-launcher dependency

Signed-off-by: ossdev <ossdev@puresoftware.com>
2020-02-29 09:53:41 -08:00
ryanmcdonough
ce79f170aa Create LICENSE 2019-12-27 20:56:56 -03:00
Christian Oliff
4c3a086866 HTTPS relevant links 2019-12-27 20:56:29 -03:00
Bjorn Hansen
20f64d82d0 Update broken link to Primer Tooltops doc 2019-04-18 14:23:49 -07:00
Christian Oliff
85981026d1 update URL in banner 2019-03-05 11:03:02 -08:00
Rouven Weßling
6802a86f60 Set babel-preset-env into UglifyJS compatability mode 2018-11-14 20:16:46 -08:00
Rouven Weßling
3522504d34 Remove the unused dev dependency babel-cli 2018-11-14 20:16:46 -08:00
Zeno Rocha
d17eca050e 2.0.4 2018-11-12 20:42:07 -08:00
kovenliao
5381600a26 fix webpack umd build () 2018-11-12 20:35:19 -08:00
Zeno Rocha
5be63e28dd 2.0.3 2018-11-11 18:56:51 -08:00
Rouven Weßling
adc669df06 Always run webpack in production mode. ()
Otherwise horribly inefficent code that evals(!) strings of code is generated.
2018-11-11 18:53:43 -08:00
Rouven Weßling
b57e6d019f Don't claim to support ESM 2018-11-11 18:51:00 -08:00
Rouven Weßling
3d005b547e Maintain banner in minified file 2018-11-11 18:49:23 -08:00
Zeno Rocha
cf9e8fd7ce Updates devDependencies 2018-11-11 09:58:44 -08:00
Zeno Rocha
f1d99de5d3 Removes .npmignore file 2018-11-11 09:48:15 -08:00
Zeno Rocha
fdb66d3f16 2.0.2 2018-11-11 09:14:19 -08:00
Zeno Rocha
e0f82241d0 Removes bundle analyzer 2018-11-11 09:10:37 -08:00
Zeno Rocha
4d4c25c505 Updates Webpack CLI 2018-11-11 08:20:47 -08:00
Samuel Oloruntoba
5ef3f1a817 Migrates to Webpack 4 2018-11-11 08:17:25 -08:00
Zeno Rocha
3382ea3d14 2.0.1 2018-04-29 16:41:13 -07:00
Paweł Lesiecki
0fcf8c9460 Update package.json ()
Correct module path
2018-04-29 16:14:20 -07:00
Zeno Rocha
e1d571b3f3 Updates docs to use ClipboardJS 2.0 2018-03-01 09:07:10 -08:00
Zeno Rocha
43beb07bac Release v2.0.0 2018-02-28 21:56:32 -08:00
Zeno Rocha
9086f3ed64 Updates demos to use new constructor 2018-02-28 21:46:48 -08:00
Zeno Rocha
750cf124d7 Changes global variable from "Clipboard" to "ClipboardJS"
This was introduced in order to prevent a name conflict with the new `window.Clipboard` native function

For more info check:
* https://w3c.github.io/clipboard-apis/
* ac319a7ed3
2018-02-28 21:45:59 -08:00
Zeno Rocha
d25dcac817 Migrates banner from Browserify to Webpack 2018-02-28 20:32:25 -08:00
Fernando Herrero
f7e2f58c96 Create composer.json for instalation with composer ()
* Create composer.json for instalation with composer

The installation is much simpler with Composer and https://github.com/RobLoach/component-installer

After merged you can test it:
```
composer config repositories.zenorocha-clipboardjs vcs "https://github.com/zenorocha/clipboard.js/"
composer require zenorocha-clipboardjs:dev-master
```
Before merged you can test using my git fork.

Composer creates a "components" folder that contains the "clipboardjs" whith all files spec in "files" of composer.json.
You can then register the "package" at https://packagist.org In this way the installation would be simpler:
`composer require zenorocha-clipboardjs:dev-master`

* Remove icon
2018-02-28 17:05:21 -08:00
Guillaume Vincent
2d5b2df811 Migrate from Browserify to Webpack ()
* Migrate from Browserify to Webpack

close https://github.com/zenorocha/clipboard.js/issues/371

 Author:    Guillaume Vincent <guillaume@oslab.fr>

* remove browserify and associated modules and update tests
2018-02-28 17:03:39 -08:00
Lucas Bento
0c3bce265f Fix GitHub Primer tooltips link () 2017-10-16 07:25:57 -07:00
Zeno Rocha
3c0dfe5a38 Adds docs on tooltips 2017-06-30 16:30:30 -03:00
Nathan
f960b0d20a use only ID name for DOM () 2017-06-30 15:52:07 -03:00
Zeno Rocha
b6e6b80ab0 Release v1.7.1 2017-05-29 16:57:52 -07:00
Patrick H. Lauke
a55c9ac513 Move focus to trigger instead of simply blur()ing ()
blur() results in a loss/reset of keyboard focus, meaning keyboard users usually get thrown right back to the start of the page. Instead, this moves focus back to the trigger (which had the focus when the trigger was activated).
As with the proposed fix in https://github.com/zenorocha/clipboard.js/pull/418 this obviously results in the focus styles (like the default outline, unless suppressed) being applied to the trigger (see the related PR for rationale and future fix using `:focus-ring`)
2017-05-29 16:54:55 -07:00
Zeno Rocha
39e622456c Release v1.7.0 2017-05-29 14:43:41 -07:00
Zeno Rocha
f8c322f163 Reviews text from 2017-05-29 14:20:36 -07:00
Peder Johnsen
f42b57067d Container option ()
* Allow container option to fix bugs related to bootstrap modals etc.

* Updated readme to reflect addition of container option

* Name link

* Removed test log

* Remove unwanted whitespace

* Refactored description
2017-05-29 14:17:26 -07:00
Zeno Rocha
4065080a17 Removes bower notes from docs 2017-05-22 13:31:36 -07:00
Gabriel Kalani
5ab50475e0 ES6 refactor ()
* little fix

* little fix /2

* test/clipboard.js refactored

* emitter: emitter --> emitter

* Examples in ES6

* es6

* back to original code

* script > npm test

* script > npm test not necessary

* updating modules

* removing export default
2017-05-02 21:34:18 -07:00
Zeno Rocha
e1394b3b8c Adds bonus section 2017-05-01 09:17:35 -07:00
Zeno Rocha
f59d4e6b4d Release v1.6.1 2017-02-28 09:58:40 -08:00
Zeno Rocha
9ddff7e591 Prevents Node env from throwing an error 2017-02-28 09:57:36 -08:00
Zeno Rocha
a00f1fe327 Release v1.6.0 2017-02-08 00:02:14 -08:00
Zeno Rocha
38ae5b34f3 Adds documentation about 2017-02-07 23:59:27 -08:00
Zeno Rocha
41b7234d50 Prevents keyboard from opening on iOS when using "data-clipboard-target" 2017-02-07 23:41:46 -08:00
Zeno Rocha
3696739e5e Prevents scroll jump on iOS when using "data-clipboard-text"
Thanks to @geraldarthur at 
2017-02-07 23:37:43 -08:00
Itai Steinherz
63d1b0f014 Add isSupported method 2017-02-07 23:36:29 -08:00
Zeno Rocha
402c9ee17b Release v1.5.16 2016-12-12 11:54:35 -02:00
Zeno Rocha
0538f6e212 Ensures that event delegation works with multiple documents (a page with iframes) 2016-12-12 11:53:41 -02:00
Zeno Rocha
8ad16a2c6c
Release v1.5.15 2016-10-22 19:44:43 -07:00
Zeno Rocha
ce0829054b
Updates dev dependencies 2016-10-22 19:37:37 -07:00
Zeno Rocha
223d30c110
Removes babel loose mode 2016-10-22 19:37:18 -07:00
Zeno Rocha
b1a68df6e9
Release v1.5.14 2016-10-22 19:36:49 -07:00
Zeno Rocha
0149e1de5e
Updates good-listener package which removes two dependencies 2016-10-22 19:10:44 -07:00
Zeno Rocha
26a9e9d56c
Release v1.5.13 2016-10-16 22:44:03 -07:00
Zeno Rocha
fce625f151
Fixes 2016-10-16 18:53:34 -07:00
Zeno Rocha
f7040bae8a
Adds meta viewport for all demos 2016-10-16 17:16:02 -07:00
Zeno Rocha
9f9d03c927
Adds Edge version 2016-09-26 13:57:28 -05:00
Cătălin Mariș
e18c26ae07 Add Edge to the Browser Support section ()
Ref: https://github.com/zenorocha/clipboard.js/pull/267#issuecomment-249620994
2016-09-26 13:48:16 -05:00
Zeno Rocha
70cfabec69 Update readme.md 2016-09-21 13:21:25 -07:00
Zeno Rocha
f700a1b12e
Moves URLs to HTTPS 2016-09-21 11:42:37 -07:00
Zeno Rocha
9e3d662c4e
Adjusts text based on 2016-09-21 11:04:07 -07:00
JY Kim
76b907949c issue solution ()
* add selectedText

* better code

* remove dist folder

* Revert "remove dist folder"

This reverts commit 50e726c7a790a1c4a76469e15b56baccc44a4599.

* orogin dist source
2016-09-08 08:57:41 -07:00
Zeno Rocha
60b6887100
Updates site preview 2016-06-17 14:49:39 -07:00
Zeno Rocha
eb7418b51b
Release v1.5.12 2016-06-09 07:57:20 -07:00
Alexander
869c4e3219 Fix bug that unable to remove fake event listener ()
* Fix bug that unable to remove fake event listener

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

11
.babelrc.json Normal file

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

@ -1,6 +0,0 @@
/*!
* clipboard.js v<%= pkg.version %>
* https://zenorocha.github.io/clipboard.js
*
* Licensed MIT © Zeno Rocha
*/

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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:**

21
.github/stale.yml vendored Normal file

@ -0,0 +1,21 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

47
.github/workflows/publish.yml vendored Normal 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

@ -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

3
.gitignore vendored

@ -2,3 +2,6 @@ lib
npm-debug.log
bower_components
node_modules
yarn-error.log
yarn.lock
.DS_Store

1
.husky/.gitignore vendored Normal file

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file

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

@ -1,7 +0,0 @@
/.*/
/demo/
/test/
/.*
/bower.json
/karma.conf.js
/src

1
.nvmrc Normal file

@ -0,0 +1 @@
14

9
.prettierignore Normal 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

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

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

21
LICENSE Normal file

@ -0,0 +1,21 @@
MIT License
Copyright (c) Zeno Rocha
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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

25
composer.json Normal file

@ -0,0 +1,25 @@
{
"name": "zenorocha/clipboardjs",
"description": "Modern copy to clipboard. No Flash. Just 3kb gzipped https://clipboardjs.com",
"type": "component",
"homepage": "https://clipboardjs.com/",
"authors": [
{
"name": "Zeno Rocha",
"homepage": "http://zenorocha.com/"
}
],
"require": {
"oomphinc/composer-installers-extender": "*"
},
"extra": {
"component": {
"scripts": [
"dist/clipboard.js"
],
"files": [
"dist/clipboard.min.js"
]
}
}
}

@ -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

@ -1,28 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-node</title>
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button id="btn" data-clipboard-text="1">Copy</button>
<div id="btn" data-clipboard-text="1">
<span>Copy</span>
</div>
<!-- 2. Include library -->
<script src="../dist/clipboard.min.js"></script>
<!-- 3. Instantiate clipboard by passing a HTML element -->
<script>
var btn = document.getElementById('btn');
var clipboard = new Clipboard(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>

@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-nodelist</title>
</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>
@ -15,16 +16,20 @@
<!-- 3. Instantiate clipboard by passing a list of HTML elements -->
<script>
var btns = document.querySelectorAll('button');
var clipboard = new Clipboard(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>

@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>constructor-selector</title>
</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>
@ -15,15 +16,19 @@
<!-- 3. Instantiate clipboard by passing a string selector -->
<script>
var clipboard = new Clipboard('.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>

@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>function-target</title>
</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>
@ -14,19 +15,23 @@
<!-- 3. Instantiate clipboard -->
<script>
var clipboard = new Clipboard('.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>

@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>function-text</title>
</head>
<body>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<!-- 1. Define some markup -->
<button class="btn">Copy</button>
@ -13,19 +14,23 @@
<!-- 3. Instantiate clipboard -->
<script>
var clipboard = new Clipboard('.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>

@ -1,28 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>target-div</title>
</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 Clipboard('.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>

@ -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>

@ -1,28 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>target-input</title>
</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 Clipboard('.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>

@ -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>

@ -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>

@ -1,28 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title>target-textarea</title>
</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 Clipboard('.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>

@ -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>

1084
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

@ -1,27 +1,36 @@
module.exports = function(karma) {
karma.set({
plugins: ['karma-browserify', 'karma-chai', 'karma-sinon', 'karma-mocha', 'karma-phantomjs-launcher'],
var webpackConfig = require('./webpack.config.js');
frameworks: ['browserify', 'chai', 'sinon', 'mocha'],
module.exports = function (karma) {
karma.set({
plugins: [
'karma-webpack',
'karma-chai',
'karma-sinon',
'karma-mocha',
'karma-chrome-launcher',
],
files: [
'src/**/*.js',
'test/**/*.js',
'./node_modules/phantomjs-polyfill/bind-polyfill.js'
],
frameworks: ['chai', 'sinon', 'mocha', 'webpack'],
exclude: ['test/module-systems.js'],
files: [
{ pattern: 'src/**/*.js', watched: false },
{ pattern: 'test/**/*.js', watched: false },
],
preprocessors: {
'src/**/*.js' : ['browserify'],
'test/**/*.js': ['browserify']
},
preprocessors: {
'src/**/*.js': ['webpack'],
'test/**/*.js': ['webpack'],
},
browserify: {
debug: true,
transform: ['babelify']
},
webpack: {
module: webpackConfig.module,
plugins: webpackConfig.plugins,
},
browsers: ['PhantomJS']
});
}
webpackMiddleware: {
stats: 'errors-only',
},
browsers: ['ChromeHeadless'],
});
};

19521
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

@ -1,12 +1,12 @@
// Package metadata for Meteor.js.
Package.describe({
name: "zenorocha:clipboard",
summary: "Modern copy to clipboard. No Flash. Just 2kb.",
version: "1.5.1",
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.min.js", "client");
Package.onUse(function (api) {
api.addFiles('dist/clipboard.js', 'client');
});

@ -1,52 +1,63 @@
{
"name": "clipboard",
"version": "1.5.2",
"version": "2.0.11",
"description": "Modern copy to clipboard. No Flash. Just 2kb",
"homepage": "https://clipboardjs.com",
"repository": "zenorocha/clipboard.js",
"license": "MIT",
"main": "lib/clipboard.js",
"browserify": {
"transform": [
[
"babelify",
{
"loose": "all"
}
]
]
},
"main": "dist/clipboard.js",
"types": "src/clipboard.d.ts",
"keywords": [
"clipboard",
"copy",
"cut"
],
"dependencies": {
"good-listener": "^1.1.2",
"select": "^1.0.4",
"tiny-emitter": "^1.0.0"
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
},
"devDependencies": {
"babel": "^5.8.29",
"babelify": "^6.3.0",
"browserify": "^11.2.0",
"karma": "^0.13.10",
"karma-browserify": "^4.4.0",
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"babel-loader": "^8.2.2",
"chai": "^4.2.0",
"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": "^0.2.0",
"karma-phantomjs-launcher": "^0.2.1",
"karma-chrome-launcher": "^3.1.0",
"karma-mocha": "^2.0.1",
"karma-sinon": "^1.0.4",
"mocha": "^2.3.3",
"phantomjs-polyfill": "0.0.1",
"uglify-js": "^2.4.24",
"watchify": "^3.4.0",
"bannerify": "Vekat/bannerify#feature-option"
"karma-webpack": "^5.0.0-alpha.5",
"lint-staged": "^10.5.3",
"mocha": "^10.1.0",
"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": "browserify src/clipboard.js -s Clipboard -p [bannerify --file .banner ] -o dist/clipboard.js",
"build-min": "uglifyjs dist/clipboard.js --comments '/!/' -m screw_ie8=true -c screw_ie8=true,unused=false -o dist/clipboard.min.js",
"build-watch": "watchify src/clipboard.js -s Clipboard -o dist/clipboard.js -v",
"build-debug": "webpack",
"build-min": "cross-env NODE_ENV=production webpack",
"build-watch": "webpack --watch",
"test": "karma start --single-run",
"prepublish": "babel src --out-dir lib --loose all"
"prepublish": "npm run build",
"lint": "eslint --ext .js src/"
},
"lint-staged": {
"*.{js,css,md}": [
"prettier --write",
"eslint --fix"
]
}
}

111
readme.md

@ -1,11 +1,11 @@
# 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 2kb
> Modern copy to clipboard. No Flash. Just 3kb gzipped.
<a href="http://clipboardjs.com/"><img width="728" src="https://cloud.githubusercontent.com/assets/398893/9983535/5ab0a950-5fb4-11e5-9602-e73c0b661883.jpg" alt="Demo"></a>
<a href="https://clipboardjs.com/"><img width="728" src="https://cloud.githubusercontent.com/assets/398893/16165747/a0f6fc46-349a-11e6-8c9b-c5fd58d9099c.png" alt="Demo"></a>
## Why
@ -21,13 +21,7 @@ You can get it on npm.
npm install clipboard --save
```
Or bower, too.
```
bower install clipboard --save
```
If you're not into package management, just [download a ZIP](https://github.com/zenorocha/clipboard.js/archive/master.zip) file.
Or if you're not into package management, just [download a ZIP](https://github.com/zenorocha/clipboard.js/archive/master.zip) file.
## Setup
@ -40,12 +34,12 @@ First, include the script located on the `dist` folder or load it from [a third-
Now, you need to instantiate it by [passing a DOM selector](https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-selector.html#L18), [HTML element](https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-node.html#L16-L17), or [list of HTML elements](https://github.com/zenorocha/clipboard.js/blob/master/demo/constructor-nodelist.html#L18-L19).
```js
new Clipboard('.btn');
new ClipboardJS('.btn');
```
Internally, we need to fetch all elements that matches with your selector and attach event listeners for each one. But guess what? If you have hundreds of matches, this operation can consume a lot of memory.
For this reason we use [event delegation](http://stackoverflow.com/questions/1687296/what-is-dom-event-delegation) which replaces multiple event listeners with just a single listener. After all, [#perfmatters](https://twitter.com/hashtag/perfmatters).
For this reason we use [event delegation](https://stackoverflow.com/questions/1687296/what-is-dom-event-delegation) which replaces multiple event listeners with just a single listener. After all, [#perfmatters](https://twitter.com/hashtag/perfmatters).
# Usage
@ -57,15 +51,15 @@ A pretty common use case is to copy content from another element. You can do tha
The value you include on this attribute needs to match another's element selector.
<a href="http://clipboardjs.com/#example-target"><img width="473" alt="example-2" src="https://cloud.githubusercontent.com/assets/398893/9983467/a4946aaa-5fb1-11e5-9780-f09fcd7ca6c8.png"></a>
<a href="https://clipboardjs.com/#example-target"><img width="473" alt="example-2" src="https://cloud.githubusercontent.com/assets/398893/9983467/a4946aaa-5fb1-11e5-9780-f09fcd7ca6c8.png"></a>
```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>
```
@ -75,7 +69,7 @@ Additionally, you can define a `data-clipboard-action` attribute to specify if y
If you omit this attribute, `copy` will be used by default.
<a href="http://clipboardjs.com/#example-action"><img width="473" alt="example-3" src="https://cloud.githubusercontent.com/assets/398893/10000358/7df57b9c-6050-11e5-9cd1-fbc51d2fd0a7.png"></a>
<a href="https://clipboardjs.com/#example-action"><img width="473" alt="example-3" src="https://cloud.githubusercontent.com/assets/398893/10000358/7df57b9c-6050-11e5-9cd1-fbc51d2fd0a7.png"></a>
```html
<!-- Target -->
@ -83,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>
```
@ -93,12 +87,15 @@ As you may expect, the `cut` action only works on `<input>` or `<textarea>` elem
Truth is, you don't even need another element to copy its content from. You can just include a `data-clipboard-text` attribute in your trigger element.
<a href="http://clipboardjs.com/#example-text"><img width="147" alt="example-1" src="https://cloud.githubusercontent.com/assets/398893/10000347/6e16cf8c-6050-11e5-9883-1c5681f9ec45.png"></a>
<a href="https://clipboardjs.com/#example-text"><img width="147" alt="example-1" src="https://cloud.githubusercontent.com/assets/398893/10000347/6e16cf8c-6050-11e5-9883-1c5681f9ec45.png"></a>
```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>
```
@ -109,23 +106,29 @@ There are cases where you'd like to show some user feedback or capture what has
That's why we fire custom events such as `success` and `error` for you to listen and implement your custom logic.
```js
var clipboard = new Clipboard('.btn');
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);
});
```
For a live demonstration, open this [site](http://clipboardjs.com/) and just your console :)
For a live demonstration, go to this [site](https://clipboardjs.com/) and open your console.
## Tooltips
Each application has different design needs, that's why clipboard.js does not include any CSS or built-in tooltip solution.
The tooltips you see on the [demo site](https://clipboardjs.com/) were built using [GitHub's Primer](https://primer.style/css/components/tooltips). You may want to check that out if you're looking for a similar look and feel.
## Advanced Options
@ -134,44 +137,56 @@ If you don't want to modify your HTML, there's a pretty handy imperative API for
For instance, if you want to dynamically set a `target`, you'll need to return a Node.
```js
new Clipboard('.btn', {
target: function(trigger) {
return trigger.nextElementSibling;
}
new ClipboardJS('.btn', {
target: function (trigger) {
return trigger.nextElementSibling;
},
});
```
If you want to dynamically set a `text`, you'll return a String.
```js
new Clipboard('.btn', {
text: function(trigger) {
return trigger.getAttribute('aria-label');
}
new ClipboardJS('.btn', {
text: function (trigger) {
return trigger.getAttribute('aria-label');
},
});
```
Also, with are working with single page apps, you may want to manage the lifecycle of the DOM more precisely. Here's how you clean up the events and objects that we create.
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.
```js
var clipboard = new Clipboard('.btn');
new ClipboardJS('.btn', {
container: document.getElementById('modal'),
});
```
Also, if you are working with single page apps, you may want to manage the lifecycle of the DOM more precisely. Here's how you clean up the events and objects that we create.
```js
var clipboard = new ClipboardJS('.btn');
clipboard.destroy();
```
## Browser Support
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 second one is supported in the following browsers.
This library relies on both [Selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection) and [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) APIs. The first one is [supported by all browsers](https://caniuse.com/#search=selection) while the second one is supported in the following browsers.
| <img src="http://clipboardjs.com/assets/images/chrome.png" width="48px" height="48px" alt="Chrome logo"> | <img src="http://clipboardjs.com/assets/images/firefox.png" width="48px" height="48px" alt="Firefox logo"> | <img src="http://clipboardjs.com/assets/images/ie.png" width="48px" height="48px" alt="Internet Explorer logo"> | <img src="http://clipboardjs.com/assets/images/opera.png" width="48px" height="48px" alt="Opera logo"> | <img src="http://clipboardjs.com/assets/images/safari.png" width="48px" height="48px" alt="Safari logo"> |
|:---:|:---:|:---:|:---:|:---:|
| 42+ ✔ | 41+ ✔ | 9+ ✔ | 29+ ✔ | Nope ✘ |
| <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+ ✔ |
Although copy/cut operations with [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand) aren't supported on Safari yet (including mobile), it gracefully degrades because [Selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection) is supported.
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.
That means you can show a tooltip saying `Copied!` when `success` event is called and `Press Ctrl+C to copy` when `error` event is called because the text is already selected.
You can also check if clipboard.js is supported or not by running `ClipboardJS.isSupported()`, that way you can hide copy/cut buttons from the UI.
For a live demonstration, open this [site](http://clipboardjs.com) on Safari.
## 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._
Install for [Chrome](https://chrome.google.com/webstore/detail/codecopy/fkbfebkcoelajmhanocgppanfoojcdmg) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/codecopy/).
## License
[MIT License](http://zenorocha.mit-license.org/) © Zeno Rocha
[MIT License](https://zenorocha.mit-license.org/) © Zeno Rocha

47
src/actions/copy.js Normal 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

@ -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

@ -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;

@ -1,196 +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.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.target) {
throw new Error('Multiple attributes declared, use either "target" or "text"');
}
else if (this.text) {
this.selectFake();
}
else if (this.target) {
this.selectTarget();
}
else {
throw new Error('Missing required attributes, use either "target" or "text"');
}
}
/**
* Creates a fake textarea element, sets its value from `text` property,
* and makes a selection on it.
*/
selectFake() {
this.removeFake();
this.fakeHandler = document.body.addEventListener('click', () => this.removeFake());
this.fakeElem = document.createElement('textarea');
this.fakeElem.style.position = 'absolute';
this.fakeElem.style.left = '-9999px';
this.fakeElem.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
this.fakeElem.setAttribute('readonly', '');
this.fakeElem.value = this.text;
document.body.appendChild(this.fakeElem);
this.selectedText = 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) {
document.body.removeEventListener('click');
this.fakeHandler = null;
}
if (this.fakeElem) {
document.body.removeChild(this.fakeElem);
this.fakeElem = null;
}
}
/**
* Selects the content from element passed on `target` property.
*/
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) {
if (succeeded) {
this.emitter.emit('success', {
action: this.action,
text: this.selectedText,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
}
else {
this.emitter.emit('error', {
action: this.action,
trigger: this.trigger,
clearSelection: this.clearSelection.bind(this)
});
}
}
/**
* Removes current selection and focus from `target` element.
*/
clearSelection() {
if (this.target) {
this.target.blur();
}
window.getSelection().removeAllRanges();
}
/**
* Sets the `action` to be performed which can be either 'copy' or 'cut'.
* @param {String} action
*/
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) {
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

@ -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;

@ -1,101 +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;
}
/**
* 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) {
if (this.clipboardAction) {
this.clipboardAction = null;
}
this.clipboardAction = new ClipboardAction({
action : this.action(e.target),
target : this.target(e.target),
text : this.text(e.target),
trigger : e.target,
emitter : this
});
}
/**
* Default `action` lookup function.
* @param {Element} trigger
*/
defaultAction(trigger) {
return getAttributeValue('action', trigger);
}
/**
* Default `target` lookup function.
* @param {Element} trigger
*/
defaultTarget(trigger) {
let selector = getAttributeValue('target', trigger);
if (selector) {
return document.querySelector(selector);
}
}
/**
* 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.
@ -103,13 +10,156 @@ class Clipboard extends Emitter {
* @param {Element} element
*/
function getAttributeValue(suffix, element) {
let attribute = `data-clipboard-${suffix}`;
const attribute = `data-clipboard-${suffix}`;
if (!element.hasAttribute(attribute)) {
return;
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

@ -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

@ -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;
}
}

@ -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

@ -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

@ -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

@ -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');
});
});
});

@ -1,242 +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(),
text: 'foo'
});
assert.property(clip, 'action');
assert.property(clip, 'emitter');
assert.property(clip, 'target');
assert.property(clip, 'text');
assert.property(clip, 'trigger');
assert.property(clip, 'selectedText');
});
});
describe('#initSelection', () => {
it('should throw an error since both "text" and "target" were passed', done => {
try {
new ClipboardAction({
text: 'foo',
target: document.querySelector('#input')
});
}
catch(e) {
assert.equal(e.message, 'Multiple attributes declared, use either "target" or "text"');
done();
}
});
it('should throw an error since neither "text" nor "target" were passed', done => {
try {
new ClipboardAction();
}
catch(e) {
assert.equal(e.message, 'Missing required attributes, use either "target" or "text"');
done();
}
});
});
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(),
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(),
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(),
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(),
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: 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: emitter,
target: document.querySelector('#input')
});
});
});
describe('#handleResult', () => {
it('should fire a success event with certain properties', done => {
let clip = new ClipboardAction({
emitter: new Emitter(),
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(),
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(),
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(),
text: 'blah'
});
clip.selectFake();
clip.destroy();
assert.equal(clip.fakeElem, null);
});
});
});

@ -1,93 +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.event = {
target: 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 = function() {};
});
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);
});
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 throws exception target', done => {
try {
var clipboard = new Clipboard('.btn', {
target: function() {
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('#destroy', () => {
it('should destroy an existing instance of ClipboardAction', () => {
let clipboard = new Clipboard('.btn');
it('should set container as body by default', () => {
let clipboard = new Clipboard('.btn');
clipboard.onClick(global.event);
clipboard.destroy();
assert.equal(clipboard.clipboardAction, null);
});
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);
});
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);
});
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

@ -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();
});
});

@ -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();
});
});

46
webpack.config.js Normal file

@ -0,0 +1,46 @@
const pkg = require('./package.json');
const path = require('path');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const production = process.env.NODE_ENV === 'production' || false;
const banner = `clipboard.js v${pkg.version}
https://clipboardjs.com/
Licensed MIT © Zeno Rocha`;
module.exports = {
entry: './src/clipboard.js',
mode: 'production',
target: ['web', 'es5'],
output: {
filename: production ? 'clipboard.min.js' : 'clipboard.js',
path: path.resolve(__dirname, 'dist'),
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 })],
};