├── .editorconfig
├── .eslintrc.json
├── .github
├── FUNDING.yml
└── workflows
│ └── ngx-clipboard.yml
├── .gitignore
├── .husky
└── pre-commit
├── .prettierignore
├── .prettierrc
├── .vscode
├── cSpell.json
├── spell.json
└── tasks.json
├── LICENSE
├── README.md
├── angular.json
├── e2e
├── protractor.conf.js
├── src
│ ├── app.e2e-spec.ts
│ └── app.po.ts
└── tsconfig.e2e.json
├── package.json
├── projects
└── ngx-clipboard
│ ├── .eslintrc.json
│ ├── karma.conf.js
│ ├── ng-package.json
│ ├── ng-package.prod.json
│ ├── package.json
│ ├── src
│ ├── lib
│ │ ├── interface.ts
│ │ ├── ngx-clipboard-if-supported.directive.spec.ts
│ │ ├── ngx-clipboard-if-supported.directive.ts
│ │ ├── ngx-clipboard.directive.spec.ts
│ │ ├── ngx-clipboard.directive.ts
│ │ ├── ngx-clipboard.module.ts
│ │ ├── ngx-clipboard.service.spec.ts
│ │ └── ngx-clipboard.service.ts
│ ├── public_api.ts
│ └── test.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ ├── tsconfig.spec.json
│ └── yarn.lock
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ └── app.module.ts
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── karma.conf.js
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
└── tslint.json
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["projects/**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["tsconfig.json", "e2e/tsconfig.json"],
9 | "createDefaultProgram": true
10 | },
11 | "extends": [
12 | "plugin:@angular-eslint/recommended",
13 | "plugin:@angular-eslint/template/process-inline-templates",
14 | "plugin:prettier/recommended",
15 | "prettier"
16 | ],
17 | "rules": {
18 | "@angular-eslint/component-selector": [
19 | "error",
20 | {
21 | "prefix": "ngx",
22 | "style": "kebab-case",
23 | "type": "element"
24 | }
25 | ],
26 | "@angular-eslint/directive-selector": [
27 | "error",
28 | {
29 | "prefix": "ngx",
30 | "style": "camelCase",
31 | "type": "attribute"
32 | }
33 | ]
34 | }
35 | },
36 | {
37 | "files": ["*.html"],
38 | "extends": ["plugin:@angular-eslint/template/recommended"],
39 | "rules": {}
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: ngx-clipboard
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/workflows/ngx-clipboard.yml:
--------------------------------------------------------------------------------
1 | name: ngx-clipboard
2 |
3 | on:
4 | pull_request:
5 | workflow_dispatch:
6 | inputs:
7 | isBeta:
8 | description: 'Is this a beta release?'
9 | required: false
10 | default: 'false'
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 |
19 | - uses: actions/setup-node@v4
20 | with:
21 | node-version: '20.x'
22 | cache: 'yarn'
23 |
24 | - uses: actions/cache@v4
25 | id: angular-cache
26 | with:
27 | path: |
28 | .angular/cache
29 | key: ${{ runner.os }}-ng-${{ hashFiles('**/yarn.lock') }}
30 | restore-keys: ${{ runner.os }}-ng
31 |
32 | - name: Install dependencies
33 | run: yarn --pure-lockfile --non-interactive --no-progress
34 |
35 | # Fix linting errors first
36 | # - name: Run TSLint
37 | # run: yarn lint
38 |
39 | - name: Run tests
40 | run: yarn test --configuration=ci
41 |
42 | - name: Build host app
43 | run: yarn ng build library-host --configuration=production
44 |
45 | - name: Build library
46 | run: yarn build
47 |
48 | - name: Publish library
49 | if: github.event_name == 'workflow_dispatch'
50 | run: |
51 | echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc && npm publish ./dist/lib --access public --tag ${{ github.event.inputs.isBeta == 'true' && 'next' || 'latest' }}
52 | env:
53 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /old
8 | # dependencies
9 | node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 | # IDE - VSCode
20 | .vscode/*
21 | !.vscode/tasks.json
22 | !.vscode/launch.json
23 | !.vscode/extensions.json
24 |
25 |
26 | # misc
27 | /.angular/cache
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | yarn-error.log
34 | testem.log
35 | /typings
36 |
37 | # System Files
38 | .DS_Store
39 | Thumbs.db
40 | package-lock.json
41 | .angular/cache
42 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .angular
2 | dist
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "bracketSpacing": true,
4 | "insertPragma": false,
5 | "jsxBracketSameLine": false,
6 | "printWidth": 120,
7 | "rangeStart": 0,
8 | "requirePragma": false,
9 | "semi": true,
10 | "singleQuote": true,
11 | "tabWidth": 4,
12 | "trailingComma": "none",
13 | "useTabs": false
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/cSpell.json:
--------------------------------------------------------------------------------
1 | // cSpell Settings
2 | {
3 | // Version of the setting file. Always 0.1
4 | "version": "0.1",
5 |
6 | // language - current active spelling language
7 | "language": "en",
8 |
9 | // words - list of words to be always considered correct
10 | "words": ["webpack"],
11 |
12 | // flagWords - list of words to be always considered incorrect
13 | // This is useful for offensive words and common spelling errors.
14 | // For example "hte" should be "the"
15 | "flagWords": ["hte"]
16 | }
17 |
--------------------------------------------------------------------------------
/.vscode/spell.json:
--------------------------------------------------------------------------------
1 | {
2 | "language": "en",
3 | "ignoreWordsList": ["npm", "Angular2", "plunker"],
4 | "mistakeTypeToStatus": {
5 | "Passive voice": "Hint",
6 | "Spelling": "Error",
7 | "Complex Expression": "Disable",
8 | "Hidden Verbs": "Information",
9 | "Hyphen Required": "Disable",
10 | "Redundant Expression": "Disable",
11 | "Did you mean...": "Disable",
12 | "Repeated Word": "Warning",
13 | "Missing apostrophe": "Warning",
14 | "Cliches": "Disable",
15 | "Missing Word": "Disable",
16 | "Make I uppercase": "Warning"
17 | },
18 | "languageIDs": ["markdown", "plaintext"],
19 | "ignoreRegExp": ["/\\(.*\\.(jpg|jpeg|png|md|gif|JPG|JPEG|PNG|MD|GIF)\\)/g", "/((http|https|ftp|git)\\S*)/g"]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "command": "tsc",
6 | "args": ["-p", "."],
7 | "problemMatcher": "$tsc",
8 | "tasks": [
9 | {
10 | "label": "tsc",
11 | "type": "shell",
12 | "command": "tsc",
13 | "args": ["-p", "."],
14 | "problemMatcher": "$tsc",
15 | "group": {
16 | "_id": "build",
17 | "isDefault": false
18 | }
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Sam Lin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://opencollective.com/ngx-clipboard)
2 | [](https://github.com/maxisam/ngx-clipboard/actions/workflows/ngx-clipboard.yml)
3 | [](https://www.npmjs.com/package/ngx-clipboard)
4 | [](https://github.com/maxisam/ngx-clipboard/releases)
5 | []()
6 |
7 | # ngx-clipboard , F.K.A [angular2-clipboard](https://www.npmjs.com/package/angular2-clipboard)
8 |
9 | From 6.0.0, there is no other JS dependency anymore. Just Angular.
10 |
11 | It works with angular version 2.0.0 and up
12 |
13 | To make more sense with the future versioning scheme of Angular, the directive selector is now rename to **ngxClipboard**
14 |
15 | ## Dependencies
16 |
17 | - If you need to use it on 2.x, please use version 7.x.x.
18 | - If you need to use it on 4.x, please use version 8.x.x.
19 | - If you need to use it on 5.x, please use version 10.x.x.
20 | - If you need to use it on 8.x, please use version 12.x.x.
21 | - If you need to use it on 9.x, please use version 13.x.x.
22 | - If you need to use it on 10.x - 12.x, please use version 14.0.2.
23 | - If you need to use it on 13.x, please use version 15.x.x. (Also thanks https://github.com/arturovt for updating & tuning)
24 |
25 | The code are pretty much the same, in v8.0.0 it uses InjectionToken which requires angular4 and above.
26 |
27 | ## Install
28 |
29 | You can get it on npm.
30 |
31 | ```bat
32 | npm install ngx-clipboard --save
33 | ```
34 |
35 | Open your module file e.g `app.module.ts` and update **imports** array
36 |
37 | ```ts
38 | import { ClipboardModule } from 'ngx-clipboard';
39 | ...
40 | imports: [
41 | ...
42 | ClipboardModule,
43 | ...
44 | ]
45 | ```
46 |
47 | ## Usage
48 |
49 | If you use SystemJS to load your files, you might have to update your config:
50 |
51 | ```js
52 | System.config({
53 | map: {
54 | 'ngx-clipboard': 'node_modules/ngx-clipboard'
55 | }
56 | });
57 | ```
58 |
59 | ### Copy source
60 |
61 | This library support multiple kinds of copy source.
62 |
63 | - Setting `cbContent` attribute
64 |
65 | ```html
66 |
67 | ```
68 |
69 | You can assign the parent **container** to avoid focus trapper issue, #145
70 |
71 | ```html
72 |
73 |
74 |
75 | ```
76 |
77 | - Setting an input target
78 |
79 | ```html
80 | ....
81 |
82 |
83 |
84 |
85 | ```
86 |
87 | - Using `copy` from `ClipboardService` to copy any text you dynamically created.
88 |
89 | ```ts
90 | import { ClipboardService } from 'ngx-clipboard'
91 |
92 | ...
93 |
94 | constructor(private _clipboardService: ClipboardService){
95 | ...
96 | }
97 |
98 | copy(text: string){
99 | this._clipboardService.copy(text)
100 | }
101 | ```
102 |
103 | ### Callbacks
104 |
105 | - `cbOnSuccess` callback attribute is triggered after copy was successful with `$event: {isSuccess: true, content: string}`
106 |
107 | ```html
108 |
109 | ```
110 |
111 | Or updating parameters directly like so
112 |
113 | ```html
114 |
115 | ```
116 |
117 | - `cbOnError` callback attribute is triggered when there's failure in copying with `$event:{isSuccess: false}`
118 |
119 | ### Conditionally render host
120 |
121 | You can also use the structural directive \*ngxClipboardIfSupported to conditionally render the host element
122 |
123 | ```html
124 |
127 | ```
128 |
129 | _Special thanks to @surajpoddar16 for implementing this feature_
130 |
131 | ### Handle copy response globally
132 |
133 | To handle copy response globally, you can subscribe to `copyResponse$` exposed by the `ClipboardService`
134 |
135 | ```
136 | export class ClipboardResponseService {
137 | constructor(
138 | private _clipboardService: ClipboardService,
139 | private _toasterService: ToasterService
140 | ) {
141 | this.handleClipboardResponse();
142 | }
143 |
144 | handleClipboardResponse() {
145 | this._clipboardService.copyResponse$.subscribe((res: IClipboardResponse) => {
146 | if (res.isSuccess) {
147 | this._toasterService.pop('success', undefined, res.successMessage);
148 | }
149 | });
150 | }
151 | }
152 | ```
153 |
154 | _Special thanks to @surajpoddar16 for implementing this feature_
155 |
156 | ### Clean up temporary textarea used by this module after each copy to clipboard
157 |
158 | This library creates a textarea element at the root of the body for its internal use. By default it only destroys it when the directive is destroyed. If you'd like it to be destroyed after each copy to clipboard, provide root level module configuration like this:
159 |
160 | ```ts
161 | ClipboardService.configure({ cleanUpAfterCopy: true });
162 | ```
163 |
164 | Special thanks to [@DmitryEfimenko](https://github.com/DmitryEfimenko) for implementing this feature
165 |
166 | ## Example
167 |
168 | [stackblitz.com](https://stackblitz.com/github/maxisam/ngx-clipboard)
169 |
170 | ## Build project
171 |
172 | ```cmd
173 | npm i && npm run build
174 | ```
175 |
176 | To run demo code locally
177 |
178 | `npm run start`
179 |
180 | ## Contributing
181 |
182 | - Your commits conform to the conventions established [here](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md)
183 |
184 | ## Troubleshooting
185 |
186 | Please ask your general questions at https://stackoverflow.com/questions/tagged/ngx-clipboard
187 |
188 | ## Shoutouts 🙏
189 |
190 | Kudos to
191 |
192 | [Thierry Templier](http://stackoverflow.com/a/36330518/667767) This project is inspired by his answer on StackOverflow.
193 |
194 | The core function is ported from [clipboard.js](http://zenorocha.github.io/clipboard.js/) by [@zenorocha](https://twitter.com/zenorocha).
195 |
196 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.
197 |
198 |
199 |
200 | Big thanks to [BrowserStack](https://www.browserstack.com) for letting the maintainers use their service to debug browser issues.
201 |
202 | ## Contributors
203 |
204 | ### Code Contributors
205 |
206 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
207 |
208 |
209 | ### Financial Contributors
210 |
211 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/ngx-clipboard/contribute)]
212 |
213 | #### Individuals
214 |
215 |
216 |
217 | #### Organizations
218 |
219 | Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/ngx-clipboard/contribute)]
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "library-host": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {},
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/library-host",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": "src/polyfills.ts",
20 | "tsConfig": "tsconfig.app.json",
21 | "assets": ["src/favicon.ico", "src/assets"],
22 | "styles": ["src/styles.css"],
23 | "scripts": [],
24 | "vendorChunk": true,
25 | "extractLicenses": false,
26 | "buildOptimizer": false,
27 | "sourceMap": true,
28 | "optimization": false,
29 | "namedChunks": true
30 | },
31 | "configurations": {
32 | "production": {
33 | "budgets": [
34 | {
35 | "type": "anyComponentStyle",
36 | "maximumWarning": "6kb"
37 | }
38 | ],
39 | "fileReplacements": [
40 | {
41 | "replace": "src/environments/environment.ts",
42 | "with": "src/environments/environment.prod.ts"
43 | }
44 | ],
45 | "optimization": true,
46 | "outputHashing": "all",
47 | "sourceMap": false,
48 | "namedChunks": false,
49 | "extractLicenses": true,
50 | "vendorChunk": false,
51 | "buildOptimizer": true
52 | }
53 | },
54 | "defaultConfiguration": ""
55 | },
56 | "serve": {
57 | "builder": "@angular-devkit/build-angular:dev-server",
58 | "options": {
59 | "browserTarget": "library-host:build"
60 | },
61 | "configurations": {
62 | "production": {
63 | "browserTarget": "library-host:build:production"
64 | }
65 | }
66 | },
67 | "extract-i18n": {
68 | "builder": "@angular-devkit/build-angular:extract-i18n",
69 | "options": {
70 | "browserTarget": "library-host:build"
71 | }
72 | },
73 | "test": {
74 | "builder": "@angular-devkit/build-angular:karma",
75 | "options": {
76 | "main": "src/test.ts",
77 | "polyfills": "src/polyfills.ts",
78 | "tsConfig": "tsconfig.spec.json",
79 | "karmaConfig": "src/karma.conf.js",
80 | "styles": ["src/styles.css"],
81 | "scripts": [],
82 | "assets": ["src/favicon.ico", "src/assets"]
83 | }
84 | },
85 | "lint": {
86 | "builder": "@angular-eslint/builder:lint",
87 | "options": {
88 | "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
89 | }
90 | }
91 | }
92 | },
93 | "library-host-e2e": {
94 | "root": "e2e/",
95 | "projectType": "application",
96 | "architect": {
97 | "e2e": {
98 | "builder": "@angular-devkit/build-angular:protractor",
99 | "options": {
100 | "protractorConfig": "e2e/protractor.conf.js",
101 | "devServerTarget": "library-host:serve"
102 | }
103 | }
104 | }
105 | },
106 | "ngx-clipboard": {
107 | "root": "projects/ngx-clipboard",
108 | "sourceRoot": "projects/ngx-clipboard/src",
109 | "projectType": "library",
110 | "prefix": "ngx",
111 | "architect": {
112 | "build": {
113 | "builder": "@angular-devkit/build-angular:ng-packagr",
114 | "options": {
115 | "tsConfig": "projects/ngx-clipboard/tsconfig.lib.json",
116 | "project": "projects/ngx-clipboard/ng-package.json"
117 | },
118 | "configurations": {
119 | "production": {
120 | "project": "projects/ngx-clipboard/ng-package.prod.json",
121 | "tsConfig": "projects/ngx-clipboard/tsconfig.lib.prod.json"
122 | }
123 | }
124 | },
125 | "test": {
126 | "builder": "@angular-devkit/build-angular:karma",
127 | "options": {
128 | "main": "projects/ngx-clipboard/src/test.ts",
129 | "tsConfig": "projects/ngx-clipboard/tsconfig.spec.json",
130 | "karmaConfig": "projects/ngx-clipboard/karma.conf.js"
131 | },
132 | "configurations": {
133 | "ci": {
134 | "watch": false,
135 | "progress": false,
136 | "browsers": "ChromeHeadlessCI"
137 | }
138 | }
139 | },
140 | "lint": {
141 | "builder": "@angular-eslint/builder:lint",
142 | "options": {
143 | "lintFilePatterns": ["projects/ngx-clipboard/**/*.ts", "projects/ngx-clipboard/**/*.html"]
144 | }
145 | }
146 | }
147 | }
148 | },
149 | "cli": {
150 | "packageManager": "yarn",
151 | "analytics": "26cce57c-232f-4bfb-ac75-bfc72a238bd7",
152 | "schematicCollections": ["@angular-eslint/schematics"]
153 | },
154 | "schematics": {
155 | "@angular-eslint/schematics:application": {
156 | "setParserOptionsProject": true
157 | },
158 | "@angular-eslint/schematics:library": {
159 | "setParserOptionsProject": true
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: ['./src/**/*.e2e-spec.ts'],
9 | capabilities: {
10 | browserName: 'chrome'
11 | },
12 | directConnect: true,
13 | baseUrl: 'http://localhost:4200/',
14 | framework: 'jasmine',
15 | jasmineNodeOpts: {
16 | showColors: true,
17 | defaultTimeoutInterval: 30000,
18 | print: function () {}
19 | },
20 | onPrepare() {
21 | require('ts-node').register({
22 | project: require('path').join(__dirname, './tsconfig.e2e.json')
23 | });
24 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('workspace-project App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to app!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/src/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "types": ["jasmine", "jasminewd2", "node"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "library-host",
3 | "version": "0.0.0",
4 | "engines": {
5 | "node": ">=14.20.0"
6 | },
7 | "scripts": {
8 | "prepare": "husky install",
9 | "ng": "ng",
10 | "prettier": "prettier --write \"**/*.{json,md,ts,html,component.html}\"",
11 | "start": "ng serve",
12 | "build": "ng build ngx-clipboard --configuration production && yarn build-copy",
13 | "tslint-check": "tslint-config-prettier-check ./tslint.json",
14 | "test": "ng test ngx-clipboard",
15 | "test:watch": "ng test ngx-clipboard --watch",
16 | "lint": "ng lint ngx-clipboard",
17 | "publish:lib": "yarn publish ./dist/lib",
18 | "publish:lib:next": "yarn publish ./dist/lib --tag next",
19 | "build-copy": "cpx \"./README.md\" \"./dist/lib\"",
20 | "e2e": "ng e2e"
21 | },
22 | "private": true,
23 | "dependencies": {
24 | "@angular/animations": "^15.1.4",
25 | "@angular/common": "^15.1.4",
26 | "@angular/compiler": "^15.1.4",
27 | "@angular/core": "^15.1.4",
28 | "@angular/forms": "^15.1.4",
29 | "@angular/platform-browser": "^15.1.4",
30 | "@angular/platform-browser-dynamic": "^15.1.4",
31 | "@angular/router": "^15.1.4",
32 | "ngx-clipboard": "15.0.0",
33 | "ngx-window-token": "7.0.0",
34 | "rxjs": "~7.4.0",
35 | "tslib": "^2.3.1",
36 | "zone.js": "~0.11.4"
37 | },
38 | "devDependencies": {
39 | "@angular-devkit/build-angular": "^15.1.5",
40 | "@angular-eslint/builder": "15.2.1",
41 | "@angular-eslint/eslint-plugin": "15.2.1",
42 | "@angular-eslint/eslint-plugin-template": "15.2.1",
43 | "@angular-eslint/schematics": "15.2.1",
44 | "@angular-eslint/template-parser": "15.2.1",
45 | "@angular/cli": "^15.1.5",
46 | "@angular/compiler-cli": "^15.1.4",
47 | "@angular/language-service": "^15.1.4",
48 | "@types/jasmine": "~3.10.2",
49 | "@types/jasminewd2": "~2.0.10",
50 | "@types/node": "^16.11.7",
51 | "@typescript-eslint/eslint-plugin": "^5.43.0",
52 | "@typescript-eslint/parser": "^5.43.0",
53 | "cpx": "^1.5.0",
54 | "eslint": "^8.28.0",
55 | "eslint-config-prettier": "^8.3.0",
56 | "eslint-plugin-prettier": "^4.0.0",
57 | "husky": "8.0.3",
58 | "jasmine-core": "~3.10.1",
59 | "jasmine-spec-reporter": "~7.0.0",
60 | "karma": "~6.3.9",
61 | "karma-chrome-launcher": "~3.1.0",
62 | "karma-coverage-istanbul-reporter": "~3.0.3",
63 | "karma-jasmine": "~4.0.1",
64 | "karma-jasmine-html-reporter": "^1.7.0",
65 | "ng-packagr": "^15.1.2",
66 | "lint-staged": "13.1.1",
67 | "prettier": "2.4.1",
68 | "protractor": "~7.0.0",
69 | "ts-node": "~10.4.0",
70 | "typescript": "~4.8.4"
71 | },
72 | "lint-staged": {
73 | "*.{json,md,ts,html,component.html}": [
74 | "prettier --write"
75 | ]
76 | },
77 | "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
78 | }
79 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc.json",
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "parserOptions": {
8 | "project": ["projects/ngx-clipboard/tsconfig.lib.json", "projects/ngx-clipboard/tsconfig.spec.json"],
9 | "createDefaultProgram": true
10 | },
11 | "rules": {
12 | "@angular-eslint/directive-selector": [
13 | "error",
14 | {
15 | "type": "attribute",
16 | "prefix": "ngx",
17 | "style": "camelCase"
18 | }
19 | ],
20 | "@angular-eslint/component-selector": [
21 | "error",
22 | {
23 | "type": "element",
24 | "prefix": "ngx",
25 | "style": "kebab-case"
26 | }
27 | ]
28 | }
29 | },
30 | {
31 | "files": ["*.html"],
32 | "rules": {}
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage'),
20 | reports: ['html', 'lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | customLaunchers: {
30 | ChromeHeadlessCI: {
31 | base: 'ChromeHeadless',
32 | flags: ['--no-sandbox', '--disable-gpu']
33 | }
34 | },
35 | singleRun: true
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/lib",
4 | "deleteDestPath": false,
5 | "allowedNonPeerDependencies": ["ngx-window-token"],
6 | "lib": {
7 | "entryFile": "src/public_api.ts"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/ng-package.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/lib",
4 | "allowedNonPeerDependencies": ["ngx-window-token"],
5 | "lib": {
6 | "entryFile": "src/public_api.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-clipboard",
3 | "description": "angular 2 clipboard",
4 | "version": "16.1.1",
5 | "author": {
6 | "name": "Sam Lin",
7 | "email": "maxisam@gmail.com"
8 | },
9 | "homepage": "https://github.com/maxisam/ngx-clipboard",
10 | "license": "MIT",
11 | "publishConfig": {
12 | "registry": "https://registry.npmjs.org/",
13 | "access": "public"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/maxisam/ngx-clipboard"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/maxisam/ngx-clipboard/issues"
21 | },
22 | "keywords": [
23 | "angular",
24 | "clipboard",
25 | "copy"
26 | ],
27 | "dependencies": {
28 | "ngx-window-token": ">=7.0.0",
29 | "tslib": "^2.0.0"
30 | },
31 | "peerDependencies": {
32 | "@angular/common": ">=13.0.0",
33 | "@angular/core": ">=13.0.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/lib/interface.ts:
--------------------------------------------------------------------------------
1 | export interface IClipboardResponse {
2 | isSuccess: boolean;
3 | content?: string;
4 | event?: Event;
5 | successMessage?: string;
6 | }
7 |
8 | export interface ClipboardParams {
9 | cleanUpAfterCopy?: boolean;
10 | }
11 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/lib/ngx-clipboard-if-supported.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3 | import { By } from '@angular/platform-browser';
4 | import { ClipboardModule } from './ngx-clipboard.module';
5 | import { ClipboardService } from './ngx-clipboard.service';
6 |
7 | @Component({
8 | // eslint-disable-next-line @angular-eslint/component-selector
9 | selector: 'test-cmp',
10 | template: ` Copy Foo Bar `
11 | })
12 | class TestComponent {}
13 |
14 | function createTestComponent(): ComponentFixture {
15 | return TestBed.createComponent(TestComponent);
16 | }
17 |
18 | describe('ngxClipboardIfSupported directive', () => {
19 | let fixture: ComponentFixture;
20 | let clipboardService: ClipboardService;
21 | let spy: jasmine.Spy;
22 |
23 | beforeEach(() => {
24 | TestBed.configureTestingModule({
25 | declarations: [TestComponent],
26 | imports: [ClipboardModule]
27 | });
28 |
29 | clipboardService = TestBed.get(ClipboardService);
30 | fixture = createTestComponent();
31 | spy = spyOnProperty(clipboardService, 'isSupported', 'get');
32 | });
33 |
34 | it(
35 | 'should not render host when copy is not supported',
36 | waitForAsync(() => {
37 | spy.and.returnValue(false);
38 | fixture.detectChanges();
39 | expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
40 | })
41 | );
42 |
43 | it(
44 | 'should render host when copy is supported',
45 | waitForAsync(() => {
46 | spy.and.returnValue(true);
47 | fixture.detectChanges();
48 | expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
49 | })
50 | );
51 | });
52 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/lib/ngx-clipboard-if-supported.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, OnInit, TemplateRef, ViewContainerRef } from '@angular/core';
2 |
3 | import { ClipboardService } from './ngx-clipboard.service';
4 |
5 | @Directive({
6 | selector: '[ngxClipboardIfSupported]'
7 | })
8 | export class ClipboardIfSupportedDirective implements OnInit {
9 | constructor(
10 | private _clipboardService: ClipboardService,
11 | private _viewContainerRef: ViewContainerRef,
12 | private _templateRef: TemplateRef
13 | ) {}
14 |
15 | ngOnInit() {
16 | if (this._clipboardService.isSupported) {
17 | this._viewContainerRef.createEmbeddedView(this._templateRef);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/lib/ngx-clipboard.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { DOCUMENT } from '@angular/common';
2 | import { Component } from '@angular/core';
3 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
4 | import { FormsModule } from '@angular/forms';
5 | import { BrowserModule } from '@angular/platform-browser';
6 |
7 | import { IClipboardResponse } from './interface';
8 | import { ClipboardModule } from './ngx-clipboard.module';
9 | import { ClipboardService } from './ngx-clipboard.service';
10 |
11 | /*
12 | * Shell component with property 'text' that will be used with our tests
13 | */
14 | @Component({
15 | // eslint-disable-next-line @angular-eslint/component-selector
16 | selector: 'test-clipboard',
17 | template: ` PlaceHolder HTML to be Replaced `
18 | })
19 | export class TestClipboardComponent {
20 | public text = 'text';
21 | public isCopied: boolean;
22 | public copySuccessMsg = 'copySuccessMsg';
23 | }
24 |
25 | /**
26 | * Helper function to easily build a component Fixture using the specified template
27 | * From: https://blog.thoughtram.io/angular/2016/12/27/angular-2-advance-testing-with-custom-matchers.html
28 | */
29 | function createTestComponent(template: string): ComponentFixture {
30 | return TestBed.overrideComponent(TestClipboardComponent, {
31 | set: { template }
32 | }).createComponent(TestClipboardComponent);
33 | }
34 |
35 | describe('Directive: clipboard', () => {
36 | describe('GIVEN: no root configuration', () => {
37 | beforeEach(() => {
38 | TestBed.configureTestingModule({
39 | declarations: [TestClipboardComponent],
40 | imports: [BrowserModule, ClipboardModule, FormsModule],
41 | providers: [ClipboardService]
42 | });
43 | });
44 |
45 | describe('copy when cbContent is set', () => {
46 | let template: string;
47 | let fixture: ComponentFixture;
48 | let clipboardService: ClipboardService;
49 | let spy: jasmine.Spy;
50 | let button: HTMLButtonElement;
51 | beforeEach(() => {
52 | template = ``;
53 | fixture = createTestComponent(template);
54 | clipboardService = fixture.debugElement.injector.get(ClipboardService);
55 | // Setup spy on the `copyText` method, somehow document.execCommand('copy') doesn't work in Karma
56 | spy = spyOn(clipboardService, 'copyText' as keyof ClipboardService);
57 | fixture.detectChanges();
58 | button = fixture.debugElement.nativeElement.querySelector('button');
59 | });
60 |
61 | it(
62 | 'should fire cbOnError if environment does not support copy',
63 | waitForAsync(() => {
64 | spy = spyOnProperty(clipboardService, 'isSupported', 'get').and.returnValue(false);
65 | button.click();
66 | fixture.whenStable().then(() => {
67 | expect(fixture.componentInstance.isCopied).toBeFalsy();
68 | });
69 | })
70 | );
71 |
72 | it(
73 | 'should fire cbOnSuccess after copy successfully',
74 | waitForAsync(() => {
75 | spy.and.returnValue(true);
76 | button.click();
77 | fixture.whenStable().then(() => {
78 | expect(fixture.componentInstance.isCopied).toBeTruthy();
79 | });
80 | })
81 | );
82 |
83 | it(
84 | 'should fire cbOnError after copy fail',
85 | waitForAsync(() => {
86 | button.click();
87 | fixture.whenStable().then(() => {
88 | expect(fixture.componentInstance.isCopied).toBeFalsy();
89 | });
90 | })
91 | );
92 |
93 | it(
94 | 'should create a textarea in dom, and remove it after calling destroy',
95 | waitForAsync(() => {
96 | const doc = fixture.debugElement.injector.get(DOCUMENT);
97 | expect(doc.querySelector('textarea')).toBeFalsy();
98 | button.click();
99 | fixture.whenStable().then(() => {
100 | expect(doc.querySelector('textarea')).toBeTruthy();
101 | clipboardService.destroy(doc.body);
102 | expect(doc.querySelector('textarea')).toBeFalsy();
103 | });
104 | })
105 | );
106 |
107 | it(
108 | 'given the configuration it should clean up temp textarea after copying automatically',
109 | waitForAsync(() => {
110 | const doc = fixture.debugElement.injector.get(DOCUMENT);
111 | clipboardService.configure({ cleanUpAfterCopy: true });
112 | clipboardService.copyFromContent('test content');
113 | fixture.whenStable().then(() => {
114 | const ta = doc.querySelector('textarea');
115 | expect(ta).toBeFalsy();
116 | });
117 | })
118 | );
119 |
120 | it(
121 | 'should push copy response to copySubject',
122 | waitForAsync(() => {
123 | spy.and.returnValue(true);
124 | const component = fixture.componentInstance;
125 | clipboardService.copyResponse$.subscribe((res: IClipboardResponse) => {
126 | expect(res).toBeDefined();
127 | expect(res.isSuccess).toEqual(true);
128 | expect(res.content).toEqual(component.text);
129 | expect(res.successMessage).toEqual(component.copySuccessMsg);
130 | expect(res.event).toBeDefined();
131 | });
132 | button.click();
133 | })
134 | );
135 | });
136 |
137 | describe('copy when cbOnSuccess is not set', () => {
138 | let template: string;
139 | let fixture: ComponentFixture;
140 | let clipboardService: ClipboardService;
141 | let spy: jasmine.Spy;
142 | let button: HTMLButtonElement;
143 | beforeEach(() => {
144 | template = ``;
145 | fixture = createTestComponent(template);
146 | clipboardService = fixture.debugElement.injector.get(ClipboardService);
147 | // Setup spy on the `copyText` method, somehow document.execCommand('copy') doesn't work in Karma
148 | spy = spyOn(clipboardService, 'copyText' as keyof ClipboardService);
149 | fixture.detectChanges();
150 | button = fixture.debugElement.nativeElement.querySelector('button');
151 | });
152 |
153 | it(
154 | 'should not fire cbOnSuccess after copy successfully',
155 | waitForAsync(() => {
156 | spy.and.returnValue(true);
157 | button.click();
158 | fixture.whenStable().then(() => {
159 | expect(fixture.componentInstance.isCopied).toBeFalsy();
160 | });
161 | })
162 | );
163 |
164 | it(
165 | 'should push copy response to copySubject',
166 | waitForAsync(() => {
167 | spy.and.returnValue(true);
168 | const component = fixture.componentInstance;
169 | clipboardService.copyResponse$.subscribe((res: IClipboardResponse) => {
170 | expect(res).toBeDefined();
171 | expect(res.isSuccess).toEqual(true);
172 | expect(res.content).toEqual(component.text);
173 | expect(res.successMessage).toEqual(component.copySuccessMsg);
174 | expect(res.event).toBeDefined();
175 | });
176 | button.click();
177 | })
178 | );
179 | });
180 |
181 | describe('copy when cbContent and container is set', () => {
182 | let template: string;
183 | let fixture: ComponentFixture;
184 | let clipboardService: ClipboardService;
185 | let spy: jasmine.Spy;
186 | let button: HTMLButtonElement;
187 | beforeEach(() => {
188 | template = ``;
189 | fixture = createTestComponent(template);
190 | clipboardService = fixture.debugElement.injector.get(ClipboardService);
191 | // Setup spy on the `copyText` method, somehow document.execCommand('copy') doesn't work in Karma
192 | spy = spyOn(clipboardService, 'copyText' as keyof ClipboardService);
193 | fixture.detectChanges();
194 | button = fixture.debugElement.nativeElement.querySelector('button');
195 | });
196 |
197 | it(
198 | 'should create a textarea in dom, and remove it after calling destroy',
199 | waitForAsync(() => {
200 | const doc = fixture.debugElement.injector.get(DOCUMENT);
201 | expect(doc.querySelector('textarea')).toBeFalsy();
202 | button.click();
203 | fixture.whenStable().then(() => {
204 | const ta = doc.querySelector('textarea');
205 | expect(ta).toBeTruthy();
206 | expect(ta!.parentElement!.className).toBe('container');
207 | clipboardService.destroy(ta!.parentElement!);
208 | expect(doc.querySelector('textarea')).toBeFalsy();
209 | });
210 | })
211 | );
212 | });
213 |
214 | describe('copy when using copyFromContent directly', () => {
215 | let template: string;
216 | let fixture: ComponentFixture;
217 | let clipboardService: ClipboardService;
218 | let spy: jasmine.Spy;
219 | let button: HTMLButtonElement;
220 | beforeEach(() => {
221 | template = ``;
222 | fixture = createTestComponent(template);
223 | clipboardService = fixture.debugElement.injector.get(ClipboardService);
224 | // Setup spy on the `copyText` method, somehow document.execCommand('copy') doesn't work in Karma
225 | spy = spyOn(clipboardService, 'copyText' as keyof ClipboardService);
226 | fixture.detectChanges();
227 | button = fixture.debugElement.nativeElement.querySelector('button');
228 | });
229 |
230 | it(
231 | 'should create a textarea in dom with parent as body, and remove it after calling destroy',
232 | waitForAsync(() => {
233 | const doc = fixture.debugElement.injector.get(DOCUMENT);
234 | expect(doc.querySelector('textarea')).toBeFalsy();
235 | clipboardService.copyFromContent('test content');
236 | fixture.whenStable().then(() => {
237 | const ta = doc.querySelector('textarea');
238 | expect(ta).toBeTruthy();
239 | expect(ta!.parentElement!.nodeName).toBe('BODY');
240 | clipboardService.destroy(ta!.parentElement!);
241 | expect(doc.querySelector('textarea')).toBeFalsy();
242 | });
243 | })
244 | );
245 | });
246 |
247 | describe('copy when target is set', () => {
248 | let template: string;
249 | let fixture: ComponentFixture;
250 | let clipboardService: ClipboardService;
251 | let spy: jasmine.Spy;
252 | let button: HTMLButtonElement;
253 | let input: HTMLInputElement;
254 | beforeEach(() => {
255 | template = `
256 | `;
257 | fixture = createTestComponent(template);
258 | clipboardService = fixture.debugElement.injector.get(ClipboardService);
259 | // Setup spy on the `copyText` method, somehow document.execCommand('copy') doesn't work in Karma
260 | spy = spyOn(clipboardService, 'copyText' as keyof ClipboardService);
261 | fixture.detectChanges();
262 | button = fixture.debugElement.nativeElement.querySelector('button');
263 | input = fixture.debugElement.nativeElement.querySelector('input');
264 | // input 'new test'
265 | input.value = 'new test';
266 | input.dispatchEvent(new Event('input'));
267 | });
268 |
269 | it(
270 | 'should fire cbOnSuccess after copy successfully',
271 | waitForAsync(() => {
272 | spy.and.returnValue(true);
273 | fixture.detectChanges();
274 | // button click to trigger copy
275 | button.click();
276 | fixture.whenStable().then(() => {
277 | expect(fixture.componentInstance.isCopied).toBeTruthy();
278 | });
279 | })
280 | );
281 |
282 | it(
283 | 'should fire cbOnError if environment does not support copy',
284 | waitForAsync(() => {
285 | spy = spyOnProperty(clipboardService, 'isSupported', 'get').and.returnValue(false);
286 | button.click();
287 | fixture.whenStable().then(() => {
288 | expect(fixture.componentInstance.isCopied).toBeFalsy();
289 | });
290 | })
291 | );
292 |
293 | it(
294 | 'should fire cbOnError after copy fail',
295 | waitForAsync(() => {
296 | spy.and.returnValue(false);
297 | fixture.detectChanges();
298 | // button click to trigger copy
299 | button.click();
300 | fixture.whenStable().then(() => {
301 | expect(fixture.componentInstance.isCopied).toBeFalsy();
302 | });
303 | })
304 | );
305 | });
306 | });
307 | });
308 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/lib/ngx-clipboard.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | ElementRef,
4 | EventEmitter,
5 | Input,
6 | NgZone,
7 | OnDestroy,
8 | OnInit,
9 | Output,
10 | Renderer2
11 | } from '@angular/core';
12 | import { IClipboardResponse } from './interface';
13 | import { ClipboardService } from './ngx-clipboard.service';
14 |
15 | @Directive({ selector: '[ngxClipboard]' })
16 | export class ClipboardDirective implements OnInit, OnDestroy {
17 | // https://github.com/maxisam/ngx-clipboard/issues/239#issuecomment-623330624
18 | // eslint-disable-next-line @angular-eslint/no-input-rename
19 | @Input('ngxClipboard')
20 | public targetElm: HTMLInputElement | HTMLTextAreaElement | undefined | '';
21 | @Input()
22 | public container: HTMLElement;
23 |
24 | @Input()
25 | public cbContent: string | undefined;
26 |
27 | @Input()
28 | public cbSuccessMsg: string;
29 |
30 | @Output()
31 | public cbOnSuccess: EventEmitter = new EventEmitter();
32 |
33 | @Output()
34 | public cbOnError: EventEmitter = new EventEmitter();
35 |
36 | private clickListener: () => void;
37 |
38 | constructor(
39 | private ngZone: NgZone,
40 | private host: ElementRef,
41 | private renderer: Renderer2,
42 | private clipboardSrv: ClipboardService
43 | ) {}
44 |
45 | // eslint-disable-next-line no-empty, @typescript-eslint/no-empty-function
46 | public ngOnInit() {
47 | this.ngZone.runOutsideAngular(() => {
48 | // By default each host listener schedules change detection and also wrapped
49 | // into additional function that calls `markForCheck()`. We're listening the `click`
50 | // event in the context of the root zone to avoid running unnecessary change detections,
51 | // since this directive doesn't do anything template-related (e.g. updates template variables).
52 | this.clickListener = this.renderer.listen(this.host.nativeElement, 'click', this.onClick);
53 | });
54 | }
55 |
56 | public ngOnDestroy() {
57 | if (this.clickListener) {
58 | this.clickListener();
59 | }
60 | this.clipboardSrv.destroy(this.container);
61 | }
62 |
63 | private onClick = (event: MouseEvent): void => {
64 | if (!this.clipboardSrv.isSupported) {
65 | this.handleResult(false, undefined, event);
66 | } else if (this.targetElm && this.clipboardSrv.isTargetValid(this.targetElm)) {
67 | this.handleResult(this.clipboardSrv.copyFromInputElement(this.targetElm), this.targetElm.value, event);
68 | } else if (this.cbContent) {
69 | this.handleResult(this.clipboardSrv.copyFromContent(this.cbContent, this.container), this.cbContent, event);
70 | }
71 | };
72 |
73 | /**
74 | * Fires an event based on the copy operation result.
75 | * @param succeeded
76 | */
77 | private handleResult(succeeded: boolean, copiedContent: string | undefined, event: MouseEvent): void {
78 | let response: IClipboardResponse = {
79 | isSuccess: succeeded,
80 | content: copiedContent,
81 | successMessage: this.cbSuccessMsg,
82 | event
83 | };
84 |
85 | if (succeeded) {
86 | if (this.cbOnSuccess.observed) {
87 | this.ngZone.run(() => {
88 | this.cbOnSuccess.emit(response);
89 | });
90 | }
91 | } else {
92 | if (this.cbOnError.observed) {
93 | this.ngZone.run(() => {
94 | this.cbOnError.emit(response);
95 | });
96 | }
97 | }
98 |
99 | this.clipboardSrv.pushCopyResponse(response);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/lib/ngx-clipboard.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 |
4 | import { ClipboardIfSupportedDirective } from './ngx-clipboard-if-supported.directive';
5 | import { ClipboardDirective } from './ngx-clipboard.directive';
6 |
7 | @NgModule({
8 | imports: [CommonModule],
9 | declarations: [ClipboardDirective, ClipboardIfSupportedDirective],
10 | exports: [ClipboardDirective, ClipboardIfSupportedDirective]
11 | })
12 | export class ClipboardModule {}
13 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/lib/ngx-clipboard.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { inject, TestBed } from '@angular/core/testing';
2 | import { BrowserModule } from '@angular/platform-browser';
3 |
4 | import { ClipboardService } from './ngx-clipboard.service';
5 |
6 | describe('Service: Clipboard', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | imports: [BrowserModule]
10 | });
11 | });
12 |
13 | it('should service work', inject([ClipboardService], (service: ClipboardService) => {
14 | expect(service).toBeTruthy();
15 | }));
16 |
17 | it('it is supported', inject([ClipboardService], (service: ClipboardService) => {
18 | expect(service.isSupported).toBeTruthy();
19 | }));
20 |
21 | describe('check if input is valid', () => {
22 | let input: HTMLInputElement;
23 | beforeEach(() => {
24 | input = document.createElement('input');
25 | });
26 |
27 | it('input is a valid target', inject([ClipboardService], (service: ClipboardService) => {
28 | expect(service.isTargetValid(input)).toBeTruthy();
29 | }));
30 |
31 | it('input[disabled] is NOT a valid target', inject([ClipboardService], (service: ClipboardService) => {
32 | input.setAttribute('disabled', '');
33 | expect(() => service.isTargetValid(input)).toThrowError(
34 | 'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'
35 | );
36 | }));
37 | });
38 |
39 | describe('check if textarea is valid', () => {
40 | let ta: HTMLTextAreaElement;
41 | beforeEach(() => {
42 | ta = document.createElement('textarea');
43 | });
44 |
45 | it('textarea is a valid target', inject([ClipboardService], (service: ClipboardService) => {
46 | expect(service.isTargetValid(ta)).toBeTruthy();
47 | }));
48 |
49 | it('ta[disabled] is NOT a valid target', inject([ClipboardService], (service: ClipboardService) => {
50 | ta.setAttribute('disabled', '');
51 | expect(() => service.isTargetValid(ta)).toThrowError(
52 | 'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'
53 | );
54 | }));
55 | });
56 |
57 | describe('check if other html element is valid', () => {
58 | let div: any;
59 | it('undefined is NOT a valid target', inject([ClipboardService], (service: ClipboardService) => {
60 | expect(() => service.isTargetValid(div)).toThrowError('Target should be input or textarea');
61 | }));
62 | it('div is NOT a valid target', inject([ClipboardService], (service: ClipboardService) => {
63 | div = document.createElement('div');
64 | expect(() => service.isTargetValid(div)).toThrowError('Target should be input or textarea');
65 | }));
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/lib/ngx-clipboard.service.ts:
--------------------------------------------------------------------------------
1 | import { DOCUMENT } from '@angular/common';
2 | import { Inject, Injectable, NgZone, Optional } from '@angular/core';
3 | import { WINDOW } from 'ngx-window-token';
4 | import { Observable, Subject } from 'rxjs';
5 |
6 | import { ClipboardParams, IClipboardResponse } from './interface';
7 |
8 | /**
9 | * The following code is heavily copied from https://github.com/zenorocha/clipboard.js
10 | */
11 | @Injectable({ providedIn: 'root' })
12 | export class ClipboardService {
13 | private copySubject = new Subject();
14 | public copyResponse$: Observable = this.copySubject.asObservable();
15 | private tempTextArea: HTMLTextAreaElement | undefined;
16 | private config: ClipboardParams = {};
17 |
18 | constructor(
19 | private ngZone: NgZone,
20 | @Inject(DOCUMENT) public document: any,
21 | @Optional() @Inject(WINDOW) private window: any
22 | ) {}
23 |
24 | public configure(config: ClipboardParams) {
25 | this.config = config;
26 | }
27 |
28 | public copy(content: string): void {
29 | if (!this.isSupported || !content) {
30 | return this.pushCopyResponse({ isSuccess: false, content });
31 | }
32 | const copyResult = this.copyFromContent(content);
33 | if (copyResult) {
34 | return this.pushCopyResponse({ content, isSuccess: copyResult });
35 | }
36 | return this.pushCopyResponse({ isSuccess: false, content });
37 | }
38 |
39 | public get isSupported(): boolean {
40 | return !!this.document.queryCommandSupported && !!this.document.queryCommandSupported('copy') && !!this.window;
41 | }
42 |
43 | public isTargetValid(element: HTMLInputElement | HTMLTextAreaElement): boolean {
44 | if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
45 | if (element.hasAttribute('disabled')) {
46 | throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
47 | }
48 | return true;
49 | }
50 | throw new Error('Target should be input or textarea');
51 | }
52 |
53 | /**
54 | * Attempts to copy from an input `targetElm`
55 | */
56 | public copyFromInputElement(targetElm: HTMLInputElement | HTMLTextAreaElement, isFocus = true): boolean {
57 | try {
58 | this.selectTarget(targetElm);
59 | const re = this.copyText();
60 | this.clearSelection(isFocus ? targetElm : undefined, this.window);
61 | return re && this.isCopySuccessInIE11();
62 | } catch (error) {
63 | return false;
64 | }
65 | }
66 |
67 | /**
68 | * This is a hack for IE11 to return `true` even if copy fails.
69 | */
70 | public isCopySuccessInIE11(): boolean {
71 | const clipboardData = this.window['clipboardData'];
72 | if (clipboardData && clipboardData.getData) {
73 | if (!clipboardData.getData('Text')) {
74 | return false;
75 | }
76 | }
77 | return true;
78 | }
79 |
80 | /**
81 | * Creates a fake textarea element, sets its value from `text` property,
82 | * and makes a selection on it.
83 | */
84 | public copyFromContent(content: string, container: HTMLElement = this.document.body): boolean {
85 | // check if the temp textarea still belongs to the current container.
86 | // In case we have multiple places using ngx-clipboard, one is in a modal using container but the other one is not.
87 | if (this.tempTextArea && !container.contains(this.tempTextArea)) {
88 | this.destroy(this.tempTextArea.parentElement || undefined);
89 | }
90 |
91 | if (!this.tempTextArea) {
92 | this.tempTextArea = this.createTempTextArea(this.document, this.window);
93 | try {
94 | container.appendChild(this.tempTextArea);
95 | } catch (error) {
96 | throw new Error('Container should be a Dom element');
97 | }
98 | }
99 | this.tempTextArea.value = content;
100 |
101 | const toReturn = this.copyFromInputElement(this.tempTextArea, false);
102 | if (this.config.cleanUpAfterCopy) {
103 | this.destroy(this.tempTextArea.parentElement || undefined);
104 | }
105 | return toReturn;
106 | }
107 |
108 | /**
109 | * Remove temporary textarea if any exists.
110 | */
111 | public destroy(container: HTMLElement = this.document.body): void {
112 | if (this.tempTextArea) {
113 | container.removeChild(this.tempTextArea);
114 | // removeChild doesn't remove the reference from memory
115 | this.tempTextArea = undefined;
116 | }
117 | }
118 |
119 | /**
120 | * Select the target html input element.
121 | */
122 | private selectTarget(inputElement: HTMLInputElement | HTMLTextAreaElement): number | undefined {
123 | inputElement.select();
124 | inputElement.setSelectionRange(0, inputElement.value.length);
125 | return inputElement.value.length;
126 | }
127 |
128 | private copyText(): boolean {
129 | return this.document.execCommand('copy');
130 | }
131 |
132 | /**
133 | * Moves focus away from `target` and back to the trigger, removes current selection.
134 | */
135 | private clearSelection(inputElement: HTMLInputElement | HTMLTextAreaElement | undefined, window: Window): void {
136 | inputElement && inputElement.focus();
137 | window.getSelection()?.removeAllRanges();
138 | }
139 |
140 | /**
141 | * Creates a fake textarea for copy command.
142 | */
143 | private createTempTextArea(doc: Document, window: Window): HTMLTextAreaElement {
144 | const isRTL = doc.documentElement.getAttribute('dir') === 'rtl';
145 | let ta: HTMLTextAreaElement;
146 | ta = doc.createElement('textarea');
147 | // Prevent zooming on iOS
148 | ta.style.fontSize = '12pt';
149 | // Reset box model
150 | ta.style.height = '1px';
151 | ta.style.width = '1px';
152 | ta.style.overflow = 'hidden';
153 | ta.style.clip = 'rect(0 0 0 0);';
154 | ta.style.border = '0';
155 | ta.style.padding = '0';
156 | ta.style.margin = '0';
157 | // Move element out of screen horizontally
158 | ta.style.position = 'absolute';
159 | ta.style[isRTL ? 'right' : 'left'] = '-9999px';
160 | // Move element to the same position vertically
161 | const yPosition = window.pageYOffset || doc.documentElement.scrollTop;
162 | ta.style.top = yPosition + 'px';
163 | ta.setAttribute('readonly', '');
164 | ta.setAttribute('aria-hidden', 'true');
165 |
166 | return ta;
167 | }
168 |
169 | /**
170 | * Pushes copy operation response to copySubject, to provide global access
171 | * to the response.
172 | */
173 | public pushCopyResponse(response: IClipboardResponse): void {
174 | if (this.copySubject.observers.length > 0) {
175 | this.ngZone.run(() => {
176 | this.copySubject.next(response);
177 | });
178 | }
179 | }
180 |
181 | /**
182 | * @deprecated use pushCopyResponse instead.
183 | */
184 | public pushCopyReponse(response: IClipboardResponse): void {
185 | this.pushCopyResponse(response);
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/public_api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of ngx-clipboard
3 | */
4 |
5 | export * from './lib/ngx-clipboard.service';
6 | export * from './lib/ngx-clipboard.directive';
7 | export * from './lib/ngx-clipboard.module';
8 | export * from './lib/ngx-clipboard-if-supported.directive';
9 | export * from './lib/interface';
10 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 | // The order of import matters
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
7 |
8 | // First, initialize the Angular testing environment.
9 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
10 | teardown: { destroyAfterEach: false }
11 | });
12 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "declarationMap": true,
6 | "declaration": true,
7 | "inlineSources": true,
8 | "types": [],
9 | "lib": ["dom", "es2018"]
10 | },
11 | "angularCompilerOptions": {
12 | "skipTemplateCodegen": true,
13 | "strictMetadataEmit": true,
14 | "enableResourceInlining": true
15 | },
16 | "exclude": ["src/test.ts", "**/*.spec.ts"]
17 | }
18 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.lib.json",
3 | "compilerOptions": {
4 | "declarationMap": false
5 | },
6 | "angularCompilerOptions": {
7 | "compilationMode": "partial"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": ["jasmine", "node"]
6 | },
7 | "files": ["src/test.ts"],
8 | "include": ["**/*.spec.ts", "**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/projects/ngx-clipboard/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | ngx-window-token@^1.0.0:
6 | version "1.0.0"
7 | resolved "https://registry.yarnpkg.com/ngx-window-token/-/ngx-window-token-1.0.0.tgz#12acb174fbbcffa5c60b3fea5a6ea78cc3304793"
8 | dependencies:
9 | tslib "^1.9.0"
10 |
11 | tslib@^1.9.0:
12 | version "1.9.1"
13 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.1.tgz#a5d1f0532a49221c87755cfcc89ca37197242ba7"
14 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | font: 400 13.3333px Arial;
3 | }
4 | .row {
5 | margin: 1rem;
6 | }
7 | .input-group {
8 | position: relative;
9 | display: -ms-flexbox;
10 | display: flex;
11 | -ms-flex-wrap: wrap;
12 | flex-wrap: wrap;
13 | -ms-flex-align: stretch;
14 | align-items: stretch;
15 | width: 100%;
16 | }
17 | .form-control {
18 | position: relative;
19 | -ms-flex: 1 1 auto;
20 | flex: 1 1 auto;
21 | width: 1%;
22 | margin-bottom: 0;
23 | display: block;
24 | height: calc(2.25rem + 2px);
25 | padding: 0.375rem 0.75rem;
26 | font-size: 1rem;
27 | line-height: 1.5;
28 | color: #495057;
29 | background-color: #fff;
30 | background-clip: padding-box;
31 | border: 1px solid #ced4da;
32 | border-radius: 0.25rem;
33 | transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
34 | }
35 | .btn {
36 | display: inline-block;
37 | font-weight: 400;
38 | text-align: center;
39 | white-space: nowrap;
40 | vertical-align: middle;
41 | -webkit-user-select: none;
42 | -moz-user-select: none;
43 | -ms-user-select: none;
44 | user-select: none;
45 | border: 1px solid transparent;
46 | padding: 0.375rem 0.75rem;
47 | font-size: 1rem;
48 | line-height: 1.5;
49 | border-radius: 0.25rem;
50 | transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
51 | box-shadow 0.15s ease-in-out;
52 | height: auto;
53 | background-color: #6c757d;
54 | color: #fff;
55 | margin: 0;
56 | }
57 | .btn-success {
58 | background-color: #266900;
59 | }
60 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Copy from text -- text
5 | Click this button, it will copy the text from the input by referring to the
6 |
text content
7 |
8 |
14 |
15 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Copy from text -- target
33 | Click this button, it will copy the text from the input by referring to the
34 |
input element
35 |
36 |
43 |
44 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, waitForAsync } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 | describe('AppComponent', () => {
4 | beforeEach(
5 | waitForAsync(() => {
6 | TestBed.configureTestingModule({
7 | declarations: [AppComponent]
8 | }).compileComponents();
9 | })
10 | );
11 | it(
12 | 'should create the app',
13 | waitForAsync(() => {
14 | const fixture = TestBed.createComponent(AppComponent);
15 | const app = fixture.debugElement.componentInstance;
16 | expect(app).toBeTruthy();
17 | })
18 | );
19 | it(
20 | `should have as title 'app'`,
21 | waitForAsync(() => {
22 | const fixture = TestBed.createComponent(AppComponent);
23 | const app = fixture.debugElement.componentInstance;
24 | expect(app.title).toEqual('app');
25 | })
26 | );
27 | it(
28 | 'should render title in a h1 tag',
29 | waitForAsync(() => {
30 | const fixture = TestBed.createComponent(AppComponent);
31 | fixture.detectChanges();
32 | const compiled = fixture.debugElement.nativeElement;
33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
34 | })
35 | );
36 | });
37 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ClipboardService } from 'ngx-clipboard';
3 |
4 | @Component({
5 | selector: 'app-root',
6 | templateUrl: './app.component.html',
7 | styleUrls: ['./app.component.css']
8 | })
9 | export class AppComponent implements OnInit {
10 | text1: string;
11 | text2: string;
12 | textModal: string;
13 | isCopied1: boolean;
14 | isCopied2: boolean;
15 | isCopied3: boolean;
16 | basic = false;
17 | constructor(private _clipboardService: ClipboardService) {}
18 |
19 | ngOnInit() {
20 | // Handle copy response globally https://github.com/maxisam/ngx-clipboard#handle-copy-response-globally
21 | this._clipboardService.copyResponse$.subscribe(re => {
22 | if (re.isSuccess) {
23 | alert('copy success!');
24 | }
25 | });
26 | }
27 | callServiceToCopy() {
28 | this._clipboardService.copy('This is copy thru service copyFromContent directly');
29 | }
30 |
31 | onCopyFailure() {
32 | alert('copy fail!');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { FormsModule } from '@angular/forms';
3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 | import { ClipboardModule } from 'ngx-clipboard';
5 | import { AppComponent } from './app.component';
6 |
7 | @NgModule({
8 | declarations: [AppComponent],
9 | imports: [BrowserAnimationsModule, FormsModule, ClipboardModule],
10 | providers: [],
11 | bootstrap: [AppComponent]
12 | })
13 | export class AppModule {}
14 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxisam/ngx-clipboard/a50d7440797677fcb25a583eb76d564f0210b8e4/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * In development mode, to ignore zone related error stack frames such as
11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
12 | * import the following file, but please comment it out in production mode
13 | * because it will have performance impact when throw error
14 | */
15 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
16 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxisam/ngx-clipboard/a50d7440797677fcb25a583eb76d564f0210b8e4/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ngxClipboard - AngularCli
6 |
7 |
13 |
14 |
15 |
16 |
17 |
18 | Loading...
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../coverage'),
20 | reports: ['html', 'lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | customLaunchers: {
30 | ChromeHeadlessCI: {
31 | base: 'ChromeHeadless',
32 | flags: ['--no-sandbox', '--disable-gpu']
33 | }
34 | },
35 | singleRun: false
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule)
13 | .catch(err => console.log(err));
14 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 | import 'zone.js';
17 |
18 | /***************************************************************************************************
19 | * BROWSER POLYFILLS
20 | */
21 |
22 | /***************************************************************************************************
23 | * BROWSER POLYFILLS
24 | */
25 |
26 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
27 |
28 | /** IE10 and IE11 requires the following for the Reflect API. */
29 | /** Evergreen browsers require these. **/
30 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
31 |
32 | /**
33 | * By default, zone.js will patch all possible macroTask and DomEvents
34 | * user can disable parts of macroTask/DomEvents patch by setting following flags
35 | */
36 |
37 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
38 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
39 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
40 |
41 | /*
42 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
43 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
44 | */
45 | // (window as any).__Zone_enable_cross_context_check = true;
46 |
47 | /***************************************************************************************************
48 | * Zone JS is required by default for Angular itself.
49 | */
50 | /***************************************************************************************************
51 | * APPLICATION IMPORTS
52 | */
53 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 | // The order of import matters
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
6 |
7 | // First, initialize the Angular testing environment.
8 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
9 | teardown: { destroyAfterEach: false }
10 | });
11 |
--------------------------------------------------------------------------------
/src/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tslint.json",
3 | "rules": {
4 | "directive-selector": [true, "attribute", "app", "camelCase"],
5 | "component-selector": [true, "element", "app", "kebab-case"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/app",
5 | "types": []
6 | },
7 | "files": ["src/main.ts", "src/polyfills.ts"],
8 | "include": ["src/**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "downlevelIteration": true,
9 | "experimentalDecorators": true,
10 | "module": "es2020",
11 | "moduleResolution": "node",
12 | "importHelpers": true,
13 | "strictNullChecks": true,
14 | "target": "ES2022",
15 | "typeRoots": ["node_modules/@types"],
16 | "lib": ["es2018", "dom"],
17 | "paths": {},
18 | "useDefineForClassFields": false
19 | },
20 | "angularCompilerOptions": {
21 | "fullTemplateTypeCheck": true,
22 | "strictInjectionParameters": true,
23 | "strictTemplates": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/spec",
5 | "types": ["jasmine", "node"]
6 | },
7 | "files": ["src/test.ts", "src/polyfills.ts"],
8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------